|
| 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 `expr` fragments |
| 20 | + /// during migration to the 2024 edition. |
| 21 | + /// |
| 22 | + /// The `expr` fragment specifier will accept more expressions in the 2024 edition. |
| 23 | + /// To maintain the current behavior, use the `expr_2021` fragment specifier. |
| 24 | + /// |
| 25 | + /// ### Example |
| 26 | + /// |
| 27 | + /// ```rust,edition2021,compile_fail |
| 28 | + /// #![deny(edition_2024_expr_fragment_specifier)] |
| 29 | + /// macro_rules! m { |
| 30 | + /// ($e:expr) => { |
| 31 | + /// $e |
| 32 | + /// } |
| 33 | + /// } |
| 34 | + /// |
| 35 | + /// fn main() { |
| 36 | + /// m!(1); |
| 37 | + /// } |
| 38 | + /// ``` |
| 39 | + /// |
| 40 | + /// {{produces}} |
| 41 | + /// |
| 42 | + /// ### Explanation |
| 43 | + /// |
| 44 | + /// Rust [editions] allow the language to evolve without breaking |
| 45 | + /// backwards compatibility. This lint catches code that uses new keywords |
| 46 | + /// that are added to the language that are used as identifiers (such as a |
| 47 | + /// variable name, function name, etc.). If you switch the compiler to a |
| 48 | + /// new edition without updating the code, then it will fail to compile if |
| 49 | + /// you are using a new keyword as an identifier. |
| 50 | + /// |
| 51 | + /// This lint solves the problem automatically. It is "allow" by default |
| 52 | + /// because the code is perfectly valid in older editions. The [`cargo |
| 53 | + /// fix`] tool with the `--edition` flag will switch this lint to "warn" |
| 54 | + /// and automatically apply the suggested fix from the compiler (which is |
| 55 | + /// to use a raw identifier). This provides a completely automated way to |
| 56 | + /// update old code for a new edition. |
| 57 | + /// |
| 58 | + /// [editions]: https://doc.rust-lang.org/edition-guide/ |
| 59 | + /// [raw identifier]: https://doc.rust-lang.org/reference/identifiers.html |
| 60 | + /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html |
| 61 | + pub EDITION_2024_EXPR_FRAGMENT_SPECIFIER, |
| 62 | + Allow, |
| 63 | + "The `expr` fragment specifier will accept more expressions in the 2024 edition. \ |
| 64 | + To keep the existing behavior, use the `expr_2021` fragment specifier.", |
| 65 | + @future_incompatible = FutureIncompatibleInfo { |
| 66 | + reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), |
| 67 | + reference: "Migration Guide <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html>", |
| 68 | + }; |
| 69 | +} |
| 70 | + |
| 71 | +declare_lint_pass!(Expr2024 => [EDITION_2024_EXPR_FRAGMENT_SPECIFIER,]); |
| 72 | + |
| 73 | +impl Expr2024 { |
| 74 | + fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: &TokenStream) { |
| 75 | + let mut prev_dollar = false; |
| 76 | + for tt in tokens.trees() { |
| 77 | + match tt { |
| 78 | + TokenTree::Token(token, _) => { |
| 79 | + if token.kind == TokenKind::Dollar { |
| 80 | + prev_dollar = true; |
| 81 | + continue; |
| 82 | + } else { |
| 83 | + if !prev_dollar { |
| 84 | + self.check_ident_token(cx, token); |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts), |
| 89 | + } |
| 90 | + prev_dollar = false; |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + fn check_ident_token(&mut self, cx: &crate::EarlyContext<'_>, token: &Token) { |
| 95 | + debug!("check_ident_token: {:?}", token); |
| 96 | + let (sym, edition) = match token.kind { |
| 97 | + TokenKind::Ident(sym, _) => (sym, Edition::Edition2024), |
| 98 | + _ => return, |
| 99 | + }; |
| 100 | + |
| 101 | + debug!("token.span.edition(): {:?}", token.span.edition()); |
| 102 | + if token.span.edition() >= edition { |
| 103 | + return; |
| 104 | + } |
| 105 | + |
| 106 | + if sym != sym::expr { |
| 107 | + return; |
| 108 | + } |
| 109 | + |
| 110 | + debug!("emitting lint"); |
| 111 | + cx.builder.emit_span_lint( |
| 112 | + &EDITION_2024_EXPR_FRAGMENT_SPECIFIER, |
| 113 | + token.span.into(), |
| 114 | + MacroExprFragment2024 { suggestion: token.span }, |
| 115 | + ); |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +impl EarlyLintPass for Expr2024 { |
| 120 | + fn check_mac_def(&mut self, cx: &crate::EarlyContext<'_>, mc: &rustc_ast::MacroDef) { |
| 121 | + self.check_tokens(cx, &mc.body.tokens); |
| 122 | + } |
| 123 | +} |
0 commit comments