Skip to content

Commit 648108f

Browse files
committed
Merge worker-15
2 parents 21d4c1a + d6842a8 commit 648108f

2 files changed

Lines changed: 135 additions & 1 deletion

File tree

WORKER_15_TASK_LIST.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Worker 15 Task List - CFA Edge Cases
2+
3+
## Current Task
4+
- [ ] **CFA-26: Add computed property name CFA**
5+
- Track side effects in computed property name evaluation
6+
- Handle variable access within computed property expressions
7+
- Test class declarations with computed property names
8+
9+
## Queue
10+
(none yet)
11+
12+
## Completed
13+
- [x] **CFA-25: Implement static block definite assignment analysis**
14+
- Added `find_enclosing_static_block` helper to detect if code is inside a static block
15+
- Added `find_class_for_static_block` helper to get the enclosing class
16+
- Added `is_variable_used_before_declaration_in_static_block` for TDZ checking
17+
- Integrated static block TDZ check into `get_type_of_identifier`
18+
- Emits TS2454 when a variable is used in a static block before its declaration

wasm/src/thin_checker.rs

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5110,7 +5110,10 @@ impl<'a> ThinCheckerState<'a> {
51105110
return TypeId::ERROR;
51115111
}
51125112
let declared_type = self.get_type_of_symbol(sym_id);
5113-
if self.should_check_definite_assignment(sym_id, idx)
5113+
// Check for static block TDZ violation (variable used before declaration in source order)
5114+
if self.is_variable_used_before_declaration_in_static_block(sym_id, idx) {
5115+
self.error_variable_used_before_assigned_at(name, idx);
5116+
} else if self.should_check_definite_assignment(sym_id, idx)
51145117
&& !self.is_definitely_assigned_at(idx)
51155118
{
51165119
self.error_variable_used_before_assigned_at(name, idx);
@@ -5613,6 +5616,119 @@ impl<'a> ThinCheckerState<'a> {
56135616
}
56145617
}
56155618

5619+
/// Find the enclosing static block for a node, if any.
5620+
///
5621+
/// Returns the NodeIndex of the CLASS_STATIC_BLOCK_DECLARATION if the node is inside one.
5622+
fn find_enclosing_static_block(&self, idx: NodeIndex) -> Option<NodeIndex> {
5623+
let mut current = idx;
5624+
while !current.is_none() {
5625+
if let Some(node) = self.ctx.arena.get(current) {
5626+
if node.kind == syntax_kind_ext::CLASS_STATIC_BLOCK_DECLARATION {
5627+
return Some(current);
5628+
}
5629+
// Stop at function boundaries (don't consider outer static blocks)
5630+
if node.kind == syntax_kind_ext::FUNCTION_DECLARATION
5631+
|| node.kind == syntax_kind_ext::FUNCTION_EXPRESSION
5632+
|| node.kind == syntax_kind_ext::ARROW_FUNCTION
5633+
|| node.kind == syntax_kind_ext::METHOD_DECLARATION
5634+
|| node.kind == syntax_kind_ext::CONSTRUCTOR
5635+
{
5636+
return None;
5637+
}
5638+
}
5639+
let ext = self.ctx.arena.get_extended(current)?;
5640+
if ext.parent.is_none() {
5641+
return None;
5642+
}
5643+
current = ext.parent;
5644+
}
5645+
None
5646+
}
5647+
5648+
/// Find the class declaration containing a static block.
5649+
///
5650+
/// Given a static block node, returns the parent CLASS_DECLARATION or CLASS_EXPRESSION.
5651+
fn find_class_for_static_block(&self, static_block_idx: NodeIndex) -> Option<NodeIndex> {
5652+
let ext = self.ctx.arena.get_extended(static_block_idx)?;
5653+
let parent = ext.parent;
5654+
if parent.is_none() {
5655+
return None;
5656+
}
5657+
let parent_node = self.ctx.arena.get(parent)?;
5658+
if parent_node.kind == syntax_kind_ext::CLASS_DECLARATION
5659+
|| parent_node.kind == syntax_kind_ext::CLASS_EXPRESSION
5660+
{
5661+
Some(parent)
5662+
} else {
5663+
None
5664+
}
5665+
}
5666+
5667+
/// Check if a variable is used in a static block before its declaration (TDZ check).
5668+
///
5669+
/// In TypeScript, if a variable is declared at module level AFTER a class declaration,
5670+
/// using that variable inside the class's static block should emit TS2454.
5671+
///
5672+
/// Example:
5673+
/// ```typescript
5674+
/// class Baz {
5675+
/// static {
5676+
/// console.log(FOO); // Error: Variable 'FOO' is used before being assigned
5677+
/// }
5678+
/// }
5679+
/// const FOO = "FOO"; // Declared after the class
5680+
/// ```
5681+
fn is_variable_used_before_declaration_in_static_block(
5682+
&self,
5683+
sym_id: SymbolId,
5684+
usage_idx: NodeIndex,
5685+
) -> bool {
5686+
// Check if we're inside a static block
5687+
let Some(static_block_idx) = self.find_enclosing_static_block(usage_idx) else {
5688+
return false;
5689+
};
5690+
5691+
// Get the class containing the static block
5692+
let Some(class_idx) = self.find_class_for_static_block(static_block_idx) else {
5693+
return false;
5694+
};
5695+
5696+
// Get the class position
5697+
let Some(class_node) = self.ctx.arena.get(class_idx) else {
5698+
return false;
5699+
};
5700+
let class_pos = class_node.pos;
5701+
5702+
// Get the symbol's declaration
5703+
let Some(symbol) = self.ctx.binder.get_symbol(sym_id) else {
5704+
return false;
5705+
};
5706+
5707+
// Check if the symbol is a module-level variable (not a class member)
5708+
// We're looking for variables declared outside the class
5709+
if (symbol.flags & symbol_flags::VARIABLE) == 0 {
5710+
return false;
5711+
}
5712+
5713+
// Get the position of the variable's declaration
5714+
for &decl_idx in &symbol.declarations {
5715+
// Check if this is a variable declaration
5716+
let Some(var_stmt_idx) = self.find_enclosing_variable_statement(decl_idx) else {
5717+
continue;
5718+
};
5719+
let Some(var_stmt_node) = self.ctx.arena.get(var_stmt_idx) else {
5720+
continue;
5721+
};
5722+
5723+
// Variable is declared AFTER the class - this is TDZ error
5724+
if var_stmt_node.pos > class_pos {
5725+
return true;
5726+
}
5727+
}
5728+
5729+
false
5730+
}
5731+
56165732
/// Get type of a symbol.
56175733
pub fn get_type_of_symbol(&mut self, sym_id: SymbolId) -> TypeId {
56185734
use crate::solver::SymbolRef;

0 commit comments

Comments
 (0)