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