From 832f1e90b65f91ee3ce367727726c81bb6447463 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Sun, 29 Mar 2026 10:32:11 +0000 Subject: [PATCH 1/2] make `cfg` parser suggest `any` or `all` on `#[cfg(a, b)]` --- .../rustc_attr_parsing/src/attributes/cfg.rs | 31 ++++++++++++- tests/rustdoc-ui/doc-cfg.stderr | 32 ++++++++++++++ tests/rustdoc-ui/invalid-cfg.stderr | 44 +++++++++++++++++++ tests/ui/cfg/suggest-any-or-all.stderr | 24 +++++++--- .../cfg-attr-syntax-validation.stderr | 24 +++++++--- 5 files changed, 142 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index c3b693425a52b..a4fbecdb6da26 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -48,8 +48,37 @@ pub fn parse_cfg( cx.adcx().expected_list(attr_span, args); return None; }; + let Some(single) = list.single() else { - cx.adcx().expected_single_argument(list.span); + let target = cx.target; + let mut adcx = cx.adcx(); + if list.is_empty() { + // `#[cfg()]` + let message = format!("if the {target} should be disabled, use `#[cfg(false)]`"); + adcx.push_suggestion(message, list.span, "(false)".to_string()); + } else { + // `#[cfg(foo, bar)]` + if let Ok(args) = adcx + .sess() + .source_map() + .span_to_source(list.span, |src, start, end| Ok(src[start..end].to_string())) + { + let all = format!("(all{args})"); + let any = format!("(any{args})"); + + let all_msg = format!( + "if the {target} should be enabled when all these predicates are, wrap them in `all`" + ); + let any_msg = format!( + "alternately, if the {target} should be enabled when any these predicates are, wrap them in `any`" + ); + + adcx.push_suggestion(all_msg, list.span, all); + adcx.push_suggestion(any_msg, list.span, any); + } + } + + adcx.expected_single_argument(list.span); return None; }; parse_cfg_entry(cx, single).ok() diff --git a/tests/rustdoc-ui/doc-cfg.stderr b/tests/rustdoc-ui/doc-cfg.stderr index fa25a441e9b79..46b07f34779e8 100644 --- a/tests/rustdoc-ui/doc-cfg.stderr +++ b/tests/rustdoc-ui/doc-cfg.stderr @@ -5,6 +5,11 @@ LL | #[doc(cfg(), cfg(foo, bar))] | ^^^^^^^^^--^^^^^^^^^^^^^^^^^ | | | expected a single argument here + | +help: if the function should be disabled, use `#[cfg(false)]` + | +LL | #[doc(cfg(false), cfg(foo, bar))] + | +++++ error[E0805]: malformed `doc` attribute input --> $DIR/doc-cfg.rs:4:1 @@ -13,6 +18,17 @@ LL | #[doc(cfg(), cfg(foo, bar))] | ^^^^^^^^^^^^^^^^----------^^ | | | expected a single argument here + | +help: if the function should be enabled when all these predicates are, wrap them in `all` + | +LL - #[doc(cfg(), cfg(foo, bar))] +LL + #[doc(cfg(), cfg(all(foo, bar)))] + | +help: alternately, if the function should be enabled when any these predicates are, wrap them in `any` + | +LL - #[doc(cfg(), cfg(foo, bar))] +LL + #[doc(cfg(), cfg(any(foo, bar)))] + | error[E0805]: malformed `doc` attribute input --> $DIR/doc-cfg.rs:7:1 @@ -21,6 +37,11 @@ LL | #[doc(cfg())] | ^^^^^^^^^--^^ | | | expected a single argument here + | +help: if the function should be disabled, use `#[cfg(false)]` + | +LL | #[doc(cfg(false))] + | +++++ error[E0805]: malformed `doc` attribute input --> $DIR/doc-cfg.rs:8:1 @@ -29,6 +50,17 @@ LL | #[doc(cfg(foo, bar))] | ^^^^^^^^^----------^^ | | | expected a single argument here + | +help: if the function should be enabled when all these predicates are, wrap them in `all` + | +LL - #[doc(cfg(foo, bar))] +LL + #[doc(cfg(all(foo, bar)))] + | +help: alternately, if the function should be enabled when any these predicates are, wrap them in `any` + | +LL - #[doc(cfg(foo, bar))] +LL + #[doc(cfg(any(foo, bar)))] + | error: aborting due to 4 previous errors diff --git a/tests/rustdoc-ui/invalid-cfg.stderr b/tests/rustdoc-ui/invalid-cfg.stderr index 5396110709692..ceea3f7c3f518 100644 --- a/tests/rustdoc-ui/invalid-cfg.stderr +++ b/tests/rustdoc-ui/invalid-cfg.stderr @@ -13,6 +13,17 @@ LL | #[doc(cfg(x, y))] | ^^^^^^^^^------^^ | | | expected a single argument here + | +help: if the struct should be enabled when all these predicates are, wrap them in `all` + | +LL - #[doc(cfg(x, y))] +LL + #[doc(cfg(all(x, y)))] + | +help: alternately, if the struct should be enabled when any these predicates are, wrap them in `any` + | +LL - #[doc(cfg(x, y))] +LL + #[doc(cfg(any(x, y)))] + | error[E0539]: malformed `doc` attribute input --> $DIR/invalid-cfg.rs:7:1 @@ -29,6 +40,17 @@ LL | #[doc(cfg(x, y))] | ^^^^^^^^^------^^ | | | expected a single argument here + | +help: if the struct should be enabled when all these predicates are, wrap them in `all` + | +LL - #[doc(cfg(x, y))] +LL + #[doc(cfg(all(x, y)))] + | +help: alternately, if the struct should be enabled when any these predicates are, wrap them in `any` + | +LL - #[doc(cfg(x, y))] +LL + #[doc(cfg(any(x, y)))] + | error[E0539]: malformed `doc` attribute input --> $DIR/invalid-cfg.rs:12:1 @@ -45,6 +67,17 @@ LL | #[doc(cfg(x, y))] | ^^^^^^^^^------^^ | | | expected a single argument here + | +help: if the struct should be enabled when all these predicates are, wrap them in `all` + | +LL - #[doc(cfg(x, y))] +LL + #[doc(cfg(all(x, y)))] + | +help: alternately, if the struct should be enabled when any these predicates are, wrap them in `any` + | +LL - #[doc(cfg(x, y))] +LL + #[doc(cfg(any(x, y)))] + | error[E0539]: malformed `doc` attribute input --> $DIR/invalid-cfg.rs:18:1 @@ -61,6 +94,17 @@ LL | #[doc(cfg(x, y))] | ^^^^^^^^^------^^ | | | expected a single argument here + | +help: if the struct should be enabled when all these predicates are, wrap them in `all` + | +LL - #[doc(cfg(x, y))] +LL + #[doc(cfg(all(x, y)))] + | +help: alternately, if the struct should be enabled when any these predicates are, wrap them in `any` + | +LL - #[doc(cfg(x, y))] +LL + #[doc(cfg(any(x, y)))] + | error: aborting due to 8 previous errors diff --git a/tests/ui/cfg/suggest-any-or-all.stderr b/tests/ui/cfg/suggest-any-or-all.stderr index 596a49f261036..e7f99c694dd66 100644 --- a/tests/ui/cfg/suggest-any-or-all.stderr +++ b/tests/ui/cfg/suggest-any-or-all.stderr @@ -3,22 +3,34 @@ error[E0805]: malformed `cfg` attribute input | LL | #[cfg(foo, bar)] | ^^^^^----------^ - | | | - | | expected a single argument here - | help: must be of the form: `#[cfg(predicate)]` + | | + | expected a single argument here | = note: for more information, visit +help: if the crate should be enabled when all these predicates are, wrap them in `all` + | +LL - #[cfg(foo, bar)] +LL + #[cfg(all(foo, bar))] + | +help: alternately, if the crate should be enabled when any these predicates are, wrap them in `any` + | +LL - #[cfg(foo, bar)] +LL + #[cfg(any(foo, bar))] + | error[E0805]: malformed `cfg` attribute input --> $DIR/suggest-any-or-all.rs:5:1 | LL | #[cfg()] | ^^^^^--^ - | | | - | | expected a single argument here - | help: must be of the form: `#[cfg(predicate)]` + | | + | expected a single argument here | = note: for more information, visit +help: if the crate should be disabled, use `#[cfg(false)]` + | +LL | #[cfg(false)] + | +++++ error: aborting due to 2 previous errors diff --git a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr index 1be52de708e5b..4fa6e041e2fc2 100644 --- a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr +++ b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr @@ -25,22 +25,34 @@ error[E0805]: malformed `cfg` attribute input | LL | #[cfg()] | ^^^^^--^ - | | | - | | expected a single argument here - | help: must be of the form: `#[cfg(predicate)]` + | | + | expected a single argument here | = note: for more information, visit +help: if the crate should be disabled, use `#[cfg(false)]` + | +LL | #[cfg(false)] + | +++++ error[E0805]: malformed `cfg` attribute input --> $DIR/cfg-attr-syntax-validation.rs:19:1 | LL | #[cfg(a, b)] | ^^^^^------^ - | | | - | | expected a single argument here - | help: must be of the form: `#[cfg(predicate)]` + | | + | expected a single argument here | = note: for more information, visit +help: if the crate should be enabled when all these predicates are, wrap them in `all` + | +LL - #[cfg(a, b)] +LL + #[cfg(all(a, b))] + | +help: alternately, if the crate should be enabled when any these predicates are, wrap them in `any` + | +LL - #[cfg(a, b)] +LL + #[cfg(any(a, b))] + | error[E0539]: malformed `cfg` attribute input --> $DIR/cfg-attr-syntax-validation.rs:25:1 From 4f81621244d31fc82b6dd894e3d8bee4703a5703 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Mon, 30 Mar 2026 14:07:12 +0000 Subject: [PATCH 2/2] make sure the right target is passed to `#[cfg()]` when it is parsed --- compiler/rustc_expand/src/expand.rs | 93 ++++++++++++++++++- tests/ui/cfg/suggest-any-or-all.stderr | 6 +- .../cfg-attr-syntax-validation.stderr | 6 +- 3 files changed, 96 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index c8ef295b2a79d..97d2a4d9560b2 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1306,6 +1306,8 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized { fn declared_idents(&self) -> Vec { vec![] } + + fn as_target(&self) -> Target; } impl InvocationCollectorNode for Box { @@ -1457,6 +1459,10 @@ impl InvocationCollectorNode for Box { self.kind.ident().into_iter().collect() } } + + fn as_target(&self) -> Target { + Target::from_ast_item(&*self) + } } struct TraitItemTag; @@ -1498,6 +1504,9 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitItemTa fn flatten_outputs(items: impl Iterator) -> Self::OutputTy { items.flatten().collect() } + fn as_target(&self) -> Target { + Target::from_assoc_item_kind(&self.wrapped.kind, AssocCtxt::Trait) + } } struct ImplItemTag; @@ -1539,6 +1548,9 @@ impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag fn flatten_outputs(items: impl Iterator) -> Self::OutputTy { items.flatten().collect() } + fn as_target(&self) -> Target { + Target::from_assoc_item_kind(&self.wrapped.kind, AssocCtxt::Impl { of_trait: false }) + } } struct TraitImplItemTag; @@ -1580,6 +1592,9 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitImplIt fn flatten_outputs(items: impl Iterator) -> Self::OutputTy { items.flatten().collect() } + fn as_target(&self) -> Target { + Target::from_assoc_item_kind(&self.wrapped.kind, AssocCtxt::Impl { of_trait: true }) + } } impl InvocationCollectorNode for Box { @@ -1602,6 +1617,17 @@ impl InvocationCollectorNode for Box { _ => unreachable!(), } } + fn as_target(&self) -> Target { + match &self.kind { + ForeignItemKind::Static(_) => Target::ForeignStatic, + ForeignItemKind::Fn(_) => Target::ForeignFn, + ForeignItemKind::TyAlias(_) => Target::ForeignTy, + ForeignItemKind::MacCall(_) => { + // Turned into another kind of foreign item during macro expansion. + unreachable!(); + } + } + } } impl InvocationCollectorNode for ast::Variant { @@ -1615,6 +1641,9 @@ impl InvocationCollectorNode for ast::Variant { fn walk_flat_map(self, collector: &mut InvocationCollector<'_, '_>) -> Self::OutputTy { walk_flat_map_variant(collector, self) } + fn as_target(&self) -> Target { + Target::Variant + } } impl InvocationCollectorNode for ast::WherePredicate { @@ -1628,6 +1657,9 @@ impl InvocationCollectorNode for ast::WherePredicate { fn walk_flat_map(self, collector: &mut InvocationCollector<'_, '_>) -> Self::OutputTy { walk_flat_map_where_predicate(collector, self) } + fn as_target(&self) -> Target { + Target::WherePredicate + } } impl InvocationCollectorNode for ast::FieldDef { @@ -1641,6 +1673,9 @@ impl InvocationCollectorNode for ast::FieldDef { fn walk_flat_map(self, collector: &mut InvocationCollector<'_, '_>) -> Self::OutputTy { walk_flat_map_field_def(collector, self) } + fn as_target(&self) -> Target { + Target::Field + } } impl InvocationCollectorNode for ast::PatField { @@ -1654,6 +1689,9 @@ impl InvocationCollectorNode for ast::PatField { fn walk_flat_map(self, collector: &mut InvocationCollector<'_, '_>) -> Self::OutputTy { walk_flat_map_pat_field(collector, self) } + fn as_target(&self) -> Target { + Target::PatField + } } impl InvocationCollectorNode for ast::ExprField { @@ -1667,6 +1705,9 @@ impl InvocationCollectorNode for ast::ExprField { fn walk_flat_map(self, collector: &mut InvocationCollector<'_, '_>) -> Self::OutputTy { walk_flat_map_expr_field(collector, self) } + fn as_target(&self) -> Target { + Target::ExprField + } } impl InvocationCollectorNode for ast::Param { @@ -1680,6 +1721,9 @@ impl InvocationCollectorNode for ast::Param { fn walk_flat_map(self, collector: &mut InvocationCollector<'_, '_>) -> Self::OutputTy { walk_flat_map_param(collector, self) } + fn as_target(&self) -> Target { + Target::Param + } } impl InvocationCollectorNode for ast::GenericParam { @@ -1693,6 +1737,25 @@ impl InvocationCollectorNode for ast::GenericParam { fn walk_flat_map(self, collector: &mut InvocationCollector<'_, '_>) -> Self::OutputTy { walk_flat_map_generic_param(collector, self) } + fn as_target(&self) -> Target { + let mut has_default = false; + Target::GenericParam { + kind: match &self.kind { + rustc_ast::GenericParamKind::Lifetime => { + rustc_hir::target::GenericParamKind::Lifetime + } + rustc_ast::GenericParamKind::Type { default } => { + has_default = default.is_some(); + rustc_hir::target::GenericParamKind::Type + } + rustc_ast::GenericParamKind::Const { default, .. } => { + has_default = default.is_some(); + rustc_hir::target::GenericParamKind::Const + } + }, + has_default, + } + } } impl InvocationCollectorNode for ast::Arm { @@ -1706,6 +1769,9 @@ impl InvocationCollectorNode for ast::Arm { fn walk_flat_map(self, collector: &mut InvocationCollector<'_, '_>) -> Self::OutputTy { walk_flat_map_arm(collector, self) } + fn as_target(&self) -> Target { + Target::Arm + } } impl InvocationCollectorNode for ast::Stmt { @@ -1779,6 +1845,9 @@ impl InvocationCollectorNode for ast::Stmt { } } } + fn as_target(&self) -> Target { + Target::Statement + } } impl InvocationCollectorNode for ast::Crate { @@ -1805,6 +1874,9 @@ impl InvocationCollectorNode for ast::Crate { // Standard prelude imports are left in the crate for backward compatibility. self.items.truncate(collector.cx.num_standard_library_imports); } + fn as_target(&self) -> Target { + Target::Crate + } } impl InvocationCollectorNode for ast::Ty { @@ -1838,6 +1910,10 @@ impl InvocationCollectorNode for ast::Ty { _ => unreachable!(), } } + fn as_target(&self) -> Target { + // This is only used for attribute parsing, which are not allowed on types. + unreachable!() + } } impl InvocationCollectorNode for ast::Pat { @@ -1861,6 +1937,9 @@ impl InvocationCollectorNode for ast::Pat { _ => unreachable!(), } } + fn as_target(&self) -> Target { + todo!(); + } } impl InvocationCollectorNode for ast::Expr { @@ -1887,6 +1966,9 @@ impl InvocationCollectorNode for ast::Expr { _ => unreachable!(), } } + fn as_target(&self) -> Target { + Target::Expression + } } struct OptExprTag; @@ -1916,6 +1998,9 @@ impl InvocationCollectorNode for AstNodeWrapper, OptExprTag> { fn pre_flat_map_node_collect_attr(cfg: &StripUnconfigured<'_>, attr: &ast::Attribute) { cfg.maybe_emit_expr_attr_err(attr); } + fn as_target(&self) -> Target { + Target::Expression + } } /// This struct is a hack to workaround unstable of `stmt_expr_attributes`. @@ -1947,6 +2032,9 @@ impl InvocationCollectorNode for AstNodeWrapper { _ => unreachable!(), } } + fn as_target(&self) -> Target { + Target::Expression + } } fn build_single_delegations<'a, Node: InvocationCollectorNode>( @@ -2210,7 +2298,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { fn expand_cfg_true( &mut self, - node: &mut (impl HasAttrs + HasNodeId), + node: &mut impl InvocationCollectorNode, attr: ast::Attribute, pos: usize, ) -> EvalConfigResult { @@ -2219,8 +2307,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { &attr, attr.span, self.cfg().lint_node_id, - // Target doesn't matter for `cfg` parsing. - Target::Crate, + node.as_target(), self.cfg().features, ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, parse_cfg, diff --git a/tests/ui/cfg/suggest-any-or-all.stderr b/tests/ui/cfg/suggest-any-or-all.stderr index e7f99c694dd66..6cb9a93dfb1ee 100644 --- a/tests/ui/cfg/suggest-any-or-all.stderr +++ b/tests/ui/cfg/suggest-any-or-all.stderr @@ -7,12 +7,12 @@ LL | #[cfg(foo, bar)] | expected a single argument here | = note: for more information, visit -help: if the crate should be enabled when all these predicates are, wrap them in `all` +help: if the function should be enabled when all these predicates are, wrap them in `all` | LL - #[cfg(foo, bar)] LL + #[cfg(all(foo, bar))] | -help: alternately, if the crate should be enabled when any these predicates are, wrap them in `any` +help: alternately, if the function should be enabled when any these predicates are, wrap them in `any` | LL - #[cfg(foo, bar)] LL + #[cfg(any(foo, bar))] @@ -27,7 +27,7 @@ LL | #[cfg()] | expected a single argument here | = note: for more information, visit -help: if the crate should be disabled, use `#[cfg(false)]` +help: if the struct should be disabled, use `#[cfg(false)]` | LL | #[cfg(false)] | +++++ diff --git a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr index 4fa6e041e2fc2..38fd351128f5f 100644 --- a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr +++ b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr @@ -29,7 +29,7 @@ LL | #[cfg()] | expected a single argument here | = note: for more information, visit -help: if the crate should be disabled, use `#[cfg(false)]` +help: if the struct should be disabled, use `#[cfg(false)]` | LL | #[cfg(false)] | +++++ @@ -43,12 +43,12 @@ LL | #[cfg(a, b)] | expected a single argument here | = note: for more information, visit -help: if the crate should be enabled when all these predicates are, wrap them in `all` +help: if the struct should be enabled when all these predicates are, wrap them in `all` | LL - #[cfg(a, b)] LL + #[cfg(all(a, b))] | -help: alternately, if the crate should be enabled when any these predicates are, wrap them in `any` +help: alternately, if the struct should be enabled when any these predicates are, wrap them in `any` | LL - #[cfg(a, b)] LL + #[cfg(any(a, b))]