Skip to content

Commit

Permalink
Auto merge of rust-lang#95379 - icewind1991:suggest-associated-type-m…
Browse files Browse the repository at this point in the history
…ore, r=jackh726

show suggestion to replace generic bounds with associated types in more cases

Moves the hint to replace generic parameters with associated type bounds from the "not all associated type bounds are specified"(`E0191`) to "to many generic type parameters provided"(`E0107`).

Since `E0191` is only emitted in places where all associated types must be specified (when creating `dyn` types), the suggesting is currently not shown for other generic type uses (such as in generic type bounds). With this change the suggesting is always emitted when the number of excess generic parameters matches the number of unbound associated types.

Main motivation for the change was a lack of useful suggesting when doing

```rust
fn foo<I: Iterator<usize>>(i: I) {}
```
  • Loading branch information
bors committed Apr 19, 2022
2 parents d5ae66c + decc04d commit e2661ba
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 24 deletions.
18 changes: 6 additions & 12 deletions compiler/rustc_typeck/src/astconv/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use rustc_span::symbol::{sym, Ident};
use rustc_span::{Span, DUMMY_SP};

use std::collections::BTreeSet;
use std::iter;

impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
/// On missing type parameters, emit an E0393 error and provide a structured suggestion using
Expand Down Expand Up @@ -323,6 +322,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
let mut suggestions = vec![];
let mut types_count = 0;
let mut where_constraints = vec![];
let mut already_has_generics_args_suggestion = false;
for (span, assoc_items) in &associated_types {
let mut names: FxHashMap<_, usize> = FxHashMap::default();
for item in assoc_items {
Expand All @@ -343,16 +343,10 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
}
}
if potential_assoc_types.len() == assoc_items.len() {
// Only suggest when the amount of missing associated types equals the number of
// extra type arguments present, as that gives us a relatively high confidence
// that the user forgot to give the associated type's name. The canonical
// example would be trying to use `Iterator<isize>` instead of
// `Iterator<Item = isize>`.
for (potential, item) in iter::zip(&potential_assoc_types, assoc_items) {
if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(*potential) {
suggestions.push((*potential, format!("{} = {}", item.name, snippet)));
}
}
// When the amount of missing associated types equals the number of
// extra type arguments present. A suggesting to replace the generic args with
// associated types is already emitted.
already_has_generics_args_suggestion = true;
} else if let (Ok(snippet), false) =
(tcx.sess.source_map().span_to_snippet(*span), dupes)
{
Expand Down Expand Up @@ -382,7 +376,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
// the same associated type name.
err.help(where_msg);
}
if suggestions.len() != 1 {
if suggestions.len() != 1 || already_has_generics_args_suggestion {
// We don't need this label if there's an inline suggestion, show otherwise.
for (span, assoc_items) in &associated_types {
let mut names: FxHashMap<_, usize> = FxHashMap::default();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use rustc_errors::{
use rustc_hir as hir;
use rustc_middle::hir::map::fn_sig;
use rustc_middle::middle::resolve_lifetime::LifetimeScopeForPath;
use rustc_middle::ty::{self as ty, TyCtxt};
use rustc_middle::ty::{self as ty, AssocItems, AssocKind, TyCtxt};
use rustc_session::Session;
use rustc_span::def_id::DefId;
use std::iter;

use GenericArgsInfo::*;

Expand Down Expand Up @@ -334,6 +335,22 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> {
.join(", ")
}

fn get_unbound_associated_types(&self) -> Vec<String> {
if self.tcx.is_trait(self.def_id) {
let items: &AssocItems<'_> = self.tcx.associated_items(self.def_id);
items
.in_definition_order()
.filter(|item| item.kind == AssocKind::Type)
.filter(|item| {
!self.gen_args.bindings.iter().any(|binding| binding.ident.name == item.name)
})
.map(|item| item.name.to_ident_string())
.collect()
} else {
Vec::default()
}
}

fn create_error_message(&self) -> String {
let def_path = self.tcx.def_path_str(self.def_id);
let def_kind = self.tcx.def_kind(self.def_id).descr(self.def_id);
Expand Down Expand Up @@ -618,6 +635,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> {
fn suggest_removing_args_or_generics(&self, err: &mut Diagnostic) {
let num_provided_lt_args = self.num_provided_lifetime_args();
let num_provided_type_const_args = self.num_provided_type_or_const_args();
let unbound_types = self.get_unbound_associated_types();
let num_provided_args = num_provided_lt_args + num_provided_type_const_args;
assert!(num_provided_args > 0);

Expand All @@ -629,6 +647,8 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> {
let redundant_type_or_const_args = num_redundant_type_or_const_args > 0;

let remove_entire_generics = num_redundant_args >= self.gen_args.args.len();
let provided_args_matches_unbound_traits =
unbound_types.len() == num_redundant_type_or_const_args;

let remove_lifetime_args = |err: &mut Diagnostic| {
let mut lt_arg_spans = Vec::new();
Expand Down Expand Up @@ -713,7 +733,28 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> {
);
};

if remove_entire_generics {
// If there is a single unbound associated type and a single excess generic param
// suggest replacing the generic param with the associated type bound
if provided_args_matches_unbound_traits && !unbound_types.is_empty() {
let mut suggestions = vec![];
let unused_generics = &self.gen_args.args[self.num_expected_type_or_const_args()..];
for (potential, name) in iter::zip(unused_generics, &unbound_types) {
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(potential.span()) {
suggestions.push((potential.span(), format!("{} = {}", name, snippet)));
}
}

if !suggestions.is_empty() {
err.multipart_suggestion(
&format!(
"replace the generic bound{s} with the associated type{s}",
s = pluralize!(unbound_types.len())
),
suggestions,
Applicability::MaybeIncorrect,
);
}
} else if remove_entire_generics {
let span = self
.path_segment
.args
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/const-generics/issues/issue-87493.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ error[E0107]: this trait takes 0 generic arguments but 1 generic argument was su
--> $DIR/issue-87493.rs:8:8
|
LL | T: MyTrait<Assoc == S::Assoc>,
| ^^^^^^^------------------- help: remove these generics
| ^^^^^^^ ----------------- help: replace the generic bound with the associated type: `Assoc = Assoc == S::Assoc`
| |
| expected 0 generic arguments
|
Expand Down
10 changes: 10 additions & 0 deletions src/test/ui/error-codes/E0107.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,14 @@ struct Baz<'a, 'b, 'c> {
//~| HELP remove this lifetime argument
}

pub trait T {
type A;
type B;
}

fn trait_bound_generic<I: T<u8, u16>>(_i: I) {
//~^ ERROR this trait takes 0 generic arguments
//~| HELP replace the generic bounds with the associated types
}

fn main() {}
18 changes: 17 additions & 1 deletion src/test/ui/error-codes/E0107.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@ note: struct defined here, with 0 lifetime parameters
LL | struct Quux<T>(T);
| ^^^^

error: aborting due to 9 previous errors
error[E0107]: this trait takes 0 generic arguments but 2 generic arguments were supplied
--> $DIR/E0107.rs:55:27
|
LL | fn trait_bound_generic<I: T<u8, u16>>(_i: I) {
| ^ expected 0 generic arguments
|
note: trait defined here, with 0 generic parameters
--> $DIR/E0107.rs:50:11
|
LL | pub trait T {
| ^
help: replace the generic bounds with the associated types
|
LL | fn trait_bound_generic<I: T<A = u8, B = u16>>(_i: I) {
| ~~~~~~ ~~~~~~~

error: aborting due to 10 previous errors

For more information about this error, try `rustc --explain E0107`.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ error[E0107]: this trait takes 2 generic arguments but 4 generic arguments were
--> $DIR/use-type-argument-instead-of-assoc-type.rs:7:16
|
LL | i: Box<dyn T<usize, usize, usize, usize, B=usize>>,
| ^ ------------ help: remove these generic arguments
| |
| expected 2 generic arguments
| ^ expected 2 generic arguments
|
note: trait defined here, with 2 generic parameters: `X`, `Y`
--> $DIR/use-type-argument-instead-of-assoc-type.rs:1:11
|
LL | pub trait T<X, Y> {
| ^ - -
help: replace the generic bounds with the associated types
|
LL | i: Box<dyn T<usize, usize, A = usize, C = usize, B=usize>>,
| ~~~~~~~~~ ~~~~~~~~~

error[E0191]: the value of the associated types `A` (from trait `T`), `C` (from trait `T`) must be specified
--> $DIR/use-type-argument-instead-of-assoc-type.rs:7:16
Expand All @@ -23,11 +25,6 @@ LL | type C;
...
LL | i: Box<dyn T<usize, usize, usize, usize, B=usize>>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated types `A`, `C` must be specified
|
help: specify the associated types
|
LL | i: Box<dyn T<usize, usize, A = usize, C = usize, B=usize>>,
| ~~~~~~~~~ ~~~~~~~~~

error: aborting due to 2 previous errors

Expand Down

0 comments on commit e2661ba

Please sign in to comment.