Skip to content

Commit 5286836

Browse files
authored
Rollup merge of #143984 - JonathanBrouwer:fix-feature-gate-ice, r=Urgau
Fix ice for feature-gated `cfg` attributes applied to the crate This PR fixes two fixes: 1. When a feature gated option of the `cfg` attribute is applied to the crate, an ICE would occur because features are not yet available at that stage. This is fixed by ignoring the feature gate at that point, the attribute will later be re-checked (this was already done) when the feature gate is available. Fixes #143977 2. Errors and lints on the `cfg` attribute applied to the crate would be produced twice, because of the re-checking. This is fixed by not producing any errors and lints during the first run. The added regression test checks both problems. r? ``@jdonszelmann``
2 parents e7efa04 + bfa45ac commit 5286836

File tree

8 files changed

+120
-38
lines changed

8 files changed

+120
-38
lines changed

compiler/rustc_attr_parsing/src/attributes/cfg.rs

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_session::parse::feature_err;
99
use rustc_span::{Span, Symbol, sym};
1010
use thin_vec::ThinVec;
1111

12-
use crate::context::{AcceptContext, Stage};
12+
use crate::context::{AcceptContext, ShouldEmit, Stage};
1313
use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser};
1414
use crate::{
1515
CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics, try_gate_cfg,
@@ -90,7 +90,7 @@ fn parse_cfg_entry_version<S: Stage>(
9090
list: &MetaItemListParser<'_>,
9191
meta_span: Span,
9292
) -> Option<CfgEntry> {
93-
try_gate_cfg(sym::version, meta_span, cx.sess(), Some(cx.features()));
93+
try_gate_cfg(sym::version, meta_span, cx.sess(), cx.features_option());
9494
let Some(version) = list.single() else {
9595
cx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral { span: list.span });
9696
return None;
@@ -119,7 +119,9 @@ fn parse_cfg_entry_target<S: Stage>(
119119
list: &MetaItemListParser<'_>,
120120
meta_span: Span,
121121
) -> Option<CfgEntry> {
122-
if !cx.features().cfg_target_compact() {
122+
if let Some(features) = cx.features_option()
123+
&& !features.cfg_target_compact()
124+
{
123125
feature_err(
124126
cx.sess(),
125127
sym::cfg_target_compact,
@@ -186,12 +188,13 @@ pub fn eval_config_entry(
186188
cfg_entry: &CfgEntry,
187189
id: NodeId,
188190
features: Option<&Features>,
191+
emit_lints: ShouldEmit,
189192
) -> EvalConfigResult {
190193
match cfg_entry {
191194
CfgEntry::All(subs, ..) => {
192195
let mut all = None;
193196
for sub in subs {
194-
let res = eval_config_entry(sess, sub, id, features);
197+
let res = eval_config_entry(sess, sub, id, features, emit_lints);
195198
// We cannot short-circuit because `eval_config_entry` emits some lints
196199
if !res.as_bool() {
197200
all.get_or_insert(res);
@@ -202,7 +205,7 @@ pub fn eval_config_entry(
202205
CfgEntry::Any(subs, span) => {
203206
let mut any = None;
204207
for sub in subs {
205-
let res = eval_config_entry(sess, sub, id, features);
208+
let res = eval_config_entry(sess, sub, id, features, emit_lints);
206209
// We cannot short-circuit because `eval_config_entry` emits some lints
207210
if res.as_bool() {
208211
any.get_or_insert(res);
@@ -214,7 +217,7 @@ pub fn eval_config_entry(
214217
})
215218
}
216219
CfgEntry::Not(sub, span) => {
217-
if eval_config_entry(sess, sub, id, features).as_bool() {
220+
if eval_config_entry(sess, sub, id, features, emit_lints).as_bool() {
218221
EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
219222
} else {
220223
EvalConfigResult::True
@@ -228,24 +231,28 @@ pub fn eval_config_entry(
228231
}
229232
}
230233
CfgEntry::NameValue { name, name_span, value, span } => {
231-
match sess.psess.check_config.expecteds.get(name) {
232-
Some(ExpectedValues::Some(values)) if !values.contains(&value.map(|(v, _)| v)) => {
233-
id.emit_span_lint(
234-
sess,
235-
UNEXPECTED_CFGS,
236-
*span,
237-
BuiltinLintDiag::UnexpectedCfgValue((*name, *name_span), *value),
238-
);
234+
if let ShouldEmit::ErrorsAndLints = emit_lints {
235+
match sess.psess.check_config.expecteds.get(name) {
236+
Some(ExpectedValues::Some(values))
237+
if !values.contains(&value.map(|(v, _)| v)) =>
238+
{
239+
id.emit_span_lint(
240+
sess,
241+
UNEXPECTED_CFGS,
242+
*span,
243+
BuiltinLintDiag::UnexpectedCfgValue((*name, *name_span), *value),
244+
);
245+
}
246+
None if sess.psess.check_config.exhaustive_names => {
247+
id.emit_span_lint(
248+
sess,
249+
UNEXPECTED_CFGS,
250+
*span,
251+
BuiltinLintDiag::UnexpectedCfgName((*name, *name_span), *value),
252+
);
253+
}
254+
_ => { /* not unexpected */ }
239255
}
240-
None if sess.psess.check_config.exhaustive_names => {
241-
id.emit_span_lint(
242-
sess,
243-
UNEXPECTED_CFGS,
244-
*span,
245-
BuiltinLintDiag::UnexpectedCfgName((*name, *name_span), *value),
246-
);
247-
}
248-
_ => { /* not unexpected */ }
249256
}
250257

251258
if sess.psess.config.contains(&(*name, value.map(|(v, _)| v))) {

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ impl Stage for Early {
226226
sess: &'sess Session,
227227
diag: impl for<'x> Diagnostic<'x>,
228228
) -> ErrorGuaranteed {
229-
if self.emit_errors {
229+
if self.emit_errors.should_emit() {
230230
sess.dcx().emit_err(diag)
231231
} else {
232232
sess.dcx().create_err(diag).delay_as_bug()
@@ -257,7 +257,7 @@ pub struct Early {
257257
/// Whether to emit errors or delay them as a bug
258258
/// For most attributes, the attribute will be parsed again in the `Late` stage and in this case the errors should be delayed
259259
/// But for some, such as `cfg`, the attribute will be removed before the `Late` stage so errors must be emitted
260-
pub emit_errors: bool,
260+
pub emit_errors: ShouldEmit,
261261
}
262262
/// used when parsing attributes during ast lowering
263263
pub struct Late;
@@ -558,6 +558,25 @@ pub enum OmitDoc {
558558
Skip,
559559
}
560560

561+
#[derive(Copy, Clone)]
562+
pub enum ShouldEmit {
563+
/// The operation will emit errors and lints.
564+
/// This is usually what you need.
565+
ErrorsAndLints,
566+
/// The operation will emit *not* errors and lints.
567+
/// Use this if you are *sure* that this operation will be called at a different time with `ShouldEmit::Emit`.
568+
Nothing,
569+
}
570+
571+
impl ShouldEmit {
572+
pub fn should_emit(&self) -> bool {
573+
match self {
574+
ShouldEmit::ErrorsAndLints => true,
575+
ShouldEmit::Nothing => false,
576+
}
577+
}
578+
}
579+
561580
/// Context created once, for example as part of the ast lowering
562581
/// context, through which all attributes can be lowered.
563582
pub struct AttributeParser<'sess, S: Stage = Late> {
@@ -600,7 +619,7 @@ impl<'sess> AttributeParser<'sess, Early> {
600619
tools: Vec::new(),
601620
parse_only: Some(sym),
602621
sess,
603-
stage: Early { emit_errors: false },
622+
stage: Early { emit_errors: ShouldEmit::Nothing },
604623
};
605624
let mut parsed = p.parse_attribute_list(
606625
attrs,
@@ -623,7 +642,7 @@ impl<'sess> AttributeParser<'sess, Early> {
623642
target_span: Span,
624643
target_node_id: NodeId,
625644
features: Option<&'sess Features>,
626-
emit_errors: bool,
645+
emit_errors: ShouldEmit,
627646
parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser<'_>) -> T,
628647
template: &AttributeTemplate,
629648
) -> T {

compiler/rustc_attr_parsing/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ pub use attributes::cfg_old::*;
9595
pub use attributes::util::{
9696
find_crate_name, is_builtin_attr, is_doc_alias_attrs_contain_symbol, parse_version,
9797
};
98-
pub use context::{AttributeParser, Early, Late, OmitDoc};
98+
pub use context::{AttributeParser, Early, Late, OmitDoc, ShouldEmit};
9999
pub use lints::emit_attribute_lint;
100100

101101
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }

compiler/rustc_expand/src/config.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_ast::{
1212
};
1313
use rustc_attr_parsing as attr;
1414
use rustc_attr_parsing::{
15-
AttributeParser, CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg_attr,
15+
AttributeParser, CFG_TEMPLATE, EvalConfigResult, ShouldEmit, eval_config_entry, parse_cfg_attr,
1616
};
1717
use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
1818
use rustc_feature::{
@@ -167,7 +167,9 @@ pub fn pre_configure_attrs(sess: &Session, attrs: &[Attribute]) -> ast::AttrVec
167167
.flat_map(|attr| strip_unconfigured.process_cfg_attr(attr))
168168
.take_while(|attr| {
169169
!is_cfg(attr)
170-
|| strip_unconfigured.cfg_true(attr, strip_unconfigured.lint_node_id).as_bool()
170+
|| strip_unconfigured
171+
.cfg_true(attr, strip_unconfigured.lint_node_id, ShouldEmit::Nothing)
172+
.as_bool()
171173
})
172174
.collect()
173175
}
@@ -401,10 +403,18 @@ impl<'a> StripUnconfigured<'a> {
401403

402404
/// Determines if a node with the given attributes should be included in this configuration.
403405
fn in_cfg(&self, attrs: &[Attribute]) -> bool {
404-
attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr, self.lint_node_id).as_bool())
406+
attrs.iter().all(|attr| {
407+
!is_cfg(attr)
408+
|| self.cfg_true(attr, self.lint_node_id, ShouldEmit::ErrorsAndLints).as_bool()
409+
})
405410
}
406411

407-
pub(crate) fn cfg_true(&self, attr: &Attribute, node: NodeId) -> EvalConfigResult {
412+
pub(crate) fn cfg_true(
413+
&self,
414+
attr: &Attribute,
415+
node: NodeId,
416+
emit_errors: ShouldEmit,
417+
) -> EvalConfigResult {
408418
// We need to run this to do basic validation of the attribute, such as that lits are valid, etc
409419
// FIXME(jdonszelmann) this should not be necessary in the future
410420
match validate_attr::parse_meta(&self.sess.psess, attr) {
@@ -428,15 +438,15 @@ impl<'a> StripUnconfigured<'a> {
428438
attr.span,
429439
node,
430440
self.features,
431-
true,
441+
emit_errors,
432442
parse_cfg_attr,
433443
&CFG_TEMPLATE,
434444
) else {
435445
// Cfg attribute was not parsable, give up
436446
return EvalConfigResult::True;
437447
};
438448

439-
eval_config_entry(self.sess, &cfg, self.lint_node_id, self.features)
449+
eval_config_entry(self.sess, &cfg, self.lint_node_id, self.features, emit_errors)
440450
}
441451

442452
/// If attributes are not allowed on expressions, emit an error for `attr`

compiler/rustc_expand/src/expand.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_ast::{
1313
MetaItemKind, ModKind, NodeId, PatKind, StmtKind, TyKind, token,
1414
};
1515
use rustc_ast_pretty::pprust;
16-
use rustc_attr_parsing::EvalConfigResult;
16+
use rustc_attr_parsing::{EvalConfigResult, ShouldEmit};
1717
use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
1818
use rustc_errors::PResult;
1919
use rustc_feature::Features;
@@ -2171,7 +2171,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
21712171
attr: ast::Attribute,
21722172
pos: usize,
21732173
) -> EvalConfigResult {
2174-
let res = self.cfg().cfg_true(&attr, node.node_id());
2174+
let res = self.cfg().cfg_true(&attr, node.node_id(), ShouldEmit::ErrorsAndLints);
21752175
if res.as_bool() {
21762176
// A trace attribute left in AST in place of the original `cfg` attribute.
21772177
// It can later be used by lints or other diagnostics.

compiler/rustc_resolve/src/def_collector.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::mem;
22

33
use rustc_ast::visit::FnKind;
44
use rustc_ast::*;
5-
use rustc_attr_parsing::{AttributeParser, Early, OmitDoc};
5+
use rustc_attr_parsing::{AttributeParser, Early, OmitDoc, ShouldEmit};
66
use rustc_expand::expand::AstFragment;
77
use rustc_hir as hir;
88
use rustc_hir::def::{CtorKind, CtorOf, DefKind};
@@ -132,7 +132,7 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> {
132132
&self.resolver.tcx.sess,
133133
self.resolver.tcx.features(),
134134
Vec::new(),
135-
Early { emit_errors: false },
135+
Early { emit_errors: ShouldEmit::Nothing },
136136
);
137137
let attrs = parser.parse_attribute_list(
138138
&i.attrs,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// https://github.com/rust-lang/rust/issues/143977
2+
// Check that features are available when an attribute is applied to a crate
3+
4+
#![cfg(version("1.0"))]
5+
//~^ ERROR `cfg(version)` is experimental and subject to change
6+
7+
// Using invalid value `does_not_exist`,
8+
// so we don't accidentally configure out the crate for any certain OS
9+
#![cfg(not(target(os = "does_not_exist")))]
10+
//~^ ERROR compact `cfg(target(..))` is experimental and subject to change
11+
//~| WARN unexpected `cfg` condition value: `does_not_exist`
12+
13+
fn main() {}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
error[E0658]: `cfg(version)` is experimental and subject to change
2+
--> $DIR/cfg-crate-features.rs:4:8
3+
|
4+
LL | #![cfg(version("1.0"))]
5+
| ^^^^^^^^^^^^^^
6+
|
7+
= note: see issue #64796 <https://github.com/rust-lang/rust/issues/64796> for more information
8+
= help: add `#![feature(cfg_version)]` to the crate attributes to enable
9+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
10+
11+
error[E0658]: compact `cfg(target(..))` is experimental and subject to change
12+
--> $DIR/cfg-crate-features.rs:9:12
13+
|
14+
LL | #![cfg(not(target(os = "does_not_exist")))]
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
|
17+
= note: see issue #96901 <https://github.com/rust-lang/rust/issues/96901> for more information
18+
= help: add `#![feature(cfg_target_compact)]` to the crate attributes to enable
19+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
20+
21+
warning: unexpected `cfg` condition value: `does_not_exist`
22+
--> $DIR/cfg-crate-features.rs:9:19
23+
|
24+
LL | #![cfg(not(target(os = "does_not_exist")))]
25+
| ^^^^^^^^^^^^^^^^^^^^^
26+
|
27+
= note: expected values for `target_os` are: `aix`, `amdhsa`, `android`, `cuda`, `cygwin`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `lynxos178`, `macos`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `psp`, `psx`, `redox`, `rtems`, `solaris`, `solid_asp3`, `teeos`, `trusty`, `tvos`, and `uefi` and 9 more
28+
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
29+
= note: `#[warn(unexpected_cfgs)]` on by default
30+
31+
error: aborting due to 2 previous errors; 1 warning emitted
32+
33+
For more information about this error, try `rustc --explain E0658`.

0 commit comments

Comments
 (0)