Skip to content

Commit 71ce831

Browse files
authored
Rollup merge of rust-lang#99935 - CAD97:unstable-syntax-lints, r=petrochenkov
Reenable disabled early syntax gates as future-incompatibility lints - MCP: rust-lang/compiler-team#535 The approach taken by this PR is - Introduce a new lint, `unstable_syntax_pre_expansion`, and reenable the early syntax gates to emit it - Use the diagnostic stashing mechanism to stash warnings the early warnings - When the hard error occurs post expansion, steal and cancel the early warning - Don't display any stashed warnings if errors are present to avoid the same noise problem that hiding type ascription errors is avoiding Commits are working commits, but in a coherent steps-to-implement manner. Can be squashed if desired. The preexisting `soft_unstable` lint seems like it would've been a good fit, but it is deny-by-default (appropriate for `#[bench]`) and these gates should be introduced as warn-by-default. It may be desirable to change the stash mechanism's behavior to not flush lint errors in the presence of other errors either (like is done for warnings here), but upgrading a stash-using lint from warn to error perhaps is enough of a request to see the lint that they shouldn't be hidden; additionally, fixing the last error to get new errors thrown at you always feels bad, so if we know the lint errors are present, we should show them. Using a new flag/mechanism for a "weak diagnostic" which is suppressed by other errors may also be desirable over assuming any stashed warnings are "weak," but this is the first user of stashing warnings and seems an appropriate use of stashing (it follows the "know more later to refine the diagnostic" pattern; here we learn that it's in a compiled position) so we get to define what it means to stash a non-hard-error diagnostic. cc ```@petrochenkov``` (seconded MCP)
2 parents c029bd0 + 944c6b6 commit 71ce831

19 files changed

+417
-46
lines changed

compiler/rustc_ast_passes/src/feature_gate.rs

+35-23
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use rustc_ast as ast;
22
use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor};
33
use rustc_ast::{AssocConstraint, AssocConstraintKind, NodeId};
44
use rustc_ast::{PatKind, RangeEnd, VariantData};
5-
use rustc_errors::{struct_span_err, Applicability};
5+
use rustc_errors::{struct_span_err, Applicability, StashKey};
6+
use rustc_feature::Features;
67
use rustc_feature::{AttributeGate, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
7-
use rustc_feature::{Features, GateIssue};
8-
use rustc_session::parse::{feature_err, feature_err_issue};
8+
use rustc_session::parse::{feature_err, feature_warn};
99
use rustc_session::Session;
1010
use rustc_span::source_map::Spanned;
1111
use rustc_span::symbol::sym;
@@ -20,9 +20,7 @@ macro_rules! gate_feature_fn {
2020
let has_feature: bool = has_feature(visitor.features);
2121
debug!("gate_feature(feature = {:?}, span = {:?}); has? {}", name, span, has_feature);
2222
if !has_feature && !span.allows_unstable($name) {
23-
feature_err_issue(&visitor.sess.parse_sess, name, span, GateIssue::Language, explain)
24-
.help(help)
25-
.emit();
23+
feature_err(&visitor.sess.parse_sess, name, span, explain).help(help).emit();
2624
}
2725
}};
2826
($visitor: expr, $has_feature: expr, $span: expr, $name: expr, $explain: expr) => {{
@@ -31,8 +29,19 @@ macro_rules! gate_feature_fn {
3129
let has_feature: bool = has_feature(visitor.features);
3230
debug!("gate_feature(feature = {:?}, span = {:?}); has? {}", name, span, has_feature);
3331
if !has_feature && !span.allows_unstable($name) {
34-
feature_err_issue(&visitor.sess.parse_sess, name, span, GateIssue::Language, explain)
35-
.emit();
32+
feature_err(&visitor.sess.parse_sess, name, span, explain).emit();
33+
}
34+
}};
35+
(future_incompatible; $visitor: expr, $has_feature: expr, $span: expr, $name: expr, $explain: expr) => {{
36+
let (visitor, has_feature, span, name, explain) =
37+
(&*$visitor, $has_feature, $span, $name, $explain);
38+
let has_feature: bool = has_feature(visitor.features);
39+
debug!(
40+
"gate_feature(feature = {:?}, span = {:?}); has? {} (future_incompatible)",
41+
name, span, has_feature
42+
);
43+
if !has_feature && !span.allows_unstable($name) {
44+
feature_warn(&visitor.sess.parse_sess, name, span, explain);
3645
}
3746
}};
3847
}
@@ -44,6 +53,9 @@ macro_rules! gate_feature_post {
4453
($visitor: expr, $feature: ident, $span: expr, $explain: expr) => {
4554
gate_feature_fn!($visitor, |x: &Features| x.$feature, $span, sym::$feature, $explain)
4655
};
56+
(future_incompatible; $visitor: expr, $feature: ident, $span: expr, $explain: expr) => {
57+
gate_feature_fn!(future_incompatible; $visitor, |x: &Features| x.$feature, $span, sym::$feature, $explain)
58+
};
4759
}
4860

4961
pub fn check_attribute(attr: &ast::Attribute, sess: &Session, features: &Features) {
@@ -588,11 +600,10 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
588600
{
589601
// When we encounter a statement of the form `foo: Ty = val;`, this will emit a type
590602
// ascription error, but the likely intention was to write a `let` statement. (#78907).
591-
feature_err_issue(
603+
feature_err(
592604
&self.sess.parse_sess,
593605
sym::type_ascription,
594606
lhs.span,
595-
GateIssue::Language,
596607
"type ascription is experimental",
597608
).span_suggestion_verbose(
598609
lhs.span.shrink_to_lo(),
@@ -615,15 +626,22 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
615626
);
616627
}
617628
ast::ExprKind::Type(..) => {
618-
// To avoid noise about type ascription in common syntax errors, only emit if it
619-
// is the *only* error.
620629
if self.sess.parse_sess.span_diagnostic.err_count() == 0 {
630+
// To avoid noise about type ascription in common syntax errors,
631+
// only emit if it is the *only* error.
621632
gate_feature_post!(
622633
&self,
623634
type_ascription,
624635
e.span,
625636
"type ascription is experimental"
626637
);
638+
} else {
639+
// And if it isn't, cancel the early-pass warning.
640+
self.sess
641+
.parse_sess
642+
.span_diagnostic
643+
.steal_diagnostic(e.span, StashKey::EarlySyntaxWarning)
644+
.map(|err| err.cancel());
627645
}
628646
}
629647
ast::ExprKind::TryBlock(_) => {
@@ -789,14 +807,12 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) {
789807

790808
// All uses of `gate_all!` below this point were added in #65742,
791809
// and subsequently disabled (with the non-early gating readded).
810+
// We emit an early future-incompatible warning for these.
811+
// New syntax gates should go above here to get a hard error gate.
792812
macro_rules! gate_all {
793813
($gate:ident, $msg:literal) => {
794-
// FIXME(eddyb) do something more useful than always
795-
// disabling these uses of early feature-gatings.
796-
if false {
797-
for span in spans.get(&sym::$gate).unwrap_or(&vec![]) {
798-
gate_feature_post!(&visitor, $gate, *span, $msg);
799-
}
814+
for span in spans.get(&sym::$gate).unwrap_or(&vec![]) {
815+
gate_feature_post!(future_incompatible; &visitor, $gate, *span, $msg);
800816
}
801817
};
802818
}
@@ -809,11 +825,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) {
809825
gate_all!(try_blocks, "`try` blocks are unstable");
810826
gate_all!(label_break_value, "labels on blocks are unstable");
811827
gate_all!(box_syntax, "box expression syntax is experimental; you can call `Box::new` instead");
812-
// To avoid noise about type ascription in common syntax errors,
813-
// only emit if it is the *only* error. (Also check it last.)
814-
if sess.parse_sess.span_diagnostic.err_count() == 0 {
815-
gate_all!(type_ascription, "type ascription is experimental");
816-
}
828+
gate_all!(type_ascription, "type ascription is experimental");
817829

818830
visit::walk_crate(&mut visitor, krate);
819831
}

compiler/rustc_errors/src/lib.rs

+63-12
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ struct HandlerInner {
459459
pub enum StashKey {
460460
ItemNoType,
461461
UnderscoreForArrayLengths,
462+
EarlySyntaxWarning,
462463
}
463464

464465
fn default_track_diagnostic(_: &Diagnostic) {}
@@ -626,19 +627,13 @@ impl Handler {
626627
/// Stash a given diagnostic with the given `Span` and `StashKey` as the key for later stealing.
627628
pub fn stash_diagnostic(&self, span: Span, key: StashKey, diag: Diagnostic) {
628629
let mut inner = self.inner.borrow_mut();
629-
// FIXME(Centril, #69537): Consider reintroducing panic on overwriting a stashed diagnostic
630-
// if/when we have a more robust macro-friendly replacement for `(span, key)` as a key.
631-
// See the PR for a discussion.
632-
inner.stashed_diagnostics.insert((span, key), diag);
630+
inner.stash((span, key), diag);
633631
}
634632

635633
/// Steal a previously stashed diagnostic with the given `Span` and `StashKey` as the key.
636634
pub fn steal_diagnostic(&self, span: Span, key: StashKey) -> Option<DiagnosticBuilder<'_, ()>> {
637-
self.inner
638-
.borrow_mut()
639-
.stashed_diagnostics
640-
.remove(&(span, key))
641-
.map(|diag| DiagnosticBuilder::new_diagnostic(self, diag))
635+
let mut inner = self.inner.borrow_mut();
636+
inner.steal((span, key)).map(|diag| DiagnosticBuilder::new_diagnostic(self, diag))
642637
}
643638

644639
/// Emit all stashed diagnostics.
@@ -1106,13 +1101,31 @@ impl HandlerInner {
11061101

11071102
/// Emit all stashed diagnostics.
11081103
fn emit_stashed_diagnostics(&mut self) -> Option<ErrorGuaranteed> {
1104+
let has_errors = self.has_errors();
11091105
let diags = self.stashed_diagnostics.drain(..).map(|x| x.1).collect::<Vec<_>>();
11101106
let mut reported = None;
11111107
for mut diag in diags {
1108+
// Decrement the count tracking the stash; emitting will increment it.
11121109
if diag.is_error() {
1113-
reported = Some(ErrorGuaranteed(()));
1110+
if matches!(diag.level, Level::Error { lint: true }) {
1111+
self.lint_err_count -= 1;
1112+
} else {
1113+
self.err_count -= 1;
1114+
}
1115+
} else {
1116+
if diag.is_force_warn() {
1117+
self.warn_count -= 1;
1118+
} else {
1119+
// Unless they're forced, don't flush stashed warnings when
1120+
// there are errors, to avoid causing warning overload. The
1121+
// stash would've been stolen already if it were important.
1122+
if has_errors {
1123+
continue;
1124+
}
1125+
}
11141126
}
1115-
self.emit_diagnostic(&mut diag);
1127+
let reported_this = self.emit_diagnostic(&mut diag);
1128+
reported = reported.or(reported_this);
11161129
}
11171130
reported
11181131
}
@@ -1302,9 +1315,47 @@ impl HandlerInner {
13021315
}
13031316
}
13041317

1318+
fn stash(&mut self, key: (Span, StashKey), diagnostic: Diagnostic) {
1319+
// Track the diagnostic for counts, but don't panic-if-treat-err-as-bug
1320+
// yet; that happens when we actually emit the diagnostic.
1321+
if diagnostic.is_error() {
1322+
if matches!(diagnostic.level, Level::Error { lint: true }) {
1323+
self.lint_err_count += 1;
1324+
} else {
1325+
self.err_count += 1;
1326+
}
1327+
} else {
1328+
// Warnings are only automatically flushed if they're forced.
1329+
if diagnostic.is_force_warn() {
1330+
self.warn_count += 1;
1331+
}
1332+
}
1333+
1334+
// FIXME(Centril, #69537): Consider reintroducing panic on overwriting a stashed diagnostic
1335+
// if/when we have a more robust macro-friendly replacement for `(span, key)` as a key.
1336+
// See the PR for a discussion.
1337+
self.stashed_diagnostics.insert(key, diagnostic);
1338+
}
1339+
1340+
fn steal(&mut self, key: (Span, StashKey)) -> Option<Diagnostic> {
1341+
let diagnostic = self.stashed_diagnostics.remove(&key)?;
1342+
if diagnostic.is_error() {
1343+
if matches!(diagnostic.level, Level::Error { lint: true }) {
1344+
self.lint_err_count -= 1;
1345+
} else {
1346+
self.err_count -= 1;
1347+
}
1348+
} else {
1349+
if diagnostic.is_force_warn() {
1350+
self.warn_count -= 1;
1351+
}
1352+
}
1353+
Some(diagnostic)
1354+
}
1355+
13051356
#[inline]
13061357
fn err_count(&self) -> usize {
1307-
self.err_count + self.stashed_diagnostics.len()
1358+
self.err_count
13081359
}
13091360

13101361
fn has_errors(&self) -> bool {

compiler/rustc_lint_defs/src/builtin.rs

+51
Original file line numberDiff line numberDiff line change
@@ -3212,6 +3212,56 @@ declare_lint! {
32123212
};
32133213
}
32143214

3215+
declare_lint! {
3216+
/// The `unstable_syntax_pre_expansion` lint detects the use of unstable
3217+
/// syntax that is discarded during attribute expansion.
3218+
///
3219+
/// ### Example
3220+
///
3221+
/// ```rust
3222+
/// #[cfg(FALSE)]
3223+
/// macro foo() {}
3224+
/// ```
3225+
///
3226+
/// {{produces}}
3227+
///
3228+
/// ### Explanation
3229+
///
3230+
/// The input to active attributes such as `#[cfg]` or procedural macro
3231+
/// attributes is required to be valid syntax. Previously, the compiler only
3232+
/// gated the use of unstable syntax features after resolving `#[cfg]` gates
3233+
/// and expanding procedural macros.
3234+
///
3235+
/// To avoid relying on unstable syntax, move the use of unstable syntax
3236+
/// into a position where the compiler does not parse the syntax, such as a
3237+
/// functionlike macro.
3238+
///
3239+
/// ```rust
3240+
/// # #![deny(unstable_syntax_pre_expansion)]
3241+
///
3242+
/// macro_rules! identity {
3243+
/// ( $($tokens:tt)* ) => { $($tokens)* }
3244+
/// }
3245+
///
3246+
/// #[cfg(FALSE)]
3247+
/// identity! {
3248+
/// macro foo() {}
3249+
/// }
3250+
/// ```
3251+
///
3252+
/// This is a [future-incompatible] lint to transition this
3253+
/// to a hard error in the future. See [issue #65860] for more details.
3254+
///
3255+
/// [issue #65860]: https://github.com/rust-lang/rust/issues/65860
3256+
/// [future-incompatible]: ../index.md#future-incompatible-lints
3257+
pub UNSTABLE_SYNTAX_PRE_EXPANSION,
3258+
Warn,
3259+
"unstable syntax can change at any point in the future, causing a hard error!",
3260+
@future_incompatible = FutureIncompatibleInfo {
3261+
reference: "issue #65860 <https://github.com/rust-lang/rust/issues/65860>",
3262+
};
3263+
}
3264+
32153265
declare_lint_pass! {
32163266
/// Does nothing as a lint pass, but registers some `Lint`s
32173267
/// that are used by other parts of the compiler.
@@ -3280,6 +3330,7 @@ declare_lint_pass! {
32803330
POINTER_STRUCTURAL_MATCH,
32813331
NONTRIVIAL_STRUCTURAL_MATCH,
32823332
SOFT_UNSTABLE,
3333+
UNSTABLE_SYNTAX_PRE_EXPANSION,
32833334
INLINE_NO_SANITIZE,
32843335
BAD_ASM_STYLE,
32853336
ASM_SUB_REGISTER,

compiler/rustc_session/src/parse.rs

+52-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
//! It also serves as an input to the parser itself.
33
44
use crate::config::CheckCfg;
5-
use crate::lint::{BufferedEarlyLint, BuiltinLintDiagnostics, Lint, LintId};
5+
use crate::lint::{
6+
builtin::UNSTABLE_SYNTAX_PRE_EXPANSION, BufferedEarlyLint, BuiltinLintDiagnostics, Lint, LintId,
7+
};
68
use crate::SessionDiagnostic;
79
use rustc_ast::node_id::NodeId;
810
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
911
use rustc_data_structures::sync::{Lock, Lrc};
1012
use rustc_errors::{emitter::SilentEmitter, ColorConfig, Handler};
1113
use rustc_errors::{
12-
error_code, fallback_fluent_bundle, Applicability, Diagnostic, DiagnosticBuilder,
13-
DiagnosticMessage, ErrorGuaranteed, MultiSpan,
14+
error_code, fallback_fluent_bundle, Applicability, Diagnostic, DiagnosticBuilder, DiagnosticId,
15+
DiagnosticMessage, ErrorGuaranteed, MultiSpan, StashKey,
1416
};
1517
use rustc_feature::{find_feature_issue, GateIssue, UnstableFeatures};
1618
use rustc_span::edition::Edition;
@@ -101,11 +103,58 @@ pub fn feature_err_issue<'a>(
101103
issue: GateIssue,
102104
explain: &str,
103105
) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
106+
let span = span.into();
107+
108+
// Cancel an earlier warning for this same error, if it exists.
109+
if let Some(span) = span.primary_span() {
110+
sess.span_diagnostic
111+
.steal_diagnostic(span, StashKey::EarlySyntaxWarning)
112+
.map(|err| err.cancel());
113+
}
114+
104115
let mut err = sess.span_diagnostic.struct_span_err_with_code(span, explain, error_code!(E0658));
105116
add_feature_diagnostics_for_issue(&mut err, sess, feature, issue);
106117
err
107118
}
108119

120+
/// Construct a future incompatibility diagnostic for a feature gate.
121+
///
122+
/// This diagnostic is only a warning and *does not cause compilation to fail*.
123+
pub fn feature_warn<'a>(sess: &'a ParseSess, feature: Symbol, span: Span, explain: &str) {
124+
feature_warn_issue(sess, feature, span, GateIssue::Language, explain);
125+
}
126+
127+
/// Construct a future incompatibility diagnostic for a feature gate.
128+
///
129+
/// This diagnostic is only a warning and *does not cause compilation to fail*.
130+
///
131+
/// This variant allows you to control whether it is a library or language feature.
132+
/// Almost always, you want to use this for a language feature. If so, prefer `feature_warn`.
133+
pub fn feature_warn_issue<'a>(
134+
sess: &'a ParseSess,
135+
feature: Symbol,
136+
span: Span,
137+
issue: GateIssue,
138+
explain: &str,
139+
) {
140+
let mut err = sess.span_diagnostic.struct_span_warn(span, explain);
141+
add_feature_diagnostics_for_issue(&mut err, sess, feature, issue);
142+
143+
// Decorate this as a future-incompatibility lint as in rustc_middle::lint::struct_lint_level
144+
let lint = UNSTABLE_SYNTAX_PRE_EXPANSION;
145+
let future_incompatible = lint.future_incompatible.as_ref().unwrap();
146+
err.code(DiagnosticId::Lint {
147+
name: lint.name_lower(),
148+
has_future_breakage: false,
149+
is_force_warn: false,
150+
});
151+
err.warn(lint.desc);
152+
err.note(format!("for more information, see {}", future_incompatible.reference));
153+
154+
// A later feature_err call can steal and cancel this warning.
155+
err.stash(span, StashKey::EarlySyntaxWarning);
156+
}
157+
109158
/// Adds the diagnostics for a feature to an existing error.
110159
pub fn add_feature_diagnostics<'a>(err: &mut Diagnostic, sess: &'a ParseSess, feature: Symbol) {
111160
add_feature_diagnostics_for_issue(err, sess, feature, GateIssue::Language);

0 commit comments

Comments
 (0)