Rollup merge of #152394 - GuillaumeGomez:macro-call, r=lolbinarycat

Correctly check if a macro call is actually a macro call in rustdoc highlighter

Fixes rust-lang/rust#151904.

Issues was that if there was a `!` following an ident, we would always assume it's a macro call... except it's very lacking. I'm actually surprised it went for so long unnoticed. To fix it, I added a check for the next (non-blank) token after the `!`, if it's a `{` or a `[` or a `(`, then only do we consider it to be a macro call.

r? @lolbinarycat
This commit is contained in:
Jacob Pratt 2026-02-12 00:41:10 -05:00 committed by GitHub
commit 4bc90240e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 81 additions and 5 deletions

View file

@ -1246,7 +1246,7 @@ impl<'src> Classifier<'src> {
LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number,
},
TokenKind::GuardedStrPrefix => return no_highlight(sink),
TokenKind::RawIdent if let Some((TokenKind::Bang, _)) = self.peek_non_trivia() => {
TokenKind::RawIdent if self.check_if_macro_call("") => {
self.new_macro_span(text, sink, before, file_span);
return;
}
@ -1268,9 +1268,7 @@ impl<'src> Classifier<'src> {
// So if it's not a keyword which can be followed by a value (like `if` or
// `return`) and the next non-whitespace token is a `!`, then we consider
// it's a macro.
if !NON_MACRO_KEYWORDS.contains(&text)
&& matches!(self.peek_non_trivia(), Some((TokenKind::Bang, _)))
{
if !NON_MACRO_KEYWORDS.contains(&text) && self.check_if_macro_call(text) {
self.new_macro_span(text, sink, before, file_span);
return;
}
@ -1278,7 +1276,7 @@ impl<'src> Classifier<'src> {
}
// If it's not a keyword and the next non whitespace token is a `!`, then
// we consider it's a macro.
_ if matches!(self.peek_non_trivia(), Some((TokenKind::Bang, _))) => {
_ if self.check_if_macro_call(text) => {
self.new_macro_span(text, sink, before, file_span);
return;
}
@ -1339,6 +1337,37 @@ impl<'src> Classifier<'src> {
self.tokens.stop_peeking();
None
}
fn check_if_macro_call(&mut self, ident: &str) -> bool {
let mut has_bang = false;
let is_macro_rule_ident = ident == "macro_rules";
while let Some((kind, _)) = self.tokens.peek_next() {
if let TokenKind::Whitespace
| TokenKind::LineComment { doc_style: None }
| TokenKind::BlockComment { doc_style: None, .. } = kind
{
continue;
}
if !has_bang {
if kind != TokenKind::Bang {
break;
}
has_bang = true;
continue;
}
self.tokens.stop_peeking();
if is_macro_rule_ident {
return matches!(kind, TokenKind::Ident | TokenKind::RawIdent);
}
return matches!(
kind,
TokenKind::OpenParen | TokenKind::OpenBracket | TokenKind::OpenBrace
);
}
self.tokens.stop_peeking();
false
}
}
fn is_keyword(symbol: Symbol) -> bool {

View file

@ -0,0 +1,18 @@
// This is yet another test to ensure that only macro calls are considered as such
// by the rustdoc highlighter, in particular when named `macro_rules`.
// This is a regression test for <https://github.com/rust-lang/rust/issues/151904>.
#![crate_name = "foo"]
//@ has src/foo/macro-call-2.rs.html
//@ count - '//code/span[@class="macro"]' 2
//@ has - '//code/span[@class="macro"]' 'macro_rules!'
//@ has - '//code/span[@class="macro"]' 'r#macro_rules!'
macro_rules! r#macro_rules {
() => {
fn main() {}
}
}
r#macro_rules!();

View file

@ -0,0 +1,29 @@
// This is yet another test to ensure that only macro calls are considered as such
// by the rustdoc highlighter.
// This is a regression test for <https://github.com/rust-lang/rust/issues/151904>.
#![crate_name = "foo"]
//@ has src/foo/macro-call.rs.html
//@ count - '//code/span[@class="macro"]' 2
//@ has - '//code/span[@class="macro"]' 'panic!'
//@ has - '//code/span[@class="macro"]' 'macro_rules!'
pub struct Layout;
impl Layout {
pub fn new<X: std::fmt::Debug>() {}
}
pub fn bar() {
let layout = Layout::new::<u32>();
if layout != Layout::new::<u32>() {
panic!();
}
let macro_rules = 3;
if macro_rules != 3 {}
}
macro_rules! blob {
() => {}
}