Skip to content

Commit 75f066d

Browse files
committed
Handle binop on unbound type param
When encountering a binary operation involving a type parameter that has no bindings, suggest adding the appropriate bound.
1 parent 6318d24 commit 75f066d

File tree

4 files changed

+110
-16
lines changed

4 files changed

+110
-16
lines changed

src/librustc_hir/hir.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2626,8 +2626,38 @@ impl Node<'_> {
26262626
match self {
26272627
Node::TraitItem(TraitItem { generics, .. })
26282628
| Node::ImplItem(ImplItem { generics, .. })
2629+
| Node::Item(Item { kind: ItemKind::Trait(_, _, generics, ..), .. })
2630+
| Node::Item(Item { kind: ItemKind::Impl { generics, .. }, .. })
26292631
| Node::Item(Item { kind: ItemKind::Fn(_, generics, _), .. }) => Some(generics),
26302632
_ => None,
26312633
}
26322634
}
2635+
2636+
pub fn hir_id(&self) -> Option<HirId> {
2637+
match self {
2638+
Node::Item(Item { hir_id, .. })
2639+
| Node::ForeignItem(ForeignItem { hir_id, .. })
2640+
| Node::TraitItem(TraitItem { hir_id, .. })
2641+
| Node::ImplItem(ImplItem { hir_id, .. })
2642+
| Node::Field(StructField { hir_id, .. })
2643+
| Node::AnonConst(AnonConst { hir_id, .. })
2644+
| Node::Expr(Expr { hir_id, .. })
2645+
| Node::Stmt(Stmt { hir_id, .. })
2646+
| Node::Ty(Ty { hir_id, .. })
2647+
| Node::Binding(Pat { hir_id, .. })
2648+
| Node::Pat(Pat { hir_id, .. })
2649+
| Node::Arm(Arm { hir_id, .. })
2650+
| Node::Block(Block { hir_id, .. })
2651+
| Node::Local(Local { hir_id, .. })
2652+
| Node::MacroDef(MacroDef { hir_id, .. })
2653+
| Node::Lifetime(Lifetime { hir_id, .. })
2654+
| Node::Param(Param { hir_id, .. })
2655+
| Node::GenericParam(GenericParam { hir_id, .. }) => Some(*hir_id),
2656+
Node::TraitRef(TraitRef { hir_ref_id, .. }) => Some(*hir_ref_id),
2657+
Node::PathSegment(PathSegment { hir_id, .. }) => *hir_id,
2658+
Node::Variant(Variant { id, .. }) => Some(*id),
2659+
Node::Ctor(variant) => variant.ctor_hir_id(),
2660+
Node::Crate(_) | Node::Visibility(_) => None,
2661+
}
2662+
}
26332663
}

src/librustc_typeck/check/op.rs

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use rustc_middle::ty::TyKind::{Adt, Array, Char, FnDef, Never, Ref, Str, Tuple,
1313
use rustc_middle::ty::{self, Ty, TypeFoldable};
1414
use rustc_span::Span;
1515
use rustc_trait_selection::infer::InferCtxtExt;
16+
use rustc_trait_selection::traits::error_reporting::suggest_constraining_type_param;
1617

1718
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
1819
/// Checks a `a <op>= b`
@@ -253,6 +254,50 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
253254
// error types are considered "builtin"
254255
if !lhs_ty.references_error() {
255256
let source_map = self.tcx.sess.source_map();
257+
258+
let suggest_constraining_param =
259+
|mut err: &mut DiagnosticBuilder<'_>,
260+
missing_trait: &str,
261+
p: ty::ParamTy,
262+
set_output: bool| {
263+
let hir = self.tcx.hir();
264+
let msg =
265+
&format!("`{}` might need a bound for `{}`", lhs_ty, missing_trait);
266+
if let Some(def_id) = hir
267+
.find(hir.get_parent_item(expr.hir_id))
268+
.and_then(|node| node.hir_id())
269+
.and_then(|hir_id| hir.opt_local_def_id(hir_id))
270+
{
271+
let generics = self.tcx.generics_of(def_id);
272+
let param_def_id = generics.type_param(&p, self.tcx).def_id;
273+
if let Some(generics) = hir
274+
.as_local_hir_id(param_def_id)
275+
.and_then(|id| hir.find(hir.get_parent_item(id)))
276+
.as_ref()
277+
.and_then(|node| node.generics())
278+
{
279+
let output = if set_output {
280+
format!("<Output = {}>", rhs_ty)
281+
} else {
282+
String::new()
283+
};
284+
suggest_constraining_type_param(
285+
self.tcx,
286+
generics,
287+
&mut err,
288+
&format!("{}", lhs_ty),
289+
&format!("{}{}", missing_trait, output),
290+
None,
291+
);
292+
} else {
293+
let span = self.tcx.def_span(param_def_id);
294+
err.span_label(span, msg);
295+
}
296+
} else {
297+
err.note(&msg);
298+
}
299+
};
300+
256301
match is_assign {
257302
IsAssign::Yes => {
258303
let mut err = struct_span_err!(
@@ -317,59 +362,65 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
317362
// This has nothing here because it means we did string
318363
// concatenation (e.g., "Hello " += "World!"). This means
319364
// we don't want the note in the else clause to be emitted
320-
} else if let ty::Param(_) = lhs_ty.kind {
321-
// FIXME: point to span of param
322-
err.note(&format!(
323-
"`{}` might need a bound for `{}`",
324-
lhs_ty, missing_trait
325-
));
365+
} else if let ty::Param(p) = lhs_ty.kind {
366+
suggest_constraining_param(&mut err, missing_trait, p, false);
326367
} else if !suggested_deref {
327368
suggest_impl_missing(&mut err, lhs_ty, &missing_trait);
328369
}
329370
}
330371
err.emit();
331372
}
332373
IsAssign::No => {
333-
let (message, missing_trait) = match op.node {
374+
let (message, missing_trait, use_output) = match op.node {
334375
hir::BinOpKind::Add => (
335376
format!("cannot add `{}` to `{}`", rhs_ty, lhs_ty),
336377
Some("std::ops::Add"),
378+
true,
337379
),
338380
hir::BinOpKind::Sub => (
339381
format!("cannot subtract `{}` from `{}`", rhs_ty, lhs_ty),
340382
Some("std::ops::Sub"),
383+
true,
341384
),
342385
hir::BinOpKind::Mul => (
343386
format!("cannot multiply `{}` to `{}`", rhs_ty, lhs_ty),
344387
Some("std::ops::Mul"),
388+
true,
345389
),
346390
hir::BinOpKind::Div => (
347391
format!("cannot divide `{}` by `{}`", lhs_ty, rhs_ty),
348392
Some("std::ops::Div"),
393+
true,
349394
),
350395
hir::BinOpKind::Rem => (
351396
format!("cannot mod `{}` by `{}`", lhs_ty, rhs_ty),
352397
Some("std::ops::Rem"),
398+
true,
353399
),
354400
hir::BinOpKind::BitAnd => (
355401
format!("no implementation for `{} & {}`", lhs_ty, rhs_ty),
356402
Some("std::ops::BitAnd"),
403+
true,
357404
),
358405
hir::BinOpKind::BitXor => (
359406
format!("no implementation for `{} ^ {}`", lhs_ty, rhs_ty),
360407
Some("std::ops::BitXor"),
408+
true,
361409
),
362410
hir::BinOpKind::BitOr => (
363411
format!("no implementation for `{} | {}`", lhs_ty, rhs_ty),
364412
Some("std::ops::BitOr"),
413+
true,
365414
),
366415
hir::BinOpKind::Shl => (
367416
format!("no implementation for `{} << {}`", lhs_ty, rhs_ty),
368417
Some("std::ops::Shl"),
418+
true,
369419
),
370420
hir::BinOpKind::Shr => (
371421
format!("no implementation for `{} >> {}`", lhs_ty, rhs_ty),
372422
Some("std::ops::Shr"),
423+
true,
373424
),
374425
hir::BinOpKind::Eq | hir::BinOpKind::Ne => (
375426
format!(
@@ -378,6 +429,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
378429
lhs_ty
379430
),
380431
Some("std::cmp::PartialEq"),
432+
false,
381433
),
382434
hir::BinOpKind::Lt
383435
| hir::BinOpKind::Le
@@ -389,6 +441,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
389441
lhs_ty
390442
),
391443
Some("std::cmp::PartialOrd"),
444+
false,
392445
),
393446
_ => (
394447
format!(
@@ -397,6 +450,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
397450
lhs_ty
398451
),
399452
None,
453+
false,
400454
),
401455
};
402456
let mut err = struct_span_err!(
@@ -459,12 +513,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
459513
// This has nothing here because it means we did string
460514
// concatenation (e.g., "Hello " + "World!"). This means
461515
// we don't want the note in the else clause to be emitted
462-
} else if let ty::Param(_) = lhs_ty.kind {
463-
// FIXME: point to span of param
464-
err.note(&format!(
465-
"`{}` might need a bound for `{}`",
466-
lhs_ty, missing_trait
467-
));
516+
} else if let ty::Param(p) = lhs_ty.kind {
517+
suggest_constraining_param(
518+
&mut err,
519+
missing_trait,
520+
p,
521+
use_output,
522+
);
468523
} else if !suggested_deref && !involves_fn {
469524
suggest_impl_missing(&mut err, lhs_ty, &missing_trait);
470525
}

src/test/ui/issues/issue-6738.stderr

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ LL | self.x += v.x;
66
| |
77
| cannot use `+=` on type `T`
88
|
9-
= note: `T` might need a bound for `std::ops::AddAssign`
9+
help: consider restricting type parameter `T`
10+
|
11+
LL | impl<T: std::ops::AddAssign> Foo<T> {
12+
| ^^^^^^^^^^^^^^^^^^^^^
1013

1114
error: aborting due to previous error
1215

src/test/ui/type/type-check/missing_trait_impl.stderr

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ LL | let z = x + y;
66
| |
77
| T
88
|
9-
= note: `T` might need a bound for `std::ops::Add`
9+
help: consider restricting type parameter `T`
10+
|
11+
LL | fn foo<T: std::ops::Add<Output = T>>(x: T, y: T) {
12+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1013

1114
error[E0368]: binary assignment operation `+=` cannot be applied to type `T`
1215
--> $DIR/missing_trait_impl.rs:9:5
@@ -16,7 +19,10 @@ LL | x += x;
1619
| |
1720
| cannot use `+=` on type `T`
1821
|
19-
= note: `T` might need a bound for `std::ops::AddAssign`
22+
help: consider restricting type parameter `T`
23+
|
24+
LL | fn bar<T: std::ops::AddAssign>(x: T) {
25+
| ^^^^^^^^^^^^^^^^^^^^^
2026

2127
error: aborting due to 2 previous errors
2228

0 commit comments

Comments
 (0)