Skip to content

Commit 41dc394

Browse files
committed
Auto merge of #75608 - estebank:suggest-boxed-match-exprs, r=lcnr,varkor
More structured suggestions for boxed trait objects instead of impl Trait on non-coerceable tail 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. Use structured suggestion for `impl T` to `Box<dyn T>`. Fix #69107
2 parents bb0067c + c6f2ddf commit 41dc394

File tree

9 files changed

+466
-58
lines changed

9 files changed

+466
-58
lines changed

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

+62-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ use rustc_middle::ty::{
7070
subst::{Subst, SubstsRef},
7171
Region, Ty, TyCtxt, TypeFoldable,
7272
};
73-
use rustc_span::{DesugaringKind, Pos, Span};
73+
use rustc_span::{BytePos, DesugaringKind, Pos, Span};
7474
use rustc_target::spec::abi;
7575
use std::{cmp, fmt};
7676

@@ -617,11 +617,20 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
617617
ref prior_arms,
618618
last_ty,
619619
scrut_hir_id,
620+
opt_suggest_box_span,
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) = opt_suggest_box_span {
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) = opt_suggest_box_span {
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+
opt_suggest_box_span,
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,48 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
690713
Applicability::MachineApplicable,
691714
);
692715
}
716+
if let Some(ret_sp) = opt_suggest_box_span {
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+
err.multipart_suggestion(
735+
"you could change the return type to be a boxed trait object",
736+
vec![
737+
(return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()),
738+
(return_sp.shrink_to_hi(), ">".to_string()),
739+
],
740+
Applicability::MaybeIncorrect,
741+
);
742+
let sugg = arm_spans
743+
.flat_map(|sp| {
744+
vec![
745+
(sp.shrink_to_lo(), "Box::new(".to_string()),
746+
(sp.shrink_to_hi(), ")".to_string()),
747+
]
748+
.into_iter()
749+
})
750+
.collect::<Vec<_>>();
751+
err.multipart_suggestion(
752+
"if you change the return type to expect trait objects, box the returned expressions",
753+
sugg,
754+
Applicability::MaybeIncorrect,
755+
);
756+
}
757+
698758
/// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value`
699759
/// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and
700760
/// populate `other_value` with `other_ty`.

compiler/rustc_middle/src/traits/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -348,13 +348,16 @@ pub struct MatchExpressionArmCause<'tcx> {
348348
pub prior_arms: Vec<Span>,
349349
pub last_ty: Ty<'tcx>,
350350
pub scrut_hir_id: hir::HirId,
351+
pub opt_suggest_box_span: Option<Span>,
351352
}
352353

353354
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
354355
pub struct IfExpressionCause {
355356
pub then: Span,
357+
pub else_sp: Span,
356358
pub outer: Option<Span>,
357359
pub semicolon: Option<Span>,
360+
pub opt_suggest_box_span: Option<Span>,
358361
}
359362

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

compiler/rustc_typeck/src/check/_match.rs

+77-9
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;
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,75 @@ 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 opt_suggest_box_span = match (
123+
orig_expected,
124+
self.ret_coercion_impl_trait.map(|ty| (self.body_id.owner, ty)),
125+
) {
126+
(Expectation::ExpectHasType(expected), Some((id, ty)))
127+
if self.in_tail_expr && self.can_coerce(arm_ty, expected) =>
128+
{
129+
let impl_trait_ret_ty = self.infcx.instantiate_opaque_types(
130+
id,
131+
self.body_id,
132+
self.param_env,
133+
&ty,
134+
arm.body.span,
135+
);
136+
let mut suggest_box = !impl_trait_ret_ty.obligations.is_empty();
137+
for o in impl_trait_ret_ty.obligations {
138+
match o.predicate.skip_binders_unchecked() {
139+
ty::PredicateAtom::Trait(t, constness) => {
140+
let pred = ty::PredicateAtom::Trait(
141+
ty::TraitPredicate {
142+
trait_ref: ty::TraitRef {
143+
def_id: t.def_id(),
144+
substs: self.infcx.tcx.mk_substs_trait(arm_ty, &[]),
145+
},
146+
},
147+
constness,
148+
);
149+
let obl = Obligation::new(
150+
o.cause.clone(),
151+
self.param_env,
152+
pred.to_predicate(self.infcx.tcx),
153+
);
154+
suggest_box &= self.infcx.predicate_must_hold_modulo_regions(&obl);
155+
if !suggest_box {
156+
// We've encountered some obligation that didn't hold, so the
157+
// return expression can't just be boxed. We don't need to
158+
// evaluate the rest of the obligations.
159+
break;
160+
}
161+
}
162+
_ => {}
163+
}
164+
}
165+
// If all the obligations hold (or there are no obligations) the tail expression
166+
// we can suggest to return a boxed trait object instead of an opaque type.
167+
if suggest_box { self.ret_type_span.clone() } else { None }
168+
}
169+
_ => None,
170+
};
171+
115172
if source_if {
116173
let then_expr = &arms[0].body;
117174
match (i, if_no_else) {
118175
(0, _) => coercion.coerce(self, &self.misc(expr.span), &arm.body, arm_ty),
119176
(_, true) => {} // Handled above to avoid duplicated type errors (#60254).
120177
(_, _) => {
121178
let then_ty = prior_arm_ty.unwrap();
122-
let cause = self.if_cause(expr.span, then_expr, &arm.body, then_ty, arm_ty);
179+
let cause = self.if_cause(
180+
expr.span,
181+
then_expr,
182+
&arm.body,
183+
then_ty,
184+
arm_ty,
185+
opt_suggest_box_span,
186+
);
123187
coercion.coerce(self, &cause, &arm.body, arm_ty);
124188
}
125189
}
@@ -142,6 +206,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
142206
prior_arms: other_arms.clone(),
143207
last_ty: prior_arm_ty.unwrap(),
144208
scrut_hir_id: scrut.hir_id,
209+
opt_suggest_box_span,
145210
}),
146211
),
147212
};
@@ -266,6 +331,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
266331
else_expr: &'tcx hir::Expr<'tcx>,
267332
then_ty: Ty<'tcx>,
268333
else_ty: Ty<'tcx>,
334+
opt_suggest_box_span: Option<Span>,
269335
) -> ObligationCause<'tcx> {
270336
let mut outer_sp = if self.tcx.sess.source_map().is_multiline(span) {
271337
// The `if`/`else` isn't in one line in the output, include some context to make it
@@ -353,8 +419,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
353419
error_sp,
354420
ObligationCauseCode::IfExpression(box IfExpressionCause {
355421
then: then_sp,
422+
else_sp: error_sp,
356423
outer: outer_sp,
357424
semicolon: remove_semicolon,
425+
opt_suggest_box_span,
358426
}),
359427
)
360428
}

compiler/rustc_typeck/src/check/coercion.rs

+29-8
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
3838
use crate::astconv::AstConv;
3939
use crate::check::FnCtxt;
40-
use rustc_errors::{struct_span_err, DiagnosticBuilder};
40+
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
4141
use rustc_hir as hir;
4242
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
4343
use rustc_infer::infer::{Coercion, InferOk, InferResult};
@@ -51,7 +51,7 @@ use rustc_middle::ty::subst::SubstsRef;
5151
use rustc_middle::ty::{self, Ty, TypeAndMut};
5252
use rustc_session::parse::feature_err;
5353
use rustc_span::symbol::sym;
54-
use rustc_span::{self, Span};
54+
use rustc_span::{self, BytePos, Span};
5555
use rustc_target::spec::abi::Abi;
5656
use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
5757
use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode};
@@ -1459,14 +1459,15 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
14591459
}
14601460
}
14611461
if let (Some(sp), Some(fn_output)) = (fcx.ret_coercion_span.borrow().as_ref(), fn_output) {
1462-
self.add_impl_trait_explanation(&mut err, fcx, expected, *sp, fn_output);
1462+
self.add_impl_trait_explanation(&mut err, cause, fcx, expected, *sp, fn_output);
14631463
}
14641464
err
14651465
}
14661466

14671467
fn add_impl_trait_explanation<'a>(
14681468
&self,
14691469
err: &mut DiagnosticBuilder<'a>,
1470+
cause: &ObligationCause<'tcx>,
14701471
fcx: &FnCtxt<'a, 'tcx>,
14711472
expected: Ty<'tcx>,
14721473
sp: Span,
@@ -1523,10 +1524,30 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
15231524
};
15241525
if has_impl {
15251526
if is_object_safe {
1526-
err.help(&format!(
1527-
"you can instead return a boxed trait object using `Box<dyn {}>`",
1528-
&snippet[5..]
1529-
));
1527+
err.multipart_suggestion(
1528+
"you could change the return type to be a boxed trait object",
1529+
vec![
1530+
(return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()),
1531+
(return_sp.shrink_to_hi(), ">".to_string()),
1532+
],
1533+
Applicability::MachineApplicable,
1534+
);
1535+
let sugg = vec![sp, cause.span]
1536+
.into_iter()
1537+
.flat_map(|sp| {
1538+
vec![
1539+
(sp.shrink_to_lo(), "Box::new(".to_string()),
1540+
(sp.shrink_to_hi(), ")".to_string()),
1541+
]
1542+
.into_iter()
1543+
})
1544+
.collect::<Vec<_>>();
1545+
err.multipart_suggestion(
1546+
"if you change the return type to expect trait objects, box the returned \
1547+
expressions",
1548+
sugg,
1549+
Applicability::MaybeIncorrect,
1550+
);
15301551
} else {
15311552
err.help(&format!(
15321553
"if the trait `{}` were object safe, you could return a boxed trait object",
@@ -1535,7 +1556,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
15351556
}
15361557
err.note(trait_obj_msg);
15371558
}
1538-
err.help("alternatively, create a new `enum` with a variant for each returned type");
1559+
err.help("you could instead create a new `enum` with a variant for each returned type");
15391560
}
15401561

15411562
fn is_return_ty_unsized(&self, fcx: &FnCtxt<'a, 'tcx>, blk_id: hir::HirId) -> bool {

0 commit comments

Comments
 (0)