Fix incorrect Box::pin suggestion

The suggestion checked if Pin<Box<T>> could be coeerced to the expected
type, but did not check predicates created by the coercion. We now
look for predicates that definitely cannot be satisfied before giving
the suggestion.

The suggestion is marked MaybeIncorrect because we allow predicates that
are still ambiguous and can't be proven.
This commit is contained in:
Tyler Mandry 2021-09-30 01:06:56 +00:00
parent 2c31c31bb8
commit 5c14433c00
4 changed files with 73 additions and 5 deletions

View file

@ -42,7 +42,7 @@ use rustc_hir as hir;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::{Coercion, InferOk, InferResult}; use rustc_infer::infer::{Coercion, InferOk, InferResult};
use rustc_infer::traits::Obligation; use rustc_infer::traits::{Obligation, TraitEngine, TraitEngineExt};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::adjustment::{ use rustc_middle::ty::adjustment::{
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCast, Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCast,
@ -146,6 +146,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
.and_then(|InferOk { value: ty, obligations }| success(f(ty), ty, obligations)) .and_then(|InferOk { value: ty, obligations }| success(f(ty), ty, obligations))
} }
#[instrument(skip(self))]
fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> {
// First, remove any resolved type variables (at the top level, at least): // First, remove any resolved type variables (at the top level, at least):
let a = self.shallow_resolve(a); let a = self.shallow_resolve(a);
@ -943,6 +944,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.probe(|_| coerce.coerce(source, target)).is_ok() self.probe(|_| coerce.coerce(source, target)).is_ok()
} }
/// Same as `try_coerce()`, but without side-effects and attempts to select
/// all predicates created by the coercion. This is useful for e.g. checking
/// that associated types are correct.
pub fn can_coerce_and_satisfy_predicates(&self, expr_ty: Ty<'tcx>, target: Ty<'tcx>) -> bool {
let source = self.resolve_vars_with_obligations(expr_ty);
debug!("coercion::can_with_predicates({:?} -> {:?})", source, target);
let cause = self.cause(rustc_span::DUMMY_SP, ObligationCauseCode::ExprAssignable);
// We don't ever need two-phase here since we throw out the result of the coercion
let coerce = Coerce::new(self, cause, AllowTwoPhase::No);
self.probe(|_| {
let ok = match coerce.coerce(source, target) {
Ok(ok) => ok,
_ => return false,
};
let mut fcx = traits::FulfillmentContext::new_in_snapshot();
fcx.register_predicate_obligations(self, ok.obligations);
fcx.select_where_possible(&self).is_ok()
})
}
/// Given a type and a target type, this function will calculate and return /// Given a type and a target type, this function will calculate and return
/// how many dereference steps needed to achieve `expr_ty <: target`. If /// how many dereference steps needed to achieve `expr_ty <: target`. If
/// it's not possible, return `None`. /// it's not possible, return `None`.

View file

@ -370,9 +370,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
_ if pin_did.is_none() || self.tcx.lang_items().owned_box().is_none() => return false, _ if pin_did.is_none() || self.tcx.lang_items().owned_box().is_none() => return false,
_ => {} _ => {}
} }
let boxed_found = self.tcx.mk_box(found); let box_found = self.tcx.mk_box(found);
let new_found = self.tcx.mk_lang_item(boxed_found, LangItem::Pin).unwrap(); let pin_box_found = self.tcx.mk_lang_item(box_found, LangItem::Pin).unwrap();
if self.can_coerce(new_found, expected) { let pin_found = self.tcx.mk_lang_item(found, LangItem::Pin).unwrap();
if self.can_coerce_and_satisfy_predicates(pin_box_found, expected) {
debug!("can coerce {:?} to {:?}, suggesting Box::pin", pin_box_found, expected);
match found.kind() { match found.kind() {
ty::Adt(def, _) if def.is_box() => { ty::Adt(def, _) if def.is_box() => {
err.help("use `Box::pin`"); err.help("use `Box::pin`");
@ -384,11 +386,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
(expr.span.shrink_to_lo(), "Box::pin(".to_string()), (expr.span.shrink_to_lo(), "Box::pin(".to_string()),
(expr.span.shrink_to_hi(), ")".to_string()), (expr.span.shrink_to_hi(), ")".to_string()),
], ],
Applicability::MachineApplicable, Applicability::MaybeIncorrect,
); );
} }
} }
true true
} else if self.can_coerce_and_satisfy_predicates(pin_found, expected) {
match found.kind() {
ty::Adt(def, _) if def.is_box() => {
err.help("use `Box::pin`");
true
}
_ => false,
}
} else { } else {
false false
} }

View file

@ -0,0 +1,22 @@
// Issue #72117
// edition:2018
use core::future::Future;
use core::pin::Pin;
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
impl<T: ?Sized> FutureExt for T where T: Future {}
trait FutureExt: Future {
fn boxed<'a>(self) -> BoxFuture<'a, Self::Output>
where
Self: Sized + Send + 'a,
{
Box::pin(self)
}
}
fn main() {
let _: BoxFuture<'static, bool> = async {}.boxed();
//~^ ERROR: mismatched types
}

View file

@ -0,0 +1,14 @@
error[E0308]: mismatched types
--> $DIR/box-future-wrong-output.rs:20:39
|
LL | let _: BoxFuture<'static, bool> = async {}.boxed();
| ------------------------ ^^^^^^^^^^^^^^^^ expected `bool`, found `()`
| |
| expected due to this
|
= note: expected struct `Pin<Box<(dyn Future<Output = bool> + Send + 'static)>>`
found struct `Pin<Box<dyn Future<Output = ()> + Send>>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.