Skip to content

Commit 9d83cc8

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

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

Lines changed: 131 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -163,58 +163,168 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
163163
};
164164

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

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

201305
hir::Node::TraitItem(hir::TraitItem {
202306
generics,
203-
kind: hir::TraitItemKind::Fn(..),
307+
kind: hir::TraitItemKind::Fn(fn_sig, ..),
204308
..
205309
})
206310
| hir::Node::ImplItem(hir::ImplItem {
207311
generics,
208-
kind: hir::ImplItemKind::Fn(..),
312+
kind: hir::ImplItemKind::Fn(fn_sig, ..),
209313
..
210314
})
211-
| hir::Node::Item(
212-
hir::Item { kind: hir::ItemKind::Fn(_, generics, _), .. }
213-
| hir::Item { kind: hir::ItemKind::Trait(_, _, generics, _, _), .. }
315+
| hir::Node::Item(hir::Item {
316+
kind: hir::ItemKind::Fn(fn_sig, generics, _), ..
317+
}) if projection.is_some() => {
318+
// Missing associated type bound.
319+
suggest_restriction(&generics, "the associated type", err, Some(fn_sig));
320+
return;
321+
}
322+
hir::Node::Item(
323+
hir::Item { kind: hir::ItemKind::Trait(_, _, generics, _, _), .. }
214324
| hir::Item { kind: hir::ItemKind::Impl { generics, .. }, .. },
215325
) if projection.is_some() => {
216326
// Missing associated type bound.
217-
suggest_restriction(&generics, "the associated type", err);
327+
suggest_restriction(&generics, "the associated type", err, None);
218328
return;
219329
}
220330

Lines changed: 29 additions & 0 deletions
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() {}
Lines changed: 48 additions & 0 deletions
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)