//! Migration code for the `expr_fragment_specifier_2024` rule. use rustc_ast::token::{Token, TokenKind}; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_session::lint::FutureIncompatibilityReason; use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::edition::Edition; use rustc_span::sym; use tracing::debug; use crate::EarlyLintPass; use crate::lints::MacroExprFragment2024; 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/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.iter() { 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); } }