forked from M-Labs/nac3
added tests
This commit is contained in:
parent
1df3f4e757
commit
d94f25583b
|
@ -403,9 +403,11 @@ dependencies = [
|
||||||
"generational-arena",
|
"generational-arena",
|
||||||
"indoc 1.0.3",
|
"indoc 1.0.3",
|
||||||
"inkwell",
|
"inkwell",
|
||||||
|
"itertools",
|
||||||
"num-bigint 0.3.2",
|
"num-bigint 0.3.2",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rustpython-parser",
|
"rustpython-parser",
|
||||||
|
"test-case",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -844,6 +846,19 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "test-case"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b114ece25254e97bf48dd4bfc2a12bad0647adacfe4cae1247a9ca6ad302cec"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-keccak"
|
name = "tiny-keccak"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
|
|
|
@ -13,3 +13,7 @@ indoc = "1.0"
|
||||||
generational-arena = "0.2"
|
generational-arena = "0.2"
|
||||||
ena = "0.14"
|
ena = "0.14"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
test-case = "1.2.0"
|
||||||
|
itertools = "0.10.1"
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,5 @@
|
||||||
// mod magic_methods;
|
// mod magic_methods;
|
||||||
// mod primitives;
|
// mod primitives;
|
||||||
// pub mod symbol_resolver;
|
// pub mod symbol_resolver;
|
||||||
|
mod test_typedef;
|
||||||
pub mod typedef;
|
pub mod typedef;
|
||||||
|
|
|
@ -0,0 +1,273 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::super::typedef::*;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use test_case::test_case;
|
||||||
|
|
||||||
|
struct TestEnvironment {
|
||||||
|
pub unifier: Unifier,
|
||||||
|
type_mapping: HashMap<String, Type>,
|
||||||
|
var_max_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestEnvironment {
|
||||||
|
fn new() -> TestEnvironment {
|
||||||
|
let unifier = Unifier::new();
|
||||||
|
let mut type_mapping = HashMap::new();
|
||||||
|
let mut var_max_id = 0;
|
||||||
|
|
||||||
|
type_mapping.insert(
|
||||||
|
"int".into(),
|
||||||
|
unifier.add_ty(TypeEnum::TObj {
|
||||||
|
obj_id: 0,
|
||||||
|
fields: HashMap::new(),
|
||||||
|
params: HashMap::new(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
type_mapping.insert(
|
||||||
|
"float".into(),
|
||||||
|
unifier.add_ty(TypeEnum::TObj {
|
||||||
|
obj_id: 1,
|
||||||
|
fields: HashMap::new(),
|
||||||
|
params: HashMap::new(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
type_mapping.insert(
|
||||||
|
"bool".into(),
|
||||||
|
unifier.add_ty(TypeEnum::TObj {
|
||||||
|
obj_id: 2,
|
||||||
|
fields: HashMap::new(),
|
||||||
|
params: HashMap::new(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let v0 = unifier.add_ty(TypeEnum::TVar { id: 0 });
|
||||||
|
var_max_id += 1;
|
||||||
|
type_mapping.insert(
|
||||||
|
"Foo".into(),
|
||||||
|
unifier.add_ty(TypeEnum::TObj {
|
||||||
|
obj_id: 3,
|
||||||
|
fields: [("a".into(), v0)].iter().cloned().collect(),
|
||||||
|
params: [(0u32, v0)].iter().cloned().collect(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
TestEnvironment {
|
||||||
|
unifier,
|
||||||
|
type_mapping,
|
||||||
|
var_max_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fresh_var(&mut self) -> Type {
|
||||||
|
let id = self.var_max_id + 1;
|
||||||
|
self.var_max_id += 1;
|
||||||
|
self.unifier.add_ty(TypeEnum::TVar { id })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(&self, typ: &str, mapping: &Mapping<String>) -> Type {
|
||||||
|
let result = self.internal_parse(typ, mapping);
|
||||||
|
assert!(result.1.is_empty());
|
||||||
|
result.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn internal_parse<'a, 'b>(
|
||||||
|
&'a self,
|
||||||
|
typ: &'b str,
|
||||||
|
mapping: &Mapping<String>,
|
||||||
|
) -> (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());
|
||||||
|
match &typ[..end] {
|
||||||
|
"Tuple" => {
|
||||||
|
let mut s = &typ[end..];
|
||||||
|
assert!(&s[0..1] == "[");
|
||||||
|
let mut ty = Vec::new();
|
||||||
|
while &s[0..1] != "]" {
|
||||||
|
let result = self.internal_parse(&s[1..], mapping);
|
||||||
|
ty.push(result.0);
|
||||||
|
s = result.1;
|
||||||
|
}
|
||||||
|
(self.unifier.add_ty(TypeEnum::TTuple { ty }), &s[1..])
|
||||||
|
}
|
||||||
|
"List" => {
|
||||||
|
assert!(&typ[end..end + 1] == "[");
|
||||||
|
let (ty, s) = self.internal_parse(&typ[end + 1..], mapping);
|
||||||
|
assert!(&s[0..1] == "]");
|
||||||
|
(self.unifier.add_ty(TypeEnum::TList { ty }), &s[1..])
|
||||||
|
}
|
||||||
|
"Record" => {
|
||||||
|
let mut s = &typ[end..];
|
||||||
|
assert!(&s[0..1] == "[");
|
||||||
|
let mut fields = HashMap::new();
|
||||||
|
while &s[0..1] != "]" {
|
||||||
|
let eq = s.find('=').unwrap();
|
||||||
|
let key = s[1..eq].to_string();
|
||||||
|
let result = self.internal_parse(&s[eq + 1..], mapping);
|
||||||
|
fields.insert(key, result.0);
|
||||||
|
s = result.1;
|
||||||
|
}
|
||||||
|
(self.unifier.add_ty(TypeEnum::TRecord { fields }), &s[1..])
|
||||||
|
}
|
||||||
|
x => {
|
||||||
|
let mut s = &typ[end..];
|
||||||
|
let ty = mapping.get(x).cloned().unwrap_or_else(|| {
|
||||||
|
// mapping should be type variables, type_mapping should be concrete types
|
||||||
|
// 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 !params.is_empty() {
|
||||||
|
assert!(&s[0..1] == "[");
|
||||||
|
let mut p = Vec::new();
|
||||||
|
while &s[0..1] != "]" {
|
||||||
|
let result = self.internal_parse(&s[1..], mapping);
|
||||||
|
p.push(result.0);
|
||||||
|
s = result.1;
|
||||||
|
}
|
||||||
|
s = &s[1..];
|
||||||
|
ty = self
|
||||||
|
.unifier
|
||||||
|
.subst(ty, ¶ms.keys().cloned().zip(p.into_iter()).collect())
|
||||||
|
.unwrap_or(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ty
|
||||||
|
});
|
||||||
|
(ty, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case(2,
|
||||||
|
&[("v1", "v2"), ("v2", "float")],
|
||||||
|
&[("v1", "float"), ("v2", "float")]
|
||||||
|
; "simple variable"
|
||||||
|
)]
|
||||||
|
#[test_case(2,
|
||||||
|
&[("v1", "List[v2]"), ("v1", "List[float]")],
|
||||||
|
&[("v1", "List[float]"), ("v2", "float")]
|
||||||
|
; "list element"
|
||||||
|
)]
|
||||||
|
#[test_case(3,
|
||||||
|
&[
|
||||||
|
("v1", "Record[a=v3,b=v3]"),
|
||||||
|
("v2", "Record[b=float,c=v3]"),
|
||||||
|
("v1", "v2")
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
("v1", "Record[a=float,b=float,c=float]"),
|
||||||
|
("v2", "Record[a=float,b=float,c=float]"),
|
||||||
|
("v3", "float")
|
||||||
|
]
|
||||||
|
; "record merge"
|
||||||
|
)]
|
||||||
|
#[test_case(3,
|
||||||
|
&[
|
||||||
|
("v1", "Record[a=float]"),
|
||||||
|
("v2", "Foo[v3]"),
|
||||||
|
("v1", "v2")
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
("v1", "Foo[float]"),
|
||||||
|
("v3", "float")
|
||||||
|
]
|
||||||
|
; "record obj merge"
|
||||||
|
)]
|
||||||
|
fn test_unify(
|
||||||
|
variable_count: u32,
|
||||||
|
unify_pairs: &[(&'static str, &'static str)],
|
||||||
|
verify_pairs: &[(&'static str, &'static str)],
|
||||||
|
) {
|
||||||
|
let unify_count = unify_pairs.len();
|
||||||
|
// test all permutations...
|
||||||
|
for perm in unify_pairs.iter().permutations(unify_count) {
|
||||||
|
let mut env = TestEnvironment::new();
|
||||||
|
let mut mapping = HashMap::new();
|
||||||
|
for i in 1..=variable_count {
|
||||||
|
let v = env.get_fresh_var();
|
||||||
|
mapping.insert(format!("v{}", i), v);
|
||||||
|
}
|
||||||
|
// unification may have side effect when we do type resolution, so freeze the types
|
||||||
|
// before doing unification.
|
||||||
|
let mut pairs = Vec::new();
|
||||||
|
for (a, b) in perm.iter() {
|
||||||
|
let t1 = env.parse(a, &mapping);
|
||||||
|
let t2 = env.parse(b, &mapping);
|
||||||
|
pairs.push((t1, t2));
|
||||||
|
}
|
||||||
|
for (t1, t2) in pairs {
|
||||||
|
env.unifier.unify(t1, t2).unwrap();
|
||||||
|
}
|
||||||
|
for (a, b) in verify_pairs.iter() {
|
||||||
|
let t1 = env.parse(a, &mapping);
|
||||||
|
let t2 = env.parse(b, &mapping);
|
||||||
|
assert!(env.unifier.eq(t1, t2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case(2,
|
||||||
|
&[
|
||||||
|
("v1", "Tuple[int]"),
|
||||||
|
("v2", "List[int]"),
|
||||||
|
],
|
||||||
|
(("v1", "v2"), "Cannot unify TTuple with TList")
|
||||||
|
; "kind mismatch"
|
||||||
|
)]
|
||||||
|
#[test_case(2,
|
||||||
|
&[
|
||||||
|
("v1", "Tuple[int]"),
|
||||||
|
("v2", "Tuple[float]"),
|
||||||
|
],
|
||||||
|
(("v1", "v2"), "Cannot unify objects with ID 0 and 1")
|
||||||
|
; "tuple parameter mismatch"
|
||||||
|
)]
|
||||||
|
#[test_case(2,
|
||||||
|
&[
|
||||||
|
("v1", "Tuple[int,int]"),
|
||||||
|
("v2", "Tuple[int]"),
|
||||||
|
],
|
||||||
|
(("v1", "v2"), "Cannot unify tuples with length 1 and 2")
|
||||||
|
; "tuple length mismatch"
|
||||||
|
)]
|
||||||
|
#[test_case(3,
|
||||||
|
&[
|
||||||
|
("v1", "Record[a=float,b=int]"),
|
||||||
|
("v2", "Foo[v3]"),
|
||||||
|
],
|
||||||
|
(("v1", "v2"), "No such attribute b")
|
||||||
|
; "record obj merge"
|
||||||
|
)]
|
||||||
|
fn test_invalid_unification(
|
||||||
|
variable_count: u32,
|
||||||
|
unify_pairs: &[(&'static str, &'static str)],
|
||||||
|
errornous_pair: ((&'static str, &'static str), &'static str),
|
||||||
|
) {
|
||||||
|
let mut env = TestEnvironment::new();
|
||||||
|
let mut mapping = HashMap::new();
|
||||||
|
for i in 1..=variable_count {
|
||||||
|
let v = env.get_fresh_var();
|
||||||
|
mapping.insert(format!("v{}", i), v);
|
||||||
|
}
|
||||||
|
// unification may have side effect when we do type resolution, so freeze the types
|
||||||
|
// before doing unification.
|
||||||
|
let mut pairs = Vec::new();
|
||||||
|
for (a, b) in unify_pairs.iter() {
|
||||||
|
let t1 = env.parse(a, &mapping);
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
for (a, b) in pairs {
|
||||||
|
env.unifier.unify(a, b).unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(env.unifier.unify(t1, t2), Err(errornous_pair.1.to_string()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue};
|
use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue};
|
||||||
use generational_arena::{Arena, Index};
|
use generational_arena::{Arena, Index};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::HashMap;
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
use std::mem::swap;
|
use std::mem::swap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@ -18,10 +18,10 @@ use std::rc::Rc;
|
||||||
// `--> TFunc
|
// `--> TFunc
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
struct Type(u32);
|
pub struct Type(u32);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
struct TypeIndex(Index);
|
pub struct TypeIndex(Index);
|
||||||
|
|
||||||
impl UnifyValue for TypeIndex {
|
impl UnifyValue for TypeIndex {
|
||||||
type Error = NoError;
|
type Error = NoError;
|
||||||
|
@ -48,19 +48,19 @@ impl UnifyKey for Type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mapping<K, V = Type> = BTreeMap<K, V>;
|
pub type Mapping<K, V = Type> = HashMap<K, V>;
|
||||||
type VarMap = Mapping<u32>;
|
pub type VarMap = Mapping<u32>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Call {
|
pub struct Call {
|
||||||
posargs: Vec<Type>,
|
posargs: Vec<Type>,
|
||||||
kwargs: BTreeMap<String, Type>,
|
kwargs: HashMap<String, Type>,
|
||||||
ret: Type,
|
ret: Type,
|
||||||
fn_id: usize,
|
fun: RefCell<Option<Type>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct FuncArg {
|
pub struct FuncArg {
|
||||||
name: String,
|
name: String,
|
||||||
ty: Type,
|
ty: Type,
|
||||||
is_optional: bool,
|
is_optional: bool,
|
||||||
|
@ -69,7 +69,7 @@ struct FuncArg {
|
||||||
// We use a lot of `Rc`/`RefCell`s here as we want to simplify our code.
|
// 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
|
// We may not really need so much `Rc`s, but we would have to do complicated
|
||||||
// stuffs otherwise.
|
// stuffs otherwise.
|
||||||
enum TypeEnum {
|
pub enum TypeEnum {
|
||||||
TVar {
|
TVar {
|
||||||
// TODO: upper/lower bound
|
// TODO: upper/lower bound
|
||||||
id: u32,
|
id: u32,
|
||||||
|
@ -95,7 +95,7 @@ enum TypeEnum {
|
||||||
ty: Type,
|
ty: Type,
|
||||||
},
|
},
|
||||||
TCall {
|
TCall {
|
||||||
calls: Vec<Call>,
|
calls: Vec<Rc<Call>>,
|
||||||
},
|
},
|
||||||
TFunc {
|
TFunc {
|
||||||
args: Vec<FuncArg>,
|
args: Vec<FuncArg>,
|
||||||
|
@ -143,19 +143,40 @@ impl TypeEnum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ObjDef {
|
pub struct ObjDef {
|
||||||
name: String,
|
name: String,
|
||||||
fields: Mapping<String>,
|
fields: Mapping<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Unifier {
|
pub struct Unifier {
|
||||||
unification_table: RefCell<InPlaceUnificationTable<Type>>,
|
unification_table: RefCell<InPlaceUnificationTable<Type>>,
|
||||||
type_arena: RefCell<Arena<Rc<RefCell<TypeEnum>>>>,
|
type_arena: RefCell<Arena<Rc<RefCell<TypeEnum>>>>,
|
||||||
obj_def_table: Vec<ObjDef>,
|
obj_def_table: Vec<ObjDef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Unifier {
|
impl Unifier {
|
||||||
fn unify(&self, mut a: Type, mut b: Type) -> Result<(), String> {
|
pub fn new() -> Unifier {
|
||||||
|
Unifier {
|
||||||
|
unification_table: RefCell::new(InPlaceUnificationTable::new()),
|
||||||
|
type_arena: RefCell::new(Arena::new()),
|
||||||
|
obj_def_table: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_ty(&self, a: TypeEnum) -> Type {
|
||||||
|
let index = self.type_arena.borrow_mut().insert(Rc::new(a.into()));
|
||||||
|
self.unification_table
|
||||||
|
.borrow_mut()
|
||||||
|
.new_key(TypeIndex(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ty(&self, a: Type) -> Rc<RefCell<TypeEnum>> {
|
||||||
|
let mut table = self.unification_table.borrow_mut();
|
||||||
|
let arena = self.type_arena.borrow();
|
||||||
|
arena.get(table.probe_value(a).0).unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unify(&self, mut a: Type, mut b: Type) -> Result<(), String> {
|
||||||
let (mut i_a, mut i_b) = {
|
let (mut i_a, mut i_b) = {
|
||||||
let mut table = self.unification_table.borrow_mut();
|
let mut table = self.unification_table.borrow_mut();
|
||||||
(table.probe_value(a), table.probe_value(b))
|
(table.probe_value(a), table.probe_value(b))
|
||||||
|
@ -186,38 +207,21 @@ impl Unifier {
|
||||||
self.occur_check(i_a, b)?;
|
self.occur_check(i_a, b)?;
|
||||||
match &*ty_a {
|
match &*ty_a {
|
||||||
TypeEnum::TVar { .. } => {
|
TypeEnum::TVar { .. } => {
|
||||||
match *ty_b {
|
// TODO: type variables bound check...
|
||||||
TypeEnum::TVar { .. } => {
|
|
||||||
// TODO: type variables bound check
|
|
||||||
let old = {
|
|
||||||
let mut table = self.unification_table.borrow_mut();
|
|
||||||
table.union(a, b);
|
|
||||||
if table.find(a) == a {
|
|
||||||
i_b
|
|
||||||
} else {
|
|
||||||
i_a
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.type_arena.borrow_mut().remove(old.0);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// TODO: type variables bound check
|
|
||||||
self.set_a_to_b(a, b);
|
self.set_a_to_b(a, b);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
TypeEnum::TSeq { map: map1 } => {
|
TypeEnum::TSeq { map: map1 } => {
|
||||||
match &*ty_b {
|
match &*ty_b {
|
||||||
TypeEnum::TSeq { map: map2 } => {
|
TypeEnum::TSeq { .. } => {
|
||||||
drop(ty_a);
|
drop(ty_b);
|
||||||
if let TypeEnum::TSeq { map: map1 } = &mut *ty_a_cell.as_ref().borrow_mut()
|
if let TypeEnum::TSeq { map: map2 } = &mut *ty_b_cell.as_ref().borrow_mut()
|
||||||
{
|
{
|
||||||
// unify them to map1
|
// unify them to map1
|
||||||
for (key, value) in map2.iter() {
|
for (key, value) in map1.iter() {
|
||||||
if let Some(ty) = map1.get(key) {
|
if let Some(ty) = map2.get(key) {
|
||||||
self.unify(*ty, *value)?;
|
self.unify(*ty, *value)?;
|
||||||
} else {
|
} else {
|
||||||
map1.insert(*key, *value);
|
map2.insert(*key, *value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -277,16 +281,16 @@ impl Unifier {
|
||||||
}
|
}
|
||||||
TypeEnum::TRecord { fields: fields1 } => {
|
TypeEnum::TRecord { fields: fields1 } => {
|
||||||
match &*ty_b {
|
match &*ty_b {
|
||||||
TypeEnum::TRecord { fields: fields2 } => {
|
TypeEnum::TRecord { .. } => {
|
||||||
drop(ty_a);
|
drop(ty_b);
|
||||||
if let TypeEnum::TRecord { fields: fields1 } =
|
if let TypeEnum::TRecord { fields: fields2 } =
|
||||||
&mut *ty_a_cell.as_ref().borrow_mut()
|
&mut *ty_b_cell.as_ref().borrow_mut()
|
||||||
{
|
{
|
||||||
for (key, value) in fields2.iter() {
|
for (key, value) in fields1.iter() {
|
||||||
if let Some(ty) = fields1.get(key) {
|
if let Some(ty) = fields2.get(key) {
|
||||||
self.unify(*ty, *value)?;
|
self.unify(*ty, *value)?;
|
||||||
} else {
|
} else {
|
||||||
fields1.insert(key.clone(), *value);
|
fields2.insert(key.clone(), *value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -341,6 +345,7 @@ impl Unifier {
|
||||||
TypeEnum::TVirtual { ty: ty1 } => {
|
TypeEnum::TVirtual { ty: ty1 } => {
|
||||||
if let TypeEnum::TVirtual { ty: ty2 } = &*ty_b {
|
if let TypeEnum::TVirtual { ty: ty2 } = &*ty_b {
|
||||||
self.unify(*ty1, *ty2)?;
|
self.unify(*ty1, *ty2)?;
|
||||||
|
self.set_a_to_b(a, b);
|
||||||
} else {
|
} else {
|
||||||
return self.report_kind_error(&*ty_a, &*ty_b);
|
return self.report_kind_error(&*ty_a, &*ty_b);
|
||||||
}
|
}
|
||||||
|
@ -427,7 +432,7 @@ impl Unifier {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subst(&self, a: Type, mapping: &VarMap) -> Option<Type> {
|
pub fn subst(&self, a: Type, mapping: &VarMap) -> Option<Type> {
|
||||||
let index = self.unification_table.borrow_mut().probe_value(a);
|
let index = self.unification_table.borrow_mut().probe_value(a);
|
||||||
let ty_cell = {
|
let ty_cell = {
|
||||||
let arena = self.type_arena.borrow();
|
let arena = self.type_arena.borrow();
|
||||||
|
@ -459,34 +464,14 @@ impl Unifier {
|
||||||
new_ty.as_mut().unwrap()[i] = t1;
|
new_ty.as_mut().unwrap()[i] = t1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new_ty.map(|t| {
|
new_ty.map(|t| self.add_ty(TypeEnum::TTuple { ty: t }))
|
||||||
let index = self
|
|
||||||
.type_arena
|
|
||||||
.borrow_mut()
|
|
||||||
.insert(Rc::new(TypeEnum::TTuple { ty: t }.into()));
|
|
||||||
self.unification_table
|
|
||||||
.borrow_mut()
|
|
||||||
.new_key(TypeIndex(index))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
TypeEnum::TList { ty } => self.subst(*ty, mapping).map(|t| {
|
TypeEnum::TList { ty } => self
|
||||||
let index = self
|
.subst(*ty, mapping)
|
||||||
.type_arena
|
.map(|t| self.add_ty(TypeEnum::TList { ty: t })),
|
||||||
.borrow_mut()
|
TypeEnum::TVirtual { ty } => self
|
||||||
.insert(Rc::new(TypeEnum::TList { ty: t }.into()));
|
.subst(*ty, mapping)
|
||||||
self.unification_table
|
.map(|t| self.add_ty(TypeEnum::TVirtual { ty: t })),
|
||||||
.borrow_mut()
|
|
||||||
.new_key(TypeIndex(index))
|
|
||||||
}),
|
|
||||||
TypeEnum::TVirtual { ty } => self.subst(*ty, mapping).map(|t| {
|
|
||||||
let index = self
|
|
||||||
.type_arena
|
|
||||||
.borrow_mut()
|
|
||||||
.insert(Rc::new(TypeEnum::TVirtual { ty: t }.into()));
|
|
||||||
self.unification_table
|
|
||||||
.borrow_mut()
|
|
||||||
.new_key(TypeIndex(index))
|
|
||||||
}),
|
|
||||||
TypeEnum::TObj {
|
TypeEnum::TObj {
|
||||||
obj_id,
|
obj_id,
|
||||||
fields,
|
fields,
|
||||||
|
@ -508,23 +493,18 @@ impl Unifier {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if need_subst {
|
if need_subst {
|
||||||
let index = self.type_arena.borrow_mut().insert(Rc::new(
|
let obj_id = *obj_id;
|
||||||
TypeEnum::TObj {
|
let params = self
|
||||||
obj_id: *obj_id,
|
|
||||||
params: self
|
|
||||||
.subst_map(¶ms, mapping)
|
.subst_map(¶ms, mapping)
|
||||||
.unwrap_or_else(|| params.clone()),
|
.unwrap_or_else(|| params.clone());
|
||||||
fields: self
|
let fields = self
|
||||||
.subst_map(&fields, mapping)
|
.subst_map(&fields, mapping)
|
||||||
.unwrap_or_else(|| fields.clone()),
|
.unwrap_or_else(|| fields.clone());
|
||||||
}
|
Some(self.add_ty(TypeEnum::TObj {
|
||||||
.into(),
|
obj_id,
|
||||||
));
|
params,
|
||||||
Some(
|
fields,
|
||||||
self.unification_table
|
}))
|
||||||
.borrow_mut()
|
|
||||||
.new_key(TypeIndex(index)),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -546,19 +526,10 @@ impl Unifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if new_params.is_some() || new_ret.is_some() || new_args.is_some() {
|
if new_params.is_some() || new_ret.is_some() || new_args.is_some() {
|
||||||
let index = self.type_arena.borrow_mut().insert(Rc::new(
|
let params = new_params.unwrap_or_else(|| params.clone());
|
||||||
TypeEnum::TFunc {
|
let ret = new_ret.unwrap_or_else(|| *ret);
|
||||||
params: new_params.unwrap_or_else(|| params.clone()),
|
let args = new_args.unwrap_or_else(|| args.clone());
|
||||||
ret: new_ret.unwrap_or_else(|| *ret),
|
Some(self.add_ty(TypeEnum::TFunc { params, ret, args }))
|
||||||
args: new_args.unwrap_or_else(|| args.clone()),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
Some(
|
|
||||||
self.unification_table
|
|
||||||
.borrow_mut()
|
|
||||||
.new_key(TypeIndex(index)),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -569,7 +540,7 @@ impl Unifier {
|
||||||
|
|
||||||
fn subst_map<K>(&self, map: &Mapping<K>, mapping: &VarMap) -> Option<Mapping<K>>
|
fn subst_map<K>(&self, map: &Mapping<K>, mapping: &VarMap) -> Option<Mapping<K>>
|
||||||
where
|
where
|
||||||
K: std::cmp::Ord + std::clone::Clone,
|
K: std::hash::Hash + std::cmp::Eq + std::clone::Clone,
|
||||||
{
|
{
|
||||||
let mut map2 = None;
|
let mut map2 = None;
|
||||||
for (k, v) in map.iter() {
|
for (k, v) in map.iter() {
|
||||||
|
@ -582,4 +553,74 @@ impl Unifier {
|
||||||
}
|
}
|
||||||
map2
|
map2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn eq(&self, a: Type, b: Type) -> bool {
|
||||||
|
if a == b {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let (i_a, i_b) = {
|
||||||
|
let mut table = self.unification_table.borrow_mut();
|
||||||
|
(table.probe_value(a), table.probe_value(b))
|
||||||
|
};
|
||||||
|
|
||||||
|
if i_a == i_b {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ty_a, ty_b) = {
|
||||||
|
let arena = self.type_arena.borrow();
|
||||||
|
(
|
||||||
|
arena.get(i_a.0).unwrap().clone(),
|
||||||
|
arena.get(i_b.0).unwrap().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.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::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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_eq<K>(&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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue