Skip to content

Commit 1ebf192

Browse files
committed
Add MACRO_TRAILING_SEMICOLON lint
cc rust-lang#79813 This PR adds an allow-by-default future-compatibility lint `MACRO_TRAILING_SEMICOLON`. It fires when a trailing semicolon in a macro body is ignored due to the macro being used in expression position: ```rust macro_rules! foo { () => { true; // WARN } } fn main() { let val = match true { true => false, _ => foo!() }; } ``` The lint takes its level from the macro call site, and can be allowed for a particular macro by adding `#[allow(macro_trailing_semicolon)]`. The lint is set to warn for all internal rustc crates (when being built by a stage1 compiler). After the next beta bump, we can enable the lint for the bootstrap compiler as well.
1 parent 613ef74 commit 1ebf192

File tree

11 files changed

+154
-2
lines changed

11 files changed

+154
-2
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3746,6 +3746,7 @@ dependencies = [
37463746
"rustc_errors",
37473747
"rustc_feature",
37483748
"rustc_lexer",
3749+
"rustc_lint_defs",
37493750
"rustc_macros",
37503751
"rustc_parse",
37513752
"rustc_serialize",

compiler/rustc_expand/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ rustc_attr = { path = "../rustc_attr" }
1818
rustc_data_structures = { path = "../rustc_data_structures" }
1919
rustc_errors = { path = "../rustc_errors" }
2020
rustc_feature = { path = "../rustc_feature" }
21+
rustc_lint_defs = { path = "../rustc_lint_defs" }
2122
rustc_macros = { path = "../rustc_macros" }
2223
rustc_lexer = { path = "../rustc_lexer" }
2324
rustc_parse = { path = "../rustc_parse" }

compiler/rustc_expand/src/base.rs

+2
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ pub trait TTMacroExpander {
347347
ecx: &'cx mut ExtCtxt<'_>,
348348
span: Span,
349349
input: TokenStream,
350+
nearest_parent: NodeId,
350351
) -> Box<dyn MacResult + 'cx>;
351352
}
352353

@@ -362,6 +363,7 @@ where
362363
ecx: &'cx mut ExtCtxt<'_>,
363364
span: Span,
364365
input: TokenStream,
366+
_nearest_parent: NodeId,
365367
) -> Box<dyn MacResult + 'cx> {
366368
self(ecx, span, input)
367369
}

compiler/rustc_expand/src/expand.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,11 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
721721
SyntaxExtensionKind::LegacyBang(expander) => {
722722
let prev = self.cx.current_expansion.prior_type_ascription;
723723
self.cx.current_expansion.prior_type_ascription = mac.prior_type_ascription;
724-
let tok_result = expander.expand(self.cx, span, mac.args.inner_tokens());
724+
// FIXME - this is imprecise. We should try to lint as close to the macro call
725+
// as possible (we cannot take attributes from the macro call itself).
726+
let nearest_parent = self.cx.resolver.lint_node_id(invoc.expansion_data.id);
727+
let tok_result =
728+
expander.expand(self.cx, span, mac.args.inner_tokens(), nearest_parent);
725729
let result = if let Some(result) = fragment_kind.make_from(tok_result) {
726730
result
727731
} else {

compiler/rustc_expand/src/mbe/macro_rules.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ use crate::mbe::transcribe::transcribe;
1111
use rustc_ast as ast;
1212
use rustc_ast::token::{self, NonterminalKind, NtTT, Token, TokenKind::*};
1313
use rustc_ast::tokenstream::{DelimSpan, TokenStream};
14+
use rustc_ast::NodeId;
1415
use rustc_ast_pretty::pprust;
1516
use rustc_attr::{self as attr, TransparencyError};
1617
use rustc_data_structures::fx::FxHashMap;
1718
use rustc_data_structures::sync::Lrc;
1819
use rustc_errors::{Applicability, DiagnosticBuilder};
1920
use rustc_feature::Features;
21+
use rustc_lint_defs::builtin::MACRO_TRAILING_SEMICOLON;
2022
use rustc_parse::parser::Parser;
2123
use rustc_session::parse::ParseSess;
2224
use rustc_session::Session;
@@ -37,6 +39,7 @@ crate struct ParserAnyMacro<'a> {
3739
site_span: Span,
3840
/// The ident of the macro we're parsing
3941
macro_ident: Ident,
42+
nearest_parent: NodeId,
4043
arm_span: Span,
4144
}
4245

@@ -110,7 +113,8 @@ fn emit_frag_parse_err(
110113

111114
impl<'a> ParserAnyMacro<'a> {
112115
crate fn make(mut self: Box<ParserAnyMacro<'a>>, kind: AstFragmentKind) -> AstFragment {
113-
let ParserAnyMacro { site_span, macro_ident, ref mut parser, arm_span } = *self;
116+
let ParserAnyMacro { site_span, macro_ident, ref mut parser, nearest_parent, arm_span } =
117+
*self;
114118
let snapshot = &mut parser.clone();
115119
let fragment = match parse_ast_fragment(parser, kind) {
116120
Ok(f) => f,
@@ -124,6 +128,12 @@ impl<'a> ParserAnyMacro<'a> {
124128
// `macro_rules! m { () => { panic!(); } }` isn't parsed by `.parse_expr()`,
125129
// but `m!()` is allowed in expression positions (cf. issue #34706).
126130
if kind == AstFragmentKind::Expr && parser.token == token::Semi {
131+
parser.sess.buffer_lint(
132+
MACRO_TRAILING_SEMICOLON,
133+
parser.token.span,
134+
nearest_parent,
135+
"trailing semicolon in macro used in expression position",
136+
);
127137
parser.bump();
128138
}
129139

@@ -149,6 +159,7 @@ impl TTMacroExpander for MacroRulesMacroExpander {
149159
cx: &'cx mut ExtCtxt<'_>,
150160
sp: Span,
151161
input: TokenStream,
162+
nearest_parent: NodeId,
152163
) -> Box<dyn MacResult + 'cx> {
153164
if !self.valid {
154165
return DummyResult::any(sp);
@@ -160,6 +171,7 @@ impl TTMacroExpander for MacroRulesMacroExpander {
160171
self.name,
161172
self.transparency,
162173
input,
174+
nearest_parent,
163175
&self.lhses,
164176
&self.rhses,
165177
)
@@ -187,6 +199,7 @@ fn generic_extension<'cx>(
187199
name: Ident,
188200
transparency: Transparency,
189201
arg: TokenStream,
202+
nearest_parent: NodeId,
190203
lhses: &[mbe::TokenTree],
191204
rhses: &[mbe::TokenTree],
192205
) -> Box<dyn MacResult + 'cx> {
@@ -287,6 +300,7 @@ fn generic_extension<'cx>(
287300
// macro leaves unparsed tokens.
288301
site_span: sp,
289302
macro_ident: name,
303+
nearest_parent,
290304
arm_span,
291305
});
292306
}

compiler/rustc_lint_defs/src/builtin.rs

+45
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// ignore-tidy-filelength
12
//! Some lints that are built in to the compiler.
23
//!
34
//! These are the built-in lints that are emitted direct in the main
@@ -2833,6 +2834,49 @@ declare_lint! {
28332834
"detects `#[unstable]` on stable trait implementations for stable types"
28342835
}
28352836

2837+
declare_lint! {
2838+
/// The `macro_trailing_semicolon` lint detects trailing semicolons
2839+
/// in macro bodies when the macro is invoked in expression position.
2840+
/// This was previous accepted, but is being phased out.
2841+
///
2842+
/// ### Example
2843+
///
2844+
/// ```rust
2845+
/// macro_rules! foo {
2846+
/// () => { true; }
2847+
/// }
2848+
///
2849+
/// fn main() {
2850+
/// let val = match true {
2851+
/// true => false,
2852+
/// _ => foo!()
2853+
/// };
2854+
/// }
2855+
/// ```
2856+
///
2857+
/// ### Explanation
2858+
///
2859+
/// Previous, Rust ignored trailing semicolon in a macro
2860+
/// body when a macro was invoked in expression position.
2861+
/// However, this makes the treatment of semicolons in the language
2862+
/// inconsistent, and could lead to unexpected runtime behavior
2863+
/// in some circumstances (e.g. if the macro author expects
2864+
/// a value to be dropped).
2865+
///
2866+
/// This is a [future-incompatible] lint to transition this
2867+
/// to a hard error in the future. See [issue #79813] for more details.
2868+
///
2869+
/// [issue #79813]: https://github.com/rust-lang/rust/issues/79813
2870+
/// [future-incompatible]: ../index.md#future-incompatible-lints
2871+
pub MACRO_TRAILING_SEMICOLON,
2872+
Allow,
2873+
"trailing semicolon in macro body used as expression",
2874+
@future_incompatible = FutureIncompatibleInfo {
2875+
reference: "issue #79813 <https://github.com/rust-lang/rust/issues/79813>",
2876+
edition: None,
2877+
};
2878+
}
2879+
28362880
declare_lint_pass! {
28372881
/// Does nothing as a lint pass, but registers some `Lint`s
28382882
/// that are used by other parts of the compiler.
@@ -2920,6 +2964,7 @@ declare_lint_pass! {
29202964
USELESS_DEPRECATED,
29212965
UNSUPPORTED_NAKED_FUNCTIONS,
29222966
MISSING_ABI,
2967+
MACRO_TRAILING_SEMICOLON,
29232968
]
29242969
}
29252970

src/bootstrap/bootstrap.py

+1
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,7 @@ def build_bootstrap(self):
833833
target_linker = self.get_toml("linker", build_section)
834834
if target_linker is not None:
835835
env["RUSTFLAGS"] += " -C linker=" + target_linker
836+
# cfg(bootstrap): Add `-Wmacro_trailing_semicolon` after the next beta bump
836837
env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes"
837838
if self.get_toml("deny-warnings", "rust") != "false":
838839
env["RUSTFLAGS"] += " -Dwarnings"

src/bootstrap/builder.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1250,6 +1250,12 @@ impl<'a> Builder<'a> {
12501250
// some code doesn't go through this `rustc` wrapper.
12511251
lint_flags.push("-Wrust_2018_idioms");
12521252
lint_flags.push("-Wunused_lifetimes");
1253+
// cfg(bootstrap): unconditionally enable this warning after the next beta bump
1254+
// This is currently disabled for the stage1 libstd, since build scripts
1255+
// will end up using the bootstrap compiler (which doesn't yet support this lint)
1256+
if compiler.stage != 0 && mode != Mode::Std {
1257+
lint_flags.push("-Wmacro_trailing_semicolon");
1258+
}
12531259

12541260
if self.config.deny_warnings {
12551261
lint_flags.push("-Dwarnings");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// check-pass
2+
// Ensure that trailing semicolons are allowed by default
3+
4+
macro_rules! foo {
5+
() => {
6+
true;
7+
}
8+
}
9+
10+
fn main() {
11+
let val = match true {
12+
true => false,
13+
_ => foo!()
14+
};
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// check-pass
2+
#![warn(macro_trailing_semicolon)]
3+
4+
#[allow(dead_code)]
5+
macro_rules! foo {
6+
($val:ident) => {
7+
true; //~ WARN trailing
8+
//~| WARN this was previously
9+
//~| WARN trailing
10+
//~| WARN this was previously
11+
}
12+
}
13+
14+
fn main() {
15+
// This `allow` doesn't work
16+
#[allow(macro_trailing_semicolon)]
17+
let _ = {
18+
foo!(first)
19+
};
20+
21+
// This 'allow' doesn't work either
22+
#[allow(macro_trailing_semicolon)]
23+
let _ = foo!(second);
24+
25+
// But this 'allow' does
26+
#[allow(macro_trailing_semicolon)]
27+
fn inner() {
28+
let _ = foo!(third);
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
warning: trailing semicolon in macro used in expression position
2+
--> $DIR/macro-trailing-semicolon.rs:7:13
3+
|
4+
LL | true;
5+
| ^
6+
...
7+
LL | foo!(first)
8+
| ----------- in this macro invocation
9+
|
10+
note: the lint level is defined here
11+
--> $DIR/macro-trailing-semicolon.rs:2:9
12+
|
13+
LL | #![warn(macro_trailing_semicolon)]
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^
15+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
16+
= note: for more information, see issue #79813 <https://github.com/rust-lang/rust/issues/79813>
17+
= note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
18+
19+
warning: trailing semicolon in macro used in expression position
20+
--> $DIR/macro-trailing-semicolon.rs:7:13
21+
|
22+
LL | true;
23+
| ^
24+
...
25+
LL | let _ = foo!(second);
26+
| ------------ in this macro invocation
27+
|
28+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
29+
= note: for more information, see issue #79813 <https://github.com/rust-lang/rust/issues/79813>
30+
= note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
31+
32+
warning: 2 warnings emitted
33+

0 commit comments

Comments
 (0)