From 8698f7b359259dc19dc2fd8c579a32e6f908280e 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 01/17] 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 934dfb54f6deb..9c2215dfeae54 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -47,25 +47,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 526ddd6a3811d..4322c6261b817 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::diagnostics::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 c6bbeda36e930..bfec9808826f1 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1219,7 +1219,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; @@ -1271,13 +1271,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 { @@ -1409,6 +1411,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) { @@ -1429,13 +1437,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; @@ -1480,6 +1485,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; @@ -1524,6 +1530,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; @@ -1567,6 +1574,7 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitImplIt } } +impl DeclaredIdents for Box {} impl InvocationCollectorNode for Box { const KIND: AstFragmentKind = AstFragmentKind::ForeignItems; fn to_annotatable(self) -> Annotatable { @@ -1597,6 +1605,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 { @@ -1613,6 +1622,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 { @@ -1629,6 +1639,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 { @@ -1645,6 +1656,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 { @@ -1661,6 +1673,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 { @@ -1677,6 +1690,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 { @@ -1693,6 +1707,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 { @@ -1725,6 +1740,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 { @@ -1741,6 +1757,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 { @@ -1817,6 +1834,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; @@ -1846,6 +1864,7 @@ impl InvocationCollectorNode for ast::Crate { } } +impl DeclaredIdents for ast::Ty {} impl InvocationCollectorNode for ast::Ty { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Ty; @@ -1883,6 +1902,7 @@ impl InvocationCollectorNode for ast::Ty { } } +impl DeclaredIdents for ast::Pat {} impl InvocationCollectorNode for ast::Pat { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Pat; @@ -1909,6 +1929,7 @@ impl InvocationCollectorNode for ast::Pat { } } +impl DeclaredIdents for ast::Expr {} impl InvocationCollectorNode for ast::Expr { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Expr; @@ -1939,6 +1960,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; @@ -1974,6 +1996,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 0f5ad1d12adc5..8c2e5bae07488 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; | ^^^^^^^^^^^^^^------------ @@ -16,7 +16,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` @@ -30,8 +30,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` @@ -45,8 +81,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` @@ -60,13 +114,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 @@ -74,26 +128,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 375e6a5753e4d318a762081a8eca351029d600e2 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 02/17] 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 4322c6261b817..6cd5732976add 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::diagnostics::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 b03b560519bf521dca5f15be493b4a577fb58b5e 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 03/17] 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 6cd5732976add..a13fdc4f50f7f 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 72ad5f6ed910e4a9e401cbf4ec95dead74433bb9 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 04/17] 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 a13fdc4f50f7f..b31e6ef03a634 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 bfec9808826f1..1106fce27aa3a 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1282,6 +1282,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 { @@ -1440,7 +1468,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; @@ -1485,7 +1512,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; @@ -1530,7 +1556,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; @@ -1574,7 +1599,6 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitImplIt } } -impl DeclaredIdents for Box {} impl InvocationCollectorNode for Box { const KIND: AstFragmentKind = AstFragmentKind::ForeignItems; fn to_annotatable(self) -> Annotatable { @@ -1605,7 +1629,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 { @@ -1622,7 +1645,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 { @@ -1639,7 +1661,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 { @@ -1656,7 +1677,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 { @@ -1673,7 +1693,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 { @@ -1690,7 +1709,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 { @@ -1707,7 +1725,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 { @@ -1740,7 +1757,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 { @@ -1757,7 +1773,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 { @@ -1834,7 +1849,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; @@ -1864,7 +1878,6 @@ impl InvocationCollectorNode for ast::Crate { } } -impl DeclaredIdents for ast::Ty {} impl InvocationCollectorNode for ast::Ty { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Ty; @@ -1902,7 +1915,6 @@ impl InvocationCollectorNode for ast::Ty { } } -impl DeclaredIdents for ast::Pat {} impl InvocationCollectorNode for ast::Pat { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Pat; @@ -1929,7 +1941,6 @@ impl InvocationCollectorNode for ast::Pat { } } -impl DeclaredIdents for ast::Expr {} impl InvocationCollectorNode for ast::Expr { type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Expr; @@ -1960,7 +1971,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 af69bb4273e63b83208a8e2396454d07681e1097 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 05/17] 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 b31e6ef03a634..072ee3c07ac81 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 3db1de085c42d09a0c63d80467b18afd7e1318b2 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 06/17] `>::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. --- .../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 + .../src/error_reporting/infer/mod.rs | 16 +- .../src/normalize_projection_ty.rs | 2 +- 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/cfg/diagnostics-same-crate.stderr | 4 +- 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 + 24 files changed, 371 insertions(+), 20 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_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 383023aa24457..d1f9c571ebe8d 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}; @@ -1545,6 +1547,8 @@ pub mod soft { vec![ WHILE_TRUE, NON_SHORTHAND_FIELD_PATTERNS, + SELF_TYPE_CONVERSION, + SELF_TYPE_CONVERSION_IN_MACRO, UNSAFE_CODE, MISSING_DOCS, MISSING_COPY_IMPLEMENTATIONS, @@ -3192,3 +3196,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 eaf9360cc3358..a0fff74845d72 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -322,6 +322,9 @@ fn register_builtins(store: &mut LintStore) { store.register_lints(&foreign_modules::lint_vec()); store.register_lints(&hardwired::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 eb82afab13186..4a191374e6f82 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 71eae246ebaff..761d1fad06034 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1134,6 +1134,7 @@ symbols! { internal_features, interrupt, into_async_iter_into_iter, + into_fn, into_future, into_iter, into_try_type, diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index 9822e8cdef8b2..623f0d54402aa 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -1473,14 +1473,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ValuePairs::TraitRefs(_) => (false, Mismatch::Fixed("trait")), ValuePairs::Aliases(ExpectedFound { expected, .. }) => { let def_id = match expected.kind { - ty::AliasTermKind::ProjectionTy { def_id } => def_id.into(), - ty::AliasTermKind::InherentTy { def_id } => def_id.into(), - ty::AliasTermKind::OpaqueTy { def_id } => def_id.into(), - ty::AliasTermKind::FreeTy { def_id } => def_id.into(), - ty::AliasTermKind::AnonConst { def_id } => def_id.into(), - ty::AliasTermKind::ProjectionConst { def_id } => def_id.into(), - ty::AliasTermKind::FreeConst { def_id } => def_id.into(), - ty::AliasTermKind::InherentConst { def_id } => def_id.into(), + ty::AliasTermKind::ProjectionTy { def_id } + | ty::AliasTermKind::InherentTy { def_id } + | ty::AliasTermKind::OpaqueTy { def_id } + | ty::AliasTermKind::FreeTy { def_id } + | ty::AliasTermKind::AnonConst { def_id } + | ty::AliasTermKind::ProjectionConst { def_id } + | ty::AliasTermKind::FreeConst { def_id } + | ty::AliasTermKind::InherentConst { def_id } => def_id, }; (false, Mismatch::Fixed(self.tcx.def_descr(def_id))) } diff --git a/compiler/rustc_traits/src/normalize_projection_ty.rs b/compiler/rustc_traits/src/normalize_projection_ty.rs index 2b904c2710464..a763bed64fb2f 100644 --- a/compiler/rustc_traits/src/normalize_projection_ty.rs +++ b/compiler/rustc_traits/src/normalize_projection_ty.rs @@ -143,7 +143,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 ae8458c199503..ef4fd69fa7646 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -451,6 +451,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/cfg/diagnostics-same-crate.stderr b/tests/ui/cfg/diagnostics-same-crate.stderr index 8c2e5bae07488..9775d62ffa108 100644 --- a/tests/ui/cfg/diagnostics-same-crate.stderr +++ b/tests/ui/cfg/diagnostics-same-crate.stderr @@ -34,7 +34,9 @@ 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` + | ^^^^^^^^^^^^^^------------ + | | + | no `selected_out` in `inner` | note: found an item that was configured out --> $DIR/diagnostics-same-crate.rs:23:21 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 . From e0e33f71339f1d84c2cdea112302ed657f38de33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 22 Jun 2026 22:31:56 +0000 Subject: [PATCH 07/17] Ignore useless `.into()` calls on fields or structs that are behind a `cfg` attr --- compiler/rustc_lint/src/builtin.rs | 58 +++++++++++++++++++++++++++ tests/ui/lint/self_type_conversion.rs | 21 ++++++++++ 2 files changed, 79 insertions(+) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index d1f9c571ebe8d..585d07f43ba36 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -3375,6 +3375,44 @@ impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { return; } + if let hir::ExprKind::Field(base, field) = rcvr.kind { + // Look at the original type. If the field being accessed or its type is behind a `cfg` + // attribute, we don't trigger the lint, as it's likely that field has different types + // in different configurations. + // + // #[cfg(true)] + // struct S { + // #[cfg(..)] field: u32, + // #[cfg(not(..))] field: u64, + // } + // + // let s = S { field: 0 }; + // let x: u32 = s.field.into(); + let ty = cx.typeck_results().expr_ty(base); + if let ty::Adt(def, _args) = ty.peel_refs().kind() + && struct_field_has_cfg(cx.tcx, def.did(), field.name) + { + return; + } + } + if let hir::Node::ExprField(field) = cx.tcx.parent_hir_node(expr.hir_id) + && let hir::Node::Expr(parent) = cx.tcx.parent_hir_node(field.hir_id) + && let hir::ExprKind::Struct(hir::QPath::Resolved(_, path), _, _) = parent.kind + && let Res::Def(DefKind::Struct, def_id) = path.res + && struct_field_has_cfg(cx.tcx, def_id, field.ident.name) + { + // The target of the value being converted corresponds to a struct that is behind a + // `cfg`: + // + // #[cfg(true)] + // struct S { + // #[cfg(..)] field: u32, + // #[cfg(not(..))] field: u64, + // } + // + // S { field: 0u32.into() }; + 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( @@ -3391,3 +3429,23 @@ impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { cx.emit_span_lint(SELF_TYPE_CONVERSION, expr.span, SelfTypeConversionDiag { ty }); } } + +fn struct_field_has_cfg(tcx: TyCtxt<'_>, def_id: DefId, field_name: Symbol) -> bool { + if find_attr!(tcx, def_id, CfgTrace(..)) { + // The item is `cfg`d. + return true; + } + if find_attr!(tcx, tcx.parent(def_id), CfgTrace(..)) { + // The `mod` enclosing the item is `cfg`d. + return true; + } + let def = tcx.adt_def(def_id); + for variant in def.variants().iter() { + for f in &variant.fields { + if f.name == field_name && find_attr!(tcx, f.did, CfgTrace(..)) { + return true; + } + } + } + false +} diff --git a/tests/ui/lint/self_type_conversion.rs b/tests/ui/lint/self_type_conversion.rs index 46f7307a97e66..2b1c1246c1dc7 100644 --- a/tests/ui/lint/self_type_conversion.rs +++ b/tests/ui/lint/self_type_conversion.rs @@ -75,7 +75,28 @@ mod b { } } +struct C { + #[cfg(true)] + foo: (), +} +#[cfg(true)] +struct D { + foo: (), +} +#[cfg(true)] +mod e { + pub(crate) struct F { + pub(crate) foo: (), + } +} + fn main() { a::bar(); b::baz(); + let c = C { foo: ().into() }; // Ok, field `C.foo` is behind a `cfg` attr + let () = c.foo.into(); // Ok, field `C.foo` is behind a `cfg` attr + let d = D { foo: ().into() }; // Ok, `D` is behind a `cfg` attr + let () = d.foo.into(); // Ok, `D` is behind a `cfg` attr + let f = e::F { foo: ().into() }; // Ok, `e` is behind a `cfg` attr + let () = f.foo.into(); // Ok, `e` is behind a `cfg` attr } From 66689fbcbb8755b1558bf8b3a17b4b65a7da0f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 22 Jun 2026 22:58:52 +0000 Subject: [PATCH 08/17] fix compile error --- compiler/rustc_lint/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index a0fff74845d72..877e29fe84ce9 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -323,7 +323,9 @@ fn register_builtins(store: &mut LintStore) { store.register_lints(&hardwired::lint_vec()); store.register_lints(&SelfTypeConversion::lint_vec()); - store.register_late_pass(|_| Box::new(SelfTypeConversion::new())); + store.register_late_pass(Box::new(|_| { + Box::new(SelfTypeConversion::new()) as Box> + })); add_lint_group!( "nonstandard_style", From 27568b05153836dfbb72a633772c4322173d0cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 22 Jun 2026 23:30:39 +0000 Subject: [PATCH 09/17] Ignore useless `.into()` calls on `const`s that are behind a cfg attr This side-steps a lot of `libc` cases. --- compiler/rustc_lint/src/builtin.rs | 9 +++++++++ tests/ui/lint/self_type_conversion.rs | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 585d07f43ba36..8a47e8da17ed9 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -3413,6 +3413,15 @@ impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { // S { field: 0u32.into() }; return; } + if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = rcvr.kind + && let Res::Def(DefKind::Const { is_type_const: _ }, def_id) = path.res + && (find_attr!(cx.tcx, def_id, CfgTrace(..)) + || find_attr!(cx.tcx, cx.tcx.parent(def_id), CfgTrace(..))) + { + // We're accessing and converting a const that is either itself annotated with `cfg` or + // its parent `mod`. Common for things like `libc::TIOCGWINSZ`. + 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( diff --git a/tests/ui/lint/self_type_conversion.rs b/tests/ui/lint/self_type_conversion.rs index 2b1c1246c1dc7..5a293643af140 100644 --- a/tests/ui/lint/self_type_conversion.rs +++ b/tests/ui/lint/self_type_conversion.rs @@ -88,7 +88,11 @@ mod e { pub(crate) struct F { pub(crate) foo: (), } + pub(crate) const G: () = (); } +#[cfg(true)] +const H: () = (); + fn main() { a::bar(); @@ -99,4 +103,6 @@ fn main() { let () = d.foo.into(); // Ok, `D` is behind a `cfg` attr let f = e::F { foo: ().into() }; // Ok, `e` is behind a `cfg` attr let () = f.foo.into(); // Ok, `e` is behind a `cfg` attr + let () = e::G.into(); // Ok, `e` is behind a `cfg` attr + let () = H.into(); // Ok, `H` is behind a `cfg` attr } From 6f1ca0fa93fcf20d4d20bee90f3b50951def75f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 23 Jun 2026 02:20:56 +0000 Subject: [PATCH 10/17] Remove useless `.into()` --- compiler/rustc_metadata/src/host_dylib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_metadata/src/host_dylib.rs b/compiler/rustc_metadata/src/host_dylib.rs index 9bd2a57fcd285..4525d36b4525a 100644 --- a/compiler/rustc_metadata/src/host_dylib.rs +++ b/compiler/rustc_metadata/src/host_dylib.rs @@ -140,7 +140,7 @@ pub(crate) fn dlsym_proc_macros( } Err(err) => { debug!("failed to dlsym proc_macros {} for symbol `{}`", path.display(), sym_name); - Err(err.into()) + Err(err) } } } From 92d8ec32f079f50c60d693e96fe95d124a80f65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 3 Jul 2026 21:54:58 +0000 Subject: [PATCH 11/17] Change way lint gets registered This makes the visitor actually visit closure bodies, as expected. --- compiler/rustc_lint/src/lib.rs | 8 ++------ compiler/rustc_lint/src/passes.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 877e29fe84ce9..51e98f05ba9dc 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -208,7 +208,7 @@ early_lint_methods!( late_lint_methods!( declare_combined_late_lint_pass, [ - BuiltinCombinedLateLintModPass, + BuiltinCombinedLateLintModPass<'tcx>, [ ForLoopsOverFallibles: ForLoopsOverFallibles, DefaultCouldBeDerived: DefaultCouldBeDerived, @@ -273,6 +273,7 @@ late_lint_methods!( InternalEqTraitMethodImpls: InternalEqTraitMethodImpls, ImplicitProvenanceCasts: ImplicitProvenanceCasts, CVoidReturns: CVoidReturns, + SelfTypeConversion<'tcx>: SelfTypeConversion { ignored_types: Default::default() }, ] ] ); @@ -322,11 +323,6 @@ fn register_builtins(store: &mut LintStore) { store.register_lints(&foreign_modules::lint_vec()); store.register_lints(&hardwired::lint_vec()); - store.register_lints(&SelfTypeConversion::lint_vec()); - store.register_late_pass(Box::new(|_| { - Box::new(SelfTypeConversion::new()) as Box> - })); - add_lint_group!( "nonstandard_style", NON_CAMEL_CASE_TYPES, diff --git a/compiler/rustc_lint/src/passes.rs b/compiler/rustc_lint/src/passes.rs index 12cf58907566d..c91045993133b 100644 --- a/compiler/rustc_lint/src/passes.rs +++ b/compiler/rustc_lint/src/passes.rs @@ -92,13 +92,13 @@ macro_rules! expand_combined_late_lint_pass_methods { /// runtime. #[macro_export] macro_rules! declare_combined_late_lint_pass { - ([$v:vis $name:ident, [$($pass:ident: $constructor:expr,)*]], $methods:tt) => ( + ([$v:vis $name:ident$(<$lt:lifetime>)?, [$($pass:ident$(<$pl:lifetime>)?: $constructor:expr,)*]], $methods:tt) => ( #[allow(non_snake_case)] - $v struct $name { - $($pass: $pass,)* + $v struct $name$(<$lt>)* { + $($pass: $pass $(<$pl>)*,)* } - impl $name { + impl $(<$lt>)* $name $(<$lt>)* { $v fn new() -> Self { Self { $($pass: $constructor,)* @@ -112,12 +112,12 @@ macro_rules! declare_combined_late_lint_pass { } } - impl<'tcx> $crate::LateLintPass<'tcx> for $name { + impl<'tcx> $crate::LateLintPass<'tcx> for $name$(<$lt>)* { $crate::expand_combined_late_lint_pass_methods!([$($pass),*], $methods); } #[allow(rustc::lint_pass_impl_without_macro)] - impl $crate::LintPass for $name { + impl$(<$lt>)* $crate::LintPass for $name$(<$lt>)* { fn name(&self) -> &'static str { stringify!($name) } From b0669b0cfaf86c0c1f8636af0b20f21f8d2d00cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 3 Jul 2026 21:56:13 +0000 Subject: [PATCH 12/17] Account for the `time` regression case and extend the diagnostic contents Account for `foo.map(Into::into)`, and modify the output to include structured suggestions. --- compiler/rustc_lint/src/builtin.rs | 77 ++++++++++++++++------ compiler/rustc_lint/src/lints.rs | 26 ++++++-- compiler/rustc_span/src/symbol.rs | 2 + library/core/src/option.rs | 1 + library/core/src/result.rs | 1 + tests/ui/lint/self_type_conversion.rs | 40 ++++++++++++ tests/ui/lint/self_type_conversion.stderr | 78 +++++++++++++++++++++-- 7 files changed, 199 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 8a47e8da17ed9..c44c6b397c0c3 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -59,8 +59,9 @@ use crate::lints::{ BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, - BuiltinUnusedDocCommentSub, BuiltinWhileTrue, EqInternalMethodImplemented, InvalidAsmLabel, - SelfTypeConversionDiag, SelfTypeConversionInMacroDiag, + BuiltinUnusedDocCommentSub, BuiltinWhileTrue, EqInternalMethodImplemented, + FullyQualifiedPathSuggestion, InvalidAsmLabel, SelfTypeConversionDiag, + SelfTypeConversionInMacroDiag, }; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; @@ -3288,7 +3289,7 @@ declare_lint! { } pub struct SelfTypeConversion<'tcx> { - ignored_types: FxHashSet>, + pub ignored_types: FxHashSet>, } impl_lint_pass!(SelfTypeConversion<'_> => [SELF_TYPE_CONVERSION, SELF_TYPE_CONVERSION_IN_MACRO]); @@ -3344,22 +3345,58 @@ impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { /// /// 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() { + let mut fully_qualified_path = None; + let (rcvr, ty, rcvr_ty, removal_span) = match expr.kind { // 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); + hir::ExprKind::MethodCall(_segment, rcvr, args, _) => { + let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else { + return; + }; + if Some(def_id) == cx.tcx.get_diagnostic_item(sym::into_fn) { + // We've got `foo.into()`. + let ty = cx.typeck_results().expr_ty(expr); + let rcvr_ty = cx.typeck_results().expr_ty(rcvr); + let removal_span = expr.span.with_lo(rcvr.span.hi()); + fully_qualified_path = Some(FullyQualifiedPathSuggestion { + ty, + pre: rcvr.span.shrink_to_lo(), + post: removal_span, + }); + (rcvr, ty, rcvr_ty, removal_span) + } else if (Some(def_id) == cx.tcx.get_diagnostic_item(sym::option_map) + || Some(def_id) == cx.tcx.get_diagnostic_item(sym::result_map)) + && let [arg] = args + && let hir::ExprKind::Path(qpath) = arg.kind + && let Res::Def(DefKind::AssocFn, def_id) = + cx.typeck_results().qpath_res(&qpath, arg.hir_id) + && Some(def_id) == cx.tcx.get_diagnostic_item(sym::into_fn) + { + // We've got a situation like `foo.map(Into::into)` where `foo` + // is an `Option` or `Result`. + let ty = cx.typeck_results().expr_ty(expr); + let rcvr_ty = cx.typeck_results().expr_ty(rcvr); + match (ty.kind(), rcvr_ty.kind()) { + // We care about the `T` in `Option` and `Result`. + (ty::Adt(_, args), ty::Adt(_, rcvr_args)) => { + tracing::info!(?args, ?rcvr_args); + ( + rcvr, + args.type_at(0), + rcvr_args.type_at(0), + expr.span.with_lo(rcvr.span.hi()), + ) + } + _ => return, + } + } else { + // We don't have `foo.into()` corresponding to `Into::into` or + // `foo.map(Into::into)`. + return; + } + } + _ => return, + }; if ty != rcvr_ty { // If the type we are converting from and converting towards are different, there's @@ -3435,7 +3472,11 @@ impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { return; } - cx.emit_span_lint(SELF_TYPE_CONVERSION, expr.span, SelfTypeConversionDiag { ty }); + cx.emit_span_lint( + SELF_TYPE_CONVERSION, + expr.span, + SelfTypeConversionDiag { ty, removal_span, fully_qualified_path }, + ); } } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 4a191374e6f82..613e8e90faceb 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -72,12 +72,30 @@ pub(crate) enum ShadowedIntoIterDiagSub { 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>, + #[suggestion( + "consider removing the conversion call", + style = "verbose", + code = "", + applicability = "machine-applicable" + )] + pub removal_span: Span, + #[subdiagnostic] + pub fully_qualified_path: Option>, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + "you can use the fully-qualified path to avoid triggering this lint", + applicability = "maybe-incorrect" +)] +pub(crate) struct FullyQualifiedPathSuggestion<'t> { + pub ty: Ty<'t>, + #[suggestion_part(code = "<{ty} as Into<_>>::into(")] + pub pre: Span, + #[suggestion_part(code = ")")] + pub post: Span, } #[derive(Diagnostic)] diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 761d1fad06034..c9ae506c8ac04 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1484,6 +1484,7 @@ symbols! { optin_builtin_traits, option, option_env, + option_map, options, or, or_patterns, @@ -1707,6 +1708,7 @@ symbols! { residual, result, result_ffi_guarantees, + result_map, return_address, return_position_impl_trait_in_trait, return_type_notation, diff --git a/library/core/src/option.rs b/library/core/src/option.rs index 163e8bf714b1d..fafaf0b8dfe56 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -1155,6 +1155,7 @@ impl Option { #[inline] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] + #[rustc_diagnostic_item = "option_map"] pub const fn map(self, f: F) -> Option where F: [const] FnOnce(T) -> U + [const] Destruct, diff --git a/library/core/src/result.rs b/library/core/src/result.rs index a1915fb2a792c..18cb59a775a35 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -828,6 +828,7 @@ impl Result { #[inline] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + #[rustc_diagnostic_item = "result_map"] pub const fn map(self, op: F) -> Result where F: [const] FnOnce(T) -> U + [const] Destruct, diff --git a/tests/ui/lint/self_type_conversion.rs b/tests/ui/lint/self_type_conversion.rs index 5a293643af140..4a3925fe661a9 100644 --- a/tests/ui/lint/self_type_conversion.rs +++ b/tests/ui/lint/self_type_conversion.rs @@ -29,6 +29,26 @@ mod a { let z: Trigger = 1; let _: u64 = z.into(); //~^ ERROR useless conversion to the same type: `u64` + + let x: Option = Some(1); + // Ok, should not lint because the type alias is behind a `cfg_select!` + let _: Option = x.map(Into::into); + let y: Option = Some(1.); + // Ok, should not lint because the type alias is behind a `cfg` attr + let _: Option = y.map(Into::into); + let z: Option = Some(1); + let _: Option = z.map(Into::into); + //~^ ERROR useless conversion to the same type: `u64` + + let x: Result = Ok(1); + // Ok, should not lint because the type alias is behind a `cfg_select!` + let _: Result = x.map(Into::into); + let y: Result = Ok(1.); + // Ok, should not lint because the type alias is behind a `cfg` attr + let _: Result = y.map(Into::into); + let z: Result = Ok(1); + let _: Result = z.map(Into::into); + //~^ ERROR useless conversion to the same type: `u64` } } @@ -72,6 +92,26 @@ mod b { let z: Trigger = 1; let _: u64 = z.into(); //~^ ERROR useless conversion to the same type: `u64` + + let x: Option = Some(1); + // Ok, should not lint because the type alias is behind a `cfg_select!` + let _: Option = x.map(Into::into); + let y: Option = Some(1.); + // Ok, should not lint because the type alias is behind a `cfg` attr + let _: Option = y.map(Into::into); + let z: Option = Some(1); + let _: Option = z.map(Into::into); + //~^ ERROR useless conversion to the same type: `u64` + + let x: Result = Ok(1); + // Ok, should not lint because the type alias is behind a `cfg_select!` + let _: Result = x.map(Into::into); + let y: Result = Ok(1.); + // Ok, should not lint because the type alias is behind a `cfg` attr + let _: Result = y.map(Into::into); + let z: Result = Ok(1); + let _: Result = z.map(Into::into); + //~^ ERROR useless conversion to the same type: `u64` } } diff --git a/tests/ui/lint/self_type_conversion.stderr b/tests/ui/lint/self_type_conversion.stderr index 8762a0bf22cba..5ad8d780efb0b 100644 --- a/tests/ui/lint/self_type_conversion.stderr +++ b/tests/ui/lint/self_type_conversion.stderr @@ -5,21 +5,91 @@ 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)] | ^^^^^^^^^^^^^^^^^^^^ +help: consider removing the conversion call + | +LL - let _: u64 = z.into(); +LL + let _: u64 = z; + | +help: you can use the fully-qualified path to avoid triggering this lint + | +LL - let _: u64 = z.into(); +LL + let _: u64 = >::into(z); + | + +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:40:30 + | +LL | let _: Option = z.map(Into::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 +help: consider removing the conversion call + | +LL - let _: Option = z.map(Into::into); +LL + let _: Option = z; + | + +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:50:34 + | +LL | let _: Result = z.map(Into::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 +help: consider removing the conversion call + | +LL - let _: Result = z.map(Into::into); +LL + let _: Result = z; + | error: useless conversion to the same type: `u64` - --> $DIR/self_type_conversion.rs:73:22 + --> $DIR/self_type_conversion.rs:93: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 +help: consider removing the conversion call + | +LL - let _: u64 = z.into(); +LL + let _: u64 = z; + | +help: you can use the fully-qualified path to avoid triggering this lint + | +LL - let _: u64 = z.into(); +LL + let _: u64 = >::into(z); + | + +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:103:30 + | +LL | let _: Option = z.map(Into::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 +help: consider removing the conversion call + | +LL - let _: Option = z.map(Into::into); +LL + let _: Option = z; + | + +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:113:34 + | +LL | let _: Result = z.map(Into::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 +help: consider removing the conversion call + | +LL - let _: Result = z.map(Into::into); +LL + let _: Result = z; + | -error: aborting due to 2 previous errors +error: aborting due to 6 previous errors From 925b97111f3fea666d653a8ff10fb63641f6c0ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sat, 4 Jul 2026 01:13:31 +0000 Subject: [PATCH 13/17] remove useless into call --- src/librustdoc/clean/utils.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index f77e201805a2b..8b461d61613c0 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -352,10 +352,10 @@ pub(crate) fn print_const(tcx: TyCtxt<'_>, n: ty::Const<'_>) -> String { match n.kind() { ty::ConstKind::Alias(_, ty::AliasConst { kind, .. }) => { let def_id: DefId = match kind { - ty::AliasConstKind::Projection { def_id } => def_id.into(), - ty::AliasConstKind::Inherent { def_id } => def_id.into(), - ty::AliasConstKind::Free { def_id } => def_id.into(), - ty::AliasConstKind::Anon { def_id } => def_id.into(), + ty::AliasConstKind::Projection { def_id } + | ty::AliasConstKind::Inherent { def_id } + | ty::AliasConstKind::Free { def_id } + | ty::AliasConstKind::Anon { def_id } => def_id, }; if let Some(local_def_id) = def_id.as_local() && let Some(body_id) = tcx.hir_maybe_body_owned_by(local_def_id) From 602e978009f8b929d450b49fe45f93ccf4699bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 6 Jul 2026 04:04:51 +0000 Subject: [PATCH 14/17] Account for macros in suggestions Do not emit a suggestion to remove the `.into()` call when the Span context of the receiver is different to the Span context of the expression to avoid malformed suggestions. The lint still triggers regardless, just with less information. --- compiler/rustc_lint/src/builtin.rs | 32 ++++++++++++++++-------------- compiler/rustc_lint/src/lints.rs | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index c44c6b397c0c3..3d47e5d57622b 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -3346,7 +3346,8 @@ impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { /// 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 mut fully_qualified_path = None; - let (rcvr, ty, rcvr_ty, removal_span) = match expr.kind { + let mut removal_span = None; + let (rcvr, ty, rcvr_ty) = match expr.kind { // If we have `foo.method(...)` with arguments, we already know that `method` can't be // `into`, so we bail. hir::ExprKind::MethodCall(_segment, rcvr, args, _) => { @@ -3357,13 +3358,17 @@ impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { // We've got `foo.into()`. let ty = cx.typeck_results().expr_ty(expr); let rcvr_ty = cx.typeck_results().expr_ty(rcvr); - let removal_span = expr.span.with_lo(rcvr.span.hi()); - fully_qualified_path = Some(FullyQualifiedPathSuggestion { - ty, - pre: rcvr.span.shrink_to_lo(), - post: removal_span, - }); - (rcvr, ty, rcvr_ty, removal_span) + // check that the span context is the same for both sides. + if expr.span.eq_ctxt(rcvr.span) { + let post = expr.span.with_lo(rcvr.span.hi()); + removal_span = Some(post); + fully_qualified_path = Some(FullyQualifiedPathSuggestion { + ty, + pre: rcvr.span.shrink_to_lo(), + post, + }); + } + (rcvr, ty, rcvr_ty) } else if (Some(def_id) == cx.tcx.get_diagnostic_item(sym::option_map) || Some(def_id) == cx.tcx.get_diagnostic_item(sym::result_map)) && let [arg] = args @@ -3379,13 +3384,10 @@ impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { match (ty.kind(), rcvr_ty.kind()) { // We care about the `T` in `Option` and `Result`. (ty::Adt(_, args), ty::Adt(_, rcvr_args)) => { - tracing::info!(?args, ?rcvr_args); - ( - rcvr, - args.type_at(0), - rcvr_args.type_at(0), - expr.span.with_lo(rcvr.span.hi()), - ) + if expr.span.eq_ctxt(rcvr.span) { + removal_span = Some(expr.span.with_lo(rcvr.span.hi())); + } + (rcvr, args.type_at(0), rcvr_args.type_at(0)) } _ => return, } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 613e8e90faceb..a240b7caf3314 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -80,7 +80,7 @@ pub(crate) struct SelfTypeConversionDiag<'t> { code = "", applicability = "machine-applicable" )] - pub removal_span: Span, + pub removal_span: Option, #[subdiagnostic] pub fully_qualified_path: Option>, } From 33233ab3587f8fc73f8dd2834854415b2e67ae93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 6 Jul 2026 04:28:12 +0000 Subject: [PATCH 15/17] Account for local type aliases with `cfg` attribute --- compiler/rustc_lint/src/builtin.rs | 16 +++++++- tests/ui/lint/self_type_conversion.rs | 48 +++++++++++++++++++++++ tests/ui/lint/self_type_conversion.stderr | 46 +++++++++++++++++++++- 3 files changed, 108 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 3d47e5d57622b..d5db3b12aff21 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -3302,7 +3302,21 @@ impl SelfTypeConversion<'_> { 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 }; + let path = match item.kind { + hir::ItemKind::Use(path, _kind) => path, + hir::ItemKind::TyAlias(_, _, _) + if find_attr!(cx.tcx, item.hir_id(), CfgTrace(..)) => + { + // We've encountered `#[cfg(..)] type Alias = Foo;`. + // FIXME(generic_const_exprs): Revisit this before stabilization. + // See also `tests/ui/const-generics/generic_const_exprs/type-alias-bounds.rs`. + let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip(); + self.ignored_types.insert(ty); + return; + } + _ => return, + }; + // Look at `cfg`d types as to account for things like `std::io::repr_bitpacked` and // `std::io::repr_unpacked`. // diff --git a/tests/ui/lint/self_type_conversion.rs b/tests/ui/lint/self_type_conversion.rs index 4a3925fe661a9..46e8e13bc6e94 100644 --- a/tests/ui/lint/self_type_conversion.rs +++ b/tests/ui/lint/self_type_conversion.rs @@ -114,6 +114,54 @@ mod b { //~^ ERROR useless conversion to the same type: `u64` } } +mod c { + 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; + + 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` + + let x: Option = Some(1); + // Ok, should not lint because the type alias is behind a `cfg_select!` + let _: Option = x.map(Into::into); + let y: Option = Some(1.); + // Ok, should not lint because the type alias is behind a `cfg` attr + let _: Option = y.map(Into::into); + let z: Option = Some(1); + let _: Option = z.map(Into::into); + //~^ ERROR useless conversion to the same type: `u64` + + let x: Result = Ok(1); + // Ok, should not lint because the type alias is behind a `cfg_select!` + let _: Result = x.map(Into::into); + let y: Result = Ok(1.); + // Ok, should not lint because the type alias is behind a `cfg` attr + let _: Result = y.map(Into::into); + let z: Result = Ok(1); + let _: Result = z.map(Into::into); + //~^ ERROR useless conversion to the same type: `u64` + } +} struct C { #[cfg(true)] diff --git a/tests/ui/lint/self_type_conversion.stderr b/tests/ui/lint/self_type_conversion.stderr index 5ad8d780efb0b..1399881999a6c 100644 --- a/tests/ui/lint/self_type_conversion.stderr +++ b/tests/ui/lint/self_type_conversion.stderr @@ -91,5 +91,49 @@ LL - let _: Result = z.map(Into::into); LL + let _: Result = z; | -error: aborting due to 6 previous errors +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:141: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 +help: consider removing the conversion call + | +LL - let _: u64 = z.into(); +LL + let _: u64 = z; + | +help: you can use the fully-qualified path to avoid triggering this lint + | +LL - let _: u64 = z.into(); +LL + let _: u64 = >::into(z); + | + +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:151:30 + | +LL | let _: Option = z.map(Into::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 +help: consider removing the conversion call + | +LL - let _: Option = z.map(Into::into); +LL + let _: Option = z; + | + +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:161:34 + | +LL | let _: Result = z.map(Into::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 +help: consider removing the conversion call + | +LL - let _: Result = z.map(Into::into); +LL + let _: Result = z; + | + +error: aborting due to 9 previous errors From 1e01856dd64928ea7f84a08d683c256ffc68e3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 6 Jul 2026 04:30:37 +0000 Subject: [PATCH 16/17] remove unneded `.into()` --- compiler/rustc_infer/src/infer/relate/generalize.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index ee6e13250e709..24d68d947bbcf 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -175,7 +175,7 @@ impl<'tcx> InferCtxt<'tcx> { if let Some(generalized_ty) = generalized_term.as_type() { match instantiation_variance { ty::Invariant => relation.register_predicates([ty::ProjectionPredicate { - projection_term: source_alias.into(), + projection_term: source_alias, term: generalized_ty.into(), }]), ty::Covariant => { @@ -190,7 +190,7 @@ impl<'tcx> InferCtxt<'tcx> { }), ty::PredicateKind::Clause(ty::ClauseKind::Projection( ty::ProjectionPredicate { - projection_term: source_alias.into(), + projection_term: source_alias, term: new_var.into(), }, )), @@ -207,7 +207,7 @@ impl<'tcx> InferCtxt<'tcx> { }), ty::PredicateKind::Clause(ty::ClauseKind::Projection( ty::ProjectionPredicate { - projection_term: source_alias.into(), + projection_term: source_alias, term: new_var.into(), }, )), From 0cf5b433dfbb90b1efc48dee85289c3cb33a7be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 6 Jul 2026 15:53:56 +0000 Subject: [PATCH 17/17] fix fmt --- compiler/rustc_lint/src/builtin.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index d5db3b12aff21..a707cf9ebf07a 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -3304,9 +3304,7 @@ impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { fn check_item_post(&mut self, cx: &LateContext<'tcx>, item: &hir::Item<'_>) { let path = match item.kind { hir::ItemKind::Use(path, _kind) => path, - hir::ItemKind::TyAlias(_, _, _) - if find_attr!(cx.tcx, item.hir_id(), CfgTrace(..)) => - { + hir::ItemKind::TyAlias(_, _, _) if find_attr!(cx.tcx, item.hir_id(), CfgTrace(..)) => { // We've encountered `#[cfg(..)] type Alias = Foo;`. // FIXME(generic_const_exprs): Revisit this before stabilization. // See also `tests/ui/const-generics/generic_const_exprs/type-alias-bounds.rs`.