Get basic struct pattern type inference working!

This commit is contained in:
Marcus Klaas de Vries 2019-01-16 20:26:58 +01:00 committed by Aleksey Kladov
parent ab5deb7811
commit 3340807bd2
3 changed files with 123 additions and 80 deletions

View File

@ -331,8 +331,8 @@ impl_arena_id!(PatId);
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct FieldPat {
name: Name,
pat: Option<PatId>,
pub(crate) name: Name,
pub(crate) pat: Option<PatId>,
}
/// Close relative to rustc's hir::PatKind
@ -392,7 +392,9 @@ impl Pat {
let total_iter = prefix.iter().chain(rest.iter()).chain(suffix.iter());
total_iter.map(|pat| *pat).for_each(f);
}
Pat::Struct { .. } => {} // TODO
Pat::Struct { args, .. } => {
args.iter().filter_map(|a| a.pat).for_each(f);
}
}
}
}
@ -814,23 +816,20 @@ impl ExprCollector {
ast::PatKind::PlaceholderPat(_) => Pat::Wild,
ast::PatKind::StructPat(p) => {
let path = p.path().and_then(Path::from_ast);
let fields = p
.field_pat_list()
.expect("every struct should have a field list")
.field_pats()
.into_iter()
.map(|f| FieldPat {
name: Name::new(f.ident),
pat: f.pat.as_ref().map(|p| self.collect_pat(p)),
})
.collect();
if let Some(field_list) = p.field_pat_list() {
let fields = field_list
.field_pats()
.into_iter()
.map(|f| FieldPat {
name: Name::new(f.ident),
pat: f.pat.as_ref().map(|p| self.collect_pat(p)),
})
.collect();
Pat::Struct {
path: path,
args: fields,
}
} else {
Pat::Missing
Pat::Struct {
path: path,
args: fields,
}
}

View File

@ -36,7 +36,7 @@ use crate::{
db::HirDatabase,
type_ref::{TypeRef, Mutability},
name::KnownName,
expr::{Body, Expr, Literal, ExprId, Pat, PatId, UnaryOp, BinaryOp, Statement},
expr::{Body, Expr, Literal, ExprId, Pat, PatId, UnaryOp, BinaryOp, Statement, FieldPat},
};
/// The ID of a type variable.
@ -872,6 +872,90 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
}
}
fn resolve_fields(&self, path: Option<&Path>) -> Option<(Ty, Vec<crate::adt::StructField>)> {
let def = path
.and_then(|path| self.module.resolve_path(self.db, &path).take_types())
.map(|def_id| def_id.resolve(self.db));
let def = if let Some(def) = def {
def
} else {
return None;
};
match def {
Def::Struct(s) => {
let fields: Vec<_> = self
.db
.struct_data(s.def_id())
.variant_data
.fields()
.iter()
.cloned()
.collect();
Some((type_for_struct(self.db, s), fields))
}
Def::EnumVariant(ev) => {
let fields: Vec<_> = ev.variant_data(self.db).fields().iter().cloned().collect();
Some((type_for_enum_variant(self.db, ev), fields))
}
_ => None,
}
}
fn infer_tuple_struct(&mut self, path: Option<&Path>, sub_pats: &[PatId]) -> Ty {
let (ty, fields) = if let Some(x) = self.resolve_fields(path) {
x
} else {
return Ty::Unknown;
};
// walk subpats
if fields.len() != sub_pats.len() {
return Ty::Unknown;
}
for (&sub_pat, field) in sub_pats.iter().zip(fields.iter()) {
let sub_ty = Ty::from_hir(
self.db,
&self.module,
self.impl_block.as_ref(),
&field.type_ref,
);
self.infer_pat(sub_pat, &Expectation::has_type(sub_ty));
}
ty
}
fn infer_struct(&mut self, path: Option<&Path>, sub_pats: &[FieldPat]) -> Ty {
let (ty, fields) = if let Some(x) = self.resolve_fields(path) {
x
} else {
return Ty::Unknown;
};
for sub_pat in sub_pats {
let tyref = fields
.iter()
.find(|field| field.name == sub_pat.name)
.map(|field| &field.type_ref);
if let Some(typeref) = tyref {
let sub_ty = Ty::from_hir(self.db, &self.module, self.impl_block.as_ref(), typeref);
if let Some(pat) = sub_pat.pat {
self.infer_pat(pat, &Expectation::has_type(sub_ty));
} else {
// TODO: deal with this case: S { x, y }
}
}
}
ty
}
// FIXME: Expectation should probably contain a reference to a Ty instead of
// a Ty itself
fn infer_pat(&mut self, pat: PatId, expected: &Expectation) -> Ty {
@ -900,54 +984,15 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
path: ref p,
args: ref sub_pats,
},
_expected,
) => {
let def = p
.as_ref()
.and_then(|path| self.module.resolve_path(self.db, &path).take_types())
.map(|def_id| def_id.resolve(self.db));
if let Some(def) = def {
let (ty, fields) = match def {
Def::Struct(s) => {
let fields: Vec<_> = self
.db
.struct_data(s.def_id())
.variant_data
.fields()
.iter()
.cloned()
.collect();
(type_for_struct(self.db, s), fields)
}
Def::EnumVariant(ev) => {
let fields: Vec<_> =
ev.variant_data(self.db).fields().iter().cloned().collect();
(type_for_enum_variant(self.db, ev), fields)
}
_ => unreachable!(),
};
// walk subpats
if fields.len() == sub_pats.len() {
for (&sub_pat, field) in sub_pats.iter().zip(fields.iter()) {
let sub_ty = Ty::from_hir(
self.db,
&self.module,
self.impl_block.as_ref(),
&field.type_ref,
);
self.infer_pat(sub_pat, &Expectation::has_type(sub_ty));
}
ty
} else {
expected.ty.clone()
}
} else {
expected.ty.clone()
}
}
_,
) => self.infer_tuple_struct(p.as_ref(), sub_pats),
(
&Pat::Struct {
path: ref p,
args: ref fields,
},
_,
) => self.infer_struct(p.as_ref(), fields),
(_, ref _expected_ty) => expected.ty.clone(),
};
// use a new type variable if we got Ty::Unknown here

View File

@ -1,12 +1,11 @@
[49; 192) '{ ... }; }': ()
[59; 60) 'e': E
[63; 76) 'E::A { x: 3 }': E
[73; 74) '3': usize
[82; 124) 'if let... }': [unknown]
[105; 106) 'e': E
[107; 124) '{ ... }': [unknown]
[117; 118) 'x': [unknown]
[130; 189) 'match ... }': [unknown]
[136; 137) 'e': E
[162; 163) 'x': [unknown]
[181; 182) '1': i32
[68; 155) '{ ...= e; }': ()
[78; 79) 'e': E
[82; 95) 'E::A { x: 3 }': E
[92; 93) '3': usize
[106; 113) 'S(y, z)': S
[108; 109) 'y': u32
[111; 112) 'z': E
[116; 119) 'foo': S
[129; 148) 'E::A {..._var }': E
[139; 146) 'new_var': usize
[151; 152) 'e': E