Rollup merge of #100019 - TaKO8Ki:suggest-boxed-trait-objects-instead-of-impl-trait, r=compiler-errors

Revive suggestions for boxed trait objects instead of impl Trait

The suggestion implemented in #75608 was not working properly, so I fixed it.
This commit is contained in:
Matthias Krüger 2022-08-07 21:10:22 +02:00 committed by GitHub
commit 7be359e51b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 217 additions and 29 deletions

View File

@ -4,7 +4,7 @@ use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::{self as hir, ExprKind}; use rustc_hir::{self as hir, ExprKind};
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::traits::Obligation; 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_span::Span;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
use rustc_trait_selection::traits::{ 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); let arm_ty = self.check_expr_with_expectation(&arm.body, expected);
all_arms_diverge &= self.diverges.get(); 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 { let (arm_block_id, arm_span) = if let hir::ExprKind::Block(blk, _) = arm.body.kind {
(Some(blk.hir_id), self.find_block_span(blk)) (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. // provide a structured suggestion in that case.
pub(crate) fn opt_suggest_box_span( pub(crate) fn opt_suggest_box_span(
&self, &self,
outer_ty: Ty<'tcx>, first_ty: Ty<'tcx>,
second_ty: Ty<'tcx>,
orig_expected: Expectation<'tcx>, orig_expected: Expectation<'tcx>,
) -> Option<Span> { ) -> Option<Span> {
match orig_expected { match orig_expected {
Expectation::ExpectHasType(expected) Expectation::ExpectHasType(expected)
if self.in_tail_expr if self.in_tail_expr
&& self.ret_coercion.as_ref()?.borrow().merged_ty().has_opaque_types() && self.return_type_has_opaque
&& self.can_coerce(outer_ty, expected) => && self.can_coerce(first_ty, expected)
&& self.can_coerce(second_ty, expected) =>
{ {
let obligations = self.fulfillment_cx.borrow().pending_obligations(); let obligations = self.fulfillment_cx.borrow().pending_obligations();
let mut suggest_box = !obligations.is_empty(); let mut suggest_box = !obligations.is_empty();
for o in obligations { 'outer: for o in obligations {
match o.predicate.kind().skip_binder() { for outer_ty in &[first_ty, second_ty] {
ty::PredicateKind::Trait(t) => { match o.predicate.kind().skip_binder() {
let pred = ty::PredicateKind::Trait(t) => {
ty::Binder::dummy(ty::PredicateKind::Trait(ty::TraitPredicate { let pred = ty::Binder::dummy(ty::PredicateKind::Trait(
trait_ref: ty::TraitRef { ty::TraitPredicate {
def_id: t.def_id(), trait_ref: ty::TraitRef {
substs: self.tcx.mk_substs_trait(outer_ty, &[]), 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(),
let obl = Obligation::new( self.param_env,
o.cause.clone(), pred.to_predicate(self.tcx),
self.param_env, );
pred.to_predicate(self.tcx), suggest_box &= self.predicate_must_hold_modulo_regions(&obl);
); if !suggest_box {
suggest_box &= self.predicate_must_hold_modulo_regions(&obl); // We've encountered some obligation that didn't hold, so the
if !suggest_box { // return expression can't just be boxed. We don't need to
// We've encountered some obligation that didn't hold, so the // evaluate the rest of the obligations.
// return expression can't just be boxed. We don't need to break 'outer;
// evaluate the rest of the obligations. }
break;
} }
_ => {}
} }
_ => {}
} }
} }
// If all the obligations hold (or there are no obligations) the tail expression // If all the obligations hold (or there are no obligations) the tail expression

View File

@ -1002,7 +1002,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let else_ty = self.check_expr_with_expectation(else_expr, expected); let else_ty = self.check_expr_with_expectation(else_expr, expected);
let else_diverges = self.diverges.get(); 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( let if_cause = self.if_cause(
sp, sp,
cond_expr.span, cond_expr.span,

View File

@ -52,6 +52,16 @@ LL | | 1u32
LL | | } LL | | }
| |_____- `if` and `else` have incompatible types | |_____- `if` and `else` have incompatible types
| |
help: you could change the return type to be a boxed trait object
|
LL | fn qux() -> Box<dyn std::fmt::Display> {
| ~~~~~~~ +
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` help: change the type of the numeric literal from `u32` to `i32`
| |
LL | 1i32 LL | 1i32
@ -114,6 +124,15 @@ LL | | _ => 2u32,
LL | | } LL | | }
| |_____- `match` arms have incompatible types | |_____- `match` arms have incompatible types
| |
help: you could change the return type to be a boxed trait object
|
LL | fn dog() -> Box<dyn std::fmt::Display> {
| ~~~~~~~ +
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` help: change the type of the numeric literal from `u32` to `i32`
| |
LL | 1 => 1i32, LL | 1 => 1i32,
@ -131,6 +150,16 @@ LL | | 1u32
LL | | } LL | | }
| |_____- `if` and `else` have incompatible types | |_____- `if` and `else` have incompatible types
| |
help: you could change the return type to be a boxed trait object
|
LL | fn apt() -> Box<dyn std::fmt::Display> {
| ~~~~~~~ +
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` help: change the type of the numeric literal from `u32` to `i32`
| |
LL | 1i32 LL | 1i32

View File

@ -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() {}

View File

@ -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`.

View File

@ -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<dyn Trait> {
if true {
Box::new(S)
} else {
Box::new(Y) //~ ERROR `if` and `else` have incompatible types
}
}
fn bar() -> Box<dyn Trait> {
match true {
true => Box::new(S),
false => Box::new(Y), //~ ERROR `match` arms have incompatible types
}
}
fn main() {}

View File

@ -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() {}

View File

@ -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<dyn Trait> {
| ~~~~~~~ +
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<dyn Trait> {
| ~~~~~~~ +
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`.