From 6005619bde6abacfdce096d1f67437323b63d8dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 24 Jan 2025 01:06:14 +0000 Subject: [PATCH 1/2] In "specify type" suggestion, skip type params that are already known When we suggest specifying a type for an expression or pattern, like in a `let` binding, we previously would print the entire type as the type system knew it. We now look at the params that have *no* inference variables, so they are fully known to the type system which means that they don't need to be specified. This helps in suggestions for types that are really long, because we can usually skip most of the type params and make the annotation as short as possible: ``` error[E0282]: type annotations needed for `Result<_, ((..., ..., ..., ...), ..., ..., ...)>` --> $DIR/really-long-type-in-let-binding-without-sufficient-type-info.rs:7:9 | LL | let y = Err(x); | ^ ------ type must be known at this point | help: consider giving `y` an explicit type, where the type for type parameter `T` is specified | LL | let y: Result = Err(x); | ++++++++++++++ ``` --- .../error_reporting/infer/need_type_info.rs | 84 ++++++++++++++++--- .../cannot-infer-closure-circular.stderr | 4 +- .../erase-type-params-in-label.stderr | 8 +- tests/ui/inference/issue-104649.stderr | 4 +- tests/ui/inference/issue-83606.stderr | 4 +- ...et-binding-without-sufficient-type-info.rs | 10 +++ ...inding-without-sufficient-type-info.stderr | 14 ++++ .../or_else-multiple-type-params.stderr | 4 +- 8 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 tests/ui/inference/really-long-type-in-let-binding-without-sufficient-type-info.rs create mode 100644 tests/ui/inference/really-long-type-in-let-binding-without-sufficient-type-info.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs index 99b70c87ccd1c..61fb7bcbc9aaa 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs @@ -18,6 +18,7 @@ use rustc_middle::ty::{ TypeFoldable, TypeFolder, TypeSuperFoldable, TypeckResults, }; use rustc_span::{BytePos, DUMMY_SP, FileName, Ident, Span, sym}; +use rustc_type_ir::visit::TypeVisitableExt; use tracing::{debug, instrument, warn}; use super::nice_region_error::placeholder_error::Highlighted; @@ -155,26 +156,85 @@ impl UnderspecifiedArgKind { } } -struct ClosureEraser<'tcx> { - tcx: TyCtxt<'tcx>, +struct ClosureEraser<'a, 'tcx> { + infcx: &'a InferCtxt<'tcx>, + // When recursing into types, if an ADT has type parameters with a default type we do *not* + // want to replace that type parameter with `_`, as it will cause the normally hidden type + // parameter to be rendered. The best example of this is `Vec`, which we want to + // render as `Vec` and not `Vec` when `T` is unknown. + do_not_hide_nested_type: bool, } -impl<'tcx> TypeFolder> for ClosureEraser<'tcx> { +impl<'a, 'tcx> ClosureEraser<'a, 'tcx> { + fn new_infer(&mut self) -> Ty<'tcx> { + self.infcx.next_ty_var(DUMMY_SP) + } +} + +impl<'a, 'tcx> TypeFolder> for ClosureEraser<'a, 'tcx> { fn cx(&self) -> TyCtxt<'tcx> { - self.tcx + self.infcx.tcx } fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { - match ty.kind() { + let prev = self.do_not_hide_nested_type; + let ty = match ty.kind() { ty::Closure(_, args) => { + // For a closure type, we turn it into a function pointer so that it gets rendered + // as `fn(args) -> Ret`. let closure_sig = args.as_closure().sig(); Ty::new_fn_ptr( - self.tcx, - self.tcx.signature_unclosure(closure_sig, hir::Safety::Safe), + self.cx(), + self.cx().signature_unclosure(closure_sig, hir::Safety::Safe), ) } - _ => ty.super_fold_with(self), - } + ty::Adt(def, _) => { + let generics = self.cx().generics_of(def.did()); + if generics.own_params.iter().any(|param| param.default_value(self.cx()).is_some()) + { + // We have a type that has default types, like the allocator in Vec. We decided + // to show `Vec` itself, because it hasn't yet been replaced by an `_` `Infer`, + // but we want to ensure that the type parameter with default types does *not* + // get replaced with `_` because then we'd end up with `Vec<_, _>`, instead of + // `Vec<_>`. + self.do_not_hide_nested_type = true; + ty.super_fold_with(self) + } else if ty.has_infer() || self.do_not_hide_nested_type { + // This type has an unsubstituted type variable, meaning that this type has a + // (potentially deeply nested) type parameter from the corresponding type's + // definition. We have explicitly asked this type to not be hidden. In either + // case, we keep the type and don't substitute with `_` just yet. + ty.super_fold_with(self) + } else { + // When we have a type that doesn't have any inference variables, so we replace + // the whole thing with `_`. The type system already knows about this type in + // its entirety and it is redundant to specify it for the user. The user only + // needs to specify the type parameters that we *couldn't* figure out. + self.new_infer() + } + } + _ if ty.has_infer() || self.do_not_hide_nested_type => { + // This type has a (potentially nested) type parameter that we couldn't figure out. + // We will print this depth of type, so at least the type name and at least one of + // its type parameters. We unset `do_not_hide_nested_type` because this type can't + // have type parameter defaults until next type we hit an ADT. + self.do_not_hide_nested_type = false; + ty.super_fold_with(self) + } + // We don't have an unknown type parameter anywhere, replace with `_`. + _ => self.new_infer(), + }; + self.do_not_hide_nested_type = prev; + ty + } + + fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> { + let prev = self.do_not_hide_nested_type; + // Avoid accidentally erasing the type of the const. + self.do_not_hide_nested_type = true; + let c = c.super_fold_with(self); + self.do_not_hide_nested_type = prev; + c } } @@ -219,9 +279,9 @@ fn ty_to_string<'tcx>( ) -> String { let mut printer = fmt_printer(infcx, Namespace::TypeNS); let ty = infcx.resolve_vars_if_possible(ty); - // We use `fn` ptr syntax for closures, but this only works when the closure - // does not capture anything. - let ty = ty.fold_with(&mut ClosureEraser { tcx: infcx.tcx }); + // We use `fn` ptr syntax for closures, but this only works when the closure does not capture + // anything. We also remove all type parameters that are fully known to the type system. + let ty = ty.fold_with(&mut ClosureEraser { infcx, do_not_hide_nested_type: false }); match (ty.kind(), called_method_def_id) { // We don't want the regular output for `fn`s because it includes its path in diff --git a/tests/ui/inference/cannot-infer-closure-circular.stderr b/tests/ui/inference/cannot-infer-closure-circular.stderr index a16e832f8ef91..ee17f7737cf30 100644 --- a/tests/ui/inference/cannot-infer-closure-circular.stderr +++ b/tests/ui/inference/cannot-infer-closure-circular.stderr @@ -9,8 +9,8 @@ LL | Ok(v) | help: consider giving this closure parameter an explicit type, where the type for type parameter `E` is specified | -LL | let x = |r: Result<(), E>| { - | +++++++++++++++ +LL | let x = |r: Result<_, E>| { + | ++++++++++++++ error: aborting due to 1 previous error diff --git a/tests/ui/inference/erase-type-params-in-label.stderr b/tests/ui/inference/erase-type-params-in-label.stderr index 4e9a74c1e403d..1ec8a33eb8205 100644 --- a/tests/ui/inference/erase-type-params-in-label.stderr +++ b/tests/ui/inference/erase-type-params-in-label.stderr @@ -12,8 +12,8 @@ LL | fn foo(t: T, k: K) -> Foo { | ^^^^^^^ required by this bound in `foo` help: consider giving `foo` an explicit type, where the type for type parameter `W` is specified | -LL | let foo: Foo = foo(1, ""); - | ++++++++++++++++++++++ +LL | let foo: Foo<_, &_, W, Z> = foo(1, ""); + | ++++++++++++++++++ error[E0283]: type annotations needed for `Bar` --> $DIR/erase-type-params-in-label.rs:5:9 @@ -29,8 +29,8 @@ LL | fn bar(t: T, k: K) -> Bar { | ^^^^^^^ required by this bound in `bar` help: consider giving `bar` an explicit type, where the type for type parameter `Z` is specified | -LL | let bar: Bar = bar(1, ""); - | +++++++++++++++++++ +LL | let bar: Bar<_, &_, Z> = bar(1, ""); + | +++++++++++++++ error: aborting due to 2 previous errors diff --git a/tests/ui/inference/issue-104649.stderr b/tests/ui/inference/issue-104649.stderr index 391ed16f349d7..27382e301341f 100644 --- a/tests/ui/inference/issue-104649.stderr +++ b/tests/ui/inference/issue-104649.stderr @@ -6,8 +6,8 @@ LL | let a = A(Result::Ok(Result::Ok(()))); | help: consider giving `a` an explicit type, where the type for type parameter `E` is specified | -LL | let a: A, Error>> = A(Result::Ok(Result::Ok(()))); - | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +LL | let a: A, _>> = A(Result::Ok(Result::Ok(()))); + | ++++++++++++++++++++++++++++++++++++++++++++++++++++++ error: aborting due to 1 previous error diff --git a/tests/ui/inference/issue-83606.stderr b/tests/ui/inference/issue-83606.stderr index 69d1d71ef3cc0..97ccad9e785e3 100644 --- a/tests/ui/inference/issue-83606.stderr +++ b/tests/ui/inference/issue-83606.stderr @@ -11,8 +11,8 @@ LL | fn foo(_: impl std::fmt::Display) -> [usize; N] { | ^^^^^^^^^^^^^^ required by this const generic parameter in `foo` help: consider giving this pattern a type, where the value of const parameter `N` is specified | -LL | let _: [usize; N] = foo("foo"); - | ++++++++++++ +LL | let _: [_; N] = foo("foo"); + | ++++++++ error: aborting due to 1 previous error diff --git a/tests/ui/inference/really-long-type-in-let-binding-without-sufficient-type-info.rs b/tests/ui/inference/really-long-type-in-let-binding-without-sufficient-type-info.rs new file mode 100644 index 0000000000000..4fd15eea9e0f1 --- /dev/null +++ b/tests/ui/inference/really-long-type-in-let-binding-without-sufficient-type-info.rs @@ -0,0 +1,10 @@ +type A = (i32, i32, i32, i32); +type B = (A, A, A, A); +type C = (B, B, B, B); +type D = (C, C, C, C); + +fn foo(x: D) { + let y = Err(x); //~ ERROR type annotations needed for `Result<_ +} + +fn main() {} diff --git a/tests/ui/inference/really-long-type-in-let-binding-without-sufficient-type-info.stderr b/tests/ui/inference/really-long-type-in-let-binding-without-sufficient-type-info.stderr new file mode 100644 index 0000000000000..65fe2ffcb7f16 --- /dev/null +++ b/tests/ui/inference/really-long-type-in-let-binding-without-sufficient-type-info.stderr @@ -0,0 +1,14 @@ +error[E0282]: type annotations needed for `Result<_, ((((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32))), (((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32))), (((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32))), (((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32)), ((i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32), (i32, i32, i32, i32))))>` + --> $DIR/really-long-type-in-let-binding-without-sufficient-type-info.rs:7:9 + | +LL | let y = Err(x); + | ^ ------ type must be known at this point + | +help: consider giving `y` an explicit type, where the type for type parameter `T` is specified + | +LL | let y: Result = Err(x); + | ++++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0282`. diff --git a/tests/ui/type-inference/or_else-multiple-type-params.stderr b/tests/ui/type-inference/or_else-multiple-type-params.stderr index 3176a2d490ebc..9bcd07f8bf164 100644 --- a/tests/ui/type-inference/or_else-multiple-type-params.stderr +++ b/tests/ui/type-inference/or_else-multiple-type-params.stderr @@ -6,8 +6,8 @@ LL | .or_else(|err| { | help: try giving this closure an explicit return type | -LL | .or_else(|err| -> Result { - | +++++++++++++++++++ +LL | .or_else(|err| -> Result<_, F> { + | +++++++++++++++ error: aborting due to 1 previous error From 576db131a38ccb53f9fbbe314e6bce37503c0050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 30 Jan 2025 18:26:31 +0000 Subject: [PATCH 2/2] Simplify recursive logic --- .../error_reporting/infer/need_type_info.rs | 91 ++++++++++--------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs index 61fb7bcbc9aaa..9e7e96dddd7c1 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs @@ -18,6 +18,7 @@ use rustc_middle::ty::{ TypeFoldable, TypeFolder, TypeSuperFoldable, TypeckResults, }; use rustc_span::{BytePos, DUMMY_SP, FileName, Ident, Span, sym}; +use rustc_type_ir::inherent::*; use rustc_type_ir::visit::TypeVisitableExt; use tracing::{debug, instrument, warn}; @@ -158,11 +159,6 @@ impl UnderspecifiedArgKind { struct ClosureEraser<'a, 'tcx> { infcx: &'a InferCtxt<'tcx>, - // When recursing into types, if an ADT has type parameters with a default type we do *not* - // want to replace that type parameter with `_`, as it will cause the normally hidden type - // parameter to be rendered. The best example of this is `Vec`, which we want to - // render as `Vec` and not `Vec` when `T` is unknown. - do_not_hide_nested_type: bool, } impl<'a, 'tcx> ClosureEraser<'a, 'tcx> { @@ -177,8 +173,7 @@ impl<'a, 'tcx> TypeFolder> for ClosureEraser<'a, 'tcx> { } fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { - let prev = self.do_not_hide_nested_type; - let ty = match ty.kind() { + match ty.kind() { ty::Closure(_, args) => { // For a closure type, we turn it into a function pointer so that it gets rendered // as `fn(args) -> Ret`. @@ -188,52 +183,64 @@ impl<'a, 'tcx> TypeFolder> for ClosureEraser<'a, 'tcx> { self.cx().signature_unclosure(closure_sig, hir::Safety::Safe), ) } - ty::Adt(def, _) => { + ty::Adt(_, args) if !args.iter().any(|a| a.has_infer()) => { + // We have a type that doesn't have any inference variables, so we replace + // the whole thing with `_`. The type system already knows about this type in + // its entirety and it is redundant to specify it for the user. The user only + // needs to specify the type parameters that we *couldn't* figure out. + self.new_infer() + } + ty::Adt(def, args) => { let generics = self.cx().generics_of(def.did()); - if generics.own_params.iter().any(|param| param.default_value(self.cx()).is_some()) - { - // We have a type that has default types, like the allocator in Vec. We decided - // to show `Vec` itself, because it hasn't yet been replaced by an `_` `Infer`, - // but we want to ensure that the type parameter with default types does *not* - // get replaced with `_` because then we'd end up with `Vec<_, _>`, instead of - // `Vec<_>`. - self.do_not_hide_nested_type = true; - ty.super_fold_with(self) - } else if ty.has_infer() || self.do_not_hide_nested_type { - // This type has an unsubstituted type variable, meaning that this type has a - // (potentially deeply nested) type parameter from the corresponding type's - // definition. We have explicitly asked this type to not be hidden. In either - // case, we keep the type and don't substitute with `_` just yet. - ty.super_fold_with(self) - } else { - // When we have a type that doesn't have any inference variables, so we replace - // the whole thing with `_`. The type system already knows about this type in - // its entirety and it is redundant to specify it for the user. The user only - // needs to specify the type parameters that we *couldn't* figure out. - self.new_infer() - } + let generics: Vec = generics + .own_params + .iter() + .map(|param| param.default_value(self.cx()).is_some()) + .collect(); + let ty = Ty::new_adt( + self.cx(), + *def, + self.cx().mk_args_from_iter(generics.into_iter().zip(args.iter()).map( + |(has_default, arg)| { + if arg.has_infer() { + // This param has an unsubstituted type variable, meaning that this + // type has a (potentially deeply nested) type parameter from the + // corresponding type's definition. We have explicitly asked this + // type to not be hidden. In either case, we keep the type and don't + // substitute with `_` just yet. + arg.fold_with(self) + } else if has_default { + // We have a type param that has a default type, like the allocator + // in Vec. We decided to show `Vec` itself, because it hasn't yet + // been replaced by an `_` `Infer`, but we want to ensure that the + // type parameter with default types does *not* get replaced with + // `_` because then we'd end up with `Vec<_, _>`, instead of + // `Vec<_>`. + arg + } else if let GenericArgKind::Type(_) = arg.kind() { + // We don't replace lifetime or const params, only type params. + self.new_infer().into() + } else { + arg.fold_with(self) + } + }, + )), + ); + ty } - _ if ty.has_infer() || self.do_not_hide_nested_type => { + _ if ty.has_infer() => { // This type has a (potentially nested) type parameter that we couldn't figure out. // We will print this depth of type, so at least the type name and at least one of - // its type parameters. We unset `do_not_hide_nested_type` because this type can't - // have type parameter defaults until next type we hit an ADT. - self.do_not_hide_nested_type = false; + // its type parameters. ty.super_fold_with(self) } // We don't have an unknown type parameter anywhere, replace with `_`. _ => self.new_infer(), - }; - self.do_not_hide_nested_type = prev; - ty + } } fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> { - let prev = self.do_not_hide_nested_type; // Avoid accidentally erasing the type of the const. - self.do_not_hide_nested_type = true; - let c = c.super_fold_with(self); - self.do_not_hide_nested_type = prev; c } } @@ -281,7 +288,7 @@ fn ty_to_string<'tcx>( let ty = infcx.resolve_vars_if_possible(ty); // We use `fn` ptr syntax for closures, but this only works when the closure does not capture // anything. We also remove all type parameters that are fully known to the type system. - let ty = ty.fold_with(&mut ClosureEraser { infcx, do_not_hide_nested_type: false }); + let ty = ty.fold_with(&mut ClosureEraser { infcx }); match (ty.kind(), called_method_def_id) { // We don't want the regular output for `fn`s because it includes its path in