Skip to content

Revive suggestions for boxed trait objects instead of impl Trait #100019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 35 additions & 28 deletions compiler/rustc_typeck/src/check/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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<Span> {
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
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_typeck/src/check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<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`
|
LL | 1i32
Expand Down Expand Up @@ -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<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`
|
LL | 1 => 1i32,
Expand All @@ -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<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`
|
LL | 1i32
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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`.
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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`.