rustc_lint/
macro_expr_fragment_specifier_2024_migration.rs

1//! Migration code for the `expr_fragment_specifier_2024` rule.
2
3use rustc_ast::token::{Token, TokenKind};
4use rustc_ast::tokenstream::{TokenStream, TokenTree};
5use rustc_session::lint::FutureIncompatibilityReason;
6use rustc_session::{declare_lint, declare_lint_pass};
7use rustc_span::edition::Edition;
8use rustc_span::sym;
9use tracing::debug;
10
11use crate::EarlyLintPass;
12use crate::lints::MacroExprFragment2024;
13
14declare_lint! {
15    /// The `edition_2024_expr_fragment_specifier` lint detects the use of
16    /// `expr` fragments in macros during migration to the 2024 edition.
17    ///
18    /// The `expr` fragment specifier will accept more expressions in the 2024
19    /// edition. To maintain the behavior from the 2021 edition and earlier, use
20    /// the `expr_2021` fragment specifier.
21    ///
22    /// ### Example
23    ///
24    /// ```rust,edition2021,compile_fail
25    /// #![deny(edition_2024_expr_fragment_specifier)]
26    /// macro_rules! m {
27    ///   ($e:expr) => {
28    ///       $e
29    ///   }
30    /// }
31    ///
32    /// fn main() {
33    ///    m!(1);
34    /// }
35    /// ```
36    ///
37    /// {{produces}}
38    ///
39    /// ### Explanation
40    ///
41    /// Rust [editions] allow the language to evolve without breaking backwards
42    /// compatibility. This lint catches code that uses [macro matcher fragment
43    /// specifiers] that have changed meaning in the 2024 edition. If you switch
44    /// to the new edition without updating the code, your macros may behave
45    /// differently.
46    ///
47    /// In the 2024 edition, the `expr` fragment specifier `expr` will also
48    /// match `const { ... }` blocks. This means if a macro had a pattern that
49    /// matched `$e:expr` and another that matches `const { $e: expr }`, for
50    /// example, that under the 2024 edition the first pattern would match while
51    /// in the 2021 and earlier editions the second pattern would match. To keep
52    /// the old behavior, use the `expr_2021` fragment specifier.
53    ///
54    /// This lint detects macros whose behavior might change due to the changing
55    /// meaning of the `expr` fragment specifier. It is "allow" by default
56    /// because the code is perfectly valid in older editions. The [`cargo fix`]
57    /// tool with the `--edition` flag will switch this lint to "warn" and
58    /// automatically apply the suggested fix from the compiler. This provides a
59    /// completely automated way to update old code for a new edition.
60    ///
61    /// Using `cargo fix --edition` with this lint will ensure that your code
62    /// retains the same behavior. This may not be the desired, as macro authors
63    /// often will want their macros to use the latest grammar for matching
64    /// expressions. Be sure to carefully review changes introduced by this lint
65    /// to ensure the macros implement the desired behavior.
66    ///
67    /// [editions]: https://doc.rust-lang.org/edition-guide/
68    /// [macro matcher fragment specifiers]: https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html
69    /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
70    pub EDITION_2024_EXPR_FRAGMENT_SPECIFIER,
71    Allow,
72    "The `expr` fragment specifier will accept more expressions in the 2024 edition. \
73    To keep the existing behavior, use the `expr_2021` fragment specifier.",
74    @future_incompatible = FutureIncompatibleInfo {
75        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
76        reference: "Migration Guide <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html>",
77    };
78}
79
80declare_lint_pass!(Expr2024 => [EDITION_2024_EXPR_FRAGMENT_SPECIFIER,]);
81
82impl Expr2024 {
83    fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: &TokenStream) {
84        let mut prev_colon = false;
85        let mut prev_identifier = false;
86        let mut prev_dollar = false;
87        for tt in tokens.iter() {
88            debug!(
89                "check_tokens: {:?} - colon {prev_dollar} - ident {prev_identifier} - colon {prev_colon}",
90                tt
91            );
92            match tt {
93                TokenTree::Token(token, _) => match token.kind {
94                    TokenKind::Dollar => {
95                        prev_dollar = true;
96                        continue;
97                    }
98                    TokenKind::Ident(..) | TokenKind::NtIdent(..) => {
99                        if prev_colon && prev_identifier && prev_dollar {
100                            self.check_ident_token(cx, token);
101                        } else if prev_dollar {
102                            prev_identifier = true;
103                            continue;
104                        }
105                    }
106                    TokenKind::Colon => {
107                        if prev_dollar && prev_identifier {
108                            prev_colon = true;
109                            continue;
110                        }
111                    }
112                    _ => {}
113                },
114                TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts),
115            }
116            prev_colon = false;
117            prev_identifier = false;
118            prev_dollar = false;
119        }
120    }
121
122    fn check_ident_token(&mut self, cx: &crate::EarlyContext<'_>, token: &Token) {
123        debug!("check_ident_token: {:?}", token);
124        let (sym, edition) = match token.kind {
125            TokenKind::Ident(sym, _) => (sym, Edition::Edition2024),
126            _ => return,
127        };
128
129        debug!("token.span.edition(): {:?}", token.span.edition());
130        if token.span.edition() >= edition {
131            return;
132        }
133
134        if sym != sym::expr {
135            return;
136        }
137
138        debug!("emitting lint");
139        cx.builder.emit_span_lint(
140            &EDITION_2024_EXPR_FRAGMENT_SPECIFIER,
141            token.span.into(),
142            MacroExprFragment2024 { suggestion: token.span },
143        );
144    }
145}
146
147impl EarlyLintPass for Expr2024 {
148    fn check_mac_def(&mut self, cx: &crate::EarlyContext<'_>, mc: &rustc_ast::MacroDef) {
149        self.check_tokens(cx, &mc.body.tokens);
150    }
151}