From 2d4f6bc52bd95662e45e935ce633b7fa6c2ddc41 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Wed, 20 Aug 2025 15:50:03 +0200 Subject: [PATCH 1/7] Fix cyclic dependencies bug --- .../src/main/java/org/pkl/core/ast/builder/AstBuilder.java | 4 ++++ .../src/main/java/org/pkl/core/ast/member/ClassNode.java | 5 ++++- pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java index 6e74fc170..80d443269 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java @@ -288,6 +288,10 @@ public AstBuilder( isMethodReturnTypeChecked = !isStdLibModule || IoUtils.isTestMode(); } + public ModuleInfo getModuleInfo() { + return moduleInfo; + } + public static AstBuilder create( Source source, VmLanguage language, diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java index 969567655..74b9e2c96 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java @@ -99,7 +99,10 @@ public VmClass executeGeneric(VirtualFrame frame) { // nodes // via static final fields without having to fear recursive field initialization. prototype = module; - prototype.setExtraStorage(moduleInfo); + // Only set ModuleInfo if it hasn't been set already (to handle cyclic dependencies) + if (!prototype.hasExtraStorage()) { + prototype.setExtraStorage(moduleInfo); + } prototype.addProperties(prototypeMembers); } else { prototype = diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java index bbe5cf674..f185302f0 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java @@ -108,6 +108,11 @@ void initializeModule( var builder = AstBuilder.create( source, this, moduleContext, moduleKey, resolvedModuleKey, moduleResolver); + + // Set the ModuleInfo on the empty module immediately to handle cyclic dependencies. + // This ensures it works even if the module is accessed during initialization via imports + emptyModule.setExtraStorage(builder.getModuleInfo()); + var moduleNode = builder.visitModule(moduleContext); moduleNode.getCallTarget().call(emptyModule, emptyModule); MinPklVersionChecker.check(emptyModule, importNode); From 38c1873d0e36d269d569f4b518ca9475c948635a Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Fri, 22 Aug 2025 18:31:22 +0200 Subject: [PATCH 2/7] Revert "Fix cyclic dependencies bug" This reverts commit 2d4f6bc52bd95662e45e935ce633b7fa6c2ddc41. --- .../src/main/java/org/pkl/core/ast/builder/AstBuilder.java | 4 ---- .../src/main/java/org/pkl/core/ast/member/ClassNode.java | 5 +---- pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java | 5 ----- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java index 80d443269..6e74fc170 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java @@ -288,10 +288,6 @@ public AstBuilder( isMethodReturnTypeChecked = !isStdLibModule || IoUtils.isTestMode(); } - public ModuleInfo getModuleInfo() { - return moduleInfo; - } - public static AstBuilder create( Source source, VmLanguage language, diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java index 74b9e2c96..969567655 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java @@ -99,10 +99,7 @@ public VmClass executeGeneric(VirtualFrame frame) { // nodes // via static final fields without having to fear recursive field initialization. prototype = module; - // Only set ModuleInfo if it hasn't been set already (to handle cyclic dependencies) - if (!prototype.hasExtraStorage()) { - prototype.setExtraStorage(moduleInfo); - } + prototype.setExtraStorage(moduleInfo); prototype.addProperties(prototypeMembers); } else { prototype = diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java index f185302f0..bbe5cf674 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java @@ -108,11 +108,6 @@ void initializeModule( var builder = AstBuilder.create( source, this, moduleContext, moduleKey, resolvedModuleKey, moduleResolver); - - // Set the ModuleInfo on the empty module immediately to handle cyclic dependencies. - // This ensures it works even if the module is accessed during initialization via imports - emptyModule.setExtraStorage(builder.getModuleInfo()); - var moduleNode = builder.visitModule(moduleContext); moduleNode.getCallTarget().call(emptyModule, emptyModule); MinPklVersionChecker.check(emptyModule, importNode); From 70c34b07304fc21cbf04240c27e5978adefa018c Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 25 Aug 2025 13:27:36 +0200 Subject: [PATCH 3/7] Properly fix not initialized cyclical dependencies for amending modules. --- .../org/pkl/core/ast/builder/AstBuilder.java | 30 +++++++++++++++++-- .../org/pkl/core/ast/member/ClassNode.java | 5 +++- .../ast/type/ResolveDeclaredTypeNode.java | 28 ++++++++++++++++- .../java/org/pkl/core/runtime/ModuleInfo.java | 29 ++++++++++++++++++ .../java/org/pkl/core/runtime/VmLanguage.java | 4 +++ .../java/org/pkl/core/runtime/VmTyped.java | 4 +++ .../input-helper/cycles/Foo.pkl | 5 ++++ .../input-helper/cycles/Qux.pkl | 3 ++ .../input-helper/cycles/amendsFoo.pkl | 1 + .../input/errors/cyclicalAmendsType.pkl | 3 ++ .../input/modules/cycles.pkl | 3 ++ .../output/errors/cyclicalAmendsType.err | 6 ++++ .../output/modules/cycles.pcf | 1 + 13 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Foo.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Qux.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/amendsFoo.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/errors/cyclicalAmendsType.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/modules/cycles.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/errors/cyclicalAmendsType.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/modules/cycles.pcf diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java index 6e74fc170..c5442b316 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java @@ -288,6 +288,10 @@ public AstBuilder( isMethodReturnTypeChecked = !isStdLibModule || IoUtils.isTestMode(); } + public ModuleInfo getModuleInfo() { + return moduleInfo; + } + public static AstBuilder create( Source source, VmLanguage language, @@ -311,7 +315,14 @@ public static AstBuilder create( var moduleName = IoUtils.inferModuleName(moduleKey); moduleInfo = new ModuleInfo( - sourceSection, headerSection, null, moduleName, moduleKey, resolvedModuleKey, false); + sourceSection, + headerSection, + null, + moduleName, + moduleKey, + resolvedModuleKey, + false, + null); } else { var declaredModuleName = moduleDecl.getName(); var moduleName = @@ -320,6 +331,20 @@ public static AstBuilder create( : IoUtils.inferModuleName(moduleKey); var clause = moduleDecl.getExtendsOrAmendsDecl(); var isAmend = clause != null && clause.getType() == ExtendsOrAmendsClause.Type.AMENDS; + + // if this is an amending module, resolve the module being amended + ModuleKey amendedModuleKey = null; + if (isAmend) { + try { + var amendedModuleUri = URI.create(clause.getUrl().getString()); + if (!amendedModuleUri.isAbsolute()) { + amendedModuleUri = resolvedModuleKey.getUri().resolve(amendedModuleUri); + } + amendedModuleKey = moduleResolver.resolve(amendedModuleUri, null); + } catch (Exception ignored) { + } + } + moduleInfo = new ModuleInfo( sourceSection, @@ -328,7 +353,8 @@ public static AstBuilder create( moduleName, moduleKey, resolvedModuleKey, - isAmend); + isAmend, + amendedModuleKey); } return new AstBuilder(source, language, moduleInfo, moduleResolver); diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java index 969567655..5a77d62c5 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java @@ -99,7 +99,10 @@ public VmClass executeGeneric(VirtualFrame frame) { // nodes // via static final fields without having to fear recursive field initialization. prototype = module; - prototype.setExtraStorage(moduleInfo); + // Only set ModuleInfo if not already set (to handle cyclic dependencies) + if (!prototype.hasExtraStorage()) { + prototype.setExtraStorage(moduleInfo); + } prototype.addProperties(prototypeMembers); } else { prototype = diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java index cdf281b70..17e82b913 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.oracle.truffle.api.source.SourceSection; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.runtime.Identifier; +import org.pkl.core.runtime.VmLanguage; import org.pkl.core.runtime.VmObjectLike; import org.pkl.core.runtime.VmTyped; import org.pkl.core.util.Nullable; @@ -73,11 +74,36 @@ protected VmTyped getImport( var result = module.getCachedValue(importName); if (result == null) { result = callNode.call(member.getCallTarget(), module, module, importName); + + var importedModule = (VmTyped) result; + if (importedModule.isNotInitialized() + && importedModule.isModuleObject() + && importedModule.getModuleInfo().isAmend()) { + // this is an amending module. Try to find the prototype + var prototypeModule = findPrototypeModule(importedModule); + if (prototypeModule != null) { + result = prototypeModule; + } + } + module.setCachedValue(importName, result); } return (VmTyped) result; } + private @Nullable VmTyped findPrototypeModule(VmTyped amendingModule) { + try { + var moduleInfo = amendingModule.getModuleInfo(); + var amendedModuleKey = moduleInfo.getAmendedModuleKey(); + + if (amendedModuleKey != null) { + return VmLanguage.get(null).loadModule(amendedModuleKey); + } + } catch (Exception ignored) { + } + return null; + } + protected @Nullable Object getType( VmTyped module, Identifier typeName, SourceSection typeNameSection) { var member = module.getMember(typeName); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java b/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java index 8d15fc1d6..8a3c58418 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java @@ -37,6 +37,7 @@ public final class ModuleInfo { private final ModuleKey moduleKey; private final ResolvedModuleKey resolvedModuleKey; private final boolean isAmend; + private final @Nullable ModuleKey amendedModuleKey; @LateInit private List annotations; @@ -54,6 +55,26 @@ public ModuleInfo( ModuleKey moduleKey, ResolvedModuleKey resolvedModuleKey, boolean isAmend) { + this( + sourceSection, + headerSection, + docComment, + moduleName, + moduleKey, + resolvedModuleKey, + isAmend, + null); + } + + public ModuleInfo( + SourceSection sourceSection, + SourceSection headerSection, + SourceSection @Nullable [] docComment, + String moduleName, + ModuleKey moduleKey, + ResolvedModuleKey resolvedModuleKey, + boolean isAmend, + @Nullable ModuleKey amendedModuleKey) { this.sourceSection = sourceSection; this.headerSection = headerSection; @@ -62,6 +83,7 @@ public ModuleInfo( this.moduleKey = moduleKey; this.resolvedModuleKey = resolvedModuleKey; this.isAmend = isAmend; + this.amendedModuleKey = amendedModuleKey; } public void initAnnotations(List annotations) { @@ -179,4 +201,11 @@ public ModuleSchema getModuleSchema(VmTyped module) { public boolean isAmend() { return isAmend; } + + /** + * Returns the ModuleKey of the module being amended, or null if this is not an amending module. + */ + public @Nullable ModuleKey getAmendedModuleKey() { + return amendedModuleKey; + } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java index bbe5cf674..f01340f2c 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java @@ -108,6 +108,10 @@ void initializeModule( var builder = AstBuilder.create( source, this, moduleContext, moduleKey, resolvedModuleKey, moduleResolver); + + // set ModuleInfo early to handle cyclic dependencies + emptyModule.setExtraStorage(builder.getModuleInfo()); + var moduleNode = builder.visitModule(moduleContext); moduleNode.getCallTarget().call(emptyModule, emptyModule); MinPklVersionChecker.check(emptyModule, importNode); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java index 7bbc1987c..f99260056 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java @@ -59,6 +59,10 @@ public VmClass getVmClass() { assert clazz != null : "VmTyped.clazz was not initialized."; return clazz; } + + public boolean isNotInitialized() { + return clazz == null; + } public @Nullable VmTyped getParent() { return (VmTyped) parent; diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Foo.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Foo.pkl new file mode 100644 index 000000000..a92ac3ecc --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Foo.pkl @@ -0,0 +1,5 @@ +import "Qux.pkl" + +prop: Qux? + +typealias Bar = "bar" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Qux.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Qux.pkl new file mode 100644 index 000000000..788d9c2df --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Qux.pkl @@ -0,0 +1,3 @@ +import "amendsFoo.pkl" + +res: amendsFoo.Bar diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/amendsFoo.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/amendsFoo.pkl new file mode 100644 index 000000000..eddd4e7c4 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/amendsFoo.pkl @@ -0,0 +1 @@ +amends "Foo.pkl" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/cyclicalAmendsType.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/cyclicalAmendsType.pkl new file mode 100644 index 000000000..eff57e1dd --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/cyclicalAmendsType.pkl @@ -0,0 +1,3 @@ +import "../../input-helper/cycles/amendsFoo.pkl" + +prop: amendsFoo diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/modules/cycles.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/modules/cycles.pkl new file mode 100644 index 000000000..df0bbfd80 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/modules/cycles.pkl @@ -0,0 +1,3 @@ +import "../../input-helper/cycles/amendsFoo.pkl" + +foo: amendsFoo.Bar = "bar" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cyclicalAmendsType.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cyclicalAmendsType.err new file mode 100644 index 000000000..93b19761b --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cyclicalAmendsType.err @@ -0,0 +1,6 @@ +–– Pkl Error –– +Module `Foo` cannot be extended or used as type because it amends another module. + +x | prop: amendsFoo + ^^^^^^^^^ +at cyclicalAmendsType (file:///$snippetsDir/input/errors/cyclicalAmendsType.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/modules/cycles.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/modules/cycles.pcf new file mode 100644 index 000000000..5abc475eb --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/modules/cycles.pcf @@ -0,0 +1 @@ +foo = "bar" From 4e1a85662e6403d80da5d0c16c221006086040aa Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Mon, 25 Aug 2025 13:53:28 +0200 Subject: [PATCH 4/7] Spotless --- pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java | 4 ++-- pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java index f01340f2c..b2853d935 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java @@ -108,10 +108,10 @@ void initializeModule( var builder = AstBuilder.create( source, this, moduleContext, moduleKey, resolvedModuleKey, moduleResolver); - + // set ModuleInfo early to handle cyclic dependencies emptyModule.setExtraStorage(builder.getModuleInfo()); - + var moduleNode = builder.visitModule(moduleContext); moduleNode.getCallTarget().call(emptyModule, emptyModule); MinPklVersionChecker.check(emptyModule, importNode); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java index f99260056..9dc44b50d 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,7 +59,7 @@ public VmClass getVmClass() { assert clazz != null : "VmTyped.clazz was not initialized."; return clazz; } - + public boolean isNotInitialized() { return clazz == null; } From bd747770478fe033de158eb2116b86d596665c06 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Wed, 27 Aug 2025 15:53:58 +0200 Subject: [PATCH 5/7] Fix review remarks --- .../ast/type/ResolveDeclaredTypeNode.java | 34 ++++++++++--------- .../java/org/pkl/core/runtime/ModuleInfo.java | 3 +- .../input-helper/cycles/Qux.pkl | 4 +-- .../input-helper/cycles/amendsFooLv2.pkl | 1 + .../input/modules/cycles2.pkl | 3 ++ .../output/modules/cycles2.pcf | 1 + 6 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/amendsFooLv2.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/modules/cycles2.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/modules/cycles2.pcf diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java index 17e82b913..3b2d96093 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java @@ -76,14 +76,9 @@ protected VmTyped getImport( result = callNode.call(member.getCallTarget(), module, module, importName); var importedModule = (VmTyped) result; - if (importedModule.isNotInitialized() - && importedModule.isModuleObject() - && importedModule.getModuleInfo().isAmend()) { + if (importedModule.isNotInitialized() && importedModule.getModuleInfo().isAmend()) { // this is an amending module. Try to find the prototype - var prototypeModule = findPrototypeModule(importedModule); - if (prototypeModule != null) { - result = prototypeModule; - } + return findPrototypeModule(importedModule, importName, importNameSection); } module.setCachedValue(importName, result); @@ -91,17 +86,24 @@ protected VmTyped getImport( return (VmTyped) result; } - private @Nullable VmTyped findPrototypeModule(VmTyped amendingModule) { - try { - var moduleInfo = amendingModule.getModuleInfo(); - var amendedModuleKey = moduleInfo.getAmendedModuleKey(); + private VmTyped findPrototypeModule( + VmTyped notInitializedModule, Identifier importName, SourceSection importNameSection) { + VmTyped amendingModule = null; + var moduleInfo = notInitializedModule.getModuleInfo(); + var amendedModuleKey = moduleInfo.getAmendedModuleKey(); - if (amendedModuleKey != null) { - return VmLanguage.get(null).loadModule(amendedModuleKey); - } - } catch (Exception ignored) { + while (amendedModuleKey != null) { + amendingModule = VmLanguage.get(this).loadModule(amendedModuleKey, this); + moduleInfo = amendingModule.getModuleInfo(); + amendedModuleKey = moduleInfo.getAmendedModuleKey(); + } + if (amendingModule == null) { + throw exceptionBuilder() + .evalError("cannotFindModuleImport", importName) + .withSourceSection(importNameSection) + .build(); } - return null; + return amendingModule; } protected @Nullable Object getType( diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java b/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java index 8a3c58418..95e7c872c 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java @@ -203,7 +203,8 @@ public boolean isAmend() { } /** - * Returns the ModuleKey of the module being amended, or null if this is not an amending module. + * Returns the {@link ModuleKey} of the module being amended, or null if this is not an amending + * module. */ public @Nullable ModuleKey getAmendedModuleKey() { return amendedModuleKey; diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Qux.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Qux.pkl index 788d9c2df..cbcd10f03 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Qux.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/Qux.pkl @@ -1,3 +1,3 @@ -import "amendsFoo.pkl" +import "amendsFooLv2.pkl" -res: amendsFoo.Bar +res: amendsFooLv2.Bar diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/amendsFooLv2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/amendsFooLv2.pkl new file mode 100644 index 000000000..bc488aa9c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/cycles/amendsFooLv2.pkl @@ -0,0 +1 @@ +amends "amendsFoo.pkl" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/modules/cycles2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/modules/cycles2.pkl new file mode 100644 index 000000000..9c0929dfc --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/modules/cycles2.pkl @@ -0,0 +1,3 @@ +import "../../input-helper/cycles/amendsFooLv2.pkl" + +foo: amendsFooLv2.Bar = "bar" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/modules/cycles2.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/modules/cycles2.pcf new file mode 100644 index 000000000..5abc475eb --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/modules/cycles2.pcf @@ -0,0 +1 @@ +foo = "bar" From b68bfff4ae6538fc294c3b0645c3e2c8c0a046f6 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Wed, 27 Aug 2025 19:03:21 +0200 Subject: [PATCH 6/7] Fix errors --- .../expression/literal/AmendModuleNode.java | 14 +++++++++---- .../ast/type/ResolveDeclaredTypeNode.java | 21 ++++++++++--------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java index 33da5e19e..d8d1fc0bc 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.graalvm.collections.EconomicMap; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.ast.type.ResolveDeclaredTypeNode; import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.ModuleInfo; import org.pkl.core.runtime.VmLanguage; @@ -63,10 +64,15 @@ protected VmTyped eval(VirtualFrame frame, VmTyped supermodule) { .build(); } - checkIsValidTypedAmendment(supermodule); + var _supermodule = supermodule; + if (_supermodule.isNotInitialized()) { + _supermodule = ResolveDeclaredTypeNode.findPrototypeModule(this, _supermodule); + assert _supermodule != null; + } + checkIsValidTypedAmendment(_supermodule); - module.lateInitVmClass(supermodule.getVmClass()); - module.lateInitParent(supermodule); + module.lateInitVmClass(_supermodule.getVmClass()); + module.lateInitParent(_supermodule); module.addProperties(members); module.setExtraStorage(moduleInfo); diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java index 3b2d96093..0953d0cb0 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java @@ -16,6 +16,7 @@ package org.pkl.core.ast.type; import com.oracle.truffle.api.nodes.IndirectCallNode; +import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.runtime.Identifier; @@ -78,7 +79,14 @@ protected VmTyped getImport( var importedModule = (VmTyped) result; if (importedModule.isNotInitialized() && importedModule.getModuleInfo().isAmend()) { // this is an amending module. Try to find the prototype - return findPrototypeModule(importedModule, importName, importNameSection); + var proto = findPrototypeModule(this, importedModule); + if (proto == null) { + throw exceptionBuilder() + .evalError("cannotFindModuleImport", importName) + .withSourceSection(importNameSection) + .build(); + } + return proto; } module.setCachedValue(importName, result); @@ -86,23 +94,16 @@ protected VmTyped getImport( return (VmTyped) result; } - private VmTyped findPrototypeModule( - VmTyped notInitializedModule, Identifier importName, SourceSection importNameSection) { + public static @Nullable VmTyped findPrototypeModule(Node node, VmTyped notInitializedModule) { VmTyped amendingModule = null; var moduleInfo = notInitializedModule.getModuleInfo(); var amendedModuleKey = moduleInfo.getAmendedModuleKey(); while (amendedModuleKey != null) { - amendingModule = VmLanguage.get(this).loadModule(amendedModuleKey, this); + amendingModule = VmLanguage.get(node).loadModule(amendedModuleKey, node); moduleInfo = amendingModule.getModuleInfo(); amendedModuleKey = moduleInfo.getAmendedModuleKey(); } - if (amendingModule == null) { - throw exceptionBuilder() - .evalError("cannotFindModuleImport", importName) - .withSourceSection(importNameSection) - .build(); - } return amendingModule; } From cb8059fc85a00bf2cc77ba285bee078de72d15b3 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Thu, 28 Aug 2025 10:43:05 +0200 Subject: [PATCH 7/7] Throw on uninitialized module --- .../org/pkl/core/ast/expression/literal/AmendModuleNode.java | 4 +++- .../src/main/resources/org/pkl/core/errorMessages.properties | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java index d8d1fc0bc..6ea1fb11f 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java @@ -67,7 +67,9 @@ protected VmTyped eval(VirtualFrame frame, VmTyped supermodule) { var _supermodule = supermodule; if (_supermodule.isNotInitialized()) { _supermodule = ResolveDeclaredTypeNode.findPrototypeModule(this, _supermodule); - assert _supermodule != null; + if (_supermodule == null) { + throw exceptionBuilder().evalError("cyclicalModuleLoading").build(); + } } checkIsValidTypedAmendment(_supermodule); diff --git a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties index efe3de0be..0ed506a5e 100644 --- a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties +++ b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties @@ -108,6 +108,9 @@ Class `{0}` cannot extend itself. moduleCannotAmendSelf=\ Module `{0}` cannot amend itself. +cyclicalModuleLoading=\ +Could not load cyclical module. + missingLocalPropertyValue=\ Missing property value.