about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincenzo Palazzo <vincenzopalazzodev@gmail.com>2024-05-25 08:44:24 +0000
committerVincenzo Palazzo <vincenzopalazzodev@gmail.com>2024-07-09 17:41:13 +0000
commit25637e2c8e69317d61e25acd0bf2129b3507693d (patch)
treec6be5838be95005c86977015408829455b7fbca5
parent9ffe52e05b063f5fea020d501216a35559ca8620 (diff)
downloadrust-25637e2c8e69317d61e25acd0bf2129b3507693d.tar.gz
rust-25637e2c8e69317d61e25acd0bf2129b3507693d.zip
Adds expr_2024 migration lit
This is adding a migration lint for the current (in the 2021 edition and previous)
to move expr to expr_2021 from expr

Co-Developed-by: Eric Holk
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
-rw-r--r--compiler/rustc_lint/messages.ftl3
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint/src/lints.rs7
-rw-r--r--compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs155
4 files changed, 168 insertions, 0 deletions
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 late;
 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 impl_trait_overcaptures::ImplTraitOvercaptures;
 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 @@ early_lint_methods!(
             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<SuggestChangingAssocTypes<'a, 'b>>,
 }
 
+#[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 <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html>",
+    };
+}
+
+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);
+    }
+}