Skip to content

Commit ce14d6d

Browse files
authored
Rollup merge of #70908 - estebank:suggest-add, r=nikomatsakis
Provide suggestions for type parameters missing bounds for associated types When implementing the binary operator traits it is easy to forget to restrict the `Output` associated type. `rustc` now accounts for different cases to lead users in the right direction to add the necessary restrictions. The structured suggestions in the following output are new: ``` error: equality constraints are not yet supported in `where` clauses --> $DIR/missing-bounds.rs:37:33 | LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B { | ^^^^^^^^^^^^^^^^^^^^^^ not supported | = note: see issue #20041 <#20041> for more information help: if `Output` is an associated type you're trying to set, use the associated type binding syntax | LL | impl<B: Add> Add for E<B> where B: Add<Output = B> { | ^^^^^^^^^^^^^^^^^ error[E0308]: mismatched types --> $DIR/missing-bounds.rs:11:11 | 7 | impl<B> Add for A<B> where B: Add { | - this type parameter ... 11 | A(self.0 + rhs.0) | ^^^^^^^^^^^^^^ expected type parameter `B`, found associated type | = note: expected type parameter `B` found associated type `<B as std::ops::Add>::Output` help: consider further restricting this bound | 7 | impl<B> Add for A<B> where B: Add + std::ops::Add<Output = B> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0369]: cannot add `B` to `B` --> $DIR/missing-bounds.rs:31:21 | 31 | Self(self.0 + rhs.0) | ------ ^ ----- B | | | B | help: consider restricting type parameter `B` | 27 | impl<B: std::ops::Add<Output = B>> Add for D<B> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``` That output is given for the following cases: ```rust struct A<B>(B); impl<B> Add for A<B> where B: Add { type Output = Self; fn add(self, rhs: Self) -> Self { A(self.0 + rhs.0) //~ ERROR mismatched types } } struct D<B>(B); impl<B> Add for D<B> { type Output = Self; fn add(self, rhs: Self) -> Self { Self(self.0 + rhs.0) //~ ERROR cannot add `B` to `B` } } struct E<B>(B); impl<B: Add> Add for E<B> where <B as Add>::Output = B { type Output = Self; fn add(self, rhs: Self) -> Self { Self(self.0 + rhs.0) } } ```
2 parents 1836e3b + b17b20c commit ce14d6d

File tree

16 files changed

+629
-224
lines changed

16 files changed

+629
-224
lines changed

src/librustc_ast_passes/ast_validation.rs

+85-11
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use rustc_session::Session;
2323
use rustc_span::symbol::{kw, sym};
2424
use rustc_span::Span;
2525
use std::mem;
26+
use std::ops::DerefMut;
2627

2728
const MORE_EXTERN: &str =
2829
"for more information, visit https://doc.rust-lang.org/std/keyword.extern.html";
@@ -1113,17 +1114,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
11131114

11141115
for predicate in &generics.where_clause.predicates {
11151116
if let WherePredicate::EqPredicate(ref predicate) = *predicate {
1116-
self.err_handler()
1117-
.struct_span_err(
1118-
predicate.span,
1119-
"equality constraints are not yet supported in `where` clauses",
1120-
)
1121-
.span_label(predicate.span, "not supported")
1122-
.note(
1123-
"see issue #20041 <https://github.com/rust-lang/rust/issues/20041> \
1124-
for more information",
1125-
)
1126-
.emit();
1117+
deny_equality_constraints(self, predicate, generics);
11271118
}
11281119
}
11291120

@@ -1300,6 +1291,89 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
13001291
}
13011292
}
13021293

1294+
/// When encountering an equality constraint in a `where` clause, emit an error. If the code seems
1295+
/// like it's setting an associated type, provide an appropriate suggestion.
1296+
fn deny_equality_constraints(
1297+
this: &mut AstValidator<'_>,
1298+
predicate: &WhereEqPredicate,
1299+
generics: &Generics,
1300+
) {
1301+
let mut err = this.err_handler().struct_span_err(
1302+
predicate.span,
1303+
"equality constraints are not yet supported in `where` clauses",
1304+
);
1305+
err.span_label(predicate.span, "not supported");
1306+
1307+
// Given `<A as Foo>::Bar = RhsTy`, suggest `A: Foo<Bar = RhsTy>`.
1308+
if let TyKind::Path(Some(qself), full_path) = &predicate.lhs_ty.kind {
1309+
if let TyKind::Path(None, path) = &qself.ty.kind {
1310+
match &path.segments[..] {
1311+
[PathSegment { ident, args: None, .. }] => {
1312+
for param in &generics.params {
1313+
if param.ident == *ident {
1314+
let param = ident;
1315+
match &full_path.segments[qself.position..] {
1316+
[PathSegment { ident, .. }] => {
1317+
// Make a new `Path` from `foo::Bar` to `Foo<Bar = RhsTy>`.
1318+
let mut assoc_path = full_path.clone();
1319+
// Remove `Bar` from `Foo::Bar`.
1320+
assoc_path.segments.pop();
1321+
let len = assoc_path.segments.len() - 1;
1322+
// Build `<Bar = RhsTy>`.
1323+
let arg = AngleBracketedArg::Constraint(AssocTyConstraint {
1324+
id: rustc_ast::node_id::DUMMY_NODE_ID,
1325+
ident: *ident,
1326+
kind: AssocTyConstraintKind::Equality {
1327+
ty: predicate.rhs_ty.clone(),
1328+
},
1329+
span: ident.span,
1330+
});
1331+
// Add `<Bar = RhsTy>` to `Foo`.
1332+
match &mut assoc_path.segments[len].args {
1333+
Some(args) => match args.deref_mut() {
1334+
GenericArgs::Parenthesized(_) => continue,
1335+
GenericArgs::AngleBracketed(args) => {
1336+
args.args.push(arg);
1337+
}
1338+
},
1339+
empty_args => {
1340+
*empty_args = AngleBracketedArgs {
1341+
span: ident.span,
1342+
args: vec![arg],
1343+
}
1344+
.into();
1345+
}
1346+
}
1347+
err.span_suggestion_verbose(
1348+
predicate.span,
1349+
&format!(
1350+
"if `{}` is an associated type you're trying to set, \
1351+
use the associated type binding syntax",
1352+
ident
1353+
),
1354+
format!(
1355+
"{}: {}",
1356+
param,
1357+
pprust::path_to_string(&assoc_path)
1358+
),
1359+
Applicability::MaybeIncorrect,
1360+
);
1361+
}
1362+
_ => {}
1363+
};
1364+
}
1365+
}
1366+
}
1367+
_ => {}
1368+
}
1369+
}
1370+
}
1371+
err.note(
1372+
"see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information",
1373+
);
1374+
err.emit();
1375+
}
1376+
13031377
pub fn check_crate(session: &Session, krate: &Crate, lints: &mut LintBuffer) -> bool {
13041378
let mut validator = AstValidator {
13051379
session,

src/librustc_hir/hir.rs

+35-1
Original file line numberDiff line numberDiff line change
@@ -2626,8 +2626,42 @@ impl Node<'_> {
26262626
match self {
26272627
Node::TraitItem(TraitItem { generics, .. })
26282628
| Node::ImplItem(ImplItem { generics, .. })
2629-
| Node::Item(Item { kind: ItemKind::Fn(_, generics, _), .. }) => Some(generics),
2629+
| Node::Item(Item {
2630+
kind:
2631+
ItemKind::Trait(_, _, generics, ..)
2632+
| ItemKind::Impl { generics, .. }
2633+
| ItemKind::Fn(_, generics, _),
2634+
..
2635+
}) => Some(generics),
26302636
_ => None,
26312637
}
26322638
}
2639+
2640+
pub fn hir_id(&self) -> Option<HirId> {
2641+
match self {
2642+
Node::Item(Item { hir_id, .. })
2643+
| Node::ForeignItem(ForeignItem { hir_id, .. })
2644+
| Node::TraitItem(TraitItem { hir_id, .. })
2645+
| Node::ImplItem(ImplItem { hir_id, .. })
2646+
| Node::Field(StructField { hir_id, .. })
2647+
| Node::AnonConst(AnonConst { hir_id, .. })
2648+
| Node::Expr(Expr { hir_id, .. })
2649+
| Node::Stmt(Stmt { hir_id, .. })
2650+
| Node::Ty(Ty { hir_id, .. })
2651+
| Node::Binding(Pat { hir_id, .. })
2652+
| Node::Pat(Pat { hir_id, .. })
2653+
| Node::Arm(Arm { hir_id, .. })
2654+
| Node::Block(Block { hir_id, .. })
2655+
| Node::Local(Local { hir_id, .. })
2656+
| Node::MacroDef(MacroDef { hir_id, .. })
2657+
| Node::Lifetime(Lifetime { hir_id, .. })
2658+
| Node::Param(Param { hir_id, .. })
2659+
| Node::GenericParam(GenericParam { hir_id, .. }) => Some(*hir_id),
2660+
Node::TraitRef(TraitRef { hir_ref_id, .. }) => Some(*hir_ref_id),
2661+
Node::PathSegment(PathSegment { hir_id, .. }) => *hir_id,
2662+
Node::Variant(Variant { id, .. }) => Some(*id),
2663+
Node::Ctor(variant) => variant.ctor_hir_id(),
2664+
Node::Crate(_) | Node::Visibility(_) => None,
2665+
}
2666+
}
26332667
}

src/librustc_middle/ty/diagnostics.rs

+183-1
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+
}

0 commit comments

Comments
 (0)