Skip to main content

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_lint_defs::{declare_lint, declare_lint_pass, fcw};
6use rustc_span::edition::Edition;
7use rustc_span::sym;
8use tracing::debug;
9
10use crate::EarlyLintPass;
11use crate::diagnostics::MacroExprFragment2024;
12
13#[doc =
r" The `edition_2024_expr_fragment_specifier` lint detects the use of"]
#[doc = r" `expr` fragments in macros during migration to the 2024 edition."]
#[doc = r""]
#[doc =
r" The `expr` fragment specifier will accept more expressions in the 2024"]
#[doc =
r" edition. To maintain the behavior from the 2021 edition and earlier, use"]
#[doc = r" the `expr_2021` fragment specifier."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust,edition2021,compile_fail"]
#[doc = r" #![deny(edition_2024_expr_fragment_specifier)]"]
#[doc = r" macro_rules! m {"]
#[doc = r"   ($e:expr) => {"]
#[doc = r"       $e"]
#[doc = r"   }"]
#[doc = r" }"]
#[doc = r""]
#[doc = r" fn main() {"]
#[doc = r"    m!(1);"]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" Rust [editions] allow the language to evolve without breaking backwards"]
#[doc =
r" compatibility. This lint catches code that uses [macro matcher fragment"]
#[doc =
r" specifiers] that have changed meaning in the 2024 edition. If you switch"]
#[doc =
r" to the new edition without updating the code, your macros may behave"]
#[doc = r" differently."]
#[doc = r""]
#[doc =
r" In the 2024 edition, the `expr` fragment specifier `expr` will also"]
#[doc =
r" match `const { ... }` blocks. This means if a macro had a pattern that"]
#[doc =
r" matched `$e:expr` and another that matches `const { $e: expr }`, for"]
#[doc =
r" example, that under the 2024 edition the first pattern would match while"]
#[doc =
r" in the 2021 and earlier editions the second pattern would match. To keep"]
#[doc = r" the old behavior, use the `expr_2021` fragment specifier."]
#[doc = r""]
#[doc =
r" This lint detects macros whose behavior might change due to the changing"]
#[doc =
r#" meaning of the `expr` fragment specifier. It is "allow" by default"#]
#[doc =
r" because the code is perfectly valid in older editions. The [`cargo fix`]"]
#[doc =
r#" tool with the `--edition` flag will switch this lint to "warn" and"#]
#[doc =
r" automatically apply the suggested fix from the compiler. This provides a"]
#[doc = r" completely automated way to update old code for a new edition."]
#[doc = r""]
#[doc =
r" Using `cargo fix --edition` with this lint will ensure that your code"]
#[doc =
r" retains the same behavior. This may not be the desired, as macro authors"]
#[doc =
r" often will want their macros to use the latest grammar for matching"]
#[doc =
r" expressions. Be sure to carefully review changes introduced by this lint"]
#[doc = r" to ensure the macros implement the desired behavior."]
#[doc = r""]
#[doc = r" [editions]: https://doc.rust-lang.org/edition-guide/"]
#[doc =
r" [macro matcher fragment specifiers]: https://doc.rust-lang.org/edition-guide/rust-2024/macro-fragment-specifiers.html"]
#[doc =
r" [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html"]
pub static EDITION_2024_EXPR_FRAGMENT_SPECIFIER: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "EDITION_2024_EXPR_FRAGMENT_SPECIFIER",
            default_level: ::rustc_lint_defs::Allow,
            desc: "The `expr` fragment specifier will accept more expressions in the 2024 edition. \
    To keep the existing behavior, use the `expr_2021` fragment specifier.",
            is_externally_loaded: false,
            future_incompatible: Some(::rustc_lint_defs::FutureIncompatibleInfo {
                    reason: ::rustc_lint_defs::FutureIncompatibilityReason::EditionSemanticsChange(::rustc_lint_defs::EditionFcw {
                            edition: rustc_span::edition::Edition::Edition2024,
                            page_slug: "macro-fragment-specifiers",
                        }),
                    ..::rustc_lint_defs::FutureIncompatibleInfo::default_fields_for_macro()
                }),
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
14    /// The `edition_2024_expr_fragment_specifier` lint detects the use of
15    /// `expr` fragments in macros during migration to the 2024 edition.
16    ///
17    /// The `expr` fragment specifier will accept more expressions in the 2024
18    /// edition. To maintain the behavior from the 2021 edition and earlier, use
19    /// the `expr_2021` fragment specifier.
20    ///
21    /// ### Example
22    ///
23    /// ```rust,edition2021,compile_fail
24    /// #![deny(edition_2024_expr_fragment_specifier)]
25    /// macro_rules! m {
26    ///   ($e:expr) => {
27    ///       $e
28    ///   }
29    /// }
30    ///
31    /// fn main() {
32    ///    m!(1);
33    /// }
34    /// ```
35    ///
36    /// {{produces}}
37    ///
38    /// ### Explanation
39    ///
40    /// Rust [editions] allow the language to evolve without breaking backwards
41    /// compatibility. This lint catches code that uses [macro matcher fragment
42    /// specifiers] that have changed meaning in the 2024 edition. If you switch
43    /// to the new edition without updating the code, your macros may behave
44    /// differently.
45    ///
46    /// In the 2024 edition, the `expr` fragment specifier `expr` will also
47    /// match `const { ... }` blocks. This means if a macro had a pattern that
48    /// matched `$e:expr` and another that matches `const { $e: expr }`, for
49    /// example, that under the 2024 edition the first pattern would match while
50    /// in the 2021 and earlier editions the second pattern would match. To keep
51    /// the old behavior, use the `expr_2021` fragment specifier.
52    ///
53    /// This lint detects macros whose behavior might change due to the changing
54    /// meaning of the `expr` fragment specifier. It is "allow" by default
55    /// because the code is perfectly valid in older editions. The [`cargo fix`]
56    /// tool with the `--edition` flag will switch this lint to "warn" and
57    /// automatically apply the suggested fix from the compiler. This provides a
58    /// completely automated way to update old code for a new edition.
59    ///
60    /// Using `cargo fix --edition` with this lint will ensure that your code
61    /// retains the same behavior. This may not be the desired, as macro authors
62    /// often will want their macros to use the latest grammar for matching
63    /// expressions. Be sure to carefully review changes introduced by this lint
64    /// to ensure the macros implement the desired behavior.
65    ///
66    /// [editions]: https://doc.rust-lang.org/edition-guide/
67    /// [macro matcher fragment specifiers]: https://doc.rust-lang.org/edition-guide/rust-2024/macro-fragment-specifiers.html
68    /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
69    pub EDITION_2024_EXPR_FRAGMENT_SPECIFIER,
70    Allow,
71    "The `expr` fragment specifier will accept more expressions in the 2024 edition. \
72    To keep the existing behavior, use the `expr_2021` fragment specifier.",
73    @future_incompatible = FutureIncompatibleInfo {
74        reason: fcw!(EditionSemanticsChange 2024 "macro-fragment-specifiers"),
75    };
76}
77
78pub struct Expr2024;
#[automatically_derived]
impl ::core::marker::Copy for Expr2024 { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Expr2024 { }
#[automatically_derived]
impl ::core::clone::Clone for Expr2024 {
    #[inline]
    fn clone(&self) -> Expr2024 { *self }
}
impl ::rustc_lint_defs::LintPass for Expr2024 {
    fn name(&self) -> &'static str { "Expr2024" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [EDITION_2024_EXPR_FRAGMENT_SPECIFIER]))
    }
}
impl Expr2024 {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [EDITION_2024_EXPR_FRAGMENT_SPECIFIER]))
    }
}declare_lint_pass!(Expr2024 => [EDITION_2024_EXPR_FRAGMENT_SPECIFIER,]);
79
80impl Expr2024 {
81    fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: &TokenStream) {
82        let mut prev_colon = false;
83        let mut prev_identifier = false;
84        let mut prev_dollar = false;
85        for tt in tokens.iter() {
86            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/0fc141305da7a8a222f65aef1f1acc739c46282b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs:86",
                        "rustc_lint::macro_expr_fragment_specifier_2024_migration",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/0fc141305da7a8a222f65aef1f1acc739c46282b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs"),
                        ::tracing_core::__macro_support::Option::Some(86u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::macro_expr_fragment_specifier_2024_migration"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("check_tokens: {0:?} - colon {1} - ident {2} - colon {3}",
                                                    tt, prev_dollar, prev_identifier, prev_colon) as
                                            &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!(
87                "check_tokens: {:?} - colon {prev_dollar} - ident {prev_identifier} - colon {prev_colon}",
88                tt
89            );
90            match tt {
91                TokenTree::Token(token, _) => match token.kind {
92                    TokenKind::Dollar => {
93                        prev_dollar = true;
94                        continue;
95                    }
96                    TokenKind::Ident(..) | TokenKind::NtIdent(..) => {
97                        if prev_colon && prev_identifier && prev_dollar {
98                            self.check_ident_token(cx, token);
99                        } else if prev_dollar {
100                            prev_identifier = true;
101                            continue;
102                        }
103                    }
104                    TokenKind::Colon => {
105                        if prev_dollar && prev_identifier {
106                            prev_colon = true;
107                            continue;
108                        }
109                    }
110                    _ => {}
111                },
112                TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts),
113            }
114            prev_colon = false;
115            prev_identifier = false;
116            prev_dollar = false;
117        }
118    }
119
120    fn check_ident_token(&mut self, cx: &crate::EarlyContext<'_>, token: &Token) {
121        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/0fc141305da7a8a222f65aef1f1acc739c46282b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs:121",
                        "rustc_lint::macro_expr_fragment_specifier_2024_migration",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/0fc141305da7a8a222f65aef1f1acc739c46282b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs"),
                        ::tracing_core::__macro_support::Option::Some(121u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::macro_expr_fragment_specifier_2024_migration"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("check_ident_token: {0:?}",
                                                    token) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("check_ident_token: {:?}", token);
122        let TokenKind::Ident(sym, _) = token.kind else { return };
123        let edition = Edition::Edition2024;
124
125        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/0fc141305da7a8a222f65aef1f1acc739c46282b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs:125",
                        "rustc_lint::macro_expr_fragment_specifier_2024_migration",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/0fc141305da7a8a222f65aef1f1acc739c46282b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs"),
                        ::tracing_core::__macro_support::Option::Some(125u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::macro_expr_fragment_specifier_2024_migration"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("token.span.edition(): {0:?}",
                                                    token.span.edition()) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("token.span.edition(): {:?}", token.span.edition());
126        if token.span.edition() >= edition {
127            return;
128        }
129
130        if sym != sym::expr {
131            return;
132        }
133
134        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/0fc141305da7a8a222f65aef1f1acc739c46282b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs:134",
                        "rustc_lint::macro_expr_fragment_specifier_2024_migration",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/0fc141305da7a8a222f65aef1f1acc739c46282b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs"),
                        ::tracing_core::__macro_support::Option::Some(134u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::macro_expr_fragment_specifier_2024_migration"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("emitting lint")
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("emitting lint");
135        cx.builder.emit_span_lint(
136            &EDITION_2024_EXPR_FRAGMENT_SPECIFIER,
137            token.span.into(),
138            MacroExprFragment2024 { suggestion: token.span },
139        );
140    }
141}
142
143impl EarlyLintPass for Expr2024 {
144    fn check_mac_def(&mut self, cx: &crate::EarlyContext<'_>, mc: &rustc_ast::MacroDef) {
145        self.check_tokens(cx, &mc.body.tokens);
146    }
147}