Skip to content

Commit fd9133b

Browse files
committed
Suggest boxed trait objects in tail match and if expressions
When encountering a `match` or `if` as a tail expression where the different arms do not have the same type *and* the return type of that `fn` is an `impl Trait`, check whether those arms can implement `Trait` and if so, suggest using boxed trait objects.
1 parent c8ee337 commit fd9133b

File tree

6 files changed

+203
-12
lines changed

6 files changed

+203
-12
lines changed

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

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,11 +617,20 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
617617
ref prior_arms,
618618
last_ty,
619619
scrut_hir_id,
620+
suggest_box,
621+
arm_span,
620622
..
621623
}) => match source {
622624
hir::MatchSource::IfLetDesugar { .. } => {
623625
let msg = "`if let` arms have incompatible types";
624626
err.span_label(cause.span, msg);
627+
if let Some(ret_sp) = suggest_box {
628+
self.suggest_boxing_for_return_impl_trait(
629+
err,
630+
ret_sp,
631+
prior_arms.iter().chain(std::iter::once(&arm_span)).map(|s| *s),
632+
);
633+
}
625634
}
626635
hir::MatchSource::TryDesugar => {
627636
if let Some(ty::error::ExpectedFound { expected, .. }) = exp_found {
@@ -675,9 +684,23 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
675684
Applicability::MachineApplicable,
676685
);
677686
}
687+
if let Some(ret_sp) = suggest_box {
688+
// Get return type span and point to it.
689+
self.suggest_boxing_for_return_impl_trait(
690+
err,
691+
ret_sp,
692+
prior_arms.iter().chain(std::iter::once(&arm_span)).map(|s| *s),
693+
);
694+
}
678695
}
679696
},
680-
ObligationCauseCode::IfExpression(box IfExpressionCause { then, outer, semicolon }) => {
697+
ObligationCauseCode::IfExpression(box IfExpressionCause {
698+
then,
699+
else_sp,
700+
outer,
701+
semicolon,
702+
suggest_box,
703+
}) => {
681704
err.span_label(then, "expected because of this");
682705
if let Some(sp) = outer {
683706
err.span_label(sp, "`if` and `else` have incompatible types");
@@ -690,11 +713,52 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
690713
Applicability::MachineApplicable,
691714
);
692715
}
716+
if let Some(ret_sp) = suggest_box {
717+
self.suggest_boxing_for_return_impl_trait(
718+
err,
719+
ret_sp,
720+
vec![then, else_sp].into_iter(),
721+
);
722+
}
693723
}
694724
_ => (),
695725
}
696726
}
697727

728+
fn suggest_boxing_for_return_impl_trait(
729+
&self,
730+
err: &mut DiagnosticBuilder<'tcx>,
731+
return_sp: Span,
732+
arm_spans: impl Iterator<Item = Span>,
733+
) {
734+
let snippet = self
735+
.tcx
736+
.sess
737+
.source_map()
738+
.span_to_snippet(return_sp)
739+
.unwrap_or_else(|_| "dyn Trait".to_string());
740+
err.span_suggestion_verbose(
741+
return_sp,
742+
"you could change the return type to be a boxed trait object",
743+
format!("Box<dyn {}>", &snippet[5..]),
744+
Applicability::MaybeIncorrect,
745+
);
746+
let sugg = arm_spans
747+
.flat_map(|sp| {
748+
vec![
749+
(sp.shrink_to_lo(), "Box::new(".to_string()),
750+
(sp.shrink_to_hi(), ")".to_string()),
751+
]
752+
.into_iter()
753+
})
754+
.collect::<Vec<_>>();
755+
err.multipart_suggestion(
756+
"if you change the return type to expect trait objects box the returned expressions",
757+
sugg,
758+
Applicability::MaybeIncorrect,
759+
);
760+
}
761+
698762
/// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value`
699763
/// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and
700764
/// populate `other_value` with `other_ty`.

compiler/rustc_middle/src/traits/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,13 +350,16 @@ pub struct MatchExpressionArmCause<'tcx> {
350350
pub prior_arms: Vec<Span>,
351351
pub last_ty: Ty<'tcx>,
352352
pub scrut_hir_id: hir::HirId,
353+
pub suggest_box: Option<Span>,
353354
}
354355

355356
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
356357
pub struct IfExpressionCause {
357358
pub then: Span,
359+
pub else_sp: Span,
358360
pub outer: Option<Span>,
359361
pub semicolon: Option<Span>,
362+
pub suggest_box: Option<Span>,
360363
}
361364

362365
#[derive(Clone, Debug, PartialEq, Eq, Hash, Lift)]

compiler/rustc_typeck/src/check/_match.rs

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
11
use crate::check::coercion::CoerceMany;
22
use crate::check::{Diverges, Expectation, FnCtxt, Needs};
3-
use rustc_hir as hir;
4-
use rustc_hir::ExprKind;
3+
use rustc_hir::{self as hir, ExprKind};
54
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
6-
use rustc_middle::ty::Ty;
5+
use rustc_infer::traits::Obligation;
6+
use rustc_middle::ty::{self, ToPredicate, Ty};
77
use rustc_span::Span;
8-
use rustc_trait_selection::traits::ObligationCauseCode;
9-
use rustc_trait_selection::traits::{IfExpressionCause, MatchExpressionArmCause, ObligationCause};
8+
use rustc_trait_selection::opaque_types::InferCtxtExt as _;
9+
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
10+
use rustc_trait_selection::traits::{
11+
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
12+
};
1013

1114
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
1215
pub fn check_match(
1316
&self,
1417
expr: &'tcx hir::Expr<'tcx>,
1518
scrut: &'tcx hir::Expr<'tcx>,
1619
arms: &'tcx [hir::Arm<'tcx>],
17-
expected: Expectation<'tcx>,
20+
orig_expected: Expectation<'tcx>,
1821
match_src: hir::MatchSource,
1922
) -> Ty<'tcx> {
2023
let tcx = self.tcx;
2124

2225
use hir::MatchSource::*;
2326
let (source_if, if_no_else, force_scrutinee_bool) = match match_src {
2427
IfDesugar { contains_else_clause } => (true, !contains_else_clause, true),
25-
IfLetDesugar { contains_else_clause } => (true, !contains_else_clause, false),
28+
IfLetDesugar { contains_else_clause, .. } => (true, !contains_else_clause, false),
2629
WhileDesugar => (false, false, true),
2730
_ => (false, false, false),
2831
};
@@ -69,7 +72,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
6972
// type in that case)
7073
let mut all_arms_diverge = Diverges::WarnedAlways;
7174

72-
let expected = expected.adjust_for_branches(self);
75+
let expected = orig_expected.adjust_for_branches(self);
7376

7477
let mut coercion = {
7578
let coerce_first = match expected {
@@ -112,14 +115,74 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
112115
self.check_expr_with_expectation(&arm.body, expected)
113116
};
114117
all_arms_diverge &= self.diverges.get();
118+
119+
// When we have a `match` as a tail expression in a `fn` with a returned `impl Trait`
120+
// we check if the different arms would work with boxed trait objects instead and
121+
// provide a structured suggestion in that case.
122+
let suggest_box = match (
123+
orig_expected,
124+
self.body_id
125+
.owner
126+
.to_def_id()
127+
.as_local()
128+
.and_then(|id| self.ret_coercion_impl_trait.map(|ty| (id, ty))),
129+
) {
130+
(Expectation::ExpectHasType(expected), Some((id, ty)))
131+
if self.in_tail_expr && self.can_coerce(arm_ty, expected) =>
132+
{
133+
let impl_trait_ret_ty = self.infcx.instantiate_opaque_types(
134+
id,
135+
self.body_id,
136+
self.param_env,
137+
&ty,
138+
arm.body.span,
139+
);
140+
let mut suggest_box = !impl_trait_ret_ty.obligations.is_empty();
141+
for o in impl_trait_ret_ty.obligations {
142+
match o.predicate.skip_binders_unchecked() {
143+
ty::PredicateAtom::Trait(t, constness) => {
144+
let pred = ty::PredicateAtom::Trait(
145+
ty::TraitPredicate {
146+
trait_ref: ty::TraitRef {
147+
def_id: t.def_id(),
148+
substs: self.infcx.tcx.mk_substs_trait(arm_ty, &[]),
149+
},
150+
},
151+
constness,
152+
);
153+
let obl = Obligation::new(
154+
o.cause.clone(),
155+
self.param_env,
156+
pred.to_predicate(self.infcx.tcx),
157+
);
158+
suggest_box &= self.infcx.predicate_must_hold_modulo_regions(&obl);
159+
if !suggest_box {
160+
break;
161+
}
162+
}
163+
_ => {}
164+
}
165+
}
166+
if suggest_box { self.ret_type_span.clone() } else { None }
167+
}
168+
_ => None,
169+
};
170+
115171
if source_if {
116172
let then_expr = &arms[0].body;
117173
match (i, if_no_else) {
118174
(0, _) => coercion.coerce(self, &self.misc(expr.span), &arm.body, arm_ty),
119175
(_, true) => {} // Handled above to avoid duplicated type errors (#60254).
120176
(_, _) => {
121177
let then_ty = prior_arm_ty.unwrap();
122-
let cause = self.if_cause(expr.span, then_expr, &arm.body, then_ty, arm_ty);
178+
let cause = self.if_cause(
179+
expr.span,
180+
then_expr,
181+
&arm.body,
182+
then_ty,
183+
arm_ty,
184+
suggest_box,
185+
);
123186
coercion.coerce(self, &cause, &arm.body, arm_ty);
124187
}
125188
}
@@ -142,6 +205,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
142205
prior_arms: other_arms.clone(),
143206
last_ty: prior_arm_ty.unwrap(),
144207
scrut_hir_id: scrut.hir_id,
208+
suggest_box,
145209
}),
146210
),
147211
};
@@ -266,6 +330,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
266330
else_expr: &'tcx hir::Expr<'tcx>,
267331
then_ty: Ty<'tcx>,
268332
else_ty: Ty<'tcx>,
333+
suggest_box: Option<Span>,
269334
) -> ObligationCause<'tcx> {
270335
let mut outer_sp = if self.tcx.sess.source_map().is_multiline(span) {
271336
// The `if`/`else` isn't in one line in the output, include some context to make it
@@ -353,8 +418,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
353418
error_sp,
354419
ObligationCauseCode::IfExpression(box IfExpressionCause {
355420
then: then_sp,
421+
else_sp: error_sp,
356422
outer: outer_sp,
357423
semicolon: remove_semicolon,
424+
suggest_box,
358425
}),
359426
)
360427
}

compiler/rustc_typeck/src/check/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,14 @@ pub struct FnCtxt<'a, 'tcx> {
570570
/// any).
571571
ret_coercion: Option<RefCell<DynamicCoerceMany<'tcx>>>,
572572

573+
ret_coercion_impl_trait: Option<Ty<'tcx>>,
574+
575+
ret_type_span: Option<Span>,
576+
577+
/// Used exclusively to reduce cost of advanced evaluation used for
578+
/// more helpful diagnostics.
579+
in_tail_expr: bool,
580+
573581
/// First span of a return site that we find. Used in error messages.
574582
ret_coercion_span: RefCell<Option<Span>>,
575583

@@ -1302,10 +1310,15 @@ fn check_fn<'a, 'tcx>(
13021310
let hir = tcx.hir();
13031311

13041312
let declared_ret_ty = fn_sig.output();
1313+
13051314
let revealed_ret_ty =
13061315
fcx.instantiate_opaque_types_from_value(fn_id, &declared_ret_ty, decl.output.span());
13071316
debug!("check_fn: declared_ret_ty: {}, revealed_ret_ty: {}", declared_ret_ty, revealed_ret_ty);
13081317
fcx.ret_coercion = Some(RefCell::new(CoerceMany::new(revealed_ret_ty)));
1318+
fcx.ret_type_span = Some(decl.output.span());
1319+
if let ty::Opaque(..) = declared_ret_ty.kind() {
1320+
fcx.ret_coercion_impl_trait = Some(declared_ret_ty);
1321+
}
13091322
fn_sig = tcx.mk_fn_sig(
13101323
fn_sig.inputs().iter().cloned(),
13111324
revealed_ret_ty,
@@ -1366,6 +1379,7 @@ fn check_fn<'a, 'tcx>(
13661379

13671380
inherited.typeck_results.borrow_mut().liberated_fn_sigs_mut().insert(fn_id, fn_sig);
13681381

1382+
fcx.in_tail_expr = true;
13691383
if let ty::Dynamic(..) = declared_ret_ty.kind() {
13701384
// FIXME: We need to verify that the return type is `Sized` after the return expression has
13711385
// been evaluated so that we have types available for all the nodes being returned, but that
@@ -1385,6 +1399,7 @@ fn check_fn<'a, 'tcx>(
13851399
fcx.require_type_is_sized(declared_ret_ty, decl.output.span(), traits::SizedReturnType);
13861400
fcx.check_return_expr(&body.value);
13871401
}
1402+
fcx.in_tail_expr = false;
13881403

13891404
// We insert the deferred_generator_interiors entry after visiting the body.
13901405
// This ensures that all nested generators appear before the entry of this generator.
@@ -3084,6 +3099,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
30843099
param_env,
30853100
err_count_on_creation: inh.tcx.sess.err_count(),
30863101
ret_coercion: None,
3102+
ret_coercion_impl_trait: None,
3103+
ret_type_span: None,
3104+
in_tail_expr: false,
30873105
ret_coercion_span: RefCell::new(None),
30883106
resume_yield_tys: None,
30893107
ps: RefCell::new(UnsafetyState::function(hir::Unsafety::Normal, hir::CRATE_HIR_ID)),

src/test/ui/point-to-type-err-cause-on-impl-trait-return.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ fn cat() -> impl std::fmt::Display {
5353
1u32 //~ ERROR mismatched types
5454
}
5555
}
56-
}
56+
}
57+
58+
fn dog() -> impl std::fmt::Display {
59+
match 13 {
60+
0 => 0i32,
61+
1 => 1u32, //~ ERROR `match` arms have incompatible types
62+
_ => 2u32,
5763
}
5864
}
5965

src/test/ui/point-to-type-err-cause-on-impl-trait-return.stderr

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ LL | | 1u32
7272
| | ^^^^ expected `i32`, found `u32`
7373
LL | | }
7474
| |_____- `if` and `else` have incompatible types
75+
|
76+
help: you could change the return type to be a boxed trait object
77+
|
78+
LL | fn qux() -> Box<dyn std::fmt::Display> {
79+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
80+
help: if you change the return type to expect trait objects box the returned expressions
81+
|
82+
LL | Box::new(0i32)
83+
LL | } else {
84+
LL | Box::new(1u32)
85+
|
7586

7687
error[E0308]: mismatched types
7788
--> $DIR/point-to-type-err-cause-on-impl-trait-return.rs:35:14
@@ -136,6 +147,28 @@ help: you could change the return type to be a boxed trait object
136147
LL | fn cat() -> Box<dyn std::fmt::Display> {
137148
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
138149

139-
error: aborting due to 7 previous errors
150+
error[E0308]: `match` arms have incompatible types
151+
--> $DIR/point-to-type-err-cause-on-impl-trait-return.rs:61:14
152+
|
153+
LL | / match 13 {
154+
LL | | 0 => 0i32,
155+
| | ---- this is found to be of type `i32`
156+
LL | | 1 => 1u32,
157+
| | ^^^^ expected `i32`, found `u32`
158+
LL | | _ => 2u32,
159+
LL | | }
160+
| |_____- `match` arms have incompatible types
161+
|
162+
help: you could change the return type to be a boxed trait object
163+
|
164+
LL | fn dog() -> Box<dyn std::fmt::Display> {
165+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
166+
help: if you change the return type to expect trait objects box the returned expressions
167+
|
168+
LL | 0 => Box::new(0i32),
169+
LL | 1 => Box::new(1u32),
170+
|
171+
172+
error: aborting due to 8 previous errors
140173

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

0 commit comments

Comments
 (0)