use crate::def::DefId;
use serde::Serialize;
use tsz_binder::SymbolId;
use tsz_common::interner::Atom;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Default)]
pub struct TypeId(pub u32);
impl TypeId {
pub const NONE: Self = Self(0);
pub const ERROR: Self = Self(1);
pub const NEVER: Self = Self(2);
pub const UNKNOWN: Self = Self(3);
pub const ANY: Self = Self(4);
pub const VOID: Self = Self(5);
pub const UNDEFINED: Self = Self(6);
pub const NULL: Self = Self(7);
pub const BOOLEAN: Self = Self(8);
pub const NUMBER: Self = Self(9);
pub const STRING: Self = Self(10);
pub const BIGINT: Self = Self(11);
pub const SYMBOL: Self = Self(12);
pub const OBJECT: Self = Self(13);
pub const BOOLEAN_TRUE: Self = Self(14);
pub const BOOLEAN_FALSE: Self = Self(15);
pub const FUNCTION: Self = Self(16);
pub const PROMISE_BASE: Self = Self(17);
pub const DELEGATE: Self = Self(18);
pub const STRICT_ANY: Self = Self(19);
pub const FIRST_USER: u32 = 100;
pub const fn is_intrinsic(self) -> bool {
self.0 < Self::FIRST_USER
}
pub fn is_error(self) -> bool {
self == Self::ERROR
}
pub fn is_any(self) -> bool {
self == Self::ANY
}
pub fn is_unknown(self) -> bool {
self == Self::UNKNOWN
}
pub fn is_never(self) -> bool {
self == Self::NEVER
}
#[inline]
pub fn is_nullish(self) -> bool {
self == Self::NULL || self == Self::UNDEFINED
}
#[inline]
pub fn is_nullable(self) -> bool {
self == Self::NULL || self == Self::UNDEFINED || self == Self::VOID
}
#[inline]
pub fn is_top_type(self) -> bool {
self == Self::ANY || self == Self::UNKNOWN
}
#[inline]
pub fn is_any_or_unknown(self) -> bool {
self.is_top_type()
}
pub const LOCAL_MASK: u32 = 0x80000000;
pub const fn is_local(self) -> bool {
(self.0 & Self::LOCAL_MASK) != 0
}
pub const fn is_global(self) -> bool {
!self.is_local()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct RelationCacheKey {
pub source: TypeId,
pub target: TypeId,
pub relation: u8,
pub flags: u16,
pub any_mode: u8,
}
impl RelationCacheKey {
pub const SUBTYPE: u8 = 0;
pub const ASSIGNABLE: u8 = 1;
pub const IDENTICAL: u8 = 2;
pub const FLAG_STRICT_NULL_CHECKS: u16 = 1 << 0;
pub const FLAG_STRICT_FUNCTION_TYPES: u16 = 1 << 1;
pub const FLAG_EXACT_OPTIONAL_PROPERTY_TYPES: u16 = 1 << 2;
pub const FLAG_NO_UNCHECKED_INDEXED_ACCESS: u16 = 1 << 3;
pub const FLAG_DISABLE_METHOD_BIVARIANCE: u16 = 1 << 4;
pub const FLAG_ALLOW_VOID_RETURN: u16 = 1 << 5;
pub const FLAG_ALLOW_BIVARIANT_REST: u16 = 1 << 6;
pub const FLAG_ALLOW_BIVARIANT_PARAM_COUNT: u16 = 1 << 7;
pub const fn subtype(source: TypeId, target: TypeId, flags: u16, any_mode: u8) -> Self {
Self {
source,
target,
relation: Self::SUBTYPE,
flags,
any_mode,
}
}
pub const fn assignability(source: TypeId, target: TypeId, flags: u16, any_mode: u8) -> Self {
Self {
source,
target,
relation: Self::ASSIGNABLE,
flags,
any_mode,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum InferencePriority {
NakedTypeVariable = 1 << 0,
HomomorphicMappedType = 1 << 1,
PartialHomomorphicMappedType = 1 << 2,
MappedType = 1 << 3,
ContravariantConditional = 1 << 4,
ReturnType = 1 << 5,
LowPriority = 1 << 6,
Circular = 1 << 7,
}
impl InferencePriority {
pub fn should_process_in_pass(&self, current_pass: Self) -> bool {
*self >= current_pass && *self != Self::Circular
}
pub const fn next_level(&self) -> Option<Self> {
match self {
Self::NakedTypeVariable => Some(Self::HomomorphicMappedType),
Self::HomomorphicMappedType => Some(Self::PartialHomomorphicMappedType),
Self::PartialHomomorphicMappedType => Some(Self::MappedType),
Self::MappedType => Some(Self::ContravariantConditional),
Self::ContravariantConditional => Some(Self::ReturnType),
Self::ReturnType => Some(Self::LowPriority),
Self::LowPriority | Self::Circular => None,
}
}
pub const NORMAL: Self = Self::ReturnType;
pub const HIGHEST: Self = Self::NakedTypeVariable;
pub const LOWEST: Self = Self::LowPriority;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct TypeListId(pub u32);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ObjectShapeId(pub u32);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct TupleListId(pub u32);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FunctionShapeId(pub u32);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct CallableShapeId(pub u32);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct TypeApplicationId(pub u32);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct TemplateLiteralId(pub u32);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ConditionalTypeId(pub u32);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct MappedTypeId(pub u32);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum WellKnownSymbolKey {
Iterator,
AsyncIterator,
HasInstance,
IsConcatSpreadable,
Match,
MatchAll,
Replace,
Search,
Split,
Species,
ToPrimitive,
ToStringTag,
Unscopables,
Dispose,
AsyncDispose,
}
impl WellKnownSymbolKey {
pub const fn as_property_name(&self) -> &'static str {
match self {
Self::Iterator => "[Symbol.iterator]",
Self::AsyncIterator => "[Symbol.asyncIterator]",
Self::HasInstance => "[Symbol.hasInstance]",
Self::IsConcatSpreadable => "[Symbol.isConcatSpreadable]",
Self::Match => "[Symbol.match]",
Self::MatchAll => "[Symbol.matchAll]",
Self::Replace => "[Symbol.replace]",
Self::Search => "[Symbol.search]",
Self::Split => "[Symbol.split]",
Self::Species => "[Symbol.species]",
Self::ToPrimitive => "[Symbol.toPrimitive]",
Self::ToStringTag => "[Symbol.toStringTag]",
Self::Unscopables => "[Symbol.unscopables]",
Self::Dispose => "[Symbol.dispose]",
Self::AsyncDispose => "[Symbol.asyncDispose]",
}
}
pub fn from_property_name(name: &str) -> Option<Self> {
match name {
"[Symbol.iterator]" => Some(Self::Iterator),
"[Symbol.asyncIterator]" => Some(Self::AsyncIterator),
"[Symbol.hasInstance]" => Some(Self::HasInstance),
"[Symbol.isConcatSpreadable]" => Some(Self::IsConcatSpreadable),
"[Symbol.match]" => Some(Self::Match),
"[Symbol.matchAll]" => Some(Self::MatchAll),
"[Symbol.replace]" => Some(Self::Replace),
"[Symbol.search]" => Some(Self::Search),
"[Symbol.split]" => Some(Self::Split),
"[Symbol.species]" => Some(Self::Species),
"[Symbol.toPrimitive]" => Some(Self::ToPrimitive),
"[Symbol.toStringTag]" => Some(Self::ToStringTag),
"[Symbol.unscopables]" => Some(Self::Unscopables),
"[Symbol.dispose]" => Some(Self::Dispose),
"[Symbol.asyncDispose]" => Some(Self::AsyncDispose),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TypeData {
Intrinsic(IntrinsicKind),
Literal(LiteralValue),
Object(ObjectShapeId),
ObjectWithIndex(ObjectShapeId),
Union(TypeListId),
Intersection(TypeListId),
Array(TypeId),
Tuple(TupleListId),
Function(FunctionShapeId),
Callable(CallableShapeId),
TypeParameter(TypeParamInfo),
BoundParameter(u32),
Lazy(DefId),
Recursive(u32),
Enum(DefId, TypeId),
Application(TypeApplicationId),
Conditional(ConditionalTypeId),
Mapped(MappedTypeId),
IndexAccess(TypeId, TypeId),
TemplateLiteral(TemplateLiteralId),
TypeQuery(SymbolRef),
KeyOf(TypeId),
ReadonlyType(TypeId),
UniqueSymbol(SymbolRef),
Infer(TypeParamInfo),
ThisType,
StringIntrinsic {
kind: StringIntrinsicKind,
type_arg: TypeId,
},
ModuleNamespace(SymbolRef),
NoInfer(TypeId),
Error,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TypeApplication {
pub base: TypeId,
pub args: Vec<TypeId>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum IntrinsicKind {
Any,
Unknown,
Never,
Void,
Null,
Undefined,
Boolean,
Number,
String,
Bigint,
Symbol,
Object,
Function,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum StringIntrinsicKind {
Uppercase,
Lowercase,
Capitalize,
Uncapitalize,
}
impl IntrinsicKind {
pub const fn to_type_id(self) -> TypeId {
match self {
Self::Any => TypeId::ANY,
Self::Unknown => TypeId::UNKNOWN,
Self::Never => TypeId::NEVER,
Self::Void => TypeId::VOID,
Self::Null => TypeId::NULL,
Self::Undefined => TypeId::UNDEFINED,
Self::Boolean => TypeId::BOOLEAN,
Self::Number => TypeId::NUMBER,
Self::String => TypeId::STRING,
Self::Bigint => TypeId::BIGINT,
Self::Symbol => TypeId::SYMBOL,
Self::Object => TypeId::OBJECT,
Self::Function => TypeId::FUNCTION,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum LiteralValue {
String(Atom),
Number(OrderedFloat),
BigInt(Atom),
Boolean(bool),
}
#[derive(Clone, Copy, Debug)]
pub struct OrderedFloat(pub f64);
impl PartialEq for OrderedFloat {
fn eq(&self, other: &Self) -> bool {
self.0.to_bits() == other.0.to_bits()
}
}
impl Eq for OrderedFloat {}
impl std::hash::Hash for OrderedFloat {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Default)]
pub enum Visibility {
#[default]
Public,
Private,
Protected,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct PropertyInfo {
pub name: Atom,
pub type_id: TypeId,
pub write_type: TypeId,
pub optional: bool,
pub readonly: bool,
pub is_method: bool,
pub visibility: Visibility,
pub parent_id: Option<SymbolId>,
}
impl PropertyInfo {
pub const fn new(name: Atom, type_id: TypeId) -> Self {
Self {
name,
type_id,
write_type: type_id,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}
}
pub const fn method(name: Atom, type_id: TypeId) -> Self {
Self {
is_method: true,
..Self::new(name, type_id)
}
}
pub const fn opt(name: Atom, type_id: TypeId) -> Self {
Self {
optional: true,
..Self::new(name, type_id)
}
}
pub const fn readonly(name: Atom, type_id: TypeId) -> Self {
Self {
readonly: true,
..Self::new(name, type_id)
}
}
pub fn find_in_slice(props: &[Self], name: Atom) -> Option<&Self> {
props.iter().find(|p| p.name == name)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PropertyLookup {
Found(usize),
NotFound,
Uncached,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct IndexSignature {
pub key_type: TypeId,
pub value_type: TypeId,
pub readonly: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct IndexInfo {
pub string_index: Option<IndexSignature>,
pub number_index: Option<IndexSignature>,
}
bitflags::bitflags! {
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ObjectFlags: u32 {
const FRESH_LITERAL = 1 << 0;
}
}
#[derive(Clone, Debug)]
pub struct ObjectShape {
pub flags: ObjectFlags,
pub properties: Vec<PropertyInfo>,
pub string_index: Option<IndexSignature>,
pub number_index: Option<IndexSignature>,
pub symbol: Option<tsz_binder::SymbolId>,
}
impl PartialEq for ObjectShape {
fn eq(&self, other: &Self) -> bool {
self.flags == other.flags
&& self.properties == other.properties
&& self.string_index == other.string_index
&& self.number_index == other.number_index
&& self.symbol == other.symbol
}
}
impl Eq for ObjectShape {}
impl std::hash::Hash for ObjectShape {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.flags.hash(state);
self.properties.hash(state);
self.string_index.hash(state);
self.number_index.hash(state);
self.symbol.hash(state);
}
}
impl Default for ObjectShape {
fn default() -> Self {
Self {
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: None,
number_index: None,
symbol: None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TupleElement {
pub type_id: TypeId,
pub name: Option<Atom>,
pub optional: bool,
pub rest: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TypePredicate {
pub asserts: bool,
pub target: TypePredicateTarget,
pub type_id: Option<TypeId>,
pub parameter_index: Option<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TypePredicateTarget {
This,
Identifier(Atom),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct FunctionShape {
pub type_params: Vec<TypeParamInfo>,
pub params: Vec<ParamInfo>,
pub this_type: Option<TypeId>,
pub return_type: TypeId,
pub type_predicate: Option<TypePredicate>,
pub is_constructor: bool,
pub is_method: bool,
}
impl FunctionShape {
pub const fn new(params: Vec<ParamInfo>, return_type: TypeId) -> Self {
Self {
type_params: Vec::new(),
params,
this_type: None,
return_type,
type_predicate: None,
is_constructor: false,
is_method: false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CallSignature {
pub type_params: Vec<TypeParamInfo>,
pub params: Vec<ParamInfo>,
pub this_type: Option<TypeId>,
pub return_type: TypeId,
pub type_predicate: Option<TypePredicate>,
pub is_method: bool,
}
impl CallSignature {
pub const fn new(params: Vec<ParamInfo>, return_type: TypeId) -> Self {
Self {
type_params: Vec::new(),
params,
this_type: None,
return_type,
type_predicate: None,
is_method: false,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct CallableShape {
pub call_signatures: Vec<CallSignature>,
pub construct_signatures: Vec<CallSignature>,
pub properties: Vec<PropertyInfo>,
pub string_index: Option<IndexSignature>,
pub number_index: Option<IndexSignature>,
pub symbol: Option<tsz_binder::SymbolId>,
}
impl PartialEq for CallableShape {
fn eq(&self, other: &Self) -> bool {
self.call_signatures == other.call_signatures
&& self.construct_signatures == other.construct_signatures
&& self.properties == other.properties
&& self.string_index == other.string_index
&& self.number_index == other.number_index
&& self.symbol == other.symbol
}
}
impl Eq for CallableShape {}
impl std::hash::Hash for CallableShape {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.call_signatures.hash(state);
self.construct_signatures.hash(state);
self.properties.hash(state);
self.string_index.hash(state);
self.number_index.hash(state);
self.symbol.hash(state);
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct ParamInfo {
pub name: Option<Atom>,
pub type_id: TypeId,
pub optional: bool,
pub rest: bool,
}
impl ParamInfo {
pub const fn required(name: Atom, type_id: TypeId) -> Self {
Self {
name: Some(name),
type_id,
optional: false,
rest: false,
}
}
pub const fn optional(name: Atom, type_id: TypeId) -> Self {
Self {
optional: true,
..Self::required(name, type_id)
}
}
pub const fn rest(name: Atom, type_id: TypeId) -> Self {
Self {
rest: true,
..Self::required(name, type_id)
}
}
pub const fn unnamed(type_id: TypeId) -> Self {
Self {
name: None,
type_id,
optional: false,
rest: false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TypeParamInfo {
pub name: Atom,
pub constraint: Option<TypeId>,
pub default: Option<TypeId>,
pub is_const: bool,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct SymbolRef(pub u32);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ConditionalType {
pub check_type: TypeId,
pub extends_type: TypeId,
pub true_type: TypeId,
pub false_type: TypeId,
pub is_distributive: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MappedType {
pub type_param: TypeParamInfo,
pub constraint: TypeId,
pub name_type: Option<TypeId>,
pub template: TypeId,
pub readonly_modifier: Option<MappedModifier>,
pub optional_modifier: Option<MappedModifier>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum MappedModifier {
Add,
Remove,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TemplateSpan {
Text(Atom),
Type(TypeId),
}
impl TemplateSpan {
pub const fn is_text(&self) -> bool {
matches!(self, Self::Text(_))
}
pub const fn is_type(&self) -> bool {
matches!(self, Self::Type(_))
}
pub const fn as_text(&self) -> Option<Atom> {
match self {
Self::Text(atom) => Some(*atom),
_ => None,
}
}
pub const fn as_type(&self) -> Option<TypeId> {
match self {
Self::Type(type_id) => Some(*type_id),
_ => None,
}
}
pub const fn type_from_id(type_id: TypeId) -> Self {
Self::Type(type_id)
}
}
pub fn process_template_escape_sequences(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let mut chars = input.chars();
let mut last_was_backslash = false;
while let Some(c) = chars.next() {
if last_was_backslash {
last_was_backslash = false;
match c {
'$' => {
result.push('$');
}
'\\' => result.push('\\'),
'n' => result.push('\n'),
'r' => result.push('\r'),
't' => result.push('\t'),
'b' => result.push('\x08'),
'f' => result.push('\x0c'),
'v' => result.push('\x0b'),
'0' => result.push('\0'),
'x' => {
let hex1 = chars.next().unwrap_or('0');
let hex2 = chars.next().unwrap_or('0');
let code = u8::from_str_radix(&format!("{hex1}{hex2}"), 16).unwrap_or(0);
result.push(code as char);
}
'u' => {
if let Some('{') = chars.next() {
let mut code_str = String::new();
for nc in chars.by_ref() {
if nc == '}' {
break;
}
code_str.push(nc);
}
if let Ok(code) = u32::from_str_radix(&code_str, 16)
&& let Some(c) = char::from_u32(code)
{
result.push(c);
}
} else {
let mut code_str = String::new();
for _ in 0..4 {
if let Some(nc) = chars.next() {
code_str.push(nc);
}
}
if let Ok(code) = u16::from_str_radix(&code_str, 16)
&& let Some(c) = char::from_u32(code as u32)
{
result.push(c);
}
}
}
_ => {
result.push('\\');
result.push(c);
}
}
} else if c == '\\' {
last_was_backslash = true;
} else {
result.push(c);
}
}
if last_was_backslash {
result.push('\\');
}
result
}
pub fn is_compiler_managed_type(name: &str) -> bool {
matches!(
name,
"Array" | "ReadonlyArray" | "Uppercase" | "Lowercase" | "Capitalize" | "Uncapitalize" )
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct Variance: u8 {
const COVARIANT = 1 << 0;
const CONTRAVARIANT = 1 << 1;
}
}
impl Variance {
pub const fn is_independent(&self) -> bool {
self.is_empty()
}
pub const fn is_covariant(&self) -> bool {
self.contains(Self::COVARIANT) && !self.contains(Self::CONTRAVARIANT)
}
pub const fn is_contravariant(&self) -> bool {
self.contains(Self::CONTRAVARIANT) && !self.contains(Self::COVARIANT)
}
pub fn is_invariant(&self) -> bool {
self.contains(Self::COVARIANT | Self::CONTRAVARIANT)
}
pub fn compose(&self, other: Self) -> Self {
if self.is_invariant() || other.is_invariant() {
return Self::COVARIANT | Self::CONTRAVARIANT;
}
if self.is_independent() || other.is_independent() {
return Self::empty();
}
let is_covariant = self.is_covariant() == other.is_covariant();
let is_contravariant = !is_covariant;
let mut result = Self::empty();
if is_covariant {
result |= Self::COVARIANT;
}
if is_contravariant {
result |= Self::CONTRAVARIANT;
}
result
}
}
#[cfg(test)]
#[path = "../tests/types_tests.rs"]
mod tests;