Skip to content

Self-referencing type alias crashes the compiler (stack overflow, no message) #370

Description

@ASDAlexander77

A type alias that refers to itself crashes the compiler with a stack overflow as soon as the alias is used. There is no diagnostic and no output. Exit code 0xC00000FD on Windows; from Git Bash it shows as exit 127, which looks like "command not found".

Repro

type Node = [value: s32, next: Reference<Node>];

function main() {
    let n: Node;
    print("ok");
}
tslang --emit=mlir --no-default-lib rec.ts -o rec.mlir

Measured on main (22878dd), Windows x64 Release:

Source Result
type Node = [value: s32, next: Reference<Node>];, never used compiles
same alias, used in let n: Node; stack overflow
same alias, used only in a declare function take(n: Reference<Node>): void; stack overflow
type Node = { value: number, next: Node };, never used compiles
same object alias, used in let n: Node; stack overflow
type Node = { value: number, next?: Node }; with const n: Node = { value: 1 }; stack overflow
interface Node { value: number; next: Node } with let n: Node; compiles

So only a used self-referencing type alias fails. An interface doesn't, and the tuple and object forms both fail.

Where it recurses

This stack is from a Debug build of 23/09, which is older than main, but the loop is in long-standing MLIRGen code. The same four frames repeat until the stack runs out:

MLIRGenImpl::resolveTypeByName            <- "Node"
MLIRGenImpl::resolveTypeByNameInNamespace
MLIRGenImpl::getType                      <- the alias's type node, expanded again
MLIRGenImpl::getTupleType
MLIRGenImpl::getTupleFieldInfo
MLIRGenImpl::getType                      <- field `next: Reference<Node>`
MLIRGenImpl::getTypeByTypeReference
MLIRGenImpl::findEmbeddedType
MLIRGenImpl::getEmbeddedTypeWithParam
MLIRGenImpl::getEmbeddedTypeWithParamBuiltins
MLIRGenImpl::getFirstTypeFromTypeArguments
MLIRGenImpl::getType                      <- type argument `Node`
MLIRGenImpl::getTypeByTypeReference
MLIRGenImpl::getTypeByTypeName
MLIRGenImpl::resolveTypeByName            <- "Node" again
...

It looks like every reference to a type alias re-runs getType on the alias's declaration, and nothing notices that the same alias is already being resolved higher up the stack. An interface doesn't hit this, presumably because it is registered by name before its members are resolved.

Expected

Either a real recursive type (at least through Reference<T>, which has a fixed size), or a normal compile error such as "type alias 'Node' circularly references itself". Never a silent crash.

Why it matters

tsbindgen (#367) turns a C struct that points to itself, such as struct node { int value; struct node *next; }, into exactly this kind of alias. For now it breaks the cycle by mapping such pointers to Opaque; see the "As built" table in docs/superpowers/specs/2026-09-25-tsbindgen-linkname-design.md. Once this is fixed, that rule can be dropped.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions