diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index ece3ee640e2a69a6f8193a4f2672032b87b8097c..43d1b8f794c3060ea0e9bc68c989539bd545288f 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -9,8 +9,8 @@ use crate::html::escape::Escape; use crate::html::render::Context; +use std::collections::VecDeque; use std::fmt::{Display, Write}; -use std::iter::Peekable; use rustc_lexer::{LiteralKind, TokenKind}; use rustc_span::edition::Edition; @@ -201,10 +201,57 @@ fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool) }) } +/// This iterator comes from the same idea than "Peekable" except that it allows to "peek" more than +/// just the next item by using `peek_next`. The `peek` method always returns the next item after +/// the current one whereas `peek_next` will return the next item after the last one peeked. +/// +/// You can use both `peek` and `peek_next` at the same time without problem. +struct PeekIter<'a> { + stored: VecDeque<(TokenKind, &'a str)>, + /// This position is reinitialized when using `next`. It is used in `peek_next`. + peek_pos: usize, + iter: TokenIter<'a>, +} + +impl PeekIter<'a> { + fn new(iter: TokenIter<'a>) -> Self { + Self { stored: VecDeque::new(), peek_pos: 0, iter } + } + /// Returns the next item after the current one. It doesn't interfer with `peek_next` output. + fn peek(&mut self) -> Option<&(TokenKind, &'a str)> { + if self.stored.is_empty() { + if let Some(next) = self.iter.next() { + self.stored.push_back(next); + } + } + self.stored.front() + } + /// Returns the next item after the last one peeked. It doesn't interfer with `peek` output. + fn peek_next(&mut self) -> Option<&(TokenKind, &'a str)> { + self.peek_pos += 1; + if self.peek_pos - 1 < self.stored.len() { + self.stored.get(self.peek_pos - 1) + } else if let Some(next) = self.iter.next() { + self.stored.push_back(next); + self.stored.back() + } else { + None + } + } +} + +impl Iterator for PeekIter<'a> { + type Item = (TokenKind, &'a str); + fn next(&mut self) -> Option { + self.peek_pos = 0; + if let Some(first) = self.stored.pop_front() { Some(first) } else { self.iter.next() } + } +} + /// Processes program tokens, classifying strings of text by highlighting /// category (`Class`). struct Classifier<'a> { - tokens: Peekable>, + tokens: PeekIter<'a>, in_attribute: bool, in_macro: bool, in_macro_nonterminal: bool, @@ -218,7 +265,7 @@ impl<'a> Classifier<'a> { /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code /// file span which will be used later on by the `span_correspondance_map`. fn new(src: &str, edition: Edition, file_span: Span) -> Classifier<'_> { - let tokens = TokenIter { src }.peekable(); + let tokens = PeekIter::new(TokenIter { src }); Classifier { tokens, in_attribute: false, @@ -369,7 +416,7 @@ fn advance( // Assume that '&' or '*' is the reference or dereference operator // or a reference or pointer type. Unless, of course, it looks like // a logical and or a multiplication operator: `&&` or `* `. - TokenKind::Star => match lookahead { + TokenKind::Star => match self.peek() { Some(TokenKind::Whitespace) => Class::Op, _ => Class::RefKeyWord, }, @@ -480,6 +527,9 @@ fn advance( None => match text { "Option" | "Result" => Class::PreludeTy, "Some" | "None" | "Ok" | "Err" => Class::PreludeVal, + // "union" is a weak keyword and is only considered as a keyword when declaring + // a union type. + "union" if self.check_if_is_union_keyword() => Class::KeyWord, _ if self.in_macro_nonterminal => { self.in_macro_nonterminal = false; Class::MacroNonTerminal @@ -500,7 +550,17 @@ fn advance( } fn peek(&mut self) -> Option { - self.tokens.peek().map(|(toke_kind, _text)| *toke_kind) + self.tokens.peek().map(|(token_kind, _text)| *token_kind) + } + + fn check_if_is_union_keyword(&mut self) -> bool { + while let Some(kind) = self.tokens.peek_next().map(|(token_kind, _text)| token_kind) { + if *kind == TokenKind::Whitespace { + continue; + } + return *kind == TokenKind::Ident; + } + false } } diff --git a/src/librustdoc/html/highlight/fixtures/union.html b/src/librustdoc/html/highlight/fixtures/union.html new file mode 100644 index 0000000000000000000000000000000000000000..c0acf31a05d08ff7e6536efb43fb63b114076b65 --- /dev/null +++ b/src/librustdoc/html/highlight/fixtures/union.html @@ -0,0 +1,8 @@ +union Foo { + i: i8, + u: i8, +} + +fn main() { + let union = 0; +} diff --git a/src/librustdoc/html/highlight/fixtures/union.rs b/src/librustdoc/html/highlight/fixtures/union.rs new file mode 100644 index 0000000000000000000000000000000000000000..269ee115d3f8f6ebc2b1e648c632e543ab2c383b --- /dev/null +++ b/src/librustdoc/html/highlight/fixtures/union.rs @@ -0,0 +1,8 @@ +union Foo { + i: i8, + u: i8, +} + +fn main() { + let union = 0; +} diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs index 68592ae96c187b273eebb1c5d76c417b0fa86954..450bbfea1ea863143504f6f0a9ec91b77b5cee80 100644 --- a/src/librustdoc/html/highlight/tests.rs +++ b/src/librustdoc/html/highlight/tests.rs @@ -54,3 +54,13 @@ fn test_keyword_highlight() { expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner()); }); } + +#[test] +fn test_union_highlighting() { + create_default_session_globals_then(|| { + let src = include_str!("fixtures/union.rs"); + let mut html = Buffer::new(); + write_code(&mut html, src, Edition::Edition2018, None); + expect_file!["fixtures/union.html"].assert_eq(&html.into_inner()); + }); +}