Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/rustc_expand/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ pub(crate) struct MissingFragmentSpecifier {
)]
pub add_span: Span,
pub valid: &'static str,
#[suggestion("use `:` instead of `;`", code = ":", applicability = "maybe-incorrect")]
pub semi_span: Option<Span>,
}

#[derive(Diagnostic)]
Expand Down
33 changes: 30 additions & 3 deletions compiler/rustc_expand/src/mbe/quoted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ fn parse(
span,
add_span: span.shrink_to_hi(),
valid: VALID_FRAGMENT_NAMES_MSG,
semi_span: None,
});

// Fall back to a `TokenTree` since that will match anything if we continue expanding.
Expand Down Expand Up @@ -144,9 +145,35 @@ fn parse(
});
result.push(TokenTree::MetaVarDecl { span, name: ident, kind });
} else {
// Whether it's none or some other tree, it doesn't belong to
// the current meta variable, returning the original span.
missing_fragment_specifier(start_sp);
// Check for typo: `;` instead of `:` before a valid fragment specifier.
let semi_span = {
let mut clone = iter.clone();
if let Some(tokenstream::TokenTree::Token(semi_token, _)) = clone.next()
&& semi_token.kind == token::Semi
&& let Some(tokenstream::TokenTree::Token(frag_token, _)) = clone.next()
&& let Some((fragment, _)) = frag_token.ident()
&& NonterminalKind::from_symbol(fragment.name, || edition).is_some()
{
Some(semi_token.span)
} else {
None
}
};
if semi_span.is_some() {
sess.dcx().emit_err(errors::MissingFragmentSpecifier {
span: start_sp,
add_span: start_sp.shrink_to_hi(),
valid: VALID_FRAGMENT_NAMES_MSG,
semi_span,
});
result.push(TokenTree::MetaVarDecl {
span: start_sp,
name: ident,
kind: NonterminalKind::TT,
Copy link
Member

@fmease fmease Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use the nonterminal kind here that you've already parsed above but are currently discarding.

That way, you'd recover $x;ty as $x:ty instead of $x:tt.

});
} else {
missing_fragment_specifier(start_sp);
}
}
}
result
Expand Down
9 changes: 9 additions & 0 deletions tests/ui/macros/macro-semicolon-for-colon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Ensure that using `;` instead of `:` in a macro fragment specifier
// produces a helpful suggestion.

macro_rules! m {
($x;tt) => {};
//~^ ERROR missing fragment specifier
}

fn main() {}
20 changes: 20 additions & 0 deletions tests/ui/macros/macro-semicolon-for-colon.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: missing fragment specifier
--> $DIR/macro-semicolon-for-colon.rs:5:6
|
LL | ($x;tt) => {};
| ^^
|
= note: fragment specifiers must be provided
= help: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item` and `vis`, along with `expr_2021` and `pat_param` for edition compatibility
help: try adding a specifier here
|
LL | ($x:spec;tt) => {};
| +++++
Comment on lines +9 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haven't looked thought, but is that possible to remove this missleading help?

help: use `:` instead of `;`
|
LL - ($x;tt) => {};
LL + ($x:tt) => {};
|

error: aborting due to 1 previous error

Loading