Skip to content

typecheck: let x: T; (decl without init) blocks method resolution on x after later assignment #590

@cs01

Description

@cs01

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:

  1. Declarationlet sock: Socket; registers sock in the symbol table with Socket as its type. Happens whether or not there's an initializer.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions