diff --git a/nac3core/src/inference.rs b/nac3core/src/inference.rs index a0820f5c..1eb351e6 100644 --- a/nac3core/src/inference.rs +++ b/nac3core/src/inference.rs @@ -197,3 +197,395 @@ pub fn resolve_call( ) -> Result>, String> { resolve_call_rec(ctx, &None, obj, func, args) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::primitives::*; + + #[test] + fn test_simple_generic() { + let mut ctx = basic_ctx(); + + assert_eq!( + resolve_call(&ctx, None, "int32", &[PrimitiveType(FLOAT_TYPE).into()]), + Ok(Some(PrimitiveType(INT32_TYPE).into())) + ); + + assert_eq!( + resolve_call(&ctx, None, "int32", &[PrimitiveType(INT32_TYPE).into()],), + Ok(Some(PrimitiveType(INT32_TYPE).into())) + ); + + assert_eq!( + resolve_call(&ctx, None, "float", &[PrimitiveType(INT32_TYPE).into()]), + Ok(Some(PrimitiveType(FLOAT_TYPE).into())) + ); + + assert_eq!( + resolve_call(&ctx, None, "float", &[PrimitiveType(BOOL_TYPE).into()]), + Err("different domain".to_string()) + ); + + assert_eq!( + resolve_call(&ctx, None, "float", &[]), + Err("incorrect parameter number".to_string()) + ); + + let v1 = ctx.add_variable(VarDef { + name: "V1", + bound: vec![ + PrimitiveType(INT32_TYPE).into(), + PrimitiveType(FLOAT_TYPE).into(), + ], + }); + + assert_eq!( + resolve_call(&ctx, None, "float", &[TypeVariable(v1).into()]), + Ok(Some(PrimitiveType(FLOAT_TYPE).into())) + ); + + let v2 = ctx.add_variable(VarDef { + name: "V2", + bound: vec![ + PrimitiveType(BOOL_TYPE).into(), + PrimitiveType(INT32_TYPE).into(), + PrimitiveType(FLOAT_TYPE).into(), + ], + }); + + assert_eq!( + resolve_call(&ctx, None, "float", &[TypeVariable(v2).into()]), + Err("different domain".to_string()) + ); + } + + #[test] + fn test_methods() { + let mut ctx = basic_ctx(); + + let v0 = Rc::new(TypeVariable(ctx.add_variable(VarDef { + name: "V0", + bound: vec![], + }))); + let v1 = Rc::new(TypeVariable(ctx.add_variable(VarDef { + name: "V1", + bound: vec![ + PrimitiveType(INT32_TYPE).into(), + PrimitiveType(FLOAT_TYPE).into(), + ], + }))); + let v2 = Rc::new(TypeVariable(ctx.add_variable(VarDef { + name: "V2", + bound: vec![ + PrimitiveType(INT32_TYPE).into(), + PrimitiveType(FLOAT_TYPE).into(), + ], + }))); + let v3 = Rc::new(TypeVariable(ctx.add_variable(VarDef { + name: "V3", + bound: vec![ + PrimitiveType(BOOL_TYPE).into(), + PrimitiveType(INT32_TYPE).into(), + PrimitiveType(FLOAT_TYPE).into(), + ], + }))); + + let int32 = Rc::new(PrimitiveType(INT32_TYPE)); + let int64 = Rc::new(PrimitiveType(INT64_TYPE)); + + // simple cases + assert_eq!( + resolve_call(&ctx, Some(int32.clone()), "__add__", &[int32.clone()]), + Ok(Some(int32.clone())) + ); + + assert_ne!( + resolve_call(&ctx, Some(int32.clone()), "__add__", &[int32.clone()]), + Ok(Some(int64.clone())) + ); + + assert_eq!( + resolve_call(&ctx, Some(int32.clone()), "__add__", &[int64.clone()]), + Err("not equal".to_string()) + ); + + // with type variables + assert_eq!( + resolve_call(&ctx, Some(v1.clone()), "__add__", &[v1.clone()]), + Ok(Some(v1.clone())) + ); + assert_eq!( + resolve_call(&ctx, Some(v0.clone()), "__add__", &[v2.clone()]), + Err("unbounded type var".to_string()) + ); + assert_eq!( + resolve_call(&ctx, Some(v1.clone()), "__add__", &[v0.clone()]), + Err("different domain".to_string()) + ); + assert_eq!( + resolve_call(&ctx, Some(v1.clone()), "__add__", &[v2.clone()]), + Err("different domain".to_string()) + ); + assert_eq!( + resolve_call(&ctx, Some(v1.clone()), "__add__", &[v3.clone()]), + Err("different domain".to_string()) + ); + assert_eq!( + resolve_call(&ctx, Some(v3.clone()), "__add__", &[v1.clone()]), + Err("no such function".to_string()) + ); + assert_eq!( + resolve_call(&ctx, Some(v3.clone()), "__add__", &[v3.clone()]), + Err("no such function".to_string()) + ); + } + + #[test] + fn test_multi_generic() { + let mut ctx = basic_ctx(); + let v0 = Rc::new(TypeVariable(ctx.add_variable(VarDef { + name: "V0", + bound: vec![], + }))); + let v1 = Rc::new(TypeVariable(ctx.add_variable(VarDef { + name: "V1", + bound: vec![], + }))); + let v2 = Rc::new(TypeVariable(ctx.add_variable(VarDef { + name: "V2", + bound: vec![], + }))); + let v3 = Rc::new(TypeVariable(ctx.add_variable(VarDef { + name: "V3", + bound: vec![], + }))); + + ctx.add_fn( + "foo", + FnDef { + args: vec![v0.clone(), v0.clone(), v1.clone()], + result: Some(v0.clone()), + }, + ); + + ctx.add_fn( + "foo1", + FnDef { + args: vec![ + ParametricType(TUPLE_TYPE, vec![v0.clone(), v0.clone(), v1.clone()]).into(), + ], + result: Some(v0.clone()), + }, + ); + + assert_eq!( + resolve_call(&ctx, None, "foo", &[v2.clone(), v2.clone(), v2.clone()]), + Ok(Some(v2.clone())) + ); + assert_eq!( + resolve_call(&ctx, None, "foo", &[v2.clone(), v2.clone(), v3.clone()]), + Ok(Some(v2.clone())) + ); + assert_eq!( + resolve_call(&ctx, None, "foo", &[v2.clone(), v3.clone(), v3.clone()]), + Err("different variables".to_string()) + ); + + assert_eq!( + resolve_call( + &ctx, + None, + "foo1", + &[ParametricType(TUPLE_TYPE, vec![v2.clone(), v2.clone(), v2.clone()]).into()] + ), + Ok(Some(v2.clone())) + ); + assert_eq!( + resolve_call( + &ctx, + None, + "foo1", + &[ParametricType(TUPLE_TYPE, vec![v2.clone(), v2.clone(), v3.clone()]).into()] + ), + Ok(Some(v2.clone())) + ); + assert_eq!( + resolve_call( + &ctx, + None, + "foo1", + &[ParametricType(TUPLE_TYPE, vec![v2.clone(), v3.clone(), v3.clone()]).into()] + ), + Err("different variables".to_string()) + ); + } + + #[test] + fn test_class_generics() { + let mut ctx = basic_ctx(); + + let list = ctx.get_parametric_mut(LIST_TYPE); + let t = Rc::new(TypeVariable(list.params[0])); + list.base.methods.insert( + "head", + FnDef { + args: vec![], + result: Some(t.clone()), + }, + ); + list.base.methods.insert( + "append", + FnDef { + args: vec![t.clone()], + result: None, + }, + ); + + let v0 = Rc::new(TypeVariable(ctx.add_variable(VarDef { + name: "V0", + bound: vec![], + }))); + let v1 = Rc::new(TypeVariable(ctx.add_variable(VarDef { + name: "V1", + bound: vec![], + }))); + + assert_eq!( + resolve_call( + &ctx, + Some(ParametricType(LIST_TYPE, vec![v0.clone()]).into()), + "head", + &[] + ), + Ok(Some(v0.clone())) + ); + assert_eq!( + resolve_call( + &ctx, + Some(ParametricType(LIST_TYPE, vec![v0.clone()]).into()), + "append", + &[v0.clone()] + ), + Ok(None) + ); + assert_eq!( + resolve_call( + &ctx, + Some(ParametricType(LIST_TYPE, vec![v0.clone()]).into()), + "append", + &[v1.clone()] + ), + Err("different variables".to_string()) + ); + } + + #[test] + fn test_virtual_class() { + let mut ctx = basic_ctx(); + + let foo = ctx.add_class(ClassDef { + base: TypeDef { + name: "Foo", + methods: HashMap::new(), + fields: HashMap::new(), + }, + parents: vec![], + }); + + let foo1 = ctx.add_class(ClassDef { + base: TypeDef { + name: "Foo1", + methods: HashMap::new(), + fields: HashMap::new(), + }, + parents: vec![foo], + }); + + let foo2 = ctx.add_class(ClassDef { + base: TypeDef { + name: "Foo2", + methods: HashMap::new(), + fields: HashMap::new(), + }, + parents: vec![foo1], + }); + + let bar = ctx.add_class(ClassDef { + base: TypeDef { + name: "bar", + methods: HashMap::new(), + fields: HashMap::new(), + }, + parents: vec![], + }); + + ctx.add_fn( + "foo", + FnDef { + args: vec![VirtualClassType(foo).into()], + result: None, + }, + ); + ctx.add_fn( + "foo1", + FnDef { + args: vec![VirtualClassType(foo1).into()], + result: None, + }, + ); + + assert_eq!( + resolve_call(&ctx, None, "foo", &[ClassType(foo).into()]), + Ok(None) + ); + + assert_eq!( + resolve_call(&ctx, None, "foo", &[ClassType(foo1).into()]), + Ok(None) + ); + + assert_eq!( + resolve_call(&ctx, None, "foo", &[ClassType(foo2).into()]), + Ok(None) + ); + + assert_eq!( + resolve_call(&ctx, None, "foo", &[ClassType(bar).into()]), + Err("not subtype".to_string()) + ); + + assert_eq!( + resolve_call(&ctx, None, "foo1", &[ClassType(foo1).into()]), + Ok(None) + ); + + assert_eq!( + resolve_call(&ctx, None, "foo1", &[ClassType(foo2).into()]), + Ok(None) + ); + + assert_eq!( + resolve_call(&ctx, None, "foo1", &[ClassType(foo).into()]), + Err("not subtype".to_string()) + ); + + // virtual class substitution + assert_eq!( + resolve_call(&ctx, None, "foo", &[VirtualClassType(foo).into()]), + Ok(None) + ); + assert_eq!( + resolve_call(&ctx, None, "foo", &[VirtualClassType(foo1).into()]), + Ok(None) + ); + assert_eq!( + resolve_call(&ctx, None, "foo", &[VirtualClassType(foo2).into()]), + Ok(None) + ); + assert_eq!( + resolve_call(&ctx, None, "foo", &[VirtualClassType(bar).into()]), + Err("not subtype".to_string()) + ); + } +}