use std::fmt::Debug; /// Alias for &TypeStruct to reduce repetition /// and make futura migration to interning /// easier pub type Type = InternedType; #[derive(Copy, Clone, PartialEq)] pub struct InternedType(usize); impl Debug for InternedType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Type({})", self.0) } } #[derive(Debug)] pub struct TypeStruct { kind: TypeKind, } #[derive(Debug)] enum TypeKind { /// Elaboration-time types ElabType(ElabKind), /// Signal/Wire of generic width Logic(ElabData), /// UInt of generic width UInt(ElabData), /// Callable Callable(FnSig), /// A type that was not given and needs to be inferred Infer, /// A reference to a type variable as DeBruijn index /// (scope, param) TypeVar(u32, u32), } #[derive(Debug, Clone)] pub struct FnSig { params: Vec, ret: Type, } #[derive(Debug, Clone)] pub struct ElabData { typ: Type, value: ElabValue, } #[derive(Debug, Clone)] enum ElabValue { /// the value is not given and has to be inferred Infer, /// the value is given as some byte representation Concrete(ElabValueData), } #[derive(Debug, Clone)] enum ElabValueData { U32(u32), Bytes(Vec), } /// Types that are only valid during Elaboration #[derive(Debug)] enum ElabKind { /// general, unsized number type Num, } #[derive(Debug)] pub enum GenericArg { Elab(ElabData), Type(Type), } #[derive(Debug)] pub enum InferenceResult { /// The first type was inferred First(Type), /// The second type was inferred Second(Type), /// A typevar was inferred TypeVar(u32, u32, Type), /// The types were incompatible Incompatible, /// Neither of the types were complete Ambigous, } pub struct PrimitiveTypes { pub elabnum: Type, pub logic: Type, pub infer: Type, } pub struct TypingContext { types: Vec, pub primitives: PrimitiveTypes, } impl TypingContext { pub fn new() -> Self { let primitives = PrimitiveTypes { elabnum: InternedType(0), logic: InternedType(1), infer: InternedType(2), }; Self { types: vec![ TypeStruct { kind: TypeKind::ElabType(ElabKind::Num), }, TypeStruct { kind: TypeKind::Logic(ElabData { typ: primitives.elabnum, value: ElabValue::Infer, }), }, TypeStruct { kind: TypeKind::Infer, }, ], primitives, } } pub fn add(&mut self, typ: TypeStruct) -> Type { let id = self.types.len(); self.types.push(typ); InternedType(id) } pub fn get(&self, typ: Type) -> &TypeStruct { &self.types[typ.0] } pub fn make_elabnum_u32(&self, num: u32) -> ElabData { ElabData { typ: self.primitives.elabnum, value: ElabValue::Concrete(ElabValueData::U32(num)), } } pub fn make_logic_size(&mut self, width: u32) -> Type { let widthnum = self.make_elabnum_u32(width); self.parameterize(self.primitives.logic, &[GenericArg::Elab(widthnum)]) .unwrap() } pub fn make_typevar(&mut self, dbi: u32, tvar: u32) -> Type { self.add(TypeStruct { kind: TypeKind::TypeVar(dbi, tvar), }) } pub fn get_width(&self, typ: Type) -> Option { match &self.get(typ).kind { TypeKind::ElabType(_) => None, TypeKind::Logic(data) => match &data.value { ElabValue::Infer => None, ElabValue::Concrete(val) => match val { ElabValueData::U32(val) => Some(*val), ElabValueData::Bytes(_) => None, }, }, TypeKind::UInt(_) => todo!(), TypeKind::Callable(_) => None, TypeKind::Infer => None, TypeKind::TypeVar(_, _) => None, } } pub fn parameterize(&mut self, typ: Type, params: &[GenericArg]) -> Option { // TODO: return proper error type here match &self.get(typ).kind { // Elab types have no params yet TypeKind::ElabType(_) => None, TypeKind::Logic(_) => { if params.len() != 1 { // invalid number of typeargs return None; }; let param = ¶ms[0]; if let GenericArg::Elab(data) = param { if data.typ == self.primitives.elabnum { Some(self.add(TypeStruct { kind: TypeKind::Logic(data.clone()), })) } else { // arg must be elabnum None } } else { // arg must be an elab value None } } TypeKind::UInt(_) => todo!(), TypeKind::Callable(_sig) => todo!("callable generic params"), // need to know what the type is to parameterize it TypeKind::Infer | &TypeKind::TypeVar(_, _) => None, } } /// Given the type of a variable in two locations, infer what the true type should be pub fn infer_type(&mut self, typ_a: Type, typ_b: Type) -> InferenceResult { match (&self.get(typ_a).kind, &self.get(typ_b).kind) { (TypeKind::TypeVar(dbi, tvar), _) => InferenceResult::TypeVar(*dbi, *tvar, typ_b), (_, TypeKind::TypeVar(dbi, tvar)) => InferenceResult::TypeVar(*dbi, *tvar, typ_a), (a, b) => { let a_full = self.is_fully_typed_kind(a); let b_full = self.is_fully_typed_kind(b); if a_full && b_full { InferenceResult::Incompatible } else if a_full && !b_full { InferenceResult::Second(typ_a) } else if !a_full && b_full { InferenceResult::First(typ_b) } else { InferenceResult::Ambigous } } } } fn is_fully_typed_kind(&self, kind: &TypeKind) -> bool { match kind { TypeKind::ElabType(_) => todo!(), TypeKind::Logic(data) => { if let ElabValue::Concrete(_) = data.value { true } else { false } } TypeKind::UInt(_) => todo!(), TypeKind::Callable(_) => todo!(), TypeKind::Infer => false, TypeKind::TypeVar(dbi, _tvar) => { // if the DeBruijn index is 0, there is no further information to gain // from a surrounding scope *dbi != 0 } } } /// return whether the type has no unfilled parameters pub fn is_fully_typed(&self, typ: Type) -> bool { self.is_fully_typed_kind(&self.get(typ).kind) } pub fn pretty_value(&self, w: &mut dyn std::fmt::Write, data: &ElabData) -> std::fmt::Result { match &data.value { ElabValue::Infer => write!(w, "?: ")?, ElabValue::Concrete(val) => match val { ElabValueData::U32(val) => write!(w, "{:?}: ", val)?, ElabValueData::Bytes(val) => write!(w, "{:?}: ", val)?, }, } self.pretty_type(w, data.typ) } pub fn pretty_type(&self, w: &mut dyn std::fmt::Write, typ: Type) -> std::fmt::Result { match &self.get(typ).kind { TypeKind::ElabType(val) => write!(w, "{:?}", val), TypeKind::Logic(val) => { let mut width = String::new(); self.pretty_value(&mut width, val)?; write!(w, "Logic<{}>", width) } TypeKind::Infer => write!(w, "?"), TypeKind::UInt(_) => todo!("print uint"), TypeKind::Callable(_sig) => todo!("print callable"), TypeKind::TypeVar(_, tvar) => write!(w, "T{}", tvar), } } }