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
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
0xC00000FDon Windows; from Git Bash it shows as exit 127, which looks like "command not found".Repro
Measured on main (22878dd), Windows x64 Release:
type Node = [value: s32, next: Reference<Node>];, never usedlet n: Node;declare function take(n: Reference<Node>): void;type Node = { value: number, next: Node };, never usedlet n: Node;type Node = { value: number, next?: Node };withconst n: Node = { value: 1 };interface Node { value: number; next: Node }withlet n: Node;So only a used self-referencing
typealias 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:
It looks like every reference to a type alias re-runs
getTypeon 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 toOpaque; see the "As built" table indocs/superpowers/specs/2026-09-25-tsbindgen-linkname-design.md. Once this is fixed, that rule can be dropped.🤖 Generated with Claude Code