Skip to content

Commit 32fb4dc

Browse files
committed
Auto merge of #69707 - estebank:impl-trait-missing-bounds, r=Centril
Handle `impl Trait` where `Trait` has an assoc type with missing bounds When encountering a type parameter that 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. For example, given ```rust fn foo(constraints: impl Iterator) { for constraint in constraints { println!("{:?}", constraint); } } ``` the previous output was ``` error[E0277]: `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug` --> src/main.rs:3:26 | 1 | fn foo(constraints: impl Iterator) { | - help: consider further restricting the associated type: `where <impl Iterator as std::iter::Iterator>::Item: std::fmt::Debug` 2 | for constraint in constraints { 3 | println!("{:?}", constraint); | ^^^^^^^^^^ `<impl Iterator as std::iter::Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | = help: the trait `std::fmt::Debug` is not implemented for `<impl Iterator as std::iter::Iterator>::Item` = note: required by `std::fmt::Debug::fmt` ``` which is incorrect as `where <impl Iterator as std::iter::Iterator>::Item: std::fmt::Debug` is not valid syntax nor would it restrict the positional `impl Iterator` parameter if it were. The output being introduced is ``` error[E0277]: `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug` --> src/main.rs:3:26 | 3 | println!("{:?}", constraint); | ^^^^^^^^^^ `<impl Iterator as std::iter::Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | = help: the trait `std::fmt::Debug` is not implemented for `<impl Iterator as std::iter::Iterator>::Item` = note: required by `std::fmt::Debug::fmt` 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 { | ^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``` This suggestion is correct and lead the user in the right direction: because you have an associated type restriction you can no longer use `impl Trait`, the only reasonable alternative is to introduce a named type parameter, bound by `Trait` and with a `where` binding on the associated type for the new type parameter `as Trait` for the missing bound. *Ideally*, we would want to suggest something like the following, but that is not valid syntax today ``` error[E0277]: `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug` --> src/main.rs:3:26 | 3 | println!("{:?}", constraint); | ^^^^^^^^^^ `<impl Iterator as std::iter::Iterator>::Item` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | = help: the trait `std::fmt::Debug` is not implemented for `<impl Iterator as std::iter::Iterator>::Item` = note: required by `std::fmt::Debug::fmt` help: introduce a type parameter with a trait bound instead of using `impl Trait` | LL | fn foo(constraints: impl Iterator<Item: std::fmt::Debug>) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``` Fix #69638.
2 parents 941d435 + 984aac6 commit 32fb4dc

File tree

7 files changed

+304
-50
lines changed

7 files changed

+304
-50
lines changed

src/librustc_trait_selection/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#![feature(in_band_lifetimes)]
1717
#![feature(crate_visibility_modifier)]
1818
#![feature(or_patterns)]
19+
#![feature(str_strip)]
20+
#![feature(option_zip)]
1921
#![recursion_limit = "512"] // For rustdoc
2022

2123
#[macro_use]

src/librustc_trait_selection/traits/error_reporting/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
388388
// which is somewhat confusing.
389389
self.suggest_restricting_param_bound(
390390
&mut err,
391-
&trait_ref,
391+
trait_ref,
392392
obligation.cause.body_id,
393393
);
394394
} else {

src/librustc_trait_selection/traits/error_reporting/suggestions.rs

+174-32
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_middle::ty::TypeckTables;
1515
use rustc_middle::ty::{
1616
self, AdtKind, DefIdTree, ToPredicate, Ty, TyCtxt, TypeFoldable, WithConstness,
1717
};
18-
use rustc_span::symbol::{kw, sym};
18+
use rustc_span::symbol::{kw, sym, Symbol};
1919
use rustc_span::{MultiSpan, Span, DUMMY_SP};
2020
use std::fmt;
2121

@@ -27,7 +27,7 @@ pub trait InferCtxtExt<'tcx> {
2727
fn suggest_restricting_param_bound(
2828
&self,
2929
err: &mut DiagnosticBuilder<'_>,
30-
trait_ref: &ty::PolyTraitRef<'_>,
30+
trait_ref: ty::PolyTraitRef<'_>,
3131
body_id: hir::HirId,
3232
);
3333

@@ -148,11 +148,128 @@ pub trait InferCtxtExt<'tcx> {
148148
fn suggest_new_overflow_limit(&self, err: &mut DiagnosticBuilder<'_>);
149149
}
150150

151+
fn predicate_constraint(generics: &hir::Generics<'_>, pred: String) -> (Span, String) {
152+
(
153+
generics.where_clause.span_for_predicates_or_empty_place().shrink_to_hi(),
154+
format!(
155+
"{} {} ",
156+
if !generics.where_clause.predicates.is_empty() { "," } else { " where" },
157+
pred,
158+
),
159+
)
160+
}
161+
162+
/// Type parameter needs more bounds. The trivial case is `T` `where T: Bound`, but
163+
/// it can also be an `impl Trait` param that needs to be decomposed to a type
164+
/// param for cleaner code.
165+
fn suggest_restriction(
166+
generics: &hir::Generics<'_>,
167+
msg: &str,
168+
err: &mut DiagnosticBuilder<'_>,
169+
fn_sig: Option<&hir::FnSig<'_>>,
170+
projection: Option<&ty::ProjectionTy<'_>>,
171+
trait_ref: ty::PolyTraitRef<'_>,
172+
) {
173+
let span = generics.where_clause.span_for_predicates_or_empty_place();
174+
if span.from_expansion() || span.desugaring_kind().is_some() {
175+
return;
176+
}
177+
// Given `fn foo(t: impl Trait)` where `Trait` requires assoc type `A`...
178+
if let Some((bound_str, fn_sig)) =
179+
fn_sig.zip(projection).and_then(|(sig, p)| match p.self_ty().kind {
180+
// Shenanigans to get the `Trait` from the `impl Trait`.
181+
ty::Param(param) => {
182+
// `fn foo(t: impl Trait)`
183+
// ^^^^^ get this string
184+
param.name.as_str().strip_prefix("impl").map(|s| (s.trim_start().to_string(), sig))
185+
}
186+
_ => None,
187+
})
188+
{
189+
// We know we have an `impl Trait` that doesn't satisfy a required projection.
190+
191+
// Find all of the ocurrences of `impl Trait` for `Trait` in the function arguments'
192+
// types. There should be at least one, but there might be *more* than one. In that
193+
// case we could just ignore it and try to identify which one needs the restriction,
194+
// but instead we choose to suggest replacing all instances of `impl Trait` with `T`
195+
// where `T: Trait`.
196+
let mut ty_spans = vec![];
197+
let impl_trait_str = format!("impl {}", bound_str);
198+
for input in fn_sig.decl.inputs {
199+
if let hir::TyKind::Path(hir::QPath::Resolved(
200+
None,
201+
hir::Path { segments: [segment], .. },
202+
)) = input.kind
203+
{
204+
if segment.ident.as_str() == impl_trait_str.as_str() {
205+
// `fn foo(t: impl Trait)`
206+
// ^^^^^^^^^^ get this to suggest `T` instead
207+
208+
// There might be more than one `impl Trait`.
209+
ty_spans.push(input.span);
210+
}
211+
}
212+
}
213+
214+
let type_param_name = generics.params.next_type_param_name(Some(&bound_str));
215+
// The type param `T: Trait` we will suggest to introduce.
216+
let type_param = format!("{}: {}", type_param_name, bound_str);
217+
218+
// FIXME: modify the `trait_ref` instead of string shenanigans.
219+
// Turn `<impl Trait as Foo>::Bar: Qux` into `<T as Foo>::Bar: Qux`.
220+
let pred = trait_ref.without_const().to_predicate().to_string();
221+
let pred = pred.replace(&impl_trait_str, &type_param_name);
222+
let mut sugg = vec![
223+
match generics
224+
.params
225+
.iter()
226+
.filter(|p| match p.kind {
227+
hir::GenericParamKind::Type {
228+
synthetic: Some(hir::SyntheticTyParamKind::ImplTrait),
229+
..
230+
} => false,
231+
_ => true,
232+
})
233+
.last()
234+
{
235+
// `fn foo(t: impl Trait)`
236+
// ^ suggest `<T: Trait>` here
237+
None => (generics.span, format!("<{}>", type_param)),
238+
// `fn foo<A>(t: impl Trait)`
239+
// ^^^ suggest `<A, T: Trait>` here
240+
Some(param) => (
241+
param.bounds_span().unwrap_or(param.span).shrink_to_hi(),
242+
format!(", {}", type_param),
243+
),
244+
},
245+
// `fn foo(t: impl Trait)`
246+
// ^ suggest `where <T as Trait>::A: Bound`
247+
predicate_constraint(generics, pred),
248+
];
249+
sugg.extend(ty_spans.into_iter().map(|s| (s, type_param_name.to_string())));
250+
251+
// Suggest `fn foo<T: Trait>(t: T) where <T as Trait>::A: Bound`.
252+
// FIXME: once `#![feature(associated_type_bounds)]` is stabilized, we should suggest
253+
// `fn foo(t: impl Trait<A: Bound>)` instead.
254+
err.multipart_suggestion(
255+
"introduce a type parameter with a trait bound instead of using `impl Trait`",
256+
sugg,
257+
Applicability::MaybeIncorrect,
258+
);
259+
} else {
260+
// Trivial case: `T` needs an extra bound: `T: Bound`.
261+
let (sp, sugg) =
262+
predicate_constraint(generics, trait_ref.without_const().to_predicate().to_string());
263+
let appl = Applicability::MachineApplicable;
264+
err.span_suggestion(sp, &format!("consider further restricting {}", msg), sugg, appl);
265+
}
266+
}
267+
151268
impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
152269
fn suggest_restricting_param_bound(
153270
&self,
154271
mut err: &mut DiagnosticBuilder<'_>,
155-
trait_ref: &ty::PolyTraitRef<'_>,
272+
trait_ref: ty::PolyTraitRef<'_>,
156273
body_id: hir::HirId,
157274
) {
158275
let self_ty = trait_ref.self_ty();
@@ -162,27 +279,6 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
162279
_ => return,
163280
};
164281

165-
let suggest_restriction =
166-
|generics: &hir::Generics<'_>, msg, err: &mut DiagnosticBuilder<'_>| {
167-
let span = generics.where_clause.span_for_predicates_or_empty_place();
168-
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"
178-
},
179-
trait_ref.without_const().to_predicate(),
180-
),
181-
Applicability::MachineApplicable,
182-
);
183-
}
184-
};
185-
186282
// FIXME: Add check for trait bound that is already present, particularly `?Sized` so we
187283
// don't suggest `T: Sized + ?Sized`.
188284
let mut hir_id = body_id;
@@ -194,27 +290,47 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
194290
..
195291
}) if param_ty && self_ty == self.tcx.types.self_param => {
196292
// Restricting `Self` for a single method.
197-
suggest_restriction(&generics, "`Self`", err);
293+
suggest_restriction(&generics, "`Self`", err, None, projection, trait_ref);
198294
return;
199295
}
200296

201297
hir::Node::TraitItem(hir::TraitItem {
202298
generics,
203-
kind: hir::TraitItemKind::Fn(..),
299+
kind: hir::TraitItemKind::Fn(fn_sig, ..),
204300
..
205301
})
206302
| hir::Node::ImplItem(hir::ImplItem {
207303
generics,
208-
kind: hir::ImplItemKind::Fn(..),
304+
kind: hir::ImplItemKind::Fn(fn_sig, ..),
209305
..
210306
})
211-
| hir::Node::Item(
212-
hir::Item { kind: hir::ItemKind::Fn(_, generics, _), .. }
213-
| hir::Item { kind: hir::ItemKind::Trait(_, _, generics, _, _), .. }
307+
| hir::Node::Item(hir::Item {
308+
kind: hir::ItemKind::Fn(fn_sig, generics, _), ..
309+
}) if projection.is_some() => {
310+
// Missing restriction on associated type of type parameter (unmet projection).
311+
suggest_restriction(
312+
&generics,
313+
"the associated type",
314+
err,
315+
Some(fn_sig),
316+
projection,
317+
trait_ref,
318+
);
319+
return;
320+
}
321+
hir::Node::Item(
322+
hir::Item { kind: hir::ItemKind::Trait(_, _, generics, _, _), .. }
214323
| hir::Item { kind: hir::ItemKind::Impl { generics, .. }, .. },
215324
) if projection.is_some() => {
216-
// Missing associated type bound.
217-
suggest_restriction(&generics, "the associated type", err);
325+
// Missing restriction on associated type of type parameter (unmet projection).
326+
suggest_restriction(
327+
&generics,
328+
"the associated type",
329+
err,
330+
None,
331+
projection,
332+
trait_ref,
333+
);
218334
return;
219335
}
220336

@@ -1588,3 +1704,29 @@ impl<'v> Visitor<'v> for ReturnsVisitor<'v> {
15881704
hir::intravisit::walk_body(self, body);
15891705
}
15901706
}
1707+
1708+
pub trait NextTypeParamName {
1709+
fn next_type_param_name(&self, name: Option<&str>) -> String;
1710+
}
1711+
1712+
impl NextTypeParamName for &[hir::GenericParam<'_>] {
1713+
fn next_type_param_name(&self, name: Option<&str>) -> String {
1714+
// This is the whitelist of possible parameter names that we might suggest.
1715+
let name = name.and_then(|n| n.chars().next()).map(|c| c.to_string().to_uppercase());
1716+
let name = name.as_ref().map(|s| s.as_str());
1717+
let possible_names = [name.unwrap_or("T"), "T", "U", "V", "X", "Y", "Z", "A", "B", "C"];
1718+
let used_names = self
1719+
.iter()
1720+
.filter_map(|p| match p.name {
1721+
hir::ParamName::Plain(ident) => Some(ident.name),
1722+
_ => None,
1723+
})
1724+
.collect::<Vec<_>>();
1725+
1726+
possible_names
1727+
.iter()
1728+
.find(|n| !used_names.contains(&Symbol::intern(n)))
1729+
.unwrap_or(&"ParamName")
1730+
.to_string()
1731+
}
1732+
}

src/librustc_typeck/collect.rs

+2-14
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ use rustc_session::parse::feature_err;
4545
use rustc_span::symbol::{kw, sym, Symbol};
4646
use rustc_span::{Span, DUMMY_SP};
4747
use rustc_target::spec::abi;
48+
use rustc_trait_selection::traits::error_reporting::suggestions::NextTypeParamName;
4849

4950
mod type_of;
5051

@@ -135,20 +136,7 @@ crate fn placeholder_type_error(
135136
if placeholder_types.is_empty() {
136137
return;
137138
}
138-
// This is the whitelist of possible parameter names that we might suggest.
139-
let possible_names = ["T", "K", "L", "A", "B", "C"];
140-
let used_names = generics
141-
.iter()
142-
.filter_map(|p| match p.name {
143-
hir::ParamName::Plain(ident) => Some(ident.name),
144-
_ => None,
145-
})
146-
.collect::<Vec<_>>();
147-
148-
let type_name = possible_names
149-
.iter()
150-
.find(|n| !used_names.contains(&Symbol::intern(n)))
151-
.unwrap_or(&"ParamName");
139+
let type_name = generics.next_type_param_name(None);
152140

153141
let mut sugg: Vec<_> =
154142
placeholder_types.iter().map(|sp| (*sp, (*type_name).to_string())).collect();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 bat<I, T: std::fmt::Debug>(t: T, constraints: impl Iterator, _: I) {
28+
for constraint in constraints {
29+
qux(t);
30+
qux(constraint);
31+
//~^ ERROR `<impl Iterator as std::iter::Iterator>::Item` doesn't implement `std::fmt::Debug`
32+
}
33+
}
34+
35+
fn bak(constraints: impl Iterator + std::fmt::Debug) {
36+
for constraint in constraints {
37+
qux(constraint);
38+
//~^ ERROR `<impl Iterator + std::fmt::Debug as std::iter::Iterator>::Item` doesn't implement
39+
}
40+
}
41+
42+
fn qux(_: impl std::fmt::Debug) {}
43+
44+
fn main() {}

0 commit comments

Comments
 (0)