Skip to content

Commit 9dc9785

Browse files
committed
Handle impl Trait where Trait has an assoc type with missing bounds
Fix #69638.
1 parent 49dc2f9 commit 9dc9785

File tree

3 files changed

+208
-21
lines changed

3 files changed

+208
-21
lines changed

src/librustc_trait_selection/traits/error_reporting/suggestions.rs

+131-21
Original file line numberDiff line numberDiff line change
@@ -162,58 +162,168 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
162162
};
163163

164164
let suggest_restriction =
165-
|generics: &hir::Generics<'_>, msg, err: &mut DiagnosticBuilder<'_>| {
165+
|generics: &hir::Generics<'_>,
166+
msg,
167+
err: &mut DiagnosticBuilder<'_>,
168+
fn_sig: Option<&hir::FnSig<'_>>| {
169+
// Type parameter needs more bounds. The trivial case is `T` `where T: Bound`, but
170+
// it can also be an `impl Trait` param that needs to be decomposed to a type
171+
// param for cleaner code.
166172
let span = generics.where_clause.span_for_predicates_or_empty_place();
167173
if !span.from_expansion() && span.desugaring_kind().is_none() {
168-
err.span_suggestion(
169-
generics.where_clause.span_for_predicates_or_empty_place().shrink_to_hi(),
170-
&format!("consider further restricting {}", msg),
171-
format!(
172-
"{} {} ",
173-
if !generics.where_clause.predicates.is_empty() {
174-
","
175-
} else {
176-
" where"
174+
// Given `fn foo(t: impl Trait)` where `Trait` requires assoc type `A`...
175+
if let Some((name, fn_sig)) = fn_sig.and_then(|sig| {
176+
projection.and_then(|p| {
177+
// Shenanigans to get the `Trait` from the `impl Trait`.
178+
match p.self_ty().kind {
179+
ty::Param(param) if param.name.as_str().starts_with("impl ") => {
180+
let n = param.name.as_str();
181+
// `fn foo(t: impl Trait)`
182+
// ^^^^^ get this string
183+
n.split_whitespace()
184+
.skip(1)
185+
.next()
186+
.map(|n| (n.to_string(), sig))
187+
}
188+
_ => None,
189+
}
190+
})
191+
}) {
192+
// FIXME: Cleanup.
193+
let mut ty_spans = vec![];
194+
let impl_name = format!("impl {}", name);
195+
for i in fn_sig.decl.inputs {
196+
if let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = i.kind {
197+
match path.segments {
198+
[segment] if segment.ident.to_string() == impl_name => {
199+
// `fn foo(t: impl Trait)`
200+
// ^^^^^^^^^^ get this to suggest
201+
// `T` instead
202+
203+
// There might be more than one `impl Trait`.
204+
ty_spans.push(i.span);
205+
}
206+
_ => {}
207+
}
208+
}
209+
}
210+
211+
let type_param = format!("{}: {}", "T", name);
212+
// FIXME: modify the `trait_ref` instead of string shenanigans.
213+
// Turn `<impl Trait as Foo>::Bar: Qux` into `<T as Foo>::Bar: Qux`.
214+
let pred = trait_ref.without_const().to_predicate().to_string();
215+
let pred = pred.replace(&impl_name, "T");
216+
let mut sugg = vec![
217+
match generics
218+
.params
219+
.iter()
220+
.filter(|p| match p.kind {
221+
hir::GenericParamKind::Type {
222+
synthetic: Some(hir::SyntheticTyParamKind::ImplTrait),
223+
..
224+
} => false,
225+
_ => true,
226+
})
227+
.last()
228+
{
229+
// `fn foo(t: impl Trait)`
230+
// ^ suggest `<T: Trait>` here
231+
None => (generics.span, format!("<{}>", type_param)),
232+
Some(param) => {
233+
(param.span.shrink_to_hi(), format!(", {}", type_param))
234+
}
177235
},
178-
trait_ref.without_const().to_predicate(),
179-
),
180-
Applicability::MachineApplicable,
181-
);
236+
(
237+
// `fn foo(t: impl Trait)`
238+
// ^ suggest `where <T as Trait>::A: Bound`
239+
generics
240+
.where_clause
241+
.span_for_predicates_or_empty_place()
242+
.shrink_to_hi(),
243+
format!(
244+
"{} {} ",
245+
if !generics.where_clause.predicates.is_empty() {
246+
","
247+
} else {
248+
" where"
249+
},
250+
pred,
251+
),
252+
),
253+
];
254+
sugg.extend(ty_spans.into_iter().map(|s| (s, "T".to_string())));
255+
// Suggest `fn foo<T: Trait>(t: T) where <T as Trait>::A: Bound`.
256+
err.multipart_suggestion(
257+
"introduce a type parameter with a trait bound instead of using \
258+
`impl Trait`",
259+
sugg,
260+
Applicability::MaybeIncorrect,
261+
);
262+
} else {
263+
// Trivial case: `T` needs an extra bound.
264+
err.span_suggestion(
265+
generics
266+
.where_clause
267+
.span_for_predicates_or_empty_place()
268+
.shrink_to_hi(),
269+
&format!("consider further restricting {}", msg),
270+
format!(
271+
"{} {} ",
272+
if !generics.where_clause.predicates.is_empty() {
273+
","
274+
} else {
275+
" where"
276+
},
277+
trait_ref.without_const().to_predicate(),
278+
),
279+
Applicability::MachineApplicable,
280+
);
281+
}
182282
}
183283
};
184284

185285
// FIXME: Add check for trait bound that is already present, particularly `?Sized` so we
186286
// don't suggest `T: Sized + ?Sized`.
187287
let mut hir_id = body_id;
188288
while let Some(node) = self.tcx.hir().find(hir_id) {
289+
debug!(
290+
"suggest_restricting_param_bound {:?} {:?} {:?} {:?}",
291+
trait_ref, self_ty.kind, projection, node
292+
);
189293
match node {
190294
hir::Node::TraitItem(hir::TraitItem {
191295
generics,
192296
kind: hir::TraitItemKind::Fn(..),
193297
..
194298
}) if param_ty && self_ty == self.tcx.types.self_param => {
195299
// Restricting `Self` for a single method.
196-
suggest_restriction(&generics, "`Self`", err);
300+
suggest_restriction(&generics, "`Self`", err, None);
197301
return;
198302
}
199303

200304
hir::Node::TraitItem(hir::TraitItem {
201305
generics,
202-
kind: hir::TraitItemKind::Fn(..),
306+
kind: hir::TraitItemKind::Fn(fn_sig, ..),
203307
..
204308
})
205309
| hir::Node::ImplItem(hir::ImplItem {
206310
generics,
207-
kind: hir::ImplItemKind::Fn(..),
311+
kind: hir::ImplItemKind::Fn(fn_sig, ..),
208312
..
209313
})
210-
| hir::Node::Item(
211-
hir::Item { kind: hir::ItemKind::Fn(_, generics, _), .. }
212-
| hir::Item { kind: hir::ItemKind::Trait(_, _, generics, _, _), .. }
314+
| hir::Node::Item(hir::Item {
315+
kind: hir::ItemKind::Fn(fn_sig, generics, _), ..
316+
}) if projection.is_some() => {
317+
// Missing associated type bound.
318+
suggest_restriction(&generics, "the associated type", err, Some(fn_sig));
319+
return;
320+
}
321+
hir::Node::Item(
322+
hir::Item { kind: hir::ItemKind::Trait(_, _, generics, _, _), .. }
213323
| hir::Item { kind: hir::ItemKind::Impl { generics, .. }, .. },
214324
) if projection.is_some() => {
215325
// Missing associated type bound.
216-
suggest_restriction(&generics, "the associated type", err);
326+
suggest_restriction(&generics, "the associated type", err, None);
217327
return;
218328
}
219329

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// The double space in `impl Iterator` is load bearing! We want to make sure we don't regress by
2+
// accident if the internal string representation changes.
3+
#[rustfmt::skip]
4+
fn foo(constraints: impl Iterator) {
5+
for constraint in constraints {
6+
qux(constraint);
7+
//~^ ERROR `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
8+
}
9+
}
10+
11+
fn bar<T>(t: T, constraints: impl Iterator) where T: std::fmt::Debug {
12+
for constraint in constraints {
13+
qux(t);
14+
qux(constraint);
15+
//~^ ERROR `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
16+
}
17+
}
18+
19+
fn baz(t: impl std::fmt::Debug, constraints: impl Iterator) {
20+
for constraint in constraints {
21+
qux(t);
22+
qux(constraint);
23+
//~^ ERROR `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
24+
}
25+
}
26+
27+
fn qux(_: impl std::fmt::Debug) {}
28+
29+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
error[E0277]: `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
2+
--> $DIR/impl-trait-with-missing-bounds.rs:6:13
3+
|
4+
LL | qux(constraint);
5+
| ^^^^^^^^^^ `<impl Iterator as std::iter::Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
6+
...
7+
LL | fn qux(_: impl std::fmt::Debug) {}
8+
| --- --------------- required by this bound in `qux`
9+
|
10+
= help: the trait `std::fmt::Debug` is not implemented for `<impl Iterator as std::iter::Iterator>::Item`
11+
help: introduce a type parameter with a trait bound instead of using `impl Trait`
12+
|
13+
LL | fn foo<T: Iterator>(constraints: T) where <T as std::iter::Iterator>::Item: std::fmt::Debug {
14+
| ^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15+
16+
error[E0277]: `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
17+
--> $DIR/impl-trait-with-missing-bounds.rs:14:13
18+
|
19+
LL | qux(constraint);
20+
| ^^^^^^^^^^ `<impl Iterator as std::iter::Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
21+
...
22+
LL | fn qux(_: impl std::fmt::Debug) {}
23+
| --- --------------- required by this bound in `qux`
24+
|
25+
= help: the trait `std::fmt::Debug` is not implemented for `<impl Iterator as std::iter::Iterator>::Item`
26+
help: introduce a type parameter with a trait bound instead of using `impl Trait`
27+
|
28+
LL | fn bar<T, T: Iterator>(t: T, constraints: T) where T: std::fmt::Debug, <T as std::iter::Iterator>::Item: std::fmt::Debug {
29+
| ^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30+
31+
error[E0277]: `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
32+
--> $DIR/impl-trait-with-missing-bounds.rs:22:13
33+
|
34+
LL | qux(constraint);
35+
| ^^^^^^^^^^ `<impl Iterator as std::iter::Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
36+
...
37+
LL | fn qux(_: impl std::fmt::Debug) {}
38+
| --- --------------- required by this bound in `qux`
39+
|
40+
= help: the trait `std::fmt::Debug` is not implemented for `<impl Iterator as std::iter::Iterator>::Item`
41+
help: introduce a type parameter with a trait bound instead of using `impl Trait`
42+
|
43+
LL | fn baz<T: Iterator>(t: impl std::fmt::Debug, constraints: T) where <T as std::iter::Iterator>::Item: std::fmt::Debug {
44+
| ^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45+
46+
error: aborting due to 3 previous errors
47+
48+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)