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],