use inkwell::{types::BasicType, values::BasicValueEnum, AddressSpace}; use nac3core::{ codegen::CodeGenContext, location::Location, symbol_resolver::{StaticValue, SymbolResolver, SymbolValue, ValueEnum}, toplevel::{DefinitionId, TopLevelDef}, typecheck::{ type_inferencer::PrimitiveStore, typedef::{Type, TypeEnum, Unifier}, }, }; use nac3parser::ast::{self, StrRef}; use parking_lot::{Mutex, RwLock}; use pyo3::{ types::{PyList, PyModule, PyTuple}, PyAny, PyObject, PyResult, Python, }; use std::{ cell::RefCell, collections::{HashMap, HashSet}, sync::Arc, }; use crate::PrimitivePythonId; pub struct InnerResolver { pub id_to_type: Mutex>, pub id_to_def: Mutex>, pub global_value_ids: Arc>>, pub class_names: Mutex>, pub pyid_to_def: Arc>>, pub pyid_to_type: Arc>>, pub primitive_ids: PrimitivePythonId, pub helper: PythonHelper, // module specific pub name_to_pyid: HashMap, pub module: PyObject, } pub struct Resolver(pub Arc); pub struct PythonHelper { pub type_fn: PyObject, pub len_fn: PyObject, pub id_fn: PyObject, pub origin_ty_fn: PyObject, pub args_ty_fn: PyObject, } struct PythonValue { id: u64, value: PyObject, resolver: Arc, } impl StaticValue for PythonValue { fn get_unique_identifier(&self) -> u64 { self.id } fn to_basic_value_enum<'ctx, 'a>( &self, ctx: &mut CodeGenContext<'ctx, 'a>, ) -> BasicValueEnum<'ctx> { Python::with_gil(|py| -> PyResult> { self.resolver .get_obj_value(py, self.value.as_ref(py), ctx) .map(Option::unwrap) }) .unwrap() } fn get_field<'ctx, 'a>( &self, name: StrRef, ctx: &mut CodeGenContext<'ctx, 'a>, ) -> Option> { Python::with_gil(|py| -> PyResult>> { let helper = &self.resolver.helper; let ty = helper.type_fn.call1(py, (&self.value,))?; let ty_id: u64 = helper.id_fn.call1(py, (ty,))?.extract(py)?; let def_id = { *self.resolver.pyid_to_def.read().get(&ty_id).unwrap() }; let mut mutable = true; let defs = ctx.top_level.definitions.read(); if let TopLevelDef::Class { fields, .. } = &*defs[def_id.0].read() { for (field_name, _, is_mutable) in fields.iter() { if field_name == &name { mutable = *is_mutable; break; } } } Ok(if mutable { None } else { let obj = self.value.getattr(py, &name.to_string())?; let id = self.resolver.helper.id_fn.call1(py, (&obj,))?.extract(py)?; Some(ValueEnum::Static(Arc::new(PythonValue { id, value: obj, resolver: self.resolver.clone(), }))) }) }) .unwrap() } } impl InnerResolver { fn get_list_elem_type( &self, py: Python, list: &PyAny, len: usize, unifier: &mut Unifier, defs: &[Arc>], primitives: &PrimitiveStore, ) -> PyResult> { let first = self.get_obj_type(py, list.get_item(0)?, unifier, defs, primitives)?; Ok((1..len).fold(first, |a, i| { let b = list .get_item(i) .map(|elem| self.get_obj_type(py, elem, unifier, defs, primitives)); a.and_then(|a| { if let Ok(Ok(Some(ty))) = b { if unifier.unify(a, ty).is_ok() { Some(a) } else { None } } else { None } }) })) } // handle python objects that represent types themselves // primitives and class types should be themselves, use `ty_id` to check, // TypeVars and GenericAlias(`A[int, bool]`) should use `ty_ty_id` to check // the `bool` value returned indicates whether they are instantiated or not fn get_pyty_obj_type( &self, py: Python, pyty: &PyAny, unifier: &mut Unifier, defs: &[Arc>], primitives: &PrimitiveStore, ) -> PyResult> { let ty_id: u64 = self .helper .id_fn .call1(py, (pyty,))? .extract(py)?; let ty_ty_id: u64 = self .helper .id_fn .call1(py, (self.helper.type_fn.call1(py, (pyty,))?,))? .extract(py)?; if ty_id == self.primitive_ids.int || ty_id == self.primitive_ids.int32 { Ok(Ok((primitives.int32, true))) } else if ty_id == self.primitive_ids.int64 { Ok(Ok((primitives.int64, true))) } else if ty_id == self.primitive_ids.bool { Ok(Ok((primitives.bool, true))) } else if ty_id == self.primitive_ids.float { Ok(Ok((primitives.float, true))) } else if ty_id == self.primitive_ids.list { // do not handle type var param and concrete check here let var = unifier.get_fresh_var().0; let list = unifier.add_ty(TypeEnum::TList { ty: var }); Ok(Ok((list, false))) } else if ty_id == self.primitive_ids.tuple { // do not handle type var param and concrete check here Ok(Ok((unifier.add_ty(TypeEnum::TTuple { ty: vec![] }), false))) } else if let Some(def_id) = self.pyid_to_def.read().get(&ty_id).cloned() { let def = defs[def_id.0].read(); if let TopLevelDef::Class { object_id, type_vars, fields, methods, .. } = &*def { // do not handle type var param and concrete check here, and no subst Ok(Ok({ let ty = TypeEnum::TObj { obj_id: *object_id, params: RefCell::new({ type_vars .iter() .map(|x| { if let TypeEnum::TVar { id, .. } = &*unifier.get_ty(*x) { (*id, *x) } else { unreachable!() } }).collect() }), fields: RefCell::new({ let mut res = methods .iter() .map(|(iden, ty, _)| (*iden, (*ty, false))) .collect::>(); res.extend(fields.clone().into_iter().map(|x| (x.0, (x.1, x.2)))); res }) }; // here also false, later instantiation use python object to check compatible (unifier.add_ty(ty), false) })) } else { // only object is supported, functions are not supported unreachable!("function type is not supported, should not be queried") } } else if ty_ty_id == self.primitive_ids.typevar { let constraint_types = { let constraints = pyty.getattr("__constraints__").unwrap(); let mut result: Vec = vec![]; for i in 0.. { if let Ok(constr) = constraints.get_item(i) { result.push({ match self.get_pyty_obj_type(py, constr, unifier, defs, primitives)? { Ok((ty, _)) => { if unifier.is_concrete(ty, &[]) { ty } else { return Ok(Err(format!( "the {}th constraint of TypeVar `{}` is not concrete", i + 1, pyty.getattr("__name__")?.extract::()? ))) } }, Err(err) => return Ok(Err(err)) } }) } else { break; } } result }; let res = unifier.get_fresh_var_with_range(&constraint_types).0; Ok(Ok((res, true))) } else if ty_ty_id == self.primitive_ids.generic_alias.0 || ty_ty_id == self.primitive_ids.generic_alias.1 { let origin = self.helper.origin_ty_fn.call1(py, (pyty,))?; let args = self.helper.args_ty_fn.call1(py, (pyty,))?; let args: &PyTuple = args.cast_as(py)?; let origin_ty = match self.get_pyty_obj_type(py, origin.as_ref(py), unifier, defs, primitives)? { Ok((ty, false)) => ty, Ok((_, true)) => return Ok(Err("instantiated type does not take type parameters".into())), Err(err) => return Ok(Err(err)) }; match &*unifier.get_ty(origin_ty) { TypeEnum::TList { .. } => { if args.len() == 1 { let ty = match self.get_pyty_obj_type(py, args.get_item(0), unifier, defs, primitives)? { Ok(ty) => ty, Err(err) => return Ok(Err(err)) }; if !unifier.is_concrete(ty.0, &[]) && !ty.1 { panic!("type list should take concrete parameters in type var ranges") } Ok(Ok((unifier.add_ty(TypeEnum::TList { ty: ty.0 }), true))) } else { return Ok(Err(format!("type list needs exactly 1 type parameters, found {}", args.len()))) } }, TypeEnum::TTuple { .. } => { let args = match args .iter() .map(|x| self.get_pyty_obj_type(py, x, unifier, defs, primitives)) .collect::, _>>()? .into_iter() .collect::, _>>() { Ok(args) if !args.is_empty() => args .into_iter() .map(|(x, check)| if !unifier.is_concrete(x, &[]) && !check { panic!("type tuple should take concrete parameters in type var ranges") } else { x } ) .collect::>(), Err(err) => return Ok(Err(err)), _ => return Ok(Err("tuple type needs at least 1 type parameters".to_string())) }; Ok(Ok((unifier.add_ty(TypeEnum::TTuple { ty: args }), true))) }, TypeEnum::TObj { params, obj_id, .. } => { let subst = { let params = &*params.borrow(); if params.len() != args.len() { return Ok(Err(format!( "for class #{}, expect {} type parameters, got {}.", obj_id.0, params.len(), args.len(), ))) } let args = match args .iter() .map(|x| self.get_pyty_obj_type(py, x, unifier, defs, primitives)) .collect::, _>>()? .into_iter() .collect::, _>>() { Ok(args) => args .into_iter() .map(|(x, check)| if !unifier.is_concrete(x, &[]) && !check { panic!("type class should take concrete parameters in type var ranges") } else { x } ) .collect::>(), Err(err) => return Ok(Err(err)), }; params .iter() .zip(args.iter()) .map(|((id, _), ty)| (*id, *ty)) .collect::>() }; Ok(Ok((unifier.subst(origin_ty, &subst).unwrap_or(origin_ty), true))) }, TypeEnum::TVirtual { .. } => { if args.len() == 1 { let ty = match self.get_pyty_obj_type(py, args.get_item(0), unifier, defs, primitives)? { Ok(ty) => ty, Err(err) => return Ok(Err(err)) }; if !unifier.is_concrete(ty.0, &[]) && !ty.1 { panic!("virtual class should take concrete parameters in type var ranges") } Ok(Ok((unifier.add_ty(TypeEnum::TVirtual { ty: ty.0 }), true))) } else { return Ok(Err(format!("virtual class needs exactly 1 type parameters, found {}", args.len()))) } } _ => unimplemented!() } } else if ty_id == self.primitive_ids.virtual_id { Ok(Ok(({ let ty = TypeEnum::TVirtual { ty: unifier.get_fresh_var().0 }; unifier.add_ty(ty) }, false))) } else { Ok(Err("unknown type".into())) } } fn get_obj_type( &self, py: Python, obj: &PyAny, unifier: &mut Unifier, defs: &[Arc>], primitives: &PrimitiveStore, ) -> PyResult> { let ty = self.helper.type_fn.call1(py, (obj,)).unwrap(); let (extracted_ty, inst_check) = match self.get_pyty_obj_type( py, { if [self.primitive_ids.typevar, self.primitive_ids.generic_alias.0, self.primitive_ids.generic_alias.1 ].contains(&self.helper.id_fn.call1(py, (ty.clone(),))?.extract::(py)?) { obj } else { ty.as_ref(py) } }, unifier, defs, primitives )? { Ok(s) => s, Err(_) => return Ok(None) }; return match (&*unifier.get_ty(extracted_ty), inst_check) { // do the instantiation for these three types (TypeEnum::TList { ty }, false) => { let len: usize = self.helper.len_fn.call1(py, (obj,))?.extract(py)?; if len == 0 { assert!(matches!( &*unifier.get_ty(extracted_ty), TypeEnum::TVar { meta: nac3core::typecheck::typedef::TypeVarMeta::Generic, range, .. } if range.borrow().is_empty() )); Ok(Some(extracted_ty)) } else { let actual_ty = self .get_list_elem_type(py, obj, len, unifier, defs, primitives)?; if let Some(actual_ty) = actual_ty { unifier.unify(*ty, actual_ty).unwrap(); Ok(Some(extracted_ty)) } else { Ok(None) } } } (TypeEnum::TTuple { .. }, false) => { let elements: &PyTuple = obj.cast_as()?; let types: Result>, _> = elements .iter() .map(|elem| self.get_obj_type(py, elem, unifier, defs, primitives)) .collect(); let types = types?; Ok(types.map(|types| unifier.add_ty(TypeEnum::TTuple { ty: types }))) } (TypeEnum::TObj { params, fields, .. }, false) => { let var_map = params .borrow() .iter() .map(|(id_var, ty)| { if let TypeEnum::TVar { id, range, .. } = &*unifier.get_ty(*ty) { assert_eq!(*id, *id_var); (*id, unifier.get_fresh_var_with_range(&range.borrow()).0) } else { unreachable!() } }) .collect::>(); // loop through non-function fields of the class to get the instantiated value for field in fields.borrow().iter() { let name: String = (*field.0).into(); if let TypeEnum::TFunc( .. ) = &*unifier.get_ty(field.1.0) { continue; } else { let field_data = obj.getattr(&name)?; let ty = self .get_obj_type(py, field_data, unifier, defs, primitives)? .unwrap_or(primitives.none); let field_ty = unifier.subst(field.1.0, &var_map).unwrap_or(field.1.0); if unifier.unify(ty, field_ty).is_err() { // field type mismatch return Ok(None); } } } for (_, ty) in var_map.iter() { // must be concrete type if !unifier.is_concrete(*ty, &[]) { return Ok(None) } } return Ok(Some(unifier.subst(extracted_ty, &var_map).unwrap_or(extracted_ty))); } _ => Ok(Some(extracted_ty)) }; } fn get_obj_value<'ctx, 'a>( &self, py: Python, obj: &PyAny, ctx: &mut CodeGenContext<'ctx, 'a>, ) -> PyResult>> { let ty_id: u64 = self .helper .id_fn .call1(py, (self.helper.type_fn.call1(py, (obj,))?,))? .extract(py)?; if ty_id == self.primitive_ids.int || ty_id == self.primitive_ids.int32 { let val: i32 = obj.extract()?; Ok(Some(ctx.ctx.i32_type().const_int(val as u64, false).into())) } else if ty_id == self.primitive_ids.int64 { let val: i64 = obj.extract()?; Ok(Some(ctx.ctx.i64_type().const_int(val as u64, false).into())) } else if ty_id == self.primitive_ids.bool { let val: bool = obj.extract()?; Ok(Some( ctx.ctx.bool_type().const_int(val as u64, false).into(), )) } else if ty_id == self.primitive_ids.float { let val: f64 = obj.extract()?; Ok(Some(ctx.ctx.f64_type().const_float(val).into())) } else if ty_id == self.primitive_ids.list { let id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?; let id_str = id.to_string(); let len: usize = self.helper.len_fn.call1(py, (obj,))?.extract(py)?; let ty = if len == 0 { ctx.primitives.int32 } else { self.get_list_elem_type( py, obj, len, &mut ctx.unifier, &ctx.top_level.definitions.read(), &ctx.primitives, )? .unwrap() }; let ty = ctx.get_llvm_type(ty); let arr_ty = ctx.ctx.struct_type( &[ ctx.ctx.i32_type().into(), ty.ptr_type(AddressSpace::Generic).into(), ], false, ); { let mut global_value_ids = self.global_value_ids.lock(); if global_value_ids.contains(&id) { let global = ctx.module.get_global(&id_str).unwrap_or_else(|| { ctx.module .add_global(arr_ty, Some(AddressSpace::Generic), &id_str) }); return Ok(Some(global.as_pointer_value().into())); } else { global_value_ids.insert(id); } } let arr: Result>, _> = (0..len) .map(|i| { obj.get_item(i) .and_then(|elem| self.get_obj_value(py, elem, ctx)) }) .collect(); let arr = arr?.unwrap(); let arr_global = ctx.module.add_global( ty.array_type(len as u32), Some(AddressSpace::Generic), &(id_str.clone() + "_"), ); let arr: BasicValueEnum = if ty.is_int_type() { let arr: Vec<_> = arr .into_iter() .map(BasicValueEnum::into_int_value) .collect(); ty.into_int_type().const_array(&arr) } else if ty.is_float_type() { let arr: Vec<_> = arr .into_iter() .map(BasicValueEnum::into_float_value) .collect(); ty.into_float_type().const_array(&arr) } else if ty.is_array_type() { let arr: Vec<_> = arr .into_iter() .map(BasicValueEnum::into_array_value) .collect(); ty.into_array_type().const_array(&arr) } else if ty.is_struct_type() { let arr: Vec<_> = arr .into_iter() .map(BasicValueEnum::into_struct_value) .collect(); ty.into_struct_type().const_array(&arr) } else if ty.is_pointer_type() { let arr: Vec<_> = arr .into_iter() .map(BasicValueEnum::into_pointer_value) .collect(); ty.into_pointer_type().const_array(&arr) } else { unreachable!() } .into(); arr_global.set_initializer(&arr); let val = arr_ty.const_named_struct(&[ ctx.ctx.i32_type().const_int(len as u64, false).into(), arr_global .as_pointer_value() .const_cast(ty.ptr_type(AddressSpace::Generic)) .into(), ]); let global = ctx .module .add_global(arr_ty, Some(AddressSpace::Generic), &id_str); global.set_initializer(&val); Ok(Some(global.as_pointer_value().into())) } else if ty_id == self.primitive_ids.tuple { let id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?; let id_str = id.to_string(); let elements: &PyTuple = obj.cast_as()?; let types: Result>, _> = elements .iter() .map(|elem| { self.get_obj_type( py, elem, &mut ctx.unifier, &ctx.top_level.definitions.read(), &ctx.primitives, ) .map(|ty| ty.map(|ty| ctx.get_llvm_type(ty))) }) .collect(); let types = types?.unwrap(); let ty = ctx.ctx.struct_type(&types, false); { let mut global_value_ids = self.global_value_ids.lock(); if global_value_ids.contains(&id) { let global = ctx.module.get_global(&id_str).unwrap_or_else(|| { ctx.module .add_global(ty, Some(AddressSpace::Generic), &id_str) }); return Ok(Some(global.as_pointer_value().into())); } else { global_value_ids.insert(id); } } let val: Result>, _> = elements .iter() .map(|elem| self.get_obj_value(py, elem, ctx)) .collect(); let val = val?.unwrap(); let val = ctx.ctx.const_struct(&val, false); let global = ctx .module .add_global(ty, Some(AddressSpace::Generic), &id_str); global.set_initializer(&val); Ok(Some(global.as_pointer_value().into())) } else { let id: u64 = self.helper.id_fn.call1(py, (obj,))?.extract(py)?; let id_str = id.to_string(); let top_level_defs = ctx.top_level.definitions.read(); let ty = self .get_obj_type(py, obj, &mut ctx.unifier, &top_level_defs, &ctx.primitives)? .unwrap(); let ty = ctx .get_llvm_type(ty) .into_pointer_type() .get_element_type() .into_struct_type() .as_basic_type_enum(); { let mut global_value_ids = self.global_value_ids.lock(); if global_value_ids.contains(&id) { let global = ctx.module.get_global(&id_str).unwrap_or_else(|| { ctx.module .add_global(ty, Some(AddressSpace::Generic), &id_str) }); return Ok(Some(global.as_pointer_value().into())); } else { global_value_ids.insert(id); } } // should be classes let definition = top_level_defs .get(self.pyid_to_def.read().get(&ty_id).unwrap().0) .unwrap() .read(); if let TopLevelDef::Class { fields, .. } = &*definition { let values: Result>, _> = fields .iter() .map(|(name, _, _)| { self.get_obj_value(py, obj.getattr(&name.to_string())?, ctx) }) .collect(); let values = values?; if let Some(values) = values { let val = ctx.ctx.const_struct(&values, false); let global = ctx .module .add_global(ty, Some(AddressSpace::Generic), &id_str); global.set_initializer(&val); Ok(Some(global.as_pointer_value().into())) } else { Ok(None) } } else { unreachable!() } } } fn get_default_param_obj_value( &self, py: Python, obj: &PyAny, ) -> PyResult> { let ty_id: u64 = self .helper .id_fn .call1(py, (self.helper.type_fn.call1(py, (obj,))?,))? .extract(py)?; Ok( if ty_id == self.primitive_ids.int || ty_id == self.primitive_ids.int32 { let val: i32 = obj.extract()?; Ok(SymbolValue::I32(val)) } else if ty_id == self.primitive_ids.int64 { let val: i64 = obj.extract()?; Ok(SymbolValue::I64(val)) } else if ty_id == self.primitive_ids.bool { let val: bool = obj.extract()?; Ok(SymbolValue::Bool(val)) } else if ty_id == self.primitive_ids.float { let val: f64 = obj.extract()?; Ok(SymbolValue::Double(val)) } else if ty_id == self.primitive_ids.tuple { let elements: &PyTuple = obj.cast_as()?; let elements: Result, String>, _> = elements .iter() .map(|elem| self.get_default_param_obj_value(py, elem)) .collect(); let elements = match elements? { Ok(el) => el, Err(err) => return Ok(Err(err)), }; Ok(SymbolValue::Tuple(elements)) } else { Err("only primitives values and tuple can be default parameter value".into()) }, ) } } impl SymbolResolver for Resolver { fn get_default_param_value(&self, expr: &ast::Expr) -> Option { match &expr.node { ast::ExprKind::Name { id, .. } => { Python::with_gil(|py| -> PyResult> { let obj: &PyAny = self.0.module.extract(py)?; let members: &PyList = PyModule::import(py, "inspect")? .getattr("getmembers")? .call1((obj,))? .cast_as()?; let mut sym_value = None; for member in members.iter() { let key: &str = member.get_item(0)?.extract()?; let val = member.get_item(1)?; if key == id.to_string() { sym_value = Some( self.0 .get_default_param_obj_value(py, val) .unwrap() .unwrap(), ); break; } } Ok(sym_value) }) .unwrap() } _ => unimplemented!("other type of expr not supported at {}", expr.location), } } fn get_symbol_type( &self, unifier: &mut Unifier, defs: &[Arc>], primitives: &PrimitiveStore, str: StrRef, ) -> Option { let mut id_to_type = self.0.id_to_type.lock(); id_to_type.get(&str).cloned().or_else(|| { let py_id = self.0.name_to_pyid.get(&str); let result = py_id.and_then(|id| { self.0.pyid_to_type.read().get(id).copied().or_else(|| { Python::with_gil(|py| -> PyResult> { let obj: &PyAny = self.0.module.extract(py)?; let members: &PyList = PyModule::import(py, "inspect")? .getattr("getmembers")? .call1((obj,))? .cast_as()?; let mut sym_ty = None; for member in members.iter() { let key: &str = member.get_item(0)?.extract()?; if key == str.to_string() { sym_ty = self.0.get_obj_type( py, member.get_item(1)?, unifier, defs, primitives, )?; break; } } Ok(sym_ty) }) .unwrap() }) }); if let Some(result) = &result { id_to_type.insert(str, *result); } result }) } fn get_symbol_value<'ctx, 'a>( &self, id: StrRef, _: &mut CodeGenContext<'ctx, 'a>, ) -> Option> { Python::with_gil(|py| -> PyResult>> { let obj: &PyAny = self.0.module.extract(py)?; let members: &PyList = PyModule::import(py, "inspect")? .getattr("getmembers")? .call1((obj,))? .cast_as()?; let mut sym_value = None; for member in members.iter() { let key: &str = member.get_item(0)?.extract()?; let val = member.get_item(1)?; if key == id.to_string() { let id = self.0.helper.id_fn.call1(py, (val,))?.extract(py)?; sym_value = Some(PythonValue { id, value: val.extract()?, resolver: self.0.clone(), }); break; } } Ok(sym_value.map(|v| ValueEnum::Static(Arc::new(v)))) }) .unwrap() } fn get_symbol_location(&self, _: StrRef) -> Option { unimplemented!() } fn get_identifier_def(&self, id: StrRef) -> Option { let mut id_to_def = self.0.id_to_def.lock(); id_to_def.get(&id).cloned().or_else(|| { let py_id = self.0.name_to_pyid.get(&id); let result = py_id.and_then(|id| self.0.pyid_to_def.read().get(id).copied()); if let Some(result) = &result { id_to_def.insert(id, *result); } result }) } }