diff --git a/nac3core/rustfmt.toml b/nac3core/rustfmt.toml new file mode 100644 index 00000000..cfaa54ae --- /dev/null +++ b/nac3core/rustfmt.toml @@ -0,0 +1 @@ + use_small_heuristics = "Max" diff --git a/nac3core/src/typecheck/function_check.rs b/nac3core/src/typecheck/function_check.rs index 537656a2..7d3a3729 100644 --- a/nac3core/src/typecheck/function_check.rs +++ b/nac3core/src/typecheck/function_check.rs @@ -32,7 +32,7 @@ impl<'a> Inferencer<'a> { // there are some cases where the custom field is None if let Some(ty) = &expr.custom { let ty = self.unifier.get_ty(*ty); - let ty = ty.as_ref().borrow(); + let ty = ty.as_ref(); if !ty.is_concrete() { return Err(format!( "expected concrete type at {} but got {}", diff --git a/nac3core/src/typecheck/type_inferencer/mod.rs b/nac3core/src/typecheck/type_inferencer/mod.rs index 1f27daba..8e5aec2a 100644 --- a/nac3core/src/typecheck/type_inferencer/mod.rs +++ b/nac3core/src/typecheck/type_inferencer/mod.rs @@ -54,17 +54,11 @@ impl<'a> fold::Fold<()> for Inferencer<'a> { fn fold_stmt(&mut self, node: ast::Stmt<()>) -> Result, Self::Error> { let stmt = match node.node { // we don't want fold over type annotation - ast::StmtKind::AnnAssign { - target, - annotation, - value, - simple, - } => { + ast::StmtKind::AnnAssign { target, annotation, value, simple } => { let target = Box::new(fold::fold_expr(self, *target)?); let value = if let Some(v) = value { let ty = Box::new(fold::fold_expr(self, *v)?); - self.unifier - .unify(target.custom.unwrap(), ty.custom.unwrap())?; + self.unifier.unify(target.custom.unwrap(), ty.custom.unwrap())?; Some(ty) } else { None @@ -73,37 +67,27 @@ impl<'a> fold::Fold<()> for Inferencer<'a> { .resolver .parse_type_name(annotation.as_ref()) .ok_or_else(|| "cannot parse type name".to_string())?; - self.unifier - .unify(annotation_type, target.custom.unwrap())?; + self.unifier.unify(annotation_type, target.custom.unwrap())?; let annotation = Box::new(NaiveFolder().fold_expr(*annotation)?); Located { location: node.location, custom: None, - node: ast::StmtKind::AnnAssign { - target, - annotation, - value, - simple, - }, + node: ast::StmtKind::AnnAssign { target, annotation, value, simple }, } } _ => fold::fold_stmt(self, node)?, }; match &stmt.node { ast::StmtKind::For { target, iter, .. } => { - let list = self.unifier.add_ty(TypeEnum::TList { - ty: target.custom.unwrap(), - }); + let list = self.unifier.add_ty(TypeEnum::TList { ty: target.custom.unwrap() }); self.unifier.unify(list, iter.custom.unwrap())?; } ast::StmtKind::If { test, .. } | ast::StmtKind::While { test, .. } => { - self.unifier - .unify(test.custom.unwrap(), self.primitives.bool)?; + self.unifier.unify(test.custom.unwrap(), self.primitives.bool)?; } ast::StmtKind::Assign { targets, value, .. } => { for target in targets.iter() { - self.unifier - .unify(target.custom.unwrap(), value.custom.unwrap())?; + self.unifier.unify(target.custom.unwrap(), value.custom.unwrap())?; } } ast::StmtKind::AnnAssign { .. } | ast::StmtKind::Expr { .. } => {} @@ -127,11 +111,7 @@ impl<'a> fold::Fold<()> for Inferencer<'a> { fn fold_expr(&mut self, node: ast::Expr<()>) -> Result, Self::Error> { let expr = match node.node { - ast::ExprKind::Call { - func, - args, - keywords, - } => { + ast::ExprKind::Call { func, args, keywords } => { return self.fold_call(node.location, *func, args, keywords); } ast::ExprKind::Lambda { args, body } => { @@ -147,19 +127,15 @@ impl<'a> fold::Fold<()> for Inferencer<'a> { ast::ExprKind::Name { id, .. } => Some(self.infer_identifier(id)?), ast::ExprKind::List { elts, .. } => Some(self.infer_list(elts)?), ast::ExprKind::Tuple { elts, .. } => Some(self.infer_tuple(elts)?), - ast::ExprKind::Attribute { - value, - attr, - ctx: _, - } => Some(self.infer_attribute(value, attr)?), + ast::ExprKind::Attribute { value, attr, ctx: _ } => { + Some(self.infer_attribute(value, attr)?) + } ast::ExprKind::BoolOp { values, .. } => Some(self.infer_bool_ops(values)?), ast::ExprKind::BinOp { left, op, right } => Some(self.infer_bin_ops(left, op, right)?), ast::ExprKind::UnaryOp { op, operand } => Some(self.infer_unary_ops(op, operand)?), - ast::ExprKind::Compare { - left, - ops, - comparators, - } => Some(self.infer_compare(left, ops, comparators)?), + ast::ExprKind::Compare { left, ops, comparators } => { + Some(self.infer_compare(left, ops, comparators)?) + } ast::ExprKind::Subscript { value, slice, .. } => { Some(self.infer_subscript(value.as_ref(), slice.as_ref())?) } @@ -172,11 +148,7 @@ impl<'a> fold::Fold<()> for Inferencer<'a> { ast::ExprKind::Slice { .. } => None, // we don't need it for slice _ => return Err("not supported yet".into()), }; - Ok(ast::Expr { - custom, - location: expr.location, - node: expr.node, - }) + Ok(ast::Expr { custom, location: expr.location, node: expr.node }) } } @@ -196,16 +168,12 @@ impl<'a> Inferencer<'a> { params: Vec, ret: Type, ) -> InferenceResult { - let call = Rc::new(Call { - posargs: params, - kwargs: HashMap::new(), - ret, - fun: RefCell::new(None), - }); + let call = + Rc::new(Call { posargs: params, kwargs: HashMap::new(), ret, fun: RefCell::new(None) }); self.calls.push(call.clone()); - let call = self.unifier.add_ty(TypeEnum::TCall { calls: vec![call] }); + let call = self.unifier.add_ty(TypeEnum::TCall(vec![call].into())); let fields = once((method, call)).collect(); - let record = self.unifier.add_ty(TypeEnum::TRecord { fields }); + let record = self.unifier.add_record(fields); self.constrain(obj, record)?; Ok(ret) } @@ -248,11 +216,7 @@ impl<'a> Inferencer<'a> { let fun = FunSignature { args: fn_args .iter() - .map(|(k, ty)| FuncArg { - name: k.clone(), - ty: *ty, - is_optional: false, - }) + .map(|(k, ty)| FuncArg { name: k.clone(), ty: *ty, is_optional: false }) .collect(), ret, vars: Default::default(), @@ -266,10 +230,7 @@ impl<'a> Inferencer<'a> { } Ok(Located { location, - node: ExprKind::Lambda { - args: args.into(), - body: body.into(), - }, + node: ExprKind::Lambda { args: args.into(), body: body.into() }, custom: Some(self.unifier.add_ty(TypeEnum::TFunc(fun))), }) } @@ -282,7 +243,7 @@ impl<'a> Inferencer<'a> { ) -> Result>, String> { if generators.len() != 1 { return Err( - "Only 1 generator statement for list comprehension is supported.".to_string(), + "Only 1 generator statement for list comprehension is supported.".to_string() ); } let variable_mapping = self.variable_mapping.clone(); @@ -309,22 +270,16 @@ impl<'a> Inferencer<'a> { // iter should be a list of targets... // actually it should be an iterator of targets, but we don't have iter type for now - let list = new_context.unifier.add_ty(TypeEnum::TList { - ty: target.custom.unwrap(), - }); + let list = new_context.unifier.add_ty(TypeEnum::TList { ty: target.custom.unwrap() }); new_context.unifier.unify(iter.custom.unwrap(), list)?; // if conditions should be bool for v in ifs.iter() { - new_context - .unifier - .unify(v.custom.unwrap(), new_context.primitives.bool)?; + new_context.unifier.unify(v.custom.unwrap(), new_context.primitives.bool)?; } Ok(Located { location, - custom: Some(new_context.unifier.add_ty(TypeEnum::TList { - ty: elt.custom.unwrap(), - })), + custom: Some(new_context.unifier.add_ty(TypeEnum::TList { ty: elt.custom.unwrap() })), node: ExprKind::ListComp { elt: Box::new(elt), generators: vec![ast::Comprehension { @@ -344,77 +299,68 @@ impl<'a> Inferencer<'a> { mut args: Vec>, keywords: Vec>, ) -> Result>, String> { - let func = if let Located { - location: func_location, - custom, - node: ExprKind::Name { id, ctx }, - } = func - { - // handle special functions that cannot be typed in the usual way... - if id == "virtual" { - if args.is_empty() || args.len() > 2 || !keywords.is_empty() { - return Err("`virtual` can only accept 1/2 positional arguments.".to_string()); - } - let arg0 = self.fold_expr(args.remove(0))?; - let ty = if let Some(arg) = args.pop() { - self.resolver - .parse_type_name(&arg) - .ok_or_else(|| "error parsing type".to_string())? - } else { - self.unifier.get_fresh_var().0 - }; - let custom = Some(self.unifier.add_ty(TypeEnum::TVirtual { ty })); - return Ok(Located { - location, - custom, - node: ExprKind::Call { - func: Box::new(Located { - custom: None, - location: func.location, - node: ExprKind::Name { id, ctx }, - }), - args: vec![arg0], - keywords: vec![], - }, - }); - } - // int64 is special because its argument can be a constant larger than int32 - if id == "int64" && args.len() == 1 { - if let ExprKind::Constant { - value: ast::Constant::Int(val), - kind, - } = &args[0].node - { - let int64: Result = val.try_into(); - let custom; - if int64.is_ok() { - custom = Some(self.primitives.int64); - } else { - return Err("Integer out of bound".into()); + let func = + if let Located { location: func_location, custom, node: ExprKind::Name { id, ctx } } = + func + { + // handle special functions that cannot be typed in the usual way... + if id == "virtual" { + if args.is_empty() || args.len() > 2 || !keywords.is_empty() { + return Err( + "`virtual` can only accept 1/2 positional arguments.".to_string() + ); } + let arg0 = self.fold_expr(args.remove(0))?; + let ty = if let Some(arg) = args.pop() { + self.resolver + .parse_type_name(&arg) + .ok_or_else(|| "error parsing type".to_string())? + } else { + self.unifier.get_fresh_var().0 + }; + let custom = Some(self.unifier.add_ty(TypeEnum::TVirtual { ty })); return Ok(Located { - location: args[0].location, + location, custom, - node: ExprKind::Constant { - value: ast::Constant::Int(val.clone()), - kind: kind.clone(), + node: ExprKind::Call { + func: Box::new(Located { + custom: None, + location: func.location, + node: ExprKind::Name { id, ctx }, + }), + args: vec![arg0], + keywords: vec![], }, }); } - } - Located { - location: func_location, - custom, - node: ExprKind::Name { id, ctx }, - } - } else { - func - }; + // int64 is special because its argument can be a constant larger than int32 + if id == "int64" && args.len() == 1 { + if let ExprKind::Constant { value: ast::Constant::Int(val), kind } = + &args[0].node + { + let int64: Result = val.try_into(); + let custom; + if int64.is_ok() { + custom = Some(self.primitives.int64); + } else { + return Err("Integer out of bound".into()); + } + return Ok(Located { + location: args[0].location, + custom, + node: ExprKind::Constant { + value: ast::Constant::Int(val.clone()), + kind: kind.clone(), + }, + }); + } + } + Located { location: func_location, custom, node: ExprKind::Name { id, ctx } } + } else { + func + }; let func = Box::new(self.fold_expr(func)?); - let args = args - .into_iter() - .map(|v| self.fold_expr(v)) - .collect::, _>>()?; + let args = args.into_iter().map(|v| self.fold_expr(v)).collect::, _>>()?; let keywords = keywords .into_iter() .map(|v| fold::fold_keyword(self, v)) @@ -430,18 +376,10 @@ impl<'a> Inferencer<'a> { ret, }); self.calls.push(call.clone()); - let call = self.unifier.add_ty(TypeEnum::TCall { calls: vec![call] }); + let call = self.unifier.add_ty(TypeEnum::TCall(vec![call].into())); self.unifier.unify(func.custom.unwrap(), call)?; - Ok(Located { - location, - custom: Some(ret), - node: ExprKind::Call { - func, - args, - keywords, - }, - }) + Ok(Located { location, custom: Some(ret), node: ExprKind::Call { func, args, keywords } }) } fn infer_identifier(&mut self, id: &str) -> InferenceResult { @@ -493,7 +431,7 @@ impl<'a> Inferencer<'a> { fn infer_attribute(&mut self, value: &ast::Expr>, attr: &str) -> InferenceResult { let (attr_ty, _) = self.unifier.get_fresh_var(); let fields = once((attr.to_string(), attr_ty)).collect(); - let record = self.unifier.add_ty(TypeEnum::TRecord { fields }); + let record = self.unifier.add_record(fields); self.constrain(value.custom.unwrap(), record)?; Ok(attr_ty) } @@ -540,9 +478,8 @@ impl<'a> Inferencer<'a> { ) -> InferenceResult { let boolean = self.primitives.bool; for (a, b, c) in izip!(once(left).chain(comparators), comparators, ops) { - let method = comparison_name(c) - .ok_or_else(|| "unsupported comparator".to_string())? - .to_string(); + let method = + comparison_name(c).ok_or_else(|| "unsupported comparator".to_string())?.to_string(); self.build_method_call(method, a.custom.unwrap(), vec![b.custom.unwrap()], boolean)?; } Ok(boolean) @@ -556,26 +493,18 @@ impl<'a> Inferencer<'a> { let ty = self.unifier.get_fresh_var().0; match &slice.node { ast::ExprKind::Slice { lower, upper, step } => { - for v in [lower.as_ref(), upper.as_ref(), step.as_ref()] - .iter() - .flatten() - { + for v in [lower.as_ref(), upper.as_ref(), step.as_ref()].iter().flatten() { self.constrain(v.custom.unwrap(), self.primitives.int32)?; } let list = self.unifier.add_ty(TypeEnum::TList { ty }); self.constrain(value.custom.unwrap(), list)?; Ok(list) } - ast::ExprKind::Constant { - value: ast::Constant::Int(val), - .. - } => { + ast::ExprKind::Constant { value: ast::Constant::Int(val), .. } => { // the index is a constant, so value can be a sequence. - let ind: i32 = val - .try_into() - .map_err(|_| "Index must be int32".to_string())?; + let ind: i32 = val.try_into().map_err(|_| "Index must be int32".to_string())?; let map = once((ind, ty)).collect(); - let seq = self.unifier.add_ty(TypeEnum::TSeq { map }); + let seq = self.unifier.add_sequence(map); self.constrain(value.custom.unwrap(), seq)?; Ok(ty) } diff --git a/nac3core/src/typecheck/typedef/mod.rs b/nac3core/src/typecheck/typedef/mod.rs index 6274c7c2..52b26df2 100644 --- a/nac3core/src/typecheck/typedef/mod.rs +++ b/nac3core/src/typecheck/typedef/mod.rs @@ -1,4 +1,5 @@ -use itertools::Itertools; +use itertools::{chain, zip, Itertools}; +use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashMap; use std::iter::once; @@ -12,9 +13,6 @@ mod test; /// Handle for a type, implementated as a key in the unification table. pub type Type = UnificationKey; -#[derive(Clone)] -pub struct TypeCell(Rc>); - pub type Mapping = HashMap; type VarMap = Mapping; @@ -40,16 +38,20 @@ pub struct FunSignature { pub vars: VarMap, } -// We use a lot of `Rc`/`RefCell`s here as we want to simplify our code. -// We may not really need so much `Rc`s, but we would have to do complicated -// stuffs otherwise. +#[derive(Clone)] +pub enum TypeVarMeta { + Generic, + Sequence(RefCell>), + Record(RefCell>), +} + #[derive(Clone)] pub enum TypeEnum { TVar { id: u32, - }, - TSeq { - map: Mapping, + meta: TypeVarMeta, + // empty indicates no restriction + range: RefCell>, }, TTuple { ty: Vec, @@ -57,9 +59,6 @@ pub enum TypeEnum { TList { ty: Type, }, - TRecord { - fields: Mapping, - }, TObj { obj_id: usize, fields: Mapping, @@ -68,33 +67,16 @@ pub enum TypeEnum { TVirtual { ty: Type, }, - TCall { - calls: Vec>, - }, + TCall(RefCell>>), TFunc(FunSignature), } -// Order: -// TVar -// |--> TSeq -// | |--> TTuple -// | `--> TList -// |--> TRecord -// | |--> TObj -// | `--> TVirtual -// `--> TCall -// `--> TFunc - impl TypeEnum { pub fn get_type_name(&self) -> &'static str { - // this function is for debugging only... - // a proper to_str implementation requires the context match self { TypeEnum::TVar { .. } => "TVar", - TypeEnum::TSeq { .. } => "TSeq", TypeEnum::TTuple { .. } => "TTuple", TypeEnum::TList { .. } => "TList", - TypeEnum::TRecord { .. } => "TRecord", TypeEnum::TObj { .. } => "TObj", TypeEnum::TVirtual { .. } => "TVirtual", TypeEnum::TCall { .. } => "TCall", @@ -103,176 +85,168 @@ impl TypeEnum { } pub fn is_concrete(&self) -> bool { - matches!( - self, - TypeEnum::TTuple { .. } - | TypeEnum::TList { .. } - | TypeEnum::TObj { .. } - | TypeEnum::TVirtual { .. } - | TypeEnum::TFunc { .. } - ) + !matches!(self, TypeEnum::TVar { .. }) } } pub struct Unifier { - unification_table: UnificationTable>>, + unification_table: UnificationTable>, var_id: u32, } impl Unifier { /// Get an empty unifier pub fn new() -> Unifier { - Unifier { - unification_table: UnificationTable::new(), - var_id: 0, - } + Unifier { unification_table: UnificationTable::new(), var_id: 0 } } /// Register a type to the unifier. /// Returns a key in the unification_table. pub fn add_ty(&mut self, a: TypeEnum) -> Type { - self.unification_table.new_key(Rc::new(a.into())) + self.unification_table.new_key(Rc::new(a)) + } + + pub fn add_record(&mut self, fields: Mapping) -> Type { + let id = self.var_id + 1; + self.var_id += 1; + self.add_ty(TypeEnum::TVar { + id, + range: vec![].into(), + meta: TypeVarMeta::Record(fields.into()), + }) + } + + pub fn add_sequence(&mut self, sequence: Mapping) -> Type { + let id = self.var_id + 1; + self.var_id += 1; + self.add_ty(TypeEnum::TVar { + id, + range: vec![].into(), + meta: TypeVarMeta::Sequence(sequence.into()), + }) } /// Get the TypeEnum of a type. - pub fn get_ty(&mut self, a: Type) -> Rc> { + pub fn get_ty(&mut self, a: Type) -> Rc { self.unification_table.probe_value(a).clone() } - /// Unify two types, i.e. a = b. - pub fn unify(&mut self, a: Type, b: Type) -> Result<(), String> { - self.unify_impl(a, b, false) + pub fn get_fresh_var(&mut self) -> (Type, u32) { + self.get_fresh_var_with_range(&[]) } /// Get a fresh type variable. - pub fn get_fresh_var(&mut self) -> (Type, u32) { + pub fn get_fresh_var_with_range(&mut self, range: &[Type]) -> (Type, u32) { let id = self.var_id + 1; self.var_id += 1; - (self.add_ty(TypeEnum::TVar { id }), id) + let range = range.to_vec().into(); + (self.add_ty(TypeEnum::TVar { id, range, meta: TypeVarMeta::Generic }), id) } - /// Get string representation of the type - pub fn stringify(&mut self, ty: Type, obj_to_name: &mut F, var_to_name: &mut G) -> String - where - F: FnMut(usize) -> String, - G: FnMut(u32) -> String, - { - let ty = self.unification_table.probe_value(ty).clone(); - let ty = ty.as_ref().borrow(); - match &*ty { - TypeEnum::TVar { id } => var_to_name(*id), - TypeEnum::TSeq { map } => { - let mut fields = map.iter().map(|(k, v)| { - format!("{}={}", k, self.stringify(*v, obj_to_name, var_to_name)) - }); - format!("seq[{}]", fields.join(", ")) - } - TypeEnum::TTuple { ty } => { - let mut fields = ty - .iter() - .map(|v| self.stringify(*v, obj_to_name, var_to_name)); - format!("tuple[{}]", fields.join(", ")) - } - TypeEnum::TList { ty } => { - format!("list[{}]", self.stringify(*ty, obj_to_name, var_to_name)) - } - TypeEnum::TVirtual { ty } => { - format!("virtual[{}]", self.stringify(*ty, obj_to_name, var_to_name)) - } - TypeEnum::TRecord { fields } => { - let mut fields = fields.iter().map(|(k, v)| { - format!("{}={}", k, self.stringify(*v, obj_to_name, var_to_name)) - }); - format!("record[{}]", fields.join(", ")) - } - TypeEnum::TObj { obj_id, params, .. } => { - let name = obj_to_name(*obj_id); - if !params.is_empty() { - let mut params = params - .values() - .map(|v| self.stringify(*v, obj_to_name, var_to_name)); - format!("{}[{}]", name, params.join(", ")) - } else { - name - } - } - TypeEnum::TCall { .. } => "call".to_owned(), - TypeEnum::TFunc(signature) => { - let params = signature - .args - .iter() - .map(|arg| { - format!( - "{}={}", - arg.name, - self.stringify(arg.ty, obj_to_name, var_to_name) - ) - }) - .join(", "); - let ret = self.stringify(signature.ret, obj_to_name, var_to_name); - format!("fn[[{}], {}]", params, ret) - } + pub fn unify(&mut self, a: Type, b: Type) -> Result<(), String> { + if self.unification_table.unioned(a, b) { + Ok(()) + } else { + self.unify_impl(a, b, false) } } fn unify_impl(&mut self, a: Type, b: Type, swapped: bool) -> Result<(), String> { use TypeEnum::*; - let (ty_a_cell, ty_b_cell) = { - if self.unification_table.unioned(a, b) { - return Ok(()); - } + use TypeVarMeta::*; + let (ty_a, ty_b) = { ( self.unification_table.probe_value(a).clone(), self.unification_table.probe_value(b).clone(), ) }; - - let (ty_a, ty_b) = { (ty_a_cell.borrow(), ty_b_cell.borrow()) }; - match (&*ty_a, &*ty_b) { - (TypeEnum::TVar { .. }, _) => { + (TVar { meta: meta1, range: range1, .. }, TVar { meta: meta2, range: range2, .. }) => { self.occur_check(a, b)?; - self.set_a_to_b(a, b); - } - (TSeq { map: map1 }, TSeq { .. }) => { - self.occur_check(a, b)?; - drop(ty_b); - if let TypeEnum::TSeq { map: map2 } = &mut *ty_b_cell.as_ref().borrow_mut() { - // unify them to map2 - for (key, value) in map1.iter() { - if let Some(ty) = map2.get(key) { - self.unify(*ty, *value)?; - } else { - map2.insert(*key, *value); + self.occur_check(b, a)?; + match (meta1, meta2) { + (Generic, _) => {} + (_, Generic) => { + return self.unify_impl(b, a, true); + } + (Record(fields1), Record(fields2)) => { + let mut fields2 = fields2.borrow_mut(); + for (key, value) in fields1.borrow().iter() { + if let Some(ty) = fields2.get(key) { + self.unify(*ty, *value)?; + } else { + fields2.insert(key.clone(), *value); + } } } - } else { - unreachable!() + (Sequence(map1), Sequence(map2)) => { + let mut map2 = map2.borrow_mut(); + for (key, value) in map1.borrow().iter() { + if let Some(ty) = map2.get(key) { + self.unify(*ty, *value)?; + } else { + map2.insert(*key, *value); + } + } + } + _ => { + return Err("Incompatible".to_string()); + } + } + let range1 = range1.borrow(); + // new range is the intersection of them + // empty range indicates no constraint + if !range1.is_empty() { + let old_range2 = range2.take(); + let mut range2 = range2.borrow_mut(); + if old_range2.is_empty() { + range2.extend_from_slice(&range1); + } + for v1 in old_range2.iter() { + for v2 in range1.iter() { + if !self.shape_match(*v1, *v2) { + continue; + } + self.unify(*v1, *v2)?; + range2.push(*v2); + } + } + if range2.is_empty() { + return Err( + "cannot unify type variables with incompatible value range".to_string() + ); + } } self.set_a_to_b(a, b); } - (TSeq { map: map1 }, TTuple { ty: types }) => { + (TVar { meta: Generic, id, range, .. }, _) => { self.occur_check(a, b)?; - let len = types.len() as i32; - for (k, v) in map1.iter() { + self.check_var_range(*id, b, &range.borrow())?; + self.set_a_to_b(a, b); + } + (TVar { meta: Sequence(map), id, range, .. }, TTuple { ty }) => { + self.occur_check(a, b)?; + let len = ty.len() as i32; + for (k, v) in map.borrow().iter() { // handle negative index let ind = if *k < 0 { len + *k } else { *k }; if ind >= len || ind < 0 { return Err(format!( "Tuple index out of range. (Length: {}, Index: {})", - types.len(), - k + len, k )); } - self.unify(*v, types[ind as usize])?; + self.unify(*v, ty[ind as usize])?; } + self.check_var_range(*id, b, &range.borrow())?; self.set_a_to_b(a, b); } - (TSeq { map: map1 }, TList { ty }) => { + (TVar { meta: Sequence(map), id, range, .. }, TList { ty }) => { self.occur_check(a, b)?; - for v in map1.values() { + for v in map.borrow().values() { self.unify(*v, *ty)?; } + self.check_var_range(*id, b, &range.borrow())?; self.set_a_to_b(a, b); } (TTuple { ty: ty1 }, TTuple { ty: ty2 }) => { @@ -292,59 +266,32 @@ impl Unifier { self.unify(*ty1, *ty2)?; self.set_a_to_b(a, b); } - (TRecord { fields: fields1 }, TRecord { .. }) => { + (TVar { meta: Record(map), id, range, .. }, TObj { fields, .. }) => { self.occur_check(a, b)?; - drop(ty_b); - if let TypeEnum::TRecord { fields: fields2 } = &mut *ty_b_cell.as_ref().borrow_mut() - { - for (key, value) in fields1.iter() { - if let Some(ty) = fields2.get(key) { - self.unify(*ty, *value)?; - } else { - fields2.insert(key.clone(), *value); - } - } - } else { - unreachable!() - } - self.set_a_to_b(a, b); - } - ( - TRecord { fields: fields1 }, - TObj { - fields: fields2, .. - }, - ) => { - self.occur_check(a, b)?; - for (key, value) in fields1.iter() { - if let Some(ty) = fields2.get(key) { - self.unify(*ty, *value)?; + for (k, v) in map.borrow().iter() { + if let Some(ty) = fields.get(k) { + self.unify(*ty, *v)?; } else { - return Err(format!("No such attribute {}", key)); + return Err(format!("No such attribute {}", k)); } } + self.check_var_range(*id, b, &range.borrow())?; self.set_a_to_b(a, b); } - (TRecord { .. }, TVirtual { ty }) => { + (TVar { meta: Record(_), id, range, .. }, TVirtual { ty }) => { + // TODO: look at this rule self.occur_check(a, b)?; + self.check_var_range(*id, b, &range.borrow())?; self.unify(a, *ty)?; } ( - TObj { - obj_id: id1, - params: params1, - .. - }, - TObj { - obj_id: id2, - params: params2, - .. - }, + TObj { obj_id: id1, params: params1, .. }, + TObj { obj_id: id2, params: params2, .. }, ) => { if id1 != id2 { return Err(format!("Cannot unify objects with ID {} and {}", id1, id2)); } - for (x, y) in params1.values().zip(params2.values()) { + for (x, y) in zip(params1.values(), params2.values()) { self.unify(*x, *y)?; } self.set_a_to_b(a, b); @@ -353,16 +300,10 @@ impl Unifier { self.unify(*ty1, *ty2)?; self.set_a_to_b(a, b); } - (TCall { calls: c1 }, TCall { .. }) => { - drop(ty_b); - if let TypeEnum::TCall { calls: c2 } = &mut *ty_b_cell.as_ref().borrow_mut() { - c2.extend(c1.iter().cloned()); - } else { - unreachable!() - } - self.set_a_to_b(a, b); + (TCall(calls1), TCall(calls2)) => { + calls2.borrow_mut().extend_from_slice(&calls1.borrow()); } - (TCall { calls }, TFunc(signature)) => { + (TCall(calls), TFunc(signature)) => { self.occur_check(a, b)?; let required: Vec = signature .args @@ -371,29 +312,20 @@ impl Unifier { .map(|v| v.name.clone()) .rev() .collect(); - for c in calls { - let Call { - posargs, - kwargs, - ret, - fun, - } = c.as_ref(); + for c in calls.borrow().iter() { + let Call { posargs, kwargs, ret, fun } = c.as_ref(); let instantiated = self.instantiate_fun(b, signature); let signature; let r = self.get_ty(instantiated); - let r = r.as_ref().borrow(); + let r = r.as_ref(); if let TypeEnum::TFunc(s) = &*r { signature = s; } else { unreachable!(); } let mut required = required.clone(); - let mut all_names: Vec<_> = signature - .args - .iter() - .map(|v| (v.name.clone(), v.ty)) - .rev() - .collect(); + let mut all_names: Vec<_> = + signature.args.iter().map(|v| (v.name.clone(), v.ty)).rev().collect(); for (i, t) in posargs.iter().enumerate() { if signature.args.len() <= i { return Err("Too many arguments.".to_string()); @@ -451,6 +383,88 @@ impl Unifier { Ok(()) } + /// Get string representation of the type + pub fn stringify(&mut self, ty: Type, obj_to_name: &mut F, var_to_name: &mut G) -> String + where + F: FnMut(usize) -> String, + G: FnMut(u32) -> String, + { + use TypeVarMeta::*; + let ty = self.unification_table.probe_value(ty).clone(); + match ty.as_ref() { + TypeEnum::TVar { id, meta: Generic, .. } => var_to_name(*id), + TypeEnum::TVar { meta: Sequence(map), .. } => { + let fields = map.borrow().iter().map(|(k, v)| { + format!("{}={}", k, self.stringify(*v, obj_to_name, var_to_name)) + }).join(", "); + format!("seq[{}]", fields) + } + TypeEnum::TVar { meta: Record(fields), .. } => { + let fields = fields.borrow().iter().map(|(k, v)| { + format!("{}={}", k, self.stringify(*v, obj_to_name, var_to_name)) + }).join(", "); + format!("record[{}]", fields) + } + TypeEnum::TTuple { ty } => { + let mut fields = ty + .iter() + .map(|v| self.stringify(*v, obj_to_name, var_to_name)); + format!("tuple[{}]", fields.join(", ")) + } + TypeEnum::TList { ty } => { + format!("list[{}]", self.stringify(*ty, obj_to_name, var_to_name)) + } + TypeEnum::TVirtual { ty } => { + format!("virtual[{}]", self.stringify(*ty, obj_to_name, var_to_name)) + } + TypeEnum::TObj { obj_id, params, .. } => { + let name = obj_to_name(*obj_id); + if !params.is_empty() { + let mut params = params + .values() + .map(|v| self.stringify(*v, obj_to_name, var_to_name)); + format!("{}[{}]", name, params.join(", ")) + } else { + name + } + } + TypeEnum::TCall { .. } => "call".to_owned(), + TypeEnum::TFunc(signature) => { + let params = signature + .args + .iter() + .map(|arg| { + format!( + "{}={}", + arg.name, + self.stringify(arg.ty, obj_to_name, var_to_name) + ) + }) + .join(", "); + let ret = self.stringify(signature.ret, obj_to_name, var_to_name); + format!("fn[[{}], {}]", params, ret) + } + } + } + + fn check_var_range(&mut self, id: u32, b: Type, range: &[Type]) -> Result<(), String> { + let mut in_range = range.is_empty(); + for t in range.iter() { + if self.shape_match(*t, b) { + self.unify(*t, b)?; + in_range = true; + } + } + if !in_range { + return Err(format!( + "Cannot unify {} with {} due to incompatible value range", + id, + self.get_ty(b).get_type_name() + )); + } + Ok(()) + } + fn set_a_to_b(&mut self, a: Type, b: Type) { // unify a and b together, and set the value to b's value. let table = &mut self.unification_table; @@ -460,77 +474,40 @@ impl Unifier { } fn incompatible_types(&self, a: &TypeEnum, b: &TypeEnum) -> Result<(), String> { - Err(format!( - "Cannot unify {} with {}", - a.get_type_name(), - b.get_type_name() - )) + Err(format!("Cannot unify {} with {}", a.get_type_name(), b.get_type_name())) } - fn occur_check(&mut self, a: Type, b: Type) -> Result<(), String> { - if self.unification_table.unioned(a, b) { - return Err("Recursive type is prohibited.".to_owned()); + /// Instantiate a function if it hasn't been instntiated. + /// Returns Some(T) where T is the instantiated type. + /// Returns None if the function is already instantiated. + fn instantiate_fun(&mut self, ty: Type, fun: &FunSignature) -> Type { + let mut instantiated = false; + let mut vars = Vec::new(); + for (k, v) in fun.vars.iter() { + if let TypeEnum::TVar { id, range, .. } = + self.unification_table.probe_value(*v).as_ref() + { + if k != id { + instantiated = true; + break; + } + // actually, if the first check succeeded, the function should be uninstatiated. + // The cloned values must be used and would not be wasted. + vars.push((*k, range.clone())); + } else { + instantiated = true; + break; + } + } + if instantiated { + ty + } else { + let mapping = vars + .into_iter() + .map(|(k, range)| (k, self.get_fresh_var_with_range(range.borrow().as_ref()).0)) + .collect(); + self.subst(ty, &mapping).unwrap_or(ty) } - let ty = self.unification_table.probe_value(b).clone(); - let ty = ty.borrow(); - - match &*ty { - TypeEnum::TVar { .. } => { - // TODO: occur check for bounds... - } - TypeEnum::TSeq { map } => { - for t in map.values() { - self.occur_check(a, *t)?; - } - } - TypeEnum::TTuple { ty } => { - for t in ty.iter() { - self.occur_check(a, *t)?; - } - } - TypeEnum::TList { ty } | TypeEnum::TVirtual { ty } => { - self.occur_check(a, *ty)?; - } - TypeEnum::TRecord { fields } => { - for t in fields.values() { - self.occur_check(a, *t)?; - } - } - TypeEnum::TObj { params: map, .. } => { - for t in map.values() { - self.occur_check(a, *t)?; - } - } - TypeEnum::TCall { calls } => { - for t in calls - .iter() - .map(|call| { - call.posargs - .iter() - .chain(call.kwargs.values()) - .chain(once(&call.ret)) - }) - .flatten() - { - self.occur_check(a, *t)?; - } - } - TypeEnum::TFunc(FunSignature { - args, - ret, - vars: params, - }) => { - for t in args - .iter() - .map(|v| &v.ty) - .chain(params.values()) - .chain(once(ret)) - { - self.occur_check(a, *t)?; - } - } - }; - Ok(()) } /// Substitute type variables within a type into other types. @@ -538,48 +515,41 @@ impl Unifier { /// If this returns None, the result type would be the original type /// (no substitution has to be done). fn subst(&mut self, a: Type, mapping: &VarMap) -> Option { - let ty_cell = self.unification_table.probe_value(a).clone(); - let ty = ty_cell.borrow(); + use TypeVarMeta::*; + let ty = self.unification_table.probe_value(a).clone(); // this function would only be called when we instantiate functions. // function type signature should ONLY contain concrete types and type // variables, i.e. things like TRecord, TCall should not occur, and we // should be safe to not implement the substitution for those variants. match &*ty { - TypeEnum::TVar { id } => mapping.get(&id).cloned(), - TypeEnum::TSeq { map } => self - .subst_map(map, mapping) - .map(|m| self.add_ty(TypeEnum::TSeq { map: m })), + TypeEnum::TVar { id, meta: Generic, .. } => mapping.get(&id).cloned(), TypeEnum::TTuple { ty } => { - let mut new_ty = None; + let mut new_ty = Cow::from(ty); for (i, t) in ty.iter().enumerate() { if let Some(t1) = self.subst(*t, mapping) { - if new_ty.is_none() { - new_ty = Some(ty.clone()); - } - new_ty.as_mut().unwrap()[i] = t1; + new_ty.to_mut()[i] = t1; } } - new_ty.map(|t| self.add_ty(TypeEnum::TTuple { ty: t })) + if matches!(new_ty, Cow::Owned(_)) { + Some(self.add_ty(TypeEnum::TTuple { ty: new_ty.into_owned() })) + } else { + None + } } - TypeEnum::TList { ty } => self - .subst(*ty, mapping) - .map(|t| self.add_ty(TypeEnum::TList { ty: t })), - TypeEnum::TVirtual { ty } => self - .subst(*ty, mapping) - .map(|t| self.add_ty(TypeEnum::TVirtual { ty: t })), - TypeEnum::TObj { - obj_id, - fields, - params, - } => { + TypeEnum::TList { ty } => { + self.subst(*ty, mapping).map(|t| self.add_ty(TypeEnum::TList { ty: t })) + } + TypeEnum::TVirtual { ty } => { + self.subst(*ty, mapping).map(|t| self.add_ty(TypeEnum::TVirtual { ty: t })) + } + TypeEnum::TObj { obj_id, fields, params } => { // Type variables in field types must be present in the type parameter. // If the mapping does not contain any type variables in the // parameter list, we don't need to substitute the fields. // This is also used to prevent infinite substitution... let need_subst = params.values().any(|v| { - let ty_cell = self.unification_table.probe_value(*v); - let ty = ty_cell.borrow(); - if let TypeEnum::TVar { id } = &*ty { + let ty = self.unification_table.probe_value(*v); + if let TypeEnum::TVar { id, .. } = ty.as_ref() { mapping.contains_key(&id) } else { false @@ -587,50 +557,29 @@ impl Unifier { }); if need_subst { let obj_id = *obj_id; - let params = self - .subst_map(¶ms, mapping) - .unwrap_or_else(|| params.clone()); - let fields = self - .subst_map(&fields, mapping) - .unwrap_or_else(|| fields.clone()); - Some(self.add_ty(TypeEnum::TObj { - obj_id, - params, - fields, - })) + let params = self.subst_map(¶ms, mapping).unwrap_or_else(|| params.clone()); + let fields = self.subst_map(&fields, mapping).unwrap_or_else(|| fields.clone()); + Some(self.add_ty(TypeEnum::TObj { obj_id, params, fields })) } else { None } } - TypeEnum::TFunc(FunSignature { - args, - ret, - vars: params, - }) => { + TypeEnum::TFunc(FunSignature { args, ret, vars: params }) => { let new_params = self.subst_map(params, mapping); let new_ret = self.subst(*ret, mapping); - let mut new_args = None; + let mut new_args = Cow::from(args); for (i, t) in args.iter().enumerate() { if let Some(t1) = self.subst(t.ty, mapping) { - if new_args.is_none() { - new_args = Some(args.clone()); - } - new_args.as_mut().unwrap()[i] = FuncArg { - name: t.name.clone(), - ty: t1, - is_optional: t.is_optional, - }; + let mut t = t.clone(); + t.ty = t1; + new_args.to_mut()[i] = t; } } - if new_params.is_some() || new_ret.is_some() || new_args.is_some() { + if new_params.is_some() || new_ret.is_some() || matches!(new_args, Cow::Owned(..)) { let params = new_params.unwrap_or_else(|| params.clone()); let ret = new_ret.unwrap_or_else(|| *ret); - let args = new_args.unwrap_or_else(|| args.clone()); - Some(self.add_ty(TypeEnum::TFunc(FunSignature { - args, - ret, - vars: params, - }))) + let args = new_args.into_owned(); + Some(self.add_ty(TypeEnum::TFunc(FunSignature { args, ret, vars: params }))) } else { None } @@ -655,95 +604,73 @@ impl Unifier { map2 } - /// Instantiate a function if it hasn't been instntiated. - /// Returns Some(T) where T is the instantiated type. - /// Returns None if the function is already instantiated. - fn instantiate_fun(&mut self, ty: Type, fun: &FunSignature) -> Type { - let mut instantiated = false; - for (k, v) in fun.vars.iter() { - if let TypeEnum::TVar { id } = - &*self.unification_table.probe_value(*v).as_ref().borrow() - { - if k != id { - instantiated = true; - break; + fn occur_check(&mut self, a: Type, b: Type) -> Result<(), String> { + use TypeVarMeta::*; + if self.unification_table.unioned(a, b) { + return Err("Recursive type is prohibited.".to_owned()); + } + let ty = self.unification_table.probe_value(b).clone(); + + match ty.as_ref() { + TypeEnum::TVar { meta: Generic, .. } => {} + TypeEnum::TVar { meta: Sequence(map), .. } => { + for t in map.borrow().values() { + self.occur_check(a, *t)?; + } + } + TypeEnum::TVar { meta: Record(map), .. } => { + for t in map.borrow().values() { + self.occur_check(a, *t)?; + } + } + TypeEnum::TCall(calls) => { + for t in calls + .borrow() + .iter() + .map(|call| chain!(call.posargs.iter(), call.kwargs.values(), once(&call.ret))) + .flatten() + { + self.occur_check(a, *t)?; + } + } + TypeEnum::TTuple { ty } => { + for t in ty.iter() { + self.occur_check(a, *t)?; + } + } + TypeEnum::TList { ty } | TypeEnum::TVirtual { ty } => { + self.occur_check(a, *ty)?; + } + TypeEnum::TObj { params: map, .. } => { + for t in map.values() { + self.occur_check(a, *t)?; + } + } + TypeEnum::TFunc(FunSignature { args, ret, vars: params }) => { + for t in chain!(args.iter().map(|v| &v.ty), params.values(), once(ret)) { + self.occur_check(a, *t)?; } - } else { - instantiated = true; - break; } } - if instantiated { - ty - } else { - let mapping = fun - .vars - .iter() - .map(|(k, _)| (*k, self.get_fresh_var().0)) - .collect(); - self.subst(ty, &mapping).unwrap_or(ty) - } + Ok(()) } - /// Check whether two types are equal. - fn eq(&mut self, a: Type, b: Type) -> bool { - if a == b { - return true; - } - let (ty_a, ty_b) = { - let table = &mut self.unification_table; - if table.unioned(a, b) { - return true; - } - (table.probe_value(a).clone(), table.probe_value(b).clone()) - }; - - let ty_a = ty_a.borrow(); - let ty_b = ty_b.borrow(); - - match (&*ty_a, &*ty_b) { - (TypeEnum::TVar { id: id1 }, TypeEnum::TVar { id: id2 }) => id1 == id2, - (TypeEnum::TSeq { map: map1 }, TypeEnum::TSeq { map: map2 }) => self.map_eq(map1, map2), - (TypeEnum::TTuple { ty: ty1 }, TypeEnum::TTuple { ty: ty2 }) => { + pub fn shape_match(&mut self, a: Type, b: Type) -> bool { + use TypeEnum::*; + let a = self.get_ty(a); + let b = self.get_ty(b); + match (a.as_ref(), b.as_ref()) { + (TVar { .. }, _) => true, + (_, TVar { .. }) => true, + (TTuple { ty: ty1 }, TTuple { ty: ty2 }) => { ty1.len() == ty2.len() - && ty1.iter().zip(ty2.iter()).all(|(t1, t2)| self.eq(*t1, *t2)) + && zip(ty1.iter(), ty2.iter()).all(|(a, b)| self.shape_match(*a, *b)) } - (TypeEnum::TList { ty: ty1 }, TypeEnum::TList { ty: ty2 }) - | (TypeEnum::TVirtual { ty: ty1 }, TypeEnum::TVirtual { ty: ty2 }) => { - self.eq(*ty1, *ty2) - } - (TypeEnum::TRecord { fields: fields1 }, TypeEnum::TRecord { fields: fields2 }) => { - self.map_eq(fields1, fields2) - } - ( - TypeEnum::TObj { - obj_id: id1, - params: params1, - .. - }, - TypeEnum::TObj { - obj_id: id2, - params: params2, - .. - }, - ) => id1 == id2 && self.map_eq(params1, params2), - // TCall and TFunc are not yet implemented + (TList { ty: ty1 }, TList { ty: ty2 }) + | (TVirtual { ty: ty1 }, TVirtual { ty: ty2 }) => self.shape_match(*ty1, *ty2), + (TObj { obj_id: id1, .. }, TObj { obj_id: id2, .. }) => id1 == id2, + // don't deal with function shape for now _ => false, } } - - fn map_eq(&mut self, map1: &Mapping, map2: &Mapping) -> bool - where - K: std::hash::Hash + std::cmp::Eq + std::clone::Clone, - { - if map1.len() != map2.len() { - return false; - } - for (k, v) in map1.iter() { - if !map2.get(k).map(|v1| self.eq(*v, *v1)).unwrap_or(false) { - return false; - } - } - true - } } diff --git a/nac3core/src/typecheck/typedef/test.rs b/nac3core/src/typecheck/typedef/test.rs index 0855b164..8732b1d4 100644 --- a/nac3core/src/typecheck/typedef/test.rs +++ b/nac3core/src/typecheck/typedef/test.rs @@ -1,8 +1,69 @@ -use super::super::typedef::*; +use super::*; use itertools::Itertools; use std::collections::HashMap; use test_case::test_case; +impl Unifier { + /// Check whether two types are equal. + fn eq(&mut self, a: Type, b: Type) -> bool { + use TypeVarMeta::*; + if a == b { + return true; + } + let (ty_a, ty_b) = { + let table = &mut self.unification_table; + if table.unioned(a, b) { + return true; + } + (table.probe_value(a).clone(), table.probe_value(b).clone()) + }; + + match (&*ty_a, &*ty_b) { + ( + TypeEnum::TVar { meta: Generic, id: id1, .. }, + TypeEnum::TVar { meta: Generic, id: id2, .. }, + ) => id1 == id2, + ( + TypeEnum::TVar { meta: Sequence(map1), .. }, + TypeEnum::TVar { meta: Sequence(map2), .. }, + ) => self.map_eq(&map1.borrow(), &map2.borrow()), + (TypeEnum::TTuple { ty: ty1 }, TypeEnum::TTuple { ty: ty2 }) => { + ty1.len() == ty2.len() + && ty1.iter().zip(ty2.iter()).all(|(t1, t2)| self.eq(*t1, *t2)) + } + (TypeEnum::TList { ty: ty1 }, TypeEnum::TList { ty: ty2 }) + | (TypeEnum::TVirtual { ty: ty1 }, TypeEnum::TVirtual { ty: ty2 }) => { + self.eq(*ty1, *ty2) + } + ( + TypeEnum::TVar { meta: Record(fields1), .. }, + TypeEnum::TVar { meta: Record(fields2), .. }, + ) => self.map_eq(&fields1.borrow(), &fields2.borrow()), + ( + TypeEnum::TObj { obj_id: id1, params: params1, .. }, + TypeEnum::TObj { obj_id: id2, params: params2, .. }, + ) => id1 == id2 && self.map_eq(params1, params2), + // TCall and TFunc are not yet implemented + _ => false, + } + } + + fn map_eq(&mut self, map1: &Mapping, map2: &Mapping) -> bool + where + K: std::hash::Hash + std::cmp::Eq + std::clone::Clone, + { + if map1.len() != map2.len() { + return false; + } + for (k, v) in map1.iter() { + if !map2.get(k).map(|v1| self.eq(*v, *v1)).unwrap_or(false) { + return false; + } + } + true + } +} + struct TestEnvironment { pub unifier: Unifier, type_mapping: HashMap, @@ -47,10 +108,7 @@ impl TestEnvironment { }), ); - TestEnvironment { - unifier, - type_mapping, - } + TestEnvironment { unifier, type_mapping } } fn parse(&mut self, typ: &str, mapping: &Mapping) -> Type { @@ -65,9 +123,7 @@ impl TestEnvironment { mapping: &Mapping, ) -> (Type, &'b str) { // for testing only, so we can just panic when the input is malformed - let end = typ - .find(|c| ['[', ',', ']', '='].contains(&c)) - .unwrap_or_else(|| typ.len()); + let end = typ.find(|c| ['[', ',', ']', '='].contains(&c)).unwrap_or_else(|| typ.len()); match &typ[..end] { "Tuple" => { let mut s = &typ[end..]; @@ -97,7 +153,7 @@ impl TestEnvironment { fields.insert(key, result.0); s = result.1; } - (self.unifier.add_ty(TypeEnum::TRecord { fields }), &s[1..]) + (self.unifier.add_record(fields), &s[1..]) } x => { let mut s = &typ[end..]; @@ -106,7 +162,7 @@ impl TestEnvironment { // we should not resolve the type of type variables. let mut ty = *self.type_mapping.get(x).unwrap(); let te = self.unifier.get_ty(ty); - if let TypeEnum::TObj { params, .. } = &*te.as_ref().borrow() { + if let TypeEnum::TObj { params, .. } = &*te.as_ref() { if !params.is_empty() { assert!(&s[0..1] == "["); let mut p = Vec::new(); @@ -192,6 +248,7 @@ fn test_unify( env.unifier.unify(t1, t2).unwrap(); } for (a, b) in verify_pairs.iter() { + println!("{} = {}", a, b); let t1 = env.parse(a, &mapping); let t2 = env.parse(b, &mapping); assert!(env.unifier.eq(t1, t2)); @@ -258,10 +315,8 @@ fn test_invalid_unification( let t2 = env.parse(b, &mapping); pairs.push((t1, t2)); } - let (t1, t2) = ( - env.parse(errornous_pair.0 .0, &mapping), - env.parse(errornous_pair.0 .1, &mapping), - ); + let (t1, t2) = + (env.parse(errornous_pair.0 .0, &mapping), env.parse(errornous_pair.0 .1, &mapping)); for (a, b) in pairs { env.unifier.unify(a, b).unwrap(); }