apple/pkl · error
cyclicTypeAlias
cyclicTypeAlias
Error message
cyclicTypeAlias
What it means
Pkl's TypeAliasTypeNode constructor refuses to build a node for a type alias that is still uninitialized, which only happens when the alias's definition cyclically depends on itself (directly or through other aliases). Throwing here prevents infinite recursion during typechecking. The error carries the code `cyclicTypeAlias`.
Solutions
- Break the cycle by using a class with a nullable/self reference instead of a type alias (e.g. `class Node { children: List<Node> }`)
- Inline one side of a mutually-recursive alias pair
- Add an indirection (a class or `Mapping`/`List` wrapper class) so the alias is not evaluated in terms of itself
- Check recent renames in the alias definition that may have created self-reference
Example fix
// before
typealias Json = String|Number|Boolean|Null|List<Json>|Mapping<String, Json> // OK, but:
typealias A = B
typealias B = A
// after
class AValue { ... }
typealias A = B
class BValue { a: A? }
typealias B = BValue Defensive patterns
Strategy: type-guard
Prevention
- Never let a typealias reference itself, even through other aliases
- Model recursive data with classes, not typealiases
- After renaming aliases, grep for self-references in typealias definitions
When it happens
Trigger: Declaring a type alias whose RHS references itself, e.g. `typealias Tree = List<Tree>` without an indirection, or two aliases referring to each other (`A = B`, `B = A`).
Common situations: Refactoring a recursive data model into type aliases; accidentally renaming an alias so it shadows itself; circular imports between modules each defining aliases.
Related errors
- abstractMemberCannotHaveBody
- abstractMethodInNonAbstractType
- array
- Cannot convert pkl.base#Int
- Cannot convert pkl.base#Int
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/d1b3a4a857e6075a.
Report an issue: GitHub.
Appendix: source
Thrown at pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java:2746
@Override
protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer) {
return consumer.accept(this);
}
}
public static final class TypeAliasTypeNode extends TypeNode {
private final VmTypeAlias typeAlias;
private final TypeNode[] typeArgumentNodes;
@Child private TypeNode aliasedTypeNode;
public TypeAliasTypeNode(
SourceSection sourceSection, VmTypeAlias typeAlias, TypeNode[] typeArgumentNodes) {
super(sourceSection);
if (!typeAlias.isInitialized()) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder().evalError("cyclicTypeAlias").build();
}
if (typeArgumentNodes.length > 0
&& typeArgumentNodes.length != typeAlias.getTypeParameterCount()) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError(
"wrongTypeArgumentCount",
typeAlias.getTypeParameterCount(),
typeArgumentNodes.length)
.build();
}
this.typeAlias = typeAlias;
this.typeArgumentNodes = typeArgumentNodes;
aliasedTypeNode = typeAlias.instantiate(typeArgumentNodes);
aliasedTypeNode.accept(
node -> {View on GitHub (pinned to f3efcbfc9b)