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

  1. Break the cycle by using a class with a nullable/self reference instead of a type alias (e.g. `class Node { children: List<Node> }`)
  2. Inline one side of a mutually-recursive alias pair
  3. Add an indirection (a class or `Mapping`/`List` wrapper class) so the alias is not evaluated in terms of itself
  4. 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

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


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)