Record autoderef adjustments

This commit is contained in:
Lukas Wirth 2021-07-09 19:12:56 +02:00
parent 9272942b92
commit 69e6511820
6 changed files with 167 additions and 80 deletions

View File

@ -13,12 +13,83 @@ use log::{info, warn};
use crate::{
db::HirDatabase, static_lifetime, AliasEq, AliasTy, BoundVar, Canonical, CanonicalVarKinds,
DebruijnIndex, InEnvironment, Interner, ProjectionTyExt, Solution, Substitution, Ty, TyBuilder,
TyKind,
DebruijnIndex, Environment, InEnvironment, Interner, ProjectionTyExt, Solution, Substitution,
Ty, TyBuilder, TyKind,
};
pub(crate) enum AutoderefKind {
Builtin,
Overloaded,
}
pub(crate) struct Autoderef<'db> {
db: &'db dyn HirDatabase,
ty: Canonical<Ty>,
at_start: bool,
krate: Option<CrateId>,
environment: Environment,
steps: Vec<(AutoderefKind, Ty)>,
}
impl<'db> Autoderef<'db> {
pub(crate) fn new(
db: &'db dyn HirDatabase,
krate: Option<CrateId>,
ty: InEnvironment<Canonical<Ty>>,
) -> Self {
let InEnvironment { goal: ty, environment } = ty;
Autoderef { db, ty, at_start: true, environment, krate, steps: Vec::new() }
}
pub(crate) fn step_count(&self) -> usize {
self.steps.len()
}
pub(crate) fn steps(&self) -> &[(AutoderefKind, chalk_ir::Ty<Interner>)] {
&self.steps
}
pub(crate) fn final_ty(&self) -> Ty {
self.ty.value.clone()
}
}
impl Iterator for Autoderef<'_> {
type Item = (Canonical<Ty>, usize);
fn next(&mut self) -> Option<Self::Item> {
if self.at_start {
self.at_start = false;
return Some((self.ty.clone(), 0));
}
if self.steps.len() >= AUTODEREF_RECURSION_LIMIT {
return None;
}
let (kind, new_ty) = if let Some(derefed) = builtin_deref(&self.ty.value) {
(AutoderefKind::Builtin, Canonical { value: derefed, binders: self.ty.binders.clone() })
} else {
(
AutoderefKind::Overloaded,
deref_by_trait(
self.db,
self.krate?,
InEnvironment { goal: &self.ty, environment: self.environment.clone() },
)?,
)
};
self.steps.push((kind, self.ty.value.clone()));
self.ty = new_ty;
Some((self.ty.clone(), self.step_count()))
}
}
const AUTODEREF_RECURSION_LIMIT: usize = 10;
// FIXME: replace uses of this with Autoderef above
pub fn autoderef<'a>(
db: &'a dyn HirDatabase,
krate: Option<CrateId>,

View File

@ -5,14 +5,17 @@
//! See <https://doc.rust-lang.org/nomicon/coercions.html> and
//! `librustc_typeck/check/coercion.rs`.
use std::iter;
use chalk_ir::{cast::Cast, Goal, Mutability, TyVariableKind};
use hir_def::{expr::ExprId, lang_item::LangItemTarget};
use stdx::always;
use crate::{
autoderef,
autoderef::{Autoderef, AutoderefKind},
infer::{
Adjust, Adjustment, AutoBorrow, InferOk, InferResult, InferenceContext, PointerCast,
TypeError, TypeMismatch,
Adjust, Adjustment, AutoBorrow, InferOk, InferResult, InferenceContext, OverloadedDeref,
PointerCast, TypeError, TypeMismatch,
},
static_lifetime, Canonical, DomainGoal, FnPointer, FnSig, InEnvironment, Interner, Solution,
Substitution, Ty, TyBuilder, TyExt, TyKind,
@ -241,9 +244,10 @@ impl<'a> InferenceContext<'a> {
/// 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) -> CoerceResult {
match from_ty.kind(&Interner) {
TyKind::Ref(mt, _, _) => {
coerce_mutabilities(*mt, to_mt)?;
let from_mt = match from_ty.kind(&Interner) {
&TyKind::Ref(mt, _, _) => {
coerce_mutabilities(mt, to_mt)?;
mt
}
_ => return self.unify_and(&from_ty, to_ty, identity),
};
@ -254,8 +258,8 @@ impl<'a> InferenceContext<'a> {
// details of coercion errors though, so I think it's useful to leave
// the structure like it is.
let canonicalized = self.canonicalize(from_ty);
let autoderef = autoderef::autoderef(
let canonicalized = self.canonicalize(from_ty.clone());
let mut autoderef = Autoderef::new(
self.db,
self.resolver.krate(),
InEnvironment {
@ -266,7 +270,7 @@ impl<'a> InferenceContext<'a> {
let mut first_error = None;
let mut found = None;
for (autoderefs, referent_ty) in autoderef.enumerate() {
for (referent_ty, autoderefs) in autoderef.by_ref() {
if autoderefs == 0 {
// Don't let this pass, otherwise it would cause
// &T to autoref to &&T.
@ -324,12 +328,43 @@ impl<'a> InferenceContext<'a> {
return Err(err);
}
};
// FIXME: record overloaded deref adjustments
success(
vec![Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(to_mt)), target: ty.clone() }],
ty,
goals,
)
if ty == from_ty && from_mt == Mutability::Not && autoderef.step_count() == 1 {
// As a special case, if we would produce `&'a *x`, that's
// a total no-op. We end up with the type `&'a T` just as
// we started with. In that case, just skip it
// altogether. This is just an optimization.
//
// Note that for `&mut`, we DO want to reborrow --
// otherwise, this would be a move, which might be an
// error. For example `foo(self.x)` where `self` and
// `self.x` both have `&mut `type would be a move of
// `self.x`, but we auto-coerce it to `foo(&mut *self.x)`,
// which is a borrow.
always!(to_mt == Mutability::Not); // can only coerce &T -> &U
return success(vec![], ty, goals);
}
let mut adjustments = self.auto_deref_adjust_steps(&autoderef);
adjustments
.push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(to_mt)), target: ty.clone() });
success(adjustments, ty, goals)
}
pub(super) fn auto_deref_adjust_steps(&self, autoderef: &Autoderef<'_>) -> Vec<Adjustment> {
let steps = autoderef.steps();
let targets =
steps.iter().skip(1).map(|(_, ty)| ty.clone()).chain(iter::once(autoderef.final_ty()));
steps
.iter()
.map(|(kind, _source)| match kind {
// We do not know what kind of deref we require at this point yet
AutoderefKind::Overloaded => Some(OverloadedDeref(Mutability::Not)),
AutoderefKind::Builtin => None,
})
.zip(targets)
.map(|(autoderef, target)| Adjustment { kind: Adjust::Deref(autoderef), target })
.collect()
}
/// Attempts to coerce from the type of a Rust function item into a function pointer.

View File

@ -1,7 +1,10 @@
//! Type inference for expressions.
use std::iter::{repeat, repeat_with};
use std::{mem, sync::Arc};
use std::{
iter::{repeat, repeat_with},
mem,
sync::Arc,
};
use chalk_ir::{cast::Cast, fold::Shift, Mutability, TyVariableKind};
use hir_def::{
@ -15,7 +18,8 @@ use stdx::always;
use syntax::ast::RangeOp;
use crate::{
autoderef, consteval,
autoderef::{self, Autoderef},
consteval,
infer::coerce::CoerceMany,
lower::lower_to_chalk_mutability,
mapping::from_chalk,
@ -314,7 +318,7 @@ impl<'a> InferenceContext<'a> {
Expr::Call { callee, args } => {
let callee_ty = self.infer_expr(*callee, &Expectation::none());
let canonicalized = self.canonicalize(callee_ty.clone());
let mut derefs = autoderef(
let mut derefs = Autoderef::new(
self.db,
self.resolver.krate(),
InEnvironment {
@ -322,14 +326,19 @@ impl<'a> InferenceContext<'a> {
environment: self.table.trait_env.env.clone(),
},
);
let (param_tys, ret_ty): (Vec<Ty>, Ty) = derefs
.find_map(|callee_deref_ty| {
self.callable_sig(
&canonicalized.decanonicalize_ty(callee_deref_ty.value),
args.len(),
)
})
.unwrap_or((Vec::new(), self.err_ty()));
let res = derefs.by_ref().find_map(|(callee_deref_ty, _)| {
self.callable_sig(
&canonicalized.decanonicalize_ty(callee_deref_ty.value),
args.len(),
)
});
let (param_tys, ret_ty): (Vec<Ty>, Ty) = match res {
Some(res) => {
self.write_expr_adj(*callee, self.auto_deref_adjust_steps(&derefs));
res
}
None => (Vec::new(), self.err_ty()),
};
self.register_obligations_for_call(&callee_ty);
self.check_call_arguments(args, &param_tys);
self.normalize_associated_types_in(ret_ty)
@ -467,15 +476,16 @@ impl<'a> InferenceContext<'a> {
Expr::Field { expr, name } => {
let receiver_ty = self.infer_expr_inner(*expr, &Expectation::none());
let canonicalized = self.canonicalize(receiver_ty);
let ty = autoderef::autoderef(
let mut autoderef = Autoderef::new(
self.db,
self.resolver.krate(),
InEnvironment {
goal: canonicalized.value.clone(),
environment: self.trait_env.env.clone(),
},
)
.find_map(|derefed_ty| {
);
let ty = autoderef.by_ref().find_map(|(derefed_ty, _)| {
let def_db = self.db.upcast();
let module = self.resolver.module();
let is_visible = |field_id: &FieldId| {
@ -524,8 +534,14 @@ impl<'a> InferenceContext<'a> {
}
_ => None,
}
})
.unwrap_or_else(|| self.err_ty());
});
let ty = match ty {
Some(ty) => {
self.write_expr_adj(*expr, self.auto_deref_adjust_steps(&autoderef));
ty
}
None => self.err_ty(),
};
let ty = self.insert_type_vars(ty);
self.normalize_associated_types_in(ty)
}

View File

@ -112,6 +112,7 @@ pub type Canonical<T> = chalk_ir::Canonical<T>;
pub type FnSig = chalk_ir::FnSig<Interner>;
pub type InEnvironment<T> = chalk_ir::InEnvironment<T>;
pub type Environment = chalk_ir::Environment<Interner>;
pub type DomainGoal = chalk_ir::DomainGoal<Interner>;
pub type Goal = chalk_ir::Goal<Interner>;
pub type AliasEq = chalk_ir::AliasEq<Interner>;

View File

@ -236,6 +236,7 @@ fn takes_ref_str(x: &str) {}
fn returns_string() -> String { loop {} }
fn test() {
takes_ref_str(&{ returns_string() });
// ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(Not))), Borrow(Ref(Not))
}
"#,
);

View File

@ -1,5 +1,7 @@
use expect_test::expect;
use crate::tests::check;
use super::{check_infer, check_types};
#[test]
@ -1137,7 +1139,7 @@ fn main() {
#[test]
fn autoderef_visibility_field() {
check_infer(
check(
r#"
//- minicore: deref
mod a {
@ -1158,33 +1160,18 @@ mod a {
mod b {
fn foo() {
let x = super::a::Bar::new().0;
// ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Not)))
// ^^^^^^^^^^^^^^^^^^^^^^ type: char
}
}
"#,
expect![[r#"
107..138 '{ ... }': Bar
121..125 'Self': Bar(i32) -> Bar
121..128 'Self(0)': Bar
126..127 '0': i32
226..230 'self': &Bar
240..273 '{ ... }': &Foo
254..263 '&Foo('z')': &Foo
255..258 'Foo': Foo(char) -> Foo
255..263 'Foo('z')': Foo
259..262 ''z'': char
303..350 '{ ... }': ()
317..318 'x': char
321..339 'super:...r::new': fn new() -> Bar
321..341 'super:...:new()': Bar
321..343 'super:...ew().0': char
"#]],
"#,
)
}
#[test]
fn autoderef_visibility_method() {
cov_mark::check!(autoderef_candidate_not_visible);
check_infer(
check(
r#"
//- minicore: deref
mod a {
@ -1213,34 +1200,10 @@ mod a {
mod b {
fn foo() {
let x = super::a::Bar::new().mango();
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type: char
}
}
"#,
expect![[r#"
75..79 'self': &Foo
89..119 '{ ... }': char
103..107 'self': &Foo
103..109 'self.0': char
195..226 '{ ... }': Bar
209..213 'Self': Bar(i32) -> Bar
209..216 'Self(0)': Bar
214..215 '0': i32
245..249 'self': &Bar
258..288 '{ ... }': i32
272..276 'self': &Bar
272..278 'self.0': i32
376..380 'self': &Bar
390..423 '{ ... }': &Foo
404..413 '&Foo('z')': &Foo
405..408 'Foo': Foo(char) -> Foo
405..413 'Foo('z')': Foo
409..412 ''z'': char
453..506 '{ ... }': ()
467..468 'x': char
471..489 'super:...r::new': fn new() -> Bar
471..491 'super:...:new()': Bar
471..499 'super:...ango()': char
"#]],
"#,
)
}