Skip to content

Commit

Permalink
Rollup merge of rust-lang#135965 - estebank:shorten-ty-sugg, r=lcnr
Browse files Browse the repository at this point in the history
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<T, _> = Err(x);
   |          ++++++++++++++
```

Fix rust-lang#135919.
  • Loading branch information
matthiaskrgr authored Feb 11, 2025
2 parents 1bae528 + 576db13 commit 147951e
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ 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};

use super::nice_region_error::placeholder_error::Highlighted;
Expand Down Expand Up @@ -155,27 +157,92 @@ impl UnderspecifiedArgKind {
}
}

struct ClosureEraser<'tcx> {
tcx: TyCtxt<'tcx>,
struct ClosureEraser<'a, 'tcx> {
infcx: &'a InferCtxt<'tcx>,
}

impl<'tcx> TypeFolder<TyCtxt<'tcx>> 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<TyCtxt<'tcx>> 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() {
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(_, 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());
let generics: Vec<bool> = 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() => {
// 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.
ty.super_fold_with(self)
}
// We don't have an unknown type parameter anywhere, replace with `_`.
_ => self.new_infer(),
}
}

fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> {
// Avoid accidentally erasing the type of the const.
c
}
}

fn fmt_printer<'a, 'tcx>(infcx: &'a InferCtxt<'tcx>, ns: Namespace) -> FmtPrinter<'a, 'tcx> {
Expand Down Expand Up @@ -219,9 +286,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 });

match (ty.kind(), called_method_def_id) {
// We don't want the regular output for `fn`s because it includes its path in
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/inference/cannot-infer-closure-circular.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions tests/ui/inference/erase-type-params-in-label.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ LL | fn foo<T, K, W: Default, Z: Default>(t: T, k: K) -> Foo<T, K, W, Z> {
| ^^^^^^^ 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<i32, &str, W, Z> = foo(1, "");
| ++++++++++++++++++++++
LL | let foo: Foo<_, &_, W, Z> = foo(1, "");
| ++++++++++++++++++

error[E0283]: type annotations needed for `Bar<i32, &str, _>`
--> $DIR/erase-type-params-in-label.rs:5:9
Expand All @@ -29,8 +29,8 @@ LL | fn bar<T, K, Z: Default>(t: T, k: K) -> Bar<T, K, Z> {
| ^^^^^^^ 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<i32, &str, Z> = bar(1, "");
| +++++++++++++++++++
LL | let bar: Bar<_, &_, Z> = bar(1, "");
| +++++++++++++++

error: aborting due to 2 previous errors

Expand Down
4 changes: 2 additions & 2 deletions tests/ui/inference/issue-104649.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::result::Result<std::result::Result<(), E>, Error>> = A(Result::Ok(Result::Ok(())));
| +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
LL | let a: A<std::result::Result<std::result::Result<_, E>, _>> = A(Result::Ok(Result::Ok(())));
| ++++++++++++++++++++++++++++++++++++++++++++++++++++++

error: aborting due to 1 previous error

Expand Down
4 changes: 2 additions & 2 deletions tests/ui/inference/issue-83606.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ LL | fn foo<const N: usize>(_: 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

Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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<T, _> = Err(x);
| ++++++++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0282`.
4 changes: 2 additions & 2 deletions tests/ui/type-inference/or_else-multiple-type-params.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ LL | .or_else(|err| {
|
help: try giving this closure an explicit return type
|
LL | .or_else(|err| -> Result<Child, F> {
| +++++++++++++++++++
LL | .or_else(|err| -> Result<_, F> {
| +++++++++++++++

error: aborting due to 1 previous error

Expand Down

0 comments on commit 147951e

Please sign in to comment.