Skip to content

Instantly share code, notes, and snippets.

@Boshen
Last active October 30, 2025 14:14
Show Gist options
  • Select an option

  • Save Boshen/d189de0fe0720a30c5182cb666e3e9a5 to your computer and use it in GitHub Desktop.

Select an option

Save Boshen/d189de0fe0720a30c5182cb666e3e9a5 to your computer and use it in GitHub Desktop.
TypeScript type inference prototype
use estree::{visit::Visit, TSType, *};
use rustc_hash::FxHashMap;
use super::{
facts::TypeFacts,
helper::*,
links::{NodeLink, SymbolLink},
resolution::{TypeResolution, TypeSystemEntity},
signature::{SignatureId, SignaturesTable},
LiteralValue, ObjectFlags, TypeFlags, TypeId, TypeKind, TypeTable, UnionReduction,
WideningContext,
};
use crate::{
flow::FlowBuilder,
node::{AstNodeId, AstNodes},
scope::{ScopeFlags, ScopeTree},
symbol::{SymbolFlags, SymbolId, SymbolMap, SymbolTable},
SemanticBuilderOptions,
};
pub type SymbolLinks = FxHashMap<SymbolId, SymbolLink>;
pub type NodeLinks = FxHashMap<Span, NodeLink>;
#[derive(Debug)]
pub struct TypeBuilder<'a> {
options: SemanticBuilderOptions,
pub types: TypeTable,
pub signatures: SignaturesTable,
pub nodes: AstNodes<'a>,
pub scopes: ScopeTree,
pub flow: FlowBuilder,
pub symbols: SymbolTable,
pub span_to_node: FxHashMap<Span, AstNodeId>,
pub symbol_links: SymbolLinks,
pub node_links: NodeLinks,
/// Type Resolution for detecting circular type queries during type inference
/// see `push_type_resolution` for implementation details
pub resolutions: Vec<TypeResolution>,
}
impl<'a> Visit<'a> for TypeBuilder<'a> {
fn enter_node(&mut self, kind: AstKind<'a>) {
self.check_source_element(kind);
}
fn visit_expression(&mut self, expr: &'a Expression<'a>) {
self.check_expression_cached(expr);
self.visit_expression_match(expr);
}
}
/// Type Building Utilities / Main Entry Points
impl<'a> TypeBuilder<'a> {
#[must_use]
pub fn new(
options: &SemanticBuilderOptions,
nodes: AstNodes<'a>,
scopes: ScopeTree,
flow: FlowBuilder,
symbols: SymbolTable,
span_to_node: FxHashMap<Span, AstNodeId>,
) -> Self {
let mut types = TypeTable::new(options, &symbols);
let mut symbol_links: FxHashMap<SymbolId, SymbolLink> = FxHashMap::default();
symbol_links.entry(symbols.unknown_symbol_id).or_default().t = Some(types.error_type_id);
symbol_links.entry(symbols.undefined_symbol_id).or_default().t =
Some(types.undefined_type_id);
let signatures = SignaturesTable::new(&mut types);
Self {
options: *options,
types,
signatures,
nodes,
scopes,
flow,
symbols,
span_to_node,
symbol_links,
node_links: FxHashMap::default(),
resolutions: Vec::new(),
}
}
#[must_use]
pub fn build(mut self, program: &'a Program<'a>) -> Self {
self.visit_program(program);
self
}
/// Get `Symbol` cache
fn get_symbol_links(&mut self, symbol_id: SymbolId) -> &mut SymbolLink {
self.symbol_links.entry(symbol_id).or_default()
}
/// Get `Node` cache
fn get_node_links(&mut self, span: Span) -> &mut NodeLink {
self.node_links.entry(span).or_default()
}
/// Main entry point for: `Symbol` -> `Type`
fn get_type_of_symbol(&mut self, symbol_id: SymbolId) -> TypeId {
let symbol = &self.symbols[symbol_id];
// A quick route for getting the type from a global symbol.
// This is not part of tsc.
match symbol_id {
_ if symbol_id == self.symbols.global_array_symbol_id => {
return self.types.global_array_type_id;
}
_ if symbol_id == self.symbols.global_object_symbol_id => {
return self.types.global_object_type_id;
}
_ if symbol_id == self.symbols.global_function_symbol_id => {
return self.types.global_function_type_id;
}
_ if symbol_id == self.symbols.global_string_symbol_id => {
return self.types.global_string_type_id;
}
_ if symbol_id == self.symbols.global_number_symbol_id => {
return self.types.global_number_type_id;
}
_ if symbol_id == self.symbols.global_boolean_symbol_id => {
return self.types.global_boolean_type_id;
}
_ if symbol_id == self.symbols.global_regexp_symbol_id => {
return self.types.global_regexp_type_id;
}
_ if symbol_id == self.symbols.global_promise_symbol_id => {
return self.types.global_promise_type_id;
}
_ => {}
}
let flags = symbol.flags;
if flags.intersects(SymbolFlags::Variable | SymbolFlags::Property) {
return self.get_type_of_variable_or_parameter_or_property(symbol_id);
}
if flags.is_function_or_class_or_enum_or_module() {
return self.get_type_of_func_class_enum_module(symbol_id);
}
self.types.any_type_id
}
/// Main entry point for: `IdentifierReference` -> `Symbol`
fn get_resolved_symbol(&mut self, name: &Atom, span: Span) -> SymbolId {
if let Some(symbol_id) = self.get_node_links(span.clone()).resolved_symbol {
return symbol_id;
}
let symbol_id =
self.resolve_name(name, span.clone()).unwrap_or(self.symbols.unknown_symbol_id);
self.get_node_links(span).resolved_symbol.replace(symbol_id);
symbol_id
}
fn resolve_name(&mut self, name: &Atom, span: Span) -> Option<SymbolId> {
self.span_to_node
.get(&span)
.and_then(|node_id| {
// try find from ancestor scopes
self.scopes
.node_scope_ancestors(&self.nodes[node_id.id()])
.map(|scope_id| self.scopes[scope_id].get())
.find_map(|scope| scope.variables.get(name))
})
// or from the global symbol table
.or_else(|| self.symbols.globals.get(name))
.copied()
}
fn with_resolved_type<F>(&mut self, span: Span, resolver: F) -> TypeId
where
F: FnOnce(&mut Self) -> TypeId,
{
if let Some(resolved) = self.get_node_links(span.clone()).resolved_type {
return resolved;
}
let type_id = resolver(self);
self.get_node_links(span).resolved_type.replace(type_id);
type_id
}
fn get_declared_type_of_symbol(&mut self, symbol_id: SymbolId) -> TypeId {
self.try_get_declared_type_of_symbol(symbol_id).unwrap_or(self.types.error_type_id)
}
fn try_get_declared_type_of_symbol(&mut self, symbol_id: SymbolId) -> Option<TypeId> {
let flags = self.symbols[symbol_id].flags;
if flags.intersects(SymbolFlags::Class | SymbolFlags::Interface) {
return Some(self.get_declared_type_of_class_or_interface(symbol_id));
}
if flags.intersects(SymbolFlags::TypeAlias) {
return Some(self.get_declared_type_of_type_alias(symbol_id));
}
if flags.intersects(SymbolFlags::Enum) {
return Some(self.get_declared_type_of_enum(symbol_id));
}
None
}
/// ```
/// semantic::check(r#"
/// type A = number;
/// let a: A;
/// // ^? number
/// "#);
/// ```
fn get_declared_type_of_type_alias(&mut self, symbol_id: SymbolId) -> TypeId {
if let Some(resolved) = self.get_symbol_links(symbol_id).declared_type {
return resolved;
}
if !self.push_type_resolution(TypeSystemEntity::DeclaredType(symbol_id)) {
return self.types.error_type_id;
}
let symbol = &self.symbols[symbol_id];
let mut type_id = symbol
.declarations
.iter()
.find_map(|node_id| match self.nodes[*node_id].kind {
AstKind::TSTypeAliasDeclaration(decl) => Some(decl),
_ => None,
})
.map_or(self.types.error_type_id, |decl| {
self.get_type_from_type_node(&decl.type_annotation)
});
if self.pop_type_resolution() {
// TODO init type parameters
} else {
type_id = self.types.error_type_id;
}
self.get_symbol_links(symbol_id).declared_type.replace(type_id);
type_id
}
fn get_declared_type_of_class_or_interface(&mut self, symbol_id: SymbolId) -> TypeId {
self.get_symbol_links(symbol_id).declared_type.unwrap_or_else(|| {
let flags = self.symbols[symbol_id].flags;
let kind = if flags.contains(SymbolFlags::Class) {
ObjectFlags::Class
} else {
ObjectFlags::Interface
};
let object_type_id = self.types.create_object_type(kind, Some(symbol_id));
self.get_symbol_links(symbol_id).declared_type.replace(object_type_id);
object_type_id
})
}
fn get_declared_type_of_enum(&mut self, symbol_id: SymbolId) -> TypeId {
self.get_symbol_links(symbol_id).declared_type.unwrap_or_else(|| {
let enum_type_id = self.types.create_enum_type(symbol_id);
self.get_symbol_links(symbol_id).declared_type.replace(enum_type_id);
enum_type_id
})
}
}
/// Type Inference Methods
impl<'a> TypeBuilder<'a> {
fn get_type_reference_type(
&mut self,
reference: &TSTypeReference<'a>,
symbol_id: SymbolId,
) -> TypeId {
if symbol_id == self.symbols.unknown_symbol_id {
return self.types.error_type_id;
}
let flags = self.symbols[symbol_id].flags;
if flags.intersects(SymbolFlags::Class | SymbolFlags::Interface) {}
if flags.intersects(SymbolFlags::TypeAlias) {
return self.get_type_from_type_alias_reference(reference, symbol_id);
}
if let Some(type_id) = self.try_get_declared_type_of_symbol(symbol_id) {
return type_id;
}
self.types.error_type_id
}
fn get_type_from_type_alias_reference(
&mut self,
_reference: &TSTypeReference<'a>,
symbol_id: SymbolId,
) -> TypeId {
self.get_declared_type_of_symbol(symbol_id)
}
fn resolve_type_reference_name(&mut self, reference: &TSTypeReference<'a>) -> SymbolId {
match &reference.type_name {
TSTypeName::IdentifierName(ident) => Some(ident),
TSTypeName::QualifiedName(_) => None,
}
.and_then(|name| self.resolve_entity_name(&name.name, name.node.range()))
.unwrap_or(self.symbols.unknown_symbol_id)
}
/// Resolves a qualified name and any involved aliases.
fn resolve_entity_name(&mut self, name: &Atom, span: Span) -> Option<SymbolId> {
self.span_to_node
.get(&span)
.and_then(|node_id| {
// try find from ancestor scopes
self.scopes
.node_scope_ancestors(&self.nodes[node_id.id()])
.map(|scope_id| self.scopes[scope_id].get())
.find_map(|scope| scope.variables.get(name))
})
// or from the global symbol table
.or_else(|| self.symbols.globals.get(name))
.copied()
}
fn resolve_call_expression(&mut self, expr: &'a CallExpression<'a>) -> SignatureId {
let func_type_id = self.check_expression(&expr.callee);
let signatures = self.get_signatures_of_type(func_type_id);
if signatures.is_empty() { self.signatures.empty_signature_id } else { signatures[0] }
}
fn resolve_new_expression(&mut self, expr: &'a NewExpression<'a>) -> SignatureId {
let expression_type = self.check_expression(&expr.callee);
let construct_signatures = self.get_signatures_of_type(expression_type);
if construct_signatures.is_empty() {
self.signatures.empty_signature_id
} else {
construct_signatures[0]
}
}
fn resolve_signature(&mut self, expr: CallLikeExpression<'a>) -> SignatureId {
match expr {
CallLikeExpression::CallExpression(expr) => self.resolve_call_expression(expr),
CallLikeExpression::NewExpression(expr) => self.resolve_new_expression(expr),
}
}
fn resolve_anonymous_type_members(&mut self, type_id: TypeId) {
let r#type = &self.types[type_id];
if let Some(symbol_id) = r#type.symbol_id {
let symbol = &self.symbols[symbol_id];
if symbol.flags.intersects(SymbolFlags::Function) {
let signatures = self.get_signatures_of_symbol(symbol_id);
if let TypeKind::ObjectType(object_type) = &mut self.types[type_id].kind {
object_type.call_signatures = signatures;
}
}
}
}
fn get_signatures_of_symbol(&mut self, symbol_id: SymbolId) -> Vec<SignatureId> {
self.symbols[symbol_id]
.declarations
.clone()
.into_iter()
.filter_map(|node_id| {
self.nodes[node_id]
.kind
.is_function_like()
.then(|| self.get_signature_from_declaration(node_id))
})
.collect()
}
fn resolve_structured_type_members(&mut self, type_id: TypeId) -> TypeId {
let ty = &self.types[type_id];
if ty.flags.intersects(TypeFlags::Object) {
if let TypeKind::ObjectType(object_type) = &ty.kind {
if object_type.object_flags.intersects(ObjectFlags::Anonymous) {
self.resolve_anonymous_type_members(type_id);
}
}
}
type_id
}
/// ```
/// semantic::check(r#"
/// function x() { };
/// // ^? () => void
/// "#);
/// ```
fn get_type_of_func_class_enum_module(&mut self, symbol_id: SymbolId) -> TypeId {
self.get_symbol_links(symbol_id).t.unwrap_or_else(|| {
let type_id = self.get_type_of_func_class_enum_module_worker(symbol_id);
self.get_symbol_links(symbol_id).t.replace(type_id);
type_id
})
}
fn get_type_of_func_class_enum_module_worker(&mut self, symbol_id: SymbolId) -> TypeId {
// TODO handle module and binary expression
let type_id = self.types.create_object_type(ObjectFlags::Anonymous, Some(symbol_id));
// TODO: this should be replaced with `contextuallyCheckFunctionExpressionOrObjectLiteralMethod` -> `resolveAnonymousTypeMembers`
match self.types[type_id].kind {
TypeKind::ObjectType(_) => {
if self.symbols[symbol_id].flags.intersects(SymbolFlags::Function) {
let call_signatures = self.get_signatures_of_symbol(symbol_id);
if let TypeKind::ObjectType(object_type) = &mut self.types[type_id].kind {
object_type.call_signatures = call_signatures;
}
}
}
_ => unreachable!(),
}
let symbol = &self.symbols[symbol_id];
if symbol.flags.intersects(SymbolFlags::Class) { self.types.any_type_id } else { type_id }
}
/// ```
/// semantic::check(r#"
/// function a (n: number) {}
/// // ^? (n: number) => void
/// "#);
/// ```
fn get_signature_from_declaration(&mut self, node_id: AstNodeId) -> SignatureId {
let kind = self.nodes[node_id].kind;
let span = kind.node().range();
if let Some(resolved) = self.get_node_links(span.clone()).resolved_signature {
return resolved;
}
let signature_id = self.signatures.create_signature(Some(node_id), None);
self.get_node_links(span).resolved_signature.replace(signature_id);
// unlike tsc, we eagerly resolve and cache return type here
if matches!(kind, AstKind::Function(_) | AstKind::ArrowExpression(_)) {
self.get_return_type_of_signature(signature_id);
}
if let AstKind::Function(Function { params, .. })
| AstKind::ArrowExpression(ArrowExpression { params, .. }) = kind
{
self.signatures[signature_id].parameters = params
.items
.iter()
.map(|param| self.symbols.span_to_symbol[&param.node.range()])
.collect();
}
signature_id
}
fn get_type_of_variable_or_parameter_or_property(&mut self, symbol_id: SymbolId) -> TypeId {
self.get_symbol_links(symbol_id).t.unwrap_or_else(|| {
let type_id = self.get_type_of_variable_or_parameter_or_property_worker(symbol_id);
self.get_symbol_links(symbol_id).t.replace(type_id);
type_id
})
}
/// ```
/// semantic::check(r#"
/// let foo = foo; // case for `push_type_resolution`
/// // ^? any
/// let a = 1, b = "2";
/// // ^? string
/// interface B { key: boolean }
/// // // ^? boolean
/// function c (n: number) {}
/// // // ^? number
/// "#);
/// ```
fn get_type_of_variable_or_parameter_or_property_worker(
&mut self,
symbol_id: SymbolId,
) -> TypeId {
let symbol = &self.symbols[symbol_id];
let declaration = symbol
.value_declaration
.map(|node_id| self.nodes.kind(*node_id))
.map_or_else(|| unreachable!(), |kind| kind);
// Handle variable, parameter or property
if !self.push_type_resolution(TypeSystemEntity::Type(symbol_id)) {
return self.types.error_type_id;
}
let type_id = match declaration {
AstKind::VariableDeclarator(decl) => self
.get_widened_type_for_variable_like_declaration(
VariableLikeDeclaration::VariableDeclarator(decl),
),
AstKind::TSPropertySignature(sig) => self
.get_widened_type_for_variable_like_declaration(
VariableLikeDeclaration::PropertySignature(sig),
),
AstKind::FormalParameter(param) => self.get_widened_type_for_variable_like_declaration(
VariableLikeDeclaration::FormalParameter(param),
),
AstKind::Function(_) => self.get_type_of_func_class_enum_module(symbol_id),
AstKind::Property(property) => self.check_property_assignment(property),
_ => self.types.any_type_id,
};
if !self.pop_type_resolution() {
// Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty`
// if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) {
// return getTypeOfFuncClassEnumModule(symbol);
// }
return self.types.error_type_id;
}
type_id
}
fn get_union_type(&mut self, types: &[TypeId], union_reduction: UnionReduction) -> TypeId {
match types[..] {
[] => return self.types.never_type_id,
[type_id] => return type_id,
_ => {}
}
let mut type_set: Vec<TypeId> = vec![];
let includes = self.add_types_to_union(&mut type_set, TypeFlags::empty(), types);
if union_reduction != UnionReduction::None {
if includes.intersects(TypeFlags::AnyOrUnknown) {
return if includes.intersects(TypeFlags::Any) {
if includes.intersects(TypeFlags::IncludesWildcard) {
self.types.wildcard_type_id
} else {
self.types.any_type_id
}
} else if includes.intersects(TypeFlags::Null)
|| type_set.contains(&self.types.unknown_type_id)
{
self.types.unknown_type_id
} else {
self.types.non_null_unknown_type_id
};
}
// if (exactOptionalPropertyTypes && includes & TypeFlags.Undefined) {
// const missingIndex = binarySearch(typeSet, missingType, getTypeId, compareValues);
// if (missingIndex >= 0 && containsType(typeSet, undefinedType)) {
// orderedRemoveItemAt(typeSet, missingIndex);
// }
// }
if includes.intersects(
TypeFlags::Literal
| TypeFlags::UniqueESSymbol
| TypeFlags::TemplateLiteral
| TypeFlags::StringMapping,
) || (includes.intersects(TypeFlags::Void)
&& includes.intersects(TypeFlags::Undefined))
{
type_set = self.remove_redundant_literal_types(
&type_set,
includes,
union_reduction == UnionReduction::Subtype,
);
}
// if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) {
// removeStringLiteralsMatchedByTemplateLiterals(typeSet);
// }
// if (unionReduction === UnionReduction.Subtype) {
// typeSet = removeSubtypes(typeSet, !!(includes & TypeFlags.Object));
// if (!typeSet) {
// return errorType;
// }
// }
if type_set.is_empty() {
return if includes.intersects(TypeFlags::Null) {
if includes.intersects(TypeFlags::IncludesNonWideningType) {
self.types.null_type_id
} else {
self.types.null_widening_type_id
}
} else if includes.intersects(TypeFlags::Undefined) {
if includes.intersects(TypeFlags::IncludesNonWideningType) {
self.types.undefined_type_id
} else {
self.types.undefined_widening_type_id
}
} else {
self.types.never_type_id
};
}
}
// if (!origin && includes & TypeFlags.Union) {
// const namedUnions: Type[] = [];
// addNamedUnions(namedUnions, types);
// const reducedTypes: Type[] = [];
// for (const t of typeSet) {
// if (!some(namedUnions, union => containsType((union as UnionType).types, t))) {
// reducedTypes.push(t);
// }
// }
// if (!aliasSymbol && namedUnions.length === 1 && reducedTypes.length === 0) {
// return namedUnions[0];
// }
// // We create a denormalized origin type only when the union was created from one or more named unions
// // (unions with alias symbols or origins) and when there is no overlap between those named unions.
// const namedTypesCount = reduceLeft(namedUnions, (sum, union) => sum + (union as UnionType).types.length, 0);
// if (namedTypesCount + reducedTypes.length === typeSet.length) {
// for (const t of namedUnions) {
// insertType(reducedTypes, t);
// }
// origin = createOriginUnionOrIntersectionType(TypeFlags.Union, reducedTypes);
// }
// }
self.types.create_union_type(type_set)
}
// This function assumes the constituent type list is sorted and deduplicated.
fn get_union_type_from_sorted_list(
&mut self,
types: &[TypeId],
_object_flags: ObjectFlags,
) -> TypeId {
match types[..] {
[] => return self.types.never_type_id,
[type_id] => return type_id,
_ => {}
}
// const typeKey = !origin ? getTypeListId(types) :
// origin.flags & TypeFlags.Union ? `|${getTypeListId((origin as UnionType).types)}` :
// origin.flags & TypeFlags.Intersection ? `&${getTypeListId((origin as IntersectionType).types)}` :
// `#${(origin as IndexType).type.id}|${getTypeListId(types)}`; // origin type id alone is insufficient, as `keyof x` may resolve to multiple WIP values while `x` is still resolving
// const id = typeKey + getAliasId(aliasSymbol, aliasTypeArguments);
// let type = unionTypes.get(id);
// if (!type) {
// type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, [>excludeKinds<] TypeFlags.Nullable);
// type.types = types;
// type.origin = origin;
// type.aliasSymbol = aliasSymbol;
// type.aliasTypeArguments = aliasTypeArguments;
// if (types.length === 2 && types[0].flags & TypeFlags.BooleanLiteral && types[1].flags & TypeFlags.BooleanLiteral) {
// type.flags |= TypeFlags.Boolean;
// (type as UnionType & IntrinsicType).intrinsicName = "boolean";
// }
// unionTypes.set(id, type);
// }
// return type;
self.types.create_union_type(types.to_vec())
}
/// ```
/// semantic::check(r#"
/// let a: null & unknown
/// // ^? null
/// "#);
/// ```
fn get_intersection_type(&mut self, types: &[TypeId]) -> TypeId {
let mut type_membership_map: FxHashMap<TypeId, TypeId> = FxHashMap::default();
let includes =
self.add_types_to_intersection(&mut type_membership_map, TypeFlags::default(), types);
let type_set: Vec<TypeId> = type_membership_map.values().copied().collect();
if includes.intersects(TypeFlags::Never) {
return if type_set.contains(&self.types.silent_never_type_id) {
self.types.silent_never_type_id
} else {
self.types.never_type_id
};
}
if self.options.strict_null_checks
&& includes.intersects(includes & TypeFlags::Nullable)
&& includes.intersects(
TypeFlags::Object | TypeFlags::NonPrimitive | TypeFlags::IncludesEmptyObject,
)
|| includes.intersects(includes & TypeFlags::NonPrimitive)
&& includes.intersects(TypeFlags::DisjointDomains - TypeFlags::NonPrimitive)
|| includes.intersects(includes & TypeFlags::StringLike)
&& includes.intersects(TypeFlags::DisjointDomains - TypeFlags::StringLike)
|| includes.intersects(includes & TypeFlags::NumberLike)
&& includes.intersects(TypeFlags::DisjointDomains - TypeFlags::NumberLike)
|| includes.intersects(includes & TypeFlags::BigIntLike)
&& includes.intersects(TypeFlags::DisjointDomains - TypeFlags::BigIntLike)
|| includes.intersects(includes & TypeFlags::ESSymbolLike)
&& includes.intersects(TypeFlags::DisjointDomains - TypeFlags::ESSymbolLike)
|| includes.intersects(includes & TypeFlags::VoidLike)
&& includes.intersects(TypeFlags::DisjointDomains - TypeFlags::VoidLike)
{
return self.types.never_type_id;
}
if includes.intersects(TypeFlags::Any) {
return if includes.intersects(TypeFlags::IncludesWildcard) {
self.types.wildcard_type_id
} else {
self.types.any_type_id
};
} else if !self.options.strict_null_checks && includes.intersects(TypeFlags::Nullable) {
return if includes.intersects(TypeFlags::IncludesEmptyObject) {
self.types.never_type_id
} else if includes.intersects(TypeFlags::Undefined) {
self.types.undefined_type_id
} else {
self.types.null_type_id
};
}
if type_set.is_empty() {
return self.types.unknown_type_id;
}
if type_set.len() == 1 {
return type_set[0];
}
self.types.any_type_id
}
// Add the given types to the given type set. Order is preserved, duplicates are removed,
// and nested types of the given kind are flattened into the set.
fn add_types_to_union(
&self,
type_set: &mut Vec<TypeId>,
includes: TypeFlags,
types: &[TypeId],
) -> TypeFlags {
types.iter().fold(includes, |includes, type_id| {
self.add_type_to_union(type_set, includes, *type_id)
})
}
fn add_type_to_union(
&self,
type_set: &mut Vec<TypeId>,
includes: TypeFlags,
type_id: TypeId,
) -> TypeFlags {
let ty = &self.types[type_id];
let flags = ty.flags;
if flags.intersects(TypeFlags::Union) {
let includes = includes
| if ty.is_named_union_type() { TypeFlags::Union } else { TypeFlags::empty() };
let types = match &ty.kind {
TypeKind::UnionType(ty) => &ty.types,
_ => unreachable!(),
};
return self.add_types_to_union(type_set, includes, types);
}
let mut includes = includes;
// We ignore 'never' types in unions
if !flags.intersects(TypeFlags::Never) {
includes |= flags & TypeFlags::IncludesMask;
if flags.intersects(TypeFlags::Instantiable) {
includes |= TypeFlags::IncludesInstantiable;
}
if type_id == self.types.wildcard_type_id {
includes |= TypeFlags::IncludesWildcard;
};
if !self.options.strict_null_checks && flags.intersects(TypeFlags::Nullable) {
if !ty.get_object_flags().intersects(ObjectFlags::ContainsWideningType) {
includes |= TypeFlags::IncludesNonWideningType;
}
} else {
match type_set.binary_search(&type_id) {
Ok(_) => {}
Err(pos) => type_set.insert(pos, type_id),
}
}
}
includes
}
// Add the given types to the given type set. Order is preserved, freshness is removed from literal
// types, duplicates are removed, and nested types of the given kind are flattened into the set.
fn add_types_to_intersection(
&mut self,
type_set: &mut FxHashMap<TypeId, TypeId>,
includes: TypeFlags,
types: &[TypeId],
) -> TypeFlags {
types.iter().fold(includes, |includes, type_id| {
let regular_type_id = self.get_regular_type_of_literal_type(*type_id);
self.add_type_to_intersection(type_set, includes, regular_type_id)
})
}
fn add_type_to_intersection(
&mut self,
type_set: &mut FxHashMap<TypeId, TypeId>,
includes: TypeFlags,
type_id: TypeId,
) -> TypeFlags {
let mut includes = includes;
let flags = self.types[type_id].flags;
if flags.intersects(TypeFlags::AnyOrUnknown) {
if type_id == self.types.wildcard_type_id {
includes |= TypeFlags::IncludesWildcard;
}
} else if self.options.strict_null_checks || !flags.intersects(TypeFlags::Nullable) {
type_set.entry(type_id).or_insert_with(|| {
if flags.intersects(TypeFlags::Unit) && includes.intersects(TypeFlags::Unit) {
includes |= TypeFlags::NonPrimitive;
}
type_id
});
}
includes |= flags & TypeFlags::IncludesMask;
includes
}
fn remove_redundant_literal_types(
&self,
types: &[TypeId],
includes: TypeFlags,
reduce_void_undefined: bool,
) -> Vec<TypeId> {
types
.iter()
.copied()
.filter(|type_id| {
let ty = &self.types[*type_id];
let flags = ty.flags;
let remove = flags.intersects(
TypeFlags::StringLiteral
| TypeFlags::TemplateLiteral
| TypeFlags::StringMapping,
) && includes.intersects(TypeFlags::String)
|| flags.intersects(TypeFlags::NumberLiteral)
&& includes.intersects(TypeFlags::Number)
|| flags.intersects(TypeFlags::BigIntLiteral)
&& includes.intersects(TypeFlags::BigInt)
|| flags.intersects(TypeFlags::UniqueESSymbol)
&& includes.intersects(TypeFlags::ESSymbol)
|| reduce_void_undefined
&& flags.intersects(TypeFlags::Undefined)
&& includes.intersects(TypeFlags::Void)
|| ty.is_fresh_literal_type()
&& types.contains(&ty.get_regular_type().unwrap());
!remove
})
.collect()
}
fn get_optional_type(&mut self, type_id: TypeId) -> TypeId {
let r#type = &self.types[type_id];
if r#type.flags.intersects(TypeFlags::Undefined) {
type_id
} else {
self.get_union_type(&[type_id, self.types.undefined_type_id], UnionReduction::None)
}
}
fn add_optionality(&mut self, type_id: TypeId, is_optional: bool) -> TypeId {
if is_optional { self.get_optional_type(type_id) } else { type_id }
}
fn get_widened_type_for_variable_like_declaration(
&mut self,
decl: VariableLikeDeclaration<'a>,
) -> TypeId {
let type_id = self.get_type_for_variable_like_declaration(decl);
self.widen_type_for_variable_like_declaration(type_id)
}
fn widen_type_for_variable_like_declaration(&mut self, type_id: TypeId) -> TypeId {
self.get_widened_type(type_id)
}
/// ```
/// semantic::check(r#"
/// for (let i in x) {}
/// // ^? string
/// for (let i = 1 in x) {}
/// // ^? string
/// "#);
/// ```
fn get_type_for_variable_like_declaration(
&mut self,
decl: VariableLikeDeclaration<'a>,
) -> TypeId {
if matches!(decl, VariableLikeDeclaration::VariableDeclarator(_)) {
// A variable declared in a for..in statement is of type string, or of type keyof T when the
// right hand expression is of a type parameter type.
let ast_node = &self.nodes[*self.span_to_node[&decl.node().range()]];
for (i, node_id) in self.nodes.node_ancestors(ast_node).enumerate() {
let kind = self.nodes.kind(node_id);
if matches!(kind, AstKind::ForInStatement(_)) {
return self.types.string_type_id;
}
if i > 3 || matches!(kind, AstKind::BlockStatement(_)) {
break;
}
}
}
// Use type from type annotation if one is present
if let Some(type_annotation) = decl.type_annotation() {
let declared_type = self.get_type_from_type_node(&type_annotation.type_annotation);
return self.add_optionality(declared_type, decl.optional());
}
if let VariableLikeDeclaration::VariableDeclarator(decl) = decl {
if decl.init.is_some() {
let type_id = self.check_declaration_initializer(decl);
return self.widen_type_inferred_from_initializer(decl, type_id);
}
}
self.types.any_type_id
}
fn get_quick_type_of_expression(&mut self, expression: &'a Expression<'a>) -> Option<TypeId> {
match expression {
Expression::CallExpression(expr)
if !matches!(expr.callee, Expression::Super(_))
&& !expr.is_require_call()
&& !expr.is_symbol_or_symbol_for_call() =>
{
None
}
Expression::TSAsExpression(expr) if !expr.type_annotation.is_const_type_reference() => {
Some(self.get_type_from_type_node(&expr.type_annotation))
}
Expression::TSTypeAssertion(expr) => {
Some(self.get_type_from_type_node(&expr.type_annotation))
}
Expression::ParenthesizedExpression(expr) => {
self.get_quick_type_of_expression(&expr.expression)
}
expr if expr.is_literal_expression() => Some(self.check_expression(expr)),
_ => None,
}
}
/// ```
/// semantic::check(r#"
/// let x = 1;
/// // ^? number
/// const y = -3n;
/// // . ^? bigint
/// "#);
/// ```
fn get_literal_type(&mut self, value: LiteralValue, flags: TypeFlags) -> TypeId {
self.types
.literal_values
.get(&value)
.copied()
.unwrap_or_else(|| self.types.create_literal_type(flags, value, None, None))
}
fn get_fresh_type_of_literal_type(&mut self, type_id: TypeId) -> TypeId {
let ty = &self.types[type_id];
if let TypeKind::LiteralType(lit) = &ty.kind {
return if lit.fresh_type == TypeId::new(1) {
let fresh_type_id = self.types.create_literal_type(
ty.flags,
lit.value.clone(),
ty.symbol_id,
Some(type_id),
);
self.types[fresh_type_id].set_fresh_type(fresh_type_id);
self.types[type_id].set_fresh_type(fresh_type_id);
fresh_type_id
} else {
lit.fresh_type
};
}
type_id
}
fn get_regular_type_of_literal_type(&self, type_id: TypeId) -> TypeId {
match &self.types[type_id].kind {
TypeKind::LiteralType(lit) => lit.regular_type,
// TODO: type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) :
_ => type_id,
}
}
fn get_widened_type(&mut self, type_id: TypeId) -> TypeId {
self.get_widened_type_with_context(type_id, None)
}
/// ```
/// semantic::check(r#"
/// let a = null;
/// // ^? any
/// let b = []
/// // ^? any[]
/// "#);
/// ```
#[allow(clippy::if_same_then_else)]
fn get_widened_type_with_context(
&mut self,
type_id: TypeId,
context: Option<WideningContext>,
) -> TypeId {
let ty = &self.types[type_id];
let flags = ty.flags;
let object_flags = ty.get_object_flags();
if object_flags.intersects(ObjectFlags::RequiresWidening) {
if context.is_none() {
if let Some(widened_type_id) = ty.widened {
return widened_type_id;
}
}
let result = if flags.intersects(TypeFlags::Any | TypeFlags::Nullable) {
Some(self.types.any_type_id)
} else if self.types.is_object_literal_type(ty) {
// result = getWidenedTypeOfObjectLiteral(type, context);
Some(self.types.any_type_id)
} else if flags.intersects(TypeFlags::Union) {
// const unionContext = context || createWideningContext([>parent*/ undefined, /*propertyName<] undefined, (type as UnionType).types);
// const widenedTypes = sameMap((type as UnionType).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext));
// result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal);
Some(self.types.any_type_id)
} else if flags.intersects(TypeFlags::Intersection) {
// result = getIntersectionType(sameMap((type as IntersectionType).types, getWidenedType));
Some(self.types.any_type_id)
} else if self.types.is_array_or_tuple_type(ty) {
let (target, resolved_type_arguments) = match &ty.kind {
TypeKind::ObjectType(object_type) => {
(object_type.target, object_type.resolved_type_arguments.clone())
}
_ => unreachable!(),
};
// TODO: getTypeArgument
// result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType));
let type_arguments = resolved_type_arguments
.iter()
.map(|type_id| self.get_widened_type(*type_id))
.collect();
Some(self.types.create_type_reference(target, type_arguments))
} else {
None
};
if result.is_some() && context.is_none() {
self.types[type_id].widened = result;
}
result.unwrap_or(type_id)
} else {
type_id
}
}
fn get_widened_literal_like_type_for_contextual_type(
&mut self,
type_id: TypeId,
_contextual_type_id: Option<TypeId>,
) -> TypeId {
// if (!isLiteralOfContextualType(type, contextualType)) {
let type_id = self.get_widened_literal_type(type_id);
// }
self.get_regular_type_of_literal_type(type_id)
}
fn get_widened_literal_type(&self, type_id: TypeId) -> TypeId {
let ty = &self.types[type_id];
let flags = ty.flags;
let is_fresh_literal_type = ty.is_fresh_literal_type();
// TODO: type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type as LiteralType) :
// TODO: type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedLiteralType) :
if flags.intersects(TypeFlags::StringLiteral) && is_fresh_literal_type {
self.types.string_type_id
} else if flags.intersects(TypeFlags::NumberLiteral) && is_fresh_literal_type {
self.types.number_type_id
} else if flags.intersects(TypeFlags::BigIntLiteral) && is_fresh_literal_type {
self.types.bigint_type_id
} else if flags.intersects(TypeFlags::BooleanLiteral) && is_fresh_literal_type {
self.types.boolean_type_id
} else {
type_id
}
}
fn get_widened_literal_like_type_for_contextual_return_type_if_needed(
&mut self,
type_id: TypeId,
_contextual_type_id: Option<TypeId>,
_is_async: bool,
) -> TypeId {
if self.types[type_id].is_unit_type() {
// const contextualType = !contextualSignatureReturnType ? undefined :
// isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) :
// contextualSignatureReturnType;
return self.get_widened_literal_like_type_for_contextual_type(type_id, None);
}
type_id
}
fn get_unary_result_type(&mut self, _operand_type: TypeId) -> TypeId {
self.types.number_type_id
}
fn get_narrowed_type_of_symbol(
&mut self,
symbol_id: SymbolId,
_location: &IdentifierReference,
) -> TypeId {
self.get_type_of_symbol(symbol_id)
}
fn widen_type_inferred_from_initializer(
&self,
decl: &VariableDeclarator<'a>,
t: TypeId,
) -> TypeId {
if decl.kind == VariableDeclarationKind::Const {
t
} else {
self.get_widened_literal_type(t)
}
}
#[must_use]
fn get_type_from_type_node(&mut self, ty: &'a TSType<'a>) -> TypeId {
self.get_type_from_type_node_worker(ty)
}
#[must_use]
#[allow(clippy::match_same_arms)]
fn get_type_from_type_node_worker(&mut self, ty: &'a TSType<'a>) -> TypeId {
match ty {
// Keyword
TSType::TSAnyKeyword(_) => self.types.any_type_id,
TSType::TSBigIntKeyword(_) => self.types.bigint_type_id,
TSType::TSBooleanKeyword(_) => self.types.boolean_type_id,
TSType::TSNeverKeyword(_) => self.types.never_type_id,
TSType::TSNullKeyword(_) => self.types.null_widening_type_id,
TSType::TSNumberKeyword(_) => self.types.number_type_id,
TSType::TSObjectKeyword(_) => self.types.non_primitive_type_id,
TSType::TSStringKeyword(_) => self.types.string_type_id,
TSType::TSSymbolKeyword(_) => self.types.es_symbol_type_id,
TSType::TSThisKeyword(node) => self.get_type_from_this_type_node(node),
TSType::TSUndefinedKeyword(_) => self.types.undefined_type_id,
TSType::TSUnknownKeyword(_) => self.types.unknown_type_id,
TSType::TSVoidKeyword(_) => self.types.void_type_id,
// Compound
TSType::TSArrayType(node) => self.get_type_from_array_type_node(node),
TSType::TSConditionalType(node) => self.get_type_from_conditional_type_node(node),
TSType::TSConstructorType(_) => self.types.any_type_id,
TSType::TSFunctionType(_) => self.types.any_type_id,
TSType::TSImportType(node) => self.get_type_from_import_type_node(node),
TSType::TSIndexedAccessType(node) => self.get_type_from_indexed_access_type_node(node),
TSType::TSInferType(node) => self.get_type_from_infer_type_node(node),
TSType::TSIntersectionType(node) => self.get_type_from_intersection_type_node(node),
TSType::TSLiteralType(node) => self.get_type_from_literal_type_node(node),
TSType::TSMappedType(node) => self.get_type_from_mapped_type_node(node),
TSType::TSQualifiedName(_) => self.types.any_type_id,
TSType::TSTemplateLiteralType(_) => self.types.string_type_id,
TSType::TSTupleType(node) => self.get_type_from_tuple_type_node(node),
TSType::TSTypeLiteral(_) => self.types.any_type_id,
TSType::TSTypeOperatorType(node) => self.get_type_from_type_operator_type_node(node),
TSType::TSTypePredicate(predicate) => {
if predicate.asserts {
self.types.void_type_id
} else {
self.types.boolean_type_id
}
}
TSType::TSTypeQuery(query) => self.get_type_from_type_query_node(query),
TSType::TSTypeReference(reference) => self.get_type_from_type_reference(reference),
TSType::TSUnionType(union) => self.get_type_from_union_type_node(union),
TSType::JSDocNullableType(_) => self.types.any_type_id,
TSType::JSDocUnknownType(_) => self.types.unknown_type_id,
}
}
fn get_type_from_this_type_node(&mut self, _node: &TSThisKeyword) -> TypeId {
self.types.any_type_id
}
/// ```
/// semantic::check(r#"
/// let a: any[] = []
/// // ^? any[]
/// "#);
/// ```
fn get_type_from_array_type_node(&mut self, ty: &'a TSArrayType<'a>) -> TypeId {
self.with_resolved_type(ty.node.range(), |builder| {
let target = builder.types.global_array_type_id;
let type_arguments = vec![builder.get_type_from_type_node(&ty.element_type)];
builder.types.create_type_reference(target, type_arguments)
})
}
fn get_type_from_conditional_type_node(&mut self, _node: &TSConditionalType<'a>) -> TypeId {
self.types.any_type_id
}
fn get_type_from_import_type_node(&mut self, _node: &TSImportType<'a>) -> TypeId {
self.types.any_type_id
}
fn get_type_from_indexed_access_type_node(
&mut self,
_node: &TSIndexedAccessType<'a>,
) -> TypeId {
self.types.any_type_id
}
fn get_type_from_infer_type_node(&mut self, _node: &TSInferType<'a>) -> TypeId {
self.types.any_type_id
}
fn get_type_from_intersection_type_node(&mut self, ty: &'a TSIntersectionType<'a>) -> TypeId {
self.with_resolved_type(ty.node.range(), |builder| {
let types = ty
.types
.iter()
.map(|type_id| builder.get_type_from_type_node(type_id))
.collect::<Vec<_>>();
builder.get_intersection_type(&types)
})
}
fn get_type_from_literal_type_node(&mut self, literal: &'a TSLiteralType<'a>) -> TypeId {
self.with_resolved_type(literal.node.range(), |builder| {
let literal_type = match &literal.literal {
TSLiteral::NullLiteral(_) => builder.types.null_type_id,
TSLiteral::BooleanLiteral(lit) => builder.check_boolean_literal(lit.value),
TSLiteral::StringLiteral(lit) => builder.check_string_literal(&lit.value),
TSLiteral::NumberLiteral(lit) => builder.check_number_literal(*lit.value),
TSLiteral::BigintLiteral(lit) => builder.check_bigint_literal(&lit.value),
TSLiteral::TemplateLiteral(lit) => builder.check_templatei_literal(lit),
TSLiteral::RegExpLiteral(_) => builder.check_regexp_literal(),
TSLiteral::UnaryExpression(unary_expr) => {
builder.check_unary_expression(unary_expr)
}
};
builder.get_fresh_type_of_literal_type(literal_type)
})
}
fn get_type_from_mapped_type_node(&mut self, _node: &TSMappedType<'a>) -> TypeId {
self.types.any_type_id
}
fn get_type_from_tuple_type_node(&mut self, _node: &TSTupleType<'a>) -> TypeId {
self.types.any_type_id
}
/// ```
/// semantic::check(r#"
/// let a: 1 | string = "s";
/// // ^? string | 1
/// "#);
/// ```
fn get_type_from_union_type_node(&mut self, union: &'a TSUnionType<'a>) -> TypeId {
self.with_resolved_type(union.node.range(), |builder| {
let types =
union.types.iter().map(|t| builder.get_type_from_type_node(t)).collect::<Vec<_>>();
builder.get_union_type(&types, UnionReduction::Literal)
})
}
fn get_type_from_type_operator_type_node(&mut self, _node: &TSTypeOperatorType<'a>) -> TypeId {
self.types.any_type_id
}
fn get_type_from_type_query_node(&mut self, query: &'a TSTypeQuery<'a>) -> TypeId {
self.with_resolved_type(query.node.range(), |builder| {
let type_id = builder.check_expression_with_type_arguments(
ExpressionWithTypeArguments::TSTypeQuery(query),
);
let widened_type = builder.get_widened_type(type_id);
builder.get_regular_type_of_literal_type(widened_type)
})
}
/// ```
/// semantic::check(r#"
/// let a: Promise<any> = "s";
/// // ^? Promise
/// "#);
/// ```
fn get_type_from_type_reference(&mut self, reference: &TSTypeReference<'a>) -> TypeId {
self.with_resolved_type(reference.node.range(), |builder| {
// TODO handle `x as const`
let symbol_id = builder.resolve_type_reference_name(reference);
builder.get_type_reference_type(reference, symbol_id)
})
}
fn get_resolved_signature(&mut self, call: CallLikeExpression<'a>) -> SignatureId {
let span = call.node().range();
if let Some(resolved) = self.get_node_links(span.clone()).resolved_signature {
return resolved;
}
let signature_id = self.resolve_signature(call);
self.get_node_links(span).resolved_signature.replace(signature_id);
signature_id
}
/// ```
/// semantic::check(r#"
/// function x(): number {};
/// const a = x();
/// // ^? number
///
/// function y(): Promise<number> {};
/// const b = y();
/// // ^? Promise
/// "#);
/// ```
fn get_return_type_of_annotation(&mut self, decl_id: AstNodeId) -> Option<TypeId> {
let kind = self.nodes[decl_id];
match kind.kind {
AstKind::Function(func) => func.return_type.as_ref(),
AstKind::ArrowExpression(arrow) => arrow.return_type.as_ref(),
_ => None,
}
.map(|annotation| self.get_type_from_type_node(&annotation.type_annotation))
}
/// ```
/// semantic::check(r#"
/// function x() { return 3 };
/// const a = x();
/// // ^? number
/// async function y() { return 3 };
/// const b = y();
/// // ^? Promise
/// let c = () => 0;
/// // ^? () => number
/// "#);
/// ```
fn get_return_type_from_body(&mut self, decl_id: AstNodeId) -> TypeId {
let (is_async, body, is_expression_body) = match self.nodes[decl_id].kind {
AstKind::Function(func) => (func.r#async, func.body.as_ref(), false),
AstKind::ArrowExpression(arrow) => (arrow.r#async, Some(&arrow.body), arrow.expression),
_ => unreachable!(),
};
let Some(body) = body else {
return self.types.error_type_id;
};
let fallback_return_type = self.types.void_type_id;
let mut return_type: Option<TypeId> = None;
// () => 0
if is_expression_body {
if let Some(Statement::ExpressionStatement(expr)) = body.statements.get(0) {
return_type.replace(self.check_expression_cached(&expr.expression));
}
// function foo() { return 0 }
} else if let Some(return_types) = self.check_and_aggregate_return_expression_types(body) {
if return_types.is_empty() {
return if is_async {
self.types.create_promise_type(self.types.void_type_id)
} else {
self.types.void_type_id
};
}
return_type.replace(self.get_union_type(&return_types, UnionReduction::Subtype));
} else {
return if is_async {
self.types.create_promise_type(self.types.never_type_id)
} else {
self.types.never_type_id
};
}
if let Some(return_type_id) = return_type {
let mut return_type_id = return_type_id;
if self.types[return_type_id].is_unit_type() {
return_type_id = self
.get_widened_literal_like_type_for_contextual_return_type_if_needed(
return_type_id,
None,
is_async,
);
}
return_type.replace(self.get_widened_type(return_type_id));
}
let return_type = return_type.unwrap_or(fallback_return_type);
if is_async { self.types.create_promise_type(return_type) } else { return_type }
}
/// ```
/// semantic::check(r#"
/// function fib(x:number) { return x <= 1 ? x : fib(x - 1) + fib(x - 2); }
/// var result = fib(5);
/// "#);
/// ```
#[allow(clippy::useless_let_if_seq)]
fn get_return_type_of_signature(&mut self, signature_id: SignatureId) -> TypeId {
if let Some(resolved_type_id) = self.signatures[signature_id].resolved_return_type {
return resolved_type_id;
}
if !self.push_type_resolution(TypeSystemEntity::ResolvedReturnType(signature_id)) {
return self.types.error_type_id;
}
let signature = &self.signatures[signature_id];
let mut type_id = signature.declaration.map_or(self.types.any_type_id, |decl_id| {
self.get_return_type_of_annotation(decl_id)
.unwrap_or_else(|| self.get_return_type_from_body(decl_id))
});
if !self.pop_type_resolution() {
type_id = self.types.any_type_id;
}
self.signatures[signature_id].resolved_return_type.replace(type_id);
type_id
}
fn get_signatures_of_type(&mut self, type_id: TypeId) -> Vec<SignatureId> {
let type_id = self.resolve_structured_type_members(type_id);
match &self.types[type_id].kind {
TypeKind::ObjectType(object_type) => object_type.call_signatures.clone(),
_ => vec![],
}
}
fn get_type_facts(&self, type_id: TypeId) -> TypeFacts {
let mut type_id = type_id;
let flags = self.types[type_id].flags;
if flags.intersects(TypeFlags::Intersection | TypeFlags::Instantiable) {
type_id =
self.get_base_constraint_of_type(type_id).unwrap_or(self.types.unknown_type_id);
}
let ty = &self.types[type_id];
let flags = ty.flags;
let strict_null_checks = self.options.strict_null_checks;
if flags.intersects(TypeFlags::String | TypeFlags::StringMapping) {
return if strict_null_checks {
TypeFacts::StringStrictFacts
} else {
TypeFacts::StringFacts
};
}
if flags.intersects(TypeFlags::StringLiteral | TypeFlags::TemplateLiteral) {
return match (strict_null_checks, ty.is_zero()) {
(true, true) => TypeFacts::EmptyStringStrictFacts,
(true, false) => TypeFacts::NonEmptyStringStrictFacts,
(false, true) => TypeFacts::EmptyStringFacts,
(false, false) => TypeFacts::NonEmptyStringFacts,
};
}
if flags.intersects(TypeFlags::Number | TypeFlags::Enum) {
return if strict_null_checks {
TypeFacts::NumberStrictFacts
} else {
TypeFacts::NumberFacts
};
}
if flags.intersects(TypeFlags::NumberLiteral) {
return match (strict_null_checks, ty.is_zero()) {
(true, true) => TypeFacts::ZeroNumberStrictFacts,
(true, false) => TypeFacts::NonZeroNumberStrictFacts,
(false, true) => TypeFacts::ZeroNumberFacts,
(false, false) => TypeFacts::NonZeroNumberFacts,
};
}
if flags.intersects(TypeFlags::BigInt) {
return if strict_null_checks {
TypeFacts::BigIntStrictFacts
} else {
TypeFacts::BigIntFacts
};
}
if flags.intersects(TypeFlags::BigIntLiteral) {
return match (strict_null_checks, ty.is_zero()) {
(true, true) => TypeFacts::ZeroBigIntStrictFacts,
(true, false) => TypeFacts::NonZeroBigIntStrictFacts,
(false, true) => TypeFacts::ZeroBigIntFacts,
(false, false) => TypeFacts::NonZeroBigIntFacts,
};
}
if flags.intersects(TypeFlags::Boolean) {
return if strict_null_checks {
TypeFacts::BooleanStrictFacts
} else {
TypeFacts::BooleanFacts
};
}
if flags.intersects(TypeFlags::BooleanLike) {
let is_falsy =
type_id == self.types.false_type_id || type_id == self.types.regular_false_type_id;
return match (strict_null_checks, is_falsy) {
(true, true) => TypeFacts::FalseStrictFacts,
(true, false) => TypeFacts::TrueStrictFacts,
(false, true) => TypeFacts::FalseFacts,
(false, false) => TypeFacts::TrueFacts,
};
}
// if flags.intersects(TypeFlags::Object) {
// return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ?
// strict_null_checks ? TypeFacts::EmptyObjectStrictFacts : TypeFacts::EmptyObjectFacts :
// isFunctionObjectType(type as ObjectType) ?
// strict_null_checks ? TypeFacts::FunctionStrictFacts : TypeFacts::FunctionFacts :
// strict_null_checks ? TypeFacts::ObjectStrictFacts : TypeFacts::ObjectFacts;
// }
if flags.intersects(TypeFlags::Void) {
return TypeFacts::VoidFacts;
}
if flags.intersects(TypeFlags::Undefined) {
return TypeFacts::UndefinedFacts;
}
if flags.intersects(TypeFlags::Null) {
return TypeFacts::NullFacts;
}
if flags.intersects(TypeFlags::ESSymbolLike) {
return if strict_null_checks {
TypeFacts::SymbolStrictFacts
} else {
TypeFacts::SymbolFacts
};
}
if flags.intersects(TypeFlags::NonPrimitive) {
return if strict_null_checks {
TypeFacts::ObjectStrictFacts
} else {
TypeFacts::ObjectFacts
};
}
if flags.intersects(TypeFlags::Never) {
return TypeFacts::None;
}
// if flags.intersects(TypeFlags::Union) {
// return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFacts::t), TypeFacts::None);
// }
// if flags.intersects(TypeFlags::Intersection) {
// return getIntersectionTypeFacts::type as IntersectionType);
// }
TypeFacts::UnknownFacts
}
fn get_type_with_facts(&mut self, type_id: TypeId, include: TypeFacts) -> TypeId {
self.filter_type(type_id, |builder, type_id| {
builder.get_type_facts(type_id).intersects(include)
})
}
// This function is similar to getTypeWithFacts, except that in strictNullChecks mode it replaces type
// unknown with the union {} | null | undefined (and reduces that accordingly), and it intersects remaining
// instantiable types with {}, {} | null, or {} | undefined in order to remove null and/or undefined.
fn get_adjusted_type_with_facts(&mut self, type_id: TypeId, facts: TypeFacts) -> TypeId {
let strict_null_checks = self.options.strict_null_checks;
let ty = &self.types[type_id];
let type_id = self.get_type_with_facts(
if strict_null_checks && ty.flags.intersects(TypeFlags::Unknown) {
self.types.unknown_union_type_id
} else {
type_id
},
facts,
);
let reduced = self.recombine_unknown_type(type_id);
if strict_null_checks {
match facts {
TypeFacts::NEUndefined => {
return self.map_type(
reduced,
|builder, type_id| {
if builder.get_type_facts(type_id).intersects(TypeFacts::EQUndefined) {
let type_id2 = if builder
.get_type_facts(type_id)
.intersects(TypeFacts::EQNull)
&& !builder.maybe_type_of_kind(reduced, TypeFlags::Null)
{
builder.get_union_type(
&[
builder.types.empty_object_type_id,
builder.types.null_type_id,
],
UnionReduction::Literal,
)
} else {
builder.types.empty_object_type_id
};
builder.get_intersection_type(&[type_id, type_id2])
} else {
type_id
}
},
false,
);
}
TypeFacts::NENull => {
return self.map_type(
reduced,
|builder, type_id| {
if builder.get_type_facts(type_id).intersects(TypeFacts::EQNull) {
let type_id2 = if builder
.get_type_facts(type_id)
.intersects(TypeFacts::EQUndefined)
&& !builder.maybe_type_of_kind(reduced, TypeFlags::Undefined)
{
builder.get_union_type(
&[
builder.types.empty_object_type_id,
builder.types.undefined_type_id,
],
UnionReduction::Literal,
)
} else {
builder.types.empty_object_type_id
};
builder.get_intersection_type(&[type_id, type_id2])
} else {
type_id
}
},
false,
);
}
TypeFacts::NEUndefinedOrNull | TypeFacts::Truthy => {
return self.map_type(
reduced,
|builder, type_id| {
if builder
.get_type_facts(type_id)
.intersects(TypeFacts::EQUndefinedOrNull)
{
builder.get_global_non_nullable_type_instantiation(type_id)
} else {
type_id
}
},
false,
);
}
_ => {}
}
}
reduced
}
#[allow(clippy::unused_self)]
const fn get_global_non_nullable_type_instantiation(&self, type_id: TypeId) -> TypeId {
type_id
// if (!deferredGlobalNonNullableTypeAlias) {
// deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, [>diagnostic<] undefined) || unknownSymbol;
// }
// return deferredGlobalNonNullableTypeAlias !== unknownSymbol ?
// getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]) :
// getIntersectionType([type, emptyObjectType]);
}
// Apply a mapping function to a type and return the resulting type. If the source type
// is a union type, the mapping function is applied to each constituent type and a union
// of the resulting types is returned.
// function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type;
// function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined;
fn map_type<F>(&mut self, type_id: TypeId, mapper: F, _no_reductions: bool) -> TypeId
where
F: Fn(&mut Self, TypeId) -> TypeId,
{
let ty = &self.types[type_id];
if ty.flags.intersects(TypeFlags::Never) {
return type_id;
}
if ty.flags.intersects(TypeFlags::Union) {
return mapper(self, type_id);
}
// const origin = (type as UnionType).origin;
// const types = origin && origin.flags & TypeFlags.Union ? (origin as UnionType).types : (type as UnionType).types;
// let mappedTypes: Type[] | undefined;
// let changed = false;
// for (const t of types) {
// const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions) : mapper(t);
// changed ||= t !== mapped;
// if (mapped) {
// if (!mappedTypes) {
// mappedTypes = [mapped];
// }
// else {
// mappedTypes.push(mapped);
// }
// }
// }
// return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type;
type_id
}
fn filter_type<F>(&mut self, type_id: TypeId, f: F) -> TypeId
where
F: Fn(&Self, TypeId) -> bool,
{
let ty = &self.types[type_id];
if let TypeKind::UnionType(union_type) = &ty.kind {
let types = &union_type.types;
let filtered =
types.iter().copied().filter(|type_id| f(self, *type_id)).collect::<Vec<_>>();
if filtered.iter().zip(types).all(|(a, b)| a == b) {
return type_id;
}
// const origin = (type as UnionType).origin;
// let newOrigin: Type | undefined;
// if (origin && origin.flags & TypeFlags.Union) {
// // If the origin type is a (denormalized) union type, filter its non-union constituents. If that ends
// // up removing a smaller number of types than in the normalized constituent set (meaning some of the
// // filtered types are within nested unions in the origin), then we can't construct a new origin type.
// // Otherwise, if we have exactly one type left in the origin set, return that as the filtered type.
// // Otherwise, construct a new filtered origin type.
// const originTypes = (origin as UnionType).types;
// const originFiltered = filter(originTypes, t => !!(t.flags & TypeFlags.Union) || f(t));
// if (originTypes.length - originFiltered.length === types.length - filtered.length) {
// if (originFiltered.length === 1) {
// return originFiltered[0];
// }
// newOrigin = createOriginUnionOrIntersectionType(TypeFlags.Union, originFiltered);
// }
// }
return self.get_union_type_from_sorted_list(&filtered, ty.get_object_flags());
}
if ty.flags.intersects(TypeFlags::Never) || f(self, type_id) {
type_id
} else {
self.types.never_type_id
}
}
fn remove_definitely_falsy_types(&mut self, type_id: TypeId) -> TypeId {
self.filter_type(type_id, |builder, type_id| {
builder.get_type_facts(type_id).intersects(TypeFacts::Truthy)
})
}
fn extract_definitely_falsy_types(&mut self, type_id: TypeId) -> TypeId {
self.map_type(
type_id,
|builder, type_id| builder.get_definitely_falsy_part_of_type(type_id),
false,
)
}
fn get_definitely_falsy_part_of_type(&self, type_id: TypeId) -> TypeId {
let ty = &self.types[type_id];
let flags = ty.flags;
if flags.intersects(TypeFlags::String) {
self.types.empty_string_type_id
} else if flags.intersects(TypeFlags::Number) {
self.types.zero_type_id
} else if flags.intersects(TypeFlags::BigInt) {
self.types.zero_big_int_type_id
} else if type_id == self.types.regular_false_type_id
|| type_id == self.types.false_type_id
|| flags.intersects(
TypeFlags::Void | TypeFlags::Undefined | TypeFlags::Null | TypeFlags::AnyOrUnknown,
)
|| flags.intersects(
TypeFlags::StringLiteral | TypeFlags::NumberLiteral | TypeFlags::BigIntLiteral,
) && ty.is_zero()
{
type_id
} else {
self.types.never_type_id
}
}
// Return true if type might be of the given kind. A union or intersection type might be of a given
// kind if at least one constituent type is of the given kind.
fn maybe_type_of_kind(&self, type_id: TypeId, kind: TypeFlags) -> bool {
let ty = &self.types[type_id];
if ty.flags.intersects(kind) {
return true;
}
if let TypeKind::UnionType(union) = &ty.kind {
for type_id in &union.types {
if self.maybe_type_of_kind(*type_id, kind) {
return true;
}
}
}
false
}
fn recombine_unknown_type(&self, type_id: TypeId) -> TypeId {
if type_id == self.types.unknown_union_type_id {
self.types.unknown_type_id
} else {
type_id
}
}
#[allow(clippy::unused_self)]
const fn get_base_constraint_of_type(&self, _type_id: TypeId) -> Option<TypeId> {
None
}
fn get_non_nullable_type(&mut self, type_id: TypeId) -> TypeId {
if self.options.strict_null_checks {
self.get_adjusted_type_with_facts(type_id, TypeFacts::NEUndefinedOrNull)
} else {
type_id
}
}
fn check_non_null_type(&mut self, type_id: TypeId) -> TypeId {
let ty = &self.types[type_id];
if self.options.strict_null_checks && ty.flags.intersects(TypeFlags::Unknown) {
// if (isEntityNameExpression(node)) {
// const nodeText = entityNameToString(node);
// if (nodeText.length < 100) {
// error(node, Diagnostics._0_is_of_type_unknown, nodeText);
// }
// error(node, Diagnostics.Object_is_of_type_unknown);
return self.types.error_type_id;
}
let facts = self.get_type_facts(type_id);
if facts.intersects(TypeFacts::IsUndefinedOrNull) {
// reportError(node, facts);
let type_id = self.get_non_nullable_type(type_id);
let ty = &self.types[type_id];
return if ty.flags.intersects(TypeFlags::Nullable | TypeFlags::Never) {
self.types.error_type_id
} else {
type_id
};
}
type_id
}
fn get_base_type_of_literal_type(&mut self, type_id: TypeId) -> TypeId {
let flags = self.types[type_id].flags;
if flags.intersects(TypeFlags::EnumLiteral) {
// getBaseTypeOfEnumLiteralType(type as LiteralType)
self.types.any_type_id
} else if flags.intersects(
TypeFlags::StringLiteral | TypeFlags::TemplateLiteral | TypeFlags::StringMapping,
) {
self.types.string_type_id
} else if flags.intersects(TypeFlags::NumberLiteral) {
self.types.number_type_id
} else if flags.intersects(TypeFlags::BigIntLiteral) {
self.types.bigint_type_id
} else if flags.intersects(TypeFlags::Union) {
// getBaseTypeOfLiteralTypeUnion(type as UnionType)
self.types.any_type_id
} else {
type_id
}
}
}
/// Type Check Methods
impl<'a> TypeBuilder<'a> {
fn check_source_element(&mut self, kind: AstKind<'a>) {
self.check_source_element_worker(kind);
}
fn check_source_element_worker(&mut self, kind: AstKind<'a>) {
match kind {
AstKind::Directive(directive) => self.check_directive(directive),
AstKind::IdentifierReference(ident) => {
self.check_identifier(ident);
}
AstKind::BindingIdentifier(ident) => self.check_binding_identifier(ident),
AstKind::FormalParameter(param) => self
.check_variable_like_declaration(VariableLikeDeclaration::FormalParameter(param)),
AstKind::Class(class) if class.is_declaration() => {
self.check_class_like_declaration(class);
}
AstKind::TSTypeAliasDeclaration(decl) => {
// cache symbol name
let symbol_id = self.symbols.get_symbol_id_of_span(&decl.node.range());
self.get_declared_type_of_symbol(symbol_id);
}
AstKind::TSEnumDeclaration(decl) => {
self.check_enum_declaration(decl);
}
AstKind::TSModuleDeclaration(decl) => {
self.check_module_declaration(decl);
}
AstKind::TSPropertySignature(sig) => {
self.check_variable_like_declaration(VariableLikeDeclaration::PropertySignature(
sig,
));
}
_ => {}
}
}
fn check_expression_cached(&mut self, expr: &'a Expression<'a>) -> TypeId {
self.with_resolved_type(expr.node().range(), |builder| builder.check_expression(expr))
}
fn check_expression(&mut self, expr: &'a Expression<'a>) -> TypeId {
self.check_expression_worker(expr)
}
fn check_expression_for_mutable_location(&mut self, expr: &'a Expression<'a>) -> TypeId {
let type_id = self.check_expression(expr);
self.get_widened_literal_like_type_for_contextual_type(type_id, None)
}
/// ```
/// semantic::check(r#"
/// undefined;
/// // ^? undefined
/// 1;
/// // ^? 1
/// "#);
/// ```
#[allow(clippy::match_same_arms)]
fn check_expression_worker(&mut self, expr: &'a Expression<'a>) -> TypeId {
match expr {
Expression::Identifier(ident) => self.check_identifier(ident),
// Literals
Expression::NullLiteral(_) => self.types.null_widening_type_id,
Expression::BooleanLiteral(lit) => self.check_boolean_literal(lit.value),
Expression::StringLiteral(lit) => self.check_string_literal(&lit.value),
Expression::NumberLiteral(lit) => self.check_number_literal(*lit.value),
Expression::BigintLiteral(lit) => self.check_bigint_literal(&lit.value),
Expression::TemplateLiteral(lit) => self.check_templatei_literal(lit),
Expression::RegExpLiteral(_) => self.check_regexp_literal(),
Expression::MetaProperty(_) => self.types.any_type_id,
Expression::Super(_) => self.types.any_type_id,
// Expressions
Expression::ParenthesizedExpression(expr) => {
self.check_expression_worker(&expr.expression)
}
Expression::ArrayExpression(expr) => self.check_array_literal(expr),
Expression::AssignmentExpression(expr) => self.check_assignment_expression(expr),
Expression::AwaitExpression(_expr) => self.types.any_type_id,
Expression::BinaryExpression(expr) => self.check_binary_expression(expr),
Expression::CallExpression(expr) => {
self.check_call_expression(CallLikeExpression::CallExpression(expr))
}
Expression::ChainExpression(expr) => self.check_chain_expression(expr),
Expression::ConditionalExpression(expr) => self.check_conditional_expression(expr),
Expression::ImportExpression(_expr) => self.types.any_type_id,
Expression::JSXElement(_expr) => self.types.any_type_id,
Expression::JSXFragment(_expr) => self.types.any_type_id,
Expression::LogicalExpression(expr) => self.check_logical_expression(expr),
Expression::MemberExpression(expr) => self.check_member_expression(expr),
Expression::NewExpression(expr) => {
self.check_call_expression(CallLikeExpression::NewExpression(expr))
}
Expression::ObjectExpression(expr) => self.check_object_literal(expr),
Expression::PrivateInExpression(_expr) => self.types.any_type_id,
Expression::SequenceExpression(expr) => self.check_sequence_expression(expr),
Expression::TaggedTemplateExpression(_expr) => self.types.any_type_id,
Expression::ThisExpression(_expr) => self.types.any_type_id,
Expression::UnaryExpression(expr) => self.check_unary_expression(expr),
Expression::UpdateExpression(expr) => self.check_update_expression(expr),
Expression::YieldExpression(_expr) => self.types.any_type_id,
Expression::ClassExpression(expr) => self.check_class_like_declaration(expr),
Expression::FunctionExpression(expr) => self
.check_function_expression_or_object_literal_method(FunctionKind::Function(expr)),
Expression::ArrowFunctionExpression(expr) => self
.check_function_expression_or_object_literal_method(FunctionKind::ArrowExpression(
expr,
)),
// Types
Expression::TSNonNullExpression(expr) => self.check_non_null_expression(expr),
Expression::TSAsExpression(expr) => {
self.check_assertion_worker(&expr.type_annotation, &expr.expression)
}
Expression::TSTypeAssertion(expr) => {
self.check_assertion_worker(&expr.type_annotation, &expr.expression)
}
Expression::TSInstantiationExpression(expr) => self
.check_expression_with_type_arguments(
ExpressionWithTypeArguments::TSInstantiationExpression(expr),
),
}
}
fn check_boolean_literal(&mut self, value: bool) -> TypeId {
if value { self.types.true_type_id } else { self.types.false_type_id }
}
/// ```
/// semantic::check(r#"
/// let a = "x";
/// // ^? "x"
/// let b = `y`;
/// // ^? "y"
/// "#);
/// ```
fn check_string_literal(&mut self, value: &Atom) -> TypeId {
let value = LiteralValue::String(value.clone());
let type_id = self.get_literal_type(value, TypeFlags::StringLiteral);
self.get_fresh_type_of_literal_type(type_id)
}
/// ```
/// semantic::check(r#"
/// let a = 1
/// // ^ 1
/// "#);
/// ```
fn check_number_literal(&mut self, value: f64) -> TypeId {
let value = LiteralValue::Number(value);
let type_id = self.get_literal_type(value, TypeFlags::NumberLiteral);
self.get_fresh_type_of_literal_type(type_id)
}
/// ```
/// semantic::check(r#"
/// let a = 1n
/// // ^ bigint
/// "#);
/// ```
fn check_bigint_literal(&mut self, value: &BigUint) -> TypeId {
let value = LiteralValue::BigInt(value.clone(), false);
let type_id = self.get_literal_type(value, TypeFlags::BigInt);
self.get_fresh_type_of_literal_type(type_id)
}
fn check_regexp_literal(&mut self) -> TypeId {
self.types.global_regexp_type_id
}
fn check_templatei_literal(&mut self, lit: &TemplateLiteral<'a>) -> TypeId {
if lit.is_no_substitution_template() {
lit.quasi().map_or(self.types.string_type_id, |quasi| self.check_string_literal(quasi))
} else {
self.types.string_type_id
}
}
/// ```
/// semantic::check(r#"
/// function x(): 3 {};
/// const y = x();
/// // ^? 3
/// let a = String()
/// // ^? string
/// let b = Number()
/// // ^? number
/// let c = Boolean()
/// // ^? boolean
/// "#);
/// ```
fn check_call_expression(&mut self, expr: CallLikeExpression<'a>) -> TypeId {
let signature_id = self.get_resolved_signature(expr);
if matches!(expr, CallLikeExpression::NewExpression(_)) {
if let Some(declaration) = self.signatures[signature_id].declaration {
let scope_id = self.nodes[declaration].scope_id;
if !self.scopes[scope_id].flags.contains(ScopeFlags::Constructor) {
// When resolved signature is a call signature (and not a construct signature) the result type is any
return self.types.any_type_id;
}
}
}
self.get_return_type_of_signature(signature_id)
}
/// ```
/// semantic::check(r#"
/// let a = () => {}
/// // ^? () => void
/// let b = function() {}
/// // ^? () => void
/// "#);
/// ```
fn check_function_expression_or_object_literal_method(&mut self, func: FunctionKind) -> TypeId {
let node = func.node();
let symbol_id = self.symbols.get_symbol_id_of_span(&node.range());
self.get_type_of_symbol(symbol_id)
}
fn check_identifier(&mut self, ident: &IdentifierReference) -> TypeId {
let symbol_id = self.get_resolved_symbol(&ident.name, ident.node.range());
if symbol_id == self.symbols.unknown_symbol_id {
return self.types.error_type_id;
}
self.get_narrowed_type_of_symbol(symbol_id, ident)
}
/// ```
/// semantic::check(r#"
/// let a = [];
/// // ^? any[]
/// let b = [1];
/// // ^? number[]
/// let c = [1, 2];
/// // ^? number[]
/// "#);
/// ```
fn check_array_literal(&mut self, array: &'a ArrayExpression<'a>) -> TypeId {
let mut element_types: Vec<TypeId> = vec![];
for e in (&array.elements).into_iter().flatten() {
if let Argument::Expression(expr) = e {
let type_id = self.check_expression_for_mutable_location(expr);
element_types.push(type_id);
}
}
let element_type = if element_types.is_empty() {
if self.options.strict_null_checks {
self.types.implicit_never_type_id
} else {
self.types.undefined_widening_type_id
}
} else {
self.get_union_type(&element_types, UnionReduction::Subtype)
};
let type_id = self.types.create_array_type(element_type, /* readonly */ false);
self.types.create_array_literal_type(type_id)
}
/// ```
/// semantic::check(r#"
/// let x = 1;
/// x = 1;
/// // ^? number
/// x += 1;
/// // ^? number
/// "#);
/// ```
fn check_assignment_expression(&mut self, expr: &'a AssignmentExpression<'a>) -> TypeId {
let left = match &expr.left {
AssignmentTarget::SimpleAssignmentTarget(target) => {
self.check_simple_assignment_target(target)
}
AssignmentTarget::AssignmentTargetPattern(_) => self.types.any_type_id,
};
let right = self.check_expression(&expr.right);
match expr.operator {
AssignmentOperator::Assign => right,
AssignmentOperator::Addition => self.check_binary_addition(left, right),
AssignmentOperator::LogicalOr => self.check_logical_or(left, right),
AssignmentOperator::LogicalAnd => self.check_logical_and(left, right),
AssignmentOperator::LogicalNullish => self.check_logical_nullish(left, right),
AssignmentOperator::Subtraction
| AssignmentOperator::Multiplication
| AssignmentOperator::Division
| AssignmentOperator::Remainder
| AssignmentOperator::Exponential
| AssignmentOperator::ShiftLeft
| AssignmentOperator::ShiftRight
| AssignmentOperator::ShiftRightZeroFill
| AssignmentOperator::BitwiseOR
| AssignmentOperator::BitwiseXOR
| AssignmentOperator::BitwiseAnd => self.check_binary_arithmetic(left, right),
}
}
fn check_simple_assignment_target(&mut self, target: &'a SimpleAssignmentTarget<'a>) -> TypeId {
match target {
SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => {
self.check_identifier(ident)
}
SimpleAssignmentTarget::TSAsExpression(expr) => self.check_expression(&expr.expression),
SimpleAssignmentTarget::TSNonNullExpression(expr) => {
self.check_expression(&expr.expression)
}
SimpleAssignmentTarget::TSTypeAssertion(expr) => {
self.check_expression(&expr.expression)
}
SimpleAssignmentTarget::MemberAssignmentTarget(expr) => {
self.check_property_access_expression(expr)
}
}
}
/// ```
/// semantic::check(r#"
/// let a = {};
/// // ^? {}
/// let b = { x: "string" };
/// // ^? string
/// "#);
/// ```
fn check_object_literal(&mut self, object: &'a ObjectExpression<'a>) -> TypeId {
let mut members = SymbolMap::default();
let mut properties = vec![];
for object_property in &object.properties {
if let ObjectProperty::Property(property) = object_property {
let symbol_id = self.symbols.get_symbol_id_of_span(&property.node.range());
let symbol_flags = self.symbols[symbol_id].flags;
let resolved_type_id = self.check_property_assignment(property);
if let Some(name) = property.key.static_name() {
let property_symbol_id = self.symbols.create_symbol(
name.as_str(),
property.key.node(),
SymbolFlags::Property | symbol_flags,
);
self.get_symbol_links(property_symbol_id).t.replace(resolved_type_id);
self.get_symbol_links(symbol_id).t.replace(resolved_type_id);
members.insert(name, property_symbol_id);
properties.push(property_symbol_id);
}
}
}
self.types.create_anonymous_type(None, members, properties)
}
/// ```
/// semantic::check(r#"
/// let _ = { x: 1 };
/// // ^? number
/// "#);
/// ```
fn check_property_assignment(&mut self, property: &'a Property<'a>) -> TypeId {
if property.shorthand {
return self.types.any_type_id;
}
match &property.value {
PropertyValue::Expression(expr) => self.check_expression_for_mutable_location(expr),
PropertyValue::Pattern(_) => self.types.any_type_id,
}
}
fn check_binding_identifier(&mut self, ident: &'a BindingIdentifier) {
self.check_variable_like_declaration(VariableLikeDeclaration::BindingIdentifier(ident));
}
fn check_variable_like_declaration(&mut self, decl: VariableLikeDeclaration<'a>) {
let symbol_id = self.symbols.get_symbol_id_of_span(&decl.node().range());
self.get_type_of_symbol(symbol_id);
}
/// ```
/// semantic::check(r#"
/// let _ = "s";
/// // ^? string
/// "#);
/// ```
fn check_declaration_initializer(&mut self, decl: &'a VariableDeclarator<'a>) -> TypeId {
let initializer = decl.init.as_ref().unwrap();
self.get_quick_type_of_expression(initializer)
.unwrap_or_else(|| self.check_expression_cached(initializer))
}
fn check_non_null_expression(&mut self, expr: &'a TSNonNullExpression<'a>) -> TypeId {
self.check_expression(&expr.expression)
}
fn check_assertion_worker(
&mut self,
type_node: &'a TSType<'a>,
expr: &'a Expression<'a>,
) -> TypeId {
if type_node.is_const_type_reference() {
let literal = self.check_expression(expr);
return self.get_regular_type_of_literal_type(literal);
}
self.get_type_from_type_node(type_node)
}
/// ```
/// semantic::check(r#"
/// const x = true ? 1 : '1';
/// // ^? 1 | "1"
/// "#);
/// ```
fn check_conditional_expression(&mut self, expr: &'a ConditionalExpression<'a>) -> TypeId {
let type1 = self.check_expression(&expr.consequent);
let type2 = self.check_expression(&expr.alternate);
self.get_union_type(&[type1, type2], UnionReduction::Subtype)
}
/// ```
/// semantic::check(r#"
/// const a = -1;
/// // ^? -1
/// const b = -0;
/// // ^? 0
/// const c = typeof x;
/// // ^? "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
/// const d = !"";
/// // ^? true
/// "#);
/// ```
fn check_unary_expression(&mut self, expr: &'a UnaryExpression<'a>) -> TypeId {
let operand_type_id = self.check_expression(&expr.argument);
if operand_type_id == self.types.silent_never_type_id {
return self.types.silent_never_type_id;
}
let operator = expr.operator;
let is_plus_or_minus =
operator == UnaryOperator::UnaryPlus || operator == UnaryOperator::UnaryNegation;
match &expr.argument {
Expression::NumberLiteral(literal) if is_plus_or_minus => {
let value = literal.value;
let value = LiteralValue::Number(
if operator == UnaryOperator::UnaryNegation && value != 0.0 {
-*value
} else {
*value
},
);
let lit = self.get_literal_type(value, TypeFlags::NumberLiteral);
return self.get_fresh_type_of_literal_type(lit);
}
Expression::BigintLiteral(literal) if is_plus_or_minus => {
let negative = operator == UnaryOperator::UnaryNegation;
let value = LiteralValue::BigInt(literal.value.clone(), negative);
let lit = self.get_literal_type(value, TypeFlags::BigIntLiteral);
return self.get_fresh_type_of_literal_type(lit);
}
_ => {}
}
match operator {
UnaryOperator::UnaryPlus | UnaryOperator::UnaryNegation | UnaryOperator::BitwiseNot => {
if operator == UnaryOperator::UnaryPlus {
return self.types.number_type_id;
}
self.get_unary_result_type(operand_type_id)
}
UnaryOperator::Delete => self.check_delete_expression(expr),
UnaryOperator::Void => self.check_void_expression(expr),
UnaryOperator::LogicalNot => {
let facts =
self.get_type_facts(operand_type_id) & (TypeFacts::Truthy | TypeFacts::Falsy);
if facts == TypeFacts::Truthy {
self.types.false_type_id
} else if facts == TypeFacts::Falsy {
self.types.true_type_id
} else {
self.types.boolean_type_id
}
}
UnaryOperator::Typeof => self.types.type_of_type_id,
}
}
/// ```
/// semantic::check(r#"
/// let a = 1 === 2;
/// // ^? boolean
/// let b = "x" + "y";
/// // ^? string
/// let c = x in y;
/// // ^? boolean
/// let d = x instanceof y;
/// // ^? boolean
/// let e = 1 + 1;
/// // ^? number
/// "#);
/// ```
fn check_binary_expression(&mut self, expr: &'a BinaryExpression<'a>) -> TypeId {
let left = self.check_expression_cached(&expr.left);
let right = self.check_expression_cached(&expr.right);
match expr.operator {
BinaryOperator::Equality
| BinaryOperator::Inequality
| BinaryOperator::StrictEquality
| BinaryOperator::StrictInequality
| BinaryOperator::LessThan
| BinaryOperator::LessEqualThan
| BinaryOperator::GreaterThan
| BinaryOperator::GreaterEqualThan => self.types.boolean_type_id,
BinaryOperator::Addition => self.check_binary_addition(left, right),
BinaryOperator::Subtraction
| BinaryOperator::Multiplication
| BinaryOperator::Division
| BinaryOperator::Remainder
| BinaryOperator::Exponential
| BinaryOperator::ShiftLeft
| BinaryOperator::ShiftRight
| BinaryOperator::ShiftRightZeroFill
| BinaryOperator::BitwiseOR
| BinaryOperator::BitwiseXOR
| BinaryOperator::BitwiseAnd => self.check_binary_arithmetic(left, right),
BinaryOperator::Instanceof => {
self.check_instance_of_expression(&expr.left, &expr.right, left, right)
}
BinaryOperator::In => self.check_in_expression(&expr.left, &expr.right, left, right),
}
}
fn check_binary_addition(&mut self, left_type: TypeId, right_type: TypeId) -> TypeId {
let mut left_type = left_type;
let mut right_type = right_type;
if left_type == self.types.silent_never_type_id
|| right_type == self.types.silent_never_type_id
{
return self.types.silent_never_type_id;
}
if !self.types.is_type_assignable_to_kind(left_type, TypeFlags::StringLike, false)
&& !self.types.is_type_assignable_to_kind(right_type, TypeFlags::StringLike, false)
{
left_type = self.check_non_null_type(left_type);
right_type = self.check_non_null_type(right_type);
}
let result =
if self.types.is_type_assignable_to_kind(left_type, TypeFlags::NumberLike, true)
&& self.types.is_type_assignable_to_kind(right_type, TypeFlags::NumberLike, true)
{
Some(self.types.number_type_id)
} else if self.types.is_type_assignable_to_kind(left_type, TypeFlags::BigIntLike, true)
&& self.types.is_type_assignable_to_kind(right_type, TypeFlags::BigIntLike, true)
{
Some(self.types.bigint_type_id)
} else if self.types.is_type_assignable_to_kind(left_type, TypeFlags::StringLike, true)
|| self.types.is_type_assignable_to_kind(right_type, TypeFlags::StringLike, true)
{
Some(self.types.string_type_id)
} else {
None
};
result.unwrap_or(self.types.any_type_id)
}
fn check_binary_arithmetic(&mut self, left_type: TypeId, right_type: TypeId) -> TypeId {
if left_type == self.types.silent_never_type_id
|| right_type == self.types.silent_never_type_id
{
return self.types.silent_never_type_id;
}
self.types.number_type_id
}
fn check_chain_expression(&mut self, chain: &'a ChainExpression<'a>) -> TypeId {
match &chain.expression {
ChainElement::CallExpression(e) => {
self.check_call_expression(CallLikeExpression::CallExpression(e))
}
ChainElement::MemberExpression(e) => self.check_member_expression(e),
}
}
/// ```
/// semantic::check(r#"
/// let a = x instanceof y;
/// // ^? boolean
/// "#);
/// ```
fn check_instance_of_expression(
&mut self,
_left: &Expression<'a>,
_right: &Expression<'a>,
_left_type_id: TypeId,
_right_type_id: TypeId,
) -> TypeId {
self.types.boolean_type_id
}
/// ```
/// semantic::check(r#"
/// let a = x in y;
/// // ^? boolean
/// "#);
/// ```
fn check_in_expression(
&mut self,
_left: &Expression<'a>,
_right: &Expression<'a>,
_left_type_id: TypeId,
_right_type_id: TypeId,
) -> TypeId {
self.types.boolean_type_id
}
// TODO
fn check_property_access_expression(&mut self, _expr: &MemberExpression<'a>) -> TypeId {
self.types.any_type_id
}
/// ```
/// semantic::check(r#"
/// let x = ("1", true, 3);
/// // ^? number
/// "#);
/// ```
fn check_sequence_expression(&mut self, expr: &'a SequenceExpression<'a>) -> TypeId {
let len = expr.expressions.len();
for (i, e) in expr.expressions.iter().enumerate() {
let type_id = self.check_expression_cached(e);
if i == len - 1 {
return type_id;
}
}
// for empty sequence expression from incorrect AST
self.types.error_type_id
}
/// ```
/// semantic::check(r#"
/// let x = 1;
/// const y = ++x;
/// // ^? number
/// "#);
/// ```
fn check_update_expression(&mut self, expr: &'a UpdateExpression<'a>) -> TypeId {
let operand_type = self.check_simple_assignment_target(&expr.argument);
self.get_unary_result_type(operand_type)
}
fn check_logical_expression(&mut self, expr: &'a LogicalExpression<'a>) -> TypeId {
let left_type_id = self.check_expression_cached(&expr.left);
let right_type_id = self.check_expression_cached(&expr.right);
match expr.operator {
LogicalOperator::Or => self.check_logical_or(left_type_id, right_type_id),
LogicalOperator::And => self.check_logical_and(left_type_id, right_type_id),
LogicalOperator::Coalesce => self.check_logical_nullish(left_type_id, right_type_id),
}
}
/// ```
/// semantic::check(r#"
/// let a = null || "string";
/// // ^? "string"
/// "#);
/// ```
fn check_logical_or(&mut self, left_type_id: TypeId, right_type_id: TypeId) -> TypeId {
if self.get_type_facts(left_type_id).intersects(TypeFacts::Falsy) {
let left_type_id = self.remove_definitely_falsy_types(left_type_id);
let left_type_id = self.get_non_nullable_type(left_type_id);
self.get_union_type(&[left_type_id, right_type_id], UnionReduction::Subtype)
} else {
left_type_id
}
}
/// ```
/// semantic::check(r#"
/// let a = true && false;
/// // ^? false
/// "#);
/// ```
fn check_logical_and(&mut self, left_type_id: TypeId, right_type_id: TypeId) -> TypeId {
if self.get_type_facts(left_type_id).intersects(TypeFacts::Truthy) {
let left_type_id = if self.options.strict_null_checks {
left_type_id
} else {
self.get_base_type_of_literal_type(right_type_id)
};
let left_type_id = self.extract_definitely_falsy_types(left_type_id);
self.get_union_type(&[left_type_id, right_type_id], UnionReduction::Literal)
} else {
left_type_id
}
}
/// ```
/// semantic::check(r#"
/// let a = null ?? "string";
/// // ^? "string"
/// "#);
/// ```
fn check_logical_nullish(&mut self, left_type_id: TypeId, right_type_id: TypeId) -> TypeId {
if self.get_type_facts(left_type_id).intersects(TypeFacts::EQUndefinedOrNull) {
let left_type_id = self.get_non_nullable_type(left_type_id);
self.get_union_type(&[left_type_id, right_type_id], UnionReduction::Subtype)
} else {
left_type_id
}
}
fn check_member_expression(&mut self, _expr: &MemberExpression<'a>) -> TypeId {
self.types.any_type_id
}
/// ```
/// semantic::check(r#"
/// let x = 1;
/// const y = delete x;
/// // ^? boolean
/// "#);
/// ```
fn check_delete_expression(&mut self, _expr: &UnaryExpression<'a>) -> TypeId {
self.types.boolean_type_id
}
/// ```
/// semantic::check(r#"
/// let x = void 0;
/// // ^? undefined
/// "#);
/// ```
fn check_void_expression(&mut self, _expr: &UnaryExpression<'a>) -> TypeId {
self.types.undefined_type_id
}
// TODO
fn check_this_expression(&mut self) -> TypeId {
self.types.any_type_id
}
// TODO
fn check_expression_with_type_arguments(
&mut self,
expr: ExpressionWithTypeArguments<'a>,
) -> TypeId {
match expr {
ExpressionWithTypeArguments::TSInstantiationExpression(expr) => {
self.check_expression(&expr.expression)
}
ExpressionWithTypeArguments::TSTypeQuery(query) => match &query.expr_name {
TSTypeName::IdentifierName(ident) => {
if ident.name == "this" {
self.check_this_expression()
} else {
self.check_identifier(&IdentifierReference {
name: ident.name.clone(),
node: ident.node,
})
}
}
// TODO
TSTypeName::QualifiedName(_name) => self.types.any_type_id,
},
}
}
/// ```
/// semantic::check(r#"
/// "use strict";
/// // ^? "use strict"
/// "#);
/// ```
fn check_directive(&mut self, directive: &Directive<'a>) {
self.with_resolved_type(directive.expression.node.range(), |builder| {
builder.check_string_literal(&directive.expression.value)
});
}
/// ```
/// semantic::check(r#"
/// function x() { if (1) {return 3} else {return '4'} };
/// const a = x();
/// // ^? 3 | "4"
/// "#);
/// ```
fn check_and_aggregate_return_expression_types(
&mut self,
body: &FunctionBody<'a>,
) -> Option<Vec<TypeId>> {
let mut aggregated_types = vec![];
let mut has_return_with_no_expression = false;
let mut has_return_of_never_type = false;
if let Some(return_nodes) = self
.span_to_node
.get(&body.node.range())
.and_then(|ast_node| self.nodes.function_return_statements.get(ast_node).cloned())
{
for node_id in return_nodes {
if let AstKind::ReturnStatement(stmt) = self.nodes[node_id].kind {
if let Some(argument) = &stmt.argument {
let type_id = self.check_expression_cached(argument);
let r#type = &self.types[type_id];
if r#type.flags.intersects(TypeFlags::Never) {
has_return_of_never_type = true;
}
aggregated_types.push(type_id);
} else {
has_return_with_no_expression = true;
}
}
}
}
if aggregated_types.is_empty() && !has_return_with_no_expression && has_return_of_never_type
{
return None;
}
Some(aggregated_types)
}
/// ```
/// semantic::check(r#"
/// class A {}
/// // ^? A
/// "#);
/// ```
// ClassLikeDeclaration = | ClassDeclaration | ClassExpression
fn check_class_like_declaration(&mut self, class: &Class<'a>) -> TypeId {
let symbol_id = self.symbols.get_symbol_id_of_span(&class.node.range());
self.get_declared_type_of_symbol(symbol_id)
}
/// ```
/// semantic::check(r#"
/// enum A {}
/// // ^? A
/// "#);
/// ```
fn check_enum_declaration(&mut self, decl: &TSEnumDeclaration<'a>) -> TypeId {
let symbol_id = self.symbols.get_symbol_id_of_span(&decl.node.range());
self.get_declared_type_of_symbol(symbol_id)
}
/// ```
/// semantic::check(r#"
/// namespace A {}
/// // ^? typeof A
/// "#);
/// ```
fn check_module_declaration(&mut self, decl: &TSModuleDeclaration<'a>) -> TypeId {
let symbol_id = self.symbols.get_symbol_id_of_span(&decl.node.range());
self.get_type_of_symbol(symbol_id)
}
}
use std::ops::{Index, IndexMut};
use estree::BigUint;
use rustc_hash::FxHashMap;
use super::{
IntrinsicType, LiteralType, LiteralValue, ObjectFlags, ObjectType, Type, TypeFlags, TypeId,
TypeKind, UnionType,
};
use crate::{
symbol::{SymbolId, SymbolMap, SymbolTable},
SemanticBuilderOptions,
};
const TYPE_OF_TYPES: &[&str; 8] =
&["string", "number", "bigint", "boolean", "symbol", "undefined", "object", "function"];
#[derive(Debug, Default)]
pub struct TypeTable {
types: Vec<Type>,
pub literal_values: FxHashMap<LiteralValue, TypeId>,
// NOTE: These must be declared in the same declaration order per TypeScript's source.
// The order of union types are sorted by TypeId
pub any_type_id: TypeId,
pub wildcard_type_id: TypeId,
pub error_type_id: TypeId,
pub unknown_type_id: TypeId,
pub non_null_unknown_type_id: TypeId,
pub undefined_type_id: TypeId,
pub undefined_widening_type_id: TypeId,
pub null_type_id: TypeId,
pub null_widening_type_id: TypeId,
pub string_type_id: TypeId,
pub number_type_id: TypeId,
pub bigint_type_id: TypeId,
pub false_type_id: TypeId,
pub regular_false_type_id: TypeId,
pub true_type_id: TypeId,
pub regular_true_type_id: TypeId,
pub boolean_type_id: TypeId,
pub es_symbol_type_id: TypeId,
pub void_type_id: TypeId,
pub never_type_id: TypeId,
pub silent_never_type_id: TypeId,
pub implicit_never_type_id: TypeId,
pub non_primitive_type_id: TypeId,
pub empty_object_type_id: TypeId,
pub unknown_union_type_id: TypeId,
pub type_of_type_id: TypeId,
// Global Types
// these should eventually be read from a .d.ts file
pub global_array_type_id: TypeId, // "Array"
pub global_object_type_id: TypeId, // "Object"
pub global_function_type_id: TypeId, // "Function"
pub global_string_type_id: TypeId, // "String"
pub global_number_type_id: TypeId, // "Number"
pub global_boolean_type_id: TypeId, // "Boolean"
pub global_regexp_type_id: TypeId, // "RegExp"
pub global_promise_type_id: TypeId, // "Promise"
pub empty_string_type_id: TypeId,
pub zero_type_id: TypeId,
pub zero_big_int_type_id: TypeId,
}
impl Index<TypeId> for TypeTable {
type Output = Type;
fn index(&self, index: TypeId) -> &Self::Output {
&self.types[index.index0()]
}
}
impl IndexMut<TypeId> for TypeTable {
fn index_mut(&mut self, index: TypeId) -> &mut Self::Output {
&mut self.types[index.index0()]
}
}
/// Type Factory Helpers
impl TypeTable {
#[rustfmt::skip]
#[must_use]
pub fn new(options: &SemanticBuilderOptions, symbols: &SymbolTable) -> Self {
let mut table = Self::default();
// NOTE: declaration order matters here, as union types are sorted by TypeId
table.any_type_id = table.create_intrinsic_type(TypeFlags::Any, "any");
table.wildcard_type_id = table.create_intrinsic_type(TypeFlags::Any, "any");
table.error_type_id = table.create_intrinsic_type(TypeFlags::Any, "error");
table.unknown_type_id = table.create_intrinsic_type(TypeFlags::Unknown, "unknown");
table.non_null_unknown_type_id = table.create_intrinsic_type(TypeFlags::Unknown, "unknown");
table.undefined_type_id = table.create_intrinsic_type(TypeFlags::Undefined, "undefined");
table.undefined_widening_type_id = if options.strict_null_checks { table.unknown_type_id } else {
table.create_intrinsic_type_with_flags(TypeFlags::Undefined, "undefined", ObjectFlags::ContainsWideningType)
};
table.null_type_id = table.create_intrinsic_type(TypeFlags::Null, "null");
table.null_widening_type_id = if options.strict_null_checks { table.null_type_id } else {
table.create_intrinsic_type_with_flags(TypeFlags::Null, "null", ObjectFlags::ContainsWideningType)
};
table.string_type_id = table.create_intrinsic_type(TypeFlags::String, "string");
table.number_type_id = table.create_intrinsic_type(TypeFlags::Number, "number");
table.bigint_type_id = table.create_intrinsic_type(TypeFlags::BigInt, "bigint");
let false_type_id = table.create_intrinsic_type(TypeFlags::BooleanLiteral, "false");
let regular_false_type_id = table.create_intrinsic_type(TypeFlags::BooleanLiteral, "false");
let true_type_id = table.create_intrinsic_type(TypeFlags::BooleanLiteral, "true");
let regular_true_type_id = table.create_intrinsic_type(TypeFlags::BooleanLiteral, "true");
table.false_type_id = false_type_id;
table.regular_false_type_id = regular_false_type_id;
table.true_type_id = true_type_id;
table.regular_true_type_id = regular_true_type_id;
table[true_type_id].set_regular_type(regular_true_type_id);
table[true_type_id].set_fresh_type(true_type_id);
table[regular_true_type_id].set_regular_type(regular_true_type_id);
table[regular_true_type_id].set_fresh_type(true_type_id);
table[false_type_id].set_regular_type(regular_false_type_id);
table[false_type_id].set_fresh_type(false_type_id);
table[regular_false_type_id].set_regular_type(regular_false_type_id);
table[regular_false_type_id].set_fresh_type(false_type_id);
table.boolean_type_id = table.create_union_type(vec![table.regular_false_type_id, table.regular_true_type_id]);
table.es_symbol_type_id = table.create_intrinsic_type(TypeFlags::ESSymbol, "symbol");
table.void_type_id = table.create_intrinsic_type(TypeFlags::Void, "void");
table.never_type_id = table.create_intrinsic_type(TypeFlags::Never, "never");
table.silent_never_type_id = table.create_intrinsic_type_with_flags(TypeFlags::Never, "never", ObjectFlags::NonInferrableType);
table.implicit_never_type_id = table.create_intrinsic_type(TypeFlags::Never, "never");
table.non_primitive_type_id = table.create_intrinsic_type(TypeFlags::NonPrimitive, "object");
table.empty_object_type_id = table.create_anonymous_type(None, SymbolMap::default(), vec![]);
// table.unknown_union_type_id = if options.strict_null_checks { table.get_union_type([table.undefined_type_id, table.null_type_id, table.unknown_empty_object_type_id]) } else { table.unknown_type_id };
let typeof_types = TYPE_OF_TYPES.iter().map(|t| table.get_string_literal_type(t)).collect();
table.type_of_type_id = table.create_union_type(typeof_types);
table.global_array_type_id = table.create_object_type(ObjectFlags::ClassOrInterface, Some(symbols.global_array_symbol_id));
table.global_object_type_id = table.create_object_type(ObjectFlags::Interface, Some(symbols.global_object_symbol_id));
table.global_function_type_id = table.create_object_type(ObjectFlags::Interface, Some(symbols.global_function_symbol_id));
table.global_string_type_id = table.create_object_type(ObjectFlags::ClassOrInterface, Some(symbols.global_string_symbol_id));
table.global_number_type_id = table.create_object_type(ObjectFlags::Interface, Some(symbols.global_number_symbol_id));
table.global_boolean_type_id = table.create_object_type(ObjectFlags::Interface, Some(symbols.global_boolean_symbol_id));
table.global_regexp_type_id = table.create_object_type(ObjectFlags::ClassOrInterface, Some(symbols.global_regexp_symbol_id));
table.global_promise_type_id = table.create_object_type(ObjectFlags::ClassOrInterface, Some(symbols.global_promise_symbol_id));
table.empty_string_type_id = table.get_string_literal_type("");
table.zero_type_id = table.get_number_literal_type(0.0);
table.zero_big_int_type_id = table.get_big_int_literal_type(BigUint::from(0u8), false);
table
}
#[must_use]
pub fn create_type(&mut self, flags: TypeFlags) -> &mut Type {
let id = TypeId::new(self.types.len() + 1);
let t = Type::new(id, flags);
self.types.push(t);
&mut self.types[id]
}
#[must_use]
pub fn create_intrinsic_type(&mut self, flags: TypeFlags, intrinsic_name: &str) -> TypeId {
let mut t = self.create_type(flags);
t.kind = TypeKind::IntrinsicType(IntrinsicType::new(intrinsic_name));
t.id
}
#[must_use]
pub fn create_intrinsic_type_with_flags(
&mut self,
flags: TypeFlags,
intrinsic_name: &str,
object_flags: ObjectFlags,
) -> TypeId {
let mut t = self.create_type(flags);
t.kind = TypeKind::IntrinsicType(IntrinsicType {
object_flags,
..IntrinsicType::new(intrinsic_name)
});
t.id
}
#[must_use]
pub fn create_literal_type(
&mut self,
flags: TypeFlags,
value: LiteralValue,
symbol_id: Option<SymbolId>,
regular_type: Option<TypeId>,
) -> TypeId {
let type_id = {
let mut ty = self.create_type(flags);
ty.kind = TypeKind::LiteralType(LiteralType::new(value.clone()));
ty.symbol_id = symbol_id;
ty.set_regular_type(regular_type.unwrap_or(ty.id));
ty.id
};
self.literal_values.insert(value, type_id);
type_id
}
#[must_use]
pub fn create_object_type(
&mut self,
object_flags: ObjectFlags,
symbol_id: Option<SymbolId>,
) -> TypeId {
let mut t = self.create_type(TypeFlags::Object);
t.kind = TypeKind::ObjectType(ObjectType::new(object_flags));
t.symbol_id = symbol_id;
t.id
}
#[must_use]
pub fn create_array_literal_type(&mut self, type_id: TypeId) -> TypeId {
let ty = &self.types[type_id];
if !ty.object_flags().contains(ObjectFlags::Reference) {
return type_id;
}
match &ty.kind {
TypeKind::ObjectType(object_type) => {
if let Some(literal_type) = object_type.literal_type {
return literal_type;
}
let literal_type_id = self.clone_type_reference(ty.id);
if let TypeKind::ObjectType(object_type) = &mut self.types[type_id].kind {
object_type.literal_type = Some(literal_type_id);
object_type.object_flags |=
ObjectFlags::ArrayLiteral | ObjectFlags::ContainsObjectOrArrayLiteral;
}
literal_type_id
}
_ => unreachable!(),
}
}
#[must_use]
pub fn create_array_type(&mut self, element_type: TypeId, _readonly: bool) -> TypeId {
self.create_type_from_generic_global_type(self.global_array_type_id, vec![element_type])
}
#[must_use]
pub fn create_type_from_generic_global_type(
&mut self,
generic_global_type: TypeId,
type_arguments: Vec<TypeId>,
) -> TypeId {
self.create_type_reference(generic_global_type, type_arguments)
}
// TODO: add instantiations cache
#[must_use]
pub fn create_type_reference(&mut self, target: TypeId, type_arguments: Vec<TypeId>) -> TypeId {
let ty = &self.types[target];
let type_id = self.create_object_type(ObjectFlags::Reference, ty.symbol_id);
let object_flags = self.get_propagating_flags_of_types(&type_arguments, None);
match &mut self.types[type_id].kind {
TypeKind::ObjectType(object_type) => {
object_type.target = target;
object_type.object_flags |= object_flags;
object_type.resolved_type_arguments = type_arguments;
}
_ => unreachable!(),
};
type_id
}
#[must_use]
pub fn create_union_type(&mut self, types: Vec<TypeId>) -> TypeId {
let mut t = self.create_type(TypeFlags::Union);
t.kind = TypeKind::UnionType(UnionType { types });
t.id
}
#[must_use]
pub fn create_anonymous_type(
&mut self,
symbol_id: Option<SymbolId>,
members: SymbolMap,
properties: Vec<SymbolId>,
) -> TypeId {
let type_id = self.create_object_type(ObjectFlags::Anonymous, symbol_id);
self.set_structured_type_members(type_id, members, properties)
}
#[must_use]
pub fn create_enum_type(&mut self, symbol_id: SymbolId) -> TypeId {
let mut t = self.create_type(TypeFlags::Enum);
t.symbol_id = Some(symbol_id);
t.id
}
pub fn create_promise_type(&mut self, _promised_type: TypeId) -> TypeId {
// TODO
self.global_promise_type_id
}
#[must_use]
fn clone_type_reference(&mut self, type_id: TypeId) -> TypeId {
let source = &self.types[type_id];
let flags = source.flags;
let kind = source.kind.clone();
let t = self.create_type(flags);
t.kind = kind;
t.id
}
fn set_structured_type_members(
&mut self,
type_id: TypeId,
members: SymbolMap,
properties: Vec<SymbolId>,
) -> TypeId {
if let TypeKind::ObjectType(object_type) = &mut self.types[type_id].kind {
object_type.members = members;
object_type.properties = properties;
}
type_id
}
}
/// Type Getter Helpers
impl TypeTable {
#[must_use]
pub fn get_string_literal_type(&mut self, value: &str) -> TypeId {
let value = LiteralValue::String(value.into());
self.literal_values.get(&value).copied().unwrap_or_else(|| {
let type_id =
self.create_literal_type(TypeFlags::StringLiteral, value.clone(), None, None);
self.literal_values.insert(value, type_id);
type_id
})
}
#[must_use]
pub fn get_number_literal_type(&mut self, value: f64) -> TypeId {
let value = LiteralValue::Number(value);
self.literal_values.get(&value).copied().unwrap_or_else(|| {
let type_id =
self.create_literal_type(TypeFlags::NumberLiteral, value.clone(), None, None);
self.literal_values.insert(value, type_id);
type_id
})
}
#[must_use]
pub fn get_big_int_literal_type(&mut self, value: BigUint, negative: bool) -> TypeId {
let value = LiteralValue::BigInt(value, negative);
self.literal_values.get(&value).copied().unwrap_or_else(|| {
let type_id =
self.create_literal_type(TypeFlags::BigIntLiteral, value.clone(), None, None);
self.literal_values.insert(value, type_id);
type_id
})
}
// This function is used to propagate certain flags when creating new object type references and union types.
// It is only necessary to do so if a constituent type might be the undefined type, the null type, the type
// of an object literal or a non-inferrable type. This is because there are operations in the type checker
// that care about the presence of such types at arbitrary depth in a containing type.
#[must_use]
pub fn get_propagating_flags_of_types(
&self,
type_ids: &[TypeId],
exclude_kinds: Option<TypeFlags>,
) -> ObjectFlags {
let mut result = ObjectFlags::empty();
for type_id in type_ids {
let ty = &self.types[*type_id];
if exclude_kinds.map_or(true, |f| !f.intersects(ty.flags)) {
result |= ty.get_object_flags();
}
}
result & ObjectFlags::PropagatingFlags
}
}
/// Type Checking Helpers
impl TypeTable {
#[must_use]
pub fn is_object_literal_type(&self, ty: &Type) -> bool {
ty.get_object_flags().intersects(ObjectFlags::ObjectLiteral)
}
#[must_use]
pub fn is_array_type(&self, ty: &Type) -> bool {
ty.get_object_flags().intersects(ObjectFlags::Reference)
&& match &ty.kind {
// TODO: (type as TypeReference).target === globalReadonlyArrayType);
TypeKind::ObjectType(object_type) => {
object_type.target == self.global_array_type_id
}
_ => false,
}
}
#[must_use]
pub const fn is_tuple_type(&self, _ty: &Type) -> bool {
// return !!(getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target.objectFlags & ObjectFlags.Tuple);
false
}
#[must_use]
pub fn is_array_or_tuple_type(&self, ty: &Type) -> bool {
self.is_array_type(ty) || self.is_tuple_type(ty)
}
#[must_use]
pub fn is_type_identifical_to(&self, source: TypeId, target: TypeId) -> bool {
self.is_type_related_to(source, target /* identity_relation */)
}
#[must_use]
pub fn is_type_assignable_to(&self, source: TypeId, target: TypeId) -> bool {
self.is_type_related_to(source, target /* assignableRelation */)
}
#[must_use]
pub fn is_type_related_to(&self, source: TypeId, target: TypeId /* relation */) -> bool {
if source == target {
return true;
}
let source = &self.types[source];
let target = &self.types[target];
if source.id == target.id {
return true;
}
if self.is_simple_type_replated_to(source, target)
|| self.is_simple_type_replated_to(target, source)
{
return true;
}
false
}
#[must_use]
#[allow(clippy::unused_self)]
const fn is_simple_type_replated_to(&self, source: &Type, target: &Type) -> bool {
let s = source.flags;
let t = target.flags;
if s.intersects(TypeFlags::StringLike) && t.intersects(TypeFlags::String) {
return true;
}
false
}
#[must_use]
pub fn is_type_assignable_to_kind(
&self,
source_id: TypeId,
kind: TypeFlags,
strict: bool,
) -> bool {
let source = &self.types[source_id];
if source.flags.intersects(kind) {
return true;
}
if strict
&& source.flags.intersects(
TypeFlags::AnyOrUnknown | TypeFlags::Void | TypeFlags::Undefined | TypeFlags::Null,
)
{
return false;
}
kind.intersects(TypeFlags::NumberLike)
&& self.is_type_assignable_to(source_id, self.number_type_id)
|| kind.intersects(TypeFlags::BigIntLike)
&& self.is_type_assignable_to(source_id, self.bigint_type_id)
|| kind.intersects(TypeFlags::StringLike)
&& self.is_type_assignable_to(source_id, self.string_type_id)
|| kind.intersects(TypeFlags::BooleanLike)
&& self.is_type_assignable_to(source_id, self.boolean_type_id)
|| kind.intersects(TypeFlags::Void)
&& self.is_type_assignable_to(source_id, self.void_type_id)
|| kind.intersects(TypeFlags::Never)
&& self.is_type_assignable_to(source_id, self.never_type_id)
|| kind.intersects(TypeFlags::Null)
&& self.is_type_assignable_to(source_id, self.null_type_id)
|| kind.intersects(TypeFlags::Undefined)
&& self.is_type_assignable_to(source_id, self.undefined_type_id)
|| kind.intersects(TypeFlags::ESSymbol)
&& self.is_type_assignable_to(source_id, self.es_symbol_type_id)
|| kind.intersects(TypeFlags::NonPrimitive)
&& self.is_type_assignable_to(source_id, self.non_primitive_type_id)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment