Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/codegen/infrastructure/type-inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,13 @@ export class TypeInference {
if (t === "boolean") return true;
if (t === "null") return true;
if (t === "regex") return true;
if (t === "array") return true;
if (t === "map") return true;
if (t === "set") return true;
if (t === "new") return true;
if (t === "object") return true;
// Only truly-static expression types are cacheable. Anything that
// recurses into variable / symbol-table lookups depends on state that's
// built mid-codegen — the pre-codegen annotator pass would cache stale
// wrong answers. Diagnosed: 342 array + 29 conditional + 26 binary
// cases where cached rich returned number[] for string[] / object[]
// declarations (VA_DIAG trail in memory). Force live re-resolution
// for all other shapes by returning false.
return false;
}

Expand Down
20 changes: 8 additions & 12 deletions src/codegen/infrastructure/variable-allocator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,8 @@ export interface VariableAllocatorContext {
setLastTypeAssertionSourceVar(name: string | null): void;
setCurrentVarDeclKey(key: string | null): void;
isStackEligibleKey(key: string): boolean;
// New methods pinned at END of interface: adding them mid-interface shifts
// native vtable slot positions for everything below — see CLAUDE.md rule #5
// and memory/native-method-deletion-breaks-vtable.md.
resolveExpressionTypeRich(expr: Expression): ResolvedType | null;
// New method pinned at END of interface: inserting mid-interface shifts
// native vtable slots (CLAUDE.md rule #5).
typeOf(expr: Expression): ResolvedType | null;
}

Expand Down Expand Up @@ -742,14 +740,12 @@ export class VariableAllocator {

const stmtDeclaredType: string = stmt.declaredType || "";
const strippedDeclType = stripNullable(stmtDeclaredType);
// NOTE: stays on flat resolveExpressionType. Switching to typeOf()
// returns a freshly-allocated enriched clone; passing that to the
// downstream consumer here produces wrong native GEP offsets (likely
// Phase C normalizer didn't canonicalize the enrichResolvedType
// literal). Calling typeOf() and DISCARDING works; USING the result
// breaks. Needs normalizer coverage on enrichResolvedType before
// this consumer can migrate.
const resolved = this.ctx.resolveExpressionType(stmtValue);
// Prefer the annotator-populated cache via typeOf when available; fall
// through to the resolver for expressions the annotator skipped.
let resolved = this.ctx.typeOf(stmtValue);
if (resolved === null) {
resolved = this.ctx.resolveExpressionType(stmtValue);
}
const nodeType = (stmtValue as ExprBase).type;

let isString: boolean;
Expand Down
22 changes: 19 additions & 3 deletions src/semantic/type-annotator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,28 @@ class TypeAnnotator {
this.visitExpr(ta.expression);
}

// After recursion, annotate this expression. Skip unknown-base and
// missing sourceKind — they represent resolver gaps, not authoritative
// info. typeOf's fallback still covers them on demand.
// After recursion, annotate this expression — but ONLY for truly-static
// shapes. Array / map / set / object / new / binary / conditional all
// recurse through variable / symbol-table lookups whose answers depend
// on codegen-time state the annotator doesn't yet see; caching them
// would freeze a pre-codegen wrong answer. Typed-literal and
// template_literal results are stable (always same base). Everything
// else gets resolved live by typeOf's fallback.
if (!this.isStableExprType(e.type)) return;
const resolved = this.sink.resolveExpressionTypeRich(expr);
if (resolved && resolved.base && resolved.base !== "unknown") {
this.sink.appendExpressionType(expr, resolved);
}
}

private isStableExprType(t: string): boolean {
return (
t === "number" ||
t === "string" ||
t === "template_literal" ||
t === "boolean" ||
t === "null" ||
t === "regex"
);
}
}
Loading