diff --git a/nac3artiq/src/codegen.rs b/nac3artiq/src/codegen.rs index 994c50484..914798c06 100644 --- a/nac3artiq/src/codegen.rs +++ b/nac3artiq/src/codegen.rs @@ -6,8 +6,8 @@ use nac3core::{ CodeGenContext, CodeGenerator, }, symbol_resolver::ValueEnum, - toplevel::{helper::PrimDef, DefinitionId, GenCall}, - typecheck::typedef::{FunSignature, FuncArg, Type, TypeEnum, VarMap}, + toplevel::{helper::PrimDef, numpy::unpack_ndarray_var_tys, DefinitionId, GenCall}, + typecheck::typedef::{iter_type_vars, FunSignature, FuncArg, Type, TypeEnum, VarMap}, }; use nac3parser::ast::{Expr, ExprKind, Located, Stmt, StmtKind, StrRef}; @@ -23,7 +23,6 @@ use pyo3::{ use crate::{symbol_resolver::InnerResolver, timeline::TimeFns}; -use nac3core::toplevel::numpy::unpack_ndarray_var_tys; use std::{ collections::hash_map::DefaultHasher, collections::HashMap, @@ -394,9 +393,11 @@ fn gen_rpc_tag( gen_rpc_tag(ctx, *ty, buffer)?; } } - TList { ty } => { + TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + let ty = iter_type_vars(params).next().unwrap().ty; + buffer.push(b'l'); - gen_rpc_tag(ctx, *ty, buffer)?; + gen_rpc_tag(ctx, ty, buffer)?; } TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => { let (ndarray_dtype, ndarray_ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ty); @@ -675,8 +676,10 @@ pub fn attributes_writeback( host_attributes.append(pydict)?; } } - TypeEnum::TList { ty: elem_ty } => { - if gen_rpc_tag(ctx, *elem_ty, &mut scratch_buffer).is_ok() { + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + let elem_ty = iter_type_vars(params).next().unwrap().ty; + + if gen_rpc_tag(ctx, elem_ty, &mut scratch_buffer).is_ok() { let pydict = PyDict::new(py); pydict.set_item("obj", val)?; host_attributes.append(pydict)?; diff --git a/nac3artiq/src/symbol_resolver.rs b/nac3artiq/src/symbol_resolver.rs index 62597b8ca..125e1bbfb 100644 --- a/nac3artiq/src/symbol_resolver.rs +++ b/nac3artiq/src/symbol_resolver.rs @@ -329,8 +329,19 @@ impl InnerResolver { Ok(Ok((primitives.exception, true))) } else if ty_id == self.primitive_ids.list { // do not handle type var param and concrete check here + let list_tvar = if let TypeEnum::TObj { obj_id, params, .. } = + &*unifier.get_ty_immutable(primitives.list) + { + assert_eq!(*obj_id, PrimDef::List.id()); + iter_type_vars(params).nth(0).unwrap() + } else { + unreachable!() + }; + let var = unifier.get_dummy_var().ty; - let list = unifier.add_ty(TypeEnum::TList { ty: var }); + let list = unifier + .subst(primitives.list, &into_var_map([TypeVar { id: list_tvar.id, ty: var }])) + .unwrap(); Ok(Ok((list, false))) } else if ty_id == self.primitive_ids.ndarray { // do not handle type var param and concrete check here @@ -460,7 +471,7 @@ impl InnerResolver { }; match &*unifier.get_ty(origin_ty) { - TypeEnum::TList { .. } => { + TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::List.id() => { if args.len() == 1 { let ty = match self.get_pyty_obj_type( py, @@ -477,7 +488,21 @@ impl InnerResolver { "type list should take concrete parameters in typevar range".into(), )); } - Ok(Ok((unifier.add_ty(TypeEnum::TList { ty: ty.0 }), true))) + let list_tvar = if let TypeEnum::TObj { obj_id, params, .. } = + &*unifier.get_ty_immutable(primitives.list) + { + assert_eq!(*obj_id, PrimDef::List.id()); + iter_type_vars(params).nth(0).unwrap() + } else { + unreachable!() + }; + let list = unifier + .subst( + primitives.list, + &into_var_map([TypeVar { id: list_tvar.id, ty: ty.0 }]), + ) + .unwrap(); + Ok(Ok((list, true))) } else { return Ok(Err(format!( "type list needs exactly 1 type parameters, found {}", @@ -693,11 +718,12 @@ impl InnerResolver { }; match (&*unifier.get_ty(extracted_ty), inst_check) { // do the instantiation for these four types - (TypeEnum::TList { ty }, false) => { + (TypeEnum::TObj { obj_id, params, .. }, false) if *obj_id == PrimDef::List.id() => { + let ty = iter_type_vars(params).nth(0).unwrap().ty; let len: usize = self.helper.len_fn.call1(py, (obj,))?.extract(py)?; if len == 0 { assert!(matches!( - &*unifier.get_ty(*ty), + &*unifier.get_ty(ty), TypeEnum::TVar { fields: None, range, .. } if range.is_empty() )); @@ -706,8 +732,25 @@ impl InnerResolver { let actual_ty = self.get_list_elem_type(py, obj, len, unifier, defs, primitives)?; match actual_ty { - Ok(t) => match unifier.unify(*ty, t) { - Ok(()) => Ok(Ok(unifier.add_ty(TypeEnum::TList { ty: *ty }))), + Ok(t) => match unifier.unify(ty, t) { + Ok(()) => { + let list_tvar = if let TypeEnum::TObj { obj_id, params, .. } = + &*unifier.get_ty_immutable(primitives.list) + { + assert_eq!(*obj_id, PrimDef::List.id()); + iter_type_vars(params).nth(0).unwrap() + } else { + unreachable!() + }; + let list = unifier + .subst( + primitives.list, + &into_var_map([TypeVar { id: list_tvar.id, ty }]), + ) + .unwrap(); + Ok(Ok(list)) + } + Err(e) => Ok(Err(format!( "type error ({}) for the list", e.to_display(unifier) @@ -942,12 +985,11 @@ impl InnerResolver { } let len: usize = self.helper.len_fn.call1(py, (obj,))?.extract(py)?; - let elem_ty = if let TypeEnum::TList { ty } = - ctx.unifier.get_ty_immutable(expected_ty).as_ref() - { - *ty - } else { - unreachable!("must be list") + let elem_ty = match ctx.unifier.get_ty_immutable(expected_ty).as_ref() { + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + iter_type_vars(params).nth(0).unwrap().ty + } + _ => unreachable!("must be list"), }; let ty = ctx.get_llvm_type(generator, elem_ty); let size_t = generator.get_size_type(ctx.ctx); diff --git a/nac3core/src/codegen/concrete_type.rs b/nac3core/src/codegen/concrete_type.rs index 15d063c5d..6488ec61b 100644 --- a/nac3core/src/codegen/concrete_type.rs +++ b/nac3core/src/codegen/concrete_type.rs @@ -47,9 +47,6 @@ pub enum ConcreteTypeEnum { TTuple { ty: Vec, }, - TList { - ty: ConcreteType, - }, TObj { obj_id: DefinitionId, fields: HashMap, @@ -167,9 +164,6 @@ impl ConcreteTypeStore { .map(|t| self.from_unifier_type(unifier, primitives, *t, cache)) .collect(), }, - TypeEnum::TList { ty } => ConcreteTypeEnum::TList { - ty: self.from_unifier_type(unifier, primitives, *ty, cache), - }, TypeEnum::TObj { obj_id, fields, params } => ConcreteTypeEnum::TObj { obj_id: *obj_id, fields: fields @@ -260,9 +254,6 @@ impl ConcreteTypeStore { .map(|cty| self.to_unifier_type(unifier, primitives, *cty, cache)) .collect(), }, - ConcreteTypeEnum::TList { ty } => { - TypeEnum::TList { ty: self.to_unifier_type(unifier, primitives, *ty, cache) } - } ConcreteTypeEnum::TVirtual { ty } => { TypeEnum::TVirtual { ty: self.to_unifier_type(unifier, primitives, *ty, cache) } } diff --git a/nac3core/src/codegen/expr.rs b/nac3core/src/codegen/expr.rs index 2d1880586..be8e6b4f2 100644 --- a/nac3core/src/codegen/expr.rs +++ b/nac3core/src/codegen/expr.rs @@ -2124,11 +2124,19 @@ pub fn gen_expr<'ctx, G: CodeGenerator>( } let ty = if elements.is_empty() { - let TypeEnum::TList { ty } = &*ctx.unifier.get_ty(expr.custom.unwrap()) else { + let ty = if let TypeEnum::TObj { obj_id, params, .. } = + &*ctx.unifier.get_ty(expr.custom.unwrap()) + { + if *obj_id != PrimDef::List.id() { + unreachable!() + } + + *params.iter().next().unwrap().1 + } else { unreachable!() }; - ctx.get_llvm_type(generator, *ty) + ctx.get_llvm_type(generator, ty) } else { elements[0].get_type() }; @@ -2550,7 +2558,9 @@ pub fn gen_expr<'ctx, G: CodeGenerator>( } ExprKind::Subscript { value, slice, .. } => { match &*ctx.unifier.get_ty(value.custom.unwrap()) { - TypeEnum::TList { ty } => { + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + let ty = params.iter().next().unwrap().1; + let v = if let Some(v) = generator.gen_expr(ctx, value)? { v.to_basic_value_enum(ctx, generator, value.custom.unwrap())? .into_pointer_value() diff --git a/nac3core/src/codegen/mod.rs b/nac3core/src/codegen/mod.rs index eb3b9d95c..179523695 100644 --- a/nac3core/src/codegen/mod.rs +++ b/nac3core/src/codegen/mod.rs @@ -456,6 +456,20 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>( .into() } + TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + let element_type = get_llvm_type( + ctx, + module, + generator, + unifier, + top_level, + type_cache, + *params.iter().next().unwrap().1, + ); + + ListType::new(generator, ctx, element_type).as_base_type().into() + } + TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => { let (dtype, _) = unpack_ndarray_var_tys(unifier, ty); let element_type = get_llvm_type( @@ -516,12 +530,6 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>( .collect_vec(); ctx.struct_type(&fields, false).into() } - TList { ty } => { - let element_type = - get_llvm_type(ctx, module, generator, unifier, top_level, type_cache, *ty); - - ListType::new(generator, ctx, element_type).as_base_type().into() - } TVirtual { .. } => unimplemented!(), _ => unreachable!("{}", ty_enum.get_type_name()), }; diff --git a/nac3core/src/codegen/numpy.rs b/nac3core/src/codegen/numpy.rs index 3811ac0bb..4db8a81f5 100644 --- a/nac3core/src/codegen/numpy.rs +++ b/nac3core/src/codegen/numpy.rs @@ -1804,10 +1804,15 @@ pub fn gen_ndarray_array<'ctx>( unpack_ndarray_var_tys(&mut context.unifier, obj_ty).0 } - TypeEnum::TList { ty } => { - let mut ty = *ty; - while let TypeEnum::TList { ty: elem_ty } = &*context.unifier.get_ty_immutable(ty) { - ty = *elem_ty; + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + let mut ty = *params.iter().next().unwrap().1; + while let TypeEnum::TObj { obj_id, params, .. } = &*context.unifier.get_ty_immutable(ty) + { + if *obj_id != PrimDef::List.id() { + break; + } + + ty = *params.iter().next().unwrap().1; } ty } diff --git a/nac3core/src/codegen/stmt.rs b/nac3core/src/codegen/stmt.rs index 5bae9a94c..94266ca26 100644 --- a/nac3core/src/codegen/stmt.rs +++ b/nac3core/src/codegen/stmt.rs @@ -136,7 +136,7 @@ pub fn gen_store_target<'ctx, G: CodeGenerator>( } ExprKind::Subscript { value, slice, .. } => { match ctx.unifier.get_ty_immutable(value.custom.unwrap()).as_ref() { - TypeEnum::TList { .. } => { + TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::List.id() => { let v = generator .gen_expr(ctx, value)? .unwrap() @@ -243,7 +243,9 @@ pub fn gen_assign<'ctx, G: CodeGenerator>( .into_pointer_value(); let value = ListValue::from_ptr_val(value, llvm_usize, None); let ty = match &*ctx.unifier.get_ty_immutable(target.custom.unwrap()) { - TypeEnum::TList { ty } => *ty, + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + *params.iter().next().unwrap().1 + } TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => { unpack_ndarray_var_tys(&mut ctx.unifier, target.custom.unwrap()).0 } diff --git a/nac3core/src/symbol_resolver.rs b/nac3core/src/symbol_resolver.rs index 753cc0764..ce9c0985c 100644 --- a/nac3core/src/symbol_resolver.rs +++ b/nac3core/src/symbol_resolver.rs @@ -382,13 +382,12 @@ pub trait SymbolResolver { } thread_local! { - static IDENTIFIER_ID: [StrRef; 12] = [ + static IDENTIFIER_ID: [StrRef; 11] = [ "int32".into(), "int64".into(), "float".into(), "bool".into(), "virtual".into(), - "list".into(), "tuple".into(), "str".into(), "Exception".into(), @@ -413,13 +412,12 @@ pub fn parse_type_annotation( let float_id = ids[2]; let bool_id = ids[3]; let virtual_id = ids[4]; - let list_id = ids[5]; - let tuple_id = ids[6]; - let str_id = ids[7]; - let exn_id = ids[8]; - let uint32_id = ids[9]; - let uint64_id = ids[10]; - let literal_id = ids[11]; + let tuple_id = ids[5]; + let str_id = ids[6]; + let exn_id = ids[7]; + let uint32_id = ids[8]; + let uint64_id = ids[9]; + let literal_id = ids[10]; let name_handling = |id: &StrRef, loc: Location, unifier: &mut Unifier| { if *id == int32_id { @@ -476,9 +474,6 @@ pub fn parse_type_annotation( if *id == virtual_id { let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?; Ok(unifier.add_ty(TypeEnum::TVirtual { ty })) - } else if *id == list_id { - let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?; - Ok(unifier.add_ty(TypeEnum::TList { ty })) } else if *id == tuple_id { if let Tuple { elts, .. } = &slice.node { let ty = elts diff --git a/nac3core/src/toplevel/builtins.rs b/nac3core/src/toplevel/builtins.rs index 2524b9a7c..954367c80 100644 --- a/nac3core/src/toplevel/builtins.rs +++ b/nac3core/src/toplevel/builtins.rs @@ -305,6 +305,8 @@ struct BuiltinBuilder<'a> { unwrap_ty: (Type, bool), option_tvar: TypeVar, + list_tvar: TypeVar, + ndarray_dtype_tvar: TypeVar, ndarray_ndims_tvar: TypeVar, ndarray_copy_ty: (Type, bool), @@ -395,7 +397,17 @@ impl<'a> BuiltinBuilder<'a> { unifier.get_fresh_var_with_range(&[num_ty.ty, ndarray_num_ty], Some("T".into()), None); let num_or_ndarray_var_map = into_var_map([num_ty, num_or_ndarray_ty]); - let list_int32 = unifier.add_ty(TypeEnum::TList { ty: int32 }); + let list_tvar = if let TypeEnum::TObj { obj_id, params, .. } = + &*unifier.get_ty_immutable(primitives.list) + { + assert_eq!(*obj_id, PrimDef::List.id()); + iter_type_vars(params).nth(0).unwrap() + } else { + unreachable!() + }; + let list_int32 = unifier + .subst(primitives.list, &into_var_map([TypeVar { id: list_tvar.id, ty: int32 }])) + .unwrap(); let ndarray_factory_fn_shape_arg_tvar = unifier.get_fresh_var(Some("Shape".into()), None); @@ -407,6 +419,8 @@ impl<'a> BuiltinBuilder<'a> { unwrap_ty, option_tvar, + list_tvar, + ndarray_dtype_tvar, ndarray_ndims_tvar, ndarray_copy_ty, @@ -457,6 +471,8 @@ impl<'a> BuiltinBuilder<'a> { | PrimDef::OptionUnwrap | PrimDef::FunSome => self.build_option_class_related(prim), + PrimDef::List => self.build_list_class_related(prim), + PrimDef::NDArray | PrimDef::NDArrayCopy | PrimDef::NDArrayFill => { self.build_ndarray_class_related(prim) } @@ -735,6 +751,27 @@ impl<'a> BuiltinBuilder<'a> { } } + fn build_list_class_related(&self, prim: PrimDef) -> TopLevelDef { + debug_assert_prim_is_allowed(prim, &[PrimDef::List]); + + match prim { + PrimDef::List => TopLevelDef::Class { + name: prim.name().into(), + object_id: prim.id(), + type_vars: vec![self.list_tvar.ty], + fields: Vec::default(), + attributes: Vec::default(), + methods: Vec::default(), + ancestors: Vec::default(), + constructor: None, + resolver: None, + loc: None, + }, + + _ => unreachable!(), + } + } + /// Build the class `ndarray` and its associated methods. fn build_ndarray_class_related(&self, prim: PrimDef) -> TopLevelDef { debug_assert_prim_is_allowed( @@ -1335,7 +1372,13 @@ impl<'a> BuiltinBuilder<'a> { let PrimitiveStore { uint64, int32, .. } = *self.primitives; let tvar = self.unifier.get_fresh_var(Some("L".into()), None); - let list = self.unifier.add_ty(TypeEnum::TList { ty: tvar.ty }); + let list = self + .unifier + .subst( + self.primitives.list, + &into_var_map([TypeVar { id: self.list_tvar.id, ty: tvar.ty }]), + ) + .unwrap(); let ndims = self.unifier.get_fresh_const_generic_var(uint64, Some("N".into()), None); let ndarray = make_ndarray_ty(self.unifier, self.primitives, Some(tvar.ty), Some(ndims.ty)); @@ -1367,7 +1410,7 @@ impl<'a> BuiltinBuilder<'a> { Some(calculate_len_for_slice_range(generator, ctx, start, end, step).into()) } else { match &*ctx.unifier.get_ty_immutable(arg_ty) { - TypeEnum::TList { .. } => { + TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::List.id() => { let int32 = ctx.ctx.i32_type(); let zero = int32.const_zero(); let len = ctx diff --git a/nac3core/src/toplevel/helper.rs b/nac3core/src/toplevel/helper.rs index 738125052..9fef74ebf 100644 --- a/nac3core/src/toplevel/helper.rs +++ b/nac3core/src/toplevel/helper.rs @@ -2,7 +2,7 @@ use std::convert::TryInto; use crate::symbol_resolver::SymbolValue; use crate::toplevel::numpy::unpack_ndarray_var_tys; -use crate::typecheck::typedef::{into_var_map, Mapping, TypeVarId, VarMap}; +use crate::typecheck::typedef::{into_var_map, iter_type_vars, Mapping, TypeVarId, VarMap}; use nac3parser::ast::{Constant, Location}; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -12,6 +12,7 @@ use super::*; /// All primitive types and functions in nac3core. #[derive(Clone, Copy, Debug, EnumIter, PartialEq, Eq)] pub enum PrimDef { + // Classes Int32, Int64, Float, @@ -23,10 +24,13 @@ pub enum PrimDef { UInt32, UInt64, Option, + List, + NDArray, + + // Member Functions OptionIsSome, OptionIsNone, OptionUnwrap, - NDArray, NDArrayCopy, NDArrayFill, FunInt32, @@ -99,6 +103,8 @@ pub enum PrimDef { FunNpLdExp, FunNpHypot, FunNpNextAfter, + + // Top-Level Functions FunSome, } @@ -177,6 +183,7 @@ impl PrimDef { PrimDef::OptionIsSome => fun("Option.is_some", Some("is_some")), PrimDef::OptionIsNone => fun("Option.is_none", Some("is_none")), PrimDef::OptionUnwrap => fun("Option.unwrap", Some("unwrap")), + PrimDef::List => class("list"), PrimDef::NDArray => class("ndarray"), PrimDef::NDArrayCopy => fun("ndarray.copy", Some("copy")), PrimDef::NDArrayFill => fun("ndarray.fill", Some("fill")), @@ -410,6 +417,13 @@ impl TopLevelComposer { _ => unreachable!(), }; + let list_elem_tvar = unifier.get_fresh_var(Some("list_elem".into()), None); + let list = unifier.add_ty(TypeEnum::TObj { + obj_id: PrimDef::List.id(), + fields: Mapping::new(), + params: into_var_map([list_elem_tvar]), + }); + let ndarray_dtype_tvar = unifier.get_fresh_var(Some("ndarray_dtype".into()), None); let ndarray_ndims_tvar = unifier.get_fresh_const_generic_var(size_t_ty, Some("ndarray_ndims".into()), None); @@ -451,6 +465,7 @@ impl TopLevelComposer { str, exception, option, + list, ndarray, size_t, }; @@ -888,7 +903,9 @@ pub fn arraylike_flatten_element_type(unifier: &mut Unifier, ty: Type) -> Type { unpack_ndarray_var_tys(unifier, ty).0 } - TypeEnum::TList { ty } => arraylike_flatten_element_type(unifier, *ty), + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + arraylike_flatten_element_type(unifier, iter_type_vars(params).next().unwrap().ty) + } _ => ty, } } @@ -909,7 +926,9 @@ pub fn arraylike_get_ndims(unifier: &mut Unifier, ty: Type) -> u64 { u64::try_from(values[0].clone()).unwrap() } - TypeEnum::TList { ty } => arraylike_get_ndims(unifier, *ty) + 1, + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + arraylike_get_ndims(unifier, iter_type_vars(params).next().unwrap().ty) + 1 + } _ => 0, } } diff --git a/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__generic_class.snap b/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__generic_class.snap index 82ab00e4b..ed94a8339 100644 --- a/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__generic_class.snap +++ b/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__generic_class.snap @@ -5,7 +5,7 @@ expression: res_vec [ "Class {\nname: \"Generic_A\",\nancestors: [\"Generic_A[V]\", \"B\"],\nfields: [\"aa\", \"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\"), (\"fun\", \"fn[[a:int32], V]\")],\ntype_vars: [\"V\"]\n}\n", "Function {\nname: \"Generic_A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", - "Function {\nname: \"Generic_A.fun\",\nsig: \"fn[[a:int32], V]\",\nvar_id: [TypeVarId(240)]\n}\n", + "Function {\nname: \"Generic_A.fun\",\nsig: \"fn[[a:int32], V]\",\nvar_id: [TypeVarId(242)]\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\"],\nfields: [\"aa\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\")],\ntype_vars: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.foo\",\nsig: \"fn[[b:T], none]\",\nvar_id: []\n}\n", diff --git a/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__inheritance_override.snap b/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__inheritance_override.snap index d3301d007..2c59687cc 100644 --- a/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__inheritance_override.snap +++ b/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__inheritance_override.snap @@ -7,7 +7,7 @@ expression: res_vec "Function {\nname: \"A.__init__\",\nsig: \"fn[[t:T], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n", "Function {\nname: \"A.foo\",\nsig: \"fn[[c:C], none]\",\nvar_id: []\n}\n", - "Class {\nname: \"B\",\nancestors: [\"B[typevar229]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"typevar229\"]\n}\n", + "Class {\nname: \"B\",\nancestors: [\"B[typevar231]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"typevar231\"]\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n", "Class {\nname: \"C\",\nancestors: [\"C\", \"B[bool]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\", \"e\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: []\n}\n", diff --git a/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__list_tuple_generic.snap b/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__list_tuple_generic.snap index 911426b93..2aa2ead60 100644 --- a/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__list_tuple_generic.snap +++ b/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__list_tuple_generic.snap @@ -5,8 +5,8 @@ expression: res_vec [ "Function {\nname: \"foo\",\nsig: \"fn[[a:list[int32], b:tuple[T, float]], A[B, bool]]\",\nvar_id: []\n}\n", "Class {\nname: \"A\",\nancestors: [\"A[T, V]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[v:V], none]\"), (\"fun\", \"fn[[a:T], V]\")],\ntype_vars: [\"T\", \"V\"]\n}\n", - "Function {\nname: \"A.__init__\",\nsig: \"fn[[v:V], none]\",\nvar_id: [TypeVarId(242)]\n}\n", - "Function {\nname: \"A.fun\",\nsig: \"fn[[a:T], V]\",\nvar_id: [TypeVarId(247)]\n}\n", + "Function {\nname: \"A.__init__\",\nsig: \"fn[[v:V], none]\",\nvar_id: [TypeVarId(244)]\n}\n", + "Function {\nname: \"A.fun\",\nsig: \"fn[[a:T], V]\",\nvar_id: [TypeVarId(249)]\n}\n", "Function {\nname: \"gfun\",\nsig: \"fn[[a:A[list[float], int32]], none]\",\nvar_id: []\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\"],\nfields: [],\nmethods: [(\"__init__\", \"fn[[], none]\")],\ntype_vars: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", diff --git a/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__self1.snap b/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__self1.snap index d60daf83f..a350d18a5 100644 --- a/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__self1.snap +++ b/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__self1.snap @@ -3,7 +3,7 @@ source: nac3core/src/toplevel/test.rs expression: res_vec --- [ - "Class {\nname: \"A\",\nancestors: [\"A[typevar228, typevar229]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[a:A[float, bool], b:B], none]\"), (\"fun\", \"fn[[a:A[float, bool]], A[bool, int32]]\")],\ntype_vars: [\"typevar228\", \"typevar229\"]\n}\n", + "Class {\nname: \"A\",\nancestors: [\"A[typevar230, typevar231]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[a:A[float, bool], b:B], none]\"), (\"fun\", \"fn[[a:A[float, bool]], A[bool, int32]]\")],\ntype_vars: [\"typevar230\", \"typevar231\"]\n}\n", "Function {\nname: \"A.__init__\",\nsig: \"fn[[a:A[float, bool], b:B], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[a:A[float, bool]], A[bool, int32]]\",\nvar_id: []\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\", \"A[int64, bool]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:A[float, bool]], A[bool, int32]]\"), (\"foo\", \"fn[[b:B], B]\"), (\"bar\", \"fn[[a:A[list[B], int32]], tuple[A[virtual[A[B, int32]], bool], B]]\")],\ntype_vars: []\n}\n", diff --git a/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__simple_class_compose.snap b/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__simple_class_compose.snap index 0fea9e25b..5fe92b13e 100644 --- a/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__simple_class_compose.snap +++ b/nac3core/src/toplevel/snapshots/nac3core__toplevel__test__test_analyze__simple_class_compose.snap @@ -6,12 +6,12 @@ expression: res_vec "Class {\nname: \"A\",\nancestors: [\"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n", "Function {\nname: \"A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n", - "Function {\nname: \"A.foo\",\nsig: \"fn[[a:T, b:V], none]\",\nvar_id: [TypeVarId(248)]\n}\n", + "Function {\nname: \"A.foo\",\nsig: \"fn[[a:T, b:V], none]\",\nvar_id: [TypeVarId(250)]\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\", \"C\", \"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Class {\nname: \"C\",\nancestors: [\"C\", \"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n", "Function {\nname: \"C.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"C.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n", "Function {\nname: \"foo\",\nsig: \"fn[[a:A], none]\",\nvar_id: []\n}\n", - "Function {\nname: \"ff\",\nsig: \"fn[[a:T], V]\",\nvar_id: [TypeVarId(256)]\n}\n", + "Function {\nname: \"ff\",\nsig: \"fn[[a:T], V]\",\nvar_id: [TypeVarId(258)]\n}\n", ] diff --git a/nac3core/src/toplevel/test.rs b/nac3core/src/toplevel/test.rs index 4651ab21e..7678452d8 100644 --- a/nac3core/src/toplevel/test.rs +++ b/nac3core/src/toplevel/test.rs @@ -1,3 +1,6 @@ +use super::*; +use crate::toplevel::helper::PrimDef; +use crate::typecheck::typedef::into_var_map; use crate::{ codegen::CodeGenContext, symbol_resolver::{SymbolResolver, ValueEnum}, @@ -14,8 +17,6 @@ use parking_lot::Mutex; use std::{collections::HashMap, sync::Arc}; use test_case::test_case; -use super::*; - struct ResolverInternal { id_to_type: Mutex>, id_to_def: Mutex>, @@ -775,8 +776,15 @@ fn make_internal_resolver_with_tvar( unifier: &mut Unifier, print: bool, ) -> Arc { + let list_elem_tvar = unifier.get_fresh_var(Some("list_elem".into()), None); + let list = unifier.add_ty(TypeEnum::TObj { + obj_id: PrimDef::List.id(), + fields: HashMap::new(), + params: into_var_map([list_elem_tvar]), + }); + let res: Arc = ResolverInternal { - id_to_def: Mutex::default(), + id_to_def: Mutex::new(HashMap::from([("list".into(), PrimDef::List.id())])), id_to_type: tvars .into_iter() .map(|(name, range)| { @@ -790,7 +798,7 @@ fn make_internal_resolver_with_tvar( }) .collect::>() .into(), - class_names: Mutex::default(), + class_names: Mutex::new(HashMap::from([("list".into(), list)])), } .into(); if print { diff --git a/nac3core/src/toplevel/type_annotation.rs b/nac3core/src/toplevel/type_annotation.rs index f598badfc..95d5acad3 100644 --- a/nac3core/src/toplevel/type_annotation.rs +++ b/nac3core/src/toplevel/type_annotation.rs @@ -18,7 +18,6 @@ pub enum TypeAnnotation { TypeVar(Type), /// A `Literal` allowing a subset of literals. Literal(Vec), - List(Box), Tuple(Vec), } @@ -51,7 +50,6 @@ impl TypeAnnotation { format!("Literal({})", values.iter().map(|v| format!("{v:?}")).join(", ")) } Virtual(ty) => format!("virtual[{}]", ty.stringify(unifier)), - List(ty) => format!("list[{}]", ty.stringify(unifier)), Tuple(types) => { format!( "tuple[{}]", @@ -145,9 +143,7 @@ pub fn parse_ast_to_type_annotation_kinds( slice: &ast::Expr, unifier: &mut Unifier, mut locked: HashMap, S>| { - if ["virtual".into(), "Generic".into(), "list".into(), "tuple".into(), "Option".into()] - .contains(id) - { + if ["virtual".into(), "Generic".into(), "tuple".into(), "Option".into()].contains(id) { return Err(HashSet::from([format!( "keywords cannot be class name (at {})", expr.location @@ -236,23 +232,6 @@ pub fn parse_ast_to_type_annotation_kinds( Ok(TypeAnnotation::Virtual(def.into())) } - // list - ast::ExprKind::Subscript { value, slice, .. } - if { - matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"list".into()) - } => - { - let def_ann = parse_ast_to_type_annotation_kinds( - resolver, - top_level_defs, - unifier, - primitives, - slice.as_ref(), - locked, - )?; - Ok(TypeAnnotation::List(def_ann.into())) - } - // option ast::ExprKind::Subscript { value, slice, .. } if { @@ -516,15 +495,6 @@ pub fn get_type_from_type_annotation_kinds( )?; Ok(unifier.add_ty(TypeEnum::TVirtual { ty })) } - TypeAnnotation::List(ty) => { - let ty = get_type_from_type_annotation_kinds( - top_level_defs, - unifier, - ty.as_ref(), - subst_list, - )?; - Ok(unifier.add_ty(TypeEnum::TList { ty })) - } TypeAnnotation::Tuple(tys) => { let tys = tys .iter() @@ -565,7 +535,7 @@ pub fn get_type_var_contained_in_type_annotation(ann: &TypeAnnotation) -> Vec = Vec::new(); match ann { TypeAnnotation::TypeVar(..) => result.push(ann.clone()), - TypeAnnotation::Virtual(ann) | TypeAnnotation::List(ann) => { + TypeAnnotation::Virtual(ann) => { result.extend(get_type_var_contained_in_type_annotation(ann.as_ref())); } TypeAnnotation::CustomClass { params, .. } => { @@ -606,8 +576,7 @@ pub fn check_overload_type_annotation_compatible( a == b } - (TypeAnnotation::Virtual(a), TypeAnnotation::Virtual(b)) - | (TypeAnnotation::List(a), TypeAnnotation::List(b)) => { + (TypeAnnotation::Virtual(a), TypeAnnotation::Virtual(b)) => { check_overload_type_annotation_compatible(a.as_ref(), b.as_ref(), unifier) } diff --git a/nac3core/src/typecheck/type_inferencer/mod.rs b/nac3core/src/typecheck/type_inferencer/mod.rs index f879650f7..c20fe78df 100644 --- a/nac3core/src/typecheck/type_inferencer/mod.rs +++ b/nac3core/src/typecheck/type_inferencer/mod.rs @@ -4,15 +4,20 @@ use std::iter::once; use std::ops::Not; use std::{cell::RefCell, sync::Arc}; -use super::typedef::{Call, FunSignature, FuncArg, RecordField, Type, TypeEnum, Unifier, VarMap}; -use super::{magic_methods::*, type_error::TypeError, typedef::CallId}; -use crate::toplevel::TopLevelDef; +use super::{ + magic_methods::*, + type_error::TypeError, + typedef::{ + into_var_map, iter_type_vars, Call, CallId, FunSignature, FuncArg, RecordField, Type, + TypeEnum, TypeVar, Unifier, VarMap, + }, +}; use crate::{ symbol_resolver::{SymbolResolver, SymbolValue}, toplevel::{ helper::{arraylike_flatten_element_type, arraylike_get_ndims, PrimDef}, numpy::{make_ndarray_ty, unpack_ndarray_var_tys}, - TopLevelContext, + TopLevelContext, TopLevelDef, }, }; use itertools::{izip, Itertools}; @@ -50,6 +55,7 @@ pub struct PrimitiveStore { pub str: Type, pub exception: Type, pub option: Type, + pub list: Type, pub ndarray: Type, pub size_t: u32, } @@ -242,8 +248,17 @@ impl<'a> Fold<()> for Inferencer<'a> { self.unify(self.primitives.int32, target.custom.unwrap(), &target.location)?; } else { let list_like_ty = match &*self.unifier.get_ty(iter.custom.unwrap()) { - TypeEnum::TList { .. } => { - self.unifier.add_ty(TypeEnum::TList { ty: target.custom.unwrap() }) + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + let list_tvar = iter_type_vars(params).nth(0).unwrap(); + self.unifier + .subst( + self.primitives.list, + &into_var_map([TypeVar { + id: list_tvar.id, + ty: target.custom.unwrap(), + }]), + ) + .unwrap() } TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => { todo!() @@ -764,6 +779,16 @@ impl<'a> Inferencer<'a> { generators[0].target.location, ); } + + let list_tvar = if let TypeEnum::TObj { obj_id, params, .. } = + &*self.unifier.get_ty_immutable(self.primitives.list) + { + assert_eq!(*obj_id, PrimDef::List.id()); + iter_type_vars(params).nth(0).unwrap() + } else { + unreachable!() + }; + let variable_mapping = self.variable_mapping.clone(); let defined_identifiers = self.defined_identifiers.clone(); let mut new_context = Inferencer { @@ -792,7 +817,13 @@ impl<'a> Inferencer<'a> { &target.location, )?; } else { - let list = new_context.unifier.add_ty(TypeEnum::TList { ty: target.custom.unwrap() }); + let list = new_context + .unifier + .subst( + self.primitives.list, + &into_var_map([TypeVar { id: list_tvar.id, ty: target.custom.unwrap() }]), + ) + .unwrap(); new_context.unify(iter.custom.unwrap(), list, &iter.location)?; } let ifs: Vec<_> = generator @@ -809,9 +840,16 @@ impl<'a> Inferencer<'a> { new_context.unify(v.custom.unwrap(), new_context.primitives.bool, &v.location)?; } + let custom = new_context + .unifier + .subst( + self.primitives.list, + &into_var_map([TypeVar { id: list_tvar.id, ty: elt.custom.unwrap() }]), + ) + .unwrap(); Ok(Located { location, - custom: Some(new_context.unifier.add_ty(TypeEnum::TList { ty: elt.custom.unwrap() })), + custom: Some(custom), node: ExprKind::ListComp { elt: Box::new(elt), generators: vec![Comprehension { @@ -893,11 +931,13 @@ impl<'a> Inferencer<'a> { // Here, we also take the opportunity to deduce `ndims` statically. let shape_ty_enum = &*self.unifier.get_ty(shape_ty); let ndims = match shape_ty_enum { - TypeEnum::TList { ty } => { + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { // Handle 1. A list of int32s + let ty = iter_type_vars(params).nth(0).unwrap().ty; + // Typecheck - self.unifier.unify(*ty, self.primitives.int32).map_err(|err| { + self.unifier.unify(ty, self.primitives.int32).map_err(|err| { HashSet::from([err .at(Some(shape.location)) .to_display(self.unifier) @@ -1563,7 +1603,19 @@ impl<'a> Inferencer<'a> { for t in elts { self.unify(ty, t.custom.unwrap(), &t.location)?; } - Ok(self.unifier.add_ty(TypeEnum::TList { ty })) + let list_tvar = if let TypeEnum::TObj { obj_id, params, .. } = + &*self.unifier.get_ty_immutable(self.primitives.list) + { + assert_eq!(*obj_id, PrimDef::List.id()); + iter_type_vars(params).nth(0).unwrap() + } else { + unreachable!() + }; + let list = self + .unifier + .subst(self.primitives.list, &into_var_map([TypeVar { id: list_tvar.id, ty }])) + .unwrap(); + Ok(list) } #[allow(clippy::unnecessary_wraps)] @@ -1885,7 +1937,16 @@ impl<'a> Inferencer<'a> { self.constrain(v.custom.unwrap(), self.primitives.int32, &v.location)?; } let list_like_ty = match &*self.unifier.get_ty(value.custom.unwrap()) { - TypeEnum::TList { .. } => self.unifier.add_ty(TypeEnum::TList { ty }), + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + let list_tvar = iter_type_vars(params).nth(0).unwrap(); + self.unifier + .subst( + self.primitives.list, + &into_var_map([TypeVar { id: list_tvar.id, ty }]), + ) + .unwrap() + } + TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => { let (_, ndims) = unpack_ndarray_var_tys(self.unifier, value.custom.unwrap()); @@ -1960,13 +2021,20 @@ impl<'a> Inferencer<'a> { // the index is not a constant, so value can only be a list-like structure match &*self.unifier.get_ty(value.custom.unwrap()) { - TypeEnum::TList { .. } => { + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { self.constrain( slice.custom.unwrap(), self.primitives.int32, &slice.location, )?; - let list = self.unifier.add_ty(TypeEnum::TList { ty }); + let list_tvar = iter_type_vars(params).nth(0).unwrap(); + let list = self + .unifier + .subst( + self.primitives.list, + &into_var_map([TypeVar { id: list_tvar.id, ty }]), + ) + .unwrap(); self.constrain(value.custom.unwrap(), list, &value.location)?; Ok(ty) } diff --git a/nac3core/src/typecheck/type_inferencer/test.rs b/nac3core/src/typecheck/type_inferencer/test.rs index 9096ae56e..0a367ae75 100644 --- a/nac3core/src/typecheck/type_inferencer/test.rs +++ b/nac3core/src/typecheck/type_inferencer/test.rs @@ -139,6 +139,12 @@ impl TestEnvironment { fields: HashMap::new(), params: VarMap::new(), }); + let list_elem_tvar = unifier.get_fresh_var(Some("list_elem".into()), None); + let list = unifier.add_ty(TypeEnum::TObj { + obj_id: PrimDef::List.id(), + fields: HashMap::new(), + params: into_var_map([list_elem_tvar]), + }); let ndarray_dtype_tvar = unifier.get_fresh_var(Some("ndarray_dtype".into()), None); let ndarray_ndims_tvar = unifier.get_fresh_const_generic_var(uint64, Some("ndarray_ndims".into()), None); @@ -159,6 +165,7 @@ impl TestEnvironment { uint32, uint64, option, + list, ndarray, size_t: 64, }; @@ -273,15 +280,35 @@ impl TestEnvironment { fields: HashMap::new(), params: VarMap::new(), }); + let list_elem_tvar = unifier.get_fresh_var(Some("list_elem".into()), None); + let list = unifier.add_ty(TypeEnum::TObj { + obj_id: PrimDef::List.id(), + fields: HashMap::new(), + params: into_var_map([list_elem_tvar]), + }); let ndarray = unifier.add_ty(TypeEnum::TObj { obj_id: PrimDef::NDArray.id(), fields: HashMap::new(), params: VarMap::new(), }); identifier_mapping.insert("None".into(), none); - for (i, name) in ["int32", "int64", "float", "bool", "none", "range", "str", "Exception"] - .iter() - .enumerate() + for (i, name) in [ + "int32", + "int64", + "float", + "bool", + "none", + "range", + "str", + "Exception", + "uint32", + "uint64", + "Option", + "list", + "ndarray", + ] + .iter() + .enumerate() { top_level_defs.push( RwLock::new(TopLevelDef::Class { @@ -299,7 +326,7 @@ impl TestEnvironment { .into(), ); } - let defs = 7; + let defs = 12; let primitives = PrimitiveStore { int32, @@ -313,6 +340,7 @@ impl TestEnvironment { uint32, uint64, option, + list, ndarray, size_t: 64, }; @@ -424,6 +452,11 @@ impl TestEnvironment { "range".into(), "str".into(), "exception".into(), + "uint32".into(), + "uint64".into(), + "option".into(), + "list".into(), + "ndarray".into(), "Foo".into(), "Bar".into(), "Bar2".into(), diff --git a/nac3core/src/typecheck/typedef/mod.rs b/nac3core/src/typecheck/typedef/mod.rs index 97c9483c5..0382830b7 100644 --- a/nac3core/src/typecheck/typedef/mod.rs +++ b/nac3core/src/typecheck/typedef/mod.rs @@ -13,7 +13,7 @@ use nac3parser::ast::{Location, StrRef}; use super::type_error::{TypeError, TypeErrorKind}; use super::unification_table::{UnificationKey, UnificationTable}; use crate::symbol_resolver::SymbolValue; -use crate::toplevel::{DefinitionId, TopLevelContext, TopLevelDef}; +use crate::toplevel::{helper::PrimDef, DefinitionId, TopLevelContext, TopLevelDef}; use crate::typecheck::type_inferencer::PrimitiveStore; #[cfg(test)] @@ -207,12 +207,6 @@ pub enum TypeEnum { ty: Vec, }, - /// A list type. - TList { - /// The type of elements present in this list. - ty: Type, - }, - /// An object type. TObj { /// The [`DefinitionId`] of this object type. @@ -246,7 +240,6 @@ impl TypeEnum { TypeEnum::TVar { .. } => "TVar", TypeEnum::TLiteral { .. } => "TConstant", TypeEnum::TTuple { .. } => "TTuple", - TypeEnum::TList { .. } => "TList", TypeEnum::TObj { .. } => "TObj", TypeEnum::TVirtual { .. } => "TVirtual", TypeEnum::TCall { .. } => "TCall", @@ -482,9 +475,27 @@ impl Unifier { ) } } - TypeEnum::TList { ty } => self - .get_instantiations(*ty) - .map(|ty| ty.iter().map(|&ty| self.add_ty(TypeEnum::TList { ty })).collect_vec()), + TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { + let tv = iter_type_vars(params).nth(0).unwrap(); + + let tv_id = if let TypeEnum::TVar { id, .. } = + self.unification_table.probe_value(tv.ty).as_ref() + { + *id + } else { + tv.id + }; + + self.get_instantiations(tv.ty).map(|ty_insts| { + ty_insts + .iter() + .map(|&ty_inst| { + self.subst(ty, &into_var_map([TypeVar { id: tv_id, ty: ty_inst }])) + .unwrap_or(ty) + }) + .collect() + }) + } TypeEnum::TVirtual { ty } => self.get_instantiations(*ty).map(|ty| { ty.iter().map(|&ty| self.add_ty(TypeEnum::TVirtual { ty })).collect_vec() }), @@ -541,9 +552,7 @@ impl Unifier { TVar { .. } => allowed_typevars.iter().any(|b| self.unification_table.unioned(a, *b)), TCall { .. } => false, - TList { ty } - | TVirtual { ty } => self.is_concrete(*ty, allowed_typevars), - + TVirtual { ty } => self.is_concrete(*ty, allowed_typevars), TTuple { ty } => ty.iter().all(|ty| self.is_concrete(*ty, allowed_typevars)), TObj { params: vars, .. } => { vars.values().all(|ty| self.is_concrete(*ty, allowed_typevars)) @@ -885,11 +894,16 @@ impl Unifier { self.unify_impl(x, b, false)?; self.set_a_to_b(a, x); } - (TVar { fields: Some(fields), range, is_const_generic: false, .. }, TList { ty }) => { + ( + TVar { fields: Some(fields), range, is_const_generic: false, .. }, + TObj { obj_id, params, .. }, + ) if *obj_id == PrimDef::List.id() => { + let ty = iter_type_vars(params).nth(0).unwrap().ty; + for (k, v) in fields { match *k { RecordKey::Int(_) => { - self.unify_impl(v.ty, *ty, false).map_err(|e| e.at(v.loc))?; + self.unify_impl(v.ty, ty, false).map_err(|e| e.at(v.loc))?; } RecordKey::Str(_) => { return Err(TypeError::new(TypeErrorKind::NoSuchField(*k, b), v.loc)) @@ -979,12 +993,6 @@ impl Unifier { } self.set_a_to_b(a, b); } - (TList { ty: ty1 }, TList { ty: ty2 }) => { - if self.unify_impl(*ty1, *ty2, false).is_err() { - return Err(TypeError::new(TypeErrorKind::IncompatibleTypes(a, b), None)); - } - self.set_a_to_b(a, b); - } (TVar { fields: Some(map), range, .. }, TObj { fields, .. }) => { for (k, field) in map { match *k { @@ -1222,9 +1230,6 @@ impl Unifier { ty.iter().map(|v| self.internal_stringify(*v, obj_to_name, var_to_name, notes)); format!("tuple[{}]", fields.join(", ")) } - TypeEnum::TList { ty } => { - format!("list[{}]", self.internal_stringify(*ty, obj_to_name, var_to_name, notes)) - } TypeEnum::TVirtual { ty } => { format!( "virtual[{}]", @@ -1357,9 +1362,6 @@ impl Unifier { None } } - TypeEnum::TList { ty } => { - self.subst_impl(*ty, mapping, cache).map(|t| self.add_ty(TypeEnum::TList { ty: t })) - } TypeEnum::TVirtual { ty } => self .subst_impl(*ty, mapping, cache) .map(|t| self.add_ty(TypeEnum::TVirtual { ty: t })), @@ -1370,6 +1372,7 @@ impl Unifier { // This is also used to prevent infinite substitution... let need_subst = params.values().any(|v| { let ty = self.unification_table.probe_value(*v); + // TODO(Derppening): #444 if let TypeEnum::TVar { id, .. } = ty.as_ref() { mapping.contains_key(id) } else { @@ -1526,8 +1529,22 @@ impl Unifier { Ok(None) } } - (TList { ty: ty1 }, TList { ty: ty2 }) => { - Ok(self.get_intersection(*ty1, *ty2)?.map(|ty| self.add_ty(TList { ty }))) + // TODO(Derppening): #444 + ( + TObj { obj_id: id1, fields, params: params1 }, + TObj { obj_id: id2, params: params2, .. }, + ) if *id1 == PrimDef::List.id() && *id2 == PrimDef::List.id() => { + let tv_id = iter_type_vars(params1).nth(0).unwrap().id; + let ty1 = iter_type_vars(params1).nth(0).unwrap().ty; + let ty2 = iter_type_vars(params2).nth(0).unwrap().ty; + + Ok(self.get_intersection(ty1, ty2)?.map(|ty| { + self.add_ty(TObj { + obj_id: *id1, + fields: fields.clone(), + params: into_var_map([TypeVar { id: tv_id, ty }]), + }) + })) } (TVirtual { ty: ty1 }, TVirtual { ty: ty2 }) => { Ok(self.get_intersection(*ty1, *ty2)?.map(|ty| self.add_ty(TVirtual { ty }))) diff --git a/nac3core/src/typecheck/typedef/test.rs b/nac3core/src/typecheck/typedef/test.rs index d56a97d80..451f5f01a 100644 --- a/nac3core/src/typecheck/typedef/test.rs +++ b/nac3core/src/typecheck/typedef/test.rs @@ -32,10 +32,7 @@ impl Unifier { 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::TVirtual { ty: ty1 }, TypeEnum::TVirtual { ty: ty2 }) => self.eq(*ty1, *ty2), ( TypeEnum::TObj { obj_id: id1, params: params1, .. }, TypeEnum::TObj { obj_id: id2, params: params2, .. }, @@ -119,6 +116,15 @@ impl TestEnvironment { params: into_var_map([tvar]), }), ); + let tvar = unifier.get_dummy_var(); + type_mapping.insert( + "list".into(), + unifier.add_ty(TypeEnum::TObj { + obj_id: PrimDef::List.id(), + fields: HashMap::new(), + params: into_var_map([tvar]), + }), + ); TestEnvironment { unifier, type_mapping } } @@ -133,6 +139,36 @@ impl TestEnvironment { // for testing only, so we can just panic when the input is malformed let end = typ.find(|c| ['[', ',', ']', '='].contains(&c)).unwrap_or(typ.len()); match &typ[..end] { + "list" => { + let mut s = &typ[end..]; + assert_eq!(&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; + } + + assert_eq!(ty.len(), 1); + + let list_elem_tvar = if let TypeEnum::TObj { params, .. } = + &*self.unifier.get_ty_immutable(self.type_mapping["list"]) + { + iter_type_vars(params).next().unwrap() + } else { + unreachable!() + }; + + ( + self.unifier + .subst( + self.type_mapping["list"], + &into_var_map([TypeVar { id: list_elem_tvar.id, ty: ty[0] }]), + ) + .unwrap(), + &s[1..], + ) + } "tuple" => { let mut s = &typ[end..]; assert_eq!(&s[0..1], "["); @@ -144,12 +180,6 @@ impl TestEnvironment { } (self.unifier.add_ty(TypeEnum::TTuple { ty }), &s[1..]) } - "list" => { - assert_eq!(&typ[end..=end], "["); - let (ty, s) = self.internal_parse(&typ[end + 1..], mapping); - assert_eq!(&s[0..1], "]"); - (self.unifier.add_ty(TypeEnum::TList { ty }), &s[1..]) - } "Record" => { let mut s = &typ[end..]; assert_eq!(&s[0..1], "["); @@ -274,7 +304,7 @@ fn test_unify( ("v1", "tuple[int]"), ("v2", "list[int]"), ], - (("v1", "v2"), "Incompatible types: list[0] and tuple[0]") + (("v1", "v2"), "Incompatible types: 11[0] and tuple[0]") ; "type mismatch" )] #[test_case(2, @@ -298,7 +328,7 @@ fn test_unify( ("v1", "Record[a=float,b=int]"), ("v2", "Foo[v3]"), ], - (("v1", "v2"), "`3[typevar4]::b` field/method does not exist") + (("v1", "v2"), "`3[typevar5]::b` field/method does not exist") ; "record obj merge" )] /// Test cases for invalid unifications. @@ -388,6 +418,14 @@ fn test_typevar_range() { let int_list = env.parse("list[int]", &HashMap::new()); let float_list = env.parse("list[float]", &HashMap::new()); + let list_elem_tvar = if let TypeEnum::TObj { params, .. } = + &*env.unifier.get_ty_immutable(env.type_mapping["list"]) + { + iter_type_vars(params).next().unwrap() + } else { + unreachable!() + }; + // unification between v and int // where v in (int, bool) let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).ty; @@ -398,7 +436,7 @@ fn test_typevar_range() { let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).ty; assert_eq!( env.unify(int_list, v), - Err("Expected any one of these types: 0, 2, but got list[0]".to_string()) + Err("Expected any one of these types: 0, 2, but got 11[0]".to_string()) ); // unification between v and float @@ -410,7 +448,11 @@ fn test_typevar_range() { ); let v1 = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).ty; - let v1_list = env.unifier.add_ty(TypeEnum::TList { ty: v1 }); + let v1_list = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: v1 }]), + }); let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).ty; // unification between v and int // where v in (int, list[v1]), v1 in (int, bool) @@ -424,9 +466,10 @@ fn test_typevar_range() { let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).ty; // unification between v and list[float] // where v in (int, list[v1]), v1 in (int, bool) + println!("float_list: {}, v: {}", env.unifier.stringify(float_list), env.unifier.stringify(v)); assert_eq!( env.unify(float_list, v), - Err("Expected any one of these types: 0, list[typevar5], but got list[1]\n\nNotes:\n typevar5 ∈ {0, 2}".to_string()) + Err("Expected any one of these types: 0, 11[typevar6], but got 11[1]\n\nNotes:\n typevar6 ∈ {0, 2}".to_string()) ); let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).ty; @@ -441,34 +484,66 @@ fn test_typevar_range() { let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).ty; let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).ty; - let a_list = env.unifier.add_ty(TypeEnum::TList { ty: a }); + let a_list = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: a }]), + }); let a_list = env.unifier.get_fresh_var_with_range(&[a_list], None, None).ty; - let b_list = env.unifier.add_ty(TypeEnum::TList { ty: b }); + let b_list = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: b }]), + }); let b_list = env.unifier.get_fresh_var_with_range(&[b_list], None, None).ty; env.unifier.unify(a_list, b_list).unwrap(); - let float_list = env.unifier.add_ty(TypeEnum::TList { ty: float }); + let float_list = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: float }]), + }); env.unifier.unify(a_list, float_list).unwrap(); // previous unifications should not affect a and b env.unifier.unify(a, int).unwrap(); let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).ty; let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).ty; - let a_list = env.unifier.add_ty(TypeEnum::TList { ty: a }); - let b_list = env.unifier.add_ty(TypeEnum::TList { ty: b }); + let a_list = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: a }]), + }); + let b_list = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: b }]), + }); env.unifier.unify(a_list, b_list).unwrap(); - let int_list = env.unifier.add_ty(TypeEnum::TList { ty: int }); + let int_list = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: int }]), + }); assert_eq!( env.unify(a_list, int_list), - Err("Incompatible types: list[typevar22] and list[0]\ - \n\nNotes:\n typevar22 ∈ {1}" + Err("Incompatible types: 11[typevar23] and 11[0]\ + \n\nNotes:\n typevar23 ∈ {1}" .into()) ); let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).ty; let b = env.unifier.get_dummy_var().ty; - let a_list = env.unifier.add_ty(TypeEnum::TList { ty: a }); + let a_list = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: a }]), + }); let a_list = env.unifier.get_fresh_var_with_range(&[a_list], None, None).ty; - let b_list = env.unifier.add_ty(TypeEnum::TList { ty: b }); + let b_list = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: b }]), + }); env.unifier.unify(a_list, b_list).unwrap(); assert_eq!( env.unify(b, boolean), @@ -482,16 +557,25 @@ fn test_rigid_var() { let a = env.unifier.get_fresh_rigid_var(None, None).ty; let b = env.unifier.get_fresh_rigid_var(None, None).ty; let x = env.unifier.get_dummy_var().ty; - let list_a = env.unifier.add_ty(TypeEnum::TList { ty: a }); - let list_x = env.unifier.add_ty(TypeEnum::TList { ty: x }); + let list_elem_tvar = env.unifier.get_fresh_var(Some("list_elem".into()), None); + let list_a = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: a }]), + }); + let list_x = env.unifier.add_ty(TypeEnum::TObj { + obj_id: env.type_mapping["list"].obj_id(&env.unifier).unwrap(), + fields: Mapping::default(), + params: into_var_map([TypeVar { id: list_elem_tvar.id, ty: x }]), + }); let int = env.parse("int", &HashMap::new()); let list_int = env.parse("list[int]", &HashMap::new()); - assert_eq!(env.unify(a, b), Err("Incompatible types: typevar3 and typevar2".to_string())); + assert_eq!(env.unify(a, b), Err("Incompatible types: typevar4 and typevar3".to_string())); env.unifier.unify(list_a, list_x).unwrap(); assert_eq!( env.unify(list_x, list_int), - Err("Incompatible types: list[typevar2] and list[0]".to_string()) + Err("Incompatible types: 11[typevar3] and 11[0]".to_string()) ); env.unifier.replace_rigid_var(a, int); @@ -506,10 +590,21 @@ fn test_instantiation() { let float = env.parse("float", &HashMap::new()); let list_int = env.parse("list[int]", &HashMap::new()); - let obj_map: HashMap<_, _> = [(0usize, "int"), (1, "float"), (2, "bool")].into(); + let list_elem_tvar = if let TypeEnum::TObj { params, .. } = + &*env.unifier.get_ty_immutable(env.type_mapping["list"]) + { + iter_type_vars(params).next().unwrap() + } else { + unreachable!() + }; + + let obj_map: HashMap<_, _> = [(0usize, "int"), (1, "float"), (2, "bool"), (11, "list")].into(); let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).ty; - let list_v = env.unifier.add_ty(TypeEnum::TList { ty: v }); + let list_v = env + .unifier + .subst(env.type_mapping["list"], &into_var_map([TypeVar { id: list_elem_tvar.id, ty: v }])) + .unwrap(); let v1 = env.unifier.get_fresh_var_with_range(&[list_v, int], None, None).ty; let v2 = env.unifier.get_fresh_var_with_range(&[list_int, float], None, None).ty; let t = env.unifier.get_dummy_var().ty; @@ -536,7 +631,7 @@ fn test_instantiation() { tuple[int, list[bool], list[int]] tuple[int, list[int], float] tuple[int, list[int], list[int]] - v5" + v6" } .split('\n') .collect_vec();