Skip to content

Commit 5430082

Browse files
authored
Rollup merge of #148655 - GuillaumeGomez:keyword-as-macros, r=yotamofek,fmease
Fix invalid macro tag generation for keywords which can be followed by values Fixes #148617. The problem didn't come from the `generate-macro-expansion` feature but was actually uncovered thanks to it. Keywords like `if` or `return`, when followed by a `!` were considered as macros, which was wrong and let to invalid class stack and to the panic. ~~While working on it, I realized that `_` was considered as a keyword, so I fixed that as well in the second commit.~~ (reverted, see #148655 (comment), #148655 (comment)) r? `@yotamofek`
2 parents 192bb9c + 2c4a593 commit 5430082

File tree

3 files changed

+97
-29
lines changed

3 files changed

+97
-29
lines changed

src/librustdoc/html/highlight.rs

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,9 @@ impl<'a> Iterator for TokenIter<'a> {
789789
}
790790
}
791791

792+
/// Used to know if a keyword followed by a `!` should never be treated as a macro.
793+
const NON_MACRO_KEYWORDS: &[&str] = &["if", "while", "match", "break", "return", "impl"];
794+
792795
/// This iterator comes from the same idea than "Peekable" except that it allows to "peek" more than
793796
/// just the next item by using `peek_next`. The `peek` method always returns the next item after
794797
/// the current one whereas `peek_next` will return the next item after the last one peeked.
@@ -1010,6 +1013,19 @@ impl<'src> Classifier<'src> {
10101013
}
10111014
}
10121015

1016+
fn new_macro_span(
1017+
&mut self,
1018+
text: &'src str,
1019+
sink: &mut dyn FnMut(Span, Highlight<'src>),
1020+
before: u32,
1021+
file_span: Span,
1022+
) {
1023+
self.in_macro = true;
1024+
let span = new_span(before, text, file_span);
1025+
sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Macro(span) });
1026+
sink(span, Highlight::Token { text, class: None });
1027+
}
1028+
10131029
/// Single step of highlighting. This will classify `token`, but maybe also a couple of
10141030
/// following ones as well.
10151031
///
@@ -1216,16 +1232,46 @@ impl<'src> Classifier<'src> {
12161232
LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number,
12171233
},
12181234
TokenKind::GuardedStrPrefix => return no_highlight(sink),
1219-
TokenKind::Ident | TokenKind::RawIdent
1220-
if let Some((TokenKind::Bang, _)) = self.peek_non_trivia() =>
1221-
{
1222-
self.in_macro = true;
1223-
let span = new_span(before, text, file_span);
1224-
sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Macro(span) });
1225-
sink(span, Highlight::Token { text, class: None });
1235+
TokenKind::RawIdent if let Some((TokenKind::Bang, _)) = self.peek_non_trivia() => {
1236+
self.new_macro_span(text, sink, before, file_span);
12261237
return;
12271238
}
1228-
TokenKind::Ident => self.classify_ident(before, text),
1239+
// Macro non-terminals (meta vars) take precedence.
1240+
TokenKind::Ident if self.in_macro_nonterminal => {
1241+
self.in_macro_nonterminal = false;
1242+
Class::MacroNonTerminal
1243+
}
1244+
TokenKind::Ident => {
1245+
let file_span = self.file_span;
1246+
let span = || new_span(before, text, file_span);
1247+
1248+
match text {
1249+
"ref" | "mut" => Class::RefKeyWord,
1250+
"false" | "true" => Class::Bool,
1251+
"self" | "Self" => Class::Self_(span()),
1252+
"Option" | "Result" => Class::PreludeTy(span()),
1253+
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal(span()),
1254+
_ if self.is_weak_keyword(text) || is_keyword(Symbol::intern(text)) => {
1255+
// So if it's not a keyword which can be followed by a value (like `if` or
1256+
// `return`) and the next non-whitespace token is a `!`, then we consider
1257+
// it's a macro.
1258+
if !NON_MACRO_KEYWORDS.contains(&text)
1259+
&& matches!(self.peek_non_trivia(), Some((TokenKind::Bang, _)))
1260+
{
1261+
self.new_macro_span(text, sink, before, file_span);
1262+
return;
1263+
}
1264+
Class::KeyWord
1265+
}
1266+
// If it's not a keyword and the next non whitespace token is a `!`, then
1267+
// we consider it's a macro.
1268+
_ if matches!(self.peek_non_trivia(), Some((TokenKind::Bang, _))) => {
1269+
self.new_macro_span(text, sink, before, file_span);
1270+
return;
1271+
}
1272+
_ => Class::Ident(span()),
1273+
}
1274+
}
12291275
TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => {
12301276
Class::Ident(new_span(before, text, file_span))
12311277
}
@@ -1246,27 +1292,6 @@ impl<'src> Classifier<'src> {
12461292
}
12471293
}
12481294

1249-
fn classify_ident(&mut self, before: u32, text: &'src str) -> Class {
1250-
// Macro non-terminals (meta vars) take precedence.
1251-
if self.in_macro_nonterminal {
1252-
self.in_macro_nonterminal = false;
1253-
return Class::MacroNonTerminal;
1254-
}
1255-
1256-
let file_span = self.file_span;
1257-
let span = || new_span(before, text, file_span);
1258-
1259-
match text {
1260-
"ref" | "mut" => Class::RefKeyWord,
1261-
"false" | "true" => Class::Bool,
1262-
"self" | "Self" => Class::Self_(span()),
1263-
"Option" | "Result" => Class::PreludeTy(span()),
1264-
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal(span()),
1265-
_ if self.is_weak_keyword(text) || is_keyword(Symbol::intern(text)) => Class::KeyWord,
1266-
_ => Class::Ident(span()),
1267-
}
1268-
}
1269-
12701295
fn is_weak_keyword(&mut self, text: &str) -> bool {
12711296
// NOTE: `yeet` (`do yeet $expr`), `catch` (`do catch $block`), `default` (specialization),
12721297
// `contract_{ensures,requires}`, `builtin` (builtin_syntax) & `reuse` (fn_delegation) are
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// This code crashed because a `if` followed by a `!` was considered a macro,
2+
// creating an invalid class stack.
3+
// Regression test for <https://github.com/rust-lang/rust/issues/148617>.
4+
5+
//@ compile-flags: -Zunstable-options --generate-macro-expansion
6+
7+
enum Enum {
8+
Variant,
9+
}
10+
11+
pub fn repro() {
12+
if !matches!(Enum::Variant, Enum::Variant) {}
13+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// This test ensures that keywords which can be followed by values (and therefore `!`)
2+
// are not considered as macros.
3+
// This is a regression test for <https://github.com/rust-lang/rust/issues/148617>.
4+
5+
#![crate_name = "foo"]
6+
#![feature(negative_impls)]
7+
8+
//@ has 'src/foo/keyword-macros.rs.html'
9+
10+
//@ has - '//*[@class="rust"]//*[@class="number"]' '2'
11+
//@ has - '//*[@class="rust"]//*[@class="number"]' '0'
12+
//@ has - '//*[@class="rust"]//*[@class="number"]' '1'
13+
const ARR: [u8; 2] = [!0,! 1];
14+
15+
trait X {}
16+
17+
//@ has - '//*[@class="rust"]//*[@class="kw"]' 'impl'
18+
impl !X for i32 {}
19+
20+
fn a() {
21+
//@ has - '//*[@class="rust"]//*[@class="kw"]' 'if'
22+
if! true{}
23+
//@ has - '//*[@class="rust"]//*[@class="kw"]' 'match'
24+
match !true { _ => {} }
25+
//@ has - '//*[@class="rust"]//*[@class="kw"]' 'while'
26+
let _ = while !true {
27+
//@ has - '//*[@class="rust"]//*[@class="kw"]' 'break'
28+
break !true;
29+
};
30+
}

0 commit comments

Comments
 (0)