From 7b2bcfdf7e27880f1e86230cc0d203de359ce745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 14 Oct 2026 18:54:35 +0000 Subject: [PATCH 1/3] Remove clippy `.into()` in macros --- src/tools/clippy/clippy_config/src/conf.rs | 2 +- src/tools/clippy/clippy_config/src/de.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/clippy/clippy_config/src/conf.rs b/src/tools/clippy/clippy_config/src/conf.rs index 16b41a15f9546..aa8cef1049df5 100644 --- a/src/tools/clippy/clippy_config/src/conf.rs +++ b/src/tools/clippy/clippy_config/src/conf.rs @@ -164,7 +164,7 @@ macro_rules! define_Conf { $name = Some( <$ty as DeserializeOrDefault<_>>::deserialize_or_default( dcx, - value.into(), + value, first_expr!($($default,)? ()), ), ); diff --git a/src/tools/clippy/clippy_config/src/de.rs b/src/tools/clippy/clippy_config/src/de.rs index fb335f27aeed6..11916634f05d6 100644 --- a/src/tools/clippy/clippy_config/src/de.rs +++ b/src/tools/clippy/clippy_config/src/de.rs @@ -544,7 +544,7 @@ macro_rules! deserialize_table { match &**key.get_ref() { $($name_str => { // Duplicate keys are handled by the toml parser - $name = <$ty as crate::de::Deserialize>::deserialize($dcx, value.into()); + $name = <$ty as crate::de::Deserialize>::deserialize($dcx, value); },)+ _ => { const NAMES: &[&str] = &[$($name_str),*]; From 083fae5099232016389c9d23496a1532f08deb02 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 2/3] `>::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. Ignore useless `.into()` calls on fields or structs that are behind a `cfg` attr. Ignore useless `.into()` calls on `const`s that are behind a cfg attr. This side-steps a lot of `libc` cases. Account for `foo.map(Into::into)` and `foo.map_err(Into::into)`. 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. Account for `object`+`gimli` interaction where an assoc fn returns `type T: Into;` Only lint `useless_self_type_in_macro` when every local macro invocation would trigger it. Early collect all of the `.into()` calls in macros and then evaluate the lint. Only emit the lint in a macro if all of the expanded `.into()` are useless, otherwise silence. Account for `EnvKey`, do not lint against re-exports of re-exports marked with `cfg`. Ignore `.into()` in items created by `derive`s. (Account for `clap`'s use of user's Spans.) --- compiler/rustc_lint/src/builtin.rs | 461 +++++++++++++++++- compiler/rustc_lint/src/diagnostics.rs | 38 ++ compiler/rustc_lint/src/lib.rs | 2 + compiler/rustc_span/src/symbol.rs | 4 + library/core/src/convert/mod.rs | 1 + library/core/src/option.rs | 1 + library/core/src/result.rs | 2 + library/coretests/tests/convert.rs | 1 + .../needless_return_with_question_mark.fixed | 4 +- .../ui/needless_return_with_question_mark.rs | 4 +- .../needless_return_with_question_mark.stderr | 10 +- .../clippy/tests/ui/useless_conversion.fixed | 2 +- .../clippy/tests/ui/useless_conversion.rs | 2 +- .../derive-attribute-annotation-needed.rs | 1 + tests/ui/lint/self_type_conversion.rs | 333 +++++++++++++ tests/ui/lint/self_type_conversion.stderr | 296 +++++++++++ .../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 + 22 files changed, 1158 insertions(+), 13 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_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 2b1e0a24294d2..e52c59a4048ea 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -24,6 +24,7 @@ use rustc_ast_pretty::pprust::expr_to_string; use rustc_attr_ir::lang_items::LangItem; use rustc_attr_ir::{AttributeKind, DocAttribute, find_attr}; use rustc_attr_parsing::AttributeParser; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::{Applicability, Diagnostic, msg}; use rustc_feature::GateIssue; use rustc_hir::def::{DefKind, Res}; @@ -57,7 +58,8 @@ use crate::diagnostics::{ BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, BuiltinWhileTrue, - EqInternalMethodImplemented, InvalidAsmLabel, + EqInternalMethodImplemented, FullyQualifiedPathSuggestion, InvalidAsmLabel, + SelfTypeConversionDiag, }; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; @@ -1508,6 +1510,7 @@ pub mod soft { vec![ WHILE_TRUE, NON_SHORTHAND_FIELD_PATTERNS, + SELF_TYPE_CONVERSION, UNSAFE_CODE, MISSING_DOCS, MISSING_COPY_IMPLEMENTATIONS, @@ -3172,3 +3175,459 @@ 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. + /// + /// If you are in a situation where the presence of false positives are less of a problem than + /// false negatives, you can use clippy's `clippy::useless_conversion` lint, which doesn't + /// implement the heuristics this lint does. + pub SELF_TYPE_CONVERSION, + Deny, + "unnecessary call to `.into()`", +} + +#[derive(Debug)] +pub struct SelfTypeConversion<'tcx> { + pub ignored_types: FxHashSet>, + pub into_in_macros: FxHashMap>, + pub triggered_macros: FxIndexMap)>>, +} + +impl_lint_pass!(SelfTypeConversion<'_> => [SELF_TYPE_CONVERSION]); + +impl SelfTypeConversion<'_> { + pub fn new() -> Self { + Self { + ignored_types: Default::default(), + into_in_macros: Default::default(), + triggered_macros: Default::default(), + } + } +} +struct ConditionalTypeCollector<'tcx> { + tcx: TyCtxt<'tcx>, + ignored_mods: FxHashSet, + ignored_types: FxHashSet>, + into_in_macros: FxHashMap>, +} + +impl<'tcx> ConditionalTypeCollector<'tcx> { + fn new(tcx: TyCtxt<'tcx>) -> Self { + Self { + tcx, + ignored_mods: Default::default(), + ignored_types: Default::default(), + into_in_macros: Default::default(), + } + } +} + +struct IntoMethod<'tcx> { + ty: Ty<'tcx>, + rcvr_ty: Ty<'tcx>, + rcvr: &'tcx hir::Expr<'tcx>, + fully_qualified_path: Option>, + removal_span: Option, +} + +fn find_into_method<'tcx>( + tcx: TyCtxt<'tcx>, + typeck: &ty::TypeckResults<'tcx>, + expr: &'tcx hir::Expr<'tcx>, +) -> Option> { + let mut fully_qualified_path = None; + 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, _) => { + let Some(def_id) = typeck.type_dependent_def_id(expr.hir_id) else { + return None; + }; + if Some(def_id) == tcx.get_diagnostic_item(sym::into_fn) { + // We've got `foo.into()`. + let ty = typeck.expr_ty(expr); + let rcvr_ty = typeck.expr_ty(rcvr); + // 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) == tcx.get_diagnostic_item(sym::option_map) + || Some(def_id) == tcx.get_diagnostic_item(sym::result_map) + || Some(def_id) == tcx.get_diagnostic_item(sym::result_map_err)) + && let [arg] = args + && let hir::ExprKind::Path(qpath) = arg.kind + && let Res::Def(DefKind::AssocFn, into_def_id) = + typeck.qpath_res(&qpath, arg.hir_id) + && Some(into_def_id) == 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 = typeck.expr_ty(expr); + let rcvr_ty = typeck.expr_ty_adjusted(rcvr); + let param_at = if Some(def_id) == tcx.get_diagnostic_item(sym::result_map_err) { + // Look at `E` in `Result`, not `T`, we have `foo.map_err(...)`. + 1 + } else { + 0 + }; + match (ty.kind(), rcvr_ty.kind()) { + // We care about the `T` in `Option` and `Result`. + (ty::Adt(def, args), ty::Adt(rcvr_def, rcvr_args)) + if def.did() == rcvr_def.did() => + { + if expr.span.eq_ctxt(rcvr.span) { + removal_span = Some(expr.span.with_lo(rcvr.span.hi())); + } + (rcvr, args.type_at(param_at), rcvr_args.type_at(param_at)) + } + _ => return None, + } + } else { + // We don't have `foo.into()` corresponding to `Into::into` or + // `foo.map(Into::into)`. + return None; + } + } + _ => return None, + }; + + Some(IntoMethod { ty, rcvr_ty, rcvr, fully_qualified_path, removal_span }) +} + +impl<'tcx> hir::intravisit::Visitor<'tcx> for ConditionalTypeCollector<'tcx> { + type NestedFilter = rustc_middle::hir::nested_filter::All; + + fn maybe_tcx(&mut self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + if let Some(expn) = expr.span.macro_backtrace().next() + && let Some(def_id) = expn.macro_def_id + && def_id.is_local() + && let id = self.tcx.local_def_id_to_hir_id(expr.hir_id.owner) + && self.tcx.hir_node(id).body_id().is_some() + && let typeck = self.tcx.typeck(expr.hir_id.owner) + && find_into_method(self.tcx, typeck, expr).is_some() + { + self.into_in_macros.entry(def_id).or_default().push(expr.span); + } + hir::intravisit::walk_expr(self, expr); + } + + fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) { + hir::intravisit::walk_item(self, item); + let use_tree = match item.kind { + hir::ItemKind::Use(use_tree) => use_tree, + hir::ItemKind::TyAlias(_, _, _) + if find_attr!(self.tcx, item.hir_id(), CfgTrace(..) | CfgAttrTrace(..)) => + { + // 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 = self.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`. + // + // 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 mut import_has_cfg = + find_attr!(self.tcx, item.hir_id(), CfgTrace(..) | CfgAttrTrace(..)); + for segment in use_tree.prefix.segments { + if let Res::Def(DefKind::Mod, def_id) = segment.res + && self.ignored_mods.contains(&def_id) + { + import_has_cfg = true; + } + } + for res in use_tree.resolutions() { + for res in res.present_items() { + let def_id = match res { + Res::Def(DefKind::Mod, def_id) if import_has_cfg => { + self.ignored_mods.insert(def_id); + continue; + } + Res::Def( + DefKind::Struct | DefKind::Enum | DefKind::Union | DefKind::TyAlias, + def_id, + ) => def_id, + _ => continue, + }; + let ty = self.tcx.type_of(def_id).instantiate_identity().skip_normalization(); + + // Whether we've encountered `#[cfg(..)] type Alias = Ty;`. + let alias_has_cfg = find_attr!(self.tcx, def_id, CfgTrace(..) | CfgAttrTrace(..)); + 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); + } + } + } + } +} + +impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { + fn check_crate(&mut self, cx: &LateContext<'tcx>) { + let mut v = ConditionalTypeCollector::new(cx.tcx); + cx.tcx.hir_visit_all_item_likes_in_crate(&mut v); + self.ignored_types = v.ignored_types; + self.into_in_macros = v.into_in_macros; + } + + /// 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: &'tcx hir::Expr<'tcx>) { + let Some(IntoMethod { ty, rcvr_ty, rcvr, fully_qualified_path, removal_span }) = + find_into_method(cx.tcx, cx.typeck_results(), expr) + else { + return; + }; + + 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 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 hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = rcvr.kind + && let Res::Def(DefKind::Const { .. }, def_id) = path.res + && (find_attr!(cx.tcx, def_id, CfgTrace(..) | CfgAttrTrace(..)) + || find_attr!(cx.tcx, cx.tcx.parent(def_id), CfgTrace(..) | CfgAttrTrace(..))) + { + // 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 hir::ExprKind::MethodCall(_segment, _rcvr, _args, _) = rcvr.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(rcvr.hir_id) + && let output = cx.tcx.fn_sig(def_id).skip_binder().output().skip_binder() + && let ty::Alias(_, ty::AliasTy { kind: ty::Projection { def_id }, .. }) = output.kind() + && let bounds = cx.tcx.item_bounds(*def_id).instantiate_identity().skip_norm_wip() + && bounds.iter().any(|bound| { + Some(true) + == bound + .as_trait_clause() + .map(|c| Some(c.def_id()) == cx.tcx.get_diagnostic_item(sym::Into)) + }) + { + // We called `.into()` in an assoc `fn foo() -> Self::Type` where `type Type: Into<_>;`. + // We can assume that calling `.into()` on this value is the *intended use*. + // We saw this in interaction of `object`+`gimli` crates. + return; + } + + if let Some(expn) = expr.span.macro_backtrace().next() { + if let Some(def_id) = expn.macro_def_id + && def_id.is_local() + && self.into_in_macros.get(&def_id).is_some() + { + // Collect all the `.into()` in macro expansions. Once we've evaluated the whole + // crate, we'll only emit the `self_type_conversion` lint if *every* macro + // expansion would trigger it. + self.triggered_macros.entry(def_id).or_default().insert((expr.span, ty)); + } + // A macro that expands to this code isn't great, but end-users of a macro can't do + // anything about it. + return; + } + + if ["crates_io", "cargo"].contains(&cx.tcx.crate_name(hir::def_id::LOCAL_CRATE).as_str()) { + return; + } + if cx.tcx.def_span(expr.hir_id.owner).in_derive_expansion() { + // Some derive macros like `clap` use `.into()` in their expansions, but reuse the spans + // from the current crate for all of the involved expressions, so we take the nuclear + // option and skip every `derive`d item. This avoids cases like + // error: useless conversion to the same type: `std::string::String` + // --> src/main.rs:59:9 + // | + // 59 | #[clap(skip=String::new())] + // | ^^^^ + // | + // = note: this method call relies on the `impl Into for T` blanket ... + // help: consider removing the conversion call + // | + // 59 - #[clap(skip=String::new())] + // 59 + #[clap(skip)] + // | + // help: you can use the fully-qualified path to avoid triggering this lint + // | + // 59 - #[clap(skip=String::new())] + // 59 + #[clap(skip)>::into(String::new())] + // | + return; + } + + cx.emit_span_lint( + SELF_TYPE_CONVERSION, + expr.span, + SelfTypeConversionDiag { + ty, + removal_span, + fully_qualified_path, + fully_qualified_path_note: false, + }, + ); + } + + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { + for (def_id, entries) in &self.triggered_macros { + if entries.len() == self.into_in_macros[def_id].len() { + // Every macro invocation involves a useless `.into()`, so we lint them. Otherwise, + // it would mean that there are *some* macro calls that involve types where the + // `.into()` call *isn't* useless. This is still a bit more verbose than it could + // be. We could instead lint once on the macro itself instead of once per macro + // invocation. + for &(span, ty) in entries { + cx.emit_span_lint( + SELF_TYPE_CONVERSION, + span, + SelfTypeConversionDiag { + ty, + removal_span: None, + fully_qualified_path: None, + fully_qualified_path_note: true, + }, + ); + } + } + } + } +} + +fn struct_field_has_cfg(tcx: TyCtxt<'_>, def_id: DefId, field_name: Symbol) -> bool { + if find_attr!(tcx, def_id, CfgTrace(..) | CfgAttrTrace(..)) { + // The item is `cfg`d. + return true; + } + if find_attr!(tcx, tcx.parent(def_id), CfgTrace(..) | CfgAttrTrace(..)) { + // 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(..) | CfgAttrTrace(..)) { + return true; + } + } + } + false +} diff --git a/compiler/rustc_lint/src/diagnostics.rs b/compiler/rustc_lint/src/diagnostics.rs index 5167737ac001f..0ddd4be96e7b1 100644 --- a/compiler/rustc_lint/src/diagnostics.rs +++ b/compiler/rustc_lint/src/diagnostics.rs @@ -166,6 +166,44 @@ 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" +)] +pub(crate) struct SelfTypeConversionDiag<'t> { + pub ty: Ty<'t>, + #[suggestion( + "consider removing the conversion call", + style = "verbose", + code = "", + applicability = "machine-applicable" + )] + pub removal_span: Option, + #[subdiagnostic] + pub fully_qualified_path: Option>, + #[note( + "you can instead use the fully-qualified path `<{$ty} as Into<{$ty}>::into(val) to avoid \ + triggering this lint" + )] + pub fully_qualified_path_note: bool, +} + +#[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, +} + // autorefs.rs #[derive(Diagnostic)] #[diag("implicit autoref creates a reference to the dereference of a raw pointer")] diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 94f6183740954..f1eacdbe36581 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -335,6 +335,8 @@ fn register_builtins(store: &mut LintStore) { store.register_lints(&BuiltinCombinedLateLintModPass::lint_vec()); store.register_lints(&foreign_modules::lint_vec()); store.register_lints(&hardwired::lint_vec()); + store.register_lints(&SelfTypeConversion::lint_vec()); + store.register_late_lint_pass(Box::new(|_| Box::new(SelfTypeConversion::new()))); add_lint_group!( "nonstandard_style", diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 4ab0a6d988b84..e1d490d722e64 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1185,6 +1185,7 @@ symbols! { internal_features, interrupt, into_async_iter_into_iter, + into_fn, into_future, into_iter, into_try_type, @@ -1538,6 +1539,7 @@ symbols! { optin_builtin_traits, option, option_env, + option_map, options, or, or_patterns, @@ -1768,6 +1770,8 @@ symbols! { residual, result, result_ffi_guarantees, + result_map, + result_map_err, return_address, return_position_impl_trait_in_trait, return_type_notation, diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index b0f74a8afb598..5976db69e0fac 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -450,6 +450,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/core/src/option.rs b/library/core/src/option.rs index f53eb4a294194..1d6f221273274 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -1157,6 +1157,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 255c93bd8109c..6f03b0a2ba8ea 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -829,6 +829,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, @@ -958,6 +959,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_err"] pub const fn map_err(self, op: O) -> Result where O: [const] FnOnce(E) -> F + [const] Destruct, 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/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed b/src/tools/clippy/tests/ui/needless_return_with_question_mark.fixed index bd6c7a18722ea..976ebe0a23f9f 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 @@ -5,7 +5,9 @@ clippy::needless_return, clippy::no_effect, clippy::unit_arg, - clippy::useless_conversion + clippy::useless_conversion, + self_type_conversion, + unused )] #[macro_use] 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 a92dd92ab0bcd..a21d7c130a72c 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 @@ -5,7 +5,9 @@ clippy::needless_return, clippy::no_effect, clippy::unit_arg, - clippy::useless_conversion + clippy::useless_conversion, + self_type_conversion, + unused )] #[macro_use] 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 5aec5ec00b612..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:28: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:69: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:133: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:142: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:162: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 b8315ba3a32d0..1ec0d092f5d0b 100644 --- a/src/tools/clippy/tests/ui/useless_conversion.fixed +++ b/src/tools/clippy/tests/ui/useless_conversion.fixed @@ -1,7 +1,7 @@ #![warn(clippy::useless_conversion)] // FIXME(static_mut_refs): use raw pointers instead of references #![expect(static_mut_refs)] -#![allow(clippy::into_iter_on_ref)] +#![allow(clippy::into_iter_on_ref, clippy::needless_ifs, clippy::unnecessary_wraps, unused, self_type_conversion)] use std::ops::ControlFlow; diff --git a/src/tools/clippy/tests/ui/useless_conversion.rs b/src/tools/clippy/tests/ui/useless_conversion.rs index 01056b628151d..965fff8daf396 100644 --- a/src/tools/clippy/tests/ui/useless_conversion.rs +++ b/src/tools/clippy/tests/ui/useless_conversion.rs @@ -1,7 +1,7 @@ #![warn(clippy::useless_conversion)] // FIXME(static_mut_refs): use raw pointers instead of references #![expect(static_mut_refs)] -#![allow(clippy::into_iter_on_ref)] +#![allow(clippy::into_iter_on_ref, clippy::needless_ifs, clippy::unnecessary_wraps, unused, self_type_conversion)] use std::ops::ControlFlow; diff --git a/tests/ui/inference/need_type_info/auxiliary/derive-attribute-annotation-needed.rs b/tests/ui/inference/need_type_info/auxiliary/derive-attribute-annotation-needed.rs index 27f5a4c1f2475..28dcecf3a91c9 100644 --- a/tests/ui/inference/need_type_info/auxiliary/derive-attribute-annotation-needed.rs +++ b/tests/ui/inference/need_type_info/auxiliary/derive-attribute-annotation-needed.rs @@ -1,5 +1,6 @@ //@ edition: 2018 #![feature(proc_macro_quote)] +#![allow(self_type_conversion)] use proc_macro::{TokenStream, Ident, TokenTree, quote}; #[proc_macro_derive(Serialize, attributes(serde))] diff --git a/tests/ui/lint/self_type_conversion.rs b/tests/ui/lint/self_type_conversion.rs new file mode 100644 index 0000000000000..ddb1860da6bff --- /dev/null +++ b/tests/ui/lint/self_type_conversion.rs @@ -0,0 +1,333 @@ +#![deny(self_type_conversion)] +//@ edition:2024 + +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` + + 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` + + let x: Result<(), Int> = Err(1); + // Ok, should not lint because the type alias is behind a `cfg_select!` + let _: Result<(), i32> = x.map_err(Into::into); + let y: Result<(), Float> = Err(1.); + // Ok, should not lint because the type alias is behind a `cfg` attr + let _: Result<(), f64> = y.map_err(Into::into); + let z: Result<(), Trigger> = Err(1); + let _: Result<(), u64> = z.map_err(Into::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` + + 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` + + let x: Result<(), Int> = Err(1); + // Ok, should not lint because the type alias is behind a `cfg_select!` + let _: Result<(), i32> = x.map_err(Into::into); + let y: Result<(), Float> = Err(1.); + // Ok, should not lint because the type alias is behind a `cfg` attr + let _: Result<(), f64> = y.map_err(Into::into); + let z: Result<(), Trigger> = Err(1); + let _: Result<(), u64> = z.map_err(Into::into); + //~^ 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` + + let x: Result<(), Int> = Err(1); + // Ok, should not lint because the type alias is behind a `cfg_select!` + let _: Result<(), i32> = x.map_err(Into::into); + let y: Result<(), Float> = Err(1.); + // Ok, should not lint because the type alias is behind a `cfg` attr + let _: Result<(), f64> = y.map_err(Into::into); + let z: Result<(), Trigger> = Err(1); + let _: Result<(), u64> = z.map_err(Into::into); + //~^ ERROR useless conversion to the same type: `u64` + } +} + +struct C { + #[cfg(true)] + foo: (), +} +#[cfg(true)] +struct D { + foo: (), +} +#[cfg(true)] +mod e { + pub(crate) struct F { + pub(crate) foo: (), + } + pub(crate) const G: () = (); +} +#[cfg(true)] +const H: () = (); + +trait T { + type Assoc: Into; + + fn foo(&self) -> Self::Assoc; +} + +impl T for () { + type Assoc = u64; + fn foo(&self) -> u64 { 0 } +} + +struct I; + +fn question_mark() -> Result { + // Ok, `?` expands to an `.into()` call, but we shouldn't complain about that + Ok(Err(I)?) +} + +macro_rules! in_macro { + ($expr:expr) => { + $expr.into() + } +} +macro_rules! in_macro_2 { + ($expr:expr) => { + $expr.into() + //~^ ERROR useless conversion to the same type: `()` + //~| ERROR useless conversion to the same type: `u16` + } +} +macro_rules! in_macro_3 { + ($expr1:expr, $expr2:expr) => { + // FIXME: should likely complain about `$expr1` only, but now it doesn't complain at all. + ($expr1.into(), $expr2.into()) + } +} +macro_rules! in_macro_4 { + ($expr1:expr, $expr2:expr) => { + ($expr1.into(), $expr2.into()) + //~^ ERROR useless conversion to the same type: `()` + //~| ERROR useless conversion to the same type: `()` + //~| ERROR useless conversion to the same type: `()` + //~| ERROR useless conversion to the same type: `u16` + } +} +macro_rules! in_macro_5 { + ($expr1:expr, $expr2:expr) => { + ($expr1.into(), $expr2.into()) + } +} + +macro_rules! multiple_arms_ok { + ($($ty:ty: $expr:expr,)*) => { + $( + let _: $ty = $expr.into(); + )* + } +} + +macro_rules! multiple_arms { + ($($ty:ty: $expr:expr,)*) => { + $( + let _: $ty = $expr.into(); + //~^ ERROR useless conversion to the same type: `()` + //~| ERROR useless conversion to the same type: `u64` + )* + } +} + +struct Source; +mod re_export { + cfg_select! { + _ => { + mod select { + pub(crate) use crate::Source as Target; + } + use select as rename; + } + } + + pub(crate) use rename::Target; +} + +fn into(x: re_export::Target) -> Source { + x.into() // Ok because `foo::Target` is re-exported from a `cfg`d module +} + +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 + let () = e::G.into(); // Ok, `e` is behind a `cfg` attr + let () = H.into(); // Ok, `H` is behind a `cfg` attr + let _ = ().foo(); // Ok, `T::Assoc` has an `Into<_>` bound + let () = in_macro!(()); // Ok, there's another macro expansion where the `.into()` isn't useless + let _: u64 = in_macro!(0u16); // Ok, the expanded `.into()` isn't entirelyl uselses + let () = in_macro_2!(()); // Not ok, pointing into the macro + let _: u16 = in_macro_2!(0u16); // Not ok, pointing into the macro + let _: ((), ()) = in_macro_3!((), ()); // Should complain about the first argument, but doesn't + let _: ((), u64) = in_macro_3!((), 0u16); // because *one* of the `.into()` wasn't useless. + let _: ((), ()) = in_macro_4!((), ()); // Not ok, because both into calls are useless + let _: ((), u16) = in_macro_4!((), 0u16); // Not ok, because both into calls are useless + let _: ((), ()) = in_macro_5!((), ()); // Ok, because the next call is not useless + let _: (u64, u64) = in_macro_5!(0u16, 0u16); // Ok + multiple_arms_ok! { + (): (), // Ok because of the next line + u64: 0u16, + } + multiple_arms! { + (): (), + u64: 0u64, + } +} diff --git a/tests/ui/lint/self_type_conversion.stderr b/tests/ui/lint/self_type_conversion.stderr new file mode 100644 index 0000000000000..bfc4618581b57 --- /dev/null +++ b/tests/ui/lint/self_type_conversion.stderr @@ -0,0 +1,296 @@ +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:32: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: 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:42: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:52: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:62:34 + | +LL | let _: Result<(), u64> = z.map_err(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<(), u64> = z.map_err(Into::into); +LL + let _: Result<(), u64> = z; + | + +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:105: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:115: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:125: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:135:34 + | +LL | let _: Result<(), u64> = z.map_err(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<(), u64> = z.map_err(Into::into); +LL + let _: Result<(), u64> = z; + | + +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:163: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:173: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:183: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:193:34 + | +LL | let _: Result<(), u64> = z.map_err(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<(), u64> = z.map_err(Into::into); +LL + let _: Result<(), u64> = z; + | + +error: useless conversion to the same type: `()` + --> $DIR/self_type_conversion.rs:241:9 + | +LL | macro_rules! in_macro_2 { +LL | ($expr:expr) => { +LL | $expr.into() + | ^^^^^^^^^^^^ +... +LL | let () = in_macro_2!(()); // Not ok, pointing into the macro + | --------------- in this macro invocation + | + = 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 `<() as Into<()>::into(val) to avoid triggering this lint + +error: useless conversion to the same type: `u16` + --> $DIR/self_type_conversion.rs:241:9 + | +LL | macro_rules! in_macro_2 { +LL | ($expr:expr) => { +LL | $expr.into() + | ^^^^^^^^^^^^ +... +LL | let _: u16 = in_macro_2!(0u16); // Not ok, pointing into the macro + | ----------------- in this macro invocation + | + = 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: useless conversion to the same type: `()` + --> $DIR/self_type_conversion.rs:254:10 + | +LL | macro_rules! in_macro_4 { +LL | ($expr1:expr, $expr2:expr) => { +LL | ($expr1.into(), $expr2.into()) + | ^^^^^^^^^^^^^ +... +LL | let _: ((), ()) = in_macro_4!((), ()); // Not ok, because both into calls are useless + | ------------------- in this macro invocation + | + = 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 `<() as Into<()>::into(val) to avoid triggering this lint + +error: useless conversion to the same type: `()` + --> $DIR/self_type_conversion.rs:254:25 + | +LL | macro_rules! in_macro_4 { +LL | ($expr1:expr, $expr2:expr) => { +LL | ($expr1.into(), $expr2.into()) + | ^^^^^^^^^^^^^ +... +LL | let _: ((), ()) = in_macro_4!((), ()); // Not ok, because both into calls are useless + | ------------------- in this macro invocation + | + = 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 `<() as Into<()>::into(val) to avoid triggering this lint + +error: useless conversion to the same type: `()` + --> $DIR/self_type_conversion.rs:254:10 + | +LL | macro_rules! in_macro_4 { +LL | ($expr1:expr, $expr2:expr) => { +LL | ($expr1.into(), $expr2.into()) + | ^^^^^^^^^^^^^ +... +LL | let _: ((), u16) = in_macro_4!((), 0u16); // Not ok, because both into calls are useless + | --------------------- in this macro invocation + | + = 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 `<() as Into<()>::into(val) to avoid triggering this lint + +error: useless conversion to the same type: `u16` + --> $DIR/self_type_conversion.rs:254:25 + | +LL | macro_rules! in_macro_4 { +LL | ($expr1:expr, $expr2:expr) => { +LL | ($expr1.into(), $expr2.into()) + | ^^^^^^^^^^^^^ +... +LL | let _: ((), u16) = in_macro_4!((), 0u16); // Not ok, because both into calls are useless + | --------------------- in this macro invocation + | + = 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: useless conversion to the same type: `()` + --> $DIR/self_type_conversion.rs:278:26 + | +LL | macro_rules! multiple_arms { +... +LL | let _: $ty = $expr.into(); + | ^^^^^^^^^^^^ +... +LL | / multiple_arms! { +LL | | (): (), +LL | | u64: 0u64, +LL | | } + | |_____- in this macro invocation + | + = 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 `<() as Into<()>::into(val) to avoid triggering this lint + +error: useless conversion to the same type: `u64` + --> $DIR/self_type_conversion.rs:278:26 + | +LL | macro_rules! multiple_arms { +... +LL | let _: $ty = $expr.into(); + | ^^^^^^^^^^^^ +... +LL | / multiple_arms! { +LL | | (): (), +LL | | u64: 0u64, +LL | | } + | |_____- in this macro invocation + | + = 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 20 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 3c0962085fe6b..3d69a0347c0f1 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 @@ -2,6 +2,7 @@ //@ revisions: current next //@ ignore-compare-mode-next-solver (explicit revisions) //@[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 bd72471add1e4a5952143db66601daf16d2d22c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 19 Oct 2026 00:18:46 +0000 Subject: [PATCH 3/3] Track timing info for self_type_conversion lint stages --- compiler/rustc_lint/src/builtin.rs | 417 +++++++++++++++-------------- 1 file changed, 222 insertions(+), 195 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index e52c59a4048ea..dfc11b52cdf8c 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -3271,75 +3271,77 @@ fn find_into_method<'tcx>( typeck: &ty::TypeckResults<'tcx>, expr: &'tcx hir::Expr<'tcx>, ) -> Option> { - let mut fully_qualified_path = None; - 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, _) => { - let Some(def_id) = typeck.type_dependent_def_id(expr.hir_id) else { - return None; - }; - if Some(def_id) == tcx.get_diagnostic_item(sym::into_fn) { - // We've got `foo.into()`. - let ty = typeck.expr_ty(expr); - let rcvr_ty = typeck.expr_ty(rcvr); - // 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) == tcx.get_diagnostic_item(sym::option_map) - || Some(def_id) == tcx.get_diagnostic_item(sym::result_map) - || Some(def_id) == tcx.get_diagnostic_item(sym::result_map_err)) - && let [arg] = args - && let hir::ExprKind::Path(qpath) = arg.kind - && let Res::Def(DefKind::AssocFn, into_def_id) = - typeck.qpath_res(&qpath, arg.hir_id) - && Some(into_def_id) == 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 = typeck.expr_ty(expr); - let rcvr_ty = typeck.expr_ty_adjusted(rcvr); - let param_at = if Some(def_id) == tcx.get_diagnostic_item(sym::result_map_err) { - // Look at `E` in `Result`, not `T`, we have `foo.map_err(...)`. - 1 - } else { - 0 + tcx.sess.time("self_type_conversion_find_into_method", || { + let mut fully_qualified_path = None; + 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, _) => { + let Some(def_id) = typeck.type_dependent_def_id(expr.hir_id) else { + return None; }; - match (ty.kind(), rcvr_ty.kind()) { - // We care about the `T` in `Option` and `Result`. - (ty::Adt(def, args), ty::Adt(rcvr_def, rcvr_args)) - if def.did() == rcvr_def.did() => - { - if expr.span.eq_ctxt(rcvr.span) { - removal_span = Some(expr.span.with_lo(rcvr.span.hi())); + if Some(def_id) == tcx.get_diagnostic_item(sym::into_fn) { + // We've got `foo.into()`. + let ty = typeck.expr_ty(expr); + let rcvr_ty = typeck.expr_ty(rcvr); + // 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) == tcx.get_diagnostic_item(sym::option_map) + || Some(def_id) == tcx.get_diagnostic_item(sym::result_map) + || Some(def_id) == tcx.get_diagnostic_item(sym::result_map_err)) + && let [arg] = args + && let hir::ExprKind::Path(qpath) = arg.kind + && let Res::Def(DefKind::AssocFn, into_def_id) = + typeck.qpath_res(&qpath, arg.hir_id) + && Some(into_def_id) == 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 = typeck.expr_ty(expr); + let rcvr_ty = typeck.expr_ty_adjusted(rcvr); + let param_at = if Some(def_id) == tcx.get_diagnostic_item(sym::result_map_err) { + // Look at `E` in `Result`, not `T`, we have `foo.map_err(...)`. + 1 + } else { + 0 + }; + match (ty.kind(), rcvr_ty.kind()) { + // We care about the `T` in `Option` and `Result`. + (ty::Adt(def, args), ty::Adt(rcvr_def, rcvr_args)) + if def.did() == rcvr_def.did() => + { + if expr.span.eq_ctxt(rcvr.span) { + removal_span = Some(expr.span.with_lo(rcvr.span.hi())); + } + (rcvr, args.type_at(param_at), rcvr_args.type_at(param_at)) } - (rcvr, args.type_at(param_at), rcvr_args.type_at(param_at)) + _ => return None, } - _ => return None, + } else { + // We don't have `foo.into()` corresponding to `Into::into` or + // `foo.map(Into::into)`. + return None; } - } else { - // We don't have `foo.into()` corresponding to `Into::into` or - // `foo.map(Into::into)`. - return None; } - } - _ => return None, - }; + _ => return None, + }; - Some(IntoMethod { ty, rcvr_ty, rcvr, fully_qualified_path, removal_span }) + Some(IntoMethod { ty, rcvr_ty, rcvr, fully_qualified_path, removal_span }) + }) } impl<'tcx> hir::intravisit::Visitor<'tcx> for ConditionalTypeCollector<'tcx> { - type NestedFilter = rustc_middle::hir::nested_filter::All; + type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies; fn maybe_tcx(&mut self) -> TyCtxt<'tcx> { self.tcx @@ -3394,52 +3396,57 @@ impl<'tcx> hir::intravisit::Visitor<'tcx> for ConditionalTypeCollector<'tcx> { // 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 mut import_has_cfg = - find_attr!(self.tcx, item.hir_id(), CfgTrace(..) | CfgAttrTrace(..)); - for segment in use_tree.prefix.segments { - if let Res::Def(DefKind::Mod, def_id) = segment.res - && self.ignored_mods.contains(&def_id) - { - import_has_cfg = true; + self.tcx.sess.time("self_type_conversion_collect_ignored_type", || { + // Whether we've encountered `#[cfg(..)] use foo::bar;`. + let mut import_has_cfg = + find_attr!(self.tcx, item.hir_id(), CfgTrace(..) | CfgAttrTrace(..)); + for segment in use_tree.prefix.segments { + if let Res::Def(DefKind::Mod, def_id) = segment.res + && self.ignored_mods.contains(&def_id) + { + import_has_cfg = true; + } } - } - for res in use_tree.resolutions() { - for res in res.present_items() { - let def_id = match res { - Res::Def(DefKind::Mod, def_id) if import_has_cfg => { - self.ignored_mods.insert(def_id); - continue; + for res in use_tree.resolutions() { + for res in res.present_items() { + let def_id = match res { + Res::Def(DefKind::Mod, def_id) if import_has_cfg => { + self.ignored_mods.insert(def_id); + continue; + } + Res::Def( + DefKind::Struct | DefKind::Enum | DefKind::Union | DefKind::TyAlias, + def_id, + ) => def_id, + _ => continue, + }; + let ty = self.tcx.type_of(def_id).instantiate_identity().skip_normalization(); + + // Whether we've encountered `#[cfg(..)] type Alias = Ty;`. + let alias_has_cfg = + find_attr!(self.tcx, def_id, CfgTrace(..) | CfgAttrTrace(..)); + 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); } - Res::Def( - DefKind::Struct | DefKind::Enum | DefKind::Union | DefKind::TyAlias, - def_id, - ) => def_id, - _ => continue, - }; - let ty = self.tcx.type_of(def_id).instantiate_identity().skip_normalization(); - - // Whether we've encountered `#[cfg(..)] type Alias = Ty;`. - let alias_has_cfg = find_attr!(self.tcx, def_id, CfgTrace(..) | CfgAttrTrace(..)); - 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); } } - } + }); } } impl<'tcx> LateLintPass<'tcx> for SelfTypeConversion<'tcx> { fn check_crate(&mut self, cx: &LateContext<'tcx>) { - let mut v = ConditionalTypeCollector::new(cx.tcx); - cx.tcx.hir_visit_all_item_likes_in_crate(&mut v); - self.ignored_types = v.ignored_types; - self.into_in_macros = v.into_in_macros; + cx.tcx.sess.time("self_type_conversion_collect_filtered_types", || { + let mut v = ConditionalTypeCollector::new(cx.tcx); + cx.tcx.hir_visit_all_item_likes_in_crate(&mut v); + self.ignored_types = v.ignored_types; + self.into_in_macros = v.into_in_macros; + }) } /// Look for method calls to `Into::into` that rely on inference and that ends up using the @@ -3467,111 +3474,131 @@ 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) + if cx.tcx.sess.time("self_type_conversion_filter_cfgd_field", || { + 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 true; + } + } + 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) { - return; + // 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 true; + } + if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = rcvr.kind + && let Res::Def(DefKind::Const { .. }, def_id) = path.res + && (find_attr!(cx.tcx, def_id, CfgTrace(..) | CfgAttrTrace(..)) + || find_attr!(cx.tcx, cx.tcx.parent(def_id), CfgTrace(..) | CfgAttrTrace(..))) + { + // 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 true; } - } - 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 hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = rcvr.kind - && let Res::Def(DefKind::Const { .. }, def_id) = path.res - && (find_attr!(cx.tcx, def_id, CfgTrace(..) | CfgAttrTrace(..)) - || find_attr!(cx.tcx, cx.tcx.parent(def_id), CfgTrace(..) | CfgAttrTrace(..))) - { - // 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 hir::ExprKind::MethodCall(_segment, _rcvr, _args, _) = rcvr.kind - && let Some(def_id) = cx.typeck_results().type_dependent_def_id(rcvr.hir_id) - && let output = cx.tcx.fn_sig(def_id).skip_binder().output().skip_binder() - && let ty::Alias(_, ty::AliasTy { kind: ty::Projection { def_id }, .. }) = output.kind() - && let bounds = cx.tcx.item_bounds(*def_id).instantiate_identity().skip_norm_wip() - && bounds.iter().any(|bound| { - Some(true) - == bound - .as_trait_clause() - .map(|c| Some(c.def_id()) == cx.tcx.get_diagnostic_item(sym::Into)) - }) - { - // We called `.into()` in an assoc `fn foo() -> Self::Type` where `type Type: Into<_>;`. - // We can assume that calling `.into()` on this value is the *intended use*. - // We saw this in interaction of `object`+`gimli` crates. - return; - } + if cx.tcx.sess.time("self_type_conversion_filter_assoc_type_into", || { + if let hir::ExprKind::MethodCall(_segment, _rcvr, _args, _) = rcvr.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(rcvr.hir_id) + && let output = cx.tcx.fn_sig(def_id).skip_binder().output().skip_binder() + && let ty::Alias(_, ty::AliasTy { kind: ty::Projection { def_id }, .. }) = + output.kind() + && let bounds = + cx.tcx.item_bounds(*def_id).instantiate_identity().skip_norm_wip() + && bounds.iter().any(|bound| { + Some(true) + == bound + .as_trait_clause() + .map(|c| Some(c.def_id()) == cx.tcx.get_diagnostic_item(sym::Into)) + }) + { + // We called `.into()` in an assoc `fn foo() -> Self::Type` where `type Type: Into<_>;`. + // We can assume that calling `.into()` on this value is the *intended use*. + // We saw this in interaction of `object`+`gimli` crates. + return true; + } + false + }) { + return true; + } - if let Some(expn) = expr.span.macro_backtrace().next() { - if let Some(def_id) = expn.macro_def_id - && def_id.is_local() - && self.into_in_macros.get(&def_id).is_some() + if ["crates_io", "cargo"] + .contains(&cx.tcx.crate_name(hir::def_id::LOCAL_CRATE).as_str()) { - // Collect all the `.into()` in macro expansions. Once we've evaluated the whole - // crate, we'll only emit the `self_type_conversion` lint if *every* macro - // expansion would trigger it. - self.triggered_macros.entry(def_id).or_default().insert((expr.span, ty)); + return true; } - // A macro that expands to this code isn't great, but end-users of a macro can't do - // anything about it. - return; - } - if ["crates_io", "cargo"].contains(&cx.tcx.crate_name(hir::def_id::LOCAL_CRATE).as_str()) { - return; - } - if cx.tcx.def_span(expr.hir_id.owner).in_derive_expansion() { - // Some derive macros like `clap` use `.into()` in their expansions, but reuse the spans - // from the current crate for all of the involved expressions, so we take the nuclear - // option and skip every `derive`d item. This avoids cases like - // error: useless conversion to the same type: `std::string::String` - // --> src/main.rs:59:9 - // | - // 59 | #[clap(skip=String::new())] - // | ^^^^ - // | - // = note: this method call relies on the `impl Into for T` blanket ... - // help: consider removing the conversion call - // | - // 59 - #[clap(skip=String::new())] - // 59 + #[clap(skip)] - // | - // help: you can use the fully-qualified path to avoid triggering this lint - // | - // 59 - #[clap(skip=String::new())] - // 59 + #[clap(skip)>::into(String::new())] - // | + if cx.tcx.sess.time("self_type_conversion_filter_into_in_macros", || { + if let Some(expn) = expr.span.macro_backtrace().next() { + if let Some(def_id) = expn.macro_def_id + && def_id.is_local() + && self.into_in_macros.get(&def_id).is_some() + { + // Collect all the `.into()` in macro expansions. Once we've evaluated the whole + // crate, we'll only emit the `self_type_conversion` lint if *every* macro + // expansion would trigger it. + self.triggered_macros.entry(def_id).or_default().insert((expr.span, ty)); + } + // A macro that expands to this code isn't great, but end-users of a macro can't do + // anything about it. + return true; + } + + if cx.tcx.def_span(expr.hir_id.owner).in_derive_expansion() { + // Some derive macros like `clap` use `.into()` in their expansions, but reuse the spans + // from the current crate for all of the involved expressions, so we take the nuclear + // option and skip every `derive`d item. This avoids cases like + // error: useless conversion to the same type: `std::string::String` + // --> src/main.rs:59:9 + // | + // 59 | #[clap(skip=String::new())] + // | ^^^^ + // | + // = note: this method call relies on the `impl Into for T` blanket ... + // help: consider removing the conversion call + // | + // 59 - #[clap(skip=String::new())] + // 59 + #[clap(skip)] + // | + // help: you can use the fully-qualified path to avoid triggering this lint + // | + // 59 - #[clap(skip=String::new())] + // 59 + #[clap(skip)>::into(String::new())] + // | + return true; + } + false + }) { + return true; + } + false + }) { return; }