diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index fdeeed92565..4f579b05d83 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -215,6 +215,7 @@ pub(crate) enum SuggestAnnotation { Unit(Span), Path(Span), Local(Span), + Turbo(Span, usize, usize), } #[derive(Clone)] @@ -244,6 +245,16 @@ fn add_to_diag_with>( SuggestAnnotation::Local(span) => { suggestions.push((span, ": ()".to_string())); } + SuggestAnnotation::Turbo(span, n_args, idx) => suggestions.push(( + span, + format!( + "::<{}>", + (0..n_args) + .map(|i| if i == idx { "()" } else { "_" }) + .collect::>() + .join(", "), + ), + )), } } diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index cefc28b400f..ada716addd0 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -9,6 +9,7 @@ use rustc_hir as hir; use rustc_hir::HirId; use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; use rustc_hir::intravisit::Visitor; use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable}; use rustc_session::lint; @@ -592,10 +593,45 @@ struct VidVisitor<'a, 'tcx> { reachable_vids: FxHashSet, fcx: &'a FnCtxt<'a, 'tcx>, } +impl<'tcx> VidVisitor<'_, 'tcx> { + fn suggest_for_segment( + &self, + arg_segment: &'tcx hir::PathSegment<'tcx>, + def_id: DefId, + id: HirId, + ) -> ControlFlow { + if arg_segment.args.is_none() + && let Some(all_args) = self.fcx.typeck_results.borrow().node_args_opt(id) + && let generics = self.fcx.tcx.generics_of(def_id) + && let args = &all_args[generics.parent_count..] + // We can't turbofish consts :( + && args.iter().all(|arg| matches!(arg.unpack(), ty::GenericArgKind::Type(_) | ty::GenericArgKind::Lifetime(_))) + { + let n_tys = args + .iter() + .filter(|arg| matches!(arg.unpack(), ty::GenericArgKind::Type(_))) + .count(); + for (idx, arg) in args.iter().enumerate() { + if let Some(ty) = arg.as_type() + && let Some(vid) = self.fcx.root_vid(ty) + && self.reachable_vids.contains(&vid) + { + return ControlFlow::Break(errors::SuggestAnnotation::Turbo( + arg_segment.ident.span.shrink_to_hi(), + n_tys, + idx, + )); + } + } + } + ControlFlow::Continue(()) + } +} impl<'tcx> Visitor<'tcx> for VidVisitor<'_, 'tcx> { type Result = ControlFlow; fn visit_ty(&mut self, hir_ty: &'tcx hir::Ty<'tcx>) -> Self::Result { + // Try to replace `_` with `()`. if let hir::TyKind::Infer = hir_ty.kind && let ty = self.fcx.typeck_results.borrow().node_type(hir_ty.hir_id) && let Some(vid) = self.fcx.root_vid(ty) @@ -606,7 +642,32 @@ fn visit_ty(&mut self, hir_ty: &'tcx hir::Ty<'tcx>) -> Self::Result { hir::intravisit::walk_ty(self, hir_ty) } + fn visit_qpath( + &mut self, + qpath: &'tcx rustc_hir::QPath<'tcx>, + id: HirId, + _span: Span, + ) -> Self::Result { + let arg_segment = match qpath { + hir::QPath::Resolved(_, path) => { + path.segments.last().expect("paths should have a segment") + } + hir::QPath::TypeRelative(_, segment) => segment, + hir::QPath::LangItem(..) => { + return hir::intravisit::walk_qpath(self, qpath, id); + } + }; + // Alternatively, try to turbofish `::<_, (), _>` (ignoring lifetimes, + // since we don't need to turbofish those; they'll be inferred). + // FIXME: Same logic could work for types... + if let Some(def_id) = self.fcx.typeck_results.borrow().qpath_res(qpath, id).opt_def_id() { + self.suggest_for_segment(arg_segment, def_id, id)?; + } + hir::intravisit::walk_qpath(self, qpath, id) + } + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Self::Result { + // Try to suggest adding an explicit qself `()` to a trait method path. if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind && let Res::Def(DefKind::AssocFn, def_id) = path.res && self.fcx.tcx.trait_of_item(def_id).is_some() @@ -618,6 +679,13 @@ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Self::Result { let span = path.span.shrink_to_lo().to(trait_segment.ident.span); return ControlFlow::Break(errors::SuggestAnnotation::Path(span)); } + // Or else turbofishing the method + if let hir::ExprKind::MethodCall(segment, ..) = expr.kind + && let Some(def_id) = + self.fcx.typeck_results.borrow().type_dependent_def_id(expr.hir_id) + { + self.suggest_for_segment(segment, def_id, expr.hir_id)?; + } hir::intravisit::walk_expr(self, expr) } diff --git a/tests/ui/editions/never-type-fallback-breaking.e2021.stderr b/tests/ui/editions/never-type-fallback-breaking.e2021.stderr index 1c6685261fb..79eee2a3def 100644 --- a/tests/ui/editions/never-type-fallback-breaking.e2021.stderr +++ b/tests/ui/editions/never-type-fallback-breaking.e2021.stderr @@ -32,6 +32,10 @@ note: in edition 2024, the requirement `!: Default` will fail | LL | deserialize()?; | ^^^^^^^^^^^^^ +help: use `()` annotations to avoid fallback changes + | +LL | deserialize::<()>()?; + | ++++++ warning: 2 warnings emitted diff --git a/tests/ui/never_type/dependency-on-fallback-to-unit.stderr b/tests/ui/never_type/dependency-on-fallback-to-unit.stderr index 065a6e559c1..79f47bb5fbc 100644 --- a/tests/ui/never_type/dependency-on-fallback-to-unit.stderr +++ b/tests/ui/never_type/dependency-on-fallback-to-unit.stderr @@ -32,6 +32,10 @@ note: in edition 2024, the requirement `!: Default` will fail | LL | deserialize()?; | ^^^^^^^^^^^^^ +help: use `()` annotations to avoid fallback changes + | +LL | deserialize::<()>()?; + | ++++++ warning: 2 warnings emitted diff --git a/tests/ui/never_type/diverging-fallback-no-leak.nofallback.stderr b/tests/ui/never_type/diverging-fallback-no-leak.nofallback.stderr index 11245cc7aab..d11c21d9573 100644 --- a/tests/ui/never_type/diverging-fallback-no-leak.nofallback.stderr +++ b/tests/ui/never_type/diverging-fallback-no-leak.nofallback.stderr @@ -13,6 +13,10 @@ note: in edition 2024, the requirement `!: Test` will fail LL | unconstrained_arg(return); | ^^^^^^ = note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default +help: use `()` annotations to avoid fallback changes + | +LL | unconstrained_arg::<()>(return); + | ++++++ warning: 1 warning emitted diff --git a/tests/ui/never_type/fallback-closure-ret.nofallback.stderr b/tests/ui/never_type/fallback-closure-ret.nofallback.stderr index 3fb5536dee7..fb0166dd9e0 100644 --- a/tests/ui/never_type/fallback-closure-ret.nofallback.stderr +++ b/tests/ui/never_type/fallback-closure-ret.nofallback.stderr @@ -13,6 +13,10 @@ note: in edition 2024, the requirement `!: Bar` will fail LL | foo(|| panic!()); | ^^^^^^^^^^^^^^^^ = note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default +help: use `()` annotations to avoid fallback changes + | +LL | foo::<(), _>(|| panic!()); + | +++++++++ warning: 1 warning emitted diff --git a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2015.stderr b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2015.stderr index b8de1026e37..6a48a7b9b47 100644 --- a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2015.stderr +++ b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2015.stderr @@ -8,6 +8,10 @@ LL | unsafe { mem::zeroed() } = note: for more information, see issue #123748 = help: specify the type explicitly = note: `#[warn(never_type_fallback_flowing_into_unsafe)]` on by default +help: use `()` annotations to avoid fallback changes + | +LL | unsafe { mem::zeroed::<()>() } + | ++++++ warning: never type fallback affects this call to an `unsafe` function --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:30:13 @@ -18,6 +22,10 @@ LL | core::mem::transmute(Zst) = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | core::mem::transmute::<_, ()>(Zst) + | +++++++++ warning: never type fallback affects this union access --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:47:18 @@ -38,6 +46,10 @@ LL | unsafe { *ptr::from_ref(&()).cast() } = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | unsafe { *ptr::from_ref(&()).cast::<()>() } + | ++++++ warning: never type fallback affects this call to an `unsafe` function --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:79:18 @@ -48,6 +60,10 @@ LL | unsafe { internally_create(x) } = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | unsafe { internally_create::<()>(x) } + | ++++++ warning: never type fallback affects this call to an `unsafe` function --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:97:18 @@ -58,6 +74,10 @@ LL | unsafe { zeroed() } = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | let zeroed = mem::zeroed::<()>; + | ++++++ warning: never type fallback affects this `unsafe` function --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:92:22 @@ -68,6 +88,10 @@ LL | let zeroed = mem::zeroed; = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | let zeroed = mem::zeroed::<()>; + | ++++++ warning: never type fallback affects this `unsafe` function --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:115:17 @@ -78,6 +102,10 @@ LL | let f = internally_create; = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | let f = internally_create::<()>; + | ++++++ warning: never type fallback affects this call to an `unsafe` method --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:140:13 diff --git a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2024.stderr b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2024.stderr index de44279ff8c..844cd62c267 100644 --- a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2024.stderr +++ b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2024.stderr @@ -8,6 +8,10 @@ LL | unsafe { mem::zeroed() } = note: for more information, see issue #123748 = help: specify the type explicitly = note: `#[deny(never_type_fallback_flowing_into_unsafe)]` on by default +help: use `()` annotations to avoid fallback changes + | +LL | unsafe { mem::zeroed::<()>() } + | ++++++ error: never type fallback affects this call to an `unsafe` function --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:30:13 @@ -18,6 +22,10 @@ LL | core::mem::transmute(Zst) = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | core::mem::transmute::<_, ()>(Zst) + | +++++++++ error: never type fallback affects this union access --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:47:18 @@ -38,6 +46,10 @@ LL | unsafe { *ptr::from_ref(&()).cast() } = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | unsafe { *ptr::from_ref(&()).cast::<()>() } + | ++++++ error: never type fallback affects this call to an `unsafe` function --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:79:18 @@ -48,6 +60,10 @@ LL | unsafe { internally_create(x) } = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | unsafe { internally_create::<()>(x) } + | ++++++ error: never type fallback affects this call to an `unsafe` function --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:97:18 @@ -58,6 +74,10 @@ LL | unsafe { zeroed() } = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | let zeroed = mem::zeroed::<()>; + | ++++++ error: never type fallback affects this `unsafe` function --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:92:22 @@ -68,6 +88,10 @@ LL | let zeroed = mem::zeroed; = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | let zeroed = mem::zeroed::<()>; + | ++++++ error: never type fallback affects this `unsafe` function --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:115:17 @@ -78,6 +102,10 @@ LL | let f = internally_create; = warning: this will change its meaning in a future release! = note: for more information, see issue #123748 = help: specify the type explicitly +help: use `()` annotations to avoid fallback changes + | +LL | let f = internally_create::<()>; + | ++++++ error: never type fallback affects this call to an `unsafe` method --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:140:13