diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index abad190b072..a255b4f83ac 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -11,7 +11,7 @@ use crate::mbe::transcribe::transcribe; use rustc_ast as ast; use rustc_ast::token::{self, NonterminalKind, NtTT, Token, TokenKind::*}; use rustc_ast::tokenstream::{DelimSpan, TokenStream}; -use rustc_ast::NodeId; +use rustc_ast::{NodeId, DUMMY_NODE_ID}; use rustc_ast_pretty::pprust; use rustc_attr::{self as attr, TransparencyError}; use rustc_data_structures::fx::FxHashMap; @@ -471,7 +471,7 @@ pub fn compile_declarative_macro( ) .pop() .unwrap(); - valid &= check_lhs_nt_follows(&sess.parse_sess, features, &def.attrs, &tt); + valid &= check_lhs_nt_follows(&sess.parse_sess, features, &def, &tt); return tt; } } @@ -540,13 +540,13 @@ pub fn compile_declarative_macro( fn check_lhs_nt_follows( sess: &ParseSess, features: &Features, - attrs: &[ast::Attribute], + def: &ast::Item, lhs: &mbe::TokenTree, ) -> bool { // lhs is going to be like TokenTree::Delimited(...), where the // entire lhs is those tts. Or, it can be a "bare sequence", not wrapped in parens. if let mbe::TokenTree::Delimited(_, ref tts) = *lhs { - check_matcher(sess, features, attrs, &tts.tts) + check_matcher(sess, features, def, &tts.tts) } else { let msg = "invalid macro matcher; matchers must be contained in balanced delimiters"; sess.span_diagnostic.span_err(lhs.span(), msg); @@ -604,13 +604,13 @@ fn check_rhs(sess: &ParseSess, rhs: &mbe::TokenTree) -> bool { fn check_matcher( sess: &ParseSess, features: &Features, - attrs: &[ast::Attribute], + def: &ast::Item, matcher: &[mbe::TokenTree], ) -> bool { let first_sets = FirstSets::new(matcher); let empty_suffix = TokenSet::empty(); let err = sess.span_diagnostic.err_count(); - check_matcher_core(sess, features, attrs, &first_sets, matcher, &empty_suffix); + check_matcher_core(sess, features, def, &first_sets, matcher, &empty_suffix); err == sess.span_diagnostic.err_count() } @@ -857,7 +857,7 @@ impl TokenSet { fn check_matcher_core( sess: &ParseSess, features: &Features, - attrs: &[ast::Attribute], + def: &ast::Item, first_sets: &FirstSets, matcher: &[mbe::TokenTree], follow: &TokenSet, @@ -903,7 +903,7 @@ fn check_matcher_core( } TokenTree::Delimited(span, ref d) => { let my_suffix = TokenSet::singleton(d.close_tt(span)); - check_matcher_core(sess, features, attrs, first_sets, &d.tts, &my_suffix); + check_matcher_core(sess, features, def, first_sets, &d.tts, &my_suffix); // don't track non NT tokens last.replace_with_irrelevant(); @@ -936,7 +936,7 @@ fn check_matcher_core( // `my_suffix` is some TokenSet that we can use // for checking the interior of `seq_rep`. let next = - check_matcher_core(sess, features, attrs, first_sets, &seq_rep.tts, my_suffix); + check_matcher_core(sess, features, def, first_sets, &seq_rep.tts, my_suffix); if next.maybe_empty { last.add_all(&next); } else { @@ -956,29 +956,31 @@ fn check_matcher_core( for token in &last.tokens { if let TokenTree::MetaVarDecl(span, name, Some(kind)) = *token { for next_token in &suffix_first.tokens { - // Check if the old pat is used and the next token is `|`. - if let NonterminalKind::PatParam { inferred: true } = kind { - if let TokenTree::Token(token) = next_token { - if let BinOp(token) = token.kind { - if let token::BinOpToken::Or = token { - // It is suggestion to use pat_param, for example: $x:pat -> $x:pat_param. - let suggestion = quoted_tt_to_string(&TokenTree::MetaVarDecl( - span, - name, - Some(NonterminalKind::PatParam { inferred: false }), - )); - sess.buffer_lint_with_diagnostic( - &OR_PATTERNS_BACK_COMPAT, - span, - ast::CRATE_NODE_ID, - &*format!("the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro",), - BuiltinLintDiagnostics::OrPatternsBackCompat( - span, suggestion, - ), - ); - } - } - } + // Check if the old pat is used and the next token is `|` + // to warn about incompatibility with Rust 2021. + // We only emit this lint if we're parsing the original + // definition of this macro_rules, not while (re)parsing + // the macro when compiling another crate that is using the + // macro. (See #86567.) + // Macros defined in the current crate have a real node id, + // whereas macros from an external crate have a dummy id. + if def.id != DUMMY_NODE_ID + && matches!(kind, NonterminalKind::PatParam { inferred: true }) + && matches!(next_token, TokenTree::Token(token) if token.kind == BinOp(token::BinOpToken::Or)) + { + // It is suggestion to use pat_param, for example: $x:pat -> $x:pat_param. + let suggestion = quoted_tt_to_string(&TokenTree::MetaVarDecl( + span, + name, + Some(NonterminalKind::PatParam { inferred: false }), + )); + sess.buffer_lint_with_diagnostic( + &OR_PATTERNS_BACK_COMPAT, + span, + ast::CRATE_NODE_ID, + "the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro", + BuiltinLintDiagnostics::OrPatternsBackCompat(span, suggestion), + ); } match is_in_follow(next_token, kind) { IsInFollow::Yes => {} diff --git a/src/test/ui/macros/auxiliary/or-pattern.rs b/src/test/ui/macros/auxiliary/or-pattern.rs new file mode 100644 index 00000000000..a319c405eb6 --- /dev/null +++ b/src/test/ui/macros/auxiliary/or-pattern.rs @@ -0,0 +1,6 @@ +#![crate_type = "lib"] + +#[macro_export] +macro_rules! a { + ($x:pat|) => (); +} diff --git a/src/test/ui/macros/macro-or-patterns-back-compat.fixed b/src/test/ui/macros/macro-or-patterns-back-compat.fixed index f4e81a6be2a..70425429278 100644 --- a/src/test/ui/macros/macro-or-patterns-back-compat.fixed +++ b/src/test/ui/macros/macro-or-patterns-back-compat.fixed @@ -1,14 +1,19 @@ // run-rustfix +// aux-build:or-pattern.rs #![deny(or_patterns_back_compat)] #![allow(unused_macros)] +#[macro_use] +extern crate or_pattern; + macro_rules! foo { ($x:pat_param | $y:pat) => {} } //~^ ERROR the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro //~| WARN this was previously accepted macro_rules! bar { ($($x:pat_param)+ | $($y:pat)+) => {} } //~^ ERROR the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro //~| WARN this was previously accepted + macro_rules! baz { ($x:pat_param | $y:pat_param) => {} } // should be ok macro_rules! qux { ($x:pat_param | $y:pat) => {} } // should be ok macro_rules! ogg { ($x:pat_param | $y:pat_param) => {} } @@ -30,4 +35,5 @@ fn main() { let result: Result = Err(42); let int: i64 = match_any!(result, Ok(i) | Err(i) => i.into()); assert_eq!(int, 42); + a!(1|); } diff --git a/src/test/ui/macros/macro-or-patterns-back-compat.rs b/src/test/ui/macros/macro-or-patterns-back-compat.rs index 49affdd38da..b19942a830e 100644 --- a/src/test/ui/macros/macro-or-patterns-back-compat.rs +++ b/src/test/ui/macros/macro-or-patterns-back-compat.rs @@ -1,14 +1,19 @@ // run-rustfix +// aux-build:or-pattern.rs #![deny(or_patterns_back_compat)] #![allow(unused_macros)] +#[macro_use] +extern crate or_pattern; + macro_rules! foo { ($x:pat | $y:pat) => {} } //~^ ERROR the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro //~| WARN this was previously accepted macro_rules! bar { ($($x:pat)+ | $($y:pat)+) => {} } //~^ ERROR the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro //~| WARN this was previously accepted + macro_rules! baz { ($x:pat_param | $y:pat_param) => {} } // should be ok macro_rules! qux { ($x:pat_param | $y:pat) => {} } // should be ok macro_rules! ogg { ($x:pat | $y:pat_param) => {} } @@ -30,4 +35,5 @@ fn main() { let result: Result = Err(42); let int: i64 = match_any!(result, Ok(i) | Err(i) => i.into()); assert_eq!(int, 42); + a!(1|); } diff --git a/src/test/ui/macros/macro-or-patterns-back-compat.stderr b/src/test/ui/macros/macro-or-patterns-back-compat.stderr index 62687eb36b8..4f5a450518f 100644 --- a/src/test/ui/macros/macro-or-patterns-back-compat.stderr +++ b/src/test/ui/macros/macro-or-patterns-back-compat.stderr @@ -1,11 +1,11 @@ error: the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro - --> $DIR/macro-or-patterns-back-compat.rs:6:21 + --> $DIR/macro-or-patterns-back-compat.rs:10:21 | LL | macro_rules! foo { ($x:pat | $y:pat) => {} } | ^^^^^^ help: use pat_param to preserve semantics: `$x:pat_param` | note: the lint level is defined here - --> $DIR/macro-or-patterns-back-compat.rs:3:9 + --> $DIR/macro-or-patterns-back-compat.rs:4:9 | LL | #![deny(or_patterns_back_compat)] | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL | #![deny(or_patterns_back_compat)] = note: for more information, see issue #84869 error: the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro - --> $DIR/macro-or-patterns-back-compat.rs:9:23 + --> $DIR/macro-or-patterns-back-compat.rs:13:23 | LL | macro_rules! bar { ($($x:pat)+ | $($y:pat)+) => {} } | ^^^^^^ help: use pat_param to preserve semantics: `$x:pat_param` @@ -22,7 +22,7 @@ LL | macro_rules! bar { ($($x:pat)+ | $($y:pat)+) => {} } = note: for more information, see issue #84869 error: the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro - --> $DIR/macro-or-patterns-back-compat.rs:14:21 + --> $DIR/macro-or-patterns-back-compat.rs:19:21 | LL | macro_rules! ogg { ($x:pat | $y:pat_param) => {} } | ^^^^^^ help: use pat_param to preserve semantics: `$x:pat_param` @@ -31,7 +31,7 @@ LL | macro_rules! ogg { ($x:pat | $y:pat_param) => {} } = note: for more information, see issue #84869 error: the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro - --> $DIR/macro-or-patterns-back-compat.rs:18:26 + --> $DIR/macro-or-patterns-back-compat.rs:23:26 | LL | ( $expr:expr , $( $( $pat:pat )|+ => $expr_arm:expr ),+ ) => { | ^^^^^^^^ help: use pat_param to preserve semantics: `$pat:pat_param`