Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

migration lint for expr2024 for the edition 2024 #125627

Merged
merged 2 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::*;
Expand Down Expand Up @@ -170,6 +172,7 @@ early_lint_methods!(
IncompleteInternalFeatures: IncompleteInternalFeatures,
RedundantSemicolons: RedundantSemicolons,
UnusedDocComment: UnusedDocComment,
Expr2024: Expr2024,
]
]
);
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
vincenzopalazzo marked this conversation as resolved.
Show resolved Hide resolved

pub struct BuiltinTypeAliasGenericBoundsSuggestion {
pub suggestions: Vec<(Span, String)>,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
24 changes: 24 additions & 0 deletions tests/ui/macros/expr_2021_cargo_fix_edition.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//@ run-rustfix
//@ check-pass
//@ compile-flags: --edition=2021
#![allow(incomplete_features)]
#![feature(expr_fragment_specifier_2024)]
#![warn(edition_2024_expr_fragment_specifier)]

macro_rules! m {
($e:expr_2021) => { //~ WARN: the `expr` fragment specifier will accept more expressions in the 2024 edition
//~^ WARN: this changes meaning in Rust 2024
$e
};
($($i:expr_2021)*) => { }; //~ WARN: the `expr` fragment specifier will accept more expressions in the 2024 edition
//~^ WARN: this changes meaning in Rust 2024
}

macro_rules! test {
(expr) => {}
}

fn main() {
m!(());
test!(expr);
}
24 changes: 24 additions & 0 deletions tests/ui/macros/expr_2021_cargo_fix_edition.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//@ run-rustfix
//@ check-pass
//@ compile-flags: --edition=2021
#![allow(incomplete_features)]
#![feature(expr_fragment_specifier_2024)]
#![warn(edition_2024_expr_fragment_specifier)]

macro_rules! m {
($e:expr) => { //~ WARN: the `expr` fragment specifier will accept more expressions in the 2024 edition
//~^ WARN: this changes meaning in Rust 2024
$e
};
($($i:expr)*) => { }; //~ WARN: the `expr` fragment specifier will accept more expressions in the 2024 edition
//~^ WARN: this changes meaning in Rust 2024
}

macro_rules! test {
(expr) => {}
}

fn main() {
m!(());
test!(expr);
}
33 changes: 33 additions & 0 deletions tests/ui/macros/expr_2021_cargo_fix_edition.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
warning: the `expr` fragment specifier will accept more expressions in the 2024 edition
--> $DIR/expr_2021_cargo_fix_edition.rs:9:9
|
LL | ($e:expr) => {
| ^^^^
|
= warning: this changes meaning in Rust 2024
= note: for more information, see Migration Guide <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html>
note: the lint level is defined here
--> $DIR/expr_2021_cargo_fix_edition.rs:6:9
|
LL | #![warn(edition_2024_expr_fragment_specifier)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to keep the existing behavior, use the `expr_2021` fragment specifier
|
LL | ($e:expr_2021) => {
| ~~~~~~~~~

warning: the `expr` fragment specifier will accept more expressions in the 2024 edition
--> $DIR/expr_2021_cargo_fix_edition.rs:13:11
|
LL | ($($i:expr)*) => { };
| ^^^^
|
= warning: this changes meaning in Rust 2024
= note: for more information, see Migration Guide <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html>
help: to keep the existing behavior, use the `expr_2021` fragment specifier
|
LL | ($($i:expr_2021)*) => { };
| ~~~~~~~~~

warning: 2 warnings emitted

4 changes: 2 additions & 2 deletions tests/ui/macros/expr_2021_inline_const.edi2021.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: no rules expected the token `const`
--> $DIR/expr_2021_inline_const.rs:21:12
--> $DIR/expr_2021_inline_const.rs:26:12
|
LL | macro_rules! m2021 {
| ------------------ when calling this macro
Expand All @@ -14,7 +14,7 @@ LL | ($e:expr_2021) => {
| ^^^^^^^^^^^^

error: no rules expected the token `const`
--> $DIR/expr_2021_inline_const.rs:22:12
--> $DIR/expr_2021_inline_const.rs:27:12
|
LL | macro_rules! m2024 {
| ------------------ when calling this macro
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/macros/expr_2021_inline_const.edi2024.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: no rules expected the token `const`
--> $DIR/expr_2021_inline_const.rs:21:12
--> $DIR/expr_2021_inline_const.rs:26:12
|
LL | macro_rules! m2021 {
| ------------------ when calling this macro
Expand Down
7 changes: 7 additions & 0 deletions tests/ui/macros/expr_2021_inline_const.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ macro_rules! m2024 {
$e
};
}

macro_rules! test {
(expr) => {}
}

fn main() {
m2021!(const { 1 }); //~ ERROR: no rules expected the token `const`
m2024!(const { 1 }); //[edi2021]~ ERROR: no rules expected the token `const`

test!(expr);
}
Loading