hm-inference #6

Merged
sb10q merged 136 commits from hm-inference into master 2021-08-19 11:46:50 +08:00
5 changed files with 240 additions and 235 deletions
Showing only changes of commit fa40fd73c6 - Show all commits

View File

@ -200,14 +200,8 @@ impl TopLevelComposer {
ast: ast::Stmt<()>, ast: ast::Stmt<()>,
resolver: Option<Arc<Mutex<dyn SymbolResolver + Send + Sync>>>, resolver: Option<Arc<Mutex<dyn SymbolResolver + Send + Sync>>>,
) -> Result<(String, DefinitionId), String> { ) -> Result<(String, DefinitionId), String> {
let ( let (mut def_list, mut ast_list) = (self.definition_list.write(), self.ast_list.write());
mut def_list,
mut ast_list
) = (
self.definition_list.write(),
self.ast_list.write()
);
assert_eq!(def_list.len(), ast_list.len()); assert_eq!(def_list.len(), ast_list.len());
match &ast.node { match &ast.node {
@ -235,11 +229,14 @@ impl TopLevelComposer {
def_list.push( def_list.push(
Self::make_top_level_function_def( Self::make_top_level_function_def(
fun_name.clone(), fun_name.clone(),
self.unifier.write().add_ty(TypeEnum::TFunc(FunSignature { self.unifier.write().add_ty(TypeEnum::TFunc(
args: Default::default(), FunSignature {
ret: self.primitives.none.into(), args: Default::default(),
vars: Default::default(), ret: self.primitives.none.into(),
}.into())), vars: Default::default(),
}
.into(),
)),
resolver.clone(), resolver.clone(),
) )
.into(), .into(),
@ -256,17 +253,14 @@ impl TopLevelComposer {
ast_list[class_def_id] = Some(ast); ast_list[class_def_id] = Some(ast);
// put the constructor into the def_list // put the constructor into the def_list
def_list.push( def_list
TopLevelDef::Initializer { class_id: DefinitionId(class_def_id) } .push(TopLevelDef::Initializer { class_id: DefinitionId(class_def_id) }.into());
.into(),
);
ast_list.push(None); ast_list.push(None);
// class, put its def_id into the to be analyzed set // class, put its def_id into the to be analyzed set
let mut to_be_analyzed = self.to_be_analyzed_class.write(); let mut to_be_analyzed = self.to_be_analyzed_class.write();
to_be_analyzed.push(DefinitionId(class_def_id)); to_be_analyzed.push(DefinitionId(class_def_id));
Ok((class_name, DefinitionId(class_def_id))) Ok((class_name, DefinitionId(class_def_id)))
} }
@ -297,27 +291,24 @@ impl TopLevelComposer {
for (class_def, class_ast) in def_list for (class_def, class_ast) in def_list
.iter_mut() .iter_mut()
.zip(ast_list.iter()) .zip(ast_list.iter())
.collect::<Vec<(&mut RwLock<TopLevelDef>, &Option<ast::Stmt<()>>)>>() { .collect::<Vec<(&mut RwLock<TopLevelDef>, &Option<ast::Stmt<()>>)>>()
{
// only deal with class def here // only deal with class def here
let ( let (class_bases, class_def_type_vars, class_resolver) = {
class_bases, if let TopLevelDef::Class { type_vars, resolver, .. } = class_def.get_mut() {
class_def_type_vars, if let Some(ast::Located {
class_resolver node: ast::StmtKind::ClassDef { bases, .. }, ..
) = { }) = class_ast
if let TopLevelDef::Class { {
type_vars,
resolver,
..
} = class_def.get_mut() {
if let Some(ast::Located {node: ast::StmtKind::ClassDef {
bases,
..
}, .. }) = class_ast {
(bases, type_vars, resolver) (bases, type_vars, resolver)
} else { unreachable!("must be both class") } } else {
} else { continue } unreachable!("must be both class")
}; }
} else {
continue;
}
};
let mut is_generic = false; let mut is_generic = false;
for b in class_bases { for b in class_bases {
match &b.node { match &b.node {
@ -326,84 +317,86 @@ impl TopLevelComposer {
// things like `class A(Generic[T, V, ImportedModule.T])` is not supported // things like `class A(Generic[T, V, ImportedModule.T])` is not supported
// i.e. only simple names are allowed in the subscript // i.e. only simple names are allowed in the subscript
// should update the TopLevelDef::Class.typevars and the TypeEnum::TObj.params // should update the TopLevelDef::Class.typevars and the TypeEnum::TObj.params
ast::ExprKind::Subscript {value, slice, ..} if { ast::ExprKind::Subscript { value, slice, .. }
// can only be `Generic[...]` and this can only appear once if {
if let ast::ExprKind::Name { id, .. } = &value.node { // can only be `Generic[...]` and this can only appear once
if id == "Generic" { if let ast::ExprKind::Name { id, .. } = &value.node {
if !is_generic { if id == "Generic" {
is_generic = true; if !is_generic {
true is_generic = true;
true
} else {
return Err(
"Only single Generic[...] can be in bases".into()
);
}
} else { } else {
return Err("Only single Generic[...] can be in bases".into()) false
} }
} else { false } } else {
} else { false } false
} => { }
} =>
{
// if `class A(Generic[T, V, G])` // if `class A(Generic[T, V, G])`
if let ast::ExprKind::Tuple { elts, .. } = &slice.node { if let ast::ExprKind::Tuple { elts, .. } = &slice.node {
// parse the type vars // parse the type vars
let type_vars = elts let type_vars = elts
.iter() .iter()
.map(|e| .map(|e| {
class_resolver class_resolver.as_ref().unwrap().lock().parse_type_annotation(
.as_ref()
.unwrap()
.lock()
.parse_type_annotation(
&self.to_top_level_context(), &self.to_top_level_context(),
unifier.borrow_mut(), unifier.borrow_mut(),
&self.primitives, &self.primitives,
e) e,
) )
})
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
// check if all are unique type vars // check if all are unique type vars
let mut occured_type_var_id: HashSet<u32> = HashSet::new(); let mut occured_type_var_id: HashSet<u32> = HashSet::new();
let all_unique_type_var = type_vars let all_unique_type_var = type_vars.iter().all(|x| {
.iter() let ty = unifier.get_ty(*x);
.all(|x| { if let TypeEnum::TVar { id, .. } = ty.as_ref() {
let ty = unifier.get_ty(*x); occured_type_var_id.insert(*id)
if let TypeEnum::TVar {id, ..} = ty.as_ref() { } else {
occured_type_var_id.insert(*id) false
} else { false } }
}); });
if !all_unique_type_var { return Err("expect unique type variables".into()) } if !all_unique_type_var {
return Err("expect unique type variables".into());
}
// add to TopLevelDef // add to TopLevelDef
class_def_type_vars.extend(type_vars); class_def_type_vars.extend(type_vars);
// `class A(Generic[T])` // `class A(Generic[T])`
} else { } else {
let ty = let ty =
class_resolver class_resolver.as_ref().unwrap().lock().parse_type_annotation(
.as_ref()
.unwrap()
.lock()
.parse_type_annotation(
&self.to_top_level_context(), &self.to_top_level_context(),
unifier.borrow_mut(), unifier.borrow_mut(),
&self.primitives, &self.primitives,
&slice &slice,
)?; )?;
// check if it is type var // check if it is type var
let is_type_var = matches!( let is_type_var =
unifier.get_ty(ty).as_ref(), matches!(unifier.get_ty(ty).as_ref(), &TypeEnum::TVar { .. });
&TypeEnum::TVar { .. } if !is_type_var {
); return Err("expect type variable here".into());
if !is_type_var { return Err("expect type variable here".into()) } }
// add to TopLevelDef // add to TopLevelDef
class_def_type_vars.push(ty); class_def_type_vars.push(ty);
} }
} }
// if others, do nothing in this function // if others, do nothing in this function
_ => continue _ => continue,
} }
} }
}
};
Ok(()) Ok(())
} }
@ -420,30 +413,29 @@ impl TopLevelComposer {
for (class_def, class_ast) in def_list for (class_def, class_ast) in def_list
.iter_mut() .iter_mut()
.zip(ast_list.iter()) .zip(ast_list.iter())
.collect::<Vec<(&mut RwLock<TopLevelDef>, &Option<ast::Stmt<()>>)>>() { .collect::<Vec<(&mut RwLock<TopLevelDef>, &Option<ast::Stmt<()>>)>>()
let ( {
class_bases, let (class_bases, class_ancestors, class_resolver) = {
class_ancestors, if let TopLevelDef::Class { ancestors, resolver, .. } = class_def.get_mut() {
class_resolver if let Some(ast::Located {
) = { node: ast::StmtKind::ClassDef { bases, .. }, ..
if let TopLevelDef::Class { }) = class_ast
ancestors, {
resolver,
..
} = class_def.get_mut() {
if let Some(ast::Located {node: ast::StmtKind::ClassDef {
bases,
..
}, .. }) = class_ast {
(bases, ancestors, resolver) (bases, ancestors, resolver)
} else { unreachable!("must be both class") } } else {
} else { continue } unreachable!("must be both class")
}; }
} else {
continue;
}
};
for b in class_bases { for b in class_bases {
// type vars have already been handled, so skip on `Generic[...]` // type vars have already been handled, so skip on `Generic[...]`
if let ast::ExprKind::Subscript {value, ..} = &b.node { if let ast::ExprKind::Subscript { value, .. } = &b.node {
if let ast::ExprKind::Name {id, ..} = &value.node { if let ast::ExprKind::Name { id, .. } = &value.node {
if id == "Generic" { continue } if id == "Generic" {
continue;
}
} }
} }
// get the def id of the base class // get the def id of the base class
@ -451,18 +443,19 @@ impl TopLevelComposer {
&self.to_top_level_context(), &self.to_top_level_context(),
unifier.borrow_mut(), unifier.borrow_mut(),
&self.primitives, &self.primitives,
b b,
)?; )?;
let base_id = let base_id =
if let TypeEnum::TObj {obj_id, ..} = unifier.get_ty(base_ty).as_ref() { if let TypeEnum::TObj { obj_id, .. } = unifier.get_ty(base_ty).as_ref() {
*obj_id *obj_id
} else { return Err("expect concrete class/type to be base class".into()) }; } else {
return Err("expect concrete class/type to be base class".into());
};
// write to the class ancestors // write to the class ancestors
class_ancestors.push(base_id); class_ancestors.push(base_id);
} }
}
};
Ok(()) Ok(())
} }
@ -478,21 +471,18 @@ impl TopLevelComposer {
let class_ind = to_be_analyzed_class.remove(0).0; let class_ind = to_be_analyzed_class.remove(0).0;
let (class_name, class_body) = { let (class_name, class_body) = {
let class_ast = &ast_list[class_ind]; let class_ast = &ast_list[class_ind];
if let Some( if let Some(ast::Located {
ast::Located { node: node: ast::StmtKind::ClassDef { name, body, .. }, ..
ast::StmtKind::ClassDef { }) = class_ast
name, {
body,
..
},
..
}
) = class_ast {
(name, body) (name, body)
} else { unreachable!("should be class def ast") } } else {
unreachable!("should be class def ast")
}
}; };
let class_methods_parsing_result: Vec<(String, Type, DefinitionId)> = Default::default(); let class_methods_parsing_result: Vec<(String, Type, DefinitionId)> =
Default::default();
let class_fields_parsing_result: Vec<(String, Type)> = Default::default(); let class_fields_parsing_result: Vec<(String, Type)> = Default::default();
for b in class_body { for b in class_body {
if let ast::StmtKind::FunctionDef { if let ast::StmtKind::FunctionDef {
@ -501,25 +491,24 @@ impl TopLevelComposer {
name: method_name, name: method_name,
returns: method_returns_ast, returns: method_returns_ast,
.. ..
} = &b.node { } = &b.node
{
let (class_def, method_def) = { let (class_def, method_def) = {
// unwrap should not fail // unwrap should not fail
let method_ind = class_method_to_def_id let method_ind = class_method_to_def_id
.get(&Self::name_mangling( .get(&Self::name_mangling(class_name.into(), method_name))
class_name.into(), .unwrap()
method_name) .0;
).unwrap().0;
// split the def_list to two parts to get the // split the def_list to two parts to get the
// mutable reference to both the method and the class // mutable reference to both the method and the class
assert_ne!(method_ind, class_ind); assert_ne!(method_ind, class_ind);
let min_ind = (if method_ind > class_ind { class_ind } else { method_ind }) + 1; let min_ind =
let (head_slice, (if method_ind > class_ind { class_ind } else { method_ind }) + 1;
tail_slice let (head_slice, tail_slice) = def_list.split_at_mut(min_ind);
) = def_list.split_at_mut(min_ind);
let (new_method_ind, new_class_ind) = ( let (new_method_ind, new_class_ind) = (
if method_ind >= min_ind { method_ind - min_ind } else { method_ind }, if method_ind >= min_ind { method_ind - min_ind } else { method_ind },
if class_ind >= min_ind { class_ind - min_ind } else { class_ind } if class_ind >= min_ind { class_ind - min_ind } else { class_ind },
); );
if new_class_ind == class_ind { if new_class_ind == class_ind {
(&mut head_slice[new_class_ind], &mut tail_slice[new_method_ind]) (&mut head_slice[new_class_ind], &mut tail_slice[new_method_ind])
@ -527,19 +516,14 @@ impl TopLevelComposer {
(&mut tail_slice[new_class_ind], &mut head_slice[new_method_ind]) (&mut tail_slice[new_class_ind], &mut head_slice[new_method_ind])
} }
}; };
let ( let (class_fields, class_methods, class_resolver) = {
class_fields, if let TopLevelDef::Class { resolver, fields, methods, .. } =
class_methods, class_def.get_mut()
class_resolver {
) = {
if let TopLevelDef::Class {
resolver,
fields,
methods,
..
} = class_def.get_mut() {
(fields, methods, resolver) (fields, methods, resolver)
} else { unreachable!("must be class def here") } } else {
unreachable!("must be class def here")
}
}; };
let arg_tys = method_args_ast let arg_tys = method_args_ast
@ -550,18 +534,17 @@ impl TopLevelComposer {
.node .node
.annotation .annotation
.as_ref() .as_ref()
.ok_or_else(|| "type annotation for function parameter is needed".to_string())? .ok_or_else(|| {
"type annotation for function parameter is needed".to_string()
})?
.as_ref(); .as_ref();
let ty = class_resolver let ty =
.as_ref() class_resolver.as_ref().unwrap().lock().parse_type_annotation(
.unwrap()
.lock()
.parse_type_annotation(
&self.to_top_level_context(), &self.to_top_level_context(),
unifier.borrow_mut(), unifier.borrow_mut(),
&self.primitives, &self.primitives,
annotation annotation,
)?; )?;
Ok(ty) Ok(ty)
}) })
@ -569,39 +552,37 @@ impl TopLevelComposer {
let ret_ty = method_returns_ast let ret_ty = method_returns_ast
.as_ref() .as_ref()
.and_then(|x| { .and_then(|x| {
Some( Some(class_resolver.as_ref().unwrap().lock().parse_type_annotation(
class_resolver &self.to_top_level_context(),
.as_ref() unifier.borrow_mut(),
.unwrap() &self.primitives,
.lock() x.as_ref(),
.parse_type_annotation( ))
&self.to_top_level_context(), })
unifier.borrow_mut(), .unwrap()?;
&self.primitives,
x.as_ref()
)
)
}).unwrap()?;
let all_tys_ok = { let all_tys_ok = {
let ret_ty_iter = vec![ret_ty]; let ret_ty_iter = vec![ret_ty];
let ret_ty_iter = ret_ty_iter.iter(); let ret_ty_iter = ret_ty_iter.iter();
let mut all_tys = chain!(arg_tys.iter(), ret_ty_iter); let mut all_tys = chain!(arg_tys.iter(), ret_ty_iter);
all_tys.all(|x| { all_tys.all(|x| {
let type_enum = unifier.get_ty(*x); let type_enum = unifier.get_ty(*x);
match type_enum.as_ref() { match type_enum.as_ref() {
TypeEnum::TObj {obj_id, ..} => { TypeEnum::TObj { obj_id, .. } => {
!to_be_analyzed_class.contains(obj_id) !to_be_analyzed_class.contains(obj_id)
},
TypeEnum::TVirtual { ty } => {
if let TypeEnum::TObj {obj_id, ..} = unifier.get_ty(*ty).as_ref() {
!to_be_analyzed_class.contains(obj_id)
} else { unreachable!() }
},
_ => unreachable!()
} }
TypeEnum::TVirtual { ty } => {
if let TypeEnum::TObj { obj_id, .. } =
unifier.get_ty(*ty).as_ref()
{
!to_be_analyzed_class.contains(obj_id)
} else {
unreachable!()
}
}
_ => unreachable!(),
} }
) })
}; };
if all_tys_ok { if all_tys_ok {
@ -614,21 +595,21 @@ impl TopLevelComposer {
} }
} else { } else {
// what should we do with `class A: a = 3`? // what should we do with `class A: a = 3`?
continue continue;
} }
} }
// TODO: now it should be confirmed that every // TODO: now it should be confirmed that every
// methods and fields of the class can be correctly typed, put the results // methods and fields of the class can be correctly typed, put the results
// into the actual def_list and the unifier // into the actual def_list and the unifier
} }
Ok(()) Ok(())
} }
fn analyze_top_level_inheritance(&mut self) -> Result<(), String> { fn analyze_top_level_inheritance(&mut self) -> Result<(), String> {
unimplemented!() unimplemented!()
} }
fn analyze_top_level_field_instantiation(&mut self) -> Result<(), String> { fn analyze_top_level_field_instantiation(&mut self) -> Result<(), String> {
unimplemented!() unimplemented!()
} }

View File

@ -80,11 +80,18 @@ pub fn impl_binop(
} else { } else {
unifier.get_fresh_var_with_range(other_ty).0 unifier.get_fresh_var_with_range(other_ty).0
}; };
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(
ret: ret_ty, FunSignature {
vars: HashMap::new(), ret: ret_ty,
args: vec![FuncArg { ty: other, default_value: None, name: "other".into() }], vars: HashMap::new(),
}.into())) args: vec![FuncArg {
ty: other,
default_value: None,
name: "other".into(),
}],
}
.into(),
))
}); });
fields.borrow_mut().insert(binop_assign_name(op).into(), { fields.borrow_mut().insert(binop_assign_name(op).into(), {
@ -93,11 +100,18 @@ pub fn impl_binop(
} else { } else {
unifier.get_fresh_var_with_range(other_ty).0 unifier.get_fresh_var_with_range(other_ty).0
}; };
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(
ret: ret_ty, FunSignature {
vars: HashMap::new(), ret: ret_ty,
args: vec![FuncArg { ty: other, default_value: None, name: "other".into() }], vars: HashMap::new(),
}.into())) args: vec![FuncArg {
ty: other,
default_value: None,
name: "other".into(),
}],
}
.into(),
))
}); });
} }
} else { } else {
@ -116,11 +130,9 @@ pub fn impl_unaryop(
for op in ops { for op in ops {
fields.borrow_mut().insert( fields.borrow_mut().insert(
unaryop_name(op).into(), unaryop_name(op).into(),
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(
ret: ret_ty, FunSignature { ret: ret_ty, vars: HashMap::new(), args: vec![] }.into(),
vars: HashMap::new(), )),
args: vec![],
}.into())),
); );
} }
} else { } else {
@ -139,11 +151,18 @@ pub fn impl_cmpop(
for op in ops { for op in ops {
fields.borrow_mut().insert( fields.borrow_mut().insert(
comparison_name(op).unwrap().into(), comparison_name(op).unwrap().into(),
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(
ret: store.bool, FunSignature {
vars: HashMap::new(), ret: store.bool,
args: vec![FuncArg { ty: other_ty, default_value: None, name: "other".into() }], vars: HashMap::new(),
}.into())), args: vec![FuncArg {
ty: other_ty,
default_value: None,
name: "other".into(),
}],
}
.into(),
)),
); );
} }
} else { } else {

View File

@ -176,18 +176,19 @@ impl TestEnvironment {
identifier_mapping.insert( identifier_mapping.insert(
"Foo".into(), "Foo".into(),
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(
args: vec![], FunSignature {
ret: foo_ty, args: vec![],
vars: [(id, v0)].iter().cloned().collect(), ret: foo_ty,
}.into())), vars: [(id, v0)].iter().cloned().collect(),
}
.into(),
)),
); );
let fun = unifier.add_ty(TypeEnum::TFunc(FunSignature { let fun = unifier.add_ty(TypeEnum::TFunc(
args: vec![], FunSignature { args: vec![], ret: int32, vars: Default::default() }.into(),
ret: int32, ));
vars: Default::default(),
}.into()));
let bar = unifier.add_ty(TypeEnum::TObj { let bar = unifier.add_ty(TypeEnum::TObj {
obj_id: DefinitionId(6), obj_id: DefinitionId(6),
fields: [("a".into(), int32), ("b".into(), fun)] fields: [("a".into(), int32), ("b".into(), fun)]
@ -207,11 +208,9 @@ impl TestEnvironment {
})); }));
identifier_mapping.insert( identifier_mapping.insert(
"Bar".into(), "Bar".into(),
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(
args: vec![], FunSignature { args: vec![], ret: bar, vars: Default::default() }.into(),
ret: bar, )),
vars: Default::default(),
}.into())),
); );
let bar2 = unifier.add_ty(TypeEnum::TObj { let bar2 = unifier.add_ty(TypeEnum::TObj {
@ -233,11 +232,9 @@ impl TestEnvironment {
})); }));
identifier_mapping.insert( identifier_mapping.insert(
"Bar2".into(), "Bar2".into(),
unifier.add_ty(TypeEnum::TFunc(FunSignature { unifier.add_ty(TypeEnum::TFunc(
args: vec![], FunSignature { args: vec![], ret: bar2, vars: Default::default() }.into(),
ret: bar2, )),
vars: Default::default(),
}.into())),
); );
let class_names = [("Bar".into(), bar), ("Bar2".into(), bar2)].iter().cloned().collect(); let class_names = [("Bar".into(), bar), ("Bar2".into(), bar2)].iter().cloned().collect();

View File

@ -472,7 +472,8 @@ impl Unifier {
} }
(TCall(calls), TFunc(signature)) => { (TCall(calls), TFunc(signature)) => {
self.occur_check(a, b)?; self.occur_check(a, b)?;
let required: Vec<String> = signature.borrow() let required: Vec<String> = signature
.borrow()
.args .args
.iter() .iter()
.filter(|v| v.default_value.is_none()) .filter(|v| v.default_value.is_none())
@ -494,8 +495,13 @@ impl Unifier {
// we check to make sure that all required arguments (those without default // we check to make sure that all required arguments (those without default
// arguments) are provided, and do not provide the same argument twice. // arguments) are provided, and do not provide the same argument twice.
let mut required = required.clone(); let mut required = required.clone();
let mut all_names: Vec<_> = let mut all_names: Vec<_> = signature
signature.borrow().args.iter().map(|v| (v.name.clone(), v.ty)).rev().collect(); .borrow()
.args
.iter()
.map(|v| (v.name.clone(), v.ty))
.rev()
.collect();
for (i, t) in posargs.iter().enumerate() { for (i, t) in posargs.iter().enumerate() {
if signature.borrow().args.len() <= i { if signature.borrow().args.len() <= i {
return Err("Too many arguments.".to_string()); return Err("Too many arguments.".to_string());
@ -741,7 +747,11 @@ impl Unifier {
let params = new_params.unwrap_or_else(|| params.clone()); let params = new_params.unwrap_or_else(|| params.clone());
let ret = new_ret.unwrap_or_else(|| *ret); let ret = new_ret.unwrap_or_else(|| *ret);
let args = new_args.into_owned(); let args = new_args.into_owned();
Some(self.add_ty(TypeEnum::TFunc(FunSignature { args, ret, vars: params }.into()))) Some(
self.add_ty(TypeEnum::TFunc(
FunSignature { args, ret, vars: params }.into(),
)),
)
} else { } else {
None None
} }

View File

@ -329,11 +329,9 @@ fn test_invalid_unification(
fn test_virtual() { fn test_virtual() {
let mut env = TestEnvironment::new(); let mut env = TestEnvironment::new();
let int = env.parse("int", &HashMap::new()); let int = env.parse("int", &HashMap::new());
let fun = env.unifier.add_ty(TypeEnum::TFunc(FunSignature { let fun = env.unifier.add_ty(TypeEnum::TFunc(
args: vec![], FunSignature { args: vec![], ret: int, vars: HashMap::new() }.into(),
ret: int, ));
vars: HashMap::new(),
}.into()));
let bar = env.unifier.add_ty(TypeEnum::TObj { let bar = env.unifier.add_ty(TypeEnum::TObj {
obj_id: DefinitionId(5), obj_id: DefinitionId(5),
fields: [("f".to_string(), fun), ("a".to_string(), int)] fields: [("f".to_string(), fun), ("a".to_string(), int)]