Skip to content

Commit 851f169

Browse files
Fix invalid handling of field followed by negated macro call
1 parent 61cc47e commit 851f169

File tree

2 files changed

+71
-31
lines changed

2 files changed

+71
-31
lines changed

src/librustdoc/html/highlight.rs

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,20 @@ impl<'a> PeekIter<'a> {
832832
.copied()
833833
}
834834

835+
fn peek_next_if<F: Fn((TokenKind, &'a str)) -> bool>(
836+
&mut self,
837+
f: F,
838+
) -> Option<(TokenKind, &'a str)> {
839+
let next = self.peek_next()?;
840+
if f(next) {
841+
Some(next)
842+
} else {
843+
// We go one step back.
844+
self.peek_pos -= 1;
845+
None
846+
}
847+
}
848+
835849
fn stop_peeking(&mut self) {
836850
self.peek_pos = 0;
837851
}
@@ -903,18 +917,19 @@ fn classify<'src>(
903917
}
904918
}
905919

906-
if let Some((TokenKind::Colon | TokenKind::Ident, _)) = classifier.tokens.peek() {
907-
let tokens = classifier.get_full_ident_path();
908-
for &(token, start, end) in &tokens {
909-
let text = &classifier.src[start..end];
910-
classifier.advance(token, text, sink, start as u32);
911-
classifier.byte_pos += text.len() as u32;
912-
}
913-
if !tokens.is_empty() {
914-
continue;
920+
if let Some((TokenKind::Colon | TokenKind::Ident, _)) = classifier.tokens.peek()
921+
&& let Some(nb_items) = classifier.get_full_ident_path()
922+
{
923+
let start = classifier.byte_pos as usize;
924+
let mut len = 0;
925+
for _ in 0..nb_items {
926+
if let Some((_, text, _)) = classifier.next() {
927+
len += text.len();
928+
}
915929
}
916-
}
917-
if let Some((token, text, before)) = classifier.next() {
930+
let text = &classifier.src[start..start + len];
931+
classifier.advance(TokenKind::Ident, text, sink, start as u32);
932+
} else if let Some((token, text, before)) = classifier.next() {
918933
classifier.advance(token, text, sink, before);
919934
} else {
920935
break;
@@ -957,47 +972,47 @@ impl<'src> Classifier<'src> {
957972
}
958973

959974
/// Concatenate colons and idents as one when possible.
960-
fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
961-
let start = self.byte_pos as usize;
962-
let mut pos = start;
975+
fn get_full_ident_path(&mut self) -> Option<usize> {
963976
let mut has_ident = false;
977+
let mut nb_items = 0;
964978

965-
loop {
979+
let ret = loop {
966980
let mut nb = 0;
967-
while let Some((TokenKind::Colon, _)) = self.tokens.peek() {
968-
self.tokens.next();
981+
while self.tokens.peek_next_if(|(token, _)| token == TokenKind::Colon).is_some() {
969982
nb += 1;
983+
nb_items += 1;
970984
}
971985
// Ident path can start with "::" but if we already have content in the ident path,
972986
// the "::" is mandatory.
973987
if has_ident && nb == 0 {
974-
return vec![(TokenKind::Ident, start, pos)];
988+
break Some(nb_items);
975989
} else if nb != 0 && nb != 2 {
976990
if has_ident {
977-
return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
991+
// Following `;` will be handled on its own.
992+
break Some(nb_items - 1);
978993
} else {
979-
return vec![(TokenKind::Colon, start, pos + nb)];
994+
break None;
980995
}
981996
}
982997

983-
if let Some((TokenKind::Ident, text)) = self.tokens.peek()
998+
if let Some((TokenKind::Ident, text)) =
999+
self.tokens.peek_next_if(|(token, _)| token == TokenKind::Ident)
9841000
&& let symbol = Symbol::intern(text)
9851001
&& (symbol.is_path_segment_keyword() || !is_keyword(symbol))
9861002
{
987-
// We only "add" the colon if there is an ident behind.
988-
pos += text.len() + nb;
9891003
has_ident = true;
990-
self.tokens.next();
1004+
nb_items += 1;
9911005
} else if nb > 0 && has_ident {
992-
return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
993-
} else if nb > 0 {
994-
return vec![(TokenKind::Colon, start, start + nb)];
1006+
// Following `;` will be handled on its own.
1007+
break Some(nb_items - 1);
9951008
} else if has_ident {
996-
return vec![(TokenKind::Ident, start, pos)];
1009+
break Some(nb_items);
9971010
} else {
998-
return Vec::new();
1011+
break None;
9991012
}
1000-
}
1013+
};
1014+
self.tokens.stop_peeking();
1015+
ret
10011016
}
10021017

10031018
/// Wraps the tokens iteration to ensure that the `byte_pos` is always correct.
@@ -1243,7 +1258,6 @@ impl<'src> Classifier<'src> {
12431258
Class::MacroNonTerminal
12441259
}
12451260
TokenKind::Ident => {
1246-
let file_span = self.file_span;
12471261
let span = || new_span(before, text, file_span);
12481262

12491263
match text {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// This test ensures that the macro expansion is correctly handled in cases like:
2+
// `field: !f!`, because the `:` was simply not considered because of how paths
3+
// are handled.
4+
5+
//@ compile-flags: -Zunstable-options --generate-macro-expansion
6+
7+
#![crate_name = "foo"]
8+
9+
//@ has 'src/foo/field-followed-by-exclamation.rs.html'
10+
11+
struct Bar {
12+
bla: bool,
13+
}
14+
15+
macro_rules! f {
16+
() => {{ false }}
17+
}
18+
19+
const X: Bar = Bar {
20+
//@ has - '//*[@class="expansion"]/*[@class="original"]/*[@class="macro"]' 'f!'
21+
//@ has - '//*[@class="expansion"]/*[@class="original"]' 'f!()'
22+
//@ has - '//*[@class="expansion"]/*[@class="expanded"]' '{ false }'
23+
// It includes both original and expanded code.
24+
//@ has - '//*[@class="expansion"]' ' bla: !{ false }f!()'
25+
bla: !f!(),
26+
};

0 commit comments

Comments
 (0)