diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 2b1e0a24294d2..dfc11b52cdf8c 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,486 @@ 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> { + 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; + }; + 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::OnlyBodies; + + 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 :) + + 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; + } + 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>) { + 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 + /// `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 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) + { + // 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 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 ["crates_io", "cargo"] + .contains(&cx.tcx.crate_name(hir::def_id::LOCAL_CRATE).as_str()) + { + return true; + } + + 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; + } + + 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/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),*]; 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 .