From b9c3080825c9c23b87293553e90f70f6fb86d174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 31 May 2026 19:21:58 +0000 Subject: [PATCH 1/6] Track items behind `cfg_select` in the same way we do for `cfg` When annotating an item with `#[cfg]` we track both the item that got annotated (with an inert attr) and the names of items that got directly cfg'd out. Extend this mechanism to also work for items within a `cfg_select!`. --- .../src/attributes/cfg_select.rs | 19 +-- .../rustc_builtin_macros/src/cfg_select.rs | 108 ++++++++++++++++-- compiler/rustc_expand/src/expand.rs | 37 ++++-- tests/ui/cfg/auxiliary/cfged_out.rs | 11 +- tests/ui/cfg/diagnostics-cross-crate.rs | 12 +- tests/ui/cfg/diagnostics-cross-crate.stderr | 39 +++++-- tests/ui/cfg/diagnostics-same-crate.rs | 25 ++++ tests/ui/cfg/diagnostics-same-crate.stderr | 74 ++++++++++-- 8 files changed, 276 insertions(+), 49 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index f6b49fe6880ed..c786a3774ae81 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -45,25 +45,28 @@ pub struct CfgSelectBranches { impl CfgSelectBranches { /// Removes the top-most branch for which `predicate` returns `true`, /// or the wildcard if none of the reachable branches satisfied the predicate. - pub fn pop_first_match(&mut self, predicate: F) -> Option<(TokenStream, Span)> + pub fn pop_first_match(&mut self, predicate: F) -> Option<(CfgEntry, TokenStream, Span)> where F: Fn(&CfgEntry) -> bool, { for (index, (cfg, _, _)) in self.reachable.iter().enumerate() { if predicate(cfg) { - let matched = self.reachable.remove(index); - return Some((matched.1, matched.2)); + return Some(self.reachable.remove(index)); } } - self.wildcard.take().map(|(_, tts, span)| (tts, span)) + self.wildcard.take().map(|(_, tts, span)| (CfgEntry::Bool(true, span), tts, span)) } /// Consume this value and iterate over all the `TokenStream`s that it stores. - pub fn into_iter_tts(self) -> impl Iterator { - let it1 = self.reachable.into_iter().map(|(_, tts, span)| (tts, span)); - let it2 = self.wildcard.into_iter().map(|(_, tts, span)| (tts, span)); - let it3 = self.unreachable.into_iter().map(|(_, tts, span)| (tts, span)); + pub fn into_iter_tts(self) -> impl Iterator { + let it1 = self.reachable.into_iter(); + let it2 = + self.wildcard.into_iter().map(|(_, tts, span)| (CfgEntry::Bool(true, span), tts, span)); + let it3 = self + .unreachable + .into_iter() + .map(|(_, tts, span)| (CfgEntry::Bool(false, span), tts, span)); it1.chain(it2).chain(it3) } diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index 35098722a910e..3b61ad6fe9fa4 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -1,9 +1,16 @@ -use rustc_ast::tokenstream::TokenStream; -use rustc_ast::{Expr, ast}; +use rustc_ast::attr::mk_attr_from_item; +use rustc_ast::token::{self, Delimiter, Token, TokenKind}; +use rustc_ast::tokenstream::{ + AttrTokenStream, AttrTokenTree, DelimSpacing, DelimSpan, LazyAttrTokenStream, Spacing, + TokenStream, +}; +use rustc_ast::{AttrItem, AttrItemKind, EarlyParsedAttribute, Expr, Path, Safety, ast}; use rustc_attr_parsing as attr; use rustc_attr_parsing::{CfgSelectBranches, EvalConfigResult, parse_cfg_select}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult}; -use rustc_span::{Ident, Span, sym}; +use rustc_expand::expand::DeclaredIdents; +use rustc_hir::attrs::CfgEntry; +use rustc_span::{DUMMY_SP, Ident, Span, sym}; use smallvec::SmallVec; use crate::errors::CfgSelectNoMatches; @@ -17,6 +24,7 @@ struct CfgSelectResult<'cx, 'sess> { selected_tts: TokenStream, selected_span: Span, other_branches: CfgSelectBranches, + cfg_entry: CfgEntry, } fn tts_to_mac_result<'cx, 'sess>( @@ -37,19 +45,100 @@ macro_rules! forward_to_parser_any_macro { fn $method_name(self: Box) -> Option<$ret_ty> { let CfgSelectResult { ecx, site_span, selected_tts, selected_span, .. } = *self; - for (tts, span) in self.other_branches.into_iter_tts() { + for (_, tts, span) in self.other_branches.into_iter_tts() { let _ = tts_to_mac_result(ecx, site_span, tts, span).$method_name(); } tts_to_mac_result(ecx, site_span, selected_tts, selected_span).$method_name() } }; + + (make_items) => { + // The same logic as above, but we also register the items that were not selected in the + // resolver for error reporting, as well as annotate the selected item with `#[cfg_trace]`. + fn make_items(self: Box) -> Option; 1]>> { + let CfgSelectResult { ecx, site_span, selected_tts, selected_span, cfg_entry, .. } = + *self; + + for (cfg_entry, tts, span) in self.other_branches.into_iter_tts() { + if let Some(items) = tts_to_mac_result(ecx, site_span, tts, span).make_items() { + // Register item names that were not selected for error reporting. We do this + // for `#[cfg]` too. + for item in items { + for name in item.declared_idents() { + ecx.resolver.append_stripped_cfg_item( + ecx.current_expansion.lint_node_id, + name, + cfg_entry.clone(), + span, + ); + } + } + } + } + + tts_to_mac_result(ecx, site_span, selected_tts, selected_span).make_items().map( + |items| { + items + .into_iter() + .map(|mut item| { + let g = &ecx.sess.psess.attr_id_generator; + let args = AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace( + cfg_entry.clone(), + )); + let trees = vec![ + AttrTokenTree::Token( + Token { kind: TokenKind::Pound, span: DUMMY_SP }, + Spacing::JointHidden, + ), + AttrTokenTree::Delimited( + DelimSpan::dummy(), + DelimSpacing::new(Spacing::JointHidden, Spacing::Alone), + Delimiter::Bracket, + AttrTokenStream::new(vec![AttrTokenTree::Token( + Token { + kind: TokenKind::Ident( + sym::cfg_trace, + token::IdentIsRaw::No, + ), + span: DUMMY_SP, + }, + Spacing::Alone, + )]), + ), + ]; + let tokens = + Some(LazyAttrTokenStream::new_direct(AttrTokenStream::new(trees))); + let attr_item = AttrItem { + unsafety: Safety::Default, + path: Path::from_ident(Ident::new( + sym::cfg_trace, + cfg_entry.span(), + )), + args, + tokens: None, + }; + let attr = mk_attr_from_item( + g, + attr_item, + tokens, + ast::AttrStyle::Outer, + cfg_entry.span(), + ); + item.attrs.push(attr); + item + }) + .collect() + }, + ) + } + }; } impl<'cx, 'sess> MacResult for CfgSelectResult<'cx, 'sess> { forward_to_parser_any_macro!(make_expr, Box); forward_to_parser_any_macro!(make_stmts, SmallVec<[ast::Stmt; 1]>); - forward_to_parser_any_macro!(make_items, SmallVec<[Box; 1]>); + forward_to_parser_any_macro!(make_items); forward_to_parser_any_macro!(make_impl_items, SmallVec<[Box; 1]>); forward_to_parser_any_macro!(make_trait_impl_items, SmallVec<[Box; 1]>); @@ -73,15 +162,18 @@ pub(super) fn expand_cfg_select<'cx>( ecx.current_expansion.lint_node_id, ) { Ok(mut branches) => { - if let Some((selected_tts, selected_span)) = branches.pop_first_match(|cfg| { - matches!(attr::eval_config_entry(&ecx.sess, cfg), EvalConfigResult::True) - }) { + if let Some((cfg_entry, selected_tts, selected_span)) = + branches.pop_first_match(|cfg| { + matches!(attr::eval_config_entry(&ecx.sess, cfg), EvalConfigResult::True) + }) + { let mac = CfgSelectResult { ecx, selected_tts, selected_span, other_branches: branches, site_span: sp, + cfg_entry, }; return ExpandResult::Ready(Box::new(mac)); } else { diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 741c34e0304af..9e56ebd70a554 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1250,7 +1250,7 @@ enum AddSemicolon { /// A trait implemented for all `AstFragment` nodes and providing all pieces /// of functionality used by `InvocationCollector`. -trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized { +trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized + DeclaredIdents { type OutputTy = SmallVec<[Self; 1]>; type ItemKind = ItemKind; const KIND: AstFragmentKind; @@ -1302,13 +1302,15 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized { collector.cx.dcx().emit_err(RemoveNodeNotSupported { span, descr: Self::descr() }); } + fn as_target(&self) -> Target; +} + +pub trait DeclaredIdents { /// All of the identifiers (items) declared by this node. /// This is an approximation and should only be used for diagnostics. fn declared_idents(&self) -> Vec { vec![] } - - fn as_target(&self) -> Target; } impl InvocationCollectorNode for Box { @@ -1440,6 +1442,12 @@ impl InvocationCollectorNode for Box { res } + fn as_target(&self) -> Target { + Target::from_ast_item(self) + } +} + +impl DeclaredIdents for Box { fn declared_idents(&self) -> Vec { if let ItemKind::Use(ut) = &self.kind { fn collect_use_tree_leaves(ut: &ast::UseTree, idents: &mut Vec) { @@ -1460,13 +1468,10 @@ impl InvocationCollectorNode for Box { self.kind.ident().into_iter().collect() } } - - fn as_target(&self) -> Target { - Target::from_ast_item(self) - } } struct TraitItemTag; +impl DeclaredIdents for AstNodeWrapper, TraitItemTag> {} impl InvocationCollectorNode for AstNodeWrapper, TraitItemTag> { type OutputTy = SmallVec<[Box; 1]>; type ItemKind = AssocItemKind; @@ -1511,6 +1516,7 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitItemTa } struct ImplItemTag; +impl DeclaredIdents for AstNodeWrapper, ImplItemTag> {} impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag> { type OutputTy = SmallVec<[Box; 1]>; type ItemKind = AssocItemKind; @@ -1555,6 +1561,7 @@ impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag } struct TraitImplItemTag; +impl DeclaredIdents for AstNodeWrapper, TraitImplItemTag> {} impl InvocationCollectorNode for AstNodeWrapper, TraitImplItemTag> { type OutputTy = SmallVec<[Box; 1]>; type ItemKind = AssocItemKind; @@ -1598,6 +1605,7 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitImplIt } } +impl DeclaredIdents for Box {} impl InvocationCollectorNode for Box { const KIND: AstFragmentKind = AstFragmentKind::ForeignItems; fn to_annotatable(self) -> Annotatable { @@ -1628,6 +1636,7 @@ impl InvocationCollectorNode for Box { } } +impl DeclaredIdents for ast::Variant {} impl InvocationCollectorNode for ast::Variant { const KIND: AstFragmentKind = AstFragmentKind::Variants; fn to_annotatable(self) -> Annotatable { @@ -1644,6 +1653,7 @@ impl InvocationCollectorNode for ast::Variant { } } +impl DeclaredIdents for ast::WherePredicate {} impl InvocationCollectorNode for ast::WherePredicate { const KIND: AstFragmentKind = AstFragmentKind::WherePredicates; fn to_annotatable(self) -> Annotatable { @@ -1660,6 +1670,7 @@ impl InvocationCollectorNode for ast::WherePredicate { } } +impl DeclaredIdents for ast::FieldDef {} impl InvocationCollectorNode for ast::FieldDef { const KIND: AstFragmentKind = AstFragmentKind::FieldDefs; fn to_annotatable(self) -> Annotatable { @@ -1676,6 +1687,7 @@ impl InvocationCollectorNode for ast::FieldDef { } } +impl DeclaredIdents for ast::PatField {} impl InvocationCollectorNode for ast::PatField { const KIND: AstFragmentKind = AstFragmentKind::PatFields; fn to_annotatable(self) -> Annotatable { @@ -1692,6 +1704,7 @@ impl InvocationCollectorNode for ast::PatField { } } +impl DeclaredIdents for ast::ExprField {} impl InvocationCollectorNode for ast::ExprField { const KIND: AstFragmentKind = AstFragmentKind::ExprFields; fn to_annotatable(self) -> Annotatable { @@ -1708,6 +1721,7 @@ impl InvocationCollectorNode for ast::ExprField { } } +impl DeclaredIdents for ast::Param {} impl InvocationCollectorNode for ast::Param { const KIND: AstFragmentKind = AstFragmentKind::Params; fn to_annotatable(self) -> Annotatable { @@ -1724,6 +1738,7 @@ impl InvocationCollectorNode for ast::Param { } } +impl DeclaredIdents for ast::GenericParam {} impl InvocationCollectorNode for ast::GenericParam { const KIND: AstFragmentKind = AstFragmentKind::GenericParams; fn to_annotatable(self) -> Annotatable { @@ -1756,6 +1771,7 @@ impl InvocationCollectorNode for ast::GenericParam { } } +impl DeclaredIdents for ast::Arm {} impl InvocationCollectorNode for ast::Arm { const KIND: AstFragmentKind = AstFragmentKind::Arms; fn to_annotatable(self) -> Annotatable { @@ -1772,6 +1788,7 @@ impl InvocationCollectorNode for ast::Arm { } } +impl DeclaredIdents for ast::Stmt {} impl InvocationCollectorNode for ast::Stmt { const KIND: AstFragmentKind = AstFragmentKind::Stmts; fn to_annotatable(self) -> Annotatable { @@ -1848,6 +1865,7 @@ impl InvocationCollectorNode for ast::Stmt { } } +impl DeclaredIdents for ast::Crate {} impl InvocationCollectorNode for ast::Crate { type OutputTy = ast::Crate; const KIND: AstFragmentKind = AstFragmentKind::Crate; @@ -1877,6 +1895,7 @@ impl InvocationCollectorNode for ast::Crate { } } +impl DeclaredIdents for ast::Ty {} impl InvocationCollectorNode for ast::Ty { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Ty; @@ -1914,6 +1933,7 @@ impl InvocationCollectorNode for ast::Ty { } } +impl DeclaredIdents for ast::Pat {} impl InvocationCollectorNode for ast::Pat { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Pat; @@ -1940,6 +1960,7 @@ impl InvocationCollectorNode for ast::Pat { } } +impl DeclaredIdents for ast::Expr {} impl InvocationCollectorNode for ast::Expr { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Expr; @@ -1970,6 +1991,7 @@ impl InvocationCollectorNode for ast::Expr { } struct OptExprTag; +impl DeclaredIdents for AstNodeWrapper, OptExprTag> {} impl InvocationCollectorNode for AstNodeWrapper, OptExprTag> { type OutputTy = Option>; const KIND: AstFragmentKind = AstFragmentKind::OptExpr; @@ -2005,6 +2027,7 @@ impl InvocationCollectorNode for AstNodeWrapper, OptExprTag> { /// It can be removed once that feature is stabilized. struct MethodReceiverTag; +impl DeclaredIdents for AstNodeWrapper {} impl InvocationCollectorNode for AstNodeWrapper { type OutputTy = AstNodeWrapper, MethodReceiverTag>; const KIND: AstFragmentKind = AstFragmentKind::MethodReceiverExpr; diff --git a/tests/ui/cfg/auxiliary/cfged_out.rs b/tests/ui/cfg/auxiliary/cfged_out.rs index 564280b24f598..fff8b7bab8cba 100644 --- a/tests/ui/cfg/auxiliary/cfged_out.rs +++ b/tests/ui/cfg/auxiliary/cfged_out.rs @@ -3,10 +3,19 @@ pub mod inner { pub fn uwu() {} #[cfg(false)] - pub mod doesnt_exist { + pub mod cfgd_out { pub fn hello() {} } + cfg_select! { + false => { + pub mod selected_out { + pub fn hello() {} + } + } + _ => {} + } + pub mod wrong { #[cfg(feature = "suggesting me fails the test!!")] pub fn meow() {} diff --git a/tests/ui/cfg/diagnostics-cross-crate.rs b/tests/ui/cfg/diagnostics-cross-crate.rs index c5d8dcdc62f0a..f7b1dd06a8923 100644 --- a/tests/ui/cfg/diagnostics-cross-crate.rs +++ b/tests/ui/cfg/diagnostics-cross-crate.rs @@ -12,10 +12,14 @@ fn main() { //~^ NOTE found an item that was configured out //~| NOTE not found in `cfged_out::inner` - // The module isn't found - we would like to get a diagnostic, but currently don't due to - // the awkward way the resolver diagnostics are currently implemented. - cfged_out::inner::doesnt_exist::hello(); //~ ERROR cannot find - //~^ NOTE could not find `doesnt_exist` in `inner` + // The module isn't found - we mention that `cfgd_out` is `cfg`d out + cfged_out::inner::cfgd_out::hello(); //~ ERROR cannot find + //~^ NOTE could not find `cfgd_out` in `inner` + //~| NOTE found an item that was configured out + + // The module isn't found - we mention that `selected_out` is `cfg_select`d out + cfged_out::inner::selected_out::hello(); //~ ERROR cannot find + //~^ NOTE could not find `selected_out` in `inner` //~| NOTE found an item that was configured out // It should find the one in the right module, not the wrong one. diff --git a/tests/ui/cfg/diagnostics-cross-crate.stderr b/tests/ui/cfg/diagnostics-cross-crate.stderr index 15e60cc43a3c9..5aa00136f3cdb 100644 --- a/tests/ui/cfg/diagnostics-cross-crate.stderr +++ b/tests/ui/cfg/diagnostics-cross-crate.stderr @@ -1,16 +1,33 @@ -error[E0433]: cannot find `doesnt_exist` in `inner` - --> $DIR/diagnostics-cross-crate.rs:17:23 +error[E0433]: cannot find `cfgd_out` in `inner` + --> $DIR/diagnostics-cross-crate.rs:16:23 | -LL | cfged_out::inner::doesnt_exist::hello(); - | ^^^^^^^^^^^^ could not find `doesnt_exist` in `inner` +LL | cfged_out::inner::cfgd_out::hello(); + | ^^^^^^^^ could not find `cfgd_out` in `inner` | note: found an item that was configured out --> $DIR/auxiliary/cfged_out.rs:6:13 | LL | #[cfg(false)] | ----- the item is gated here -LL | pub mod doesnt_exist { - | ^^^^^^^^^^^^ +LL | pub mod cfgd_out { + | ^^^^^^^^ + +error[E0433]: cannot find `selected_out` in `inner` + --> $DIR/diagnostics-cross-crate.rs:21:23 + | +LL | cfged_out::inner::selected_out::hello(); + | ^^^^^^^^^^^^ could not find `selected_out` in `inner` + | +note: found an item that was configured out + --> $DIR/auxiliary/cfged_out.rs:12:21 + | +LL | / false => { +LL | | pub mod selected_out { + | | ^^^^^^^^^^^^ +LL | | pub fn hello() {} +... | +LL | | _ => {} + | |_________- the item is gated here error[E0425]: cannot find function `uwu` in crate `cfged_out` --> $DIR/diagnostics-cross-crate.rs:7:16 @@ -33,13 +50,13 @@ LL | pub fn uwu() {} | ^^^ error[E0425]: cannot find function `meow` in module `cfged_out::inner::right` - --> $DIR/diagnostics-cross-crate.rs:22:30 + --> $DIR/diagnostics-cross-crate.rs:26:30 | LL | cfged_out::inner::right::meow(); | ^^^^ not found in `cfged_out::inner::right` | note: found an item that was configured out - --> $DIR/auxiliary/cfged_out.rs:17:16 + --> $DIR/auxiliary/cfged_out.rs:26:16 | LL | #[cfg(feature = "what-a-cool-feature")] | ------------------------------- the item is gated behind the `what-a-cool-feature` feature @@ -47,20 +64,20 @@ LL | pub fn meow() {} | ^^^^ error[E0425]: cannot find function `vanished` in crate `cfged_out` - --> $DIR/diagnostics-cross-crate.rs:27:16 + --> $DIR/diagnostics-cross-crate.rs:31:16 | LL | cfged_out::vanished(); | ^^^^^^^^ not found in `cfged_out` | note: found an item that was configured out - --> $DIR/auxiliary/cfged_out.rs:22:8 + --> $DIR/auxiliary/cfged_out.rs:31:8 | LL | #[cfg(i_dont_exist_and_you_can_do_nothing_about_it)] | -------------------------------------------- the item is gated here LL | pub fn vanished() {} | ^^^^^^^^ -error: aborting due to 5 previous errors +error: aborting due to 6 previous errors Some errors have detailed explanations: E0425, E0433. For more information about an error, try `rustc --explain E0425`. diff --git a/tests/ui/cfg/diagnostics-same-crate.rs b/tests/ui/cfg/diagnostics-same-crate.rs index 40babaa3d4c9a..00369eb9c8507 100644 --- a/tests/ui/cfg/diagnostics-same-crate.rs +++ b/tests/ui/cfg/diagnostics-same-crate.rs @@ -16,6 +16,21 @@ pub mod inner { pub mod hi {} } + cfg_select! { + false => { //~ NOTE the item is gated here + //~^ NOTE the item is gated here + //~| NOTE the item is gated here + pub mod selected_out { + //~^ NOTE found an item that was configured out + //~| NOTE found an item that was configured out + //~| NOTE found an item that was configured out + pub fn hello() {} + pub mod hi {} + } + } + _ => {} + } + pub mod wrong { #[cfg(feature = "suggesting me fails the test!!")] pub fn meow() {} @@ -35,6 +50,12 @@ mod placeholder { use super::inner::doesnt_exist::hi; //~^ ERROR unresolved import `super::inner::doesnt_exist` //~| NOTE could not find `doesnt_exist` in `inner` + use super::inner::selected_out; + //~^ ERROR unresolved import `super::inner::selected_out` + //~| NOTE no `selected_out` in `inner` + use super::inner::selected_out::hi; + //~^ ERROR unresolved import `super::inner::selected_out` + //~| NOTE could not find `selected_out` in `inner` } #[cfg(i_dont_exist_and_you_can_do_nothing_about_it)] //~ NOTE the item is gated here @@ -53,6 +74,10 @@ fn main() { inner::doesnt_exist::hello(); //~ ERROR cannot find //~| NOTE could not find `doesnt_exist` in `inner` + // The module isn't found - we get a diagnostic. + inner::selected_out::hello(); //~ ERROR cannot find + //~| NOTE could not find `selected_out` in `inner` + // It should find the one in the right module, not the wrong one. inner::right::meow(); //~ ERROR cannot find function //~| NOTE not found in `inner::right diff --git a/tests/ui/cfg/diagnostics-same-crate.stderr b/tests/ui/cfg/diagnostics-same-crate.stderr index c20542e19eaf3..e28075d30cc5d 100644 --- a/tests/ui/cfg/diagnostics-same-crate.stderr +++ b/tests/ui/cfg/diagnostics-same-crate.stderr @@ -1,5 +1,5 @@ error[E0432]: unresolved import `super::inner::doesnt_exist` - --> $DIR/diagnostics-same-crate.rs:32:9 + --> $DIR/diagnostics-same-crate.rs:47:9 | LL | use super::inner::doesnt_exist; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ no `doesnt_exist` in `inner` @@ -14,7 +14,7 @@ LL | pub mod doesnt_exist { | ^^^^^^^^^^^^ error[E0432]: unresolved import `super::inner::doesnt_exist` - --> $DIR/diagnostics-same-crate.rs:35:23 + --> $DIR/diagnostics-same-crate.rs:50:23 | LL | use super::inner::doesnt_exist::hi; | ^^^^^^^^^^^^ could not find `doesnt_exist` in `inner` @@ -28,8 +28,44 @@ LL | #[cfg(false)] LL | pub mod doesnt_exist { | ^^^^^^^^^^^^ +error[E0432]: unresolved import `super::inner::selected_out` + --> $DIR/diagnostics-same-crate.rs:53:9 + | +LL | use super::inner::selected_out; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ no `selected_out` in `inner` + | +note: found an item that was configured out + --> $DIR/diagnostics-same-crate.rs:23:21 + | +LL | / false => { +LL | | +LL | | +LL | | pub mod selected_out { + | | ^^^^^^^^^^^^ +... | +LL | | _ => {} + | |_________- the item is gated here + +error[E0432]: unresolved import `super::inner::selected_out` + --> $DIR/diagnostics-same-crate.rs:56:23 + | +LL | use super::inner::selected_out::hi; + | ^^^^^^^^^^^^ could not find `selected_out` in `inner` + | +note: found an item that was configured out + --> $DIR/diagnostics-same-crate.rs:23:21 + | +LL | / false => { +LL | | +LL | | +LL | | pub mod selected_out { + | | ^^^^^^^^^^^^ +... | +LL | | _ => {} + | |_________- the item is gated here + error[E0433]: cannot find `doesnt_exist` in `inner` - --> $DIR/diagnostics-same-crate.rs:53:12 + --> $DIR/diagnostics-same-crate.rs:74:12 | LL | inner::doesnt_exist::hello(); | ^^^^^^^^^^^^ could not find `doesnt_exist` in `inner` @@ -43,8 +79,26 @@ LL | #[cfg(false)] LL | pub mod doesnt_exist { | ^^^^^^^^^^^^ +error[E0433]: cannot find `selected_out` in `inner` + --> $DIR/diagnostics-same-crate.rs:78:12 + | +LL | inner::selected_out::hello(); + | ^^^^^^^^^^^^ could not find `selected_out` in `inner` + | +note: found an item that was configured out + --> $DIR/diagnostics-same-crate.rs:23:21 + | +LL | / false => { +LL | | +LL | | +LL | | pub mod selected_out { + | | ^^^^^^^^^^^^ +... | +LL | | _ => {} + | |_________- the item is gated here + error[E0425]: cannot find function `uwu` in module `inner` - --> $DIR/diagnostics-same-crate.rs:49:12 + --> $DIR/diagnostics-same-crate.rs:70:12 | LL | inner::uwu(); | ^^^ not found in `inner` @@ -58,13 +112,13 @@ LL | pub fn uwu() {} | ^^^ error[E0425]: cannot find function `meow` in module `inner::right` - --> $DIR/diagnostics-same-crate.rs:57:19 + --> $DIR/diagnostics-same-crate.rs:82:19 | LL | inner::right::meow(); | ^^^^ not found in `inner::right` | note: found an item that was configured out - --> $DIR/diagnostics-same-crate.rs:26:16 + --> $DIR/diagnostics-same-crate.rs:41:16 | LL | #[cfg(feature = "what-a-cool-feature")] | ------------------------------- the item is gated behind the `what-a-cool-feature` feature @@ -72,26 +126,26 @@ LL | pub fn meow() {} | ^^^^ error[E0425]: cannot find function `uwu` in this scope - --> $DIR/diagnostics-same-crate.rs:45:5 + --> $DIR/diagnostics-same-crate.rs:66:5 | LL | uwu(); | ^^^ not found in this scope error[E0425]: cannot find function `vanished` in this scope - --> $DIR/diagnostics-same-crate.rs:62:5 + --> $DIR/diagnostics-same-crate.rs:87:5 | LL | vanished(); | ^^^^^^^^ not found in this scope | note: found an item that was configured out - --> $DIR/diagnostics-same-crate.rs:41:8 + --> $DIR/diagnostics-same-crate.rs:62:8 | LL | #[cfg(i_dont_exist_and_you_can_do_nothing_about_it)] | -------------------------------------------- the item is gated here LL | pub fn vanished() {} | ^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 10 previous errors Some errors have detailed explanations: E0425, E0432, E0433. For more information about an error, try `rustc --explain E0425`. From 40603d8477a9136b30981013a1c49d3a0e646c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 31 May 2026 22:02:23 +0000 Subject: [PATCH 2/6] Move attr building logic out of the happy-path --- .../rustc_builtin_macros/src/cfg_select.rs | 90 +++++++++---------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index 3b61ad6fe9fa4..340563d451b17 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -1,16 +1,15 @@ -use rustc_ast::attr::mk_attr_from_item; -use rustc_ast::token::{self, Delimiter, Token, TokenKind}; -use rustc_ast::tokenstream::{ - AttrTokenStream, AttrTokenTree, DelimSpacing, DelimSpan, LazyAttrTokenStream, Spacing, - TokenStream, +use rustc_ast::attr::{AttrIdGenerator, mk_attr_from_item}; +use rustc_ast::tokenstream::TokenStream; +use rustc_ast::{ + AttrItem, AttrItemKind, EarlyParsedAttribute, Expr, Path, Safety, ast, token, + tokenstream as tts, }; -use rustc_ast::{AttrItem, AttrItemKind, EarlyParsedAttribute, Expr, Path, Safety, ast}; use rustc_attr_parsing as attr; use rustc_attr_parsing::{CfgSelectBranches, EvalConfigResult, parse_cfg_select}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult}; use rustc_expand::expand::DeclaredIdents; use rustc_hir::attrs::CfgEntry; -use rustc_span::{DUMMY_SP, Ident, Span, sym}; +use rustc_span::{Ident, Span, sym}; use smallvec::SmallVec; use crate::errors::CfgSelectNoMatches; @@ -82,50 +81,10 @@ macro_rules! forward_to_parser_any_macro { items .into_iter() .map(|mut item| { - let g = &ecx.sess.psess.attr_id_generator; - let args = AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace( + item.attrs.push(mk_attr( + &ecx.sess.psess.attr_id_generator, cfg_entry.clone(), )); - let trees = vec![ - AttrTokenTree::Token( - Token { kind: TokenKind::Pound, span: DUMMY_SP }, - Spacing::JointHidden, - ), - AttrTokenTree::Delimited( - DelimSpan::dummy(), - DelimSpacing::new(Spacing::JointHidden, Spacing::Alone), - Delimiter::Bracket, - AttrTokenStream::new(vec![AttrTokenTree::Token( - Token { - kind: TokenKind::Ident( - sym::cfg_trace, - token::IdentIsRaw::No, - ), - span: DUMMY_SP, - }, - Spacing::Alone, - )]), - ), - ]; - let tokens = - Some(LazyAttrTokenStream::new_direct(AttrTokenStream::new(trees))); - let attr_item = AttrItem { - unsafety: Safety::Default, - path: Path::from_ident(Ident::new( - sym::cfg_trace, - cfg_entry.span(), - )), - args, - tokens: None, - }; - let attr = mk_attr_from_item( - g, - attr_item, - tokens, - ast::AttrStyle::Outer, - cfg_entry.span(), - ); - item.attrs.push(attr); item }) .collect() @@ -135,6 +94,39 @@ macro_rules! forward_to_parser_any_macro { }; } +/// Construct a `#[]` attribute from a `CfgEntry`. This allows us to keep track of items +/// that were behind a `cfg_select!`, which is relevant for some diagnostics. +fn mk_attr(g: &AttrIdGenerator, cfg_entry: CfgEntry) -> ast::Attribute { + let cfg_span = cfg_entry.span(); + let args = AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace(cfg_entry)); + let trees = vec![ + tts::AttrTokenTree::Token( + token::Token { kind: token::TokenKind::Pound, span: cfg_span }, + tts::Spacing::JointHidden, + ), + tts::AttrTokenTree::Delimited( + tts::DelimSpan::dummy(), + tts::DelimSpacing::new(tts::Spacing::JointHidden, tts::Spacing::Alone), + token::Delimiter::Bracket, + tts::AttrTokenStream::new(vec![tts::AttrTokenTree::Token( + token::Token { + kind: token::TokenKind::Ident(sym::cfg_trace, token::IdentIsRaw::No), + span: cfg_span, + }, + tts::Spacing::Alone, + )]), + ), + ]; + let tokens = Some(tts::LazyAttrTokenStream::new_direct(tts::AttrTokenStream::new(trees))); + let attr_item = AttrItem { + unsafety: Safety::Default, + path: Path::from_ident(Ident::new(sym::cfg_trace, cfg_span)), + args, + tokens: None, + }; + mk_attr_from_item(g, attr_item, tokens, ast::AttrStyle::Outer, cfg_span) +} + impl<'cx, 'sess> MacResult for CfgSelectResult<'cx, 'sess> { forward_to_parser_any_macro!(make_expr, Box); forward_to_parser_any_macro!(make_stmts, SmallVec<[ast::Stmt; 1]>); From 3ec96cc9eb043aabe735af7db7782512fa498aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 1 Jun 2026 00:07:35 +0000 Subject: [PATCH 3/6] Clean up macro --- .../rustc_builtin_macros/src/cfg_select.rs | 83 ++++++++++--------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index 340563d451b17..c66e3727eeb32 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -40,57 +40,60 @@ fn tts_to_mac_result<'cx, 'sess>( } macro_rules! forward_to_parser_any_macro { - ($method_name:ident, $ret_ty:ty) => { + ($method_name:ident, $ret_ty:ty, $other:expr, $selected:expr) => { fn $method_name(self: Box) -> Option<$ret_ty> { - let CfgSelectResult { ecx, site_span, selected_tts, selected_span, .. } = *self; + let CfgSelectResult { ecx, site_span, selected_tts, selected_span, cfg_entry, .. } = + *self; - for (_, tts, span) in self.other_branches.into_iter_tts() { - let _ = tts_to_mac_result(ecx, site_span, tts, span).$method_name(); + for (cfg_entry, tts, span) in self.other_branches.into_iter_tts() { + let result = tts_to_mac_result(ecx, site_span, tts, span).$method_name(); + ($other)(&mut *ecx, cfg_entry, span, result); } - tts_to_mac_result(ecx, site_span, selected_tts, selected_span).$method_name() + tts_to_mac_result(ecx, site_span, selected_tts, selected_span) + .$method_name() + .map(|elements| ($selected)(&mut *ecx, cfg_entry, elements)) } }; - (make_items) => { - // The same logic as above, but we also register the items that were not selected in the - // resolver for error reporting, as well as annotate the selected item with `#[cfg_trace]`. - fn make_items(self: Box) -> Option; 1]>> { - let CfgSelectResult { ecx, site_span, selected_tts, selected_span, cfg_entry, .. } = - *self; + ($method_name:ident, $ret_ty:ty) => { + forward_to_parser_any_macro!($method_name, $ret_ty, |_, _, _, _| {}, |_, _, elements| { + elements + }); + }; - for (cfg_entry, tts, span) in self.other_branches.into_iter_tts() { - if let Some(items) = tts_to_mac_result(ecx, site_span, tts, span).make_items() { - // Register item names that were not selected for error reporting. We do this - // for `#[cfg]` too. - for item in items { - for name in item.declared_idents() { - ecx.resolver.append_stripped_cfg_item( - ecx.current_expansion.lint_node_id, - name, - cfg_entry.clone(), - span, - ); - } + (make_items) => { + forward_to_parser_any_macro!( + make_items, + SmallVec<[Box; 1]>, + |ecx: &mut ExtCtxt<'_>, + cfg_entry: CfgEntry, + span: Span, + items: Option; 1]>>| if let Some(items) = items { + // Register item names that were not selected for error reporting. We do this + // for `#[cfg]` too. + for item in items { + for name in item.declared_idents() { + ecx.resolver.append_stripped_cfg_item( + ecx.current_expansion.lint_node_id, + name, + cfg_entry.clone(), + span, + ); } } + }, + |ecx: &mut ExtCtxt<'_>, cfg_entry: CfgEntry, items: SmallVec<[Box; 1]>| { + items + .into_iter() + .map(|mut item| { + item.attrs + .push(mk_attr(&ecx.sess.psess.attr_id_generator, cfg_entry.clone())); + item + }) + .collect() } - - tts_to_mac_result(ecx, site_span, selected_tts, selected_span).make_items().map( - |items| { - items - .into_iter() - .map(|mut item| { - item.attrs.push(mk_attr( - &ecx.sess.psess.attr_id_generator, - cfg_entry.clone(), - )); - item - }) - .collect() - }, - ) - } + ); }; } From 8ec23f0e7a11df7e8c2fa6ff6d0ff6da8de8564a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 3 Jun 2026 16:32:36 +0000 Subject: [PATCH 4/6] Address review comments --- .../rustc_builtin_macros/src/cfg_select.rs | 69 +++++++++---------- compiler/rustc_expand/src/expand.rs | 46 ++++++++----- 2 files changed, 60 insertions(+), 55 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index c66e3727eeb32..de0d71aa4adf5 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -47,12 +47,12 @@ macro_rules! forward_to_parser_any_macro { for (cfg_entry, tts, span) in self.other_branches.into_iter_tts() { let result = tts_to_mac_result(ecx, site_span, tts, span).$method_name(); - ($other)(&mut *ecx, cfg_entry, span, result); + $other(&mut *ecx, cfg_entry, span, result); } tts_to_mac_result(ecx, site_span, selected_tts, selected_span) .$method_name() - .map(|elements| ($selected)(&mut *ecx, cfg_entry, elements)) + .map(|elements| $selected(&mut *ecx, cfg_entry, elements)) } }; @@ -61,40 +61,6 @@ macro_rules! forward_to_parser_any_macro { elements }); }; - - (make_items) => { - forward_to_parser_any_macro!( - make_items, - SmallVec<[Box; 1]>, - |ecx: &mut ExtCtxt<'_>, - cfg_entry: CfgEntry, - span: Span, - items: Option; 1]>>| if let Some(items) = items { - // Register item names that were not selected for error reporting. We do this - // for `#[cfg]` too. - for item in items { - for name in item.declared_idents() { - ecx.resolver.append_stripped_cfg_item( - ecx.current_expansion.lint_node_id, - name, - cfg_entry.clone(), - span, - ); - } - } - }, - |ecx: &mut ExtCtxt<'_>, cfg_entry: CfgEntry, items: SmallVec<[Box; 1]>| { - items - .into_iter() - .map(|mut item| { - item.attrs - .push(mk_attr(&ecx.sess.psess.attr_id_generator, cfg_entry.clone())); - item - }) - .collect() - } - ); - }; } /// Construct a `#[]` attribute from a `CfgEntry`. This allows us to keep track of items @@ -133,7 +99,36 @@ fn mk_attr(g: &AttrIdGenerator, cfg_entry: CfgEntry) -> ast::Attribute { impl<'cx, 'sess> MacResult for CfgSelectResult<'cx, 'sess> { forward_to_parser_any_macro!(make_expr, Box); forward_to_parser_any_macro!(make_stmts, SmallVec<[ast::Stmt; 1]>); - forward_to_parser_any_macro!(make_items); + forward_to_parser_any_macro!( + make_items, + SmallVec<[Box; 1]>, + |ecx: &mut ExtCtxt<'_>, + cfg_entry: CfgEntry, + span: Span, + items: Option; 1]>>| if let Some(items) = items { + // Register item names that were not selected for error reporting. We do this + // for `#[cfg]` too. + for item in items { + for name in item.declared_idents() { + ecx.resolver.append_stripped_cfg_item( + ecx.current_expansion.lint_node_id, + name, + cfg_entry.clone(), + span, + ); + } + } + }, + |ecx: &mut ExtCtxt<'_>, cfg_entry: CfgEntry, items: SmallVec<[Box; 1]>| { + items + .into_iter() + .map(|mut item| { + item.attrs.push(mk_attr(&ecx.sess.psess.attr_id_generator, cfg_entry.clone())); + item + }) + .collect() + } + ); forward_to_parser_any_macro!(make_impl_items, SmallVec<[Box; 1]>); forward_to_parser_any_macro!(make_trait_impl_items, SmallVec<[Box; 1]>); diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 9e56ebd70a554..53b75d8d1c8c3 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1313,6 +1313,34 @@ pub trait DeclaredIdents { } } +macro_rules! declared_idents { + ($($ty:ty),*) => { + $(impl DeclaredIdents for $ty {})* + }; +} + +// Use the default "empty" list of idents for the following: +declared_idents! { + AstNodeWrapper, TraitItemTag>, + AstNodeWrapper, ImplItemTag>, + AstNodeWrapper, TraitImplItemTag>, + Box, + ast::Variant, + ast::WherePredicate, + ast::FieldDef, + ast::PatField, + ast::ExprField, + ast::Param, + ast::GenericParam, + ast::Arm, + ast::Stmt, + ast::Crate, + ast::Ty, + ast::Pat, + ast::Expr, + AstNodeWrapper, OptExprTag> +} + impl InvocationCollectorNode for Box { const KIND: AstFragmentKind = AstFragmentKind::Items; fn to_annotatable(self) -> Annotatable { @@ -1471,7 +1499,6 @@ impl DeclaredIdents for Box { } struct TraitItemTag; -impl DeclaredIdents for AstNodeWrapper, TraitItemTag> {} impl InvocationCollectorNode for AstNodeWrapper, TraitItemTag> { type OutputTy = SmallVec<[Box; 1]>; type ItemKind = AssocItemKind; @@ -1516,7 +1543,6 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitItemTa } struct ImplItemTag; -impl DeclaredIdents for AstNodeWrapper, ImplItemTag> {} impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag> { type OutputTy = SmallVec<[Box; 1]>; type ItemKind = AssocItemKind; @@ -1561,7 +1587,6 @@ impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag } struct TraitImplItemTag; -impl DeclaredIdents for AstNodeWrapper, TraitImplItemTag> {} impl InvocationCollectorNode for AstNodeWrapper, TraitImplItemTag> { type OutputTy = SmallVec<[Box; 1]>; type ItemKind = AssocItemKind; @@ -1605,7 +1630,6 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitImplIt } } -impl DeclaredIdents for Box {} impl InvocationCollectorNode for Box { const KIND: AstFragmentKind = AstFragmentKind::ForeignItems; fn to_annotatable(self) -> Annotatable { @@ -1636,7 +1660,6 @@ impl InvocationCollectorNode for Box { } } -impl DeclaredIdents for ast::Variant {} impl InvocationCollectorNode for ast::Variant { const KIND: AstFragmentKind = AstFragmentKind::Variants; fn to_annotatable(self) -> Annotatable { @@ -1653,7 +1676,6 @@ impl InvocationCollectorNode for ast::Variant { } } -impl DeclaredIdents for ast::WherePredicate {} impl InvocationCollectorNode for ast::WherePredicate { const KIND: AstFragmentKind = AstFragmentKind::WherePredicates; fn to_annotatable(self) -> Annotatable { @@ -1670,7 +1692,6 @@ impl InvocationCollectorNode for ast::WherePredicate { } } -impl DeclaredIdents for ast::FieldDef {} impl InvocationCollectorNode for ast::FieldDef { const KIND: AstFragmentKind = AstFragmentKind::FieldDefs; fn to_annotatable(self) -> Annotatable { @@ -1687,7 +1708,6 @@ impl InvocationCollectorNode for ast::FieldDef { } } -impl DeclaredIdents for ast::PatField {} impl InvocationCollectorNode for ast::PatField { const KIND: AstFragmentKind = AstFragmentKind::PatFields; fn to_annotatable(self) -> Annotatable { @@ -1704,7 +1724,6 @@ impl InvocationCollectorNode for ast::PatField { } } -impl DeclaredIdents for ast::ExprField {} impl InvocationCollectorNode for ast::ExprField { const KIND: AstFragmentKind = AstFragmentKind::ExprFields; fn to_annotatable(self) -> Annotatable { @@ -1721,7 +1740,6 @@ impl InvocationCollectorNode for ast::ExprField { } } -impl DeclaredIdents for ast::Param {} impl InvocationCollectorNode for ast::Param { const KIND: AstFragmentKind = AstFragmentKind::Params; fn to_annotatable(self) -> Annotatable { @@ -1738,7 +1756,6 @@ impl InvocationCollectorNode for ast::Param { } } -impl DeclaredIdents for ast::GenericParam {} impl InvocationCollectorNode for ast::GenericParam { const KIND: AstFragmentKind = AstFragmentKind::GenericParams; fn to_annotatable(self) -> Annotatable { @@ -1771,7 +1788,6 @@ impl InvocationCollectorNode for ast::GenericParam { } } -impl DeclaredIdents for ast::Arm {} impl InvocationCollectorNode for ast::Arm { const KIND: AstFragmentKind = AstFragmentKind::Arms; fn to_annotatable(self) -> Annotatable { @@ -1788,7 +1804,6 @@ impl InvocationCollectorNode for ast::Arm { } } -impl DeclaredIdents for ast::Stmt {} impl InvocationCollectorNode for ast::Stmt { const KIND: AstFragmentKind = AstFragmentKind::Stmts; fn to_annotatable(self) -> Annotatable { @@ -1865,7 +1880,6 @@ impl InvocationCollectorNode for ast::Stmt { } } -impl DeclaredIdents for ast::Crate {} impl InvocationCollectorNode for ast::Crate { type OutputTy = ast::Crate; const KIND: AstFragmentKind = AstFragmentKind::Crate; @@ -1895,7 +1909,6 @@ impl InvocationCollectorNode for ast::Crate { } } -impl DeclaredIdents for ast::Ty {} impl InvocationCollectorNode for ast::Ty { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Ty; @@ -1933,7 +1946,6 @@ impl InvocationCollectorNode for ast::Ty { } } -impl DeclaredIdents for ast::Pat {} impl InvocationCollectorNode for ast::Pat { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Pat; @@ -1960,7 +1972,6 @@ impl InvocationCollectorNode for ast::Pat { } } -impl DeclaredIdents for ast::Expr {} impl InvocationCollectorNode for ast::Expr { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Expr; @@ -1991,7 +2002,6 @@ impl InvocationCollectorNode for ast::Expr { } struct OptExprTag; -impl DeclaredIdents for AstNodeWrapper, OptExprTag> {} impl InvocationCollectorNode for AstNodeWrapper, OptExprTag> { type OutputTy = Option>; const KIND: AstFragmentKind = AstFragmentKind::OptExpr; From c84dfed88fb7b35d5e944aa20b171d95b15376b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 3 Jun 2026 18:06:33 +0000 Subject: [PATCH 5/6] Do not include tokens in attr_item --- .../rustc_builtin_macros/src/cfg_select.rs | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index de0d71aa4adf5..16ec6fa44156d 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -1,8 +1,7 @@ use rustc_ast::attr::{AttrIdGenerator, mk_attr_from_item}; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{ - AttrItem, AttrItemKind, EarlyParsedAttribute, Expr, Path, Safety, ast, token, - tokenstream as tts, + AttrItem, AttrItemKind, EarlyParsedAttribute, Expr, Path, Safety, ast, tokenstream as tts, }; use rustc_attr_parsing as attr; use rustc_attr_parsing::{CfgSelectBranches, EvalConfigResult, parse_cfg_select}; @@ -68,25 +67,8 @@ macro_rules! forward_to_parser_any_macro { fn mk_attr(g: &AttrIdGenerator, cfg_entry: CfgEntry) -> ast::Attribute { let cfg_span = cfg_entry.span(); let args = AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace(cfg_entry)); - let trees = vec![ - tts::AttrTokenTree::Token( - token::Token { kind: token::TokenKind::Pound, span: cfg_span }, - tts::Spacing::JointHidden, - ), - tts::AttrTokenTree::Delimited( - tts::DelimSpan::dummy(), - tts::DelimSpacing::new(tts::Spacing::JointHidden, tts::Spacing::Alone), - token::Delimiter::Bracket, - tts::AttrTokenStream::new(vec![tts::AttrTokenTree::Token( - token::Token { - kind: token::TokenKind::Ident(sym::cfg_trace, token::IdentIsRaw::No), - span: cfg_span, - }, - tts::Spacing::Alone, - )]), - ), - ]; - let tokens = Some(tts::LazyAttrTokenStream::new_direct(tts::AttrTokenStream::new(trees))); + // This makes the trace attributes unobservable to token-based proc macros. + let tokens = Some(tts::LazyAttrTokenStream::new_direct(tts::AttrTokenStream::default())); let attr_item = AttrItem { unsafety: Safety::Default, path: Path::from_ident(Ident::new(sym::cfg_trace, cfg_span)), From a0d064f1a375fae6157c0a10050b2a26d6b092e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 24 Mar 2026 01:29:46 +0000 Subject: [PATCH 6/6] `>::into` lint Add two lints, very similar to `clippy::useless_conversion`, detecting when `.into()` is being called unnecessarily. We present two separate lints, one that triggers inside of macros, because it is common for macros to have `.into()` calls to make the caller side easier to use. In those cases people should still use the fully-qualified path instead, but allow them to silence that lint without silencing the more general, more likely to be problematic case. This lint allows us to protect developers from the semver-hazard that time 0.3.34 encountered, where a std change caused a valid method chain to start producing inference errors. This is explicitly allowed by the Rust project's backwards compatibility guarantees (inference is not included in them), which means that relying on the blanket `impl Into` is a problem. The lint as implemented has false negatives: because of the way type aliases are handled by the type system, we can't know whether `let _: i32 = 0i32.into()` corresponds to a call on `i32` *or* a call on a type alias that is `i32` only on some platforms (like `#[cfg(..)] type Int = i32;`). To avoid false positives, we keep track of type aliases that have been imported in the local crate and mark their types for exclusion. This means that calling `let _: i32 = Int::into(0i32);` will not be linted against even though it should. --- .../rustc_borrowck/src/region_infer/values.rs | 6 +- compiler/rustc_builtin_macros/src/autodiff.rs | 3 +- .../src/intrinsics/simd.rs | 2 +- .../src/interpret/intrinsics.rs | 2 +- .../src/infer/snapshot/undo_log.rs | 1 + compiler/rustc_lint/src/builtin.rs | 199 ++++++++++++++++++ compiler/rustc_lint/src/lib.rs | 3 + compiler/rustc_lint/src/lints.rs | 30 +++ compiler/rustc_span/src/symbol.rs | 1 + compiler/rustc_symbol_mangling/src/lib.rs | 2 +- .../src/normalize_projection_ty.rs | 4 +- library/core/src/convert/mod.rs | 1 + library/coretests/tests/convert.rs | 1 + library/std/src/sys/process/env.rs | 2 + .../needless_return_with_question_mark.fixed | 1 + .../ui/needless_return_with_question_mark.rs | 1 + .../needless_return_with_question_mark.stderr | 10 +- .../clippy/tests/ui/useless_conversion.fixed | 2 +- .../clippy/tests/ui/useless_conversion.rs | 2 +- tests/ui/lint/self_type_conversion.rs | 81 +++++++ tests/ui/lint/self_type_conversion.stderr | 25 +++ .../macro-or-patterns-back-compat.fixed | 2 +- .../macros/macro-or-patterns-back-compat.rs | 2 +- .../macro-pat-pattern-followed-by-or.rs | 2 +- ...re-inference-hr-ambig-alias-naming-self.rs | 1 + .../global-param-env-after-norm.rs | 1 + ...aram-method-from-unnormalized-param-env.rs | 1 + 27 files changed, 367 insertions(+), 21 deletions(-) create mode 100644 tests/ui/lint/self_type_conversion.rs create mode 100644 tests/ui/lint/self_type_conversion.stderr diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 626b7e1084192..90171cfce6573 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -437,14 +437,12 @@ impl ToElementIndex<'_> for RegionVid { impl<'tcx> ToElementIndex<'tcx> for ty::PlaceholderRegion<'tcx> { fn add_to_row(self, values: &mut RegionValues<'tcx, N>, row: N) -> bool { - let placeholder: ty::PlaceholderRegion<'tcx> = self.into(); - let index = values.placeholder_indices.lookup_index(placeholder); + let index = values.placeholder_indices.lookup_index(self); values.placeholders.insert(row, index) } fn contained_in_row(self, values: &RegionValues<'tcx, N>, row: N) -> bool { - let placeholder: ty::PlaceholderRegion<'tcx> = self.into(); - let index = values.placeholder_indices.lookup_index(placeholder); + let index = values.placeholder_indices.lookup_index(self); values.placeholders.contains(row, index) } } diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index 76c43e0df1a24..f7bda717ae825 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -567,8 +567,7 @@ mod llvm_enzyme { PatKind::Ident(_, ident, _) => ecx.expr_path(ecx.path_ident(span, ident)), _ => todo!(), }) - .collect::>() - .into(), + .collect::>(), ); let enzyme_path_idents = ecx.std_path(&[sym::intrinsics, sym::autodiff]); diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs index b8e1886b2d3c1..5d7e457b8e1ba 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs @@ -371,7 +371,7 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( } for i in 0..lane_count { - let ret_lane = ret.place_lane(fx, i.into()); + let ret_lane = ret.place_lane(fx, i); ret_lane.write_cvalue(fx, value); } } diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 3f85e10e5ca94..bd5ff02254c55 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -773,7 +773,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { _ => return interp_ok(false), } - trace!("{:?}", self.dump_place(&dest.clone().into())); + trace!("{:?}", self.dump_place(&dest)); self.return_to_block(ret)?; interp_ok(true) } diff --git a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs index 09d8eb3bf9232..d1cf26faf49f2 100644 --- a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs +++ b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs @@ -39,6 +39,7 @@ macro_rules! impl_from { $( impl<'tcx> From<$ty> for UndoLog<'tcx> { fn from(x: $ty) -> Self { + #[cfg_attr(not(bootstrap), allow(self_type_conversion))] UndoLog::$ctor(x.into()) } } diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 2c7ccfb25ae9b..1ba8e130a9ea2 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -22,6 +22,7 @@ use rustc_ast::visit::{FnCtxt, FnKind}; use rustc_ast::{self as ast, *}; use rustc_ast_pretty::pprust::expr_to_string; use rustc_attr_parsing::AttributeParser; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::{Applicability, Diagnostic, msg}; use rustc_feature::GateIssue; use rustc_hir::attrs::{AttributeKind, DocAttribute}; @@ -59,6 +60,7 @@ use crate::lints::{ BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, BuiltinWhileTrue, EqInternalMethodImplemented, InvalidAsmLabel, + SelfTypeConversionDiag, SelfTypeConversionInMacroDiag, }; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; @@ -1599,6 +1601,8 @@ declare_lint_pass!( SoftLints => [ WHILE_TRUE, NON_SHORTHAND_FIELD_PATTERNS, + SELF_TYPE_CONVERSION, + SELF_TYPE_CONVERSION_IN_MACRO, UNSAFE_CODE, MISSING_DOCS, MISSING_COPY_IMPLEMENTATIONS, @@ -3246,3 +3250,198 @@ impl<'tcx> LateLintPass<'tcx> for InternalEqTraitMethodImpls { } } } + +declare_lint! { + /// The `self_type_conversion` lint detects when a call to `.into()` does not have any effect. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(self_type_conversion)] + /// fn main() { + /// let _: i32 = 0i32.into(); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The standard library provides an `impl Into for T` implementation, which lets you call + /// `.into()` on any type. Relying on that `impl` is a potential semver problem, as a new, more + /// specific `impl` of `Into` for the type could be added in the future, causing an inference + /// error on code that previously compiled successfully. + /// + /// As a way to side-step this potential future failure, it is a good idea to instead use the + /// fully-qualified path to the correct impl, like `>::into(value)`. When + /// calling the method with this syntax, inference does not come into play. + /// + /// ### Limitations + /// + /// The lint as currently implemented has both false negatives *and* false positives. + /// + /// When `use` imports and/or type aliases have `cfg` attributes or are behind a `cfg_select!` + /// macro invocation, their *underlying* type will *not* be considered for the purposes of this + /// lint, in order to avoid complaining about useless conversions for types that are platform + /// dependent. + /// + /// Even then, the analysis for whether a type is different under different platforms isn't + /// exhaustive: if a containing *module* is the one that is gated with a `cfg` attribute, this + /// lint will not detect that. + pub SELF_TYPE_CONVERSION, + Warn, + "unnecessary call to `.into()`", +} + +declare_lint! { + /// The `self_type_conversion_in_macro` lint detects when a call to `.into()` within a macro + /// expansion does not have any effect. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(self_type_conversion_in_macro)] + /// macro_rules! foo { + /// ($x:expr) => { + /// $x.into() + /// } + /// } + /// fn main() { + /// let () = foo!(()); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The standard library provides an `impl Into for T` implementation, which lets you call + /// `.into()` on any type. Relying on that `impl` is a potential semver problem, as a new, more + /// specific `impl` of `Into` for the type could be added in the future, causing an inference + /// error on code that previously compiled successfully. + /// + /// As a way to side-step this potential future failure, it is a good idea to instead use the + /// fully-qualified path to the correct impl, like `>::into(value)`. When + /// calling the method with this syntax, inference does not come into play. + /// + /// ### Limitations + /// + /// The lint as currently implemented has both false negatives *and* false positives. + /// + /// When `use` imports and/or type aliases have `cfg` attributes or are behind a `cfg_select!` + /// macro invocation, their *underlying* type will *not* be considered for the purposes of this + /// lint, in order to avoid complaining about useless conversions for types that are platform + /// dependent. + /// + /// Even then, the analysis for whether a type is different under different platforms isn't + /// exhaustive: if a containing *module* is the one that is gated with a `cfg` attribute, this + /// lint will not detect that. + pub SELF_TYPE_CONVERSION_IN_MACRO, + Allow, + "unnecessary call to `.into()` within a macro expansion", +} + +pub struct SelfTypeConversion<'tcx> { + ignored_types: FxHashSet>, +} + +impl_lint_pass!(SelfTypeConversion<'_> => [SELF_TYPE_CONVERSION, SELF_TYPE_CONVERSION_IN_MACRO]); + +impl SelfTypeConversion<'_> { + pub fn new() -> Self { + Self { ignored_types: Default::default() } + } +} + +impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { + fn check_item_post(&mut self, cx: &LateContext<'tcx>, item: &hir::Item<'_>) { + let hir::ItemKind::Use(path, _kind) = item.kind else { return }; + // Look at `cfg`d types as to account for things like `std::io::repr_bitpacked` and + // `std::io::repr_unpacked`. + // + // The compiler currently doesn't track when a type alias has been interacted with in type + // type system, which means that when given `type Alias = i32;` and `let x: i32 = 42;` or + // `let y: Alias = 42`, we can't differentiate between calling `let _: i32 = x.into();` and + // `let _: i32 = y.into();`: as far as the compiler is concerned in both cases the receiver + // type is `i32`. Worse yet, type aliases are often used to select different types in + // different platforms, meaning that `y.into()` might be a no-op in some platforms, while + // being required in others. To avoid some false positives, we keep track of type aliases + // that have `cfg` attributes and will *not* emit the lint against calling `.into()` on the + // underlying type. This *will* cause false negatives. + // + // There are likely other combinations that we should check for in order to avoid false + // positives, like looking at the parent items for the type alias for `cfg` attributes, but + // empirically these two checks seem to account for the majority of the cases in the wild. + // FIXME: verify the above with crater run :) + + // Whether we've encountered `#[cfg(..)] use foo::bar;`. + let import_has_cfg = find_attr!(cx.tcx, item.hir_id(), CfgTrace(..)); + for res in path.res.iter() { + let Some(Res::Def(DefKind::TyAlias, def_id)) = res else { continue }; + let ty = cx.tcx.type_of(*def_id).instantiate_identity().skip_normalization(); + + // Whether we've encountered `#[cfg(..)] type Alias = Ty;`. + let alias_has_cfg = find_attr!(cx.tcx, *def_id, CfgTrace(..)); + if alias_has_cfg || import_has_cfg { + // We have in scope a type alias of type `ty` which is gated behind a `cfg` + // attribute, either at its definition or through its import, which is indicative + // of platform specific code. This kind of code often ends up with `.into()` method + // calls that are useless in some configurations, but *necessary* in others. As a + // first-order approximation, we ignore *all* `val_of_ty.into()` calls. + self.ignored_types.insert(ty); + } + } + } + + /// Look for method calls to `Into::into` that rely on inference and that ends up using the + /// `impl Into for T {}` blanket `impl`. + /// + /// This filters on explicit `foo.into()` method calls (ignoring `<_ as Into<_>>::into(foo)`). + fn check_expr_post(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { + let hir::ExprKind::MethodCall(_segment, rcvr, args, _) = expr.kind else { return }; + if !args.is_empty() { + // If we have `foo.method(...)` with arguments, we already know that `method` can't be + // `into`, so we bail. + return; + } + + let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else { return }; + tracing::debug!(?def_id); + if Some(def_id) != cx.tcx.get_diagnostic_item(sym::into_fn) { + // We don't have `foo.into()` corresponding to `Into::into`. + return; + } + + let ty = cx.typeck_results().expr_ty(expr); + let rcvr_ty = cx.typeck_results().expr_ty(rcvr); + + if ty != rcvr_ty { + // If the type we are converting from and converting towards are different, there's + // nothing to complain about. + return; + } + + if self.ignored_types.contains(&ty) { + // Found `<{ty} as Into<{ty}>::into()` call, but that type has been detected to have + // been annotated with `#[cfg]`, meaning it there are likely configurations in which + // the receiver and target types are different. + tracing::debug!("Skipping linting `<{ty} as Into<{ty}>::into()` call"); + return; + } + + if let Some(expn) = expr.span.macro_backtrace().next() { + if expn.macro_def_id.map_or(false, |did| did.is_local()) { + cx.emit_span_lint( + SELF_TYPE_CONVERSION_IN_MACRO, + expr.span, + SelfTypeConversionInMacroDiag { ty }, + ); + } + // A macro that expands to this code isn't great, but end-users of a macro can't do + // anything about it. + return; + } + + cx.emit_span_lint(SELF_TYPE_CONVERSION, expr.span, SelfTypeConversionDiag { ty }); + } +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 1efc8b70ef22d..3a2f588c922dc 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -287,6 +287,9 @@ fn register_builtins(store: &mut LintStore) { store.register_lints(&foreign_modules::get_lints()); store.register_lints(&HardwiredLints::lint_vec()); + store.register_lints(&SelfTypeConversion::lint_vec()); + store.register_late_pass(|_| Box::new(SelfTypeConversion::new())); + add_lint_group!( "nonstandard_style", NON_CAMEL_CASE_TYPES, diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 227a413ea9b07..4326586a777d8 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -65,6 +65,36 @@ pub(crate) enum ShadowedIntoIterDiagSub { }, } +#[derive(Diagnostic)] +#[diag("useless conversion to the same type: `{$ty}`")] +#[note( + "this method call relies on the `impl Into for T` blanket implementation and type \ + inference, which is a semver hazard as a new `impl Into<_>` that affects your type might be \ + added in the future causing type inference errors" +)] +#[note( + "you can instead use the fully-qualified path `<{$ty} as Into<{$ty}>::into(val) to avoid \ + triggering this lint" +)] +pub(crate) struct SelfTypeConversionDiag<'t> { + pub ty: Ty<'t>, +} + +#[derive(Diagnostic)] +#[diag("useless conversion to the same type: `{$ty}`")] +#[note( + "this method call relies on the `impl Into for T` blanket implementation and type \ + inference, which is a semver hazard as a new `impl Into<_>` that affects your type might be \ + added in the future causing type inference errors" +)] +#[note( + "you can instead use the fully-qualified path `<{$ty} as Into<{$ty}>::into(val) to avoid \ + triggering this lint" +)] +pub(crate) struct SelfTypeConversionInMacroDiag<'t> { + pub ty: Ty<'t>, +} + // autorefs.rs #[derive(Diagnostic)] #[diag("implicit autoref creates a reference to the dereference of a raw pointer")] diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index fb433aef68cf8..f43184c3b566f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1110,6 +1110,7 @@ symbols! { internal_eq_trait_method_impls, internal_features, into_async_iter_into_iter, + into_fn, into_future, into_iter, into_try_type, diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs index c052037f05b39..475b0827a53d3 100644 --- a/compiler/rustc_symbol_mangling/src/lib.rs +++ b/compiler/rustc_symbol_mangling/src/lib.rs @@ -219,7 +219,7 @@ fn compute_symbol_name<'tcx>( // However, we don't have the wasm import module map there yet. tcx.is_foreign_item(def_id) && tcx.sess.target.is_like_wasm - && tcx.wasm_import_module_map(def_id.krate).contains_key(&def_id.into()) + && tcx.wasm_import_module_map(def_id.krate).contains_key(&def_id) }; if !wasm_import_module_exception_force_mangling { diff --git a/compiler/rustc_traits/src/normalize_projection_ty.rs b/compiler/rustc_traits/src/normalize_projection_ty.rs index 96586713aed6f..ccc67ed41d5fa 100644 --- a/compiler/rustc_traits/src/normalize_projection_ty.rs +++ b/compiler/rustc_traits/src/normalize_projection_ty.rs @@ -35,7 +35,7 @@ fn normalize_canonicalized_projection<'tcx>( let normalized_term = traits::normalize_projection_term( selcx, param_env, - goal.into(), + goal, cause, 0, &mut obligations, @@ -111,7 +111,7 @@ fn normalize_canonicalized_inherent_projection<'tcx>( let normalized_term = traits::normalize_inherent_projection( selcx, param_env, - goal.into(), + goal, cause, 0, &mut obligations, diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index 34cf9c5d0a5b2..aa66dc09dc7bc 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -449,6 +449,7 @@ pub const trait AsMut: PointeeSized { #[rustc_const_unstable(feature = "const_convert", issue = "143773")] pub const trait Into: Sized { /// Converts this type into the (usually inferred) input type. + #[rustc_diagnostic_item = "into_fn"] #[must_use] #[stable(feature = "rust1", since = "1.0.0")] fn into(self) -> T; diff --git a/library/coretests/tests/convert.rs b/library/coretests/tests/convert.rs index 1eb7468e56ea7..c0d1de2f27356 100644 --- a/library/coretests/tests/convert.rs +++ b/library/coretests/tests/convert.rs @@ -8,6 +8,7 @@ fn convert() { assert_eq!(FOO, 42); const fn into(x: Vec) -> Vec { + #[allow(self_type_conversion)] x.into() } diff --git a/library/std/src/sys/process/env.rs b/library/std/src/sys/process/env.rs index 15065c6e2c922..90d3d0df9ddfe 100644 --- a/library/std/src/sys/process/env.rs +++ b/library/std/src/sys/process/env.rs @@ -25,6 +25,7 @@ impl CommandEnv { let mut result = BTreeMap::::new(); if !self.clear { for (k, v) in env::vars_os() { + #[allow(self_type_conversion)] result.insert(k.into(), v); } } @@ -137,6 +138,7 @@ impl Iterator for CommandResolvedEnvs { type Item = (OsString, OsString); fn next(&mut self) -> Option { + #[allow(self_type_conversion)] self.inner.next().map(|(key, value)| (key.into(), value)) } diff --git a/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed b/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed index a8e9f09fa5ecf..053cc72d70ef2 100644 --- a/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed +++ b/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed @@ -6,6 +6,7 @@ clippy::useless_conversion, clippy::diverging_sub_expression, clippy::let_unit_value, + self_type_conversion, unused )] diff --git a/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs b/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs index aba7ffb273bce..bc44c68a6a51d 100644 --- a/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs +++ b/src/tools/clippy/tests/ui/needless_return_with_question_mark.rs @@ -6,6 +6,7 @@ clippy::useless_conversion, clippy::diverging_sub_expression, clippy::let_unit_value, + self_type_conversion, unused )] diff --git a/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr b/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr index d73a0e3fdc896..53141b862fe4e 100644 --- a/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr +++ b/src/tools/clippy/tests/ui/needless_return_with_question_mark.stderr @@ -1,5 +1,5 @@ error: unneeded `return` statement with `?` operator - --> tests/ui/needless_return_with_question_mark.rs:29:5 + --> tests/ui/needless_return_with_question_mark.rs:30:5 | LL | return Err(())?; | ^^^^^^^ help: remove it @@ -8,25 +8,25 @@ LL | return Err(())?; = help: to override `-D warnings` add `#[allow(clippy::needless_return_with_question_mark)]` error: unneeded `return` statement with `?` operator - --> tests/ui/needless_return_with_question_mark.rs:70:9 + --> tests/ui/needless_return_with_question_mark.rs:71:9 | LL | return Err(())?; | ^^^^^^^ help: remove it error: unneeded `return` statement with `?` operator - --> tests/ui/needless_return_with_question_mark.rs:134:9 + --> tests/ui/needless_return_with_question_mark.rs:135:9 | LL | return Err(())?; | ^^^^^^^ help: remove it error: unneeded `return` statement with `?` operator - --> tests/ui/needless_return_with_question_mark.rs:143:13 + --> tests/ui/needless_return_with_question_mark.rs:144:13 | LL | return Err(())?; | ^^^^^^^ help: remove it error: unneeded `return` statement with `?` operator - --> tests/ui/needless_return_with_question_mark.rs:163:5 + --> tests/ui/needless_return_with_question_mark.rs:164:5 | LL | return Err(())?; | ^^^^^^^ help: remove it diff --git a/src/tools/clippy/tests/ui/useless_conversion.fixed b/src/tools/clippy/tests/ui/useless_conversion.fixed index a22df7013f988..488008ce4adee 100644 --- a/src/tools/clippy/tests/ui/useless_conversion.fixed +++ b/src/tools/clippy/tests/ui/useless_conversion.fixed @@ -1,6 +1,6 @@ #![deny(clippy::useless_conversion)] #![allow(clippy::into_iter_on_ref)] -#![allow(clippy::needless_ifs, clippy::unnecessary_wraps, unused)] +#![allow(clippy::needless_ifs, clippy::unnecessary_wraps, unused, self_type_conversion)] // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] diff --git a/src/tools/clippy/tests/ui/useless_conversion.rs b/src/tools/clippy/tests/ui/useless_conversion.rs index 1f170cf87ac58..d1183cd068632 100644 --- a/src/tools/clippy/tests/ui/useless_conversion.rs +++ b/src/tools/clippy/tests/ui/useless_conversion.rs @@ -1,6 +1,6 @@ #![deny(clippy::useless_conversion)] #![allow(clippy::into_iter_on_ref)] -#![allow(clippy::needless_ifs, clippy::unnecessary_wraps, unused)] +#![allow(clippy::needless_ifs, clippy::unnecessary_wraps, unused, self_type_conversion)] // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] diff --git a/tests/ui/lint/self_type_conversion.rs b/tests/ui/lint/self_type_conversion.rs new file mode 100644 index 0000000000000..46f7307a97e66 --- /dev/null +++ b/tests/ui/lint/self_type_conversion.rs @@ -0,0 +1,81 @@ +#![deny(self_type_conversion)] +mod a { + mod foo { + cfg_select! { + any(target_arch = "avr", target_arch = "msp430") => { + pub type Int = i16; + } + _ => { + pub type Int = i32; + } + } + #[cfg(false)] + pub type Float = f32; + #[cfg(true)] + pub type Float = f64; + + pub type Trigger = u64; + } + + use self::foo::{Int, Float, Trigger}; + + pub fn bar() { + let x: Int = 1; + // Ok, should not lint because the type alias is behind a `cfg_select!` + let _: i32 = x.into(); + let y: Float = 1.; + // Ok, should not lint because the type alias is behind a `cfg` attr + let _: f64 = y.into(); + let z: Trigger = 1; + let _: u64 = z.into(); + //~^ ERROR useless conversion to the same type: `u64` + } +} + +mod b { + #[cfg(any(target_arch = "avr", target_arch = "msp430"))] + mod foo { + pub type Int = i16; + pub type Float = f32; + } + #[cfg(not(any(target_arch = "avr", target_arch = "msp430")))] + mod bar { + pub type Int = i32; + pub type Float = f64; + } + + mod qux { + pub type Trigger = u64; + } + + cfg_select! { + any(target_arch = "avr", target_arch = "msp430") => { + use self::foo::Int; + } + _ => { + use self::bar::Int; + } + } + + #[cfg(any(target_arch = "avr", target_arch = "msp430"))] + use self::foo::Float; + #[cfg(not(any(target_arch = "avr", target_arch = "msp430")))] + use self::bar::Float; + + use self::qux::Trigger; + + pub fn baz() { + let x: Int = 1; + let _: i32 = x.into(); // Ok, should not lint because the import is behind a `cfg_select!` + let y: Float = 1.; + let _: f64 = y.into(); // Ok, should not lint because the import is behind a `cfg` attr + let z: Trigger = 1; + let _: u64 = z.into(); + //~^ ERROR useless conversion to the same type: `u64` + } +} + +fn main() { + a::bar(); + b::baz(); +} diff --git a/tests/ui/lint/self_type_conversion.stderr b/tests/ui/lint/self_type_conversion.stderr new file mode 100644 index 0000000000000..8762a0bf22cba --- /dev/null +++ b/tests/ui/lint/self_type_conversion.stderr @@ -0,0 +1,25 @@ +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:30:22 + | +LL | let _: u64 = z.into(); + | ^^^^^^^^ + | + = note: this method call relies on the `impl Into for T` blanket implementation and type inference, which is a semver hazard as a new `impl Into<_>` that affects your type might be added in the future causing type inference errors + = note: you can instead use the fully-qualified path `::into(val) to avoid triggering this lint +note: the lint level is defined here + --> $DIR/self_type_conversion.rs:1:9 + | +LL | #![deny(self_type_conversion)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:73:22 + | +LL | let _: u64 = z.into(); + | ^^^^^^^^ + | + = note: this method call relies on the `impl Into for T` blanket implementation and type inference, which is a semver hazard as a new `impl Into<_>` that affects your type might be added in the future causing type inference errors + = note: you can instead use the fully-qualified path `::into(val) to avoid triggering this lint + +error: aborting due to 2 previous errors + diff --git a/tests/ui/macros/macro-or-patterns-back-compat.fixed b/tests/ui/macros/macro-or-patterns-back-compat.fixed index 0321a36ae3b07..abb07549fd1a9 100644 --- a/tests/ui/macros/macro-or-patterns-back-compat.fixed +++ b/tests/ui/macros/macro-or-patterns-back-compat.fixed @@ -4,7 +4,7 @@ //@ reference: macro.decl.follow-set.edition2021 #![deny(rust_2021_incompatible_or_patterns)] -#![allow(unused_macros)] +#![allow(unused_macros, self_type_conversion)] #[macro_use] extern crate or_pattern; diff --git a/tests/ui/macros/macro-or-patterns-back-compat.rs b/tests/ui/macros/macro-or-patterns-back-compat.rs index 5d96ad456dc94..381b34eceb40c 100644 --- a/tests/ui/macros/macro-or-patterns-back-compat.rs +++ b/tests/ui/macros/macro-or-patterns-back-compat.rs @@ -4,7 +4,7 @@ //@ reference: macro.decl.follow-set.edition2021 #![deny(rust_2021_incompatible_or_patterns)] -#![allow(unused_macros)] +#![allow(unused_macros, self_type_conversion)] #[macro_use] extern crate or_pattern; diff --git a/tests/ui/macros/macro-pat-pattern-followed-by-or.rs b/tests/ui/macros/macro-pat-pattern-followed-by-or.rs index f5a56a7e8edda..953f8f7360955 100644 --- a/tests/ui/macros/macro-pat-pattern-followed-by-or.rs +++ b/tests/ui/macros/macro-pat-pattern-followed-by-or.rs @@ -2,7 +2,7 @@ //@ run-pass //@ reference: macro.decl.follow-set.token-pat //@ reference: macro.decl.follow-set.edition2021 -#![allow(unused_macros)] +#![allow(unused_macros, self_type_conversion)] macro_rules! foo { ($x:pat | $y:pat) => {} } // should be ok macro_rules! bar { ($($x:pat)+ | $($y:pat)+) => {} } // should be ok macro_rules! qux { ($x:pat, $y:pat) => {} } // should be ok diff --git a/tests/ui/traits/next-solver/closure-signature-inference-hr-ambig-alias-naming-self.rs b/tests/ui/traits/next-solver/closure-signature-inference-hr-ambig-alias-naming-self.rs index 25649d9290326..f04e2565f3ca2 100644 --- a/tests/ui/traits/next-solver/closure-signature-inference-hr-ambig-alias-naming-self.rs +++ b/tests/ui/traits/next-solver/closure-signature-inference-hr-ambig-alias-naming-self.rs @@ -1,6 +1,7 @@ //@ check-pass //@ revisions: current next //@[next] compile-flags: -Znext-solver +#![allow(self_type_conversion)] // When type checking a closure expr we look at the list of unsolved goals // to determine if there are any bounds on the closure type to infer a signature from. diff --git a/tests/ui/traits/next-solver/global-param-env-after-norm.rs b/tests/ui/traits/next-solver/global-param-env-after-norm.rs index 0d098db67d36d..f99a2004fd0fd 100644 --- a/tests/ui/traits/next-solver/global-param-env-after-norm.rs +++ b/tests/ui/traits/next-solver/global-param-env-after-norm.rs @@ -1,5 +1,6 @@ //@ check-pass //@ compile-flags: -Znext-solver +#![allow(self_type_conversion)] struct NewSolver; struct OldSolver; diff --git a/tests/ui/traits/next-solver/method/param-method-from-unnormalized-param-env.rs b/tests/ui/traits/next-solver/method/param-method-from-unnormalized-param-env.rs index dde4f745879e4..624303bde52a6 100644 --- a/tests/ui/traits/next-solver/method/param-method-from-unnormalized-param-env.rs +++ b/tests/ui/traits/next-solver/method/param-method-from-unnormalized-param-env.rs @@ -1,5 +1,6 @@ //@ check-pass //@ compile-flags: -Znext-solver +#![allow(self_type_conversion)] // Regression test for .