From 9779526d8f20f4dcdb1d33eab0170cc7d5f410a9 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 6 Jul 2021 18:05:40 +0200 Subject: [PATCH 1/6] Record coercion adjustments --- crates/hir_ty/src/diagnostics/match_check.rs | 2 +- crates/hir_ty/src/infer.rs | 93 +++++++- crates/hir_ty/src/infer/closure.rs | 8 +- crates/hir_ty/src/infer/coerce.rs | 220 ++++++++++++++----- crates/hir_ty/src/infer/expr.rs | 48 ++-- crates/hir_ty/src/infer/pat.rs | 10 +- crates/hir_ty/src/infer/unify.rs | 6 +- crates/hir_ty/src/tests.rs | 35 ++- crates/hir_ty/src/tests/coercion.rs | 3 + 9 files changed, 339 insertions(+), 86 deletions(-) diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs index a30e426993..7838bbe5c1 100644 --- a/crates/hir_ty/src/diagnostics/match_check.rs +++ b/crates/hir_ty/src/diagnostics/match_check.rs @@ -109,7 +109,7 @@ impl<'a> PatCtxt<'a> { self.infer.pat_adjustments.get(&pat).map(|it| &**it).unwrap_or_default().iter().rev().fold( unadjusted_pat, |subpattern, ref_ty| Pat { - ty: ref_ty.clone(), + ty: ref_ty.target.clone(), kind: Box::new(PatKind::Deref { subpattern }), }, ) diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs index 1ca7105f24..a66caa369c 100644 --- a/crates/hir_ty/src/infer.rs +++ b/crates/hir_ty/src/infer.rs @@ -16,7 +16,7 @@ use std::ops::Index; use std::sync::Arc; -use chalk_ir::{cast::Cast, DebruijnIndex, Mutability}; +use chalk_ir::{cast::Cast, DebruijnIndex, Mutability, Safety}; use hir_def::{ body::Body, data::{ConstData, FunctionData, StaticData}, @@ -103,12 +103,20 @@ impl Default for BindingMode { } #[derive(Debug)] -pub(crate) struct InferOk { +pub(crate) struct InferOk { + value: T, goals: Vec>, } + +impl InferOk { + fn map(self, f: impl FnOnce(T) -> U) -> InferOk { + InferOk { value: f(self.value), goals: self.goals } + } +} + #[derive(Debug)] pub(crate) struct TypeError; -pub(crate) type InferResult = Result; +pub(crate) type InferResult = Result, TypeError>; #[derive(Debug, PartialEq, Eq, Clone)] pub enum InferenceDiagnostic { @@ -134,6 +142,78 @@ impl Default for InternedStandardTypes { } } +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Adjustment { + pub kind: Adjust, + pub target: Ty, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Adjust { + /// Go from ! to any type. + NeverToAny, + + /// Dereference once, producing a place. + Deref(Option), + + /// Take the address and produce either a `&` or `*` pointer. + Borrow(AutoBorrow), + + Pointer(PointerCast), +} + +// impl fmt::Display for Adjust { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// match self { +// Adjust::NeverToAny => write!(f, "NeverToAny"), +// Adjust::Deref(_) => write!(f, "Deref"), // FIXME +// Adjust::Borrow(AutoBorrow::Ref(mt)) => write!(f, "BorrowRef{:?}", mt), +// Adjust::Borrow(AutoBorrow::RawPtr(mt)) => write!(f, "BorrowRawPtr{:?}", mt), +// Adjust::Pointer(cast) => write!(f, "PtrCast{:?}", cast), +// } +// } +// } + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct OverloadedDeref(Mutability); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum AutoBorrow { + Ref(Mutability), + RawPtr(Mutability), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum PointerCast { + /// Go from a fn-item type to a fn-pointer type. + ReifyFnPointer, + + /// Go from a safe fn pointer to an unsafe fn pointer. + UnsafeFnPointer, + + /// Go from a non-capturing closure to an fn pointer or an unsafe fn pointer. + /// It cannot convert a closure that requires unsafe. + ClosureFnPointer(Safety), + + /// Go from a mut raw pointer to a const raw pointer. + MutToConstPointer, + + /// Go from `*const [T; N]` to `*const T` + ArrayToPointer, + + /// Unsize a pointer/reference value, e.g., `&[T; n]` to + /// `&[T]`. Note that the source could be a thin or fat pointer. + /// This will do things like convert thin pointers to fat + /// pointers, or convert structs containing thin pointers to + /// structs containing fat pointers, or convert between fat + /// pointers. We don't store the details of how the transform is + /// done (in fact, we don't know that, because it might depend on + /// the precise type parameters). We just store the target + /// type. Codegen backends and miri figure out what has to be done + /// based on the precise source/target type at hand. + Unsize, +} + /// The result of type inference: A mapping from expressions and patterns to types. #[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct InferenceResult { @@ -156,7 +236,8 @@ pub struct InferenceResult { /// Interned Unknown to return references to. standard_types: InternedStandardTypes, /// Stores the types which were implicitly dereferenced in pattern binding modes. - pub pat_adjustments: FxHashMap>, + pub pat_adjustments: FxHashMap>, + pub expr_adjustments: FxHashMap>, } impl InferenceResult { @@ -303,6 +384,10 @@ impl<'a> InferenceContext<'a> { self.result.type_of_expr.insert(expr, ty); } + fn write_expr_adj(&mut self, expr: ExprId, adjustments: Vec) { + self.result.expr_adjustments.insert(expr, adjustments); + } + fn write_method_resolution(&mut self, expr: ExprId, func: FunctionId, subst: Substitution) { self.result.method_resolutions.insert(expr, (func, subst)); } diff --git a/crates/hir_ty/src/infer/closure.rs b/crates/hir_ty/src/infer/closure.rs index 2de4c0f5db..a5c97f25dd 100644 --- a/crates/hir_ty/src/infer/closure.rs +++ b/crates/hir_ty/src/infer/closure.rs @@ -1,7 +1,7 @@ //! Inference of closure parameter types based on the closure's expected type. use chalk_ir::{cast::Cast, AliasTy, FnSubst, WhereClause}; -use hir_def::HasModule; +use hir_def::{expr::ExprId, HasModule}; use smallvec::SmallVec; use crate::{ @@ -14,6 +14,7 @@ use super::{Expectation, InferenceContext}; impl InferenceContext<'_> { pub(super) fn deduce_closure_type_from_expectations( &mut self, + closure_expr: ExprId, closure_ty: &Ty, sig_ty: &Ty, expectation: &Expectation, @@ -24,8 +25,9 @@ impl InferenceContext<'_> { }; // Deduction from where-clauses in scope, as well as fn-pointer coercion are handled here. - self.coerce(closure_ty, &expected_ty); - + if let Ok(res) = self.coerce(closure_ty, &expected_ty) { + self.write_expr_adj(closure_expr, res.value.0); + } // Deduction based on the expected `dyn Fn` is done separately. if let TyKind::Dyn(dyn_ty) = expected_ty.kind(&Interner) { if let Some(sig) = self.deduce_sig_from_dyn_ty(dyn_ty) { diff --git a/crates/hir_ty/src/infer/coerce.rs b/crates/hir_ty/src/infer/coerce.rs index 7be9144519..578e1b07a0 100644 --- a/crates/hir_ty/src/infer/coerce.rs +++ b/crates/hir_ty/src/infer/coerce.rs @@ -5,30 +5,52 @@ //! See and //! `librustc_typeck/check/coercion.rs`. -use chalk_ir::{cast::Cast, Mutability, TyVariableKind}; +use chalk_ir::{cast::Cast, Goal, Mutability, TyVariableKind}; use hir_def::{expr::ExprId, lang_item::LangItemTarget}; use crate::{ - autoderef, infer::TypeMismatch, static_lifetime, Canonical, DomainGoal, FnPointer, FnSig, - Interner, Solution, Substitution, Ty, TyBuilder, TyExt, TyKind, + autoderef, + infer::{Adjust, Adjustment, AutoBorrow, PointerCast, TypeMismatch}, + static_lifetime, Canonical, DomainGoal, FnPointer, FnSig, Interner, Solution, Substitution, Ty, + TyBuilder, TyExt, TyKind, }; -use super::{InEnvironment, InferOk, InferResult, InferenceContext, TypeError}; +use super::{InEnvironment, InferOk, InferenceContext, TypeError}; + +pub(crate) type CoerceResult = Result, Ty)>, TypeError>; + +/// Do not require any adjustments, i.e. coerce `x -> x`. +fn identity(_: Ty) -> Vec { + vec![] +} + +fn simple(kind: Adjust) -> impl FnOnce(Ty) -> Vec { + move |target| vec![Adjustment { kind, target }] +} + +/// This always returns `Ok(...)`. +fn success( + adj: Vec, + target: Ty, + goals: Vec>>, +) -> CoerceResult { + Ok(InferOk { goals, value: (adj, target) }) +} impl<'a> InferenceContext<'a> { /// Unify two types, but may coerce the first one to the second one /// using "implicit coercion rules" if needed. - pub(super) fn coerce(&mut self, from_ty: &Ty, to_ty: &Ty) -> bool { + pub(super) fn coerce(&mut self, from_ty: &Ty, to_ty: &Ty) -> CoerceResult { let from_ty = self.resolve_ty_shallow(from_ty); let to_ty = self.resolve_ty_shallow(to_ty); match self.coerce_inner(from_ty, &to_ty) { - Ok(result) => { - self.table.register_infer_ok(result); - true + Ok(InferOk { value, goals }) => { + self.table.register_infer_ok(InferOk { value: (), goals }); + Ok(InferOk { value, goals: Vec::new() }) } - Err(_) => { + Err(e) => { // FIXME deal with error - false + Err(e) } } } @@ -41,6 +63,7 @@ impl<'a> InferenceContext<'a> { /// - if we were concerned with lifetime subtyping, we'd need to look for a /// least upper bound. pub(super) fn coerce_merge_branch(&mut self, id: Option, ty1: &Ty, ty2: &Ty) -> Ty { + // TODO let ty1 = self.resolve_ty_shallow(ty1); let ty2 = self.resolve_ty_shallow(ty2); // Special case: two function types. Try to coerce both to @@ -72,9 +95,9 @@ impl<'a> InferenceContext<'a> { // type is a type variable and the new one is `!`, trying it the other // way around first would mean we make the type variable `!`, instead of // just marking it as possibly diverging. - if self.coerce(&ty2, &ty1) { + if self.coerce(&ty2, &ty1).is_ok() { ty1 - } else if self.coerce(&ty1, &ty2) { + } else if self.coerce(&ty1, &ty2).is_ok() { ty2 } else { if let Some(id) = id { @@ -87,7 +110,7 @@ impl<'a> InferenceContext<'a> { } } - fn coerce_inner(&mut self, from_ty: Ty, to_ty: &Ty) -> InferResult { + fn coerce_inner(&mut self, from_ty: Ty, to_ty: &Ty) -> CoerceResult { if from_ty.is_never() { // Subtle: If we are coercing from `!` to `?T`, where `?T` is an unbound // type variable, we want `?T` to fallback to `!` if not @@ -96,13 +119,10 @@ impl<'a> InferenceContext<'a> { // let _: Option = Some({ return; }); // // here, we would coerce from `!` to `?T`. - match to_ty.kind(&Interner) { - TyKind::InferenceVar(tv, TyVariableKind::General) => { - self.table.set_diverging(*tv, true); - } - _ => {} + if let TyKind::InferenceVar(tv, TyVariableKind::General) = to_ty.kind(&Interner) { + self.table.set_diverging(*tv, true); } - return Ok(InferOk { goals: Vec::new() }); + return success(simple(Adjust::NeverToAny)(to_ty.clone()), to_ty.clone(), vec![]); } // Consider coercing the subtype to a DST @@ -143,35 +163,64 @@ impl<'a> InferenceContext<'a> { } _ => { // Otherwise, just use unification rules. - self.table.try_unify(&from_ty, to_ty) + self.unify_and(&from_ty, to_ty, identity) } } } - fn coerce_ptr(&mut self, from_ty: Ty, to_ty: &Ty, to_mt: Mutability) -> InferResult { - let (_is_ref, from_mt, from_inner) = match from_ty.kind(&Interner) { + /// Unify two types (using sub or lub) and produce a specific coercion. + fn unify_and(&mut self, t1: &Ty, t2: &Ty, f: F) -> CoerceResult + where + F: FnOnce(Ty) -> Vec, + { + self.table + .try_unify(t1, t2) + .and_then(|InferOk { goals, .. }| success(f(t1.clone()), t1.clone(), goals)) + } + + fn coerce_ptr(&mut self, from_ty: Ty, to_ty: &Ty, to_mt: Mutability) -> CoerceResult { + let (is_ref, from_mt, from_inner) = match from_ty.kind(&Interner) { TyKind::Ref(mt, _, ty) => (true, mt, ty), TyKind::Raw(mt, ty) => (false, mt, ty), - _ => return self.table.try_unify(&from_ty, to_ty), + _ => return self.unify_and(&from_ty, to_ty, identity), }; coerce_mutabilities(*from_mt, to_mt)?; // Check that the types which they point at are compatible. let from_raw = TyKind::Raw(to_mt, from_inner.clone()).intern(&Interner); - // FIXME: behavior differs based on is_ref once we're computing adjustments - self.table.try_unify(&from_raw, to_ty) + // self.table.try_unify(&from_raw, to_ty); + + // Although references and unsafe ptrs have the same + // representation, we still register an Adjust::DerefRef so that + // regionck knows that the region for `a` must be valid here. + if is_ref { + self.unify_and(&from_raw, to_ty, |target| { + vec![ + Adjustment { kind: Adjust::Deref(None), target: from_inner.clone() }, + Adjustment { kind: Adjust::Borrow(AutoBorrow::RawPtr(to_mt)), target }, + ] + }) + } else if *from_mt != to_mt { + self.unify_and( + &from_raw, + to_ty, + simple(Adjust::Pointer(PointerCast::MutToConstPointer)), + ) + } else { + self.unify_and(&from_raw, to_ty, identity) + } } /// Reborrows `&mut A` to `&mut B` and `&(mut) A` to `&B`. /// To match `A` with `B`, autoderef will be performed, /// calling `deref`/`deref_mut` where necessary. - fn coerce_ref(&mut self, from_ty: Ty, to_ty: &Ty, to_mt: Mutability) -> InferResult { + fn coerce_ref(&mut self, from_ty: Ty, to_ty: &Ty, to_mt: Mutability) -> CoerceResult { match from_ty.kind(&Interner) { TyKind::Ref(mt, _, _) => { coerce_mutabilities(*mt, to_mt)?; } - _ => return self.table.try_unify(&from_ty, to_ty), + _ => return self.unify_and(&from_ty, to_ty, identity), }; // NOTE: this code is mostly copied and adapted from rustc, and @@ -227,7 +276,7 @@ impl<'a> InferenceContext<'a> { let derefd_from_ty = TyKind::Ref(to_mt, lt, referent_ty).intern(&Interner); match self.table.try_unify(&derefd_from_ty, to_ty) { Ok(result) => { - found = Some(result); + found = Some(result.map(|()| derefd_from_ty)); break; } Err(err) => { @@ -243,19 +292,23 @@ impl<'a> InferenceContext<'a> { // (e.g., in example above, the failure from relating `Vec` // to the target type), since that should be the least // confusing. - let result = match found { + let InferOk { value: ty, goals } = match found { Some(d) => d, None => { let err = first_error.expect("coerce_borrowed_pointer had no error"); return Err(err); } }; - - Ok(result) + // FIXME: record overloarded deref adjustments + success( + vec![Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(to_mt)), target: ty.clone() }], + ty, + goals, + ) } /// Attempts to coerce from the type of a Rust function item into a function pointer. - fn coerce_from_fn_item(&mut self, from_ty: Ty, to_ty: &Ty) -> InferResult { + fn coerce_from_fn_item(&mut self, from_ty: Ty, to_ty: &Ty) -> CoerceResult { match to_ty.kind(&Interner) { TyKind::Function(_) => { let from_sig = from_ty.callable_sig(self.db).expect("FnDef had no sig"); @@ -267,11 +320,28 @@ impl<'a> InferenceContext<'a> { let from_sig = from_sig.to_fn_ptr(); let from_fn_pointer = TyKind::Function(from_sig.clone()).intern(&Interner); - let ok = self.coerce_from_safe_fn(from_fn_pointer, &from_sig, to_ty)?; + let ok = self.coerce_from_safe_fn( + from_fn_pointer.clone(), + &from_sig, + to_ty, + |unsafe_ty| { + vec![ + Adjustment { + kind: Adjust::Pointer(PointerCast::ReifyFnPointer), + target: from_fn_pointer, + }, + Adjustment { + kind: Adjust::Pointer(PointerCast::UnsafeFnPointer), + target: unsafe_ty, + }, + ] + }, + simple(Adjust::Pointer(PointerCast::ReifyFnPointer)), + )?; Ok(ok) } - _ => self.table.try_unify(&from_ty, to_ty), + _ => self.unify_and(&from_ty, to_ty, identity), } } @@ -280,26 +350,38 @@ impl<'a> InferenceContext<'a> { from_ty: Ty, from_f: &FnPointer, to_ty: &Ty, - ) -> InferResult { - self.coerce_from_safe_fn(from_ty, from_f, to_ty) + ) -> CoerceResult { + self.coerce_from_safe_fn( + from_ty, + from_f, + to_ty, + simple(Adjust::Pointer(PointerCast::UnsafeFnPointer)), + identity, + ) } - fn coerce_from_safe_fn( + fn coerce_from_safe_fn( &mut self, from_ty: Ty, from_fn_ptr: &FnPointer, to_ty: &Ty, - ) -> InferResult { + to_unsafe: F, + normal: G, + ) -> CoerceResult + where + F: FnOnce(Ty) -> Vec, + G: FnOnce(Ty) -> Vec, + { if let TyKind::Function(to_fn_ptr) = to_ty.kind(&Interner) { if let (chalk_ir::Safety::Safe, chalk_ir::Safety::Unsafe) = (from_fn_ptr.sig.safety, to_fn_ptr.sig.safety) { let from_unsafe = TyKind::Function(safe_to_unsafe_fn_ty(from_fn_ptr.clone())).intern(&Interner); - return self.table.try_unify(&from_unsafe, to_ty); + return self.unify_and(&from_unsafe, to_ty, to_unsafe); } } - self.table.try_unify(&from_ty, to_ty) + self.unify_and(&from_ty, to_ty, normal) } /// Attempts to coerce from the type of a non-capturing closure into a @@ -309,9 +391,10 @@ impl<'a> InferenceContext<'a> { from_ty: Ty, from_substs: &Substitution, to_ty: &Ty, - ) -> InferResult { + ) -> CoerceResult { match to_ty.kind(&Interner) { - TyKind::Function(fn_ty) /* if from_substs is non-capturing (FIXME) */ => { + // if from_substs is non-capturing (FIXME) + TyKind::Function(fn_ty) => { // We coerce the closure, which has fn type // `extern "rust-call" fn((arg0,arg1,...)) -> _` // to @@ -320,16 +403,20 @@ impl<'a> InferenceContext<'a> { // `unsafe fn(arg0,arg1,...) -> _` let safety = fn_ty.sig.safety; let pointer_ty = coerce_closure_fn_ty(from_substs, safety); - self.table.try_unify(&pointer_ty, to_ty) + self.unify_and( + &pointer_ty, + to_ty, + simple(Adjust::Pointer(PointerCast::ClosureFnPointer(safety))), + ) } - _ => self.table.try_unify(&from_ty, to_ty), + _ => self.unify_and(&from_ty, to_ty, identity), } } /// Coerce a type using `from_ty: CoerceUnsized` /// /// See: - fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> InferResult { + fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> CoerceResult { // These 'if' statements require some explanation. // The `CoerceUnsized` trait is special - it is only // possible to write `impl CoerceUnsized for A` where @@ -341,7 +428,7 @@ impl<'a> InferenceContext<'a> { // // Both of these trigger a special `CoerceUnsized`-related error (E0376) // - // We can take advantage of this fact to avoid performing unecessary work. + // We can take advantage of this fact to avoid performing unnecessary work. // If either `source` or `target` is a type variable, then any applicable impl // would need to be generic over the self-type (`impl CoerceUnsized for T`) // or generic over the `CoerceUnsized` type parameter (`impl CoerceUnsized for @@ -359,20 +446,34 @@ impl<'a> InferenceContext<'a> { } // Handle reborrows before trying to solve `Source: CoerceUnsized`. - let coerce_from = match (from_ty.kind(&Interner), to_ty.kind(&Interner)) { - (TyKind::Ref(from_mt, _, from_inner), TyKind::Ref(to_mt, _, _)) => { - coerce_mutabilities(*from_mt, *to_mt)?; + let reborrow = match (from_ty.kind(&Interner), to_ty.kind(&Interner)) { + (TyKind::Ref(from_mt, _, from_inner), &TyKind::Ref(to_mt, _, _)) => { + coerce_mutabilities(*from_mt, to_mt)?; let lt = static_lifetime(); - TyKind::Ref(*to_mt, lt, from_inner.clone()).intern(&Interner) + Some(( + Adjustment { kind: Adjust::Deref(None), target: from_inner.clone() }, + Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(to_mt)), + target: TyKind::Ref(to_mt, lt, from_inner.clone()).intern(&Interner), + }, + )) } - (TyKind::Ref(from_mt, _, from_inner), TyKind::Raw(to_mt, _)) => { - coerce_mutabilities(*from_mt, *to_mt)?; + (TyKind::Ref(from_mt, _, from_inner), &TyKind::Raw(to_mt, _)) => { + coerce_mutabilities(*from_mt, to_mt)?; - TyKind::Raw(*to_mt, from_inner.clone()).intern(&Interner) + Some(( + Adjustment { kind: Adjust::Deref(None), target: from_inner.clone() }, + Adjustment { + kind: Adjust::Borrow(AutoBorrow::RawPtr(to_mt)), + target: TyKind::Raw(to_mt, from_inner.clone()).intern(&Interner), + }, + )) } - _ => from_ty.clone(), + _ => None, }; + let coerce_from = + reborrow.as_ref().map_or_else(|| from_ty.clone(), |(_, adj)| adj.target.clone()); let krate = self.resolver.krate().unwrap(); let coerce_unsized_trait = match self.db.lang_item(krate, "coerce_unsized".into()) { @@ -417,8 +518,15 @@ impl<'a> InferenceContext<'a> { // FIXME: should we accept ambiguous results here? _ => return Err(TypeError), }; - - Ok(InferOk { goals: Vec::new() }) + // TODO: this is probably wrong? + let coerce_target = self.table.new_type_var(); + self.unify_and(&coerce_target, to_ty, |target| { + let unsize = Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), target }; + match reborrow { + None => vec![unsize], + Some((ref deref, ref autoref)) => vec![deref.clone(), autoref.clone(), unsize], + } + }) } } diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs index 746c3e4ee8..a582d700f2 100644 --- a/crates/hir_ty/src/infer/expr.rs +++ b/crates/hir_ty/src/infer/expr.rs @@ -56,15 +56,19 @@ impl<'a> InferenceContext<'a> { pub(super) fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { let ty = self.infer_expr_inner(expr, expected); let ty = if let Some(target) = expected.only_has_type(&mut self.table) { - if !self.coerce(&ty, &target) { - self.result - .type_mismatches - .insert(expr.into(), TypeMismatch { expected: target, actual: ty.clone() }); - // Return actual type when type mismatch. - // This is needed for diagnostic when return type mismatch. - ty - } else { - target + match self.coerce(&ty, &target) { + Ok(res) => { + self.result.expr_adjustments.insert(expr, res.value.0); + target + } + Err(_) => { + self.result + .type_mismatches + .insert(expr.into(), TypeMismatch { expected: target, actual: ty.clone() }); + // Return actual type when type mismatch. + // This is needed for diagnostic when return type mismatch. + ty + } } } else { ty @@ -163,8 +167,12 @@ impl<'a> InferenceContext<'a> { break_ty: break_ty.clone(), label: label.map(|label| self.body[label].name.clone()), }); - let ty = - self.infer_block(statements, *tail, &Expectation::has_type(break_ty)); + let ty = self.infer_block( + tgt_expr, + statements, + *tail, + &Expectation::has_type(break_ty), + ); let ctxt = self.breakables.pop().expect("breakable stack broken"); if ctxt.may_break { ctxt.break_ty @@ -172,7 +180,7 @@ impl<'a> InferenceContext<'a> { ty } } - None => self.infer_block(statements, *tail, expected), + None => self.infer_block(tgt_expr, statements, *tail, expected), }; self.resolver = old_resolver; ty @@ -284,7 +292,12 @@ impl<'a> InferenceContext<'a> { // Eagerly try to relate the closure type with the expected // type, otherwise we often won't have enough information to // infer the body. - self.deduce_closure_type_from_expectations(&closure_ty, &sig_ty, expected); + self.deduce_closure_type_from_expectations( + tgt_expr, + &closure_ty, + &sig_ty, + expected, + ); // Now go through the argument patterns for (arg_pat, arg_ty) in args.iter().zip(sig_tys) { @@ -400,7 +413,9 @@ impl<'a> InferenceContext<'a> { self.infer_expr_coerce(*expr, &Expectation::has_type(self.return_ty.clone())); } else { let unit = TyBuilder::unit(); - self.coerce(&unit, &self.return_ty.clone()); + if let Ok(ok) = self.coerce(&unit, &self.return_ty.clone()) { + self.write_expr_adj(tgt_expr, ok.value.0); + } } TyKind::Never.intern(&Interner) } @@ -810,6 +825,7 @@ impl<'a> InferenceContext<'a> { fn infer_block( &mut self, + expr: ExprId, statements: &[Statement], tail: Option, expected: &Expectation, @@ -856,7 +872,9 @@ impl<'a> InferenceContext<'a> { self.table.new_maybe_never_var() } else { if let Some(t) = expected.only_has_type(&mut self.table) { - self.coerce(&TyBuilder::unit(), &t); + if let Ok(ok) = self.coerce(&TyBuilder::unit(), &t) { + self.write_expr_adj(expr, ok.value.0); + } } TyBuilder::unit() } diff --git a/crates/hir_ty/src/infer/pat.rs b/crates/hir_ty/src/infer/pat.rs index c79ed91eac..86e2d1b8df 100644 --- a/crates/hir_ty/src/infer/pat.rs +++ b/crates/hir_ty/src/infer/pat.rs @@ -12,8 +12,9 @@ use hir_expand::name::Name; use super::{BindingMode, Expectation, InferenceContext, TypeMismatch}; use crate::{ - lower::lower_to_chalk_mutability, static_lifetime, Interner, Substitution, Ty, TyBuilder, - TyExt, TyKind, + infer::{Adjust, Adjustment, AutoBorrow}, + lower::lower_to_chalk_mutability, + static_lifetime, Interner, Substitution, Ty, TyBuilder, TyExt, TyKind, }; impl<'a> InferenceContext<'a> { @@ -103,7 +104,10 @@ impl<'a> InferenceContext<'a> { if is_non_ref_pat(&body, pat) { let mut pat_adjustments = Vec::new(); while let Some((inner, _lifetime, mutability)) = expected.as_reference() { - pat_adjustments.push(expected.clone()); + pat_adjustments.push(Adjustment { + target: expected.clone(), + kind: Adjust::Borrow(AutoBorrow::Ref(mutability)), + }); expected = self.resolve_ty_shallow(inner); default_bm = match default_bm { BindingMode::Move => BindingMode::Ref(mutability), diff --git a/crates/hir_ty/src/infer/unify.rs b/crates/hir_ty/src/infer/unify.rs index ea5684229f..f9e4796c27 100644 --- a/crates/hir_ty/src/infer/unify.rs +++ b/crates/hir_ty/src/infer/unify.rs @@ -315,7 +315,7 @@ impl<'a> InferenceTable<'a> { /// Unify two types and return new trait goals arising from it, so the /// caller needs to deal with them. - pub(crate) fn try_unify>(&mut self, t1: &T, t2: &T) -> InferResult { + pub(crate) fn try_unify>(&mut self, t1: &T, t2: &T) -> InferResult<()> { match self.var_unification_table.relate( &Interner, &self.db, @@ -324,7 +324,7 @@ impl<'a> InferenceTable<'a> { t1, t2, ) { - Ok(result) => Ok(InferOk { goals: result.goals }), + Ok(result) => Ok(InferOk { goals: result.goals, value: () }), Err(chalk_ir::NoSolution) => Err(TypeError), } } @@ -347,7 +347,7 @@ impl<'a> InferenceTable<'a> { } } - pub(crate) fn register_infer_ok(&mut self, infer_ok: InferOk) { + pub(crate) fn register_infer_ok(&mut self, infer_ok: InferOk) { infer_ok.goals.into_iter().for_each(|goal| self.register_obligation_in_env(goal)); } diff --git a/crates/hir_ty/src/tests.rs b/crates/hir_ty/src/tests.rs index 0651f34ae6..af6e773217 100644 --- a/crates/hir_ty/src/tests.rs +++ b/crates/hir_ty/src/tests.rs @@ -33,7 +33,11 @@ use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; use tracing_tree::HierarchicalLayer; use crate::{ - db::HirDatabase, display::HirDisplay, infer::TypeMismatch, test_db::TestDB, InferenceResult, Ty, + db::HirDatabase, + display::HirDisplay, + infer::{Adjustment, TypeMismatch}, + test_db::TestDB, + InferenceResult, Ty, }; // These tests compare the inference results for all expressions in a file @@ -79,6 +83,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour let mut had_annotations = false; let mut mismatches = HashMap::new(); let mut types = HashMap::new(); + let mut adjustments = HashMap::<_, Vec<_>>::new(); for (file_id, annotations) in db.extract_annotations() { for (range, expected) in annotations { let file_range = FileRange { file_id, range }; @@ -88,6 +93,15 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour types.insert(file_range, expected.trim_start_matches("type: ").to_string()); } else if expected.starts_with("expected") { mismatches.insert(file_range, expected); + } else if expected.starts_with("adjustments: ") { + adjustments.insert( + file_range, + expected + .trim_start_matches("adjustments: ") + .split(',') + .map(|it| it.trim().to_string()) + .collect(), + ); } else { panic!("unexpected annotation: {}", expected); } @@ -155,6 +169,19 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour }; assert_eq!(actual, expected); } + if let Some(expected) = adjustments.remove(&range) { + if let Some(adjustments) = inference_result.expr_adjustments.get(&expr) { + assert_eq!( + expected, + adjustments + .iter() + .map(|Adjustment { kind, .. }| format!("{:?}", kind)) + .collect::>() + ); + } else { + panic!("expected {:?} adjustments, found none", expected); + } + } } for (pat, mismatch) in inference_result.pat_type_mismatches() { @@ -212,6 +239,12 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour format_to!(buf, "{:?}: type {}\n", t.0.range, t.1); } } + if !adjustments.is_empty() { + format_to!(buf, "Unchecked adjustments annotations:\n"); + for t in adjustments { + format_to!(buf, "{:?}: type {:?}\n", t.0.range, t.1); + } + } assert!(buf.is_empty(), "{}", buf); } diff --git a/crates/hir_ty/src/tests/coercion.rs b/crates/hir_ty/src/tests/coercion.rs index 6c57bf697d..6f146f9dd3 100644 --- a/crates/hir_ty/src/tests/coercion.rs +++ b/crates/hir_ty/src/tests/coercion.rs @@ -96,6 +96,7 @@ fn foo(x: &[T]) -> &[T] { x } fn test() { let x = if true { foo(&[1]) + // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize) } else { &[1] }; @@ -130,6 +131,7 @@ fn foo(x: &[T]) -> &[T] { x } fn test(i: i32) { let x = match i { 2 => foo(&[2]), + // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize) 1 => &[1], _ => &[3], }; @@ -144,6 +146,7 @@ fn match_second_coerce() { r#" //- minicore: coerce_unsized fn foo(x: &[T]) -> &[T] { loop {} } + // ^^^^^^^ adjustments: NeverToAny fn test(i: i32) { let x = match i { 1 => &[1], From 64a1b26b8df3f154d162c3d75b7ca7f7d4ebae5f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 8 Jul 2021 14:16:23 +0200 Subject: [PATCH 2/6] Implement `CoerceMany` --- crates/hir_ty/src/infer/closure.rs | 5 +- crates/hir_ty/src/infer/coerce.rs | 134 ++++++++++++++++++----------- crates/hir_ty/src/infer/expr.rs | 70 +++++++-------- 3 files changed, 120 insertions(+), 89 deletions(-) diff --git a/crates/hir_ty/src/infer/closure.rs b/crates/hir_ty/src/infer/closure.rs index a5c97f25dd..e4fcc56bc4 100644 --- a/crates/hir_ty/src/infer/closure.rs +++ b/crates/hir_ty/src/infer/closure.rs @@ -25,9 +25,8 @@ impl InferenceContext<'_> { }; // Deduction from where-clauses in scope, as well as fn-pointer coercion are handled here. - if let Ok(res) = self.coerce(closure_ty, &expected_ty) { - self.write_expr_adj(closure_expr, res.value.0); - } + let _ = self.coerce(Some(closure_expr), closure_ty, &expected_ty); + // Deduction based on the expected `dyn Fn` is done separately. if let TyKind::Dyn(dyn_ty) = expected_ty.kind(&Interner) { if let Some(sig) = self.deduce_sig_from_dyn_ty(dyn_ty) { diff --git a/crates/hir_ty/src/infer/coerce.rs b/crates/hir_ty/src/infer/coerce.rs index 578e1b07a0..7a624090b6 100644 --- a/crates/hir_ty/src/infer/coerce.rs +++ b/crates/hir_ty/src/infer/coerce.rs @@ -10,7 +10,7 @@ use hir_def::{expr::ExprId, lang_item::LangItemTarget}; use crate::{ autoderef, - infer::{Adjust, Adjustment, AutoBorrow, PointerCast, TypeMismatch}, + infer::{Adjust, Adjustment, AutoBorrow, InferResult, PointerCast, TypeMismatch}, static_lifetime, Canonical, DomainGoal, FnPointer, FnSig, Interner, Solution, Substitution, Ty, TyBuilder, TyExt, TyKind, }; @@ -36,23 +36,25 @@ fn success( ) -> CoerceResult { Ok(InferOk { goals, value: (adj, target) }) } +#[derive(Clone, Debug)] +pub(super) struct CoerceMany { + expected_ty: Ty, +} -impl<'a> InferenceContext<'a> { - /// Unify two types, but may coerce the first one to the second one - /// using "implicit coercion rules" if needed. - pub(super) fn coerce(&mut self, from_ty: &Ty, to_ty: &Ty) -> CoerceResult { - let from_ty = self.resolve_ty_shallow(from_ty); - let to_ty = self.resolve_ty_shallow(to_ty); - match self.coerce_inner(from_ty, &to_ty) { - Ok(InferOk { value, goals }) => { - self.table.register_infer_ok(InferOk { value: (), goals }); - Ok(InferOk { value, goals: Vec::new() }) - } - Err(e) => { - // FIXME deal with error - Err(e) - } - } +impl CoerceMany { + pub(super) fn new(expected: Ty) -> Self { + CoerceMany { expected_ty: expected } + } + + pub(super) fn once( + ctx: &mut InferenceContext<'_>, + expected: Ty, + expr: Option, + expr_ty: &Ty, + ) -> Ty { + let mut this = CoerceMany::new(expected); + this.coerce(ctx, expr, expr_ty); + this.complete() } /// Merge two types from different branches, with possible coercion. @@ -62,51 +64,88 @@ impl<'a> InferenceContext<'a> { /// coerce both to function pointers; /// - if we were concerned with lifetime subtyping, we'd need to look for a /// least upper bound. - pub(super) fn coerce_merge_branch(&mut self, id: Option, ty1: &Ty, ty2: &Ty) -> Ty { - // TODO - let ty1 = self.resolve_ty_shallow(ty1); - let ty2 = self.resolve_ty_shallow(ty2); + pub(super) fn coerce( + &mut self, + ctx: &mut InferenceContext<'_>, + expr: Option, + expr_ty: &Ty, + ) { + let expr_ty = ctx.resolve_ty_shallow(expr_ty); + self.expected_ty = ctx.resolve_ty_shallow(&self.expected_ty); + // Special case: two function types. Try to coerce both to // pointers to have a chance at getting a match. See // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916 - let sig = match (ty1.kind(&Interner), ty2.kind(&Interner)) { + let sig = match (self.expected_ty.kind(&Interner), expr_ty.kind(&Interner)) { (TyKind::FnDef(..) | TyKind::Closure(..), TyKind::FnDef(..) | TyKind::Closure(..)) => { // FIXME: we're ignoring safety here. To be more correct, if we have one FnDef and one Closure, // we should be coercing the closure to a fn pointer of the safety of the FnDef cov_mark::hit!(coerce_fn_reification); - let sig = ty1.callable_sig(self.db).expect("FnDef without callable sig"); + let sig = + self.expected_ty.callable_sig(ctx.db).expect("FnDef without callable sig"); Some(sig) } _ => None, }; if let Some(sig) = sig { let target_ty = TyKind::Function(sig.to_fn_ptr()).intern(&Interner); - let result1 = self.coerce_inner(ty1.clone(), &target_ty); - let result2 = self.coerce_inner(ty2.clone(), &target_ty); + let result1 = ctx.coerce_inner(self.expected_ty.clone(), &target_ty); + let result2 = ctx.coerce_inner(expr_ty.clone(), &target_ty); if let (Ok(result1), Ok(result2)) = (result1, result2) { - self.table.register_infer_ok(result1); - self.table.register_infer_ok(result2); - return target_ty; + ctx.table.register_infer_ok(result1); + ctx.table.register_infer_ok(result2); + return self.expected_ty = target_ty; } } - // It might not seem like it, but order is important here: ty1 is our - // "previous" type, ty2 is the "new" one being added. If the previous + // It might not seem like it, but order is important here: If the expected // type is a type variable and the new one is `!`, trying it the other // way around first would mean we make the type variable `!`, instead of // just marking it as possibly diverging. - if self.coerce(&ty2, &ty1).is_ok() { - ty1 - } else if self.coerce(&ty1, &ty2).is_ok() { - ty2 + if ctx.coerce(expr, &expr_ty, &self.expected_ty).is_ok() { + /* self.expected_ty is already correct */ + } else if ctx.coerce(expr, &self.expected_ty, &expr_ty).is_ok() { + self.expected_ty = expr_ty; } else { - if let Some(id) = id { - self.result - .type_mismatches - .insert(id.into(), TypeMismatch { expected: ty1.clone(), actual: ty2 }); + if let Some(id) = expr { + ctx.result.type_mismatches.insert( + id.into(), + TypeMismatch { expected: self.expected_ty.clone(), actual: expr_ty }, + ); } cov_mark::hit!(coerce_merge_fail_fallback); - ty1 + /* self.expected_ty is already correct */ + } + } + + pub(super) fn complete(self) -> Ty { + self.expected_ty + } +} + +impl<'a> InferenceContext<'a> { + /// Unify two types, but may coerce the first one to the second one + /// using "implicit coercion rules" if needed. + pub(super) fn coerce( + &mut self, + expr: Option, + from_ty: &Ty, + to_ty: &Ty, + ) -> InferResult { + let from_ty = self.resolve_ty_shallow(from_ty); + let to_ty = self.resolve_ty_shallow(to_ty); + match self.coerce_inner(from_ty, &to_ty) { + Ok(InferOk { value: (adjustments, ty), goals }) => { + if let Some(expr) = expr { + self.write_expr_adj(expr, adjustments); + } + self.table.register_infer_ok(InferOk { value: (), goals }); + Ok(InferOk { value: ty, goals: Vec::new() }) + } + Err(e) => { + // FIXME deal with error + Err(e) + } } } @@ -189,7 +228,6 @@ impl<'a> InferenceContext<'a> { // Check that the types which they point at are compatible. let from_raw = TyKind::Raw(to_mt, from_inner.clone()).intern(&Interner); - // self.table.try_unify(&from_raw, to_ty); // Although references and unsafe ptrs have the same // representation, we still register an Adjust::DerefRef so that @@ -518,15 +556,13 @@ impl<'a> InferenceContext<'a> { // FIXME: should we accept ambiguous results here? _ => return Err(TypeError), }; - // TODO: this is probably wrong? - let coerce_target = self.table.new_type_var(); - self.unify_and(&coerce_target, to_ty, |target| { - let unsize = Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), target }; - match reborrow { - None => vec![unsize], - Some((ref deref, ref autoref)) => vec![deref.clone(), autoref.clone(), unsize], - } - }) + let unsize = + Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), target: to_ty.clone() }; + let adjustments = match reborrow { + None => vec![unsize], + Some((deref, autoref)) => vec![deref, autoref, unsize], + }; + success(adjustments, to_ty.clone(), vec![]) } } diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs index a582d700f2..20830d03e7 100644 --- a/crates/hir_ty/src/infer/expr.rs +++ b/crates/hir_ty/src/infer/expr.rs @@ -16,6 +16,7 @@ use syntax::ast::RangeOp; use crate::{ autoderef, consteval, + infer::coerce::CoerceMany, lower::lower_to_chalk_mutability, mapping::from_chalk, method_resolution, op, @@ -56,11 +57,8 @@ impl<'a> InferenceContext<'a> { pub(super) fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { let ty = self.infer_expr_inner(expr, expected); let ty = if let Some(target) = expected.only_has_type(&mut self.table) { - match self.coerce(&ty, &target) { - Ok(res) => { - self.result.expr_adjustments.insert(expr, res.value.0); - target - } + match self.coerce(Some(expr), &ty, &target) { + Ok(res) => res.value, Err(_) => { self.result .type_mismatches @@ -128,31 +126,32 @@ impl<'a> InferenceContext<'a> { let body = Arc::clone(&self.body); // avoid borrow checker problem let ty = match &body[tgt_expr] { Expr::Missing => self.err_ty(), - Expr::If { condition, then_branch, else_branch } => { + &Expr::If { condition, then_branch, else_branch } => { // if let is desugared to match, so this is always simple if self.infer_expr( - *condition, + condition, &Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(&Interner)), ); let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let mut both_arms_diverge = Diverges::Always; - let mut result_ty = self.table.new_type_var(); - let then_ty = self.infer_expr_inner(*then_branch, expected); + let result_ty = self.table.new_type_var(); + let then_ty = self.infer_expr_inner(then_branch, expected); both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe); - result_ty = self.coerce_merge_branch(Some(*then_branch), &result_ty, &then_ty); + let mut coerce = CoerceMany::new(result_ty); + coerce.coerce(self, Some(then_branch), &then_ty); let else_ty = match else_branch { - Some(else_branch) => self.infer_expr_inner(*else_branch, expected), + Some(else_branch) => self.infer_expr_inner(else_branch, expected), None => TyBuilder::unit(), }; both_arms_diverge &= self.diverges; // FIXME: create a synthetic `else {}` so we have something to refer to here instead of None? - result_ty = self.coerce_merge_branch(*else_branch, &result_ty, &else_ty); + coerce.coerce(self, else_branch, &else_ty); self.diverges = condition_diverges | both_arms_diverge; - result_ty + coerce.complete() } Expr::Block { statements, tail, label, id: _ } => { let old_resolver = mem::replace( @@ -193,7 +192,7 @@ impl<'a> InferenceContext<'a> { } Expr::Async { body } => { // Use the first type parameter as the output type of future. - // existenail type AsyncBlockImplTrait: Future + // existential type AsyncBlockImplTrait: Future let inner_ty = self.infer_expr(*body, &Expectation::none()); let impl_trait_id = crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, *body); let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into(); @@ -223,6 +222,7 @@ impl<'a> InferenceContext<'a> { self.breakables.push(BreakableContext { may_break: false, break_ty: self.err_ty(), + label: label.map(|label| self.body[label].name.clone()), }); // while let is desugared to a match loop, so this is always simple while @@ -344,7 +344,7 @@ impl<'a> InferenceContext<'a> { let expected = expected.adjust_for_branches(&mut self.table); - let mut result_ty = if arms.is_empty() { + let result_ty = if arms.is_empty() { TyKind::Never.intern(&Interner) } else { match &expected { @@ -352,6 +352,7 @@ impl<'a> InferenceContext<'a> { _ => self.table.new_type_var(), } }; + let mut coerce = CoerceMany::new(result_ty); let matchee_diverges = self.diverges; let mut all_arms_diverge = Diverges::Always; @@ -368,12 +369,12 @@ impl<'a> InferenceContext<'a> { let arm_ty = self.infer_expr_inner(arm.expr, &expected); all_arms_diverge &= self.diverges; - result_ty = self.coerce_merge_branch(Some(arm.expr), &result_ty, &arm_ty); + coerce.coerce(self, Some(arm.expr), &arm_ty); } self.diverges = matchee_diverges | all_arms_diverge; - result_ty + coerce.complete() } Expr::Path(p) => { // FIXME this could be more efficient... @@ -382,6 +383,7 @@ impl<'a> InferenceContext<'a> { } Expr::Continue { .. } => TyKind::Never.intern(&Interner), Expr::Break { expr, label } => { + let expr = *expr; let last_ty = if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { ctxt.break_ty.clone() @@ -390,13 +392,13 @@ impl<'a> InferenceContext<'a> { }; let val_ty = if let Some(expr) = expr { - self.infer_expr(*expr, &Expectation::none()) + self.infer_expr(expr, &Expectation::none()) } else { TyBuilder::unit() }; // FIXME: create a synthetic `()` during lowering so we have something to refer to here? - let merged_type = self.coerce_merge_branch(*expr, &last_ty, &val_ty); + let merged_type = CoerceMany::once(self, last_ty, expr, &val_ty); if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { ctxt.break_ty = merged_type; @@ -413,9 +415,7 @@ impl<'a> InferenceContext<'a> { self.infer_expr_coerce(*expr, &Expectation::has_type(self.return_ty.clone())); } else { let unit = TyBuilder::unit(); - if let Ok(ok) = self.coerce(&unit, &self.return_ty.clone()) { - self.write_expr_adj(tgt_expr, ok.value.0); - } + let _ = self.coerce(Some(tgt_expr), &unit, &self.return_ty.clone()); } TyKind::Never.intern(&Interner) } @@ -744,39 +744,37 @@ impl<'a> InferenceContext<'a> { TyKind::Tuple(tys.len(), Substitution::from_iter(&Interner, tys)).intern(&Interner) } Expr::Array(array) => { - let mut elem_ty = + let elem_ty = match expected.to_option(&mut self.table).as_ref().map(|t| t.kind(&Interner)) { Some(TyKind::Array(st, _) | TyKind::Slice(st)) => st.clone(), _ => self.table.new_type_var(), }; + let mut coerce = CoerceMany::new(elem_ty.clone()); let expected = Expectation::has_type(elem_ty.clone()); let len = match array { Array::ElementList(items) => { - for expr in items.iter() { - let cur_elem_ty = self.infer_expr_inner(*expr, &expected); - elem_ty = self.coerce_merge_branch(Some(*expr), &elem_ty, &cur_elem_ty); + for &expr in items.iter() { + let cur_elem_ty = self.infer_expr_inner(expr, &expected); + coerce.coerce(self, Some(expr), &cur_elem_ty); } Some(items.len() as u64) } - Array::Repeat { initializer, repeat } => { - self.infer_expr_coerce( - *initializer, - &Expectation::has_type(elem_ty.clone()), - ); + &Array::Repeat { initializer, repeat } => { + self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty)); self.infer_expr( - *repeat, + repeat, &Expectation::has_type( TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(&Interner), ), ); - let repeat_expr = &self.body.exprs[*repeat]; + let repeat_expr = &self.body.exprs[repeat]; consteval::eval_usize(repeat_expr) } }; - TyKind::Array(elem_ty, consteval::usize_const(len)).intern(&Interner) + TyKind::Array(coerce.complete(), consteval::usize_const(len)).intern(&Interner) } Expr::Literal(lit) => match lit { Literal::Bool(..) => TyKind::Scalar(Scalar::Bool).intern(&Interner), @@ -872,9 +870,7 @@ impl<'a> InferenceContext<'a> { self.table.new_maybe_never_var() } else { if let Some(t) = expected.only_has_type(&mut self.table) { - if let Ok(ok) = self.coerce(&TyBuilder::unit(), &t) { - self.write_expr_adj(expr, ok.value.0); - } + let _ = self.coerce(Some(expr), &TyBuilder::unit(), &t); } TyBuilder::unit() } From f73d0ee439054c7f19c4dced0d0732c2663626e6 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 8 Jul 2021 14:27:54 +0200 Subject: [PATCH 3/6] Minor cleanup --- crates/hir_ty/src/infer.rs | 4 ++-- crates/hir_ty/src/infer/coerce.rs | 22 ++++++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs index a66caa369c..630d72f6e6 100644 --- a/crates/hir_ty/src/infer.rs +++ b/crates/hir_ty/src/infer.rs @@ -34,10 +34,10 @@ use rustc_hash::FxHashMap; use stdx::impl_from; use syntax::SmolStr; -use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty}; use crate::{ db::HirDatabase, fold_tys, lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, - Goal, Interner, Substitution, TyBuilder, TyExt, TyKind, + DomainGoal, Goal, InEnvironment, Interner, ProjectionTy, Substitution, TraitEnvironment, + TraitRef, Ty, TyBuilder, TyExt, TyKind, }; // This lint has a false positive here. See the link below for details. diff --git a/crates/hir_ty/src/infer/coerce.rs b/crates/hir_ty/src/infer/coerce.rs index 7a624090b6..46d1f9afe1 100644 --- a/crates/hir_ty/src/infer/coerce.rs +++ b/crates/hir_ty/src/infer/coerce.rs @@ -10,13 +10,14 @@ use hir_def::{expr::ExprId, lang_item::LangItemTarget}; use crate::{ autoderef, - infer::{Adjust, Adjustment, AutoBorrow, InferResult, PointerCast, TypeMismatch}, - static_lifetime, Canonical, DomainGoal, FnPointer, FnSig, Interner, Solution, Substitution, Ty, - TyBuilder, TyExt, TyKind, + infer::{ + Adjust, Adjustment, AutoBorrow, InferOk, InferResult, InferenceContext, PointerCast, + TypeError, TypeMismatch, + }, + static_lifetime, Canonical, DomainGoal, FnPointer, FnSig, InEnvironment, Interner, Solution, + Substitution, Ty, TyBuilder, TyExt, TyKind, }; -use super::{InEnvironment, InferOk, InferenceContext, TypeError}; - pub(crate) type CoerceResult = Result, Ty)>, TypeError>; /// Do not require any adjustments, i.e. coerce `x -> x`. @@ -36,6 +37,7 @@ fn success( ) -> CoerceResult { Ok(InferOk { goals, value: (adj, target) }) } + #[derive(Clone, Debug)] pub(super) struct CoerceMany { expected_ty: Ty, @@ -171,12 +173,8 @@ impl<'a> InferenceContext<'a> { // Examine the supertype and consider auto-borrowing. match to_ty.kind(&Interner) { - TyKind::Raw(mt, _) => { - return self.coerce_ptr(from_ty, to_ty, *mt); - } - TyKind::Ref(mt, _, _) => { - return self.coerce_ref(from_ty, to_ty, *mt); - } + TyKind::Raw(mt, _) => return self.coerce_ptr(from_ty, to_ty, *mt), + TyKind::Ref(mt, _, _) => return self.coerce_ref(from_ty, to_ty, *mt), _ => {} } @@ -337,7 +335,7 @@ impl<'a> InferenceContext<'a> { return Err(err); } }; - // FIXME: record overloarded deref adjustments + // FIXME: record overloaded deref adjustments success( vec![Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(to_mt)), target: ty.clone() }], ty, From 349f2535fb6b78c7f1293f7444b06a988f90e8d1 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 8 Jul 2021 14:31:16 +0200 Subject: [PATCH 4/6] Copy some comments from rustc --- crates/hir_ty/src/infer.rs | 62 ++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs index 630d72f6e6..f5713d5480 100644 --- a/crates/hir_ty/src/infer.rs +++ b/crates/hir_ty/src/infer.rs @@ -141,7 +141,46 @@ impl Default for InternedStandardTypes { InternedStandardTypes { unknown: TyKind::Error.intern(&Interner) } } } - +/// Represents coercing a value to a different type of value. +/// +/// We transform values by following a number of `Adjust` steps in order. +/// See the documentation on variants of `Adjust` for more details. +/// +/// Here are some common scenarios: +/// +/// 1. The simplest cases are where a pointer is not adjusted fat vs thin. +/// Here the pointer will be dereferenced N times (where a dereference can +/// happen to raw or borrowed pointers or any smart pointer which implements +/// Deref, including Box<_>). The types of dereferences is given by +/// `autoderefs`. It can then be auto-referenced zero or one times, indicated +/// by `autoref`, to either a raw or borrowed pointer. In these cases unsize is +/// `false`. +/// +/// 2. A thin-to-fat coercion involves unsizing the underlying data. We start +/// with a thin pointer, deref a number of times, unsize the underlying data, +/// then autoref. The 'unsize' phase may change a fixed length array to a +/// dynamically sized one, a concrete object to a trait object, or statically +/// sized struct to a dynamically sized one. E.g., &[i32; 4] -> &[i32] is +/// represented by: +/// +/// ``` +/// Deref(None) -> [i32; 4], +/// Borrow(AutoBorrow::Ref) -> &[i32; 4], +/// Unsize -> &[i32], +/// ``` +/// +/// Note that for a struct, the 'deep' unsizing of the struct is not recorded. +/// E.g., `struct Foo { x: T }` we can coerce &Foo<[i32; 4]> to &Foo<[i32]> +/// The autoderef and -ref are the same as in the above example, but the type +/// stored in `unsize` is `Foo<[i32]>`, we don't store any further detail about +/// the underlying conversions from `[i32; 4]` to `[i32]`. +/// +/// 3. Coercing a `Box` to `Box` is an interesting special case. In +/// that case, we have the pointer we need coming in, so there are no +/// autoderefs, and no autoref. Instead we just do the `Unsize` transformation. +/// At some point, of course, `Box` should move out of the compiler, in which +/// case this is analogous to transforming a struct. E.g., Box<[i32; 4]> -> +/// Box<[i32]> is an `Adjust::Unsize` with the target `Box<[i32]>`. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Adjustment { pub kind: Adjust, @@ -152,34 +191,25 @@ pub struct Adjustment { pub enum Adjust { /// Go from ! to any type. NeverToAny, - /// Dereference once, producing a place. Deref(Option), - /// Take the address and produce either a `&` or `*` pointer. Borrow(AutoBorrow), - Pointer(PointerCast), } -// impl fmt::Display for Adjust { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// match self { -// Adjust::NeverToAny => write!(f, "NeverToAny"), -// Adjust::Deref(_) => write!(f, "Deref"), // FIXME -// Adjust::Borrow(AutoBorrow::Ref(mt)) => write!(f, "BorrowRef{:?}", mt), -// Adjust::Borrow(AutoBorrow::RawPtr(mt)) => write!(f, "BorrowRawPtr{:?}", mt), -// Adjust::Pointer(cast) => write!(f, "PtrCast{:?}", cast), -// } -// } -// } - +/// An overloaded autoderef step, representing a `Deref(Mut)::deref(_mut)` +/// call, with the signature `&'a T -> &'a U` or `&'a mut T -> &'a mut U`. +/// The target type is `U` in both cases, with the region and mutability +/// being those shared by both the receiver and the returned reference. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct OverloadedDeref(Mutability); #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum AutoBorrow { + /// Converts from T to &T. Ref(Mutability), + /// Converts from T to *T. RawPtr(Mutability), } From e968d834caad94069d863f44f90dc5872de29941 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 8 Jul 2021 15:03:57 +0200 Subject: [PATCH 5/6] Add some more adjustment test annotations --- crates/hir_ty/src/tests/coercion.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/hir_ty/src/tests/coercion.rs b/crates/hir_ty/src/tests/coercion.rs index 6f146f9dd3..cc5dc5ab65 100644 --- a/crates/hir_ty/src/tests/coercion.rs +++ b/crates/hir_ty/src/tests/coercion.rs @@ -51,7 +51,9 @@ fn let_stmt_coerce() { //- minicore: coerce_unsized fn test() { let x: &[isize] = &[1]; + // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize) let x: *const [isize] = &[1]; + // ^^^^ adjustments: Deref(None), Borrow(RawPtr(Not)), Pointer(Unsize) } ", ); @@ -171,9 +173,12 @@ fn test() { 2 => t as &i32, //^^^^^^^^^ expected *mut i32, got &i32 _ => t as *const i32, + // ^^^^^^^^^^^^^^^ adjustments: Pointer(MutToConstPointer) + }; x; //^ type: *const i32 + } ", ); @@ -258,6 +263,9 @@ fn coerce_fn_item_to_fn_ptr() { fn foo(x: u32) -> isize { 1 } fn test() { let f: fn(u32) -> isize = foo; + // ^^^ adjustments: Pointer(ReifyFnPointer) + let f: unsafe fn(u32) -> isize = foo; + // ^^^ adjustments: Pointer(ReifyFnPointer) }", ); } From 9272942b92c069df9421ce5a2073913711ec7a7c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 9 Jul 2021 14:56:49 +0200 Subject: [PATCH 6/6] Use `CoerceMany` in `BreakableContext` --- crates/hir_ty/src/infer.rs | 8 +++--- crates/hir_ty/src/infer/coerce.rs | 11 -------- crates/hir_ty/src/infer/expr.rs | 43 ++++++++++++++++--------------- 3 files changed, 26 insertions(+), 36 deletions(-) diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs index f5713d5480..b1c0432273 100644 --- a/crates/hir_ty/src/infer.rs +++ b/crates/hir_ty/src/infer.rs @@ -35,9 +35,9 @@ use stdx::impl_from; use syntax::SmolStr; use crate::{ - db::HirDatabase, fold_tys, lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, - DomainGoal, Goal, InEnvironment, Interner, ProjectionTy, Substitution, TraitEnvironment, - TraitRef, Ty, TyBuilder, TyExt, TyKind, + db::HirDatabase, fold_tys, infer::coerce::CoerceMany, lower::ImplTraitLoweringMode, + to_assoc_type_id, AliasEq, AliasTy, DomainGoal, Goal, InEnvironment, Interner, ProjectionTy, + Substitution, TraitEnvironment, TraitRef, Ty, TyBuilder, TyExt, TyKind, }; // This lint has a false positive here. See the link below for details. @@ -349,7 +349,7 @@ struct InferenceContext<'a> { #[derive(Clone, Debug)] struct BreakableContext { may_break: bool, - break_ty: Ty, + coerce: CoerceMany, label: Option, } diff --git a/crates/hir_ty/src/infer/coerce.rs b/crates/hir_ty/src/infer/coerce.rs index 46d1f9afe1..2599a8c6b1 100644 --- a/crates/hir_ty/src/infer/coerce.rs +++ b/crates/hir_ty/src/infer/coerce.rs @@ -48,17 +48,6 @@ impl CoerceMany { CoerceMany { expected_ty: expected } } - pub(super) fn once( - ctx: &mut InferenceContext<'_>, - expected: Ty, - expr: Option, - expr_ty: &Ty, - ) -> Ty { - let mut this = CoerceMany::new(expected); - this.coerce(ctx, expr, expr_ty); - this.complete() - } - /// Merge two types from different branches, with possible coercion. /// /// Mostly this means trying to coerce one to the other, but diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs index 20830d03e7..9904676096 100644 --- a/crates/hir_ty/src/infer/expr.rs +++ b/crates/hir_ty/src/infer/expr.rs @@ -163,7 +163,7 @@ impl<'a> InferenceContext<'a> { let break_ty = self.table.new_type_var(); self.breakables.push(BreakableContext { may_break: false, - break_ty: break_ty.clone(), + coerce: CoerceMany::new(break_ty.clone()), label: label.map(|label| self.body[label].name.clone()), }); let ty = self.infer_block( @@ -174,7 +174,7 @@ impl<'a> InferenceContext<'a> { ); let ctxt = self.breakables.pop().expect("breakable stack broken"); if ctxt.may_break { - ctxt.break_ty + ctxt.coerce.complete() } else { ty } @@ -202,18 +202,16 @@ impl<'a> InferenceContext<'a> { Expr::Loop { body, label } => { self.breakables.push(BreakableContext { may_break: false, - break_ty: self.table.new_type_var(), + coerce: CoerceMany::new(self.table.new_type_var()), label: label.map(|label| self.body[label].name.clone()), }); self.infer_expr(*body, &Expectation::has_type(TyBuilder::unit())); let ctxt = self.breakables.pop().expect("breakable stack broken"); - if ctxt.may_break { - self.diverges = Diverges::Maybe; - } if ctxt.may_break { - ctxt.break_ty + self.diverges = Diverges::Maybe; + ctxt.coerce.complete() } else { TyKind::Never.intern(&Interner) } @@ -221,8 +219,7 @@ impl<'a> InferenceContext<'a> { Expr::While { condition, body, label } => { self.breakables.push(BreakableContext { may_break: false, - break_ty: self.err_ty(), - + coerce: CoerceMany::new(self.err_ty()), label: label.map(|label| self.body[label].name.clone()), }); // while let is desugared to a match loop, so this is always simple while @@ -241,7 +238,7 @@ impl<'a> InferenceContext<'a> { self.breakables.push(BreakableContext { may_break: false, - break_ty: self.err_ty(), + coerce: CoerceMany::new(self.err_ty()), label: label.map(|label| self.body[label].name.clone()), }); let pat_ty = @@ -383,31 +380,35 @@ impl<'a> InferenceContext<'a> { } Expr::Continue { .. } => TyKind::Never.intern(&Interner), Expr::Break { expr, label } => { - let expr = *expr; - let last_ty = - if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { - ctxt.break_ty.clone() - } else { - self.err_ty() - }; + let mut coerce = match find_breakable(&mut self.breakables, label.as_ref()) { + Some(ctxt) => { + // avoiding the borrowck + mem::replace( + &mut ctxt.coerce, + CoerceMany::new(self.result.standard_types.unknown.clone()), + ) + } + None => CoerceMany::new(self.result.standard_types.unknown.clone()), + }; - let val_ty = if let Some(expr) = expr { + let val_ty = if let Some(expr) = *expr { self.infer_expr(expr, &Expectation::none()) } else { TyBuilder::unit() }; // FIXME: create a synthetic `()` during lowering so we have something to refer to here? - let merged_type = CoerceMany::once(self, last_ty, expr, &val_ty); + coerce.coerce(self, *expr, &val_ty); if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { - ctxt.break_ty = merged_type; + ctxt.coerce = coerce; ctxt.may_break = true; } else { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { expr: tgt_expr, }); - } + }; + TyKind::Never.intern(&Interner) } Expr::Return { expr } => {