use std::collections::HashMap;
use oxc_ast::ast::{
TSFunctionType, TSInterfaceDeclaration, TSSignature, TSType,
TSTypeAliasDeclaration, TSTypeReference, TSTypeName,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypeId(pub u32);
#[derive(Debug, Clone, PartialEq)]
pub enum LltsType {
Number,
I8,
I16,
I32,
I64,
U8,
U16,
U32,
U64,
F32,
F64,
Boolean,
String,
Void,
Never,
Struct(StructType),
Array(Box<LltsType>),
Tuple(Vec<LltsType>),
Union(UnionType),
Enum(EnumType),
Option(Box<LltsType>),
Result {
ok: Box<LltsType>,
err: Box<LltsType>,
},
Function(FunctionType),
Generic(GenericType),
Readonly(Box<LltsType>),
Weak(Box<LltsType>),
Alias {
name: std::string::String,
inner: Box<LltsType>,
},
Ref(TypeId),
Unknown,
}
#[derive(Debug, Clone, PartialEq)]
pub struct StructField {
pub name: std::string::String,
pub ty: LltsType,
pub readonly: bool,
pub optional: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct StructType {
pub name: std::string::String,
pub fields: Vec<StructField>,
pub type_params: Vec<std::string::String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UnionVariant {
pub tag: i32,
pub ty: LltsType,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UnionType {
pub name: Option<std::string::String>,
pub variants: Vec<UnionVariant>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumVariant {
pub name: std::string::String,
pub tag: i32,
pub value: EnumValue,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EnumValue {
Numeric(i64),
String(std::string::String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumType {
pub name: std::string::String,
pub variants: Vec<EnumVariant>,
pub is_const: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionParam {
pub name: std::string::String,
pub ty: LltsType,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionType {
pub params: Vec<FunctionParam>,
pub return_type: Box<LltsType>,
pub type_params: Vec<std::string::String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GenericType {
pub name: std::string::String,
pub type_params: Vec<std::string::String>,
pub base: Box<LltsType>,
}
#[derive(Debug, Default)]
pub struct TypeRegistry {
types: HashMap<std::string::String, (TypeId, LltsType)>,
id_to_name: HashMap<TypeId, std::string::String>,
next_id: u32,
}
impl TypeRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, name: std::string::String, ty: LltsType) -> TypeId {
if let Some((id, _)) = self.types.get(&name) {
return *id;
}
let id = TypeId(self.next_id);
self.next_id += 1;
self.id_to_name.insert(id, name.clone());
self.types.insert(name, (id, ty));
id
}
pub fn update(&mut self, name: &str, ty: LltsType) {
if let Some(entry) = self.types.get_mut(name) {
entry.1 = ty;
}
}
pub fn get(&self, name: &str) -> Option<&LltsType> {
self.types.get(name).map(|(_, ty)| ty)
}
pub fn get_by_id(&self, id: TypeId) -> Option<&LltsType> {
let name = self.id_to_name.get(&id)?;
self.types.get(name).map(|(_, ty)| ty)
}
pub fn id_of(&self, name: &str) -> Option<TypeId> {
self.types.get(name).map(|(id, _)| *id)
}
pub fn name_of(&self, id: TypeId) -> Option<&str> {
self.id_to_name.get(&id).map(|s| s.as_str())
}
pub fn structurally_equal(&self, a: &LltsType, b: &LltsType) -> bool {
match (a, b) {
(LltsType::Ref(id_a), LltsType::Ref(id_b)) if id_a == id_b => true,
(LltsType::Ref(id), other) | (other, LltsType::Ref(id)) => {
if let Some(resolved) = self.get_by_id(*id) {
self.structurally_equal(resolved, other)
} else {
false
}
}
(LltsType::Alias { inner, .. }, other) | (other, LltsType::Alias { inner, .. }) => {
self.structurally_equal(inner, other)
}
(LltsType::Struct(a), LltsType::Struct(b)) => {
a.fields.len() == b.fields.len()
&& a.fields.iter().zip(&b.fields).all(|(fa, fb)| {
fa.name == fb.name && self.structurally_equal(&fa.ty, &fb.ty)
})
}
(LltsType::Array(a), LltsType::Array(b)) => self.structurally_equal(a, b),
(LltsType::Tuple(a), LltsType::Tuple(b)) => {
a.len() == b.len()
&& a.iter()
.zip(b.iter())
.all(|(ta, tb)| self.structurally_equal(ta, tb))
}
(LltsType::Function(a), LltsType::Function(b)) => {
a.params.len() == b.params.len()
&& a.params
.iter()
.zip(&b.params)
.all(|(pa, pb)| self.structurally_equal(&pa.ty, &pb.ty))
&& self.structurally_equal(&a.return_type, &b.return_type)
}
(LltsType::Option(a), LltsType::Option(b)) => self.structurally_equal(a, b),
(
LltsType::Result { ok: ok_a, err: err_a },
LltsType::Result { ok: ok_b, err: err_b },
) => self.structurally_equal(ok_a, ok_b) && self.structurally_equal(err_a, err_b),
(LltsType::Readonly(a), LltsType::Readonly(b)) => self.structurally_equal(a, b),
(LltsType::Weak(a), LltsType::Weak(b)) => self.structurally_equal(a, b),
_ => a == b,
}
}
pub fn iter(&self) -> impl Iterator<Item = (&str, TypeId, &LltsType)> {
self.types
.iter()
.map(|(name, (id, ty))| (name.as_str(), *id, ty))
}
}
pub struct TypeResolver<'a> {
pub registry: &'a mut TypeRegistry,
}
impl<'a> TypeResolver<'a> {
pub fn new(registry: &'a mut TypeRegistry) -> Self {
Self { registry }
}
pub fn resolve_ts_type(&mut self, ts_type: &TSType<'_>) -> LltsType {
match ts_type {
TSType::TSNumberKeyword(_) => LltsType::Number,
TSType::TSBooleanKeyword(_) => LltsType::Boolean,
TSType::TSStringKeyword(_) => LltsType::String,
TSType::TSVoidKeyword(_) => LltsType::Void,
TSType::TSNeverKeyword(_) => LltsType::Never,
TSType::TSNullKeyword(_) | TSType::TSUndefinedKeyword(_) => {
LltsType::Void
}
TSType::TSAnyKeyword(_)
| TSType::TSUnknownKeyword(_)
| TSType::TSBigIntKeyword(_)
| TSType::TSSymbolKeyword(_)
| TSType::TSObjectKeyword(_) => LltsType::Unknown,
TSType::TSTypeReference(type_ref) => self.resolve_type_reference(type_ref),
TSType::TSArrayType(arr) => {
let elem = self.resolve_ts_type(&arr.element_type);
LltsType::Array(Box::new(elem))
}
TSType::TSTupleType(tuple) => {
let elems: Vec<LltsType> = tuple
.element_types
.iter()
.map(|el| self.resolve_tuple_element(el))
.collect();
LltsType::Tuple(elems)
}
TSType::TSUnionType(union) => self.resolve_union(&union.types),
TSType::TSFunctionType(func) => self.resolve_function_type(func),
TSType::TSTypeLiteral(lit) => self.resolve_type_literal(&lit.members),
TSType::TSParenthesizedType(paren) => self.resolve_ts_type(&paren.type_annotation),
TSType::TSLiteralType(_) => {
LltsType::Unknown
}
TSType::TSIntersectionType(inter) => {
self.resolve_intersection(&inter.types)
}
TSType::TSTypeOperatorType(op) => {
use oxc_ast::ast::TSTypeOperatorOperator;
match op.operator {
TSTypeOperatorOperator::Readonly => {
let inner = self.resolve_ts_type(&op.type_annotation);
LltsType::Readonly(Box::new(inner))
}
_ => LltsType::Unknown,
}
}
_ => LltsType::Unknown,
}
}
fn resolve_type_reference(&mut self, type_ref: &TSTypeReference<'_>) -> LltsType {
let name = ts_type_name_to_string(&type_ref.type_name);
match name.as_str() {
"i8" => return LltsType::I8,
"i16" => return LltsType::I16,
"i32" => return LltsType::I32,
"i64" => return LltsType::I64,
"u8" => return LltsType::U8,
"u16" => return LltsType::U16,
"u32" => return LltsType::U32,
"u64" => return LltsType::U64,
"f32" => return LltsType::F32,
"f64" => return LltsType::F64,
_ => {}
}
let type_args: Vec<LltsType> = type_ref
.type_arguments
.as_ref()
.map(|args| {
args.params
.iter()
.map(|arg| self.resolve_ts_type(arg))
.collect()
})
.unwrap_or_default();
match name.as_str() {
"Array" => {
let elem = type_args.into_iter().next().unwrap_or(LltsType::Unknown);
return LltsType::Array(Box::new(elem));
}
"Readonly" => {
let inner = type_args.into_iter().next().unwrap_or(LltsType::Unknown);
return LltsType::Readonly(Box::new(inner));
}
"Weak" => {
let inner = type_args.into_iter().next().unwrap_or(LltsType::Unknown);
return LltsType::Weak(Box::new(inner));
}
"Option" => {
let inner = type_args.into_iter().next().unwrap_or(LltsType::Unknown);
return LltsType::Option(Box::new(inner));
}
"Result" => {
let mut args = type_args.into_iter();
let ok = args.next().unwrap_or(LltsType::Unknown);
let err = args.next().unwrap_or(LltsType::Unknown);
return LltsType::Result {
ok: Box::new(ok),
err: Box::new(err),
};
}
_ => {}
}
if let Some(id) = self.registry.id_of(&name) {
return LltsType::Ref(id);
}
let id = self.registry.register(name, LltsType::Unknown);
LltsType::Ref(id)
}
fn resolve_union(&mut self, types: &[TSType<'_>]) -> LltsType {
let mut resolved: Vec<LltsType> = Vec::new();
let mut has_null = false;
for ty in types {
match ty {
TSType::TSNullKeyword(_) | TSType::TSUndefinedKeyword(_) => {
has_null = true;
}
_ => {
resolved.push(self.resolve_ts_type(ty));
}
}
}
if has_null && resolved.len() == 1 {
return LltsType::Option(Box::new(resolved.remove(0)));
}
if resolved.is_empty() {
return LltsType::Void;
}
if resolved.len() == 1 && !has_null {
return resolved.remove(0);
}
let mut variants: Vec<UnionVariant> = resolved
.into_iter()
.enumerate()
.map(|(i, ty)| UnionVariant {
tag: i as i32,
ty,
})
.collect();
if has_null {
variants.push(UnionVariant {
tag: variants.len() as i32,
ty: LltsType::Void,
});
}
LltsType::Union(UnionType {
name: None,
variants,
})
}
fn resolve_function_type(&mut self, func: &TSFunctionType<'_>) -> LltsType {
let params: Vec<FunctionParam> = func
.params
.items
.iter()
.map(|param| {
let name = binding_pattern_name(¶m.pattern);
let ty = param
.type_annotation
.as_ref()
.map(|ann| self.resolve_ts_type(&ann.type_annotation))
.unwrap_or(LltsType::Unknown);
FunctionParam { name, ty }
})
.collect();
let return_type = self.resolve_ts_type(&func.return_type.type_annotation);
let type_params: Vec<std::string::String> = func
.type_parameters
.as_ref()
.map(|tp| {
tp.params
.iter()
.map(|p| p.name.name.to_string())
.collect()
})
.unwrap_or_default();
LltsType::Function(FunctionType {
params,
return_type: Box::new(return_type),
type_params,
})
}
fn resolve_type_literal(&mut self, members: &[TSSignature<'_>]) -> LltsType {
let fields = self.resolve_signatures(members);
LltsType::Struct(StructType {
name: std::string::String::new(),
fields,
type_params: Vec::new(),
})
}
fn resolve_intersection(&mut self, types: &[TSType<'_>]) -> LltsType {
let mut all_fields = Vec::new();
for ty in types {
match self.resolve_ts_type(ty) {
LltsType::Struct(s) => all_fields.extend(s.fields),
_ => return LltsType::Unknown,
}
}
LltsType::Struct(StructType {
name: std::string::String::new(),
fields: all_fields,
type_params: Vec::new(),
})
}
pub fn resolve_interface(&mut self, decl: &TSInterfaceDeclaration<'_>) -> TypeId {
let name = decl.id.name.to_string();
let type_params: Vec<std::string::String> = decl
.type_parameters
.as_ref()
.map(|tp| {
tp.params
.iter()
.map(|p| p.name.name.to_string())
.collect()
})
.unwrap_or_default();
let fields = self.resolve_signatures(&decl.body.body);
let ty = LltsType::Struct(StructType {
name: name.clone(),
fields,
type_params,
});
self.registry.register(name, ty)
}
pub fn resolve_type_alias(&mut self, decl: &TSTypeAliasDeclaration<'_>) -> TypeId {
let name = decl.id.name.to_string();
let inner = self.resolve_ts_type(&decl.type_annotation);
let ty = match &inner {
LltsType::Struct(_) | LltsType::Union(_) | LltsType::Enum(_) => inner,
_ => LltsType::Alias {
name: name.clone(),
inner: Box::new(inner),
},
};
self.registry.register(name, ty)
}
fn resolve_signatures(&mut self, members: &[TSSignature<'_>]) -> Vec<StructField> {
let mut fields = Vec::new();
for member in members {
if let TSSignature::TSPropertySignature(prop) = member {
let field_name = property_key_name(&prop.key);
let ty = prop
.type_annotation
.as_ref()
.map(|ann| self.resolve_ts_type(&ann.type_annotation))
.unwrap_or(LltsType::Unknown);
fields.push(StructField {
name: field_name,
ty,
readonly: prop.readonly,
optional: prop.optional,
});
}
}
fields
}
fn resolve_tuple_element(&mut self, elem: &oxc_ast::ast::TSTupleElement<'_>) -> LltsType {
match elem {
oxc_ast::ast::TSTupleElement::TSNamedTupleMember(named) => {
self.resolve_tuple_element(&named.element_type)
}
oxc_ast::ast::TSTupleElement::TSOptionalType(opt) => {
let inner = self.resolve_ts_type(&opt.type_annotation);
LltsType::Option(Box::new(inner))
}
oxc_ast::ast::TSTupleElement::TSRestType(rest) => {
let inner = self.resolve_ts_type(&rest.type_annotation);
LltsType::Array(Box::new(inner))
}
_ => {
self.resolve_ts_type(elem.to_ts_type())
}
}
}
}
pub fn ts_type_name_to_string(name: &TSTypeName<'_>) -> std::string::String {
match name {
TSTypeName::IdentifierReference(ident) => ident.name.to_string(),
TSTypeName::QualifiedName(qual) => {
let left = ts_type_name_to_string(&qual.left);
let right = qual.right.name.to_string();
format!("{left}.{right}")
}
TSTypeName::ThisExpression(_) => "this".to_string(),
}
}
fn binding_pattern_name(pattern: &oxc_ast::ast::BindingPattern<'_>) -> std::string::String {
match pattern {
oxc_ast::ast::BindingPattern::BindingIdentifier(id) => id.name.to_string(),
_ => "_".to_string(),
}
}
fn property_key_name(key: &oxc_ast::ast::PropertyKey<'_>) -> std::string::String {
match key {
oxc_ast::ast::PropertyKey::StaticIdentifier(id) => id.name.to_string(),
_ => "<computed>".to_string(),
}
}
impl LltsType {
pub fn is_primitive(&self) -> bool {
matches!(
self,
LltsType::Number
| LltsType::I8
| LltsType::I16
| LltsType::I32
| LltsType::I64
| LltsType::U8
| LltsType::U16
| LltsType::U32
| LltsType::U64
| LltsType::F32
| LltsType::F64
| LltsType::Boolean
| LltsType::Void
| LltsType::Never
)
}
pub fn is_copy(&self) -> bool {
self.is_primitive()
}
pub fn is_numeric(&self) -> bool {
matches!(
self,
LltsType::Number
| LltsType::I8
| LltsType::I16
| LltsType::I32
| LltsType::I64
| LltsType::U8
| LltsType::U16
| LltsType::U32
| LltsType::U64
| LltsType::F32
| LltsType::F64
)
}
pub fn is_integer(&self) -> bool {
matches!(
self,
LltsType::I8
| LltsType::I16
| LltsType::I32
| LltsType::I64
| LltsType::U8
| LltsType::U16
| LltsType::U32
| LltsType::U64
)
}
pub fn is_signed(&self) -> bool {
matches!(
self,
LltsType::I8 | LltsType::I16 | LltsType::I32 | LltsType::I64
)
}
pub fn is_unsigned(&self) -> bool {
matches!(
self,
LltsType::U8 | LltsType::U16 | LltsType::U32 | LltsType::U64
)
}
pub fn is_float(&self) -> bool {
matches!(self, LltsType::Number | LltsType::F32 | LltsType::F64)
}
pub fn needs_heap(&self) -> bool {
matches!(
self,
LltsType::String
| LltsType::Array(_)
| LltsType::Struct(_)
| LltsType::Union(_)
| LltsType::Enum(_)
| LltsType::Function(_)
)
}
pub fn is_small_struct(&self) -> bool {
match self {
LltsType::Struct(s) => s.fields.len() <= 4 && s.fields.iter().all(|f| f.ty.is_primitive()),
LltsType::Tuple(elems) => elems.len() <= 4 && elems.iter().all(|t| t.is_primitive()),
_ => false,
}
}
pub fn int_bits(&self) -> Option<u32> {
match self {
LltsType::Boolean => Some(1),
LltsType::I8 | LltsType::U8 => Some(8),
LltsType::I16 | LltsType::U16 => Some(16),
LltsType::I32 | LltsType::U32 => Some(32),
LltsType::I64 | LltsType::U64 => Some(64),
_ => None,
}
}
pub fn can_widen_to(&self, target: &LltsType) -> bool {
match (self, target) {
(LltsType::I8, LltsType::I16 | LltsType::I32 | LltsType::I64) => true,
(LltsType::I16, LltsType::I32 | LltsType::I64) => true,
(LltsType::I32, LltsType::I64) => true,
(LltsType::U8, LltsType::U16 | LltsType::U32 | LltsType::U64) => true,
(LltsType::U16, LltsType::U32 | LltsType::U64) => true,
(LltsType::U32, LltsType::U64) => true,
(LltsType::F32, LltsType::F64 | LltsType::Number) => true,
(ty, LltsType::F64 | LltsType::Number) if ty.is_integer() => true,
_ => false,
}
}
}