Handle impl Trait
where Trait
has an assoc type with missing bounds
Fix #69638.
This commit is contained in:
parent
e82734e56b
commit
9d83cc8331
@ -163,23 +163,123 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let suggest_restriction =
|
let suggest_restriction =
|
||||||
|generics: &hir::Generics<'_>, msg, err: &mut DiagnosticBuilder<'_>| {
|
|generics: &hir::Generics<'_>,
|
||||||
|
msg,
|
||||||
|
err: &mut DiagnosticBuilder<'_>,
|
||||||
|
fn_sig: Option<&hir::FnSig<'_>>| {
|
||||||
|
// Type parameter needs more bounds. The trivial case is `T` `where T: Bound`, but
|
||||||
|
// it can also be an `impl Trait` param that needs to be decomposed to a type
|
||||||
|
// param for cleaner code.
|
||||||
let span = generics.where_clause.span_for_predicates_or_empty_place();
|
let span = generics.where_clause.span_for_predicates_or_empty_place();
|
||||||
if !span.from_expansion() && span.desugaring_kind().is_none() {
|
if !span.from_expansion() && span.desugaring_kind().is_none() {
|
||||||
err.span_suggestion(
|
// Given `fn foo(t: impl Trait)` where `Trait` requires assoc type `A`...
|
||||||
generics.where_clause.span_for_predicates_or_empty_place().shrink_to_hi(),
|
if let Some((name, fn_sig)) = fn_sig.and_then(|sig| {
|
||||||
&format!("consider further restricting {}", msg),
|
projection.and_then(|p| {
|
||||||
format!(
|
// Shenanigans to get the `Trait` from the `impl Trait`.
|
||||||
"{} {} ",
|
match p.self_ty().kind {
|
||||||
if !generics.where_clause.predicates.is_empty() {
|
ty::Param(param) if param.name.as_str().starts_with("impl ") => {
|
||||||
","
|
let n = param.name.as_str();
|
||||||
} else {
|
// `fn foo(t: impl Trait)`
|
||||||
" where"
|
// ^^^^^ get this string
|
||||||
|
n.split_whitespace()
|
||||||
|
.skip(1)
|
||||||
|
.next()
|
||||||
|
.map(|n| (n.to_string(), sig))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
// FIXME: Cleanup.
|
||||||
|
let mut ty_spans = vec![];
|
||||||
|
let impl_name = format!("impl {}", name);
|
||||||
|
for i in fn_sig.decl.inputs {
|
||||||
|
if let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = i.kind {
|
||||||
|
match path.segments {
|
||||||
|
[segment] if segment.ident.to_string() == impl_name => {
|
||||||
|
// `fn foo(t: impl Trait)`
|
||||||
|
// ^^^^^^^^^^ get this to suggest
|
||||||
|
// `T` instead
|
||||||
|
|
||||||
|
// There might be more than one `impl Trait`.
|
||||||
|
ty_spans.push(i.span);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let type_param = format!("{}: {}", "T", name);
|
||||||
|
// FIXME: modify the `trait_ref` instead of string shenanigans.
|
||||||
|
// Turn `<impl Trait as Foo>::Bar: Qux` into `<T as Foo>::Bar: Qux`.
|
||||||
|
let pred = trait_ref.without_const().to_predicate().to_string();
|
||||||
|
let pred = pred.replace(&impl_name, "T");
|
||||||
|
let mut sugg = vec![
|
||||||
|
match generics
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.filter(|p| match p.kind {
|
||||||
|
hir::GenericParamKind::Type {
|
||||||
|
synthetic: Some(hir::SyntheticTyParamKind::ImplTrait),
|
||||||
|
..
|
||||||
|
} => false,
|
||||||
|
_ => true,
|
||||||
|
})
|
||||||
|
.last()
|
||||||
|
{
|
||||||
|
// `fn foo(t: impl Trait)`
|
||||||
|
// ^ suggest `<T: Trait>` here
|
||||||
|
None => (generics.span, format!("<{}>", type_param)),
|
||||||
|
Some(param) => {
|
||||||
|
(param.span.shrink_to_hi(), format!(", {}", type_param))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
trait_ref.without_const().to_predicate(),
|
(
|
||||||
),
|
// `fn foo(t: impl Trait)`
|
||||||
Applicability::MachineApplicable,
|
// ^ suggest `where <T as Trait>::A: Bound`
|
||||||
);
|
generics
|
||||||
|
.where_clause
|
||||||
|
.span_for_predicates_or_empty_place()
|
||||||
|
.shrink_to_hi(),
|
||||||
|
format!(
|
||||||
|
"{} {} ",
|
||||||
|
if !generics.where_clause.predicates.is_empty() {
|
||||||
|
","
|
||||||
|
} else {
|
||||||
|
" where"
|
||||||
|
},
|
||||||
|
pred,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
sugg.extend(ty_spans.into_iter().map(|s| (s, "T".to_string())));
|
||||||
|
// Suggest `fn foo<T: Trait>(t: T) where <T as Trait>::A: Bound`.
|
||||||
|
err.multipart_suggestion(
|
||||||
|
"introduce a type parameter with a trait bound instead of using \
|
||||||
|
`impl Trait`",
|
||||||
|
sugg,
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Trivial case: `T` needs an extra bound.
|
||||||
|
err.span_suggestion(
|
||||||
|
generics
|
||||||
|
.where_clause
|
||||||
|
.span_for_predicates_or_empty_place()
|
||||||
|
.shrink_to_hi(),
|
||||||
|
&format!("consider further restricting {}", msg),
|
||||||
|
format!(
|
||||||
|
"{} {} ",
|
||||||
|
if !generics.where_clause.predicates.is_empty() {
|
||||||
|
","
|
||||||
|
} else {
|
||||||
|
" where"
|
||||||
|
},
|
||||||
|
trait_ref.without_const().to_predicate(),
|
||||||
|
),
|
||||||
|
Applicability::MachineApplicable,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -187,6 +287,10 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
|
|||||||
// don't suggest `T: Sized + ?Sized`.
|
// don't suggest `T: Sized + ?Sized`.
|
||||||
let mut hir_id = body_id;
|
let mut hir_id = body_id;
|
||||||
while let Some(node) = self.tcx.hir().find(hir_id) {
|
while let Some(node) = self.tcx.hir().find(hir_id) {
|
||||||
|
debug!(
|
||||||
|
"suggest_restricting_param_bound {:?} {:?} {:?} {:?}",
|
||||||
|
trait_ref, self_ty.kind, projection, node
|
||||||
|
);
|
||||||
match node {
|
match node {
|
||||||
hir::Node::TraitItem(hir::TraitItem {
|
hir::Node::TraitItem(hir::TraitItem {
|
||||||
generics,
|
generics,
|
||||||
@ -194,27 +298,33 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
|
|||||||
..
|
..
|
||||||
}) if param_ty && self_ty == self.tcx.types.self_param => {
|
}) if param_ty && self_ty == self.tcx.types.self_param => {
|
||||||
// Restricting `Self` for a single method.
|
// Restricting `Self` for a single method.
|
||||||
suggest_restriction(&generics, "`Self`", err);
|
suggest_restriction(&generics, "`Self`", err, None);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hir::Node::TraitItem(hir::TraitItem {
|
hir::Node::TraitItem(hir::TraitItem {
|
||||||
generics,
|
generics,
|
||||||
kind: hir::TraitItemKind::Fn(..),
|
kind: hir::TraitItemKind::Fn(fn_sig, ..),
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
| hir::Node::ImplItem(hir::ImplItem {
|
| hir::Node::ImplItem(hir::ImplItem {
|
||||||
generics,
|
generics,
|
||||||
kind: hir::ImplItemKind::Fn(..),
|
kind: hir::ImplItemKind::Fn(fn_sig, ..),
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
| hir::Node::Item(
|
| hir::Node::Item(hir::Item {
|
||||||
hir::Item { kind: hir::ItemKind::Fn(_, generics, _), .. }
|
kind: hir::ItemKind::Fn(fn_sig, generics, _), ..
|
||||||
| hir::Item { kind: hir::ItemKind::Trait(_, _, generics, _, _), .. }
|
}) if projection.is_some() => {
|
||||||
|
// Missing associated type bound.
|
||||||
|
suggest_restriction(&generics, "the associated type", err, Some(fn_sig));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hir::Node::Item(
|
||||||
|
hir::Item { kind: hir::ItemKind::Trait(_, _, generics, _, _), .. }
|
||||||
| hir::Item { kind: hir::ItemKind::Impl { generics, .. }, .. },
|
| hir::Item { kind: hir::ItemKind::Impl { generics, .. }, .. },
|
||||||
) if projection.is_some() => {
|
) if projection.is_some() => {
|
||||||
// Missing associated type bound.
|
// Missing associated type bound.
|
||||||
suggest_restriction(&generics, "the associated type", err);
|
suggest_restriction(&generics, "the associated type", err, None);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
src/test/ui/suggestions/impl-trait-with-missing-bounds.rs
Normal file
29
src/test/ui/suggestions/impl-trait-with-missing-bounds.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// The double space in `impl Iterator` is load bearing! We want to make sure we don't regress by
|
||||||
|
// accident if the internal string representation changes.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn foo(constraints: impl Iterator) {
|
||||||
|
for constraint in constraints {
|
||||||
|
qux(constraint);
|
||||||
|
//~^ ERROR `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar<T>(t: T, constraints: impl Iterator) where T: std::fmt::Debug {
|
||||||
|
for constraint in constraints {
|
||||||
|
qux(t);
|
||||||
|
qux(constraint);
|
||||||
|
//~^ ERROR `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn baz(t: impl std::fmt::Debug, constraints: impl Iterator) {
|
||||||
|
for constraint in constraints {
|
||||||
|
qux(t);
|
||||||
|
qux(constraint);
|
||||||
|
//~^ ERROR `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qux(_: impl std::fmt::Debug) {}
|
||||||
|
|
||||||
|
fn main() {}
|
@ -0,0 +1,48 @@
|
|||||||
|
error[E0277]: `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
|
||||||
|
--> $DIR/impl-trait-with-missing-bounds.rs:6:13
|
||||||
|
|
|
||||||
|
LL | qux(constraint);
|
||||||
|
| ^^^^^^^^^^ `<impl Iterator as std::iter::Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
|
||||||
|
...
|
||||||
|
LL | fn qux(_: impl std::fmt::Debug) {}
|
||||||
|
| --- --------------- required by this bound in `qux`
|
||||||
|
|
|
||||||
|
= help: the trait `std::fmt::Debug` is not implemented for `<impl Iterator as std::iter::Iterator>::Item`
|
||||||
|
help: introduce a type parameter with a trait bound instead of using `impl Trait`
|
||||||
|
|
|
||||||
|
LL | fn foo<T: Iterator>(constraints: T) where <T as std::iter::Iterator>::Item: std::fmt::Debug {
|
||||||
|
| ^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error[E0277]: `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
|
||||||
|
--> $DIR/impl-trait-with-missing-bounds.rs:14:13
|
||||||
|
|
|
||||||
|
LL | qux(constraint);
|
||||||
|
| ^^^^^^^^^^ `<impl Iterator as std::iter::Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
|
||||||
|
...
|
||||||
|
LL | fn qux(_: impl std::fmt::Debug) {}
|
||||||
|
| --- --------------- required by this bound in `qux`
|
||||||
|
|
|
||||||
|
= help: the trait `std::fmt::Debug` is not implemented for `<impl Iterator as std::iter::Iterator>::Item`
|
||||||
|
help: introduce a type parameter with a trait bound instead of using `impl Trait`
|
||||||
|
|
|
||||||
|
LL | fn bar<T, T: Iterator>(t: T, constraints: T) where T: std::fmt::Debug, <T as std::iter::Iterator>::Item: std::fmt::Debug {
|
||||||
|
| ^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error[E0277]: `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
|
||||||
|
--> $DIR/impl-trait-with-missing-bounds.rs:22:13
|
||||||
|
|
|
||||||
|
LL | qux(constraint);
|
||||||
|
| ^^^^^^^^^^ `<impl Iterator as std::iter::Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
|
||||||
|
...
|
||||||
|
LL | fn qux(_: impl std::fmt::Debug) {}
|
||||||
|
| --- --------------- required by this bound in `qux`
|
||||||
|
|
|
||||||
|
= help: the trait `std::fmt::Debug` is not implemented for `<impl Iterator as std::iter::Iterator>::Item`
|
||||||
|
help: introduce a type parameter with a trait bound instead of using `impl Trait`
|
||||||
|
|
|
||||||
|
LL | fn baz<T: Iterator>(t: impl std::fmt::Debug, constraints: T) where <T as std::iter::Iterator>::Item: std::fmt::Debug {
|
||||||
|
| ^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: aborting due to 3 previous errors
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0277`.
|
Loading…
x
Reference in New Issue
Block a user