Bug
Declaring a typed variable without an initializer fails to seed the symbol table with its declared type. Later assignments don't re-seed it either, so method resolution on the variable has no type to consult and falls through to inference from the RHS — which may not know the declared interface.
let sock: Socket;
sock = createConnection("localhost", 5432);
sock.on("connect", ...); // ← error: Method 'on' on 'sock' is not supported
Impact
Breaks the common conditional-initialization pattern, which has no clean workaround:
let sock: Socket;
if (useTls) { sock = tlsConnect(...); } else { sock = createConnection(...); }
sock.write(buf);
Ternary works for 2-way branches (const sock = useTls ? tlsConnect(...) : createConnection(...)) but not for 3+ or statement-bodied branches. IIFE works but is noisy. Neither is a real fix — the declared type should propagate regardless of how the variable is initialized.
Correct behavior
Standard declared-type flow:
- Declaration —
let sock: Socket; registers sock in the symbol table with Socket as its type. Happens whether or not there's an initializer.
- Assignment — each
sock = RHS checks RHS is assignable to Socket. Pass: keep the declared type. Fail: emit a compile error at the assignment site (location info included) and refuse to continue.
- Method / member access — reads the declared type from the symbol table. Does not care how many branches assigned it or what the RHS expressions resolved to.
This is straightforward declared-type tracking that should already exist for the initialized case.
Likely fix location
variable-allocator.ts — when processing a VariableDeclaration with declaredType and no value, call setResolvedType(name, parseTypeString(declaredType)) so later member access resolves via the interface vtable. Plus a semantic-pass check that every subsequent assignment's RHS is type-compatible with the declared type.
Related: #595 (Phase-E typed-AST convergence) routes method resolution through the canonical typeOf() lookup, which makes the declared-type path the single source of truth once seeded.
Bug
Declaring a typed variable without an initializer fails to seed the symbol table with its declared type. Later assignments don't re-seed it either, so method resolution on the variable has no type to consult and falls through to inference from the RHS — which may not know the declared interface.
Impact
Breaks the common conditional-initialization pattern, which has no clean workaround:
Ternary works for 2-way branches (
const sock = useTls ? tlsConnect(...) : createConnection(...)) but not for 3+ or statement-bodied branches. IIFE works but is noisy. Neither is a real fix — the declared type should propagate regardless of how the variable is initialized.Correct behavior
Standard declared-type flow:
let sock: Socket;registerssockin the symbol table withSocketas its type. Happens whether or not there's an initializer.sock = RHSchecks RHS is assignable toSocket. Pass: keep the declared type. Fail: emit a compile error at the assignment site (location info included) and refuse to continue.This is straightforward declared-type tracking that should already exist for the initialized case.
Likely fix location
variable-allocator.ts— when processing aVariableDeclarationwithdeclaredTypeand novalue, callsetResolvedType(name, parseTypeString(declaredType))so later member access resolves via the interface vtable. Plus a semantic-pass check that every subsequent assignment's RHS is type-compatible with the declared type.Related: #595 (Phase-E typed-AST convergence) routes method resolution through the canonical
typeOf()lookup, which makes the declared-type path the single source of truth once seeded.