Skip to content

Commit 34f4f3d

Browse files
committed
Suggest boxing both arms of if expr if that solves divergent arms involving impl Trait
When encountering the following ```rust // run-rustfix trait Trait {} struct Struct; impl Trait for Struct {} fn foo() -> Box<dyn Trait> { Box::new(Struct) } fn bar() -> impl Trait { Struct } fn main() { let _ = if true { Struct } else { foo() //~ ERROR E0308 }; let _ = if true { foo() } else { Struct //~ ERROR E0308 }; let _ = if true { Struct } else { bar() // impl Trait }; let _ = if true { bar() // impl Trait } else { Struct }; } ``` suggest boxing both arms ```rust let _ = if true { Box::new(Struct) as Box<dyn Trait> } else { Box::new(bar()) }; let _ = if true { Box::new(bar()) as Box<dyn Trait> } else { Box::new(Struct) }; ```
1 parent ac56a2b commit 34f4f3d

File tree

4 files changed

+149
-19
lines changed

4 files changed

+149
-19
lines changed

compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs

+70-16
Original file line numberDiff line numberDiff line change
@@ -294,30 +294,84 @@ impl<T> Trait<T> for X {
294294
);
295295
}
296296
}
297-
(ty::Alias(ty::Opaque, alias), _) | (_, ty::Alias(ty::Opaque, alias))
298-
if alias.def_id.is_local()
297+
(_, ty::Alias(ty::Opaque, opaque_ty))
298+
| (ty::Alias(ty::Opaque, opaque_ty), _) => {
299+
if opaque_ty.def_id.is_local()
299300
&& matches!(
300301
tcx.def_kind(body_owner_def_id),
301302
DefKind::Fn
302303
| DefKind::Static(_)
303304
| DefKind::Const
304305
| DefKind::AssocFn
305306
| DefKind::AssocConst
306-
) =>
307-
{
308-
if tcx.is_type_alias_impl_trait(alias.def_id) {
309-
if !tcx
307+
)
308+
&& tcx.is_type_alias_impl_trait(opaque_ty.def_id)
309+
&& !tcx
310310
.opaque_types_defined_by(body_owner_def_id.expect_local())
311-
.contains(&alias.def_id.expect_local())
312-
{
313-
let sp = tcx
314-
.def_ident_span(body_owner_def_id)
315-
.unwrap_or_else(|| tcx.def_span(body_owner_def_id));
316-
diag.span_note(
317-
sp,
318-
"\
319-
this item must have the opaque type in its signature \
320-
in order to be able to register hidden types",
311+
.contains(&opaque_ty.def_id.expect_local())
312+
{
313+
let sp = tcx
314+
.def_ident_span(body_owner_def_id)
315+
.unwrap_or_else(|| tcx.def_span(body_owner_def_id));
316+
diag.span_note(
317+
sp,
318+
"this item must have the opaque type in its signature in order to \
319+
be able to register hidden types",
320+
);
321+
}
322+
// If two if arms can be coerced to a trait object, provide a structured
323+
// suggestion.
324+
let ObligationCauseCode::IfExpression(cause) = cause.code() else {
325+
return;
326+
};
327+
let hir::Node::Block(blk) = self.tcx.hir_node(cause.then_id) else {
328+
return;
329+
};
330+
let Some(then) = blk.expr else {
331+
return;
332+
};
333+
let hir::Node::Block(blk) = self.tcx.hir_node(cause.else_id) else {
334+
return;
335+
};
336+
let Some(else_) = blk.expr else {
337+
return;
338+
};
339+
let expected = match values.found.kind() {
340+
ty::Alias(..) => values.expected,
341+
_ => values.found,
342+
};
343+
let preds = tcx.explicit_item_bounds(opaque_ty.def_id);
344+
for (pred, _span) in preds.skip_binder() {
345+
let ty::ClauseKind::Trait(trait_predicate) = pred.kind().skip_binder()
346+
else {
347+
continue;
348+
};
349+
if trait_predicate.polarity != ty::ImplPolarity::Positive {
350+
continue;
351+
}
352+
let def_id = trait_predicate.def_id();
353+
let mut impl_def_ids = vec![];
354+
tcx.for_each_relevant_impl(def_id, expected, |did| {
355+
impl_def_ids.push(did)
356+
});
357+
if let [_] = &impl_def_ids[..] {
358+
let trait_name = tcx.item_name(def_id);
359+
diag.multipart_suggestion(
360+
format!(
361+
"`{expected}` implements `{trait_name}` so you can box \
362+
both arms and coerce to the trait object \
363+
`Box<dyn {trait_name}>`",
364+
),
365+
vec![
366+
(then.span.shrink_to_lo(), "Box::new(".to_string()),
367+
(
368+
then.span.shrink_to_hi(),
369+
format!(") as Box<dyn {}>", tcx.def_path_str(def_id)),
370+
),
371+
(else_.span.shrink_to_lo(), "Box::new(".to_string()),
372+
(else_.span.shrink_to_hi(), ")".to_string()),
373+
],
374+
MachineApplicable,
321375
);
322376
}
323377
}

tests/ui/typeck/suggest-box-on-divergent-if-else-arms.fixed

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ impl Trait for Struct {}
55
fn foo() -> Box<dyn Trait> {
66
Box::new(Struct)
77
}
8+
fn bar() -> impl Trait {
9+
Struct
10+
}
811
fn main() {
912
let _ = if true {
1013
Box::new(Struct)
@@ -16,4 +19,14 @@ fn main() {
1619
} else {
1720
Box::new(Struct) //~ ERROR E0308
1821
};
22+
let _ = if true {
23+
Box::new(Struct) as Box<dyn Trait>
24+
} else {
25+
Box::new(bar()) //~ ERROR E0308
26+
};
27+
let _ = if true {
28+
Box::new(bar()) as Box<dyn Trait>
29+
} else {
30+
Box::new(Struct) //~ ERROR E0308
31+
};
1932
}

tests/ui/typeck/suggest-box-on-divergent-if-else-arms.rs

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ impl Trait for Struct {}
55
fn foo() -> Box<dyn Trait> {
66
Box::new(Struct)
77
}
8+
fn bar() -> impl Trait {
9+
Struct
10+
}
811
fn main() {
912
let _ = if true {
1013
Struct
@@ -16,4 +19,14 @@ fn main() {
1619
} else {
1720
Struct //~ ERROR E0308
1821
};
22+
let _ = if true {
23+
Struct
24+
} else {
25+
bar() //~ ERROR E0308
26+
};
27+
let _ = if true {
28+
bar()
29+
} else {
30+
Struct //~ ERROR E0308
31+
};
1932
}

tests/ui/typeck/suggest-box-on-divergent-if-else-arms.stderr

+53-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0308]: `if` and `else` have incompatible types
2-
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:12:9
2+
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:15:9
33
|
44
LL | let _ = if true {
55
| _____________-
@@ -19,7 +19,7 @@ LL | Box::new(Struct)
1919
| +++++++++ +
2020

2121
error[E0308]: `if` and `else` have incompatible types
22-
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:17:9
22+
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:20:9
2323
|
2424
LL | let _ = if true {
2525
| _____________-
@@ -39,6 +39,56 @@ help: store this in the heap by calling `Box::new`
3939
LL | Box::new(Struct)
4040
| +++++++++ +
4141

42-
error: aborting due to 2 previous errors
42+
error[E0308]: `if` and `else` have incompatible types
43+
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:25:9
44+
|
45+
LL | fn bar() -> impl Trait {
46+
| ---------- the found opaque type
47+
...
48+
LL | let _ = if true {
49+
| _____________-
50+
LL | | Struct
51+
| | ------ expected because of this
52+
LL | | } else {
53+
LL | | bar()
54+
| | ^^^^^ expected `Struct`, found opaque type
55+
LL | | };
56+
| |_____- `if` and `else` have incompatible types
57+
|
58+
= note: expected struct `Struct`
59+
found opaque type `impl Trait`
60+
help: `Struct` implements `Trait` so you can box both arms and coerce to the trait object `Box<dyn Trait>`
61+
|
62+
LL ~ Box::new(Struct) as Box<dyn Trait>
63+
LL | } else {
64+
LL ~ Box::new(bar())
65+
|
66+
67+
error[E0308]: `if` and `else` have incompatible types
68+
--> $DIR/suggest-box-on-divergent-if-else-arms.rs:30:9
69+
|
70+
LL | fn bar() -> impl Trait {
71+
| ---------- the expected opaque type
72+
...
73+
LL | let _ = if true {
74+
| _____________-
75+
LL | | bar()
76+
| | ----- expected because of this
77+
LL | | } else {
78+
LL | | Struct
79+
| | ^^^^^^ expected opaque type, found `Struct`
80+
LL | | };
81+
| |_____- `if` and `else` have incompatible types
82+
|
83+
= note: expected opaque type `impl Trait`
84+
found struct `Struct`
85+
help: `Struct` implements `Trait` so you can box both arms and coerce to the trait object `Box<dyn Trait>`
86+
|
87+
LL ~ Box::new(bar()) as Box<dyn Trait>
88+
LL | } else {
89+
LL ~ Box::new(Struct)
90+
|
91+
92+
error: aborting due to 4 previous errors
4393

4494
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)