From 5c14433c00b29fb2065af0eb664e2040f88b4429 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Thu, 30 Sep 2021 01:06:56 +0000 Subject: [PATCH] Fix incorrect Box::pin suggestion The suggestion checked if Pin> 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. --- compiler/rustc_typeck/src/check/coercion.rs | 24 ++++++++++++++++++- .../src/check/fn_ctxt/suggestions.rs | 18 ++++++++++---- .../ui/suggestions/box-future-wrong-output.rs | 22 +++++++++++++++++ .../box-future-wrong-output.stderr | 14 +++++++++++ 4 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 src/test/ui/suggestions/box-future-wrong-output.rs create mode 100644 src/test/ui/suggestions/box-future-wrong-output.stderr diff --git a/compiler/rustc_typeck/src/check/coercion.rs b/compiler/rustc_typeck/src/check/coercion.rs index 07e542b70b9..556c5d152df 100644 --- a/compiler/rustc_typeck/src/check/coercion.rs +++ b/compiler/rustc_typeck/src/check/coercion.rs @@ -42,7 +42,7 @@ use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; 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::ty::adjustment::{ 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)) } + #[instrument(skip(self))] fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { // First, remove any resolved type variables (at the top level, at least): let a = self.shallow_resolve(a); @@ -943,6 +944,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { 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 /// how many dereference steps needed to achieve `expr_ty <: target`. If /// it's not possible, return `None`. diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs index 4d25399a123..62cacdbbce3 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs @@ -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, _ => {} } - let boxed_found = self.tcx.mk_box(found); - let new_found = self.tcx.mk_lang_item(boxed_found, LangItem::Pin).unwrap(); - if self.can_coerce(new_found, expected) { + let box_found = self.tcx.mk_box(found); + let pin_box_found = self.tcx.mk_lang_item(box_found, LangItem::Pin).unwrap(); + 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() { ty::Adt(def, _) if def.is_box() => { 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_hi(), ")".to_string()), ], - Applicability::MachineApplicable, + Applicability::MaybeIncorrect, ); } } 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 { false } diff --git a/src/test/ui/suggestions/box-future-wrong-output.rs b/src/test/ui/suggestions/box-future-wrong-output.rs new file mode 100644 index 00000000000..d49819fcb14 --- /dev/null +++ b/src/test/ui/suggestions/box-future-wrong-output.rs @@ -0,0 +1,22 @@ +// Issue #72117 +// edition:2018 + +use core::future::Future; +use core::pin::Pin; + +pub type BoxFuture<'a, T> = Pin + Send + 'a>>; + +impl 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 +} diff --git a/src/test/ui/suggestions/box-future-wrong-output.stderr b/src/test/ui/suggestions/box-future-wrong-output.stderr new file mode 100644 index 00000000000..e0c57af25b3 --- /dev/null +++ b/src/test/ui/suggestions/box-future-wrong-output.stderr @@ -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 + Send + 'static)>>` + found struct `Pin + Send>>` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`.