diff --git a/compiler/rustc_typeck/src/check/_match.rs b/compiler/rustc_typeck/src/check/_match.rs index 1b13c98e4c3fa..2d50412007d90 100644 --- a/compiler/rustc_typeck/src/check/_match.rs +++ b/compiler/rustc_typeck/src/check/_match.rs @@ -4,7 +4,7 @@ use rustc_errors::{Applicability, MultiSpan}; use rustc_hir::{self as hir, ExprKind}; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::traits::Obligation; -use rustc_middle::ty::{self, ToPredicate, Ty, TypeVisitable}; +use rustc_middle::ty::{self, ToPredicate, Ty}; use rustc_span::Span; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::{ @@ -94,7 +94,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let arm_ty = self.check_expr_with_expectation(&arm.body, expected); all_arms_diverge &= self.diverges.get(); - let opt_suggest_box_span = self.opt_suggest_box_span(arm_ty, orig_expected); + let opt_suggest_box_span = prior_arm.and_then(|(_, prior_arm_ty, _)| { + self.opt_suggest_box_span(prior_arm_ty, arm_ty, orig_expected) + }); let (arm_block_id, arm_span) = if let hir::ExprKind::Block(blk, _) = arm.body.kind { (Some(blk.hir_id), self.find_block_span(blk)) @@ -473,43 +475,48 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // provide a structured suggestion in that case. pub(crate) fn opt_suggest_box_span( &self, - outer_ty: Ty<'tcx>, + first_ty: Ty<'tcx>, + second_ty: Ty<'tcx>, orig_expected: Expectation<'tcx>, ) -> Option { match orig_expected { Expectation::ExpectHasType(expected) if self.in_tail_expr - && self.ret_coercion.as_ref()?.borrow().merged_ty().has_opaque_types() - && self.can_coerce(outer_ty, expected) => + && self.return_type_has_opaque + && self.can_coerce(first_ty, expected) + && self.can_coerce(second_ty, expected) => { let obligations = self.fulfillment_cx.borrow().pending_obligations(); let mut suggest_box = !obligations.is_empty(); - for o in obligations { - match o.predicate.kind().skip_binder() { - ty::PredicateKind::Trait(t) => { - let pred = - ty::Binder::dummy(ty::PredicateKind::Trait(ty::TraitPredicate { - trait_ref: ty::TraitRef { - def_id: t.def_id(), - substs: self.tcx.mk_substs_trait(outer_ty, &[]), + 'outer: for o in obligations { + for outer_ty in &[first_ty, second_ty] { + match o.predicate.kind().skip_binder() { + ty::PredicateKind::Trait(t) => { + let pred = ty::Binder::dummy(ty::PredicateKind::Trait( + ty::TraitPredicate { + trait_ref: ty::TraitRef { + def_id: t.def_id(), + substs: self.tcx.mk_substs_trait(*outer_ty, &[]), + }, + constness: t.constness, + polarity: t.polarity, }, - constness: t.constness, - polarity: t.polarity, - })); - let obl = Obligation::new( - o.cause.clone(), - self.param_env, - pred.to_predicate(self.tcx), - ); - suggest_box &= self.predicate_must_hold_modulo_regions(&obl); - if !suggest_box { - // We've encountered some obligation that didn't hold, so the - // return expression can't just be boxed. We don't need to - // evaluate the rest of the obligations. - break; + )); + let obl = Obligation::new( + o.cause.clone(), + self.param_env, + pred.to_predicate(self.tcx), + ); + suggest_box &= self.predicate_must_hold_modulo_regions(&obl); + if !suggest_box { + // We've encountered some obligation that didn't hold, so the + // return expression can't just be boxed. We don't need to + // evaluate the rest of the obligations. + break 'outer; + } } + _ => {} } - _ => {} } } // If all the obligations hold (or there are no obligations) the tail expression diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs index 6e97b0bf2ab7d..6d61f3dbb41c5 100644 --- a/compiler/rustc_typeck/src/check/expr.rs +++ b/compiler/rustc_typeck/src/check/expr.rs @@ -1002,7 +1002,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let else_ty = self.check_expr_with_expectation(else_expr, expected); let else_diverges = self.diverges.get(); - let opt_suggest_box_span = self.opt_suggest_box_span(else_ty, orig_expected); + let opt_suggest_box_span = self.opt_suggest_box_span(then_ty, else_ty, orig_expected); let if_cause = self.if_cause( sp, cond_expr.span, diff --git a/src/test/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr b/src/test/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr index 11c1072f02ccc..3c65fd998c5af 100644 --- a/src/test/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr +++ b/src/test/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr @@ -52,6 +52,16 @@ LL | | 1u32 LL | | } | |_____- `if` and `else` have incompatible types | +help: you could change the return type to be a boxed trait object + | +LL | fn qux() -> Box { + | ~~~~~~~ + +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ Box::new(0i32) +LL | } else { +LL ~ Box::new(1u32) + | help: change the type of the numeric literal from `u32` to `i32` | LL | 1i32 @@ -114,6 +124,15 @@ LL | | _ => 2u32, LL | | } | |_____- `match` arms have incompatible types | +help: you could change the return type to be a boxed trait object + | +LL | fn dog() -> Box { + | ~~~~~~~ + +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ 0 => Box::new(0i32), +LL ~ 1 => Box::new(1u32), + | help: change the type of the numeric literal from `u32` to `i32` | LL | 1 => 1i32, @@ -131,6 +150,16 @@ LL | | 1u32 LL | | } | |_____- `if` and `else` have incompatible types | +help: you could change the return type to be a boxed trait object + | +LL | fn apt() -> Box { + | ~~~~~~~ + +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ Box::new(0i32) +LL | } else { +LL ~ Box::new(1u32) + | help: change the type of the numeric literal from `u32` to `i32` | LL | 1i32 diff --git a/src/test/ui/mismatched_types/do-not-suggest-boxed-trait-objects-instead-of-impl-trait.rs b/src/test/ui/mismatched_types/do-not-suggest-boxed-trait-objects-instead-of-impl-trait.rs new file mode 100644 index 0000000000000..d302dc99bd8aa --- /dev/null +++ b/src/test/ui/mismatched_types/do-not-suggest-boxed-trait-objects-instead-of-impl-trait.rs @@ -0,0 +1,23 @@ +struct S; +struct Y; + +trait Trait {} + +impl Trait for Y {} + +fn foo() -> impl Trait { + if true { + S + } else { + Y //~ ERROR `if` and `else` have incompatible types + } +} + +fn bar() -> impl Trait { + match true { + true => S, + false => Y, //~ ERROR `match` arms have incompatible types + } +} + +fn main() {} diff --git a/src/test/ui/mismatched_types/do-not-suggest-boxed-trait-objects-instead-of-impl-trait.stderr b/src/test/ui/mismatched_types/do-not-suggest-boxed-trait-objects-instead-of-impl-trait.stderr new file mode 100644 index 0000000000000..2f814445bbadf --- /dev/null +++ b/src/test/ui/mismatched_types/do-not-suggest-boxed-trait-objects-instead-of-impl-trait.stderr @@ -0,0 +1,26 @@ +error[E0308]: `if` and `else` have incompatible types + --> $DIR/do-not-suggest-boxed-trait-objects-instead-of-impl-trait.rs:12:9 + | +LL | / if true { +LL | | S + | | - expected because of this +LL | | } else { +LL | | Y + | | ^ expected struct `S`, found struct `Y` +LL | | } + | |_____- `if` and `else` have incompatible types + +error[E0308]: `match` arms have incompatible types + --> $DIR/do-not-suggest-boxed-trait-objects-instead-of-impl-trait.rs:19:18 + | +LL | / match true { +LL | | true => S, + | | - this is found to be of type `S` +LL | | false => Y, + | | ^ expected struct `S`, found struct `Y` +LL | | } + | |_____- `match` arms have incompatible types + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/mismatched_types/suggest-boxed-trait-objects-instead-of-impl-trait.fixed b/src/test/ui/mismatched_types/suggest-boxed-trait-objects-instead-of-impl-trait.fixed new file mode 100644 index 0000000000000..f30feaed05574 --- /dev/null +++ b/src/test/ui/mismatched_types/suggest-boxed-trait-objects-instead-of-impl-trait.fixed @@ -0,0 +1,28 @@ +// run-rustfix + +#![allow(dead_code)] + +struct S; +struct Y; + +trait Trait {} + +impl Trait for S {} +impl Trait for Y {} + +fn foo() -> Box { + if true { + Box::new(S) + } else { + Box::new(Y) //~ ERROR `if` and `else` have incompatible types + } +} + +fn bar() -> Box { + match true { + true => Box::new(S), + false => Box::new(Y), //~ ERROR `match` arms have incompatible types + } +} + +fn main() {} diff --git a/src/test/ui/mismatched_types/suggest-boxed-trait-objects-instead-of-impl-trait.rs b/src/test/ui/mismatched_types/suggest-boxed-trait-objects-instead-of-impl-trait.rs new file mode 100644 index 0000000000000..2bd8146e289d9 --- /dev/null +++ b/src/test/ui/mismatched_types/suggest-boxed-trait-objects-instead-of-impl-trait.rs @@ -0,0 +1,28 @@ +// run-rustfix + +#![allow(dead_code)] + +struct S; +struct Y; + +trait Trait {} + +impl Trait for S {} +impl Trait for Y {} + +fn foo() -> impl Trait { + if true { + S + } else { + Y //~ ERROR `if` and `else` have incompatible types + } +} + +fn bar() -> impl Trait { + match true { + true => S, + false => Y, //~ ERROR `match` arms have incompatible types + } +} + +fn main() {} diff --git a/src/test/ui/mismatched_types/suggest-boxed-trait-objects-instead-of-impl-trait.stderr b/src/test/ui/mismatched_types/suggest-boxed-trait-objects-instead-of-impl-trait.stderr new file mode 100644 index 0000000000000..f58b9c3ec16de --- /dev/null +++ b/src/test/ui/mismatched_types/suggest-boxed-trait-objects-instead-of-impl-trait.stderr @@ -0,0 +1,47 @@ +error[E0308]: `if` and `else` have incompatible types + --> $DIR/suggest-boxed-trait-objects-instead-of-impl-trait.rs:17:9 + | +LL | / if true { +LL | | S + | | - expected because of this +LL | | } else { +LL | | Y + | | ^ expected struct `S`, found struct `Y` +LL | | } + | |_____- `if` and `else` have incompatible types + | +help: you could change the return type to be a boxed trait object + | +LL | fn foo() -> Box { + | ~~~~~~~ + +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ Box::new(S) +LL | } else { +LL ~ Box::new(Y) + | + +error[E0308]: `match` arms have incompatible types + --> $DIR/suggest-boxed-trait-objects-instead-of-impl-trait.rs:24:18 + | +LL | / match true { +LL | | true => S, + | | - this is found to be of type `S` +LL | | false => Y, + | | ^ expected struct `S`, found struct `Y` +LL | | } + | |_____- `match` arms have incompatible types + | +help: you could change the return type to be a boxed trait object + | +LL | fn bar() -> Box { + | ~~~~~~~ + +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ true => Box::new(S), +LL ~ false => Box::new(Y), + | + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`.