diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 3e952558d29..f048f6fe8ad 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -439,6 +439,9 @@ lint_lintpass_by_hand = implementing `LintPass` by hand lint_macro_expanded_macro_exports_accessed_by_absolute_paths = macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths .note = the macro is defined here +lint_macro_expr_fragment_specifier_2024_migration = + the `expr` fragment specifier will accept more expressions in the 2024 edition + .suggestion = to keep the existing behavior, use the `expr_2021` fragment specifier lint_macro_is_private = macro `{$ident}` is private lint_macro_rule_never_used = rule #{$n} of macro `{$name}` is never used diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 17f9d4421ae..868a44a980a 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -60,6 +60,7 @@ mod let_underscore; mod levels; mod lints; +mod macro_expr_fragment_specifier_2024_migration; mod map_unit_fn; mod methods; mod multiple_supertrait_upcastable; @@ -97,6 +98,7 @@ use internal::*; use invalid_from_utf8::*; use let_underscore::*; +use macro_expr_fragment_specifier_2024_migration::*; use map_unit_fn::*; use methods::*; use multiple_supertrait_upcastable::*; @@ -170,6 +172,7 @@ fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { IncompleteInternalFeatures: IncompleteInternalFeatures, RedundantSemicolons: RedundantSemicolons, UnusedDocComment: UnusedDocComment, + Expr2024: Expr2024, ] ] ); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 7c5640f5959..54c73710eca 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -317,6 +317,13 @@ pub struct BuiltinTypeAliasGenericBounds<'a, 'b> { pub sub: Option>, } +#[derive(LintDiagnostic)] +#[diag(lint_macro_expr_fragment_specifier_2024_migration)] +pub struct MacroExprFragment2024 { + #[suggestion(code = "expr_2021", applicability = "machine-applicable")] + pub suggestion: Span, +} + pub struct BuiltinTypeAliasGenericBoundsSuggestion { pub suggestions: Vec<(Span, String)>, } diff --git a/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs new file mode 100644 index 00000000000..867e132b106 --- /dev/null +++ b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs @@ -0,0 +1,155 @@ +//! Migration code for the `expr_fragment_specifier_2024` +//! rule. +use tracing::debug; + +use rustc_ast::token::Token; +use rustc_ast::token::TokenKind; +use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenstream::TokenTree; +use rustc_session::declare_lint; +use rustc_session::declare_lint_pass; +use rustc_session::lint::FutureIncompatibilityReason; +use rustc_span::edition::Edition; +use rustc_span::sym; + +use crate::lints::MacroExprFragment2024; +use crate::EarlyLintPass; + +declare_lint! { + /// The `edition_2024_expr_fragment_specifier` lint detects the use of + /// `expr` fragments in macros during migration to the 2024 edition. + /// + /// The `expr` fragment specifier will accept more expressions in the 2024 + /// edition. To maintain the behavior from the 2021 edition and earlier, use + /// the `expr_2021` fragment specifier. + /// + /// ### Example + /// + /// ```rust,edition2021,compile_fail + /// #![deny(edition_2024_expr_fragment_specifier)] + /// macro_rules! m { + /// ($e:expr) => { + /// $e + /// } + /// } + /// + /// fn main() { + /// m!(1); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Rust [editions] allow the language to evolve without breaking backwards + /// compatibility. This lint catches code that uses [macro matcher fragment + /// specifiers] that have changed meaning in the 2024 edition. If you switch + /// to the new edition without updating the code, your macros may behave + /// differently. + /// + /// In the 2024 edition, the `expr` fragment specifier `expr` will also + /// match `const { ... }` blocks. This means if a macro had a pattern that + /// matched `$e:expr` and another that matches `const { $e: expr }`, for + /// example, that under the 2024 edition the first pattern would match while + /// in the 2021 and earlier editions the second pattern would match. To keep + /// the old behavior, use the `expr_2021` fragment specifier. + /// + /// This lint detects macros whose behavior might change due to the changing + /// meaning of the `expr` fragment specifier. It is "allow" by default + /// because the code is perfectly valid in older editions. The [`cargo fix`] + /// tool with the `--edition` flag will switch this lint to "warn" and + /// automatically apply the suggested fix from the compiler. This provides a + /// completely automated way to update old code for a new edition. + /// + /// Using `cargo fix --edition` with this lint will ensure that your code + /// retains the same behavior. This may not be the desired, as macro authors + /// often will want their macros to use the latest grammar for matching + /// expressions. Be sure to carefully review changes introduced by this lint + /// to ensure the macros implement the desired behavior. + /// + /// [editions]: https://doc.rust-lang.org/edition-guide/ + /// [macro matcher fragment specifiers]: https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html + /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html + pub EDITION_2024_EXPR_FRAGMENT_SPECIFIER, + Allow, + "The `expr` fragment specifier will accept more expressions in the 2024 edition. \ + To keep the existing behavior, use the `expr_2021` fragment specifier.", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), + reference: "Migration Guide ", + }; +} + +declare_lint_pass!(Expr2024 => [EDITION_2024_EXPR_FRAGMENT_SPECIFIER,]); + +impl Expr2024 { + fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: &TokenStream) { + let mut prev_colon = false; + let mut prev_identifier = false; + let mut prev_dollar = false; + for tt in tokens.trees() { + debug!( + "check_tokens: {:?} - colon {prev_dollar} - ident {prev_identifier} - colon {prev_colon}", + tt + ); + match tt { + TokenTree::Token(token, _) => match token.kind { + TokenKind::Dollar => { + prev_dollar = true; + continue; + } + TokenKind::Ident(..) | TokenKind::NtIdent(..) => { + if prev_colon && prev_identifier && prev_dollar { + self.check_ident_token(cx, token); + } else if prev_dollar { + prev_identifier = true; + continue; + } + } + TokenKind::Colon => { + if prev_dollar && prev_identifier { + prev_colon = true; + continue; + } + } + _ => {} + }, + TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts), + } + prev_colon = false; + prev_identifier = false; + prev_dollar = false; + } + } + + fn check_ident_token(&mut self, cx: &crate::EarlyContext<'_>, token: &Token) { + debug!("check_ident_token: {:?}", token); + let (sym, edition) = match token.kind { + TokenKind::Ident(sym, _) => (sym, Edition::Edition2024), + _ => return, + }; + + debug!("token.span.edition(): {:?}", token.span.edition()); + if token.span.edition() >= edition { + return; + } + + if sym != sym::expr { + return; + } + + debug!("emitting lint"); + cx.builder.emit_span_lint( + &EDITION_2024_EXPR_FRAGMENT_SPECIFIER, + token.span.into(), + MacroExprFragment2024 { suggestion: token.span }, + ); + } +} + +impl EarlyLintPass for Expr2024 { + fn check_mac_def(&mut self, cx: &crate::EarlyContext<'_>, mc: &rustc_ast::MacroDef) { + self.check_tokens(cx, &mc.body.tokens); + } +} diff --git a/tests/ui/macros/expr_2021_cargo_fix_edition.fixed b/tests/ui/macros/expr_2021_cargo_fix_edition.fixed new file mode 100644 index 00000000000..1becd8a92d6 --- /dev/null +++ b/tests/ui/macros/expr_2021_cargo_fix_edition.fixed @@ -0,0 +1,24 @@ +//@ run-rustfix +//@ check-pass +//@ compile-flags: --edition=2021 +#![allow(incomplete_features)] +#![feature(expr_fragment_specifier_2024)] +#![warn(edition_2024_expr_fragment_specifier)] + +macro_rules! m { + ($e:expr_2021) => { //~ WARN: the `expr` fragment specifier will accept more expressions in the 2024 edition + //~^ WARN: this changes meaning in Rust 2024 + $e + }; + ($($i:expr_2021)*) => { }; //~ WARN: the `expr` fragment specifier will accept more expressions in the 2024 edition + //~^ WARN: this changes meaning in Rust 2024 +} + +macro_rules! test { + (expr) => {} +} + +fn main() { + m!(()); + test!(expr); +} diff --git a/tests/ui/macros/expr_2021_cargo_fix_edition.rs b/tests/ui/macros/expr_2021_cargo_fix_edition.rs new file mode 100644 index 00000000000..ec0b86d2c23 --- /dev/null +++ b/tests/ui/macros/expr_2021_cargo_fix_edition.rs @@ -0,0 +1,24 @@ +//@ run-rustfix +//@ check-pass +//@ compile-flags: --edition=2021 +#![allow(incomplete_features)] +#![feature(expr_fragment_specifier_2024)] +#![warn(edition_2024_expr_fragment_specifier)] + +macro_rules! m { + ($e:expr) => { //~ WARN: the `expr` fragment specifier will accept more expressions in the 2024 edition + //~^ WARN: this changes meaning in Rust 2024 + $e + }; + ($($i:expr)*) => { }; //~ WARN: the `expr` fragment specifier will accept more expressions in the 2024 edition + //~^ WARN: this changes meaning in Rust 2024 +} + +macro_rules! test { + (expr) => {} +} + +fn main() { + m!(()); + test!(expr); +} diff --git a/tests/ui/macros/expr_2021_cargo_fix_edition.stderr b/tests/ui/macros/expr_2021_cargo_fix_edition.stderr new file mode 100644 index 00000000000..e8a44fed322 --- /dev/null +++ b/tests/ui/macros/expr_2021_cargo_fix_edition.stderr @@ -0,0 +1,33 @@ +warning: the `expr` fragment specifier will accept more expressions in the 2024 edition + --> $DIR/expr_2021_cargo_fix_edition.rs:9:9 + | +LL | ($e:expr) => { + | ^^^^ + | + = warning: this changes meaning in Rust 2024 + = note: for more information, see Migration Guide +note: the lint level is defined here + --> $DIR/expr_2021_cargo_fix_edition.rs:6:9 + | +LL | #![warn(edition_2024_expr_fragment_specifier)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: to keep the existing behavior, use the `expr_2021` fragment specifier + | +LL | ($e:expr_2021) => { + | ~~~~~~~~~ + +warning: the `expr` fragment specifier will accept more expressions in the 2024 edition + --> $DIR/expr_2021_cargo_fix_edition.rs:13:11 + | +LL | ($($i:expr)*) => { }; + | ^^^^ + | + = warning: this changes meaning in Rust 2024 + = note: for more information, see Migration Guide +help: to keep the existing behavior, use the `expr_2021` fragment specifier + | +LL | ($($i:expr_2021)*) => { }; + | ~~~~~~~~~ + +warning: 2 warnings emitted + diff --git a/tests/ui/macros/expr_2021_inline_const.edi2021.stderr b/tests/ui/macros/expr_2021_inline_const.edi2021.stderr index 5e880964454..b55ae62030c 100644 --- a/tests/ui/macros/expr_2021_inline_const.edi2021.stderr +++ b/tests/ui/macros/expr_2021_inline_const.edi2021.stderr @@ -1,5 +1,5 @@ error: no rules expected the token `const` - --> $DIR/expr_2021_inline_const.rs:21:12 + --> $DIR/expr_2021_inline_const.rs:26:12 | LL | macro_rules! m2021 { | ------------------ when calling this macro @@ -14,7 +14,7 @@ LL | ($e:expr_2021) => { | ^^^^^^^^^^^^ error: no rules expected the token `const` - --> $DIR/expr_2021_inline_const.rs:22:12 + --> $DIR/expr_2021_inline_const.rs:27:12 | LL | macro_rules! m2024 { | ------------------ when calling this macro diff --git a/tests/ui/macros/expr_2021_inline_const.edi2024.stderr b/tests/ui/macros/expr_2021_inline_const.edi2024.stderr index 237ecb2cc19..285db53d6c8 100644 --- a/tests/ui/macros/expr_2021_inline_const.edi2024.stderr +++ b/tests/ui/macros/expr_2021_inline_const.edi2024.stderr @@ -1,5 +1,5 @@ error: no rules expected the token `const` - --> $DIR/expr_2021_inline_const.rs:21:12 + --> $DIR/expr_2021_inline_const.rs:26:12 | LL | macro_rules! m2021 { | ------------------ when calling this macro diff --git a/tests/ui/macros/expr_2021_inline_const.rs b/tests/ui/macros/expr_2021_inline_const.rs index ebc5ea36421..06b74a466d6 100644 --- a/tests/ui/macros/expr_2021_inline_const.rs +++ b/tests/ui/macros/expr_2021_inline_const.rs @@ -17,7 +17,14 @@ macro_rules! m2024 { $e }; } + +macro_rules! test { + (expr) => {} +} + fn main() { m2021!(const { 1 }); //~ ERROR: no rules expected the token `const` m2024!(const { 1 }); //[edi2021]~ ERROR: no rules expected the token `const` + + test!(expr); }