From 1a3dc52a96c932e0993ce20bcb1d5f5fb6e534a5 Mon Sep 17 00:00:00 2001 From: Islon Scherer Date: Tue, 28 Jul 2026 09:19:15 +0200 Subject: [PATCH] Fix bug in `force` (#1790) --- .../java/org/pkl/core/runtime/VmObject.java | 7 +++++ .../traceThenIterateUndefinedMember.pkl | 21 +++++++++++++++ .../traceThenIterateUndefinedMember.err | 27 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/errors/traceThenIterateUndefinedMember.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/errors/traceThenIterateUndefinedMember.err diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java index 367ef47ff..6b8fa40dc 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java @@ -156,8 +156,11 @@ public abstract class VmObject extends VmObjectLike { public final void force(boolean allowUndefinedValues, boolean recurse) { if (forced) return; + // set eagerly to break reference cycles if (recurse) forced = true; + var fullyForced = true; + try { for (VmObjectLike owner = this; owner != null; owner = owner.getParent()) { var cursor = EconomicMaps.getEntries(owner.getMembers()); @@ -177,6 +180,7 @@ public abstract class VmObject extends VmObjectLike { memberValue = VmUtils.doReadMember(this, owner, memberKey, member); } catch (VmUndefinedValueException e) { if (!allowUndefinedValues) throw e; + fullyForced = false; continue; } } @@ -190,6 +194,9 @@ public abstract class VmObject extends VmObjectLike { forced = false; throw t; } + + // make sure uncached values are not marked as forced + if (recurse && !fullyForced) forced = false; } @Override diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/traceThenIterateUndefinedMember.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/traceThenIterateUndefinedMember.pkl new file mode 100644 index 000000000..7c3a000e4 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/traceThenIterateUndefinedMember.pkl @@ -0,0 +1,21 @@ +undefinedValue: String + +items = new Listing { + undefinedValue +} + +lookup = new Mapping { + ["x"] = 1 +} + +result = + let (_ = trace(items)) + new Listing { + for (item in items) { + lookup.containsKey(item) + } + } + +output { + value = result +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/traceThenIterateUndefinedMember.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/traceThenIterateUndefinedMember.err new file mode 100644 index 000000000..59ebdbec2 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/traceThenIterateUndefinedMember.err @@ -0,0 +1,27 @@ +–– Pkl Error –– +Tried to read property `undefinedValue` but its value is undefined. + +x | undefinedValue: String + ^^^^^^^^^^^^^^ +at traceThenIterateUndefinedMember#undefinedValue (file:///$snippetsDir/input/errors/traceThenIterateUndefinedMember.pkl) + +x | undefinedValue + ^^^^^^^^^^^^^^ +at traceThenIterateUndefinedMember#items[#1] (file:///$snippetsDir/input/errors/traceThenIterateUndefinedMember.pkl) + +xx | let (_ = trace(items)) + ^^^^^^^^^^^^^^^^^^^^^^ +at traceThenIterateUndefinedMember#result. (file:///$snippetsDir/input/errors/traceThenIterateUndefinedMember.pkl) + +xx | value = result + ^^^^^^ +at traceThenIterateUndefinedMember#output.value (file:///$snippetsDir/input/errors/traceThenIterateUndefinedMember.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) +pkl: TRACE: items = new Listing { ? } (file:///$snippetsDir/input/errors/traceThenIterateUndefinedMember.pkl)