Skip to content

A generic specialization made during an import's discovery is regenerated - #380

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-specialization-in-declaration-mode
Sep 28, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-specialization-in-declaration-mode

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Indexing a generic-class field of an imported class failed to lower. Found in Document.ts of the BrowserLib sources attached to #231. It is the SymbolCallInternal error listed there.

// node.ts
export class Node {
    get toStringTag(): string { return "Node"; }
    childNodes: Array<Node>;
    constructor() { this.childNodes = new Array<Node>(); }
}

// doc.ts, compiled on its own with --emit=obj
import { Node } from "./node";
export function at(n: Node, i: number): Node {
    return n.childNodes[i];   // error: failed to legalize operation 'ts.SymbolCallInternal'
}

Cause

When a file's discovery pass meets an import of a source file, mlirGenInclude compiles the imported file in full, both its discovery and its real pass. At that point theModule is the importer's discovery module, which is erased afterwards.

A generic class specialized there (Array<Node>, the type of childNodes) had its members generated into that throwaway module, but its processing state stayed set. When the importer's real pass reached Array<Node>, mlirGen(ClassLikeDeclaration) saw it as already processed and returned early. The module that used the class never got any of its members. The index access became a call to Array<Node>.get, a symbol the module did not have.

Temporary tracing showed the sequence. The specialization's real-pass generation (allowPartialResolve=0) ran inside module=discovery_module, and the later pass in the importer's own module found processing=11 and skipped it.

Non-generic classes never hit this, because they are regenerated every time. Only specializations have the early return.

Fix

  • The actual fix: ClassInfo records the module its processing state was reached in (processingModule). A specialization is skipped only when it was processed in the current module; in another module it is generated again. Discovery (allowPartialResolve) keeps its own state, which is unchanged.
  • Also changed: instantiateSpecializedClassType (both overloads) turns declarationMode off while it generates the specialization. The members are linkonce_odr and belong to every module that uses them. An importer should not rely on the imported module's object happening to keep the members it needs.

Tests

  • New test-compile-import-specialization: a Tree class with a children: Box<Tree> field, where Box<T> has an indexer, imported and walked recursively through children[i]. It uses a user-defined generic because the suite runs without the default lib. It fails on main with the same SymbolCallInternal error.
  • Full release suite: 2988/2988 passed.
  • TypeScriptCompilerDefaultLib tests, release jit and compile: 156/156 each.

Not in this PR

The same test through a -shared library (test-compile-shared-… / test-jit-shared-…) fails on main with generic type Box can't be found. The importer cannot name a generic class declared in the library it imports. That is a separate gap, so only the static variant is registered.

🤖 Generated with Claude Code

…ated

An import met during a file's discovery pass is generated in full, into the
discovery module, which is then erased. A generic class specialized there
(Array<Node> for an imported `childNodes: Array<Node>`) was marked processed,
and the real pass returned early for it, so its members never reached the
module that used them. Indexing the field failed to lower: "failed to legalize
operation 'ts.SymbolCallInternal'" (BrowserLib's Document.ts, #231).

ClassInfo now records the module its processing state was reached in, and a
specialization is only skipped in that same module. A specialization is also
generated in full while an imported file is being declared: its members are
linkonce_odr and belong to every module that uses them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 8a0e0b1 into main Sep 28, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-specialization-in-declaration-mode branch September 28, 2026 08:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant