Skip to content

Commit 76aa9e5

Browse files
authored
Rollup merge of #95395 - compiler-errors:infer-on-impl-for-trait, r=oli-obk
Better error message for `_` in function signature in `impl Trait for Ty` Provides a replacement suggestion for when `_` is present in the function signature for `impl Trait for Ty`, using the substitutions from the trait to compute the exact type. Fixes #95097
2 parents 18f314e + 319fbe3 commit 76aa9e5

7 files changed

+260
-72
lines changed

compiler/rustc_typeck/src/astconv/mod.rs

+83-9
Original file line numberDiff line numberDiff line change
@@ -2563,40 +2563,77 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
25632563

25642564
// We proactively collect all the inferred type params to emit a single error per fn def.
25652565
let mut visitor = HirPlaceholderCollector::default();
2566-
for ty in decl.inputs {
2567-
visitor.visit_ty(ty);
2568-
}
2566+
let mut infer_replacements = vec![];
2567+
25692568
walk_generics(&mut visitor, generics);
25702569

2571-
let input_tys = decl.inputs.iter().map(|a| self.ty_of_arg(a, None));
2570+
let input_tys: Vec<_> = decl
2571+
.inputs
2572+
.iter()
2573+
.enumerate()
2574+
.map(|(i, a)| {
2575+
if let hir::TyKind::Infer = a.kind && !self.allow_ty_infer() {
2576+
if let Some(suggested_ty) =
2577+
self.suggest_trait_fn_ty_for_impl_fn_infer(hir_id, Some(i))
2578+
{
2579+
infer_replacements.push((a.span, suggested_ty.to_string()));
2580+
return suggested_ty;
2581+
}
2582+
}
2583+
2584+
// Only visit the type looking for `_` if we didn't fix the type above
2585+
visitor.visit_ty(a);
2586+
self.ty_of_arg(a, None)
2587+
})
2588+
.collect();
2589+
25722590
let output_ty = match decl.output {
25732591
hir::FnRetTy::Return(output) => {
2574-
visitor.visit_ty(output);
2575-
self.ast_ty_to_ty(output)
2592+
if let hir::TyKind::Infer = output.kind
2593+
&& !self.allow_ty_infer()
2594+
&& let Some(suggested_ty) =
2595+
self.suggest_trait_fn_ty_for_impl_fn_infer(hir_id, None)
2596+
{
2597+
infer_replacements.push((output.span, suggested_ty.to_string()));
2598+
suggested_ty
2599+
} else {
2600+
visitor.visit_ty(output);
2601+
self.ast_ty_to_ty(output)
2602+
}
25762603
}
25772604
hir::FnRetTy::DefaultReturn(..) => tcx.mk_unit(),
25782605
};
25792606

25802607
debug!("ty_of_fn: output_ty={:?}", output_ty);
25812608

2582-
let fn_ty = tcx.mk_fn_sig(input_tys, output_ty, decl.c_variadic, unsafety, abi);
2609+
let fn_ty = tcx.mk_fn_sig(input_tys.into_iter(), output_ty, decl.c_variadic, unsafety, abi);
25832610
let bare_fn_ty = ty::Binder::bind_with_vars(fn_ty, bound_vars);
25842611

2585-
if !self.allow_ty_infer() {
2612+
if !self.allow_ty_infer() && !(visitor.0.is_empty() && infer_replacements.is_empty()) {
25862613
// We always collect the spans for placeholder types when evaluating `fn`s, but we
25872614
// only want to emit an error complaining about them if infer types (`_`) are not
25882615
// allowed. `allow_ty_infer` gates this behavior. We check for the presence of
25892616
// `ident_span` to not emit an error twice when we have `fn foo(_: fn() -> _)`.
25902617

2591-
crate::collect::placeholder_type_error(
2618+
let mut diag = crate::collect::placeholder_type_error_diag(
25922619
tcx,
25932620
ident_span.map(|sp| sp.shrink_to_hi()),
25942621
generics.params,
25952622
visitor.0,
2623+
infer_replacements.iter().map(|(s, _)| *s).collect(),
25962624
true,
25972625
hir_ty,
25982626
"function",
25992627
);
2628+
2629+
if !infer_replacements.is_empty() {
2630+
diag.multipart_suggestion(&format!(
2631+
"try replacing `_` with the type{} in the corresponding trait method signature",
2632+
rustc_errors::pluralize!(infer_replacements.len()),
2633+
), infer_replacements, Applicability::MachineApplicable);
2634+
}
2635+
2636+
diag.emit();
26002637
}
26012638

26022639
// Find any late-bound regions declared in return type that do
@@ -2624,6 +2661,43 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
26242661
bare_fn_ty
26252662
}
26262663

2664+
/// Given a fn_hir_id for a impl function, suggest the type that is found on the
2665+
/// corresponding function in the trait that the impl implements, if it exists.
2666+
/// If arg_idx is Some, then it corresponds to an input type index, otherwise it
2667+
/// corresponds to the return type.
2668+
fn suggest_trait_fn_ty_for_impl_fn_infer(
2669+
&self,
2670+
fn_hir_id: hir::HirId,
2671+
arg_idx: Option<usize>,
2672+
) -> Option<Ty<'tcx>> {
2673+
let tcx = self.tcx();
2674+
let hir = tcx.hir();
2675+
2676+
let hir::Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Fn(..), ident, .. }) =
2677+
hir.get(fn_hir_id) else { return None };
2678+
let hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(i), .. }) =
2679+
hir.get(hir.get_parent_node(fn_hir_id)) else { bug!("ImplItem should have Impl parent") };
2680+
2681+
let trait_ref =
2682+
self.instantiate_mono_trait_ref(i.of_trait.as_ref()?, self.ast_ty_to_ty(i.self_ty));
2683+
2684+
let x: &ty::AssocItem = tcx.associated_items(trait_ref.def_id).find_by_name_and_kind(
2685+
tcx,
2686+
*ident,
2687+
ty::AssocKind::Fn,
2688+
trait_ref.def_id,
2689+
)?;
2690+
2691+
let fn_sig = tcx.fn_sig(x.def_id).subst(
2692+
tcx,
2693+
trait_ref.substs.extend_to(tcx, x.def_id, |param, _| tcx.mk_param_from_def(param)),
2694+
);
2695+
2696+
let ty = if let Some(arg_idx) = arg_idx { fn_sig.input(arg_idx) } else { fn_sig.output() };
2697+
2698+
Some(tcx.erase_late_bound_regions(ty))
2699+
}
2700+
26272701
fn validate_late_bound_regions(
26282702
&self,
26292703
constrained_regions: FxHashSet<ty::BoundRegionKind>,

compiler/rustc_typeck/src/collect.rs

+107-45
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,33 @@ crate fn placeholder_type_error<'tcx>(
160160
return;
161161
}
162162

163+
placeholder_type_error_diag(
164+
tcx,
165+
span,
166+
generics,
167+
placeholder_types,
168+
vec![],
169+
suggest,
170+
hir_ty,
171+
kind,
172+
)
173+
.emit();
174+
}
175+
176+
crate fn placeholder_type_error_diag<'tcx>(
177+
tcx: TyCtxt<'tcx>,
178+
span: Option<Span>,
179+
generics: &[hir::GenericParam<'_>],
180+
placeholder_types: Vec<Span>,
181+
additional_spans: Vec<Span>,
182+
suggest: bool,
183+
hir_ty: Option<&hir::Ty<'_>>,
184+
kind: &'static str,
185+
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
186+
if placeholder_types.is_empty() {
187+
return bad_placeholder(tcx, additional_spans, kind);
188+
}
189+
163190
let type_name = generics.next_type_param_name(None);
164191
let mut sugg: Vec<_> =
165192
placeholder_types.iter().map(|sp| (*sp, (*type_name).to_string())).collect();
@@ -182,7 +209,8 @@ crate fn placeholder_type_error<'tcx>(
182209
sugg.push((span, format!(", {}", type_name)));
183210
}
184211

185-
let mut err = bad_placeholder(tcx, placeholder_types, kind);
212+
let mut err =
213+
bad_placeholder(tcx, placeholder_types.into_iter().chain(additional_spans).collect(), kind);
186214

187215
// Suggest, but only if it is not a function in const or static
188216
if suggest {
@@ -218,7 +246,8 @@ crate fn placeholder_type_error<'tcx>(
218246
);
219247
}
220248
}
221-
err.emit();
249+
250+
err
222251
}
223252

224253
fn reject_placeholder_type_signatures_in_item<'tcx>(
@@ -1868,50 +1897,17 @@ fn fn_sig(tcx: TyCtxt<'_>, def_id: DefId) -> ty::PolyFnSig<'_> {
18681897
generics,
18691898
..
18701899
})
1871-
| ImplItem(hir::ImplItem { kind: ImplItemKind::Fn(sig, _), ident, generics, .. })
18721900
| Item(hir::Item { kind: ItemKind::Fn(sig, generics, _), ident, .. }) => {
1873-
match get_infer_ret_ty(&sig.decl.output) {
1874-
Some(ty) => {
1875-
let fn_sig = tcx.typeck(def_id).liberated_fn_sigs()[hir_id];
1876-
// Typeck doesn't expect erased regions to be returned from `type_of`.
1877-
let fn_sig = tcx.fold_regions(fn_sig, &mut false, |r, _| match *r {
1878-
ty::ReErased => tcx.lifetimes.re_static,
1879-
_ => r,
1880-
});
1881-
let fn_sig = ty::Binder::dummy(fn_sig);
1882-
1883-
let mut visitor = HirPlaceholderCollector::default();
1884-
visitor.visit_ty(ty);
1885-
let mut diag = bad_placeholder(tcx, visitor.0, "return type");
1886-
let ret_ty = fn_sig.skip_binder().output();
1887-
if !ret_ty.references_error() {
1888-
if !ret_ty.is_closure() {
1889-
let ret_ty_str = match ret_ty.kind() {
1890-
// Suggest a function pointer return type instead of a unique function definition
1891-
// (e.g. `fn() -> i32` instead of `fn() -> i32 { f }`, the latter of which is invalid
1892-
// syntax)
1893-
ty::FnDef(..) => ret_ty.fn_sig(tcx).to_string(),
1894-
_ => ret_ty.to_string(),
1895-
};
1896-
diag.span_suggestion(
1897-
ty.span,
1898-
"replace with the correct return type",
1899-
ret_ty_str,
1900-
Applicability::MaybeIncorrect,
1901-
);
1902-
} else {
1903-
// We're dealing with a closure, so we should suggest using `impl Fn` or trait bounds
1904-
// to prevent the user from getting a papercut while trying to use the unique closure
1905-
// syntax (e.g. `[closure@src/lib.rs:2:5: 2:9]`).
1906-
diag.help("consider using an `Fn`, `FnMut`, or `FnOnce` trait bound");
1907-
diag.note("for more information on `Fn` traits and closure types, see https://doc.rust-lang.org/book/ch13-01-closures.html");
1908-
}
1909-
}
1910-
diag.emit();
1901+
infer_return_ty_for_fn_sig(tcx, sig, *ident, generics, def_id, &icx)
1902+
}
19111903

1912-
fn_sig
1913-
}
1914-
None => <dyn AstConv<'_>>::ty_of_fn(
1904+
ImplItem(hir::ImplItem { kind: ImplItemKind::Fn(sig, _), ident, generics, .. }) => {
1905+
// Do not try to inference the return type for a impl method coming from a trait
1906+
if let Item(hir::Item { kind: ItemKind::Impl(i), .. }) =
1907+
tcx.hir().get(tcx.hir().get_parent_node(hir_id))
1908+
&& i.of_trait.is_some()
1909+
{
1910+
<dyn AstConv<'_>>::ty_of_fn(
19151911
&icx,
19161912
hir_id,
19171913
sig.header.unsafety,
@@ -1920,7 +1916,9 @@ fn fn_sig(tcx: TyCtxt<'_>, def_id: DefId) -> ty::PolyFnSig<'_> {
19201916
generics,
19211917
Some(ident.span),
19221918
None,
1923-
),
1919+
)
1920+
} else {
1921+
infer_return_ty_for_fn_sig(tcx, sig, *ident, generics, def_id, &icx)
19241922
}
19251923
}
19261924

@@ -1982,6 +1980,70 @@ fn fn_sig(tcx: TyCtxt<'_>, def_id: DefId) -> ty::PolyFnSig<'_> {
19821980
}
19831981
}
19841982

1983+
fn infer_return_ty_for_fn_sig<'tcx>(
1984+
tcx: TyCtxt<'tcx>,
1985+
sig: &hir::FnSig<'_>,
1986+
ident: Ident,
1987+
generics: &hir::Generics<'_>,
1988+
def_id: LocalDefId,
1989+
icx: &ItemCtxt<'tcx>,
1990+
) -> ty::PolyFnSig<'tcx> {
1991+
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
1992+
1993+
match get_infer_ret_ty(&sig.decl.output) {
1994+
Some(ty) => {
1995+
let fn_sig = tcx.typeck(def_id).liberated_fn_sigs()[hir_id];
1996+
// Typeck doesn't expect erased regions to be returned from `type_of`.
1997+
let fn_sig = tcx.fold_regions(fn_sig, &mut false, |r, _| match *r {
1998+
ty::ReErased => tcx.lifetimes.re_static,
1999+
_ => r,
2000+
});
2001+
let fn_sig = ty::Binder::dummy(fn_sig);
2002+
2003+
let mut visitor = HirPlaceholderCollector::default();
2004+
visitor.visit_ty(ty);
2005+
let mut diag = bad_placeholder(tcx, visitor.0, "return type");
2006+
let ret_ty = fn_sig.skip_binder().output();
2007+
if !ret_ty.references_error() {
2008+
if !ret_ty.is_closure() {
2009+
let ret_ty_str = match ret_ty.kind() {
2010+
// Suggest a function pointer return type instead of a unique function definition
2011+
// (e.g. `fn() -> i32` instead of `fn() -> i32 { f }`, the latter of which is invalid
2012+
// syntax)
2013+
ty::FnDef(..) => ret_ty.fn_sig(tcx).to_string(),
2014+
_ => ret_ty.to_string(),
2015+
};
2016+
diag.span_suggestion(
2017+
ty.span,
2018+
"replace with the correct return type",
2019+
ret_ty_str,
2020+
Applicability::MaybeIncorrect,
2021+
);
2022+
} else {
2023+
// We're dealing with a closure, so we should suggest using `impl Fn` or trait bounds
2024+
// to prevent the user from getting a papercut while trying to use the unique closure
2025+
// syntax (e.g. `[closure@src/lib.rs:2:5: 2:9]`).
2026+
diag.help("consider using an `Fn`, `FnMut`, or `FnOnce` trait bound");
2027+
diag.note("for more information on `Fn` traits and closure types, see https://doc.rust-lang.org/book/ch13-01-closures.html");
2028+
}
2029+
}
2030+
diag.emit();
2031+
2032+
fn_sig
2033+
}
2034+
None => <dyn AstConv<'_>>::ty_of_fn(
2035+
icx,
2036+
hir_id,
2037+
sig.header.unsafety,
2038+
sig.header.abi,
2039+
sig.decl,
2040+
generics,
2041+
Some(ident.span),
2042+
None,
2043+
),
2044+
}
2045+
}
2046+
19852047
fn impl_trait_ref(tcx: TyCtxt<'_>, def_id: DefId) -> Option<ty::TraitRef<'_>> {
19862048
let icx = ItemCtxt::new(tcx, def_id);
19872049
match tcx.hir().expect_item(def_id.expect_local()).kind {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
4+
trait Foo<T>: Sized {
5+
fn bar(i: i32, t: T, s: &Self) -> (T, i32);
6+
}
7+
8+
impl Foo<usize> for () {
9+
fn bar(i: i32, t: usize, s: &()) -> (usize, i32) {
10+
//~^ ERROR the placeholder `_` is not allowed within types on item signatures for functions
11+
(1, 2)
12+
}
13+
}
14+
15+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
4+
trait Foo<T>: Sized {
5+
fn bar(i: i32, t: T, s: &Self) -> (T, i32);
6+
}
7+
8+
impl Foo<usize> for () {
9+
fn bar(i: _, t: _, s: _) -> _ {
10+
//~^ ERROR the placeholder `_` is not allowed within types on item signatures for functions
11+
(1, 2)
12+
}
13+
}
14+
15+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error[E0121]: the placeholder `_` is not allowed within types on item signatures for functions
2+
--> $DIR/replace-impl-infer-ty-from-trait.rs:9:15
3+
|
4+
LL | fn bar(i: _, t: _, s: _) -> _ {
5+
| ^ ^ ^ ^ not allowed in type signatures
6+
| | | |
7+
| | | not allowed in type signatures
8+
| | not allowed in type signatures
9+
| not allowed in type signatures
10+
|
11+
help: try replacing `_` with the types in the corresponding trait method signature
12+
|
13+
LL | fn bar(i: i32, t: usize, s: &()) -> (usize, i32) {
14+
| ~~~ ~~~~~ ~~~ ~~~~~~~~~~~~
15+
16+
error: aborting due to previous error
17+
18+
For more information about this error, try `rustc --explain E0121`.

src/test/ui/typeck/typeck_type_placeholder_item.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ unsafe fn test12(x: *const usize) -> *const *const _ {
5757

5858
impl Clone for Test9 {
5959
fn clone(&self) -> _ { Test9 }
60-
//~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types
60+
//~^ ERROR the placeholder `_` is not allowed within types on item signatures for functions
6161

6262
fn clone_from(&mut self, other: _) { *self = Test9; }
6363
//~^ ERROR the placeholder `_` is not allowed within types on item signatures for functions
@@ -113,7 +113,7 @@ pub fn main() {
113113

114114
impl Clone for FnTest9 {
115115
fn clone(&self) -> _ { FnTest9 }
116-
//~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types
116+
//~^ ERROR the placeholder `_` is not allowed within types on item signatures for functions
117117

118118
fn clone_from(&mut self, other: _) { *self = FnTest9; }
119119
//~^ ERROR the placeholder `_` is not allowed within types on item signatures for functions

0 commit comments

Comments
 (0)