use crate::CheckerContext;
use crate::context::CheckerOptions;
use tsz_binder::BinderState;
use tsz_binder::SymbolId;
use tsz_parser::parser::NodeIndex;
use tsz_parser::parser::node::NodeArena;
use tsz_parser::parser::syntax_kind_ext;
use tsz_solver::{QueryDatabase, TypeId, substitute_this_type};
thread_local! {
static CROSS_ARENA_DEPTH: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
}
pub struct CheckerState<'a> {
pub ctx: CheckerContext<'a>,
}
pub use tsz_common::limits::MAX_CALL_DEPTH;
pub use tsz_common::limits::MAX_INSTANTIATION_DEPTH;
pub use tsz_common::limits::MAX_TREE_WALK_ITERATIONS;
pub use tsz_common::limits::MAX_TYPE_RESOLUTION_OPS;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum EnumKind {
Numeric,
String,
Mixed,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum MemberAccessLevel {
Private,
Protected,
}
#[derive(Clone, Debug)]
pub(crate) struct MemberAccessInfo {
pub(crate) level: MemberAccessLevel,
pub(crate) declaring_class_idx: NodeIndex,
pub(crate) declaring_class_name: String,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum MemberLookup {
NotFound,
Public,
Restricted(MemberAccessLevel),
}
pub(crate) use crate::flow_analysis::{ComputedKey, PropertyKey};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum ParamTypeResolutionMode {
InTypeLiteral,
FromTypeNode,
OfNode,
}
pub(crate) struct CheckerOverrideProvider<'a, 'b> {
checker: &'a CheckerState<'b>,
env: Option<&'a tsz_solver::TypeEnvironment>,
}
impl<'a, 'b> CheckerOverrideProvider<'a, 'b> {
pub(crate) const fn new(
checker: &'a CheckerState<'b>,
env: Option<&'a tsz_solver::TypeEnvironment>,
) -> Self {
Self { checker, env }
}
}
impl<'a, 'b> tsz_solver::AssignabilityOverrideProvider for CheckerOverrideProvider<'a, 'b> {
fn enum_assignability_override(&self, _source: TypeId, _target: TypeId) -> Option<bool> {
None
}
fn abstract_constructor_assignability_override(
&self,
source: TypeId,
target: TypeId,
) -> Option<bool> {
self.checker
.abstract_constructor_assignability_override(source, target, self.env)
}
fn constructor_accessibility_override(&self, source: TypeId, target: TypeId) -> Option<bool> {
self.checker
.constructor_accessibility_override(source, target, self.env)
}
}
impl<'a> CheckerState<'a> {
pub fn new(
arena: &'a NodeArena,
binder: &'a BinderState,
types: &'a dyn QueryDatabase,
file_name: String,
compiler_options: CheckerOptions,
) -> Self {
CheckerState {
ctx: CheckerContext::new(arena, binder, types, file_name, compiler_options),
}
}
pub fn new_with_shared_def_store(
arena: &'a NodeArena,
binder: &'a BinderState,
types: &'a dyn QueryDatabase,
file_name: String,
compiler_options: CheckerOptions,
definition_store: std::sync::Arc<tsz_solver::def::DefinitionStore>,
) -> Self {
CheckerState {
ctx: CheckerContext::new_with_shared_def_store(
arena,
binder,
types,
file_name,
compiler_options,
definition_store,
),
}
}
pub fn with_cache(
arena: &'a NodeArena,
binder: &'a BinderState,
types: &'a dyn QueryDatabase,
file_name: String,
cache: crate::TypeCache,
compiler_options: CheckerOptions,
) -> Self {
CheckerState {
ctx: CheckerContext::with_cache(
arena,
binder,
types,
file_name,
cache,
compiler_options,
),
}
}
pub fn with_parent_cache(
arena: &'a NodeArena,
binder: &'a BinderState,
types: &'a dyn QueryDatabase,
file_name: String,
compiler_options: CheckerOptions,
parent: &Self,
) -> Self {
CheckerState {
ctx: CheckerContext::with_parent_cache(
arena,
binder,
types,
file_name,
compiler_options,
&parent.ctx,
),
}
}
pub(crate) fn enter_cross_arena_delegation() -> bool {
let d = CROSS_ARENA_DEPTH.with(std::cell::Cell::get);
if d >= 5 {
return false;
}
CROSS_ARENA_DEPTH.with(|c| c.set(d + 1));
true
}
pub(crate) fn leave_cross_arena_delegation() {
CROSS_ARENA_DEPTH.with(|c| c.set(c.get().saturating_sub(1)));
}
fn should_apply_flow_narrowing_for_identifier(&self, idx: NodeIndex) -> bool {
use tsz_binder::symbol_flags;
use tsz_scanner::SyntaxKind;
if self.ctx.skip_flow_narrowing {
return false;
}
let Some(node) = self.ctx.arena.get(idx) else {
return false;
};
if node.kind == SyntaxKind::ThisKeyword as u16 {
return true;
}
if node.kind != SyntaxKind::Identifier as u16 {
return false;
}
let Some(sym_id) = self
.ctx
.binder
.get_node_symbol(idx)
.or_else(|| self.ctx.binder.resolve_identifier(self.ctx.arena, idx))
else {
return false;
};
let Some(symbol) = self.ctx.binder.get_symbol(sym_id) else {
return false;
};
if (symbol.flags & symbol_flags::VARIABLE) == 0 {
return false;
}
let mut value_decl = symbol.value_declaration;
if value_decl.is_none() {
return true;
}
let Some(mut decl_node) = self.ctx.arena.get(value_decl) else {
return true;
};
if decl_node.kind == SyntaxKind::Identifier as u16
&& let Some(ext) = self.ctx.arena.get_extended(value_decl)
&& ext.parent.is_some()
&& let Some(parent_node) = self.ctx.arena.get(ext.parent)
&& parent_node.kind == syntax_kind_ext::VARIABLE_DECLARATION
{
value_decl = ext.parent;
decl_node = parent_node;
}
if decl_node.kind != syntax_kind_ext::VARIABLE_DECLARATION {
return true;
}
if !self.is_const_variable_declaration(value_decl) {
return true;
}
let Some(var_decl) = self.ctx.arena.get_variable_declaration(decl_node) else {
return true;
};
if var_decl.type_annotation.is_some() || var_decl.initializer.is_none() {
return true;
}
let Some(init_node) = self.ctx.arena.get(var_decl.initializer) else {
return true;
};
!(init_node.kind == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
|| init_node.kind == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION)
}
pub(crate) fn is_in_cross_arena_delegation() -> bool {
CROSS_ARENA_DEPTH.with(|c| c.get() > 0)
}
pub(crate) const fn has_parse_errors(&self) -> bool {
self.ctx.has_parse_errors
}
pub(crate) const fn has_syntax_parse_errors(&self) -> bool {
self.ctx.has_syntax_parse_errors
}
pub(crate) fn node_span_contains_parse_error(&self, idx: NodeIndex) -> bool {
if !self.has_syntax_parse_errors() || self.ctx.syntax_parse_error_positions.is_empty() {
return false;
}
let Some(node) = self.ctx.arena.get(idx) else {
return false;
};
for &err_pos in &self.ctx.syntax_parse_error_positions {
if err_pos >= node.pos && err_pos < node.end {
return true;
}
}
false
}
pub(crate) fn node_has_nearby_parse_error(&self, idx: NodeIndex) -> bool {
if !self.has_syntax_parse_errors() || self.ctx.syntax_parse_error_positions.is_empty() {
return false;
}
let Some(node) = self.ctx.arena.get(idx) else {
return false;
};
const MARGIN: u32 = 8;
let node_start = node.pos.saturating_sub(MARGIN);
let node_end = node.end.saturating_add(MARGIN);
for &err_pos in &self.ctx.syntax_parse_error_positions {
if err_pos >= node_start && err_pos <= node_end {
return true;
}
}
false
}
pub(crate) fn apply_this_substitution_to_call_return(
&mut self,
return_type: tsz_solver::TypeId,
call_expression: tsz_parser::parser::NodeIndex,
) -> tsz_solver::TypeId {
use tsz_solver::TypeId;
if return_type.is_intrinsic() {
return return_type;
}
let node = match self.ctx.arena.get(call_expression) {
Some(n) => n,
None => return return_type,
};
if let Some(access) = self.ctx.arena.get_access_expr(node) {
let receiver_type = self.get_type_of_node(access.expression);
if receiver_type != TypeId::ERROR && receiver_type != TypeId::ANY {
return substitute_this_type(self.ctx.types, return_type, receiver_type);
}
}
return_type
}
pub fn with_options(
arena: &'a NodeArena,
binder: &'a BinderState,
types: &'a dyn QueryDatabase,
file_name: String,
compiler_options: &CheckerOptions,
) -> Self {
CheckerState {
ctx: CheckerContext::with_options(arena, binder, types, file_name, compiler_options),
}
}
pub fn with_options_and_shared_def_store(
arena: &'a NodeArena,
binder: &'a BinderState,
types: &'a dyn QueryDatabase,
file_name: String,
compiler_options: &CheckerOptions,
definition_store: std::sync::Arc<tsz_solver::def::DefinitionStore>,
) -> Self {
let compiler_options = compiler_options.clone().apply_strict_defaults();
CheckerState {
ctx: CheckerContext::new_with_shared_def_store(
arena,
binder,
types,
file_name,
compiler_options,
definition_store,
),
}
}
pub fn with_cache_and_options(
arena: &'a NodeArena,
binder: &'a BinderState,
types: &'a dyn QueryDatabase,
file_name: String,
cache: crate::TypeCache,
compiler_options: &CheckerOptions,
) -> Self {
CheckerState {
ctx: CheckerContext::with_cache_and_options(
arena,
binder,
types,
file_name,
cache,
compiler_options,
),
}
}
pub fn extract_cache(self) -> crate::TypeCache {
self.ctx.extract_cache()
}
pub(crate) fn cache_symbol_type(&mut self, sym_id: SymbolId, type_id: TypeId) {
self.ctx.symbol_types.insert(sym_id, type_id);
}
pub(crate) fn record_symbol_dependency(&mut self, dependency: SymbolId) {
let Some(¤t) = self.ctx.symbol_dependency_stack.last() else {
return;
};
if current == dependency {
return;
}
self.ctx
.symbol_dependencies
.entry(current)
.or_default()
.insert(dependency);
}
pub(crate) fn push_symbol_dependency(&mut self, sym_id: SymbolId, clear_deps: bool) {
if clear_deps {
self.ctx.symbol_dependencies.remove(&sym_id);
}
self.ctx.symbol_dependency_stack.push(sym_id);
}
pub(crate) fn pop_symbol_dependency(&mut self) {
self.ctx.symbol_dependency_stack.pop();
}
pub(crate) fn infer_parameter_types_from_context(&mut self, params: &[NodeIndex]) {
for ¶m_idx in params {
let Some(param_node) = self.ctx.arena.get(param_idx) else {
continue;
};
let Some(param) = self.ctx.arena.get_parameter(param_node) else {
continue;
};
if param.type_annotation.is_some() || param.initializer.is_some() {
continue;
}
let Some(sym_id) = self
.ctx
.binder
.get_node_symbol(param.name)
.or_else(|| self.ctx.binder.get_node_symbol(param_idx))
else {
continue;
};
if let Some(name_node) = self.ctx.arena.get(param.name)
&& (name_node.kind == syntax_kind_ext::OBJECT_BINDING_PATTERN
|| name_node.kind == syntax_kind_ext::ARRAY_BINDING_PATTERN)
{
continue;
}
if let Some(&cached) = self.ctx.symbol_types.get(&sym_id)
&& cached != TypeId::UNKNOWN
&& cached != TypeId::ANY
&& cached != TypeId::ERROR
{
continue;
}
let inferred = self.get_type_of_identifier(param.name);
if inferred != TypeId::UNKNOWN && inferred != TypeId::ERROR {
self.cache_symbol_type(sym_id, inferred);
}
}
}
pub fn push_return_type(&mut self, return_type: TypeId) {
self.ctx.push_return_type(return_type);
}
pub fn pop_return_type(&mut self) {
self.ctx.pop_return_type();
}
pub fn current_return_type(&self) -> Option<TypeId> {
self.ctx.current_return_type()
}
pub fn error(&mut self, start: u32, length: u32, message: String, code: u32) {
self.ctx.error(start, length, message, code);
}
pub fn get_node_span(&self, idx: NodeIndex) -> Option<(u32, u32)> {
self.ctx.get_node_span(idx)
}
pub fn emit_error_at(&mut self, start: u32, length: u32, message: &str, code: u32) {
self.ctx
.diagnostics
.push(crate::diagnostics::Diagnostic::error(
self.ctx.file_name.clone(),
start,
length,
message.to_string(),
code,
));
}
pub fn get_symbol_at_node(&self, idx: NodeIndex) -> Option<SymbolId> {
self.ctx.binder.get_node_symbol(idx)
}
pub fn get_symbol_by_name(&self, name: &str) -> Option<SymbolId> {
self.ctx.binder.file_locals.get(name)
}
pub fn get_type_of_node(&mut self, idx: NodeIndex) -> TypeId {
if let Some(&cached) = self.ctx.node_types.get(&idx.0) {
let should_narrow = self.should_apply_flow_narrowing_for_identifier(idx);
if should_narrow {
let narrowed = self.apply_flow_narrowing(idx, cached);
if narrowed != cached && narrowed != TypeId::ERROR {
let evaluated_cached = self.evaluate_type_for_assignability(cached);
let widened_cached =
tsz_solver::widening::widen_type(self.ctx.types, evaluated_cached);
if widened_cached == narrowed {
return cached;
}
}
return narrowed;
}
if self.ctx.skip_flow_narrowing
&& self.ctx.arena.get(idx).is_some_and(|node| {
use tsz_parser::parser::syntax_kind_ext;
node.kind == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION
|| node.kind == syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION
})
{
} else {
tracing::trace!(idx = idx.0, type_id = cached.0, "(cached) get_type_of_node");
return cached;
}
}
if !self.ctx.consume_fuel() {
self.ctx.node_types.insert(idx.0, TypeId::ERROR);
return TypeId::ERROR;
}
if self.ctx.node_resolution_set.contains(&idx) {
self.ctx.node_types.insert(idx.0, TypeId::ERROR);
return TypeId::ERROR;
}
self.ctx.node_resolution_stack.push(idx);
self.ctx.node_resolution_set.insert(idx);
self.ctx.node_types.insert(idx.0, TypeId::ERROR);
let result = self.compute_type_of_node(idx);
self.ctx.node_resolution_stack.pop();
self.ctx.node_resolution_set.remove(&idx);
self.ctx.node_types.insert(idx.0, result);
let should_narrow_computed = self.should_apply_flow_narrowing_for_identifier(idx);
if should_narrow_computed {
let mut narrowed = self.apply_flow_narrowing(idx, result);
if !self.ctx.compiler_options.sound_mode {
use tsz_solver::relations::freshness::{is_fresh_object_type, widen_freshness};
if is_fresh_object_type(self.ctx.types, narrowed) {
narrowed = widen_freshness(self.ctx.types, narrowed);
}
}
if narrowed != result && narrowed != TypeId::ERROR {
let evaluated_result = self.evaluate_type_for_assignability(result);
let widened_result =
tsz_solver::widening::widen_type(self.ctx.types, evaluated_result);
if widened_result == narrowed {
narrowed = result;
}
}
tracing::trace!(
idx = idx.0,
type_id = result.0,
narrowed_type_id = narrowed.0,
"get_type_of_node (computed+narrowed)"
);
return narrowed;
}
tracing::trace!(idx = idx.0, type_id = result.0, "get_type_of_node");
result
}
pub(crate) fn clear_type_cache_recursive(&mut self, idx: NodeIndex) {
use tsz_parser::parser::syntax_kind_ext;
if idx.is_none() {
return;
}
self.ctx.node_types.remove(&idx.0);
let Some(node) = self.ctx.arena.get(idx) else {
return;
};
match node.kind {
k if k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION => {
if let Some(array) = self.ctx.arena.get_literal_expr(node) {
for &elem_idx in &array.elements.nodes {
self.clear_type_cache_recursive(elem_idx);
}
}
}
k if k == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION => {
if let Some(obj) = self.ctx.arena.get_literal_expr(node) {
for &prop_idx in &obj.elements.nodes {
self.clear_type_cache_recursive(prop_idx);
}
}
}
k if k == syntax_kind_ext::PROPERTY_ASSIGNMENT => {
if let Some(prop) = self.ctx.arena.get_property_assignment(node) {
self.clear_type_cache_recursive(prop.initializer);
}
}
k if k == syntax_kind_ext::PARENTHESIZED_EXPRESSION => {
if let Some(paren) = self.ctx.arena.get_parenthesized(node) {
self.clear_type_cache_recursive(paren.expression);
}
}
k if k == syntax_kind_ext::CALL_EXPRESSION => {
if let Some(call) = self.ctx.arena.get_call_expr(node) {
self.clear_type_cache_recursive(call.expression);
if let Some(ref args) = call.arguments {
for &arg_idx in &args.nodes {
self.clear_type_cache_recursive(arg_idx);
}
}
}
}
k if k == syntax_kind_ext::BINARY_EXPRESSION => {
if let Some(bin) = self.ctx.arena.get_binary_expr(node) {
self.clear_type_cache_recursive(bin.left);
self.clear_type_cache_recursive(bin.right);
}
}
k if k == syntax_kind_ext::CONDITIONAL_EXPRESSION => {
if let Some(cond) = self.ctx.arena.get_conditional_expr(node) {
self.clear_type_cache_recursive(cond.condition);
self.clear_type_cache_recursive(cond.when_true);
self.clear_type_cache_recursive(cond.when_false);
}
}
k if k == syntax_kind_ext::SPREAD_ELEMENT => {
if let Some(spread) = self.ctx.arena.get_unary_expr_ex(node) {
self.clear_type_cache_recursive(spread.expression);
}
}
k if k == syntax_kind_ext::AS_EXPRESSION => {
if let Some(as_expr) = self.ctx.arena.get_type_assertion(node) {
self.clear_type_cache_recursive(as_expr.expression);
}
}
_ => {}
}
}
pub(crate) fn is_keyword_type_used_as_value_position(&self, idx: NodeIndex) -> bool {
use tsz_parser::parser::syntax_kind_ext;
let Some(ext) = self.ctx.arena.get_extended(idx) else {
return false;
};
let parent = ext.parent;
if parent.is_none() {
return false;
}
let Some(parent_node) = self.ctx.arena.get(parent) else {
return false;
};
if matches!(
parent_node.kind,
k if k == syntax_kind_ext::EXPRESSION_STATEMENT
|| k == syntax_kind_ext::LABELED_STATEMENT
|| k == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION
|| k == syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION
|| k == syntax_kind_ext::CALL_EXPRESSION
|| k == syntax_kind_ext::NEW_EXPRESSION
|| k == syntax_kind_ext::BINARY_EXPRESSION
|| k == syntax_kind_ext::RETURN_STATEMENT
|| k == syntax_kind_ext::VARIABLE_DECLARATION
|| k == syntax_kind_ext::PROPERTY_ASSIGNMENT
|| k == syntax_kind_ext::SHORTHAND_PROPERTY_ASSIGNMENT
|| k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION
|| k == syntax_kind_ext::PARENTHESIZED_EXPRESSION
|| k == syntax_kind_ext::CONDITIONAL_EXPRESSION
) {
return true;
}
if parent_node.kind == syntax_kind_ext::ARRAY_TYPE {
let Some(parent_ext) = self.ctx.arena.get_extended(parent) else {
return false;
};
let grandparent = parent_ext.parent;
if grandparent.is_none() {
return false;
}
let Some(grandparent_node) = self.ctx.arena.get(grandparent) else {
return false;
};
return matches!(
grandparent_node.kind,
k if k == syntax_kind_ext::EXPRESSION_STATEMENT
|| k == syntax_kind_ext::LABELED_STATEMENT
|| k == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION
|| k == syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION
|| k == syntax_kind_ext::CALL_EXPRESSION
|| k == syntax_kind_ext::NEW_EXPRESSION
|| k == syntax_kind_ext::BINARY_EXPRESSION
|| k == syntax_kind_ext::RETURN_STATEMENT
|| k == syntax_kind_ext::VARIABLE_DECLARATION
|| k == syntax_kind_ext::PROPERTY_ASSIGNMENT
|| k == syntax_kind_ext::SHORTHAND_PROPERTY_ASSIGNMENT
|| k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION
|| k == syntax_kind_ext::PARENTHESIZED_EXPRESSION
|| k == syntax_kind_ext::CONDITIONAL_EXPRESSION
);
}
false
}
pub(crate) fn compute_type_of_node(&mut self, idx: NodeIndex) -> TypeId {
use crate::ExpressionChecker;
let expr_result = {
let mut expr_checker = ExpressionChecker::new(&mut self.ctx);
expr_checker.compute_type_uncached(idx)
};
let result = if expr_result != TypeId::DELEGATE {
expr_result
} else {
self.compute_type_of_node_complex(idx)
};
self.validate_regex_literal_flags(idx);
result
}
fn compute_type_of_node_complex(&mut self, idx: NodeIndex) -> TypeId {
use crate::dispatch::ExpressionDispatcher;
let mut dispatcher = ExpressionDispatcher::new(self);
dispatcher.dispatch_type_computation(idx)
}
}