Skip to content

Commit b3eaccb

Browse files
committed
Suggest restricting type param when it doesn't satisfy projection
When encountering a projection that isn't satisfied by a type parameter, suggest constraining the type parameter.
1 parent 2e89ade commit b3eaccb

File tree

11 files changed

+358
-194
lines changed

11 files changed

+358
-194
lines changed

src/librustc_middle/ty/diagnostics.rs

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
33
use crate::ty::sty::InferTy;
44
use crate::ty::TyKind::*;
5-
use crate::ty::TyS;
5+
use crate::ty::{TyCtxt, TyS};
6+
use rustc_errors::{Applicability, DiagnosticBuilder};
7+
use rustc_hir as hir;
8+
use rustc_hir::def_id::DefId;
9+
use rustc_hir::{QPath, TyKind, WhereBoundPredicate, WherePredicate};
10+
use rustc_span::{BytePos, Span};
611

712
impl<'tcx> TyS<'tcx> {
813
/// Similar to `TyS::is_primitive`, but also considers inferred numeric values to be primitive.
@@ -67,3 +72,180 @@ impl<'tcx> TyS<'tcx> {
6772
}
6873
}
6974
}
75+
76+
/// Suggest restricting a type param with a new bound.
77+
pub fn suggest_constraining_type_param(
78+
tcx: TyCtxt<'_>,
79+
generics: &hir::Generics<'_>,
80+
err: &mut DiagnosticBuilder<'_>,
81+
param_name: &str,
82+
constraint: &str,
83+
def_id: Option<DefId>,
84+
) -> bool {
85+
let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
86+
87+
let param = if let Some(param) = param {
88+
param
89+
} else {
90+
return false;
91+
};
92+
93+
const MSG_RESTRICT_BOUND_FURTHER: &str = "consider further restricting this bound";
94+
let msg_restrict_type = format!("consider restricting type parameter `{}`", param_name);
95+
let msg_restrict_type_further =
96+
format!("consider further restricting type parameter `{}`", param_name);
97+
98+
if def_id == tcx.lang_items().sized_trait() {
99+
// Type parameters are already `Sized` by default.
100+
err.span_label(param.span, &format!("this type parameter needs to be `{}`", constraint));
101+
return true;
102+
}
103+
let mut suggest_restrict = |span| {
104+
err.span_suggestion_verbose(
105+
span,
106+
MSG_RESTRICT_BOUND_FURTHER,
107+
format!(" + {}", constraint),
108+
Applicability::MachineApplicable,
109+
);
110+
};
111+
112+
if param_name.starts_with("impl ") {
113+
// If there's an `impl Trait` used in argument position, suggest
114+
// restricting it:
115+
//
116+
// fn foo(t: impl Foo) { ... }
117+
// --------
118+
// |
119+
// help: consider further restricting this bound with `+ Bar`
120+
//
121+
// Suggestion for tools in this case is:
122+
//
123+
// fn foo(t: impl Foo) { ... }
124+
// --------
125+
// |
126+
// replace with: `impl Foo + Bar`
127+
128+
suggest_restrict(param.span.shrink_to_hi());
129+
return true;
130+
}
131+
132+
if generics.where_clause.predicates.is_empty()
133+
// Given `trait Base<T = String>: Super<T>` where `T: Copy`, suggest restricting in the
134+
// `where` clause instead of `trait Base<T: Copy = String>: Super<T>`.
135+
&& !matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. })
136+
{
137+
if let Some(bounds_span) = param.bounds_span() {
138+
// If user has provided some bounds, suggest restricting them:
139+
//
140+
// fn foo<T: Foo>(t: T) { ... }
141+
// ---
142+
// |
143+
// help: consider further restricting this bound with `+ Bar`
144+
//
145+
// Suggestion for tools in this case is:
146+
//
147+
// fn foo<T: Foo>(t: T) { ... }
148+
// --
149+
// |
150+
// replace with: `T: Bar +`
151+
suggest_restrict(bounds_span.shrink_to_hi());
152+
} else {
153+
// If user hasn't provided any bounds, suggest adding a new one:
154+
//
155+
// fn foo<T>(t: T) { ... }
156+
// - help: consider restricting this type parameter with `T: Foo`
157+
err.span_suggestion_verbose(
158+
param.span.shrink_to_hi(),
159+
&msg_restrict_type,
160+
format!(": {}", constraint),
161+
Applicability::MachineApplicable,
162+
);
163+
}
164+
165+
true
166+
} else {
167+
// This part is a bit tricky, because using the `where` clause user can
168+
// provide zero, one or many bounds for the same type parameter, so we
169+
// have following cases to consider:
170+
//
171+
// 1) When the type parameter has been provided zero bounds
172+
//
173+
// Message:
174+
// fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
175+
// - help: consider restricting this type parameter with `where X: Bar`
176+
//
177+
// Suggestion:
178+
// fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
179+
// - insert: `, X: Bar`
180+
//
181+
//
182+
// 2) When the type parameter has been provided one bound
183+
//
184+
// Message:
185+
// fn foo<T>(t: T) where T: Foo { ... }
186+
// ^^^^^^
187+
// |
188+
// help: consider further restricting this bound with `+ Bar`
189+
//
190+
// Suggestion:
191+
// fn foo<T>(t: T) where T: Foo { ... }
192+
// ^^
193+
// |
194+
// replace with: `T: Bar +`
195+
//
196+
//
197+
// 3) When the type parameter has been provided many bounds
198+
//
199+
// Message:
200+
// fn foo<T>(t: T) where T: Foo, T: Bar {... }
201+
// - help: consider further restricting this type parameter with `where T: Zar`
202+
//
203+
// Suggestion:
204+
// fn foo<T>(t: T) where T: Foo, T: Bar {... }
205+
// - insert: `, T: Zar`
206+
207+
let mut param_spans = Vec::new();
208+
209+
for predicate in generics.where_clause.predicates {
210+
if let WherePredicate::BoundPredicate(WhereBoundPredicate {
211+
span, bounded_ty, ..
212+
}) = predicate
213+
{
214+
if let TyKind::Path(QPath::Resolved(_, path)) = &bounded_ty.kind {
215+
if let Some(segment) = path.segments.first() {
216+
if segment.ident.to_string() == param_name {
217+
param_spans.push(span);
218+
}
219+
}
220+
}
221+
}
222+
}
223+
224+
let where_clause_span = generics.where_clause.span_for_predicates_or_empty_place();
225+
// Account for `fn foo<T>(t: T) where T: Foo,` so we don't suggest two trailing commas.
226+
let mut trailing_comma = false;
227+
if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(where_clause_span) {
228+
trailing_comma = snippet.ends_with(',');
229+
}
230+
let where_clause_span = if trailing_comma {
231+
let hi = where_clause_span.hi();
232+
Span::new(hi - BytePos(1), hi, where_clause_span.ctxt())
233+
} else {
234+
where_clause_span.shrink_to_hi()
235+
};
236+
237+
match &param_spans[..] {
238+
&[&param_span] => suggest_restrict(param_span.shrink_to_hi()),
239+
_ => {
240+
err.span_suggestion_verbose(
241+
where_clause_span,
242+
&msg_restrict_type_further,
243+
format!(", {}: {}", param_name, constraint),
244+
Applicability::MachineApplicable,
245+
);
246+
}
247+
}
248+
249+
true
250+
}
251+
}

src/librustc_middle/ty/error.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::ty::diagnostics::suggest_constraining_type_param;
12
use crate::ty::{self, BoundRegion, Region, Ty, TyCtxt};
23
use rustc_ast::ast;
34
use rustc_errors::{pluralize, Applicability, DiagnosticBuilder};
@@ -389,8 +390,43 @@ impl<'tcx> TyCtxt<'tcx> {
389390
(ty::Projection(_), ty::Projection(_)) => {
390391
db.note("an associated type was expected, but a different one was found");
391392
}
392-
(ty::Param(_), ty::Projection(_)) | (ty::Projection(_), ty::Param(_)) => {
393-
db.note("you might be missing a type parameter or trait bound");
393+
(ty::Param(p), ty::Projection(proj)) | (ty::Projection(proj), ty::Param(p)) => {
394+
let generics = self.generics_of(body_owner_def_id);
395+
let p_span = self.def_span(generics.type_param(p, self).def_id);
396+
if !sp.contains(p_span) {
397+
db.span_label(p_span, "this type parameter");
398+
}
399+
let hir = self.hir();
400+
let mut note = true;
401+
if let Some(generics) = hir
402+
.as_local_hir_id(generics.type_param(p, self).def_id)
403+
.and_then(|id| self.hir().find(self.hir().get_parent_node(id)))
404+
.as_ref()
405+
.and_then(|node| node.generics())
406+
{
407+
// Synthesize the associated type restriction `Add<Output = Expected>`.
408+
// FIXME: extract this logic for use in other diagnostics.
409+
let trait_ref = proj.trait_ref(self);
410+
let path =
411+
self.def_path_str_with_substs(trait_ref.def_id, trait_ref.substs);
412+
let item_name = self.item_name(proj.item_def_id);
413+
let path = if path.ends_with('>') {
414+
format!("{}, {} = {}>", &path[..path.len() - 1], item_name, p)
415+
} else {
416+
format!("{}<{} = {}>", path, item_name, p)
417+
};
418+
note = !suggest_constraining_type_param(
419+
self,
420+
generics,
421+
db,
422+
&format!("{}", proj.self_ty()),
423+
&path,
424+
None,
425+
);
426+
}
427+
if note {
428+
db.note("you might be missing a type parameter or trait bound");
429+
}
394430
}
395431
(ty::Param(p), _) | (_, ty::Param(p)) => {
396432
let generics = self.generics_of(body_owner_def_id);

src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ use rustc_middle::mir::{
99
FakeReadCause, Local, LocalDecl, LocalInfo, LocalKind, Location, Operand, Place, PlaceRef,
1010
ProjectionElem, Rvalue, Statement, StatementKind, TerminatorKind, VarBindingForm,
1111
};
12-
use rustc_middle::ty::{self, Ty};
12+
use rustc_middle::ty::{self, suggest_constraining_type_param, Ty};
1313
use rustc_span::source_map::DesugaringKind;
1414
use rustc_span::Span;
15-
use rustc_trait_selection::traits::error_reporting::suggest_constraining_type_param;
1615

1716
use crate::dataflow::drop_flag_effects;
1817
use crate::dataflow::indexes::{MoveOutIndex, MovePathIndex};

0 commit comments

Comments
 (0)