1//! Migration code for the `expr_fragment_specifier_2024` rule.
23use rustc_ast::token::{Token, TokenKind};
4use rustc_ast::tokenstream::{TokenStream, TokenTree};
5use rustc_session::lint::fcw;
6use rustc_session::{declare_lint, declare_lint_pass};
7use rustc_span::edition::Edition;
8use rustc_span::sym;
9use tracing::debug;
1011use crate::EarlyLintPass;
12use crate::lints::MacroExprFragment2024;
1314#[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! {
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/edition-guide/rust-2024/macro-fragment-specifiers.html
69 /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
70pub 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: fcw!(EditionSemanticsChange 2024 "macro-fragment-specifiers"),
76 };
77}7879pub 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 {
<[_]>::into_vec(::alloc::boxed::box_new([EDITION_2024_EXPR_FRAGMENT_SPECIFIER]))
}
}
impl Expr2024 {
#[allow(unused)]
pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
<[_]>::into_vec(::alloc::boxed::box_new([EDITION_2024_EXPR_FRAGMENT_SPECIFIER]))
}
}declare_lint_pass!(Expr2024 => [EDITION_2024_EXPR_FRAGMENT_SPECIFIER,]);
8081impl Expr2024 {
82fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: &TokenStream) {
83let mut prev_colon = false;
84let mut prev_identifier = false;
85let mut prev_dollar = false;
86for tt in tokens.iter() {
87{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs:87",
"rustc_lint::macro_expr_fragment_specifier_2024_migration",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs"),
::tracing_core::__macro_support::Option::Some(87u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::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 Value))])
});
} else { ; }
};debug!(
88"check_tokens: {:?} - colon {prev_dollar} - ident {prev_identifier} - colon {prev_colon}",
89 tt
90 );
91match tt {
92 TokenTree::Token(token, _) => match token.kind {
93 TokenKind::Dollar => {
94 prev_dollar = true;
95continue;
96 }
97 TokenKind::Ident(..) | TokenKind::NtIdent(..) => {
98if prev_colon && prev_identifier && prev_dollar {
99self.check_ident_token(cx, token);
100 } else if prev_dollar {
101 prev_identifier = true;
102continue;
103 }
104 }
105 TokenKind::Colon => {
106if prev_dollar && prev_identifier {
107 prev_colon = true;
108continue;
109 }
110 }
111_ => {}
112 },
113 TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts),
114 }
115 prev_colon = false;
116 prev_identifier = false;
117 prev_dollar = false;
118 }
119 }
120121fn check_ident_token(&mut self, cx: &crate::EarlyContext<'_>, token: &Token) {
122{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs:122",
"rustc_lint::macro_expr_fragment_specifier_2024_migration",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs"),
::tracing_core::__macro_support::Option::Some(122u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("check_ident_token: {0:?}",
token) as &dyn Value))])
});
} else { ; }
};debug!("check_ident_token: {:?}", token);
123let TokenKind::Ident(sym, _) = token.kind else { return };
124let edition = Edition::Edition2024;
125126{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs:126",
"rustc_lint::macro_expr_fragment_specifier_2024_migration",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs"),
::tracing_core::__macro_support::Option::Some(126u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("token.span.edition(): {0:?}",
token.span.edition()) as &dyn Value))])
});
} else { ; }
};debug!("token.span.edition(): {:?}", token.span.edition());
127if token.span.edition() >= edition {
128return;
129 }
130131if sym != sym::expr {
132return;
133 }
134135{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs:135",
"rustc_lint::macro_expr_fragment_specifier_2024_migration",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs"),
::tracing_core::__macro_support::Option::Some(135u32),
::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};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("emitting lint")
as &dyn Value))])
});
} else { ; }
};debug!("emitting lint");
136cx.builder.emit_span_lint(
137&EDITION_2024_EXPR_FRAGMENT_SPECIFIER,
138token.span.into(),
139MacroExprFragment2024 { suggestion: token.span },
140 );
141 }
142}
143144impl EarlyLintPassfor Expr2024 {
145fn check_mac_def(&mut self, cx: &crate::EarlyContext<'_>, mc: &rustc_ast::MacroDef) {
146self.check_tokens(cx, &mc.body.tokens);
147 }
148}