diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index 6dc6d1026f621..b5834d2af2940 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -1101,6 +1101,8 @@ pub enum NonterminalKind { Path, Vis, TT, + Fn, + Adt, } impl NonterminalKind { @@ -1138,6 +1140,8 @@ impl NonterminalKind { sym::path => NonterminalKind::Path, sym::vis => NonterminalKind::Vis, sym::tt => NonterminalKind::TT, + kw::Fn => NonterminalKind::Fn, + sym::adt => NonterminalKind::Adt, _ => return None, }) } @@ -1159,6 +1163,8 @@ impl NonterminalKind { NonterminalKind::Path => sym::path, NonterminalKind::Vis => sym::vis, NonterminalKind::TT => sym::tt, + NonterminalKind::Fn => kw::Fn, + NonterminalKind::Adt => sym::adt, } } } diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl index 47c00bff5c950..440833206b355 100644 --- a/compiler/rustc_expand/messages.ftl +++ b/compiler/rustc_expand/messages.ftl @@ -142,12 +142,13 @@ expand_mve_extra_tokens = *[other] these tokens } -expand_mve_missing_paren = - expected `(` - .label = for this this metavariable expression +expand_mve_extra_tokens_after_field = unexpected trailing tokens after field + +expand_mve_missing_paren_or_dot = + expected `(` or `.` + .label = after this metavariable expression .unexpected = unexpected token - .note = metavariable expressions use function-like parentheses syntax - .suggestion = try adding parentheses + .note = metavariable expressions use parentheses for functions or dot for fields expand_mve_unrecognized_expr = unrecognized metavariable expression @@ -157,6 +158,8 @@ expand_mve_unrecognized_expr = expand_mve_unrecognized_var = variable `{$key}` is not recognized in meta-variable expression +expand_mve_expr_has_no_field = expression of type `{$pnr_type}` has no field `{$field}` + expand_non_inline_modules_in_proc_macro_input_are_unstable = non-inline modules in proc macro input are unstable diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index c37c2d88d9cd2..a0efca9d7b40f 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -487,17 +487,23 @@ mod metavar_exprs { pub name: String, } + #[derive(Diagnostic)] + #[diag(expand_mve_extra_tokens_after_field)] + pub(crate) struct MveExtraTokensAfterField { + #[primary_span] + #[suggestion(code = "", applicability = "machine-applicable")] + pub span: Span, + } + #[derive(Diagnostic)] #[note] - #[diag(expand_mve_missing_paren)] - pub(crate) struct MveMissingParen { + #[diag(expand_mve_missing_paren_or_dot)] + pub(crate) struct MveMissingParenOrDot { #[primary_span] #[label] pub ident_span: Span, #[label(expand_unexpected)] pub unexpected_span: Option, - #[suggestion(code = "( /* ... */ )", applicability = "has-placeholders")] - pub insert_span: Option, } #[derive(Diagnostic)] @@ -517,6 +523,15 @@ mod metavar_exprs { pub span: Span, pub key: MacroRulesNormalizedIdent, } + + #[derive(Diagnostic)] + #[diag(expand_mve_expr_has_no_field)] + pub(crate) struct MveExprHasNoField { + #[primary_span] + pub span: Span, + pub pnr_type: &'static str, + pub field: Ident, + } } #[derive(Diagnostic)] diff --git a/compiler/rustc_expand/src/mbe.rs b/compiler/rustc_expand/src/mbe.rs index 3082c881a7a66..3af0b74c7a5f2 100644 --- a/compiler/rustc_expand/src/mbe.rs +++ b/compiler/rustc_expand/src/mbe.rs @@ -12,7 +12,7 @@ mod metavar_expr; mod quoted; mod transcribe; -use metavar_expr::MetaVarExpr; +use metavar_expr::{MetaVarExpr, MetaVarRecursiveExpr}; use rustc_ast::token::{Delimiter, NonterminalKind, Token, TokenKind}; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan}; use rustc_macros::{Decodable, Encodable}; diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index f5edaf50edd52..8ab740b90601c 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -4,7 +4,7 @@ use rustc_ast::token::{self, Token}; use rustc_ast::tokenstream::TokenStream; use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage}; use rustc_macros::Subdiagnostic; -use rustc_parse::parser::{Parser, Recovery, token_descr}; +use rustc_parse::parser::{ForceCollect, Parser, Recovery, token_descr}; use rustc_session::parse::ParseSess; use rustc_span::source_map::SourceMap; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span}; @@ -44,12 +44,14 @@ pub(super) fn failed_to_match_macro( // diagnostics. let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp); + // We don't need to collect tokens to process failures, since we'll never expand them. + let no = ForceCollect::No; let try_success_result = match args { - FailedMacro::Func => try_match_macro(psess, name, body, rules, &mut tracker), + FailedMacro::Func => try_match_macro(psess, name, body, rules, no, &mut tracker), FailedMacro::Attr(attr_args) => { - try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker) + try_match_macro_attr(psess, name, attr_args, body, rules, no, &mut tracker) } - FailedMacro::Derive => try_match_macro_derive(psess, name, body, rules, &mut tracker), + FailedMacro::Derive => try_match_macro_derive(psess, name, body, rules, no, &mut tracker), }; if try_success_result.is_ok() { @@ -108,9 +110,12 @@ pub(super) fn failed_to_match_macro( let parser = parser_from_cx(psess, body.clone(), Recovery::Allowed); let mut tt_parser = TtParser::new(name); - if let Success(_) = - tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker) - { + if let Success(_) = tt_parser.parse_tt( + &mut Cow::Borrowed(&parser), + lhs, + ForceCollect::No, + &mut NoopTracker, + ) { if comma_span.is_dummy() { err.note("you might be missing a comma"); } else { diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs index ebd6e887f7d28..b5dfc5f49d808 100644 --- a/compiler/rustc_expand/src/mbe/macro_check.rs +++ b/compiler/rustc_expand/src/mbe/macro_check.rs @@ -109,6 +109,7 @@ use rustc_ast::token::{Delimiter, IdentIsRaw, Token, TokenKind}; use rustc_ast::{DUMMY_NODE_ID, NodeId}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::DecorateDiagCompat; +use rustc_parse::parser::ForceCollect; use rustc_session::lint::builtin::META_VARIABLE_MISUSE; use rustc_session::parse::ParseSess; use rustc_span::{ErrorGuaranteed, MacroRulesNormalizedIdent, Span, kw}; @@ -193,20 +194,23 @@ struct MacroState<'a> { /// - `psess` is used to emit diagnostics and lints /// - `node_id` is used to emit lints /// - `args`, `lhs`, and `rhs` represent the rule +/// - `collect` is set to the token collection mode needed for the macro (recursive if any metavar exprs) pub(super) fn check_meta_variables( psess: &ParseSess, node_id: NodeId, args: Option<&TokenTree>, lhs: &TokenTree, rhs: &TokenTree, + collect: &mut ForceCollect, ) -> Result<(), ErrorGuaranteed> { let mut guar = None; let mut binders = Binders::default(); + let ops = &Stack::Empty; if let Some(args) = args { - check_binders(psess, node_id, args, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar); + check_binders(psess, node_id, args, &Stack::Empty, &mut binders, ops, collect, &mut guar); } - check_binders(psess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar); - check_occurrences(psess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut guar); + check_binders(psess, node_id, lhs, &Stack::Empty, &mut binders, ops, collect, &mut guar); + check_occurrences(psess, node_id, rhs, &Stack::Empty, &binders, ops, collect, &mut guar); guar.map_or(Ok(()), Err) } @@ -220,6 +224,7 @@ pub(super) fn check_meta_variables( /// - `macros` is the stack of possible outer macros /// - `binders` contains the binders of the LHS /// - `ops` is the stack of Kleene operators from the LHS +/// - `collect` is updated to recursive if we encounter any metadata expressions /// - `guar` is set in case of errors fn check_binders( psess: &ParseSess, @@ -228,6 +233,7 @@ fn check_binders( macros: &Stack<'_, MacroState<'_>>, binders: &mut Binders, ops: &Stack<'_, KleeneToken>, + collect: &mut ForceCollect, guar: &mut Option, ) { match *lhs { @@ -255,7 +261,7 @@ fn check_binders( binders.insert(name, BinderInfo { span, ops: ops.into() }); } else { // 3. The meta-variable is bound: This is an occurrence. - check_occurrences(psess, node_id, lhs, macros, binders, ops, guar); + check_occurrences(psess, node_id, lhs, macros, binders, ops, collect, guar); } } // Similarly, this can only happen when checking a toplevel macro. @@ -280,13 +286,13 @@ fn check_binders( TokenTree::MetaVarExpr(..) => {} TokenTree::Delimited(.., ref del) => { for tt in &del.tts { - check_binders(psess, node_id, tt, macros, binders, ops, guar); + check_binders(psess, node_id, tt, macros, binders, ops, collect, guar); } } TokenTree::Sequence(_, ref seq) => { let ops = ops.push(seq.kleene); for tt in &seq.tts { - check_binders(psess, node_id, tt, macros, binders, &ops, guar); + check_binders(psess, node_id, tt, macros, binders, &ops, collect, guar); } } } @@ -316,6 +322,7 @@ fn get_binder_info<'a>( /// - `macros` is the stack of possible outer macros /// - `binders` contains the binders of the associated LHS /// - `ops` is the stack of Kleene operators from the RHS +/// - `collect` is updated to recursive if we encounter any metadata expressions /// - `guar` is set in case of errors fn check_occurrences( psess: &ParseSess, @@ -324,6 +331,7 @@ fn check_occurrences( macros: &Stack<'_, MacroState<'_>>, binders: &Binders, ops: &Stack<'_, KleeneToken>, + collect: &mut ForceCollect, guar: &mut Option, ) { match *rhs { @@ -336,17 +344,21 @@ fn check_occurrences( check_ops_is_prefix(psess, node_id, macros, binders, ops, span, name); } TokenTree::MetaVarExpr(dl, ref mve) => { + // Require recursive token collection if we have any metadata expressions + *collect = ForceCollect::Recursive; mve.for_each_metavar((), |_, ident| { let name = MacroRulesNormalizedIdent::new(*ident); check_ops_is_prefix(psess, node_id, macros, binders, ops, dl.entire(), name); }); } TokenTree::Delimited(.., ref del) => { - check_nested_occurrences(psess, node_id, &del.tts, macros, binders, ops, guar); + check_nested_occurrences(psess, node_id, &del.tts, macros, binders, ops, collect, guar); } TokenTree::Sequence(_, ref seq) => { let ops = ops.push(seq.kleene); - check_nested_occurrences(psess, node_id, &seq.tts, macros, binders, &ops, guar); + check_nested_occurrences( + psess, node_id, &seq.tts, macros, binders, &ops, collect, guar, + ); } } } @@ -381,6 +393,7 @@ enum NestedMacroState { /// - `macros` is the stack of possible outer macros /// - `binders` contains the binders of the associated LHS /// - `ops` is the stack of Kleene operators from the RHS +/// - `collect` is updated to recursive if we encounter metadata expressions or nested macros /// - `guar` is set in case of errors fn check_nested_occurrences( psess: &ParseSess, @@ -389,6 +402,7 @@ fn check_nested_occurrences( macros: &Stack<'_, MacroState<'_>>, binders: &Binders, ops: &Stack<'_, KleeneToken>, + collect: &mut ForceCollect, guar: &mut Option, ) { let mut state = NestedMacroState::Empty; @@ -421,16 +435,26 @@ fn check_nested_occurrences( (NestedMacroState::MacroRulesBang, &TokenTree::MetaVar(..)) => { state = NestedMacroState::MacroRulesBangName; // We check that the meta-variable is correctly used. - check_occurrences(psess, node_id, tt, macros, binders, ops, guar); + check_occurrences(psess, node_id, tt, macros, binders, ops, collect, guar); } (NestedMacroState::MacroRulesBangName, TokenTree::Delimited(.., del)) | (NestedMacroState::MacroName, TokenTree::Delimited(.., del)) if del.delim == Delimiter::Brace => { + // Conservatively assume that we might need recursive tokens, since our parsing in + // the face of nested macro definitions is fuzzy. + *collect = ForceCollect::Recursive; let macro_rules = state == NestedMacroState::MacroRulesBangName; state = NestedMacroState::Empty; - let rest = - check_nested_macro(psess, node_id, macro_rules, &del.tts, &nested_macros, guar); + let rest = check_nested_macro( + psess, + node_id, + macro_rules, + &del.tts, + &nested_macros, + collect, + guar, + ); // If we did not check the whole macro definition, then check the rest as if outside // the macro definition. check_nested_occurrences( @@ -440,6 +464,7 @@ fn check_nested_occurrences( macros, binders, ops, + collect, guar, ); } @@ -452,7 +477,7 @@ fn check_nested_occurrences( (NestedMacroState::Macro, &TokenTree::MetaVar(..)) => { state = NestedMacroState::MacroName; // We check that the meta-variable is correctly used. - check_occurrences(psess, node_id, tt, macros, binders, ops, guar); + check_occurrences(psess, node_id, tt, macros, binders, ops, collect, guar); } (NestedMacroState::MacroName, TokenTree::Delimited(.., del)) if del.delim == Delimiter::Parenthesis => @@ -466,6 +491,7 @@ fn check_nested_occurrences( &nested_macros, &mut nested_binders, &Stack::Empty, + collect, guar, ); } @@ -480,12 +506,13 @@ fn check_nested_occurrences( &nested_macros, &nested_binders, &Stack::Empty, + collect, guar, ); } (_, tt) => { state = NestedMacroState::Empty; - check_occurrences(psess, node_id, tt, macros, binders, ops, guar); + check_occurrences(psess, node_id, tt, macros, binders, ops, collect, guar); } } } @@ -504,6 +531,7 @@ fn check_nested_occurrences( /// - `macro_rules` specifies whether the macro is `macro_rules` /// - `tts` is checked as a list of (LHS) => {RHS} /// - `macros` is the stack of outer macros +/// - `collect` is passed down through to the macro checking code (but is already recursive) /// - `guar` is set in case of errors fn check_nested_macro( psess: &ParseSess, @@ -511,6 +539,7 @@ fn check_nested_macro( macro_rules: bool, tts: &[TokenTree], macros: &Stack<'_, MacroState<'_>>, + collect: &mut ForceCollect, guar: &mut Option, ) -> usize { let n = tts.len(); @@ -528,8 +557,8 @@ fn check_nested_macro( let lhs = &tts[i]; let rhs = &tts[i + 2]; let mut binders = Binders::default(); - check_binders(psess, node_id, lhs, macros, &mut binders, &Stack::Empty, guar); - check_occurrences(psess, node_id, rhs, macros, &binders, &Stack::Empty, guar); + check_binders(psess, node_id, lhs, macros, &mut binders, &Stack::Empty, collect, guar); + check_occurrences(psess, node_id, rhs, macros, &binders, &Stack::Empty, collect, guar); // Since the last semicolon is optional for `macro_rules` macros and decl_macro are not terminated, // we increment our checked position by how many token trees we already checked (the 3 // above) before checking for the separator. diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index ab8e059b7b77f..80e0a8986964b 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -81,7 +81,7 @@ use rustc_ast::token::{self, DocComment, NonterminalKind, Token, TokenKind}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::ErrorGuaranteed; use rustc_lint_defs::pluralize; -use rustc_parse::parser::{ParseNtResult, Parser, token_descr}; +use rustc_parse::parser::{ForceCollect, ParseNtResult, Parser, token_descr}; use rustc_span::{Ident, MacroRulesNormalizedIdent, Span}; use crate::mbe::macro_rules::Tracker; @@ -616,6 +616,7 @@ impl TtParser { &mut self, parser: &mut Cow<'_, Parser<'_>>, matcher: &'matcher [MatcherLoc], + collect_mode: ForceCollect, track: &mut T, ) -> NamedParseResult { // A queue of possible matcher positions. We initialize it with the matcher position in @@ -675,7 +676,7 @@ impl TtParser { { // We use the span of the metavariable declaration to determine any // edition-specific matching behavior for non-terminals. - let nt = match parser.to_mut().parse_nonterminal(kind) { + let nt = match parser.to_mut().parse_nonterminal(kind, collect_mode) { Err(err) => { let guarantee = err.with_span_label( span, diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index d4504ba720e46..ed297e5e9d57d 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -21,7 +21,7 @@ use rustc_lint_defs::builtin::{ RUST_2021_INCOMPATIBLE_OR_PATTERNS, SEMICOLON_IN_EXPRESSIONS_FROM_MACROS, }; use rustc_parse::exp; -use rustc_parse::parser::{Parser, Recovery}; +use rustc_parse::parser::{ForceCollect, Parser, Recovery}; use rustc_session::Session; use rustc_session::parse::{ParseSess, feature_err}; use rustc_span::edition::Edition; @@ -147,6 +147,7 @@ pub struct MacroRulesMacroExpander { span: Span, transparency: Transparency, kinds: MacroKinds, + collect: ForceCollect, rules: Vec, } @@ -183,7 +184,7 @@ impl MacroRulesMacroExpander { trace_macros_note(&mut cx.expansions, sp, msg); } - match try_match_macro_derive(psess, name, body, rules, &mut NoopTracker) { + match try_match_macro_derive(psess, name, body, rules, self.collect, &mut NoopTracker) { Ok((rule_index, rule, named_matches)) => { let MacroRule::Derive { rhs, .. } = rule else { panic!("try_match_macro_derive returned non-derive rule"); @@ -239,6 +240,7 @@ impl TTMacroExpander for MacroRulesMacroExpander { self.node_id, self.name, self.transparency, + self.collect, input, &self.rules, )) @@ -260,6 +262,7 @@ impl AttrProcMacro for MacroRulesMacroExpander { self.node_id, self.name, self.transparency, + self.collect, args, body, &self.rules, @@ -332,6 +335,7 @@ fn expand_macro<'cx>( node_id: NodeId, name: Ident, transparency: Transparency, + collect: ForceCollect, arg: TokenStream, rules: &[MacroRule], ) -> Box { @@ -343,7 +347,7 @@ fn expand_macro<'cx>( } // Track nothing for the best performance. - let try_success_result = try_match_macro(psess, name, &arg, rules, &mut NoopTracker); + let try_success_result = try_match_macro(psess, name, &arg, rules, collect, &mut NoopTracker); match try_success_result { Ok((rule_index, rule, named_matches)) => { @@ -408,6 +412,7 @@ fn expand_macro_attr( node_id: NodeId, name: Ident, transparency: Transparency, + collect: ForceCollect, args: TokenStream, body: TokenStream, rules: &[MacroRule], @@ -427,7 +432,7 @@ fn expand_macro_attr( } // Track nothing for the best performance. - match try_match_macro_attr(psess, name, &args, &body, rules, &mut NoopTracker) { + match try_match_macro_attr(psess, name, &args, &body, rules, collect, &mut NoopTracker) { Ok((i, rule, named_matches)) => { let MacroRule::Attr { rhs, .. } = rule else { panic!("try_macro_match_attr returned non-attr rule"); @@ -484,6 +489,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( name: Ident, arg: &TokenStream, rules: &'matcher [MacroRule], + collect_mode: ForceCollect, track: &mut T, ) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { // We create a base parser that can be used for the "black box" parts. @@ -518,7 +524,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( // are not recorded. On the first `Success(..)`ful matcher, the spans are merged. let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); - let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track); + let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, collect_mode, track); track.after_arm(true, &result); @@ -565,6 +571,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( attr_args: &TokenStream, attr_body: &TokenStream, rules: &'matcher [MacroRule], + collect_mode: ForceCollect, track: &mut T, ) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { // This uses the same strategy as `try_match_macro` @@ -576,7 +583,8 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); - let result = tt_parser.parse_tt(&mut Cow::Borrowed(&args_parser), args, track); + let result = + tt_parser.parse_tt(&mut Cow::Borrowed(&args_parser), args, collect_mode, track); track.after_arm(false, &result); let mut named_matches = match result { @@ -589,7 +597,8 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( ErrorReported(guar) => return Err(CanRetry::No(guar)), }; - let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track); + let result = + tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, collect_mode, track); track.after_arm(true, &result); match result { @@ -619,6 +628,7 @@ pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>( name: Ident, body: &TokenStream, rules: &'matcher [MacroRule], + collect_mode: ForceCollect, track: &mut T, ) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { // This uses the same strategy as `try_match_macro` @@ -629,7 +639,8 @@ pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>( let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); - let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track); + let result = + tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, collect_mode, track); track.after_arm(true, &result); match result { @@ -679,6 +690,7 @@ pub fn compile_declarative_macro( let mut kinds = MacroKinds::empty(); let mut rules = Vec::new(); + let mut collect = ForceCollect::Yes; while p.token != token::Eof { let (args, is_derive) = if p.eat_keyword_noexpect(sym::attr) { @@ -744,7 +756,14 @@ pub fn compile_declarative_macro( let rhs_tt = p.parse_token_tree(); let rhs_tt = parse_one_tt(rhs_tt, RulePart::Body, sess, node_id, features, edition); check_emission(check_rhs(sess, &rhs_tt)); - check_emission(check_meta_variables(&sess.psess, node_id, args.as_ref(), &lhs_tt, &rhs_tt)); + check_emission(check_meta_variables( + &sess.psess, + node_id, + args.as_ref(), + &lhs_tt, + &rhs_tt, + &mut collect, + )); let lhs_span = lhs_tt.span(); // Convert the lhs into `MatcherLoc` form, which is better for doing the // actual matching. @@ -792,7 +811,8 @@ pub fn compile_declarative_macro( // Return the number of rules for unused rule linting, if this is a local macro. let nrules = if is_defined_in_current_crate(node_id) { rules.len() } else { 0 }; - let exp = MacroRulesMacroExpander { name: ident, kinds, span, node_id, transparency, rules }; + let exp = + MacroRulesMacroExpander { name: ident, kinds, span, node_id, transparency, collect, rules }; (mk_syn_ext(SyntaxExtensionKind::MacroRules(Arc::new(exp))), nrules) } @@ -1550,7 +1570,7 @@ fn is_in_follow(tok: &mbe::TokenTree, kind: NonterminalKind) -> IsInFollow { IsInFollow::Yes } else { match kind { - NonterminalKind::Item => { + NonterminalKind::Item | NonterminalKind::Fn | NonterminalKind::Adt => { // since items *must* be followed by either a `;` or a `}`, we can // accept anything after them IsInFollow::Yes diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs index 70d796cda11c0..6fe61bc409b38 100644 --- a/compiler/rustc_expand/src/mbe/metavar_expr.rs +++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs @@ -31,6 +31,9 @@ pub(crate) enum MetaVarExpr { /// The length of the repetition at a particular depth, where 0 is the innermost /// repetition. The `usize` is the depth. Len(usize), + + /// An expression that can contain other expressions + Recursive(MetaVarRecursiveExpr), } impl MetaVarExpr { @@ -41,19 +44,20 @@ impl MetaVarExpr { psess: &'psess ParseSess, ) -> PResult<'psess, MetaVarExpr> { let mut iter = input.iter(); + // FIXME: Refactor this to use Parser. + // All macro metavariable expressions current start with an ident. let ident = parse_ident(&mut iter, psess, outer_span)?; - let next = iter.next(); - let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) = next else { - // No `()`; wrong or no delimiters. Point at a problematic span or a place to - // add parens if it makes sense. - let (unexpected_span, insert_span) = match next { - Some(TokenTree::Delimited(..)) => (None, None), - Some(tt) => (Some(tt.span()), None), - None => (None, Some(ident.span.shrink_to_hi())), - }; - let err = - errors::MveMissingParen { ident_span: ident.span, unexpected_span, insert_span }; - return Err(psess.dcx().create_err(err)); + let args = match iter.next() { + Some(TokenTree::Token(Token { kind: token::Dot, .. }, _)) => { + return parse_field(ident, iter, psess, outer_span); + } + Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) => args, + next => { + return Err(psess.dcx().create_err(errors::MveMissingParenOrDot { + ident_span: ident.span, + unexpected_span: next.map(|tt| tt.span()), + })); + } }; // Ensure there are no trailing tokens in the braces, e.g. `${foo() extra}` @@ -102,10 +106,55 @@ impl MetaVarExpr { } MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => cb(aux, ident), MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => aux, + MetaVarExpr::Recursive(expr) => expr.for_each_metavar(aux, cb), + } + } +} + +/// A recursive meta-variable expression, which may contain other meta-variable expressions. +#[derive(Debug, PartialEq, Encodable, Decodable)] +pub(crate) enum MetaVarRecursiveExpr { + /// A single identifier, currently the only base of a recursive expression. + Ident(Ident), + + /// A macro fragment field. + Field { expr: Box, field: Ident }, +} + +impl MetaVarRecursiveExpr { + fn for_each_metavar(&self, aux: A, mut cb: impl FnMut(A, &Ident) -> A) -> A { + match self { + Self::Ident(ident) => cb(aux, ident), + Self::Field { expr, field } => expr.for_each_metavar(cb(aux, field), cb), } } } +/// Attempt to parse a meta-variable field. +/// +/// The initial ident and dot have already been eaten. +pub(crate) fn parse_field<'psess>( + base_ident: Ident, + mut iter: TokenStreamIter<'_>, + psess: &'psess ParseSess, + fallback_span: Span, +) -> PResult<'psess, MetaVarExpr> { + let mut expr = MetaVarRecursiveExpr::Ident(base_ident); + let field = parse_ident(&mut iter, psess, fallback_span)?; + expr = MetaVarRecursiveExpr::Field { expr: Box::new(expr), field }; + while let Some(TokenTree::Token(Token { kind: token::Dot, .. }, _)) = iter.next() { + let field = parse_ident(&mut iter, psess, fallback_span)?; + expr = MetaVarRecursiveExpr::Field { expr: Box::new(expr), field }; + } + + // Check for unexpected tokens + if let Some(span) = iter_span(&iter) { + return Err(psess.dcx().create_err(errors::MveExtraTokensAfterField { span })); + } + + Ok(MetaVarExpr::Recursive(expr)) +} + /// Checks if there are any remaining tokens (for example, `${ignore($valid, extra)}`) and create /// a diag with the correct arg count if so. fn check_trailing_tokens<'psess>( diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs index eb874a27cece5..5d59a38d4cae5 100644 --- a/compiler/rustc_expand/src/mbe/quoted.rs +++ b/compiler/rustc_expand/src/mbe/quoted.rs @@ -14,7 +14,8 @@ use crate::mbe::{Delimited, KleeneOp, KleeneToken, MetaVarExpr, SequenceRepetiti pub(crate) const VALID_FRAGMENT_NAMES_MSG: &str = "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"; + `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, \ + along with `expr_2021` and `pat_param` for edition compatibility"; /// Which part of a macro rule we're parsing #[derive(Copy, Clone)] @@ -142,6 +143,12 @@ fn parse( }); NonterminalKind::TT }); + if matches!(kind, NonterminalKind::Fn | NonterminalKind::Adt) + && !features.macro_fragments_more() + { + let msg = "macro `:fn` and `:adt` fragments are unstable"; + feature_err(sess, sym::macro_fragments_more, span, msg).emit(); + } result.push(TokenTree::MetaVarDecl { span, name: ident, kind }); } else { // Whether it's none or some other tree, it doesn't belong to @@ -183,6 +190,13 @@ fn maybe_emit_macro_metavar_expr_concat_feature(features: &Features, sess: &Sess } } +fn maybe_emit_macro_fragment_fields_feature(features: &Features, sess: &Session, span: Span) { + if !features.macro_fragment_fields() { + let msg = "macro fragment fields are unstable"; + feature_err(sess, sym::macro_fragment_fields, span, msg).emit(); + } +} + /// Takes a `tokenstream::TokenTree` and returns a `self::TokenTree`. Specifically, this takes a /// generic `TokenTree`, such as is used in the rest of the compiler, and returns a `TokenTree` /// for use in parsing a macro. @@ -250,18 +264,26 @@ fn parse_tree<'a>( return TokenTree::token(token::Dollar, dollar_span); } Ok(elem) => { - if let MetaVarExpr::Concat(_) = elem { - maybe_emit_macro_metavar_expr_concat_feature( - features, - sess, - delim_span.entire(), - ); - } else { - maybe_emit_macro_metavar_expr_feature( + match elem { + MetaVarExpr::Concat(..) => { + maybe_emit_macro_metavar_expr_concat_feature( + features, + sess, + delim_span.entire(), + ) + } + MetaVarExpr::Recursive(..) => { + maybe_emit_macro_fragment_fields_feature( + features, + sess, + delim_span.entire(), + ) + } + _ => maybe_emit_macro_metavar_expr_feature( features, sess, delim_span.entire(), - ); + ), } return TokenTree::MetaVarExpr(delim_span, elem); } diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index dddd62a4945a2..a6885f125ec99 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -1,10 +1,11 @@ +use std::borrow::Cow; use std::mem; use rustc_ast::token::{ self, Delimiter, IdentIsRaw, InvisibleOrigin, Lit, LitKind, MetaVarKind, Token, TokenKind, }; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; -use rustc_ast::{ExprKind, StmtKind, TyKind, UnOp}; +use rustc_ast::{self as ast, ExprKind, ItemKind, StmtKind, TyKind, UnOp}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Diag, DiagCtxtHandle, PResult, pluralize}; use rustc_parse::lexer::nfc_normalize; @@ -18,12 +19,12 @@ use smallvec::{SmallVec, smallvec}; use crate::errors::{ CountRepetitionMisplaced, MacroVarStillRepeating, MetaVarsDifSeqMatchers, MustRepeatOnce, - MveUnrecognizedVar, NoSyntaxVarsExprRepeat, + MveExprHasNoField, MveUnrecognizedVar, NoSyntaxVarsExprRepeat, }; use crate::mbe::macro_parser::NamedMatch; use crate::mbe::macro_parser::NamedMatch::*; use crate::mbe::metavar_expr::{MetaVarExprConcatElem, RAW_IDENT_ERR}; -use crate::mbe::{self, KleeneOp, MetaVarExpr}; +use crate::mbe::{self, KleeneOp, MetaVarExpr, MetaVarRecursiveExpr}; /// Context needed to perform transcription of metavariable expressions. struct TranscrCtx<'psess, 'itp> { @@ -437,7 +438,7 @@ fn transcribe_pnr<'tx>( let kind = token::NtLifetime(*ident, *is_raw); TokenTree::token_alone(kind, sp) } - ParseNtResult::Item(item) => { + ParseNtResult::Item(item) | ParseNtResult::Fn(item) | ParseNtResult::Adt(item) => { mk_delimited(item.span, MetaVarKind::Item, TokenStream::from_ast(item)) } ParseNtResult::Block(block) => { @@ -540,11 +541,120 @@ fn transcribe_metavar_expr<'tx>( return Err(out_of_bounds_err(dcx, tscx.repeats.len(), dspan.entire(), "len")); } }, + MetaVarExpr::Recursive(ref expr) => { + let pnr = eval_metavar_recursive_expr(tscx, expr)?; + return transcribe_pnr(tscx, dspan.entire(), &pnr); + } }; tscx.result.push(tt); Ok(()) } +/// Evaluate recursive metavariable expressions into a `ParseNtResult`. +/// +/// This does not do any transcription; that's handled in the caller. +/// +/// It's okay to recurse here for now, because we expect a limited degree of nested fields. More +/// complex expressions may require translating this into a proper interpreter. +fn eval_metavar_recursive_expr<'psess, 'interp>( + tscx: &TranscrCtx<'psess, 'interp>, + expr: &MetaVarRecursiveExpr, +) -> PResult<'psess, Cow<'interp, ParseNtResult>> { + let dcx = tscx.psess.dcx(); + match expr { + MetaVarRecursiveExpr::Ident(ident) => { + let span = ident.span; + let ident = MacroRulesNormalizedIdent::new(*ident); + match matched_from_mrn_ident(dcx, span, ident, tscx.interp)? { + NamedMatch::MatchedSingle(pnr) => Ok(Cow::Borrowed(pnr)), + NamedMatch::MatchedSeq(named_matches) => { + let Some((curr_idx, _)) = tscx.repeats.last() else { + return Err(dcx.struct_span_err(span, "invalid syntax")); + }; + match &named_matches[*curr_idx] { + MatchedSeq(_) => { + Err(dcx.create_err(MacroVarStillRepeating { span, ident })) + } + MatchedSingle(pnr) => Ok(Cow::Borrowed(pnr)), + } + } + } + } + MetaVarRecursiveExpr::Field { expr: base_expr, field } => { + let base_pnr = eval_metavar_recursive_expr(tscx, base_expr)?; + let err_unknown_field = || { + Err(dcx.create_err(MveExprHasNoField { + span: field.span, + pnr_type: pnr_type(&base_pnr), + field: *field, + })) + }; + match (base_pnr.as_ref(), field.name) { + (ParseNtResult::Item(item), sym::vis) => Ok(vis_pnr(&item.vis)), + (ParseNtResult::Adt(adt_item), sym::name) => { + let ident = match adt_item.kind { + ItemKind::Struct(ident, ..) + | ItemKind::Enum(ident, ..) + | ItemKind::Union(ident, ..) => ident, + _ => dcx.span_bug(field.span, "`adt` item was not an adt"), + }; + Ok(ident_pnr(ident)) + } + (ParseNtResult::Adt(adt_item), sym::vis) => { + if !matches!(adt_item.kind, ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)) { + dcx.span_bug(field.span, "`adt` item was not an adt"); + } + Ok(vis_pnr(&adt_item.vis)) + } + (ParseNtResult::Fn(fn_item), sym::name) => { + let f = require_fn_item(fn_item); + Ok(ident_pnr(f.ident)) + } + (ParseNtResult::Fn(fn_item), sym::vis) => { + let _ = require_fn_item(fn_item); + Ok(vis_pnr(&fn_item.vis)) + } + (_, _) => err_unknown_field(), + } + } + } +} + +fn ident_pnr(ident: Ident) -> Cow<'static, ParseNtResult> { + Cow::Owned(ParseNtResult::Ident(ident, ident.is_raw_guess().into())) +} + +fn vis_pnr(vis: &ast::Visibility) -> Cow<'static, ParseNtResult> { + Cow::Owned(ParseNtResult::Vis(Box::new(vis.clone()))) +} + +fn pnr_type(pnr: &ParseNtResult) -> &'static str { + match pnr { + ParseNtResult::Tt(..) => "tt", + ParseNtResult::Ident(..) => "ident", + ParseNtResult::Lifetime(..) => "lifetime", + ParseNtResult::Item(..) => "item", + ParseNtResult::Fn(..) => "fn", + ParseNtResult::Adt(..) => "adt", + ParseNtResult::Block(..) => "block", + ParseNtResult::Stmt(..) => "stmt", + ParseNtResult::Pat(..) => "pat", + ParseNtResult::Expr(..) => "expr", + ParseNtResult::Literal(..) => "literal", + ParseNtResult::Ty(..) => "ty", + ParseNtResult::Meta(..) => "meta", + ParseNtResult::Path(..) => "path", + ParseNtResult::Vis(..) => "vis", + } +} + +fn require_fn_item(item: &ast::Item) -> &ast::Fn { + match item.kind { + ItemKind::Fn(ref f) => &f, + _ => panic!("`fn` item was not a fn"), + } +} + /// Handle the `${concat(...)}` metavariable expression. fn metavar_expr_concat<'tx>( tscx: &mut TranscrCtx<'tx, '_>, @@ -891,8 +1001,19 @@ fn matched_from_ident<'ctx, 'interp, 'rslt>( where 'interp: 'rslt, { - let span = ident.span; - let key = MacroRulesNormalizedIdent::new(ident); + matched_from_mrn_ident(dcx, ident.span, MacroRulesNormalizedIdent::new(ident), interp) +} + +/// Returns a `NamedMatch` item declared on the LHS given an arbitrary [MacroRulesNormalizedIdent] +fn matched_from_mrn_ident<'ctx, 'interp, 'rslt>( + dcx: DiagCtxtHandle<'ctx>, + span: Span, + key: MacroRulesNormalizedIdent, + interp: &'interp FxHashMap, +) -> PResult<'ctx, &'rslt NamedMatch> +where + 'interp: 'rslt, +{ interp.get(&key).ok_or_else(|| dcx.create_err(MveUnrecognizedVar { span, key })) } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index e63f29a9570de..04161f8eee677 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -553,6 +553,10 @@ declare_features! ( (unstable, macro_attr, "1.91.0", Some(143547)), /// Allow `macro_rules!` derive rules (unstable, macro_derive, "1.91.0", Some(143549)), + /// Allow macro fragment fields: `${fragment.field}` + (incomplete, macro_fragment_fields, "CURRENT_RUSTC_VERSION", Some(147023)), + /// Allow `macro_rules!` fragments `:fn` and `:adt` + (incomplete, macro_fragments_more, "CURRENT_RUSTC_VERSION", Some(147023)), /// Give access to additional metadata about declarative macro meta-variables. (unstable, macro_metavar_expr, "1.61.0", Some(83527)), /// Provides a way to concatenate identifiers using metavariable expressions. diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index f83cf645f829d..5a6e660af86de 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -721,6 +721,8 @@ parse_no_digits_literal = no valid digits found for number parse_non_string_abi_literal = non-string ABI literal .suggestion = specify the ABI with a string literal +parse_nonterminal_expected_adt = expected a struct, enum, or union +parse_nonterminal_expected_fn = expected a function parse_nonterminal_expected_ident = expected ident, found `{$token}` parse_nonterminal_expected_item_keyword = expected an item keyword parse_nonterminal_expected_lifetime = expected a lifetime, found `{$token}` diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 1abeee6fe433e..61f7eb6a23fe1 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -2169,6 +2169,10 @@ pub(crate) enum UnexpectedNonterminal { Item(#[primary_span] Span), #[diag(parse_nonterminal_expected_statement)] Statement(#[primary_span] Span), + #[diag(parse_nonterminal_expected_fn)] + Fn(#[primary_span] Span), + #[diag(parse_nonterminal_expected_adt)] + Adt(#[primary_span] Span), #[diag(parse_nonterminal_expected_ident)] Ident { #[primary_span] diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 88b67d792deb9..77c942af97f91 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -9,6 +9,7 @@ #![feature(default_field_values)] #![feature(if_let_guard)] #![feature(iter_intersperse)] +#![feature(type_changing_struct_update)] #![recursion_limit = "256"] // tidy-alphabetical-end diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs index 44fdf146f9c73..616e6ff026e24 100644 --- a/compiler/rustc_parse/src/parser/attr_wrapper.rs +++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs @@ -150,7 +150,7 @@ impl<'a> Parser<'a> { // if any of the following conditions hold. // - We are force collecting tokens (because force collection requires // tokens by definition). - let needs_collection = matches!(force_collect, ForceCollect::Yes) + let needs_collection = force_collect.yes() // - Any of our outer attributes require tokens. || needs_tokens(&attrs.attrs) // - Our target supports custom inner attributes (custom @@ -233,7 +233,7 @@ impl<'a> Parser<'a> { // We must collect if anything could observe the collected tokens, i.e. // if any of the following conditions hold. // - We are force collecting tokens. - let needs_collection = matches!(force_collect, ForceCollect::Yes) + let needs_collection = force_collect.yes() // - Any of our outer *or* inner attributes require tokens. // (`attr.attrs` was just outer attributes, but `ret.attrs()` is // outer and inner attributes. So this check is more precise than diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index eb264f59fedbe..172b37c7fc5bd 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -140,17 +140,18 @@ impl<'a> Parser<'a> { fn_parse_mode: FnParseMode, force_collect: ForceCollect, ) -> PResult<'a, Option> { - if let Some(item) = - self.eat_metavar_seq(MetaVarKind::Item, |this| this.parse_item(ForceCollect::Yes)) + if let Some(item) = self + .eat_metavar_seq(MetaVarKind::Item, |this| this.parse_item(force_collect.for_reparse())) { let mut item = item.expect("an actual item"); attrs.prepend_to_nt_inner(&mut item.attrs); return Ok(Some(*item)); } + // FIXME: Handle `ForceCollect::Recursive` in more cases self.collect_tokens(None, attrs, force_collect, |this, mut attrs| { let lo = this.token.span; - let vis = this.parse_visibility(FollowedByType::No)?; + let vis = this.parse_visibility(force_collect.recurse(), FollowedByType::No)?; let mut def = this.parse_defaultness(); let kind = this.parse_item_kind( &mut attrs, @@ -1638,7 +1639,7 @@ impl<'a> Parser<'a> { self.collect_tokens(None, variant_attrs, ForceCollect::No, |this, variant_attrs| { let vlo = this.token.span; - let vis = this.parse_visibility(FollowedByType::No)?; + let vis = this.parse_visibility(ForceCollect::No, FollowedByType::No)?; if !this.recover_nested_adt_item(kw::Enum)? { return Ok((None, Trailing::No, UsePreAttrPos::No)); } @@ -1885,7 +1886,7 @@ impl<'a> Parser<'a> { snapshot = Some(p.create_snapshot_for_diagnostic()); } let lo = p.token.span; - let vis = match p.parse_visibility(FollowedByType::Yes) { + let vis = match p.parse_visibility(ForceCollect::No, FollowedByType::Yes) { Ok(vis) => vis, Err(err) => { if let Some(ref mut snapshot) = snapshot { @@ -1949,7 +1950,7 @@ impl<'a> Parser<'a> { self.recover_vcs_conflict_marker(); self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| { let lo = this.token.span; - let vis = this.parse_visibility(FollowedByType::No)?; + let vis = this.parse_visibility(ForceCollect::No, FollowedByType::No)?; let safety = this.parse_unsafe_field(); this.parse_single_struct_field(adt_ty, lo, vis, safety, attrs, ident_span) .map(|field| (field, Trailing::No, UsePreAttrPos::No)) @@ -2913,13 +2914,14 @@ impl<'a> Parser<'a> { else if self.check_keyword(exp!(Pub)) { let sp = sp_start.to(self.prev_token.span); if let Ok(snippet) = self.span_to_snippet(sp) { - let current_vis = match self.parse_visibility(FollowedByType::No) { - Ok(v) => v, - Err(d) => { - d.cancel(); - return Err(err); - } - }; + let current_vis = + match self.parse_visibility(ForceCollect::No, FollowedByType::No) { + Ok(v) => v, + Err(d) => { + d.cancel(); + return Err(err); + } + }; let vs = pprust::vis_to_string(¤t_vis); let vs = vs.trim_end(); diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index ed4069dae933c..8b407baa7edb9 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -141,8 +141,35 @@ enum BlockMode { /// regardless of whether or not it has attributes #[derive(Clone, Copy, Debug, PartialEq)] pub enum ForceCollect { - Yes, + /// Don't force collection of tokens No, + /// Force collection of tokens for the node, but not for its child nodes + Yes, + /// Force collection of tokens for the node and its child nodes + Recursive, +} + +impl ForceCollect { + #[inline] + fn recurse(self) -> Self { + match self { + Self::Recursive => Self::Recursive, + _ => Self::No, + } + } + + #[inline] + fn for_reparse(self) -> Self { + match self { + Self::Recursive => Self::Recursive, + _ => Self::Yes, + } + } + + #[inline] + fn yes(self) -> bool { + self != Self::No + } } /// If the next tokens are ill-formed `$ty::` recover them as `<$ty>::`. @@ -1455,13 +1482,21 @@ impl<'a> Parser<'a> { /// it's not a tuple struct field), and the contents within the parentheses aren't valid, /// so emit a proper diagnostic. // Public for rustfmt usage. - pub fn parse_visibility(&mut self, fbt: FollowedByType) -> PResult<'a, Visibility> { - if let Some(vis) = self - .eat_metavar_seq(MetaVarKind::Vis, |this| this.parse_visibility(FollowedByType::Yes)) - { + pub fn parse_visibility( + &mut self, + force_collect: ForceCollect, + fbt: FollowedByType, + ) -> PResult<'a, Visibility> { + if let Some(vis) = self.eat_metavar_seq(MetaVarKind::Vis, |this| { + this.parse_visibility(force_collect, FollowedByType::Yes) + }) { return Ok(vis); } + self.maybe_collect_tokens_no_attrs(force_collect, |this| this.parse_visibility_inner(fbt)) + } + + fn parse_visibility_inner(&mut self, fbt: FollowedByType) -> PResult<'a, Visibility> { if !self.eat_keyword(exp!(Pub)) { // We need a span for our `Spanned`, but there's inherently no // keyword to grab a span from for inherited visibility; an empty span at the @@ -1565,6 +1600,20 @@ impl<'a> Parser<'a> { } } + #[inline] + fn maybe_collect_tokens_no_attrs( + &mut self, + force_collect: ForceCollect, + f: impl FnOnce(&mut Self) -> PResult<'a, R>, + ) -> PResult<'a, R> { + if !force_collect.yes() { + return f(self); + } + self.collect_tokens(None, AttrWrapper::empty(), force_collect, |this, _attrs| { + Ok((f(this)?, Trailing::No, UsePreAttrPos::No)) + }) + } + fn collect_tokens_no_attrs( &mut self, f: impl FnOnce(&mut Self) -> PResult<'a, R>, @@ -1669,6 +1718,8 @@ pub enum ParseNtResult { Ident(Ident, IdentIsRaw), Lifetime(Ident, IdentIsRaw), Item(Box), + Fn(Box), + Adt(Box), Block(Box), Stmt(Box), Pat(Box, NtPatKind), diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index 42eb07858c2fe..6c605ed743411 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -1,3 +1,4 @@ +use rustc_ast::ItemKind; use rustc_ast::token::NtExprKind::*; use rustc_ast::token::NtPatKind::*; use rustc_ast::token::{self, InvisibleOrigin, MetaVarKind, NonterminalKind, Token}; @@ -101,16 +102,22 @@ impl<'a> Parser<'a> { token::Lifetime(..) | token::NtLifetime(..) => true, _ => false, }, - NonterminalKind::TT | NonterminalKind::Item | NonterminalKind::Stmt => { - token.kind.close_delim().is_none() - } + NonterminalKind::TT + | NonterminalKind::Item + | NonterminalKind::Stmt + | NonterminalKind::Fn + | NonterminalKind::Adt => token.kind.close_delim().is_none(), } } /// Parse a non-terminal (e.g. MBE `:pat` or `:ident`). Inlined because there is only one call /// site. #[inline] - pub fn parse_nonterminal(&mut self, kind: NonterminalKind) -> PResult<'a, ParseNtResult> { + pub fn parse_nonterminal( + &mut self, + kind: NonterminalKind, + collect: ForceCollect, + ) -> PResult<'a, ParseNtResult> { // A `macro_rules!` invocation may pass a captured item/expr to a proc-macro, // which requires having captured tokens available. Since we cannot determine // in advance whether or not a proc-macro will be (transitively) invoked, @@ -118,10 +125,32 @@ impl<'a> Parser<'a> { match kind { // Note that TT is treated differently to all the others. NonterminalKind::TT => Ok(ParseNtResult::Tt(self.parse_token_tree())), - NonterminalKind::Item => match self.parse_item(ForceCollect::Yes)? { + // FIXME: handle `ForceCollect::Recursive` in more kinds of nonterminals. + NonterminalKind::Item => match self.parse_item(collect)? { Some(item) => Ok(ParseNtResult::Item(item)), None => Err(self.dcx().create_err(UnexpectedNonterminal::Item(self.token.span))), }, + NonterminalKind::Fn => { + if let Some(item) = self.parse_item(collect)? + && let ItemKind::Fn(_) = item.kind + { + Ok(ParseNtResult::Fn(item)) + } else { + Err(self.dcx().create_err(UnexpectedNonterminal::Fn(self.token.span))) + } + } + NonterminalKind::Adt => { + if let Some(item) = self.parse_item(collect)? + && matches!( + item.kind, + ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..) + ) + { + Ok(ParseNtResult::Adt(item)) + } else { + Err(self.dcx().create_err(UnexpectedNonterminal::Adt(self.token.span))) + } + } NonterminalKind::Block => { // While a block *expression* may have attributes (e.g. `#[my_attr] { ... }`), // the ':block' matcher does not support them @@ -176,7 +205,7 @@ impl<'a> Parser<'a> { Ok(ParseNtResult::Meta(Box::new(self.parse_attr_item(ForceCollect::Yes)?))) } NonterminalKind::Vis => Ok(ParseNtResult::Vis(Box::new( - self.collect_tokens_no_attrs(|this| this.parse_visibility(FollowedByType::Yes))?, + self.parse_visibility(collect, FollowedByType::Yes)?, ))), NonterminalKind::Lifetime => { // We want to keep `'keyword` parsing, just like `keyword` is still diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 63b0de377c963..28fa60ccf44b6 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -422,6 +422,7 @@ symbols! { add_assign, add_with_overflow, address, + adt, adt_const_params, advanced_slice_patterns, adx_target_feature, @@ -1347,6 +1348,8 @@ symbols! { macro_derive, macro_escape, macro_export, + macro_fragment_fields, + macro_fragments_more, macro_lifetime_matcher, macro_literal_matcher, macro_metavar_expr, @@ -1817,6 +1820,7 @@ symbols! { result_ok_method, resume, return_position_impl_trait_in_trait, + return_type, return_type_notation, riscv_target_feature, rlib, diff --git a/tests/ui/feature-gates/feature-gate-macro-fragment-fields.rs b/tests/ui/feature-gates/feature-gate-macro-fragment-fields.rs new file mode 100644 index 0000000000000..8c0f4f20734df --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-macro-fragment-fields.rs @@ -0,0 +1,6 @@ +#![crate_type = "lib"] + +macro_rules! m { + //~v ERROR: macro fragment fields are unstable + ($x:ident) => { ${x.field} }; +} diff --git a/tests/ui/feature-gates/feature-gate-macro-fragment-fields.stderr b/tests/ui/feature-gates/feature-gate-macro-fragment-fields.stderr new file mode 100644 index 0000000000000..b7fb751fbcc10 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-macro-fragment-fields.stderr @@ -0,0 +1,13 @@ +error[E0658]: macro fragment fields are unstable + --> $DIR/feature-gate-macro-fragment-fields.rs:5:22 + | +LL | ($x:ident) => { ${x.field} }; + | ^^^^^^^^^ + | + = note: see issue #147023 for more information + = help: add `#![feature(macro_fragment_fields)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/feature-gate-macro-fragments-more.rs b/tests/ui/feature-gates/feature-gate-macro-fragments-more.rs new file mode 100644 index 0000000000000..5634ae48e35b6 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-macro-fragments-more.rs @@ -0,0 +1,7 @@ +#![crate_type = "lib"] + +macro_rules! use_fn { ($f:fn) => {} } +//~^ ERROR macro `:fn` and `:adt` fragments are unstable + +macro_rules! use_adt { ($f:adt) => {} } +//~^ ERROR macro `:fn` and `:adt` fragments are unstable diff --git a/tests/ui/feature-gates/feature-gate-macro-fragments-more.stderr b/tests/ui/feature-gates/feature-gate-macro-fragments-more.stderr new file mode 100644 index 0000000000000..d47e33ea923c9 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-macro-fragments-more.stderr @@ -0,0 +1,23 @@ +error[E0658]: macro `:fn` and `:adt` fragments are unstable + --> $DIR/feature-gate-macro-fragments-more.rs:3:24 + | +LL | macro_rules! use_fn { ($f:fn) => {} } + | ^^^^^ + | + = note: see issue #147023 for more information + = help: add `#![feature(macro_fragments_more)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: macro `:fn` and `:adt` fragments are unstable + --> $DIR/feature-gate-macro-fragments-more.rs:6:25 + | +LL | macro_rules! use_adt { ($f:adt) => {} } + | ^^^^^^ + | + = note: see issue #147023 for more information + = help: add `#![feature(macro_fragments_more)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/lint/unused/unused-macro-with-bad-frag-spec.stderr b/tests/ui/lint/unused/unused-macro-with-bad-frag-spec.stderr index 003c6975c95c4..18efdc97627bf 100644 --- a/tests/ui/lint/unused/unused-macro-with-bad-frag-spec.stderr +++ b/tests/ui/lint/unused/unused-macro-with-bad-frag-spec.stderr @@ -4,7 +4,7 @@ error: invalid fragment specifier `t_ty` LL | ($wrong:t_ty) => () | ^^^^^^^^^^^ | - = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility error: aborting due to 1 previous error diff --git a/tests/ui/macros/invalid-fragment-specifier.stderr b/tests/ui/macros/invalid-fragment-specifier.stderr index a51ea619b3662..2fe56cc05054e 100644 --- a/tests/ui/macros/invalid-fragment-specifier.stderr +++ b/tests/ui/macros/invalid-fragment-specifier.stderr @@ -4,7 +4,7 @@ error: invalid fragment specifier `id` LL | ($wrong:id) => {}; | ^^^^^^^^^ | - = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility error: invalid fragment specifier `r#if` --> $DIR/invalid-fragment-specifier.rs:7:6 @@ -12,7 +12,7 @@ error: invalid fragment specifier `r#if` LL | ($wrong:r#if) => {}; | ^^^^^^^^^^^ | - = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility error: aborting due to 2 previous errors diff --git a/tests/ui/macros/issue-21356.stderr b/tests/ui/macros/issue-21356.stderr index 5ff9264251487..ee2b24b36cb63 100644 --- a/tests/ui/macros/issue-21356.stderr +++ b/tests/ui/macros/issue-21356.stderr @@ -4,7 +4,7 @@ error: invalid fragment specifier `t_ty` LL | macro_rules! test { ($wrong:t_ty ..) => () } | ^^^^^^^^^^^ | - = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility error: aborting due to 1 previous error diff --git a/tests/ui/macros/issue-39404.stderr b/tests/ui/macros/issue-39404.stderr index 62d0bc1018c59..6a9eb3ec4b98a 100644 --- a/tests/ui/macros/issue-39404.stderr +++ b/tests/ui/macros/issue-39404.stderr @@ -5,7 +5,7 @@ LL | ($i) => {}; | ^^ | = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility help: try adding a specifier here | LL | ($i:spec) => {}; diff --git a/tests/ui/macros/macro-adt-fragment.rs b/tests/ui/macros/macro-adt-fragment.rs new file mode 100644 index 0000000000000..e59253cc067e3 --- /dev/null +++ b/tests/ui/macros/macro-adt-fragment.rs @@ -0,0 +1,99 @@ +//@ edition:2024 + +#![crate_type = "lib"] +#![feature(macro_attr)] +#![allow(incomplete_features)] +#![feature(macro_fragments_more)] + +macro_rules! parse_adt { + ($a:adt) => {}; +} + +parse_adt! { + struct S; +} + +parse_adt! { + pub(crate) struct S(u32); +} + +parse_adt! { + #[repr(C)] + pub struct S { + x: u64, + y: u64, + } +} + +parse_adt! { + enum E {} +} + +parse_adt! { + enum E { + V1, + V2, + } +} + +parse_adt! { + enum E { + V1, + V2(u64), + V3 { field: f64 }, + } +} + +parse_adt! { + union U { + f: f64, + u: u64, + } +} + +//~vv ERROR: expected a struct, enum, or union +parse_adt! { + fn f() {} +} + +//~vv ERROR: expected identifier +parse_adt! { + struct; +} + +//~vv ERROR: expected identifier +parse_adt! { + enum; +} + +//~vv ERROR: expected one of `!` or `::`, found `;` +parse_adt! { + union; +} + +macro_rules! adtattr { + attr() ($a:adt) => { parse_adt!($a); }; +} + +#[adtattr] +struct S { + u: u64, + i: i64, +} + +#[adtattr] +enum E1 { + V1, +} + +#[adtattr] +fn f() {} +//~^ ERROR: expected a struct, enum, or union + +#[adtattr] +type T = u64; +//~^ ERROR: expected a struct, enum, or union + +#[adtattr] +trait Trait {} +//~^ ERROR: expected a struct, enum, or union diff --git a/tests/ui/macros/macro-adt-fragment.stderr b/tests/ui/macros/macro-adt-fragment.stderr new file mode 100644 index 0000000000000..0cc40a72b6f3b --- /dev/null +++ b/tests/ui/macros/macro-adt-fragment.stderr @@ -0,0 +1,65 @@ +error: expected a struct, enum, or union + --> $DIR/macro-adt-fragment.rs:56:13 + | +LL | ($a:adt) => {}; + | ------ while parsing argument for this `adt` macro fragment +... +LL | fn f() {} + | ^ + +error: expected identifier, found `;` + --> $DIR/macro-adt-fragment.rs:61:11 + | +LL | ($a:adt) => {}; + | ------ while parsing argument for this `adt` macro fragment +... +LL | struct; + | ^ expected identifier + +error: expected identifier, found `;` + --> $DIR/macro-adt-fragment.rs:66:9 + | +LL | ($a:adt) => {}; + | ------ while parsing argument for this `adt` macro fragment +... +LL | enum; + | ^ expected identifier + +error: expected one of `!` or `::`, found `;` + --> $DIR/macro-adt-fragment.rs:71:10 + | +LL | ($a:adt) => {}; + | ------ while parsing argument for this `adt` macro fragment +... +LL | union; + | ^ expected one of `!` or `::` + +error: expected a struct, enum, or union + --> $DIR/macro-adt-fragment.rs:90:9 + | +LL | attr() ($a:adt) => { parse_adt!($a); }; + | ------ while parsing argument for this `adt` macro fragment +... +LL | fn f() {} + | ^ + +error: expected a struct, enum, or union + --> $DIR/macro-adt-fragment.rs:94:13 + | +LL | attr() ($a:adt) => { parse_adt!($a); }; + | ------ while parsing argument for this `adt` macro fragment +... +LL | type T = u64; + | ^ + +error: expected a struct, enum, or union + --> $DIR/macro-adt-fragment.rs:98:14 + | +LL | attr() ($a:adt) => { parse_adt!($a); }; + | ------ while parsing argument for this `adt` macro fragment +... +LL | trait Trait {} + | ^ + +error: aborting due to 7 previous errors + diff --git a/tests/ui/macros/macro-fn-fragment.rs b/tests/ui/macros/macro-fn-fragment.rs new file mode 100644 index 0000000000000..15e79d74f7b03 --- /dev/null +++ b/tests/ui/macros/macro-fn-fragment.rs @@ -0,0 +1,43 @@ +//@ edition:2024 + +#![crate_type = "lib"] +#![feature(macro_attr)] +#![allow(incomplete_features)] +#![feature(macro_fragments_more)] + +macro_rules! parse_fn { + ($f:fn) => {}; +} + +parse_fn! { + fn f1(); +} + +parse_fn! { + pub async fn f2() { + loop {} + } +} + +//~vv ERROR: expected a function +parse_fn! { + struct S; +} + +//~vv ERROR: expected identifier +parse_fn! { + extern "C" fn; +} + +macro_rules! fnattr { + attr() ($f:fn) => { parse_fn!($f); }; +} + +#[fnattr] +fn f3() {} + +#[fnattr] +enum E1 { + V1, +} +//~^ ERROR: expected a function diff --git a/tests/ui/macros/macro-fn-fragment.stderr b/tests/ui/macros/macro-fn-fragment.stderr new file mode 100644 index 0000000000000..7c22342b85c30 --- /dev/null +++ b/tests/ui/macros/macro-fn-fragment.stderr @@ -0,0 +1,29 @@ +error: expected a function + --> $DIR/macro-fn-fragment.rs:24:13 + | +LL | ($f:fn) => {}; + | ----- while parsing argument for this `fn` macro fragment +... +LL | struct S; + | ^ + +error: expected identifier, found `;` + --> $DIR/macro-fn-fragment.rs:29:18 + | +LL | ($f:fn) => {}; + | ----- while parsing argument for this `fn` macro fragment +... +LL | extern "C" fn; + | ^ expected identifier + +error: expected a function + --> $DIR/macro-fn-fragment.rs:42:1 + | +LL | attr() ($f:fn) => { parse_fn!($f); }; + | ----- while parsing argument for this `fn` macro fragment +... +LL | } + | ^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/macros/macro-fragment-fields-errors.rs b/tests/ui/macros/macro-fragment-fields-errors.rs new file mode 100644 index 0000000000000..b4fe0294fc0af --- /dev/null +++ b/tests/ui/macros/macro-fragment-fields-errors.rs @@ -0,0 +1,55 @@ +//@ edition:2024 +#![crate_type = "lib"] +#![feature(macro_metavar_expr)] +#![allow(incomplete_features)] +#![feature(macro_fragment_fields)] +#![feature(macro_fragments_more)] + +macro_rules! bad1 { + ($f:fn) => { ${f.unknown_field} }; + //~^ ERROR: expression of type `fn` has no field `unknown_field` +} + +bad1! { fn f() {} } + +macro_rules! bad2 { + ($f:fn) => { ${f.name.unknown_field} }; + //~^ ERROR: expression of type `ident` has no field `unknown_field` +} + +bad2! { fn f() {} } + +macro_rules! bad3 { + ($f:fn) => { ${f.name.unknown_field.unknown_field} }; + //~^ ERROR: expression of type `ident` has no field `unknown_field` +} + +bad3! { fn f() {} } + +macro_rules! bad4 { + ($f:item) => { ${f.name} }; + //~^ ERROR: expression of type `item` has no field `name` +} + +bad4! { fn f() {} } + +macro_rules! bad5 { + ($f:block) => { ${f.unknown_field} }; + //~^ ERROR: expression of type `block` has no field `unknown_field` +} + +bad5! { { 42; } } + +macro_rules! bad6 { + ($a:adt) => { ${a.unknown_field} }; + //~^ ERROR: expression of type `adt` has no field `unknown_field` +} + +bad6! { struct S; } + +macro_rules! bad7 { + ($a:adt) => { ${a.name.unknown_field} }; + //~^ ERROR: expression of type `ident` has no field `unknown_field` +} + +bad7! { struct S; } diff --git a/tests/ui/macros/macro-fragment-fields-errors.stderr b/tests/ui/macros/macro-fragment-fields-errors.stderr new file mode 100644 index 0000000000000..b0c6acc7ad2a4 --- /dev/null +++ b/tests/ui/macros/macro-fragment-fields-errors.stderr @@ -0,0 +1,44 @@ +error: expression of type `fn` has no field `unknown_field` + --> $DIR/macro-fragment-fields-errors.rs:9:22 + | +LL | ($f:fn) => { ${f.unknown_field} }; + | ^^^^^^^^^^^^^ + +error: expression of type `ident` has no field `unknown_field` + --> $DIR/macro-fragment-fields-errors.rs:16:27 + | +LL | ($f:fn) => { ${f.name.unknown_field} }; + | ^^^^^^^^^^^^^ + +error: expression of type `ident` has no field `unknown_field` + --> $DIR/macro-fragment-fields-errors.rs:23:27 + | +LL | ($f:fn) => { ${f.name.unknown_field.unknown_field} }; + | ^^^^^^^^^^^^^ + +error: expression of type `item` has no field `name` + --> $DIR/macro-fragment-fields-errors.rs:30:24 + | +LL | ($f:item) => { ${f.name} }; + | ^^^^ + +error: expression of type `block` has no field `unknown_field` + --> $DIR/macro-fragment-fields-errors.rs:37:25 + | +LL | ($f:block) => { ${f.unknown_field} }; + | ^^^^^^^^^^^^^ + +error: expression of type `adt` has no field `unknown_field` + --> $DIR/macro-fragment-fields-errors.rs:44:23 + | +LL | ($a:adt) => { ${a.unknown_field} }; + | ^^^^^^^^^^^^^ + +error: expression of type `ident` has no field `unknown_field` + --> $DIR/macro-fragment-fields-errors.rs:51:28 + | +LL | ($a:adt) => { ${a.name.unknown_field} }; + | ^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/tests/ui/macros/macro-fragment-fields.rs b/tests/ui/macros/macro-fragment-fields.rs new file mode 100644 index 0000000000000..02e76f7a1f084 --- /dev/null +++ b/tests/ui/macros/macro-fragment-fields.rs @@ -0,0 +1,141 @@ +//@ build-pass +//@ edition:2024 +#![crate_type = "lib"] +#![feature(const_cmp)] +#![feature(const_trait_impl)] +#![feature(macro_metavar_expr)] +#![allow(incomplete_features)] +#![feature(macro_fragment_fields)] +#![feature(macro_fragments_more)] + +macro_rules! assert_fn_name { + ($name:literal, $f:fn) => { + const _: () = { + assert!(stringify!(${f.name}) == $name); + }; + }; +} + +assert_fn_name! { + "f1", + fn f1() {} +} + +assert_fn_name! { + "f2", + extern "C" fn f2() {} +} + +macro_rules! assert_adt_name { + ($name:literal, $a:adt) => { + const _: () = { + assert!(stringify!(${a.name}) == $name); + }; + }; +} + +assert_adt_name! { + "S", + struct S; +} + +assert_adt_name! { + "S", + pub(crate) struct S(u32, T); +} + +assert_adt_name! { + "E", + enum E { + V1, + V2, + } +} + +assert_adt_name! { + "U", + union U { + f: f64, + u: u64, + } +} + +macro_rules! assert_fn_vis { + ($v:vis, $f:fn) => { + const _: () = { + assert!(stringify!(${f.vis}) == stringify!($v)); + }; + } +} + +assert_fn_vis! { + pub, + pub fn f() {} +} + +assert_fn_vis! { + pub(crate), + pub(crate) fn f() {} +} + +assert_fn_vis! { + , + fn f() {} +} + +macro_rules! assert_adt_vis { + ($v:vis, $s:adt) => { + const _: () = { + assert!(stringify!(${s.vis}) == stringify!($v)); + }; + } +} + +assert_adt_vis! { + pub, + pub struct S; +} + +assert_adt_vis! { + pub(crate), + pub(crate) union U {} +} + +macro_rules! assert_item_vis { + ($v:vis, $i:item) => { + const _: () = { + assert!(stringify!(${i.vis}) == stringify!($v)); + }; + } +} + +assert_item_vis! { + pub, + pub mod m; +} + +assert_item_vis! { + pub(crate), + pub(crate) type T = u32; +} + +assert_item_vis! { + pub(self), + pub(self) const Const: u32 = 42; +} + +macro_rules! use_vis { + ($f:fn) => {${f.vis} struct StructWithFnVis;} +} + +macro_rules! use_adt_vis { + ($s:adt) => {${s.vis} fn fn_with_struct_vis() {}} +} + +mod module { + use_vis! { pub fn f() {} } + use_adt_vis! { pub(crate) struct S; } +} + +const C: module::StructWithFnVis = module::StructWithFnVis; +use module::fn_with_struct_vis; diff --git a/tests/ui/macros/macro-match-nonterminal.stderr b/tests/ui/macros/macro-match-nonterminal.stderr index a92d099ca0083..f0c671dc3701f 100644 --- a/tests/ui/macros/macro-match-nonterminal.stderr +++ b/tests/ui/macros/macro-match-nonterminal.stderr @@ -5,7 +5,7 @@ LL | ($a, $b) => { | ^^ | = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility help: try adding a specifier here | LL | ($a:spec, $b) => { @@ -18,7 +18,7 @@ LL | ($a, $b) => { | ^^ | = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility help: try adding a specifier here | LL | ($a, $b:spec) => { diff --git a/tests/ui/macros/macro-missing-fragment-deduplication.stderr b/tests/ui/macros/macro-missing-fragment-deduplication.stderr index 29d2ae0e16edb..2fc22f4eec27b 100644 --- a/tests/ui/macros/macro-missing-fragment-deduplication.stderr +++ b/tests/ui/macros/macro-missing-fragment-deduplication.stderr @@ -5,7 +5,7 @@ LL | ($name) => {}; | ^^^^^ | = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility help: try adding a specifier here | LL | ($name:spec) => {}; diff --git a/tests/ui/macros/macro-missing-fragment.stderr b/tests/ui/macros/macro-missing-fragment.stderr index 886292378d1a0..ef41ca97bcec7 100644 --- a/tests/ui/macros/macro-missing-fragment.stderr +++ b/tests/ui/macros/macro-missing-fragment.stderr @@ -5,7 +5,7 @@ LL | ( $( any_token $field_rust_type )* ) => {}; | ^^^^^^^^^^^^^^^^ | = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility help: try adding a specifier here | LL | ( $( any_token $field_rust_type:spec )* ) => {}; @@ -18,7 +18,7 @@ LL | ( $name ) => {}; | ^^^^^ | = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility help: try adding a specifier here | LL | ( $name:spec ) => {}; @@ -31,7 +31,7 @@ LL | ( $name ) => {}; | ^^^^^ | = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility help: try adding a specifier here | LL | ( $name:spec ) => {}; diff --git a/tests/ui/macros/metavar-expressions/syntax-errors.stderr b/tests/ui/macros/metavar-expressions/syntax-errors.stderr index bf1c7673a6ce1..305c42663d62b 100644 --- a/tests/ui/macros/metavar-expressions/syntax-errors.stderr +++ b/tests/ui/macros/metavar-expressions/syntax-errors.stderr @@ -40,33 +40,33 @@ error: only unsuffixes integer literals are supported in meta-variable expressio LL | ( $( $i:ident ),* ) => { ${ index(1u32) } }; | ^^^^^ -error: expected `(` +error: expected `(` or `.` --> $DIR/syntax-errors.rs:32:33 | LL | ( $( $i:ident ),* ) => { ${ count } }; - | ^^^^^- help: try adding parentheses: `( /* ... */ )` - | | - | for this this metavariable expression + | ^^^^^ after this metavariable expression | - = note: metavariable expressions use function-like parentheses syntax + = note: metavariable expressions use parentheses for functions or dot for fields -error: expected `(` +error: expected `(` or `.` --> $DIR/syntax-errors.rs:49:33 | LL | ( $( $i:ident ),* ) => { ${ count $i ($i) } }; | ^^^^^ - unexpected token | | - | for this this metavariable expression + | after this metavariable expression | - = note: metavariable expressions use function-like parentheses syntax + = note: metavariable expressions use parentheses for functions or dot for fields -error: expected `(` +error: expected `(` or `.` --> $DIR/syntax-errors.rs:54:33 | LL | ( $( $i:ident ),* ) => { ${ count{i} } }; - | ^^^^^ for this this metavariable expression + | ^^^^^--- unexpected token + | | + | after this metavariable expression | - = note: metavariable expressions use function-like parentheses syntax + = note: metavariable expressions use parentheses for functions or dot for fields error: expected identifier, found `123` --> $DIR/syntax-errors.rs:59:23 diff --git a/tests/ui/parser/macro/issue-33569.stderr b/tests/ui/parser/macro/issue-33569.stderr index dd8e38f0d6e94..08b26d18a1466 100644 --- a/tests/ui/parser/macro/issue-33569.stderr +++ b/tests/ui/parser/macro/issue-33569.stderr @@ -11,7 +11,7 @@ LL | { $+ } => { | ^ | = 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: valid fragment specifiers are `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item`, `fn`, 'adt', and `vis`, along with `expr_2021` and `pat_param` for edition compatibility help: try adding a specifier here | LL | { $+:spec } => {