Record coercion adjustments

This commit is contained in:
Lukas Wirth 2021-07-06 18:05:40 +02:00
parent e44c56b616
commit 9779526d8f
9 changed files with 339 additions and 86 deletions

View File

@ -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 }),
},
)

View File

@ -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<T> {
value: T,
goals: Vec<InEnvironment<Goal>>,
}
impl<T> InferOk<T> {
fn map<U>(self, f: impl FnOnce(T) -> U) -> InferOk<U> {
InferOk { value: f(self.value), goals: self.goals }
}
}
#[derive(Debug)]
pub(crate) struct TypeError;
pub(crate) type InferResult = Result<InferOk, TypeError>;
pub(crate) type InferResult<T> = Result<InferOk<T>, 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<OverloadedDeref>),
/// 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<PatId, Vec<Ty>>,
pub pat_adjustments: FxHashMap<PatId, Vec<Adjustment>>,
pub expr_adjustments: FxHashMap<ExprId, Vec<Adjustment>>,
}
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<Adjustment>) {
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));
}

View File

@ -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) {

View File

@ -5,30 +5,52 @@
//! See <https://doc.rust-lang.org/nomicon/coercions.html> 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<InferOk<(Vec<Adjustment>, Ty)>, TypeError>;
/// Do not require any adjustments, i.e. coerce `x -> x`.
fn identity(_: Ty) -> Vec<Adjustment> {
vec![]
}
fn simple(kind: Adjust) -> impl FnOnce(Ty) -> Vec<Adjustment> {
move |target| vec![Adjustment { kind, target }]
}
/// This always returns `Ok(...)`.
fn success(
adj: Vec<Adjustment>,
target: Ty,
goals: Vec<InEnvironment<Goal<Interner>>>,
) -> 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<ExprId>, 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<?T> = 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<F>(&mut self, t1: &Ty, t2: &Ty, f: F) -> CoerceResult
where
F: FnOnce(Ty) -> Vec<Adjustment>,
{
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<T>`
// 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<F, G>(
&mut self,
from_ty: Ty,
from_fn_ptr: &FnPointer,
to_ty: &Ty,
) -> InferResult {
to_unsafe: F,
normal: G,
) -> CoerceResult
where
F: FnOnce(Ty) -> Vec<Adjustment>,
G: FnOnce(Ty) -> Vec<Adjustment>,
{
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<ty_ty>`
///
/// See: <https://doc.rust-lang.org/nightly/std/marker/trait.CoerceUnsized.html>
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<B> 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<T> CoerceUnsized<SomeType> for T`)
// or generic over the `CoerceUnsized` type parameter (`impl<T> CoerceUnsized<T> for
@ -359,20 +446,34 @@ impl<'a> InferenceContext<'a> {
}
// Handle reborrows before trying to solve `Source: CoerceUnsized<Target>`.
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],
}
})
}
}

View File

@ -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<ExprId>,
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()
}

View File

@ -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),

View File

@ -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<T: Zip<Interner>>(&mut self, t1: &T, t2: &T) -> InferResult {
pub(crate) fn try_unify<T: Zip<Interner>>(&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<T>(&mut self, infer_ok: InferOk<T>) {
infer_ok.goals.into_iter().for_each(|goal| self.register_obligation_in_env(goal));
}

View File

@ -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::<Vec<_>>()
);
} 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);
}

View File

@ -96,6 +96,7 @@ fn foo<T>(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<T>(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<T>(x: &[T]) -> &[T] { loop {} }
// ^^^^^^^ adjustments: NeverToAny
fn test(i: i32) {
let x = match i {
1 => &[1],