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