diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 95400ac2ca3b0..030a7738b1d83 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -417,7 +417,7 @@ pub trait Emitter { if !redundant_span || always_backtrace { let msg: Cow<'static, _> = match trace.kind { ExpnKind::Macro(MacroKind::Attr, _) => { - "this procedural macro expansion".into() + "this attribute macro expansion".into() } ExpnKind::Macro(MacroKind::Derive, _) => { "this derive macro expansion".into() diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl index 5a53670c865d4..1f8f3be68092a 100644 --- a/compiler/rustc_expand/messages.ftl +++ b/compiler/rustc_expand/messages.ftl @@ -70,6 +70,9 @@ expand_invalid_fragment_specifier = invalid fragment specifier `{$fragment}` .help = {$help} +expand_macro_args_bad_delim = macro attribute argument matchers require parentheses +expand_macro_args_bad_delim_sugg = the delimiters should be `(` and `)` + expand_macro_body_stability = macros cannot have body stability attributes .label = invalid body stability attribute diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index fd1391a554a8a..e58269991fcb0 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -482,3 +482,21 @@ mod metavar_exprs { pub key: MacroRulesNormalizedIdent, } } + +#[derive(Diagnostic)] +#[diag(expand_macro_args_bad_delim)] +pub(crate) struct MacroArgsBadDelim { + #[primary_span] + pub span: Span, + #[subdiagnostic] + pub sugg: MacroArgsBadDelimSugg, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(expand_macro_args_bad_delim_sugg, applicability = "machine-applicable")] +pub(crate) struct MacroArgsBadDelimSugg { + #[suggestion_part(code = "(")] + pub open: Span, + #[suggestion_part(code = ")")] + pub close: Span, +} diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 7a280d671f413..0f490c57c38e9 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -7,29 +7,40 @@ use rustc_macros::Subdiagnostic; use rustc_parse::parser::{Parser, Recovery, token_descr}; use rustc_session::parse::ParseSess; use rustc_span::source_map::SourceMap; -use rustc_span::{ErrorGuaranteed, Ident, Span}; +use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span}; use tracing::debug; use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx}; use crate::expand::{AstFragmentKind, parse_ast_fragment}; use crate::mbe::macro_parser::ParseResult::*; use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser}; -use crate::mbe::macro_rules::{Tracker, try_match_macro}; +use crate::mbe::macro_rules::{Tracker, try_match_macro, try_match_macro_attr}; pub(super) fn failed_to_match_macro( psess: &ParseSess, sp: Span, def_span: Span, name: Ident, - arg: TokenStream, + attr_args: Option<&TokenStream>, + body: &TokenStream, rules: &[MacroRule], ) -> (Span, ErrorGuaranteed) { debug!("failed to match macro"); + let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { + psess.source_map().guess_head_span(def_span) + } else { + DUMMY_SP + }; + // An error occurred, try the expansion again, tracking the expansion closely for better // diagnostics. let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp); - let try_success_result = try_match_macro(psess, name, &arg, rules, &mut tracker); + let try_success_result = if let Some(attr_args) = attr_args { + try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker) + } else { + try_match_macro(psess, name, body, rules, &mut tracker) + }; if try_success_result.is_ok() { // Nonterminal parser recovery might turn failed matches into successful ones, @@ -47,6 +58,13 @@ pub(super) fn failed_to_match_macro( let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure else { + if attr_args.is_none() && !rules.iter().any(|rule| matches!(rule, MacroRule::Func { .. })) { + let mut err = psess.dcx().struct_span_err(sp, "invoked macro has no invocation rules"); + if !def_head_span.is_dummy() { + err.span_label(def_head_span, "this macro has no rules to invoke"); + } + return (sp, err.emit()); + } return (sp, psess.dcx().span_delayed_bug(sp, "failed to match a macro")); }; @@ -54,8 +72,8 @@ pub(super) fn failed_to_match_macro( let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)); err.span_label(span, label); - if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { - err.span_label(psess.source_map().guess_head_span(def_span), "when calling this macro"); + if !def_head_span.is_dummy() { + err.span_label(def_head_span, "when calling this macro"); } annotate_doc_comment(&mut err, psess.source_map(), span); @@ -79,13 +97,16 @@ pub(super) fn failed_to_match_macro( } // Check whether there's a missing comma in this macro call, like `println!("{}" a);` - if let Some((arg, comma_span)) = arg.add_comma() { + if attr_args.is_none() + && let Some((body, comma_span)) = body.add_comma() + { for rule in rules { - let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed); + let MacroRule::Func { lhs, .. } = rule else { continue }; + 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), &rule.lhs, &mut NoopTracker) + tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker) { if comma_span.is_dummy() { err.note("you might be missing a comma"); @@ -116,13 +137,13 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> { struct BestFailure { token: Token, - position_in_tokenstream: u32, + position_in_tokenstream: (bool, u32), msg: &'static str, remaining_matcher: MatcherLoc, } impl BestFailure { - fn is_better_position(&self, position: u32) -> bool { + fn is_better_position(&self, position: (bool, u32)) -> bool { position > self.position_in_tokenstream } } @@ -142,7 +163,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match } } - fn after_arm(&mut self, result: &NamedParseResult) { + fn after_arm(&mut self, in_body: bool, result: &NamedParseResult) { match result { Success(_) => { // Nonterminal parser recovery might turn failed matches into successful ones, @@ -155,14 +176,15 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match Failure((token, approx_position, msg)) => { debug!(?token, ?msg, "a new failure of an arm"); + let position_in_tokenstream = (in_body, *approx_position); if self .best_failure .as_ref() - .is_none_or(|failure| failure.is_better_position(*approx_position)) + .is_none_or(|failure| failure.is_better_position(position_in_tokenstream)) { self.best_failure = Some(BestFailure { token: *token, - position_in_tokenstream: *approx_position, + position_in_tokenstream, msg, remaining_matcher: self .remaining_matcher diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs index bbdff866feba4..25987a5036635 100644 --- a/compiler/rustc_expand/src/mbe/macro_check.rs +++ b/compiler/rustc_expand/src/mbe/macro_check.rs @@ -193,15 +193,19 @@ struct MacroState<'a> { /// Arguments: /// - `psess` is used to emit diagnostics and lints /// - `node_id` is used to emit lints -/// - `lhs` and `rhs` represent the rule +/// - `args`, `lhs`, and `rhs` represent the rule pub(super) fn check_meta_variables( psess: &ParseSess, node_id: NodeId, + args: Option<&TokenTree>, lhs: &TokenTree, rhs: &TokenTree, ) -> Result<(), ErrorGuaranteed> { let mut guar = None; let mut binders = Binders::default(); + if let Some(args) = args { + check_binders(psess, node_id, args, &Stack::Empty, &mut binders, &Stack::Empty, &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); guar.map_or(Ok(()), Err) diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index febe6f8b88cc7..db3bf50c495e0 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -6,8 +6,8 @@ use std::{mem, slice}; use ast::token::IdentIsRaw; use rustc_ast::token::NtPatKind::*; use rustc_ast::token::TokenKind::*; -use rustc_ast::token::{self, NonterminalKind, Token, TokenKind}; -use rustc_ast::tokenstream::{DelimSpan, TokenStream}; +use rustc_ast::token::{self, Delimiter, NonterminalKind, Token, TokenKind}; +use rustc_ast::tokenstream::{self, DelimSpan, TokenStream}; use rustc_ast::{self as ast, DUMMY_NODE_ID, NodeId}; use rustc_ast_pretty::pprust; use rustc_attr_data_structures::{AttributeKind, find_attr}; @@ -22,23 +22,26 @@ use rustc_lint_defs::builtin::{ use rustc_parse::exp; use rustc_parse::parser::{Parser, Recovery}; use rustc_session::Session; -use rustc_session::parse::ParseSess; +use rustc_session::parse::{ParseSess, feature_err}; use rustc_span::edition::Edition; use rustc_span::hygiene::Transparency; use rustc_span::{Ident, Span, kw, sym}; use tracing::{debug, instrument, trace, trace_span}; +use super::diagnostics::failed_to_match_macro; use super::macro_parser::{NamedMatches, NamedParseResult}; use super::{SequenceRepetition, diagnostics}; use crate::base::{ - DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, SyntaxExtension, - SyntaxExtensionKind, TTMacroExpander, + AttrProcMacro, DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, + SyntaxExtension, SyntaxExtensionKind, TTMacroExpander, }; +use crate::errors; use crate::expand::{AstFragment, AstFragmentKind, ensure_complete_parse, parse_ast_fragment}; +use crate::mbe::macro_check::check_meta_variables; use crate::mbe::macro_parser::{Error, ErrorReported, Failure, MatcherLoc, Success, TtParser}; use crate::mbe::quoted::{RulePart, parse_one_tt}; use crate::mbe::transcribe::transcribe; -use crate::mbe::{self, KleeneOp, macro_check}; +use crate::mbe::{self, KleeneOp}; pub(crate) struct ParserAnyMacro<'a> { parser: Parser<'a>, @@ -122,10 +125,11 @@ impl<'a> ParserAnyMacro<'a> { } } -pub(super) struct MacroRule { - pub(super) lhs: Vec, - lhs_span: Span, - rhs: mbe::TokenTree, +pub(super) enum MacroRule { + /// A function-style rule, for use with `m!()` + Func { lhs: Vec, lhs_span: Span, rhs: mbe::TokenTree }, + /// An attr rule, for use with `#[m]` + Attr { args: Vec, body: Vec, lhs_span: Span, rhs: mbe::TokenTree }, } pub struct MacroRulesMacroExpander { @@ -139,8 +143,9 @@ pub struct MacroRulesMacroExpander { impl MacroRulesMacroExpander { pub fn get_unused_rule(&self, rule_i: usize) -> Option<(&Ident, Span)> { // If the rhs contains an invocation like `compile_error!`, don't report it as unused. - let rule = &self.rules[rule_i]; - if has_compile_error_macro(&rule.rhs) { None } else { Some((&self.name, rule.lhs_span)) } + let (MacroRule::Func { lhs_span, ref rhs, .. } | MacroRule::Attr { lhs_span, ref rhs, .. }) = + self.rules[rule_i]; + if has_compile_error_macro(rhs) { None } else { Some((&self.name, lhs_span)) } } } @@ -164,6 +169,28 @@ impl TTMacroExpander for MacroRulesMacroExpander { } } +impl AttrProcMacro for MacroRulesMacroExpander { + fn expand( + &self, + cx: &mut ExtCtxt<'_>, + sp: Span, + args: TokenStream, + body: TokenStream, + ) -> Result { + expand_macro_attr( + cx, + sp, + self.span, + self.node_id, + self.name, + self.transparency, + args, + body, + &self.rules, + ) + } +} + struct DummyExpander(ErrorGuaranteed); impl TTMacroExpander for DummyExpander { @@ -196,7 +223,7 @@ pub(super) trait Tracker<'matcher> { /// This is called after an arm has been parsed, either successfully or unsuccessfully. When /// this is called, `before_match_loc` was called at least once (with a `MatcherLoc::Eof`). - fn after_arm(&mut self, _result: &NamedParseResult) {} + fn after_arm(&mut self, _in_body: bool, _result: &NamedParseResult) {} /// For tracing. fn description() -> &'static str; @@ -244,14 +271,17 @@ fn expand_macro<'cx>( match try_success_result { Ok((rule_index, rule, named_matches)) => { - let mbe::TokenTree::Delimited(rhs_span, _, ref rhs) = rule.rhs else { + let MacroRule::Func { rhs, .. } = rule else { + panic!("try_match_macro returned non-func rule"); + }; + let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else { cx.dcx().span_bug(sp, "malformed macro rhs"); }; - let arm_span = rule.rhs.span(); + let arm_span = rhs_span.entire(); // rhs has holes ( `$id` and `$(...)` that need filled) let id = cx.current_expansion.id; - let tts = match transcribe(psess, &named_matches, rhs, rhs_span, transparency, id) { + let tts = match transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) { Ok(tts) => tts, Err(err) => { let guar = err.emit(); @@ -279,13 +309,76 @@ fn expand_macro<'cx>( Err(CanRetry::Yes) => { // Retry and emit a better error. let (span, guar) = - diagnostics::failed_to_match_macro(cx.psess(), sp, def_span, name, arg, rules); + failed_to_match_macro(cx.psess(), sp, def_span, name, None, &arg, rules); cx.macro_error_and_trace_macros_diag(); DummyResult::any(span, guar) } } } +/// Expands the rules based macro defined by `rules` for a given attribute `args` and `body`. +#[instrument(skip(cx, transparency, args, body, rules))] +fn expand_macro_attr( + cx: &mut ExtCtxt<'_>, + sp: Span, + def_span: Span, + node_id: NodeId, + name: Ident, + transparency: Transparency, + args: TokenStream, + body: TokenStream, + rules: &[MacroRule], +) -> Result { + let psess = &cx.sess.psess; + // Macros defined in the current crate have a real node id, + // whereas macros from an external crate have a dummy id. + let is_local = node_id != DUMMY_NODE_ID; + + if cx.trace_macros() { + let msg = format!( + "expanding `$[{name}({})] {}`", + pprust::tts_to_string(&args), + pprust::tts_to_string(&body), + ); + trace_macros_note(&mut cx.expansions, sp, msg); + } + + // Track nothing for the best performance. + match try_match_macro_attr(psess, name, &args, &body, rules, &mut NoopTracker) { + Ok((i, rule, named_matches)) => { + let MacroRule::Attr { rhs, .. } = rule else { + panic!("try_macro_match_attr returned non-attr rule"); + }; + let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else { + cx.dcx().span_bug(sp, "malformed macro rhs"); + }; + + let id = cx.current_expansion.id; + let tts = transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) + .map_err(|e| e.emit())?; + + if cx.trace_macros() { + let msg = format!("to `{}`", pprust::tts_to_string(&tts)); + trace_macros_note(&mut cx.expansions, sp, msg); + } + + if is_local { + cx.resolver.record_macro_rule_usage(node_id, i); + } + + Ok(tts) + } + Err(CanRetry::No(guar)) => Err(guar), + Err(CanRetry::Yes) => { + // Retry and emit a better error. + let (_, guar) = + failed_to_match_macro(cx.psess(), sp, def_span, name, Some(&args), &body, rules); + cx.trace_macros_diag(); + Err(guar) + } + } +} + pub(super) enum CanRetry { Yes, /// We are not allowed to retry macro expansion as a fatal error has been emitted already. @@ -326,6 +419,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( // Try each arm's matchers. let mut tt_parser = TtParser::new(name); for (i, rule) in rules.iter().enumerate() { + let MacroRule::Func { lhs, .. } = rule else { continue }; let _tracing_span = trace_span!("Matching arm", %i); // Take a snapshot of the state of pre-expansion gating at this point. @@ -334,9 +428,9 @@ 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), &rule.lhs, track); + let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track); - track.after_arm(&result); + track.after_arm(true, &result); match result { Success(named_matches) => { @@ -371,6 +465,60 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( Err(CanRetry::Yes) } +/// Try expanding the macro attribute. Returns the index of the successful arm and its +/// named_matches if it was successful, and nothing if it failed. On failure, it's the caller's job +/// to use `track` accordingly to record all errors correctly. +#[instrument(level = "debug", skip(psess, attr_args, attr_body, rules, track), fields(tracking = %T::description()))] +pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( + psess: &ParseSess, + name: Ident, + attr_args: &TokenStream, + attr_body: &TokenStream, + rules: &'matcher [MacroRule], + track: &mut T, +) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { + // This uses the same strategy as `try_match_macro` + let args_parser = parser_from_cx(psess, attr_args.clone(), T::recovery()); + let body_parser = parser_from_cx(psess, attr_body.clone(), T::recovery()); + let mut tt_parser = TtParser::new(name); + for (i, rule) in rules.iter().enumerate() { + let MacroRule::Attr { args, body, .. } = rule else { continue }; + + 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); + track.after_arm(false, &result); + + let mut named_matches = match result { + Success(named_matches) => named_matches, + Failure(_) => { + mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()); + continue; + } + Error(_, _) => return Err(CanRetry::Yes), + ErrorReported(guar) => return Err(CanRetry::No(guar)), + }; + + let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track); + track.after_arm(true, &result); + + match result { + Success(body_named_matches) => { + psess.gated_spans.merge(gated_spans_snapshot); + named_matches.extend(body_named_matches); + return Ok((i, rule, named_matches)); + } + Failure(_) => { + mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) + } + Error(_, _) => return Err(CanRetry::Yes), + ErrorReported(guar) => return Err(CanRetry::No(guar)), + } + } + + Err(CanRetry::Yes) +} + /// Converts a macro item into a syntax extension. pub fn compile_declarative_macro( sess: &Session, @@ -381,13 +529,13 @@ pub fn compile_declarative_macro( span: Span, node_id: NodeId, edition: Edition, -) -> (SyntaxExtension, usize) { - let mk_syn_ext = |expander| { - let kind = SyntaxExtensionKind::LegacyBang(expander); +) -> (SyntaxExtension, Option>, usize) { + let mk_syn_ext = |kind| { let is_local = is_defined_in_current_crate(node_id); SyntaxExtension::new(sess, kind, span, Vec::new(), edition, ident.name, attrs, is_local) }; - let dummy_syn_ext = |guar| (mk_syn_ext(Arc::new(DummyExpander(guar))), 0); + let mk_bang_ext = |expander| mk_syn_ext(SyntaxExtensionKind::LegacyBang(expander)); + let dummy_syn_ext = |guar| (mk_bang_ext(Arc::new(DummyExpander(guar))), None, 0); let macro_rules = macro_def.macro_rules; let exp_sep = if macro_rules { exp!(Semi) } else { exp!(Comma) }; @@ -400,9 +548,31 @@ pub fn compile_declarative_macro( let mut guar = None; let mut check_emission = |ret: Result<(), ErrorGuaranteed>| guar = guar.or(ret.err()); + let mut has_attr_rules = false; let mut rules = Vec::new(); while p.token != token::Eof { + let args = if p.eat_keyword_noexpect(sym::attr) { + has_attr_rules = true; + if !features.macro_attr() { + let msg = "`macro_rules!` attributes are unstable"; + let e = feature_err(sess, sym::macro_attr, span, msg); + return dummy_syn_ext(e.emit()); + } + if let Some(guar) = check_no_eof(sess, &p, "expected macro attr args") { + return dummy_syn_ext(guar); + } + let args = p.parse_token_tree(); + check_emission(check_args_parens(sess, &args)); + let args = parse_one_tt(args, RulePart::Pattern, sess, node_id, features, edition); + check_emission(check_lhs(sess, node_id, &args)); + if let Some(guar) = check_no_eof(sess, &p, "expected macro attr body") { + return dummy_syn_ext(guar); + } + Some(args) + } else { + None + }; let lhs_tt = p.parse_token_tree(); let lhs_tt = parse_one_tt(lhs_tt, RulePart::Pattern, sess, node_id, features, edition); check_emission(check_lhs(sess, node_id, &lhs_tt)); @@ -415,7 +585,7 @@ 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(macro_check::check_meta_variables(&sess.psess, node_id, &lhs_tt, &rhs_tt)); + check_emission(check_meta_variables(&sess.psess, node_id, args.as_ref(), &lhs_tt, &rhs_tt)); let lhs_span = lhs_tt.span(); // Convert the lhs into `MatcherLoc` form, which is better for doing the // actual matching. @@ -424,7 +594,16 @@ pub fn compile_declarative_macro( } else { return dummy_syn_ext(guar.unwrap()); }; - rules.push(MacroRule { lhs, lhs_span, rhs: rhs_tt }); + if let Some(args) = args { + let lhs_span = args.span().to(lhs_span); + let mbe::TokenTree::Delimited(.., delimited) = args else { + return dummy_syn_ext(guar.unwrap()); + }; + let args = mbe::macro_parser::compute_locs(&delimited.tts); + rules.push(MacroRule::Attr { args, body: lhs, lhs_span, rhs: rhs_tt }); + } else { + rules.push(MacroRule::Func { lhs, lhs_span, rhs: rhs_tt }); + } if p.token == token::Eof { break; } @@ -450,9 +629,12 @@ 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 expander = - Arc::new(MacroRulesMacroExpander { name: ident, span, node_id, transparency, rules }); - (mk_syn_ext(expander), nrules) + let exp = Arc::new(MacroRulesMacroExpander { name: ident, span, node_id, transparency, rules }); + let opt_attr_ext = has_attr_rules.then(|| { + let exp = Arc::clone(&exp); + Arc::new(mk_syn_ext(SyntaxExtensionKind::Attr(exp))) + }); + (mk_bang_ext(exp), opt_attr_ext, nrules) } fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option { @@ -468,6 +650,19 @@ fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option Result<(), ErrorGuaranteed> { + // This does not handle the non-delimited case; that gets handled separately by `check_lhs`. + if let tokenstream::TokenTree::Delimited(dspan, _, delim, _) = args + && *delim != Delimiter::Parenthesis + { + return Err(sess.dcx().emit_err(errors::MacroArgsBadDelim { + span: dspan.entire(), + sugg: errors::MacroArgsBadDelimSugg { open: dspan.open, close: dspan.close }, + })); + } + Ok(()) +} + fn check_lhs(sess: &Session, node_id: NodeId, lhs: &mbe::TokenTree) -> Result<(), ErrorGuaranteed> { let e1 = check_lhs_nt_follows(sess, node_id, lhs); let e2 = check_lhs_no_empty_seq(sess, slice::from_ref(lhs)); diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index e985e04ba3307..24e460d6649b3 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -555,6 +555,8 @@ declare_features! ( (unstable, link_arg_attribute, "1.76.0", Some(99427)), /// Allows fused `loop`/`match` for direct intraprocedural jumps. (incomplete, loop_match, "CURRENT_RUSTC_VERSION", Some(132306)), + /// Allow `macro_rules!` attribute rules + (unstable, macro_attr, "CURRENT_RUSTC_VERSION", Some(83527)), /// 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_resolve/messages.ftl b/compiler/rustc_resolve/messages.ftl index 39e9a9cc58afd..381c5ceb1d439 100644 --- a/compiler/rustc_resolve/messages.ftl +++ b/compiler/rustc_resolve/messages.ftl @@ -53,6 +53,9 @@ resolve_binding_shadows_something_unacceptable = resolve_binding_shadows_something_unacceptable_suggestion = try specify the pattern arguments +resolve_builtin_macro_with_non_invocation_rules = + built-in macro `{$ident}` has non-invocation rules + resolve_cannot_be_reexported_crate_public = `{$ident}` is only public within the crate, and cannot be re-exported outside @@ -243,7 +246,7 @@ resolve_lowercase_self = .suggestion = try using `Self` resolve_macro_cannot_use_as_attr = - `{$ident}` exists, but a declarative macro cannot be used as an attribute macro + `{$ident}` exists, but has no `attr` rules resolve_macro_cannot_use_as_derive = `{$ident}` exists, but a declarative macro cannot be used as a derive macro diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index d6b1e4de6ea1a..8a9495afe31a8 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1054,6 +1054,14 @@ pub(crate) struct NameReservedInAttributeNamespace { pub(crate) ident: Ident, } +#[derive(Diagnostic)] +#[diag(resolve_builtin_macro_with_non_invocation_rules)] +pub(crate) struct BuiltinMacroWithNonInvocationRules { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + #[derive(Diagnostic)] #[diag(resolve_cannot_find_builtin_macro_with_name)] pub(crate) struct CannotFindBuiltinMacroWithName { diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index f5bc46bf05304..37ec728ab77b5 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -625,9 +625,20 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; match result { - Ok((binding, flags)) - if sub_namespace_match(binding.macro_kind(), macro_kind) => - { + Ok((binding, flags)) => { + let binding_macro_kind = binding.macro_kind(); + // If we're looking for an attribute, that might be supported by a + // `macro_rules!` macro. + if !(sub_namespace_match(binding_macro_kind, macro_kind) + || (binding_macro_kind == Some(MacroKind::Bang) + && macro_kind == Some(MacroKind::Attr) + && this + .get_macro(binding.res()) + .is_some_and(|macro_data| macro_data.attr_ext.is_some()))) + { + return None; + } + if finalize.is_none() || matches!(scope_set, ScopeSet::Late(..)) { return Some(Ok(binding)); } @@ -704,7 +715,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { innermost_result = Some((binding, flags)); } } - Ok(..) | Err(Determinacy::Determined) => {} + Err(Determinacy::Determined) => {} Err(Determinacy::Undetermined) => determinacy = Determinacy::Undetermined, } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 8115b87dcae06..6feb418db0998 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1027,13 +1027,14 @@ struct DeriveData { struct MacroData { ext: Arc, + attr_ext: Option>, nrules: usize, macro_rules: bool, } impl MacroData { fn new(ext: Arc) -> MacroData { - MacroData { ext, nrules: 0, macro_rules: false } + MacroData { ext, attr_ext: None, nrules: 0, macro_rules: false } } } diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 20504ea609df3..0e03f83e33bb4 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -834,7 +834,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } _ => None, }, - None => self.get_macro(res).map(|macro_data| Arc::clone(¯o_data.ext)), + None => self.get_macro(res).map(|macro_data| match kind { + Some(MacroKind::Attr) if let Some(ref ext) = macro_data.attr_ext => Arc::clone(ext), + _ => Arc::clone(¯o_data.ext), + }), }; Ok((ext, res)) } @@ -1167,7 +1170,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { node_id: NodeId, edition: Edition, ) -> MacroData { - let (mut ext, mut nrules) = compile_declarative_macro( + let (mut ext, attr_ext, mut nrules) = compile_declarative_macro( self.tcx.sess, self.tcx.features(), macro_def, @@ -1179,6 +1182,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ); if let Some(builtin_name) = ext.builtin_name { + if attr_ext.is_some() { + self.dcx().emit_err(errors::BuiltinMacroWithNonInvocationRules { span, ident }); + } // The macro was marked with `#[rustc_builtin_macro]`. if let Some(builtin_ext_kind) = self.builtin_macros.get(&builtin_name) { // The macro is a built-in, replace its expander function @@ -1190,7 +1196,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } - MacroData { ext: Arc::new(ext), nrules, macro_rules: macro_def.macro_rules } + MacroData { ext: Arc::new(ext), attr_ext, nrules, macro_rules: macro_def.macro_rules } } fn path_accessible( diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index d54175548e30e..5224e2c737fff 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1310,6 +1310,7 @@ symbols! { lt, m68k_target_feature, macro_at_most_once_rep, + macro_attr, macro_attributes_in_derive_output, macro_concat, macro_escape, diff --git a/src/tools/miri/tests/fail/alloc/alloc_error_handler_custom.stderr b/src/tools/miri/tests/fail/alloc/alloc_error_handler_custom.stderr index 29c56ca81f752..a2a4be30eca4a 100644 --- a/src/tools/miri/tests/fail/alloc/alloc_error_handler_custom.stderr +++ b/src/tools/miri/tests/fail/alloc/alloc_error_handler_custom.stderr @@ -11,7 +11,7 @@ note: inside `_::__rg_oom` --> tests/fail/alloc/alloc_error_handler_custom.rs:LL:CC | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | fn alloc_error_handler(layout: Layout) -> ! { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: inside `alloc::alloc::handle_alloc_error::rt_error` at RUSTLIB/alloc/src/alloc.rs:LL:CC diff --git a/tests/ui/alloc-error/alloc-error-handler-bad-signature-1.stderr b/tests/ui/alloc-error/alloc-error-handler-bad-signature-1.stderr index 15314fac37b2c..aad45c422e2b8 100644 --- a/tests/ui/alloc-error/alloc-error-handler-bad-signature-1.stderr +++ b/tests/ui/alloc-error/alloc-error-handler-bad-signature-1.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/alloc-error-handler-bad-signature-1.rs:10:1 | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | // fn oom( LL | || info: &Layout, LL | || ) -> () @@ -23,7 +23,7 @@ error[E0308]: mismatched types --> $DIR/alloc-error-handler-bad-signature-1.rs:10:1 | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | // fn oom( LL | || info: &Layout, LL | || ) -> () diff --git a/tests/ui/alloc-error/alloc-error-handler-bad-signature-2.stderr b/tests/ui/alloc-error/alloc-error-handler-bad-signature-2.stderr index 2ab4263841128..581d19474191f 100644 --- a/tests/ui/alloc-error/alloc-error-handler-bad-signature-2.stderr +++ b/tests/ui/alloc-error/alloc-error-handler-bad-signature-2.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/alloc-error-handler-bad-signature-2.rs:10:1 | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | // fn oom( LL | || info: Layout, LL | || ) { @@ -31,7 +31,7 @@ error[E0308]: mismatched types --> $DIR/alloc-error-handler-bad-signature-2.rs:10:1 | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | // fn oom( LL | || info: Layout, LL | || ) { diff --git a/tests/ui/alloc-error/alloc-error-handler-bad-signature-3.stderr b/tests/ui/alloc-error/alloc-error-handler-bad-signature-3.stderr index 3a410174f548e..91147df71eb4e 100644 --- a/tests/ui/alloc-error/alloc-error-handler-bad-signature-3.stderr +++ b/tests/ui/alloc-error/alloc-error-handler-bad-signature-3.stderr @@ -2,7 +2,7 @@ error[E0061]: this function takes 0 arguments but 1 argument was supplied --> $DIR/alloc-error-handler-bad-signature-3.rs:10:1 | LL | #[alloc_error_handler] - | ---------------------- in this procedural macro expansion + | ---------------------- in this attribute macro expansion LL | fn oom() -> ! { | _-^^^^^^^^^^^^ LL | | loop {} diff --git a/tests/ui/allocator/not-an-allocator.stderr b/tests/ui/allocator/not-an-allocator.stderr index 079bf9334ebc3..f33a698ed7817 100644 --- a/tests/ui/allocator/not-an-allocator.stderr +++ b/tests/ui/allocator/not-an-allocator.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `usize: GlobalAlloc` is not satisfied --> $DIR/not-an-allocator.rs:2:11 | LL | #[global_allocator] - | ------------------- in this procedural macro expansion + | ------------------- in this attribute macro expansion LL | static A: usize = 0; | ^^^^^ the trait `GlobalAlloc` is not implemented for `usize` | @@ -12,7 +12,7 @@ error[E0277]: the trait bound `usize: GlobalAlloc` is not satisfied --> $DIR/not-an-allocator.rs:2:11 | LL | #[global_allocator] - | ------------------- in this procedural macro expansion + | ------------------- in this attribute macro expansion LL | static A: usize = 0; | ^^^^^ the trait `GlobalAlloc` is not implemented for `usize` | @@ -23,7 +23,7 @@ error[E0277]: the trait bound `usize: GlobalAlloc` is not satisfied --> $DIR/not-an-allocator.rs:2:11 | LL | #[global_allocator] - | ------------------- in this procedural macro expansion + | ------------------- in this attribute macro expansion LL | static A: usize = 0; | ^^^^^ the trait `GlobalAlloc` is not implemented for `usize` | @@ -34,7 +34,7 @@ error[E0277]: the trait bound `usize: GlobalAlloc` is not satisfied --> $DIR/not-an-allocator.rs:2:11 | LL | #[global_allocator] - | ------------------- in this procedural macro expansion + | ------------------- in this attribute macro expansion LL | static A: usize = 0; | ^^^^^ the trait `GlobalAlloc` is not implemented for `usize` | diff --git a/tests/ui/allocator/two-allocators.stderr b/tests/ui/allocator/two-allocators.stderr index 5308232a20b59..1a9a5910eecc4 100644 --- a/tests/ui/allocator/two-allocators.stderr +++ b/tests/ui/allocator/two-allocators.stderr @@ -4,7 +4,7 @@ error: cannot define multiple global allocators LL | static A: System = System; | -------------------------- previous global allocator defined here LL | #[global_allocator] - | ------------------- in this procedural macro expansion + | ------------------- in this attribute macro expansion LL | static B: System = System; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot define a new global allocator diff --git a/tests/ui/custom_test_frameworks/mismatch.stderr b/tests/ui/custom_test_frameworks/mismatch.stderr index c7798635fbf17..7cdfaa9e804ee 100644 --- a/tests/ui/custom_test_frameworks/mismatch.stderr +++ b/tests/ui/custom_test_frameworks/mismatch.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `TestDescAndFn: Testable` is not satisfied --> $DIR/mismatch.rs:9:1 | LL | #[test] - | ------- in this procedural macro expansion + | ------- in this attribute macro expansion LL | fn wrong_kind(){} | ^^^^^^^^^^^^^^^^^ the trait `Testable` is not implemented for `TestDescAndFn` | diff --git a/tests/ui/feature-gates/feature-gate-macro-attr.rs b/tests/ui/feature-gates/feature-gate-macro-attr.rs new file mode 100644 index 0000000000000..8419d85114725 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-macro-attr.rs @@ -0,0 +1,4 @@ +#![crate_type = "lib"] + +macro_rules! myattr { attr() {} => {} } +//~^ ERROR `macro_rules!` attributes are unstable diff --git a/tests/ui/feature-gates/feature-gate-macro-attr.stderr b/tests/ui/feature-gates/feature-gate-macro-attr.stderr new file mode 100644 index 0000000000000..b58418527c5b0 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-macro-attr.stderr @@ -0,0 +1,13 @@ +error[E0658]: `macro_rules!` attributes are unstable + --> $DIR/feature-gate-macro-attr.rs:3:1 + | +LL | macro_rules! myattr { attr() {} => {} } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #83527 for more information + = help: add `#![feature(macro_attr)]` 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/macros/macro-rules-as-derive-or-attr-issue-132928.stderr b/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr index e5b913b208dca..77f8bef83a452 100644 --- a/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr +++ b/tests/ui/macros/macro-rules-as-derive-or-attr-issue-132928.stderr @@ -11,7 +11,7 @@ error: cannot find attribute `sample` in this scope --> $DIR/macro-rules-as-derive-or-attr-issue-132928.rs:5:3 | LL | macro_rules! sample { () => {} } - | ------ `sample` exists, but a declarative macro cannot be used as an attribute macro + | ------ `sample` exists, but has no `attr` rules LL | LL | #[sample] | ^^^^^^ diff --git a/tests/ui/macros/macro-rules-attr-error.rs b/tests/ui/macros/macro-rules-attr-error.rs new file mode 100644 index 0000000000000..f8e1028656cfa --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-error.rs @@ -0,0 +1,15 @@ +#![feature(macro_attr)] + +macro_rules! local_attr { + attr() { $($body:tt)* } => { + compile_error!(concat!("local_attr: ", stringify!($($body)*))); + }; + //~^^ ERROR: local_attr +} + +fn main() { + #[local_attr] + struct S; + + local_attr!(arg); //~ ERROR: invoked macro has no invocation rules +} diff --git a/tests/ui/macros/macro-rules-attr-error.stderr b/tests/ui/macros/macro-rules-attr-error.stderr new file mode 100644 index 0000000000000..3e342271d4549 --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-error.stderr @@ -0,0 +1,22 @@ +error: local_attr: struct S; + --> $DIR/macro-rules-attr-error.rs:5:9 + | +LL | compile_error!(concat!("local_attr: ", stringify!($($body)*))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | #[local_attr] + | ------------- in this attribute macro expansion + | + = note: this error originates in the attribute macro `local_attr` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invoked macro has no invocation rules + --> $DIR/macro-rules-attr-error.rs:14:5 + | +LL | macro_rules! local_attr { + | ----------------------- this macro has no rules to invoke +... +LL | local_attr!(arg); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/macros/macro-rules-attr-infinite-recursion.rs b/tests/ui/macros/macro-rules-attr-infinite-recursion.rs new file mode 100644 index 0000000000000..dc54c32cad364 --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-infinite-recursion.rs @@ -0,0 +1,12 @@ +#![crate_type = "lib"] +#![feature(macro_attr)] + +macro_rules! attr { + attr() { $($body:tt)* } => { + #[attr] $($body)* + }; + //~^^ ERROR: recursion limit reached +} + +#[attr] +struct S; diff --git a/tests/ui/macros/macro-rules-attr-infinite-recursion.stderr b/tests/ui/macros/macro-rules-attr-infinite-recursion.stderr new file mode 100644 index 0000000000000..7d9a94338f51f --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-infinite-recursion.stderr @@ -0,0 +1,14 @@ +error: recursion limit reached while expanding `#[attr]` + --> $DIR/macro-rules-attr-infinite-recursion.rs:6:9 + | +LL | #[attr] $($body)* + | ^^^^^^^ +... +LL | #[attr] + | ------- in this attribute macro expansion + | + = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`macro_rules_attr_infinite_recursion`) + = note: this error originates in the attribute macro `attr` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + diff --git a/tests/ui/macros/macro-rules-attr-nested.rs b/tests/ui/macros/macro-rules-attr-nested.rs new file mode 100644 index 0000000000000..af5c30f00dd9d --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-nested.rs @@ -0,0 +1,24 @@ +//@ run-pass +//@ check-run-results +#![feature(macro_attr)] + +macro_rules! nest { + attr() { struct $name:ident; } => { + println!("nest"); + #[nest(1)] + struct $name; + }; + attr(1) { struct $name:ident; } => { + println!("nest(1)"); + #[nest(2)] + struct $name; + }; + attr(2) { struct $name:ident; } => { + println!("nest(2)"); + }; +} + +fn main() { + #[nest] + struct S; +} diff --git a/tests/ui/macros/macro-rules-attr-nested.run.stdout b/tests/ui/macros/macro-rules-attr-nested.run.stdout new file mode 100644 index 0000000000000..46017f9ae084f --- /dev/null +++ b/tests/ui/macros/macro-rules-attr-nested.run.stdout @@ -0,0 +1,3 @@ +nest +nest(1) +nest(2) diff --git a/tests/ui/macros/macro-rules-attr.rs b/tests/ui/macros/macro-rules-attr.rs new file mode 100644 index 0000000000000..1d6f09b53e1a5 --- /dev/null +++ b/tests/ui/macros/macro-rules-attr.rs @@ -0,0 +1,90 @@ +//@ run-pass +//@ check-run-results +#![feature(macro_attr)] +#![warn(unused)] + +#[macro_export] +macro_rules! exported_attr { + attr($($args:tt)*) { $($body:tt)* } => { + println!( + "exported_attr: args={:?}, body={:?}", + stringify!($($args)*), + stringify!($($body)*), + ); + }; + { $($args:tt)* } => { + println!("exported_attr!({:?})", stringify!($($args)*)); + }; + attr() {} => { + unused_rule(); + }; + attr() {} => { + compile_error!(); + }; + {} => { + unused_rule(); + }; + {} => { + compile_error!(); + }; +} + +macro_rules! local_attr { + attr($($args:tt)*) { $($body:tt)* } => { + println!( + "local_attr: args={:?}, body={:?}", + stringify!($($args)*), + stringify!($($body)*), + ); + }; + { $($args:tt)* } => { + println!("local_attr!({:?})", stringify!($($args)*)); + }; + attr() {} => { //~ WARN: never used + unused_rule(); + }; + attr() {} => { + compile_error!(); + }; + {} => { //~ WARN: never used + unused_rule(); + }; + {} => { + compile_error!(); + }; +} + +fn main() { + #[crate::exported_attr] + struct S; + #[::exported_attr(arguments, key = "value")] + fn func(_arg: u32) {} + #[self::exported_attr(1)] + #[self::exported_attr(2)] + struct Twice; + + crate::exported_attr!(); + crate::exported_attr!(invoked, arguments); + + #[exported_attr] + struct S; + #[exported_attr(arguments, key = "value")] + fn func(_arg: u32) {} + #[exported_attr(1)] + #[exported_attr(2)] + struct Twice; + + exported_attr!(); + exported_attr!(invoked, arguments); + + #[local_attr] + struct S; + #[local_attr(arguments, key = "value")] + fn func(_arg: u32) {} + #[local_attr(1)] + #[local_attr(2)] + struct Twice; + + local_attr!(); + local_attr!(invoked, arguments); +} diff --git a/tests/ui/macros/macro-rules-attr.run.stdout b/tests/ui/macros/macro-rules-attr.run.stdout new file mode 100644 index 0000000000000..77aa94d54f801 --- /dev/null +++ b/tests/ui/macros/macro-rules-attr.run.stdout @@ -0,0 +1,15 @@ +exported_attr: args="", body="struct S;" +exported_attr: args="arguments, key = \"value\"", body="fn func(_arg: u32) {}" +exported_attr: args="1", body="#[self::exported_attr(2)] struct Twice;" +exported_attr!("") +exported_attr!("invoked, arguments") +exported_attr: args="", body="struct S;" +exported_attr: args="arguments, key = \"value\"", body="fn func(_arg: u32) {}" +exported_attr: args="1", body="#[exported_attr(2)] struct Twice;" +exported_attr!("") +exported_attr!("invoked, arguments") +local_attr: args="", body="struct S;" +local_attr: args="arguments, key = \"value\"", body="fn func(_arg: u32) {}" +local_attr: args="1", body="#[local_attr(2)] struct Twice;" +local_attr!("") +local_attr!("invoked, arguments") diff --git a/tests/ui/macros/macro-rules-attr.stderr b/tests/ui/macros/macro-rules-attr.stderr new file mode 100644 index 0000000000000..5941017b1349b --- /dev/null +++ b/tests/ui/macros/macro-rules-attr.stderr @@ -0,0 +1,21 @@ +warning: rule #3 of macro `local_attr` is never used + --> $DIR/macro-rules-attr.rs:43:9 + | +LL | attr() {} => { + | ^^^^^ + | +note: the lint level is defined here + --> $DIR/macro-rules-attr.rs:4:9 + | +LL | #![warn(unused)] + | ^^^^^^ + = note: `#[warn(unused_macro_rules)]` implied by `#[warn(unused)]` + +warning: rule #5 of macro `local_attr` is never used + --> $DIR/macro-rules-attr.rs:49:5 + | +LL | {} => { + | ^^ + +warning: 2 warnings emitted + diff --git a/tests/ui/parser/macro/macro-attr-bad.rs b/tests/ui/parser/macro/macro-attr-bad.rs new file mode 100644 index 0000000000000..4313a4d04abf4 --- /dev/null +++ b/tests/ui/parser/macro/macro-attr-bad.rs @@ -0,0 +1,32 @@ +#![crate_type = "lib"] +#![feature(macro_attr)] + +macro_rules! attr_incomplete_1 { attr } +//~^ ERROR macro definition ended unexpectedly + +macro_rules! attr_incomplete_2 { attr() } +//~^ ERROR macro definition ended unexpectedly + +macro_rules! attr_incomplete_3 { attr() {} } +//~^ ERROR expected `=>` + +macro_rules! attr_incomplete_4 { attr() {} => } +//~^ ERROR macro definition ended unexpectedly + +macro_rules! attr_noparens_1 { attr{} {} => {} } +//~^ ERROR macro attribute argument matchers require parentheses + +macro_rules! attr_noparens_2 { attr[] {} => {} } +//~^ ERROR macro attribute argument matchers require parentheses + +macro_rules! attr_noparens_3 { attr _ {} => {} } +//~^ ERROR invalid macro matcher + +macro_rules! attr_dup_matcher_1 { attr() {$x:ident $x:ident} => {} } +//~^ ERROR duplicate matcher binding + +macro_rules! attr_dup_matcher_2 { attr($x:ident $x:ident) {} => {} } +//~^ ERROR duplicate matcher binding + +macro_rules! attr_dup_matcher_3 { attr($x:ident) {$x:ident} => {} } +//~^ ERROR duplicate matcher binding diff --git a/tests/ui/parser/macro/macro-attr-bad.stderr b/tests/ui/parser/macro/macro-attr-bad.stderr new file mode 100644 index 0000000000000..4d286b6664937 --- /dev/null +++ b/tests/ui/parser/macro/macro-attr-bad.stderr @@ -0,0 +1,80 @@ +error: macro definition ended unexpectedly + --> $DIR/macro-attr-bad.rs:4:38 + | +LL | macro_rules! attr_incomplete_1 { attr } + | ^ expected macro attr args + +error: macro definition ended unexpectedly + --> $DIR/macro-attr-bad.rs:7:40 + | +LL | macro_rules! attr_incomplete_2 { attr() } + | ^ expected macro attr body + +error: expected `=>`, found end of macro arguments + --> $DIR/macro-attr-bad.rs:10:43 + | +LL | macro_rules! attr_incomplete_3 { attr() {} } + | ^ expected `=>` + +error: macro definition ended unexpectedly + --> $DIR/macro-attr-bad.rs:13:46 + | +LL | macro_rules! attr_incomplete_4 { attr() {} => } + | ^ expected right-hand side of macro rule + +error: macro attribute argument matchers require parentheses + --> $DIR/macro-attr-bad.rs:16:36 + | +LL | macro_rules! attr_noparens_1 { attr{} {} => {} } + | ^^ + | +help: the delimiters should be `(` and `)` + | +LL - macro_rules! attr_noparens_1 { attr{} {} => {} } +LL + macro_rules! attr_noparens_1 { attr() {} => {} } + | + +error: macro attribute argument matchers require parentheses + --> $DIR/macro-attr-bad.rs:19:36 + | +LL | macro_rules! attr_noparens_2 { attr[] {} => {} } + | ^^ + | +help: the delimiters should be `(` and `)` + | +LL - macro_rules! attr_noparens_2 { attr[] {} => {} } +LL + macro_rules! attr_noparens_2 { attr() {} => {} } + | + +error: invalid macro matcher; matchers must be contained in balanced delimiters + --> $DIR/macro-attr-bad.rs:22:37 + | +LL | macro_rules! attr_noparens_3 { attr _ {} => {} } + | ^ + +error: duplicate matcher binding + --> $DIR/macro-attr-bad.rs:25:52 + | +LL | macro_rules! attr_dup_matcher_1 { attr() {$x:ident $x:ident} => {} } + | -------- ^^^^^^^^ duplicate binding + | | + | previous binding + +error: duplicate matcher binding + --> $DIR/macro-attr-bad.rs:28:49 + | +LL | macro_rules! attr_dup_matcher_2 { attr($x:ident $x:ident) {} => {} } + | -------- ^^^^^^^^ duplicate binding + | | + | previous binding + +error: duplicate matcher binding + --> $DIR/macro-attr-bad.rs:31:51 + | +LL | macro_rules! attr_dup_matcher_3 { attr($x:ident) {$x:ident} => {} } + | -------- ^^^^^^^^ duplicate binding + | | + | previous binding + +error: aborting due to 10 previous errors + diff --git a/tests/ui/proc-macro/span-from-proc-macro.stderr b/tests/ui/proc-macro/span-from-proc-macro.stderr index 452c04df8779e..c79ab04eadf4e 100644 --- a/tests/ui/proc-macro/span-from-proc-macro.stderr +++ b/tests/ui/proc-macro/span-from-proc-macro.stderr @@ -10,7 +10,7 @@ LL | field: MissingType ::: $DIR/span-from-proc-macro.rs:8:1 | LL | #[error_from_attribute] - | ----------------------- in this procedural macro expansion + | ----------------------- in this attribute macro expansion error[E0412]: cannot find type `OtherMissingType` in this scope --> $DIR/auxiliary/span-from-proc-macro.rs:42:21 diff --git a/tests/ui/rfcs/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr b/tests/ui/rfcs/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr index b89c5e8dda840..020c2e6f24198 100644 --- a/tests/ui/rfcs/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr +++ b/tests/ui/rfcs/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `f32: Termination` is not satisfied --> $DIR/termination-trait-test-wrong-type.rs:6:31 | LL | #[test] - | ------- in this procedural macro expansion + | ------- in this attribute macro expansion LL | fn can_parse_zero_as_f32() -> Result { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Termination` is not implemented for `f32` | diff --git a/tests/ui/test-attrs/issue-12997-2.stderr b/tests/ui/test-attrs/issue-12997-2.stderr index 1123630a4a1f1..41d0074ad1a20 100644 --- a/tests/ui/test-attrs/issue-12997-2.stderr +++ b/tests/ui/test-attrs/issue-12997-2.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/issue-12997-2.rs:8:1 | LL | #[bench] - | -------- in this procedural macro expansion + | -------- in this attribute macro expansion LL | fn bar(x: isize) { } | ^^^^^^^^^^^^^^^^^^^^ | | diff --git a/tests/ui/test-attrs/test-function-signature.stderr b/tests/ui/test-attrs/test-function-signature.stderr index c025163c0bd9c..55d09970b3203 100644 --- a/tests/ui/test-attrs/test-function-signature.stderr +++ b/tests/ui/test-attrs/test-function-signature.stderr @@ -26,7 +26,7 @@ error[E0277]: the trait bound `i32: Termination` is not satisfied --> $DIR/test-function-signature.rs:9:13 | LL | #[test] - | ------- in this procedural macro expansion + | ------- in this attribute macro expansion LL | fn bar() -> i32 { | ^^^ the trait `Termination` is not implemented for `i32` |