{"record":{"id":"d1b3a4a857e6075a","repo":"apple/pkl","slug":"cyclictypealias","errorCode":"cyclicTypeAlias","errorMessage":"cyclicTypeAlias","messagePattern":"cyclicTypeAlias","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java","lineNumber":2746,"sourceCode":"\n    @Override\n    protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer) {\n      return consumer.accept(this);\n    }\n  }\n\n  public static final class TypeAliasTypeNode extends TypeNode {\n    private final VmTypeAlias typeAlias;\n    private final TypeNode[] typeArgumentNodes;\n    @Child private TypeNode aliasedTypeNode;\n\n    public TypeAliasTypeNode(\n        SourceSection sourceSection, VmTypeAlias typeAlias, TypeNode[] typeArgumentNodes) {\n      super(sourceSection);\n\n      if (!typeAlias.isInitialized()) {\n        CompilerDirectives.transferToInterpreter();\n        throw exceptionBuilder().evalError(\"cyclicTypeAlias\").build();\n      }\n\n      if (typeArgumentNodes.length > 0\n          && typeArgumentNodes.length != typeAlias.getTypeParameterCount()) {\n        CompilerDirectives.transferToInterpreter();\n        throw exceptionBuilder()\n            .evalError(\n                \"wrongTypeArgumentCount\",\n                typeAlias.getTypeParameterCount(),\n                typeArgumentNodes.length)\n            .build();\n      }\n\n      this.typeAlias = typeAlias;\n      this.typeArgumentNodes = typeArgumentNodes;\n      aliasedTypeNode = typeAlias.instantiate(typeArgumentNodes);\n      aliasedTypeNode.accept(\n          node -> {","sourceCodeStart":2728,"sourceCodeEnd":2764,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java#L2728-L2764","documentation":"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`.","triggerScenarios":"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`).","commonSituations":"Refactoring a recursive data model into type aliases; accidentally renaming an alias so it shadows itself; circular imports between modules each defining aliases.","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"],"exampleFix":"// before\ntypealias Json = String|Number|Boolean|Null|List<Json>|Mapping<String, Json> // OK, but:\ntypealias A = B\ntypealias B = A\n// after\nclass AValue { ... }\ntypealias A = B\nclass BValue { a: A? }\ntypealias B = BValue","handlingStrategy":"type-guard","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["pkl","typealias","circular-reference"],"backgroundTag":"cyclic-type-alias-definition","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}