Skip to content

Improve type parameter suggestion heuristic for missing types #140073

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2764,14 +2764,39 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {

let (msg, sugg) = match source {
PathSource::Type | PathSource::PreciseCapturingArg(TypeNS) => {
("you might be missing a type parameter", ident)
("you might be missing a type parameter", ident.clone())
}
PathSource::Expr(_) | PathSource::PreciseCapturingArg(ValueNS) => (
"you might be missing a const parameter",
format!("const {ident}: /* Type */"),
),
_ => return None,
};

// Heuristically determine whether a type name is likely intended to be a generic.
//
// We apply three rules:
// 1. Short names (like `T`, `U`, `E`) are common for generics.
// 2. Certain well-known names (e.g., `Item`, `Output`, `Error`) are commonly used as generics.
// 3. Names in UpperCamelCase are more likely to be concrete types.
//
// This approach may produce false positives or negatives, but works well in most cases.

let common_generic_names: &[&str] = &[
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let common_generic_names: &[&str] = &[
let common_type_param_names: &[&str] = &[

since it doesn't make sense for const params

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'd like to draw attention to the

    && segment.ident.name != kw::SelfUpper
    && segment.ident.name != kw::Dyn =>

at the very start of the function. We're already filtering out certain identifiers (in this case Self and dyn). I'd like us to unify this check and the common_generic_names one is some way or another

Copy link
Contributor Author

@Kivooeo Kivooeo Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is good point, will it be better to move my logic right after this let (ident, span) = ... or integrate them inside the match?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what @fmease might expect is a change like...

        let [segment] = segments else {
               return None;
         };

          // comment here explaining why we skip these
         let skip_names = [kw::SelfUpper, kw::Dyn, sym::This, sym::Error, /* etc */];

         if !segment.has_generic_args && !skip_names.contains(segment.ident.name) {
                 return None;
         }
         let ident = segment.ident.to_string();
         let span =  segment.ident.span;

I personally much prefer let_else over a match in this case. Feel free to write it with a match if you prefer.

Also, not all these names are available as symbols in sym. Feel free to add them there as needed,

"Item", "Output", "Error", "Target", "Value", "Args",
"Res", "Ret", "This", "Iter", "Type",
];
let is_common_generic = common_generic_names.contains(&&*ident);
let looks_like_camel_case = ident
.chars()
.next()
.is_some_and(|c| c.is_uppercase())
&& ident.chars().skip(1).any(|c| c.is_lowercase());

// If it's not a known generic name and looks like a concrete type, skip the suggestion.
if !is_common_generic && looks_like_camel_case && ident.len() > 3 {
return None;
}
let (span, sugg) = if let [.., param] = &generics.params[..] {
let span = if let [.., bound] = &param.bounds[..] {
bound.span()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ error[E0425]: cannot find value `HashesEntryLEN` in this scope
|
LL | struct EntriesBuffer(Box<[[u8; HashesEntryLEN]; 5]>);
| ^^^^^^^^^^^^^^ not found in this scope
|
help: you might be missing a const parameter
|
LL | struct EntriesBuffer<const HashesEntryLEN: /* Type */>(Box<[[u8; HashesEntryLEN]; 5]>);
| ++++++++++++++++++++++++++++++++++

error: aborting due to 1 previous error

Expand Down
5 changes: 0 additions & 5 deletions tests/ui/consts/error-is-freeze.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ error[E0412]: cannot find type `UndefinedType` in this scope
|
LL | foo: Option<UndefinedType>,
| ^^^^^^^^^^^^^ not found in this scope
|
help: you might be missing a type parameter
|
LL | struct MyStruct<UndefinedType> {
| +++++++++++++++

error: aborting due to 1 previous error

Expand Down
5 changes: 0 additions & 5 deletions tests/ui/issues/issue-58712.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ error[E0412]: cannot find type `DeviceId` in this scope
|
LL | impl<H> AddrVec<H, DeviceId> {
| ^^^^^^^^ not found in this scope
|
help: you might be missing a type parameter
|
LL | impl<H, DeviceId> AddrVec<H, DeviceId> {
| ++++++++++

error[E0412]: cannot find type `DeviceId` in this scope
--> $DIR/issue-58712.rs:8:29
Expand Down
5 changes: 0 additions & 5 deletions tests/ui/layout/thaw-transmute-invalid-enum.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ error[E0412]: cannot find type `Subset` in this scope
|
LL | assert::is_transmutable::<Superset, Subset>();
| ^^^^^^ not found in this scope
|
help: you might be missing a type parameter
|
LL | fn test<Subset>() {
| ++++++++

error[E0517]: attribute should be applied to a struct or union
--> $DIR/thaw-transmute-invalid-enum.rs:21:11
Expand Down
17 changes: 16 additions & 1 deletion tests/ui/missing/missing-items/missing-type-parameter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
fn foo<X>() { }
fn foo<X>() {}

fn do_something() -> Option<ForgotToImport> {
//~^ cannot find type `ForgotToImport` in this scope [E0412]
None
}

fn do_something_T() -> Option<T> {
//~^ cannot find type `T` in this scope [E0412]
None
}

fn do_something_Type() -> Option<Type> {
//~^ cannot find type `Type` in this scope [E0412]
None
}

fn main() {
foo(); //~ ERROR type annotations needed
Expand Down
35 changes: 32 additions & 3 deletions tests/ui/missing/missing-items/missing-type-parameter.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
error[E0412]: cannot find type `ForgotToImport` in this scope
--> $DIR/missing-type-parameter.rs:3:29
|
LL | fn do_something() -> Option<ForgotToImport> {
| ^^^^^^^^^^^^^^ not found in this scope

error[E0412]: cannot find type `T` in this scope
--> $DIR/missing-type-parameter.rs:8:31
|
LL | fn do_something_T() -> Option<T> {
| ^ not found in this scope
|
help: you might be missing a type parameter
|
LL | fn do_something_T<T>() -> Option<T> {
| +++

error[E0412]: cannot find type `Type` in this scope
--> $DIR/missing-type-parameter.rs:13:34
|
LL | fn do_something_Type() -> Option<Type> {
| ^^^^ not found in this scope
|
help: you might be missing a type parameter
|
LL | fn do_something_Type<Type>() -> Option<Type> {
| ++++++

error[E0282]: type annotations needed
--> $DIR/missing-type-parameter.rs:4:5
--> $DIR/missing-type-parameter.rs:19:5
|
LL | foo();
| ^^^ cannot infer type of the type parameter `X` declared on the function `foo`
Expand All @@ -9,6 +37,7 @@ help: consider specifying the generic argument
LL | foo::<X>();
| +++++

error: aborting due to 1 previous error
error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0282`.
Some errors have detailed explanations: E0282, E0412.
For more information about an error, try `rustc --explain E0282`.
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ error[E0412]: cannot find type `NonExistent` in this scope
LL | struct Foo(NonExistent);
| ^^^^^^^^^^^ not found in this scope
|
help: you might be missing a type parameter
|
LL | struct Foo<NonExistent>(NonExistent);
| +++++++++++++
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error[E0658]: `impl Trait` in type aliases is unstable
--> $DIR/issue-119493-type-error-ice.rs:9:14
Expand Down
5 changes: 1 addition & 4 deletions tests/ui/traits/issue-50480.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ error[E0412]: cannot find type `NotDefined` in this scope
LL | struct Foo(N, NotDefined, <i32 as Iterator>::Item, Vec<i32>, String);
| ^^^^^^^^^^ not found in this scope
|
help: you might be missing a type parameter
|
LL | struct Foo<NotDefined>(N, NotDefined, <i32 as Iterator>::Item, Vec<i32>, String);
| ++++++++++++
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error[E0412]: cannot find type `N` in this scope
--> $DIR/issue-50480.rs:14:18
Expand Down
5 changes: 0 additions & 5 deletions tests/ui/traits/trait-selection-ice-84727.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ error[E0412]: cannot find type `NewBg` in this scope
|
LL | fn over(self) -> Cell<NewBg> {
| ^^^^^ not found in this scope
|
help: you might be missing a type parameter
|
LL | impl<'b, TopFg, TopBg, BottomFg, BottomBg, NewBg> Over<&Cell<BottomFg, BottomBg>, ()>
| +++++++

error[E0308]: mismatched types
--> $DIR/trait-selection-ice-84727.rs:21:22
Expand Down
Loading