Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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,
Comment thread
ozankenangungor marked this conversation as resolved.
Outdated
});
} 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Give that we already check that a valid fragment specifier is provided, do you think it's better to use a separate independent diagnostics than MissingFragmentSpecifier?
We probably only need the typo help here.

= 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 thread
ozankenangungor marked this conversation as resolved.
Outdated
help: use `:` instead of `;`
|
LL - ($x;tt) => {};
LL + ($x:tt) => {};
|

error: aborting due to 1 previous error

Loading