Skip to content

Commit 42ea15b

Browse files
authored
Rollup merge of rust-lang#87428 - GuillaumeGomez:union-highlighting, r=notriddle
Fix union keyword highlighting in rustdoc HTML sources I followed this logic: if I find an ident "union", I check if it followed by another ident or not. If it's the case, then I consider this is a keyword because it's declaring a union type. To do so I created a new Iterator which allows to peek the next items without moving the current iterator position. This is part of rust-lang#85016. If the fix makes sense, I'll extend it to other weak keywords (the issue only mentions they exist but https://doc.rust-lang.org/nightly/reference/keywords.html#weak-keywords only talks about `dyn` and `'static` so not sure if there is anything else to be done?). cc `@notriddle` (you're one of the last ones who worked on this part of rustdoc so here you go 😉 ) r? `@jyn514`
2 parents 24a789b + ee38116 commit 42ea15b

File tree

4 files changed

+91
-5
lines changed

4 files changed

+91
-5
lines changed

src/librustdoc/html/highlight.rs

+65-5
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use crate::clean::PrimitiveType;
99
use crate::html::escape::Escape;
1010
use crate::html::render::Context;
1111

12+
use std::collections::VecDeque;
1213
use std::fmt::{Display, Write};
13-
use std::iter::Peekable;
1414

1515
use rustc_lexer::{LiteralKind, TokenKind};
1616
use rustc_span::edition::Edition;
@@ -201,10 +201,57 @@ fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool)
201201
})
202202
}
203203

204+
/// This iterator comes from the same idea than "Peekable" except that it allows to "peek" more than
205+
/// just the next item by using `peek_next`. The `peek` method always returns the next item after
206+
/// the current one whereas `peek_next` will return the next item after the last one peeked.
207+
///
208+
/// You can use both `peek` and `peek_next` at the same time without problem.
209+
struct PeekIter<'a> {
210+
stored: VecDeque<(TokenKind, &'a str)>,
211+
/// This position is reinitialized when using `next`. It is used in `peek_next`.
212+
peek_pos: usize,
213+
iter: TokenIter<'a>,
214+
}
215+
216+
impl PeekIter<'a> {
217+
fn new(iter: TokenIter<'a>) -> Self {
218+
Self { stored: VecDeque::new(), peek_pos: 0, iter }
219+
}
220+
/// Returns the next item after the current one. It doesn't interfer with `peek_next` output.
221+
fn peek(&mut self) -> Option<&(TokenKind, &'a str)> {
222+
if self.stored.is_empty() {
223+
if let Some(next) = self.iter.next() {
224+
self.stored.push_back(next);
225+
}
226+
}
227+
self.stored.front()
228+
}
229+
/// Returns the next item after the last one peeked. It doesn't interfer with `peek` output.
230+
fn peek_next(&mut self) -> Option<&(TokenKind, &'a str)> {
231+
self.peek_pos += 1;
232+
if self.peek_pos - 1 < self.stored.len() {
233+
self.stored.get(self.peek_pos - 1)
234+
} else if let Some(next) = self.iter.next() {
235+
self.stored.push_back(next);
236+
self.stored.back()
237+
} else {
238+
None
239+
}
240+
}
241+
}
242+
243+
impl Iterator for PeekIter<'a> {
244+
type Item = (TokenKind, &'a str);
245+
fn next(&mut self) -> Option<Self::Item> {
246+
self.peek_pos = 0;
247+
if let Some(first) = self.stored.pop_front() { Some(first) } else { self.iter.next() }
248+
}
249+
}
250+
204251
/// Processes program tokens, classifying strings of text by highlighting
205252
/// category (`Class`).
206253
struct Classifier<'a> {
207-
tokens: Peekable<TokenIter<'a>>,
254+
tokens: PeekIter<'a>,
208255
in_attribute: bool,
209256
in_macro: bool,
210257
in_macro_nonterminal: bool,
@@ -218,7 +265,7 @@ impl<'a> Classifier<'a> {
218265
/// Takes as argument the source code to HTML-ify, the rust edition to use and the source code
219266
/// file span which will be used later on by the `span_correspondance_map`.
220267
fn new(src: &str, edition: Edition, file_span: Span) -> Classifier<'_> {
221-
let tokens = TokenIter { src }.peekable();
268+
let tokens = PeekIter::new(TokenIter { src });
222269
Classifier {
223270
tokens,
224271
in_attribute: false,
@@ -369,7 +416,7 @@ impl<'a> Classifier<'a> {
369416
// Assume that '&' or '*' is the reference or dereference operator
370417
// or a reference or pointer type. Unless, of course, it looks like
371418
// a logical and or a multiplication operator: `&&` or `* `.
372-
TokenKind::Star => match lookahead {
419+
TokenKind::Star => match self.peek() {
373420
Some(TokenKind::Whitespace) => Class::Op,
374421
_ => Class::RefKeyWord,
375422
},
@@ -480,6 +527,9 @@ impl<'a> Classifier<'a> {
480527
None => match text {
481528
"Option" | "Result" => Class::PreludeTy,
482529
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
530+
// "union" is a weak keyword and is only considered as a keyword when declaring
531+
// a union type.
532+
"union" if self.check_if_is_union_keyword() => Class::KeyWord,
483533
_ if self.in_macro_nonterminal => {
484534
self.in_macro_nonterminal = false;
485535
Class::MacroNonTerminal
@@ -500,7 +550,17 @@ impl<'a> Classifier<'a> {
500550
}
501551

502552
fn peek(&mut self) -> Option<TokenKind> {
503-
self.tokens.peek().map(|(toke_kind, _text)| *toke_kind)
553+
self.tokens.peek().map(|(token_kind, _text)| *token_kind)
554+
}
555+
556+
fn check_if_is_union_keyword(&mut self) -> bool {
557+
while let Some(kind) = self.tokens.peek_next().map(|(token_kind, _text)| token_kind) {
558+
if *kind == TokenKind::Whitespace {
559+
continue;
560+
}
561+
return *kind == TokenKind::Ident;
562+
}
563+
false
504564
}
505565
}
506566

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<span class="kw">union</span> <span class="ident">Foo</span> {
2+
<span class="ident">i</span>: <span class="ident">i8</span>,
3+
<span class="ident">u</span>: <span class="ident">i8</span>,
4+
}
5+
6+
<span class="kw">fn</span> <span class="ident">main</span>() {
7+
<span class="kw">let</span> <span class="ident">union</span> <span class="op">=</span> <span class="number">0</span>;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
union Foo {
2+
i: i8,
3+
u: i8,
4+
}
5+
6+
fn main() {
7+
let union = 0;
8+
}

src/librustdoc/html/highlight/tests.rs

+10
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,13 @@ let y = Self::whatever;";
5454
expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner());
5555
});
5656
}
57+
58+
#[test]
59+
fn test_union_highlighting() {
60+
create_default_session_globals_then(|| {
61+
let src = include_str!("fixtures/union.rs");
62+
let mut html = Buffer::new();
63+
write_code(&mut html, src, Edition::Edition2018, None);
64+
expect_file!["fixtures/union.html"].assert_eq(&html.into_inner());
65+
});
66+
}

0 commit comments

Comments
 (0)