use crate::diagnostics::{Diagnostic, diagnostic_codes, diagnostic_messages, format_message};
use crate::state::CheckerState;
use tsz_binder::symbol_flags;
use tsz_parser::parser::NodeIndex;
use tsz_parser::parser::flags::node_flags;
use tsz_parser::parser::syntax_kind_ext;
use tsz_scanner::SyntaxKind;
use tsz_solver::TypeId;
impl<'a> CheckerState<'a> {
pub(crate) const fn is_assignment_operator(&self, operator: u16) -> bool {
matches!(
operator,
k if k == SyntaxKind::EqualsToken as u16
|| k == SyntaxKind::PlusEqualsToken as u16
|| k == SyntaxKind::MinusEqualsToken as u16
|| k == SyntaxKind::AsteriskEqualsToken as u16
|| k == SyntaxKind::AsteriskAsteriskEqualsToken as u16
|| k == SyntaxKind::SlashEqualsToken as u16
|| k == SyntaxKind::PercentEqualsToken as u16
|| k == SyntaxKind::LessThanLessThanEqualsToken as u16
|| k == SyntaxKind::GreaterThanGreaterThanEqualsToken as u16
|| k == SyntaxKind::GreaterThanGreaterThanGreaterThanEqualsToken as u16
|| k == SyntaxKind::AmpersandEqualsToken as u16
|| k == SyntaxKind::BarEqualsToken as u16
|| k == SyntaxKind::BarBarEqualsToken as u16
|| k == SyntaxKind::AmpersandAmpersandEqualsToken as u16
|| k == SyntaxKind::QuestionQuestionEqualsToken as u16
|| k == SyntaxKind::CaretEqualsToken as u16
)
}
pub(crate) fn is_valid_assignment_target(&self, idx: NodeIndex) -> bool {
use tsz_parser::parser::syntax_kind_ext;
let Some(node) = self.ctx.arena.get(idx) else {
return false;
};
match node.kind {
k if k == SyntaxKind::Identifier as u16 => true,
k if k == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION
|| k == syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION =>
{
true
}
k if k == syntax_kind_ext::OBJECT_BINDING_PATTERN
|| k == syntax_kind_ext::ARRAY_BINDING_PATTERN
|| k == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
|| k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION =>
{
true
}
k if k == syntax_kind_ext::PARENTHESIZED_EXPRESSION => {
if let Some(paren) = self.ctx.arena.get_parenthesized(node) {
self.is_valid_assignment_target(paren.expression)
} else {
false
}
}
k if k == syntax_kind_ext::SATISFIES_EXPRESSION
|| k == syntax_kind_ext::AS_EXPRESSION =>
{
if let Some(assertion) = self.ctx.arena.get_type_assertion(node) {
self.is_valid_assignment_target(assertion.expression)
} else {
false
}
}
_ => false,
}
}
fn get_const_variable_name(&self, ident_idx: NodeIndex) -> Option<String> {
let ident_idx = self.unwrap_assignment_target_for_symbol(ident_idx);
let node = self.ctx.arena.get(ident_idx)?;
if node.kind != SyntaxKind::Identifier as u16 {
return None;
}
let ident = self.ctx.arena.get_identifier(node)?;
let name = ident.escaped_text.clone();
let sym_id = self
.ctx
.binder
.resolve_identifier(self.ctx.arena, ident_idx)?;
let mut target_binder = self.ctx.binder;
let mut target_arena = self.ctx.arena;
if let Some(&file_idx) = self.ctx.cross_file_symbol_targets.borrow().get(&sym_id) {
if let Some(all_binders) = &self.ctx.all_binders
&& let Some(b) = all_binders.get(file_idx)
{
target_binder = b;
}
if let Some(all_arenas) = &self.ctx.all_arenas
&& let Some(a) = all_arenas.get(file_idx)
{
target_arena = a;
}
} else if let Some(arena) = self.ctx.binder.symbol_arenas.get(&sym_id) {
target_arena = arena.as_ref();
}
for lib in &self.ctx.lib_contexts {
if let Some(sym) = lib.binder.get_symbol(sym_id)
&& sym.escaped_name == name
{
target_binder = &lib.binder;
target_arena = lib.arena.as_ref();
break;
}
}
let symbol = target_binder
.get_symbol(sym_id)
.or_else(|| self.ctx.binder.get_symbol(sym_id))?;
let value_decl = symbol.value_declaration;
if value_decl.is_none() {
return None;
}
if let Some(arenas) = self
.ctx
.binder
.declaration_arenas
.get(&(sym_id, value_decl))
&& let Some(first) = arenas.first()
{
target_arena = first.as_ref();
}
let decl_node = target_arena.get(value_decl)?;
let mut decl_flags = decl_node.flags as u32;
if (decl_flags & (node_flags::LET | node_flags::CONST)) == 0
&& let Some(ext) = target_arena.get_extended(value_decl)
&& let Some(parent_node) = target_arena.get(ext.parent)
&& parent_node.kind == syntax_kind_ext::VARIABLE_DECLARATION_LIST
{
decl_flags |= parent_node.flags as u32;
}
(decl_flags & node_flags::CONST != 0).then_some(name)
}
fn check_strict_assignment_target(&mut self, target_idx: NodeIndex) {
let inner = self.skip_parenthesized_expression(target_idx);
if !self.is_strict_mode_for_node(inner) {
return;
}
let Some(node) = self.ctx.arena.get(inner) else {
return;
};
if node.kind != SyntaxKind::Identifier as u16 {
return;
}
let Some(id_data) = self.ctx.arena.get_identifier(node) else {
return;
};
if id_data.escaped_text != "arguments" && id_data.escaped_text != "eval" {
return;
}
let code = if self.ctx.enclosing_class.is_some() {
diagnostic_codes::CODE_CONTAINED_IN_A_CLASS_IS_EVALUATED_IN_JAVASCRIPTS_STRICT_MODE_WHICH_DOES_NOT
} else {
diagnostic_codes::INVALID_USE_OF_IN_STRICT_MODE
};
self.error_at_node_msg(inner, code, &[&id_data.escaped_text]);
}
fn unwrap_assignment_target_for_symbol(&self, idx: NodeIndex) -> NodeIndex {
self.ctx.arena.skip_parenthesized_and_assertions(idx)
}
pub(crate) fn check_increment_decrement_operand(&mut self, operand_idx: NodeIndex) -> bool {
let inner = self.skip_assignment_transparent_wrappers(operand_idx);
let Some(node) = self.ctx.arena.get(inner) else {
return false;
};
let is_valid = node.kind == SyntaxKind::Identifier as u16
|| node.kind == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION
|| node.kind == syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION;
if !is_valid {
self.error_at_node(
operand_idx,
diagnostic_messages::THE_OPERAND_OF_AN_INCREMENT_OR_DECREMENT_OPERATOR_MUST_BE_A_VARIABLE_OR_A_PROPER,
diagnostic_codes::THE_OPERAND_OF_AN_INCREMENT_OR_DECREMENT_OPERATOR_MUST_BE_A_VARIABLE_OR_A_PROPER,
);
return true;
}
false
}
fn skip_assignment_transparent_wrappers(&self, idx: NodeIndex) -> NodeIndex {
self.ctx.arena.skip_parenthesized_and_assertions(idx)
}
pub(crate) fn check_const_assignment(&mut self, target_idx: NodeIndex) -> bool {
let inner = self.skip_parenthesized_expression(target_idx);
if let Some(name) = self.get_const_variable_name(inner) {
self.error_at_node_msg(
inner,
diagnostic_codes::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_A_CONSTANT,
&[&name],
);
return true;
}
false
}
pub(crate) fn check_function_assignment(&mut self, target_idx: NodeIndex) -> bool {
let inner = self.skip_parenthesized_expression(target_idx);
let Some(node) = self.ctx.arena.get(inner) else {
return false;
};
if node.kind != SyntaxKind::Identifier as u16 {
return false;
}
let Some(id_data) = self.ctx.arena.get_identifier(node) else {
return false;
};
let name = &id_data.escaped_text;
if name == "undefined" {
let message = format_message(
diagnostic_messages::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_NOT_A_VARIABLE,
&[name],
);
self.ctx.diagnostics.push(Diagnostic::error(
self.ctx.file_name.clone(),
node.pos,
node.end.saturating_sub(node.pos),
message,
diagnostic_codes::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_NOT_A_VARIABLE,
));
return true;
}
if name == "eval" {
use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
let message = format_message(
diagnostic_messages::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_A_FUNCTION,
&[name],
);
self.ctx.diagnostics.push(Diagnostic::error(
self.ctx.file_name.clone(),
node.pos,
node.end.saturating_sub(node.pos),
message,
diagnostic_codes::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_A_FUNCTION,
));
return true;
}
let sym_id = self.ctx.binder.resolve_identifier(self.ctx.arena, inner);
let Some(sym_id) = sym_id else {
return false;
};
let Some(symbol) = self.ctx.binder.get_symbol(sym_id) else {
return false;
};
let is_namespace = (symbol.flags & symbol_flags::NAMESPACE_MODULE) != 0;
let value_flags_except_module = symbol_flags::VALUE & !symbol_flags::VALUE_MODULE;
let has_other_value = (symbol.flags & value_flags_except_module) != 0;
if is_namespace && !has_other_value {
let mut is_instantiated = false;
for decl_idx in &symbol.declarations {
if self.is_namespace_declaration_instantiated(*decl_idx) {
is_instantiated = true;
break;
}
}
if !is_instantiated {
self.error_namespace_used_as_value_at(name, inner);
return true;
}
}
if symbol.flags & symbol_flags::TYPE != 0 && symbol.flags & symbol_flags::VALUE == 0 {
self.error_type_only_value_at(name, inner);
return true;
}
use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
let (msg_template, code) = if symbol.flags & symbol_flags::MODULE != 0 {
(
diagnostic_messages::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_A_NAMESPACE,
diagnostic_codes::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_A_NAMESPACE,
)
} else if symbol.flags & symbol_flags::CLASS != 0 {
(
diagnostic_messages::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_A_CLASS,
diagnostic_codes::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_A_CLASS,
)
} else if symbol.flags & symbol_flags::ENUM != 0 {
(
diagnostic_messages::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_AN_ENUM,
diagnostic_codes::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_AN_ENUM,
)
} else if symbol.flags & symbol_flags::FUNCTION != 0 {
(
diagnostic_messages::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_A_FUNCTION,
diagnostic_codes::CANNOT_ASSIGN_TO_BECAUSE_IT_IS_A_FUNCTION,
)
} else {
return false;
};
let message = format_message(msg_template, &[name]);
self.ctx.diagnostics.push(Diagnostic::error(
self.ctx.file_name.clone(),
node.pos,
node.end.saturating_sub(node.pos),
message,
code,
));
true
}
pub(crate) fn check_assignment_expression(
&mut self,
left_idx: NodeIndex,
right_idx: NodeIndex,
expr_idx: NodeIndex,
) -> TypeId {
if !self.is_valid_assignment_target(left_idx) && !self.node_has_nearby_parse_error(left_idx)
{
self.error_at_node(
left_idx,
"The left-hand side of an assignment expression must be a variable or a property access.",
diagnostic_codes::THE_LEFT_HAND_SIDE_OF_AN_ASSIGNMENT_EXPRESSION_MUST_BE_A_VARIABLE_OR_A_PROPERTY,
);
self.get_type_of_node(left_idx);
self.get_type_of_node(right_idx);
return TypeId::ANY;
}
let is_const = self.check_const_assignment(left_idx);
let is_function_assignment = self.check_function_assignment(left_idx);
self.check_strict_assignment_target(left_idx);
let (is_destructuring, is_array_destructuring) =
if let Some(left_node) = self.ctx.arena.get(left_idx) {
let is_obj = left_node.kind == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION;
let is_arr = left_node.kind == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION;
(is_obj || is_arr, is_arr)
} else {
(false, false)
};
let prev_destructuring = self.ctx.in_destructuring_target;
if is_destructuring {
self.ctx.in_destructuring_target = true;
}
let left_target = self.get_type_of_assignment_target(left_idx);
self.ctx.in_destructuring_target = prev_destructuring;
let mut left_type = self.resolve_type_query_type(left_target);
let is_js_file = self.ctx.file_name.ends_with(".js")
|| self.ctx.file_name.ends_with(".jsx")
|| self.ctx.file_name.ends_with(".mjs")
|| self.ctx.file_name.ends_with(".cjs");
if is_js_file
&& self.ctx.compiler_options.check_js
&& let Some(jsdoc_left_type) = self
.jsdoc_type_annotation_for_node_direct(expr_idx)
.or_else(|| self.jsdoc_type_annotation_for_node_direct(left_idx))
{
left_type = jsdoc_left_type;
}
let prev_context = self.ctx.contextual_type;
if left_type != TypeId::ANY
&& left_type != TypeId::NEVER
&& left_type != TypeId::UNKNOWN
&& !self.type_contains_error(left_type)
{
self.ctx.contextual_type = Some(left_type);
}
let right_raw = self.get_type_of_node(right_idx);
let right_type = self.resolve_type_query_type(right_raw);
self.ctx.contextual_type = prev_context;
if is_function_assignment {
return right_type;
}
self.ensure_relation_input_ready(right_type);
self.ensure_relation_input_ready(left_type);
if is_array_destructuring {
let should_check_iterability = self
.ctx
.arena
.get(left_idx)
.and_then(|node| self.ctx.arena.get_literal_expr(node))
.is_none_or(|array_lit| !array_lit.elements.nodes.is_empty());
if should_check_iterability {
self.check_destructuring_iterability(left_idx, right_type, NodeIndex::NONE);
}
self.check_tuple_destructuring_bounds(left_idx, right_type);
}
let is_readonly = if !is_const {
self.check_readonly_assignment(left_idx, expr_idx)
} else {
false
};
let blocked_generic_index_write =
!is_const && !is_readonly && self.check_generic_indexed_write_restriction(left_idx);
if !is_const && !is_readonly && !blocked_generic_index_write && left_type != TypeId::ANY {
let mut check_assignability = !is_array_destructuring;
if check_assignability {
let widened_left = tsz_solver::widening::widen_type(self.ctx.types, left_type);
if widened_left != left_type
&& let Some(right_node) = self.ctx.arena.get(right_idx)
{
use tsz_parser::parser::syntax_kind_ext;
use tsz_scanner::SyntaxKind;
if right_node.kind == syntax_kind_ext::BINARY_EXPRESSION
&& let Some(bin) = self.ctx.arena.get_binary_expr(right_node)
{
let op = bin.operator_token;
let is_compound_like = op == SyntaxKind::PlusToken as u16
|| op == SyntaxKind::MinusToken as u16
|| op == SyntaxKind::AsteriskToken as u16
|| op == SyntaxKind::SlashToken as u16
|| op == SyntaxKind::PercentToken as u16
|| op == SyntaxKind::AsteriskAsteriskToken as u16
|| op == SyntaxKind::LessThanLessThanToken as u16
|| op == SyntaxKind::GreaterThanGreaterThanToken as u16
|| op == SyntaxKind::GreaterThanGreaterThanGreaterThanToken as u16;
if is_compound_like && self.is_assignable_to(right_type, widened_left) {
check_assignability = false;
}
}
}
}
self.check_assignment_compatibility(
left_idx,
right_idx,
right_type,
left_type,
check_assignability, true,
);
if left_type != TypeId::UNKNOWN
&& let Some(right_node) = self.ctx.arena.get(right_idx)
&& right_node.kind == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
{
self.check_object_literal_excess_properties(right_type, left_type, right_idx);
}
}
right_type
}
fn check_generic_indexed_write_restriction(&mut self, left_idx: NodeIndex) -> bool {
let Some(left_node) = self.ctx.arena.get(left_idx) else {
return false;
};
if left_node.kind != syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION {
return false;
}
let Some(access) = self.ctx.arena.get_access_expr(left_node) else {
return false;
};
if self
.get_literal_index_from_node(access.name_or_argument)
.is_some()
{
return false;
}
let prev_skip_narrowing = self.ctx.skip_flow_narrowing;
self.ctx.skip_flow_narrowing = true;
let object_type_raw = self.get_type_of_node(access.expression);
let object_type = self.resolve_type_query_type(object_type_raw);
self.ctx.skip_flow_narrowing = prev_skip_narrowing;
if object_type == TypeId::ERROR || object_type == TypeId::ANY {
return false;
}
if !tsz_solver::type_queries::contains_type_parameters_db(self.ctx.types, object_type) {
return false;
}
let evaluated_object = tsz_solver::evaluate_type(self.ctx.types, object_type);
if !tsz_solver::type_queries::is_uninstantiated_type_parameter(
self.ctx.types,
evaluated_object,
) {
return false;
}
if self.index_expression_constrained_to_object_keys(object_type, access.name_or_argument) {
return false;
}
let object_type_str = self.format_type(object_type);
let message = format_message(
diagnostic_messages::TYPE_IS_GENERIC_AND_CAN_ONLY_BE_INDEXED_FOR_READING,
&[&object_type_str],
);
self.error_at_node(
left_idx,
&message,
diagnostic_codes::TYPE_IS_GENERIC_AND_CAN_ONLY_BE_INDEXED_FOR_READING,
);
true
}
fn index_expression_constrained_to_object_keys(
&mut self,
object_type: TypeId,
index_expr: NodeIndex,
) -> bool {
use tsz_solver::type_queries::{get_keyof_type, get_type_parameter_constraint};
let index_type = self.get_type_of_node(index_expr);
if index_type == TypeId::ERROR {
return false;
}
let Some(index_constraint) = get_type_parameter_constraint(self.ctx.types, index_type)
else {
return false;
};
let Some(constraint_source) = get_keyof_type(self.ctx.types, index_constraint) else {
return false;
};
self.is_subtype_of(constraint_source, object_type)
|| self.is_subtype_of(object_type, constraint_source)
}
fn check_tuple_destructuring_bounds(&mut self, left_idx: NodeIndex, right_type: TypeId) {
let rhs = tsz_solver::type_queries::unwrap_readonly(self.ctx.types, right_type);
let Some(tuple_elements) =
tsz_solver::type_queries::get_tuple_elements(self.ctx.types, rhs)
else {
return;
};
let has_rest_tail = tuple_elements.last().is_some_and(|element| element.rest);
if has_rest_tail {
return;
}
let Some(left_node) = self.ctx.arena.get(left_idx) else {
return;
};
let Some(array_lit) = self.ctx.arena.get_literal_expr(left_node) else {
return;
};
for (index, &element_idx) in array_lit.elements.nodes.iter().enumerate() {
if index < tuple_elements.len() || element_idx.is_none() {
continue;
}
let Some(element_node) = self.ctx.arena.get(element_idx) else {
continue;
};
if element_node.kind == syntax_kind_ext::OMITTED_EXPRESSION {
continue;
}
if element_node.kind == syntax_kind_ext::SPREAD_ELEMENT {
return;
}
self.error_at_node(
element_idx,
&format!(
"Tuple type of length '{}' has no element at index '{}'.",
tuple_elements.len(),
index
),
diagnostic_codes::TUPLE_TYPE_OF_LENGTH_HAS_NO_ELEMENT_AT_INDEX,
);
return;
}
}
fn is_arithmetic_operand(&self, type_id: TypeId) -> bool {
use tsz_solver::BinaryOpEvaluator;
if let Some(sym_id) = self.ctx.resolve_type_to_symbol_id(type_id)
&& let Some(symbol) = self.ctx.binder.get_symbol(sym_id)
{
use tsz_binder::symbol_flags;
if (symbol.flags & symbol_flags::ENUM) != 0 {
return true;
}
}
let evaluator = BinaryOpEvaluator::new(self.ctx.types);
evaluator.is_arithmetic_operand(type_id)
}
fn check_arithmetic_operands(
&mut self,
left_idx: NodeIndex,
right_idx: NodeIndex,
left_type: TypeId,
right_type: TypeId,
) -> bool {
let left_eval = self.evaluate_type_for_binary_ops(left_type);
let right_eval = self.evaluate_type_for_binary_ops(right_type);
let left_is_valid = self.is_arithmetic_operand(left_eval);
let right_is_valid = self.is_arithmetic_operand(right_eval);
if !left_is_valid && let Some(loc) = self.get_source_location(left_idx) {
self.ctx.diagnostics.push(Diagnostic::error(self.ctx.file_name.clone(), loc.start, loc.length(), "The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.".to_string(), diagnostic_codes::THE_LEFT_HAND_SIDE_OF_AN_ARITHMETIC_OPERATION_MUST_BE_OF_TYPE_ANY_NUMBER_BIGINT));
}
if !right_is_valid && let Some(loc) = self.get_source_location(right_idx) {
self.ctx.diagnostics.push(Diagnostic::error(self.ctx.file_name.clone(), loc.start, loc.length(), "The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.".to_string(), diagnostic_codes::THE_RIGHT_HAND_SIDE_OF_AN_ARITHMETIC_OPERATION_MUST_BE_OF_TYPE_ANY_NUMBER_BIGINT));
}
!left_is_valid || !right_is_valid
}
fn emit_boolean_operator_error(&mut self, node_idx: NodeIndex, op_str: &str, suggestion: &str) {
if let Some(loc) = self.get_source_location(node_idx) {
let message = format!(
"The '{op_str}' operator is not allowed for boolean types. Consider using '{suggestion}' instead."
);
self.ctx.diagnostics.push(Diagnostic::error(self.ctx.file_name.clone(), loc.start, loc.length(), message, diagnostic_codes::THE_OPERATOR_IS_NOT_ALLOWED_FOR_BOOLEAN_TYPES_CONSIDER_USING_INSTEAD));
}
}
pub(crate) fn check_compound_assignment_expression(
&mut self,
left_idx: NodeIndex,
right_idx: NodeIndex,
operator: u16,
expr_idx: NodeIndex,
) -> TypeId {
if !self.is_valid_assignment_target(left_idx) && !self.node_has_nearby_parse_error(left_idx)
{
self.error_at_node(
left_idx,
"The left-hand side of an assignment expression must be a variable or a property access.",
diagnostic_codes::THE_LEFT_HAND_SIDE_OF_AN_ASSIGNMENT_EXPRESSION_MUST_BE_A_VARIABLE_OR_A_PROPERTY,
);
self.get_type_of_node(left_idx);
self.get_type_of_node(right_idx);
return TypeId::ANY;
}
let is_const = self.check_const_assignment(left_idx);
let is_function_assignment = self.check_function_assignment(left_idx);
self.check_strict_assignment_target(left_idx);
if let Some(left_node) = self.ctx.arena.get(left_idx)
&& left_node.kind == SyntaxKind::Identifier as u16
&& let Some(sym_id) = self.resolve_identifier_symbol(left_idx)
{
let declared_type = self.get_type_of_symbol(sym_id);
self.check_flow_usage(left_idx, declared_type, sym_id);
}
let left_target = self.get_type_of_assignment_target(left_idx);
let left_type = self.resolve_type_query_type(left_target);
let prev_context = self.ctx.contextual_type;
if left_type != TypeId::ANY
&& left_type != TypeId::NEVER
&& left_type != TypeId::UNKNOWN
&& !self.type_contains_error(left_type)
{
self.ctx.contextual_type = Some(left_type);
}
let right_raw = self.get_type_of_node(right_idx);
let right_type = self.resolve_type_query_type(right_raw);
self.ctx.contextual_type = prev_context;
self.ensure_relation_input_ready(right_type);
self.ensure_relation_input_ready(left_type);
let is_readonly = if !is_const {
self.check_readonly_assignment(left_idx, expr_idx)
} else {
false
};
let mut emitted_operator_error = is_const || is_readonly || is_function_assignment;
let op_str = match operator {
k if k == SyntaxKind::PlusEqualsToken as u16 => "+",
k if k == SyntaxKind::MinusEqualsToken as u16 => "-",
k if k == SyntaxKind::AsteriskEqualsToken as u16 => "*",
k if k == SyntaxKind::SlashEqualsToken as u16 => "/",
k if k == SyntaxKind::PercentEqualsToken as u16 => "%",
k if k == SyntaxKind::AsteriskAsteriskEqualsToken as u16 => "**",
k if k == SyntaxKind::AmpersandEqualsToken as u16 => "&",
k if k == SyntaxKind::BarEqualsToken as u16 => "|",
k if k == SyntaxKind::CaretEqualsToken as u16 => "^",
k if k == SyntaxKind::LessThanLessThanEqualsToken as u16 => "<<",
k if k == SyntaxKind::GreaterThanGreaterThanEqualsToken as u16 => ">>",
k if k == SyntaxKind::GreaterThanGreaterThanGreaterThanEqualsToken as u16 => ">>>",
_ => "",
};
if !op_str.is_empty() {
emitted_operator_error |= self.check_and_emit_nullish_binary_operands(
left_idx, right_idx, left_type, right_type, op_str,
);
}
let is_arithmetic_compound = matches!(
operator,
k if k == SyntaxKind::MinusEqualsToken as u16
|| k == SyntaxKind::AsteriskEqualsToken as u16
|| k == SyntaxKind::SlashEqualsToken as u16
|| k == SyntaxKind::PercentEqualsToken as u16
|| k == SyntaxKind::AsteriskAsteriskEqualsToken as u16
);
if is_arithmetic_compound && !is_function_assignment {
if left_type != TypeId::ERROR && right_type != TypeId::ERROR {
emitted_operator_error |=
self.check_arithmetic_operands(left_idx, right_idx, left_type, right_type);
}
}
if operator == SyntaxKind::AsteriskAsteriskEqualsToken as u16
&& (self.ctx.compiler_options.target as u32)
< (tsz_common::common::ScriptTarget::ES2016 as u32)
&& left_type != TypeId::ANY
&& right_type != TypeId::ANY
&& left_type != TypeId::UNKNOWN
&& right_type != TypeId::UNKNOWN
&& self.is_subtype_of(left_type, TypeId::BIGINT)
&& self.is_subtype_of(right_type, TypeId::BIGINT)
{
self.error_at_node_msg(
expr_idx,
crate::diagnostics::diagnostic_codes::EXPONENTIATION_CANNOT_BE_PERFORMED_ON_BIGINT_VALUES_UNLESS_THE_TARGET_OPTION_IS,
&[],
);
emitted_operator_error = true;
}
let is_boolean_bitwise_compound = matches!(
operator,
k if k == SyntaxKind::AmpersandEqualsToken as u16
|| k == SyntaxKind::BarEqualsToken as u16
|| k == SyntaxKind::CaretEqualsToken as u16
);
let is_shift_compound = matches!(
operator,
k if k == SyntaxKind::LessThanLessThanEqualsToken as u16
|| k == SyntaxKind::GreaterThanGreaterThanEqualsToken as u16
|| k == SyntaxKind::GreaterThanGreaterThanGreaterThanEqualsToken as u16
);
if is_boolean_bitwise_compound && !is_function_assignment {
let evaluator = tsz_solver::BinaryOpEvaluator::new(self.ctx.types);
let left_is_boolean = evaluator.is_boolean_like(left_type);
let right_is_boolean = evaluator.is_boolean_like(right_type);
if left_is_boolean && right_is_boolean {
let (op_str, suggestion) = match operator {
k if k == SyntaxKind::AmpersandEqualsToken as u16 => ("&=", "&&"),
k if k == SyntaxKind::BarEqualsToken as u16 => ("|=", "||"),
_ => ("^=", "!=="),
};
self.emit_boolean_operator_error(left_idx, op_str, suggestion);
emitted_operator_error = true;
} else if left_type != TypeId::ERROR && right_type != TypeId::ERROR {
emitted_operator_error |=
self.check_arithmetic_operands(left_idx, right_idx, left_type, right_type);
}
} else if is_shift_compound
&& !is_function_assignment
&& left_type != TypeId::ERROR
&& right_type != TypeId::ERROR
{
emitted_operator_error |=
self.check_arithmetic_operands(left_idx, right_idx, left_type, right_type);
}
let result_type = self.compound_assignment_result_type(left_type, right_type, operator);
let is_logical_assignment = matches!(
operator,
k if k == SyntaxKind::AmpersandAmpersandEqualsToken as u16
|| k == SyntaxKind::BarBarEqualsToken as u16
|| k == SyntaxKind::QuestionQuestionEqualsToken as u16
);
let assigned_type = if is_logical_assignment {
right_type
} else {
result_type
};
if left_type != TypeId::ANY && !emitted_operator_error {
self.check_assignment_compatibility(
left_idx,
right_idx,
assigned_type,
left_type,
true,
false,
);
if left_type != TypeId::UNKNOWN
&& let Some(right_node) = self.ctx.arena.get(right_idx)
&& right_node.kind == tsz_parser::parser::syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
{
self.check_object_literal_excess_properties(right_type, left_type, right_idx);
}
}
result_type
}
fn compound_assignment_result_type(
&self,
left_type: TypeId,
right_type: TypeId,
operator: u16,
) -> TypeId {
use tsz_solver::{BinaryOpEvaluator, BinaryOpResult};
let evaluator = BinaryOpEvaluator::new(self.ctx.types);
let op_str = match operator {
k if k == SyntaxKind::PlusEqualsToken as u16 => Some("+"),
k if k == SyntaxKind::MinusEqualsToken as u16 => Some("-"),
k if k == SyntaxKind::AsteriskEqualsToken as u16 => Some("*"),
k if k == SyntaxKind::AsteriskAsteriskEqualsToken as u16 => Some("*"),
k if k == SyntaxKind::SlashEqualsToken as u16 => Some("/"),
k if k == SyntaxKind::PercentEqualsToken as u16 => Some("%"),
k if k == SyntaxKind::AmpersandAmpersandEqualsToken as u16 => Some("&&"),
k if k == SyntaxKind::BarBarEqualsToken as u16 => Some("||"),
_ => None,
};
if let Some(op) = op_str {
return match evaluator.evaluate(left_type, right_type, op) {
BinaryOpResult::Success(result) => result,
BinaryOpResult::TypeError { .. } => TypeId::ANY,
};
}
if operator == SyntaxKind::QuestionQuestionEqualsToken as u16 {
let factory = self.ctx.types.factory();
return factory.union(vec![left_type, right_type]);
}
if matches!(
operator,
k if k == SyntaxKind::AmpersandEqualsToken as u16
|| k == SyntaxKind::BarEqualsToken as u16
|| k == SyntaxKind::CaretEqualsToken as u16
|| k == SyntaxKind::LessThanLessThanEqualsToken as u16
|| k == SyntaxKind::GreaterThanGreaterThanEqualsToken as u16
|| k == SyntaxKind::GreaterThanGreaterThanGreaterThanEqualsToken as u16
) {
return TypeId::NUMBER;
}
TypeId::ANY
}
fn check_assignment_compatibility(
&mut self,
left_idx: NodeIndex,
right_idx: NodeIndex,
source_type: TypeId,
target_type: TypeId,
check_assignability: bool,
suppress_error_for_error_types: bool,
) {
if let Some((source_level, target_level)) =
self.constructor_accessibility_mismatch_for_assignment(left_idx, right_idx)
{
self.error_constructor_accessibility_not_assignable(
source_type,
target_type,
source_level,
target_level,
right_idx,
);
return;
}
if !check_assignability {
return;
}
if suppress_error_for_error_types
&& (source_type == TypeId::ERROR || target_type == TypeId::ERROR)
{
return;
}
let _ = self.check_assignable_or_report_at(source_type, target_type, right_idx, left_idx);
}
}