Compare commits

...

20 Commits

Author SHA1 Message Date
Daniel Chao e805a04a74 [docs] Add documetation about release/evolution/roadmap (#937) 2025-02-05 11:26:34 -08:00
Dan Chao edf704b469 Run spotless apply 2025-01-22 09:04:02 -08:00
Dan Chao d9cfb68048 Prepare 0.27.2 release 2025-01-22 08:57:27 -08:00
Daniel Chao 1e75001382 Add release notes for 0.27.2 (#894) 2025-01-21 11:56:33 -08:00
Dan Chao 376eb2bdbf Run spotless apply 2025-01-17 10:32:24 -08:00
Daniel Chao 56a3b0a193 Update license year (#871)
* Update license header file spec to use placeholder year
* Update spotless to use ratchet formatting (only format files that have changed)
2025-01-17 10:32:24 -08:00
Josh B 6c8e45b19a Fix NPE when handling ExternalReader specs with null arguments (#882) 2025-01-17 10:32:24 -08:00
Josh B 0bc1b7156e Fix page size for Linux AArch64 native executables (#875)
Graal Native Image is assuming 4k page size here, which is a naughty assumption to make in the modern Linux-on-ARM landscape.
Two very common hardware configurations require 16k minumum page size: the Raspberry Pi 5 and Asahi Linux (running on Apple Silicon hardware).

This change forces 64k pages for Linux/AArch64 native executables to guarantee compatibility with these platforms.
DEVELOPMENT.adoc is also updated to cover the additional dependencies required for building native executables on Linux.
2025-01-17 10:32:24 -08:00
Stefan M. f0b961de81 Make Test Report locale independent (#868)
Format numbers with `.` decimals
2025-01-17 10:32:24 -08:00
Daniel Chao 3ece353e0c Download JDK for windows build (#851)
Don't use the system Java on Windows builds, instead download them from Adoptium.

Also:

* Fail job if curl returns a 4xx status code
* Add java version to `GradleJob`
2025-01-17 09:58:03 -08:00
Josh B 639cc2430e Fix CreateEvaluatorRequest decoding (#853)
Handle case when request specifies external reader with null arguments
2024-12-19 10:00:46 -08:00
Philip K.F. Hölzenspies 48a710f439 Prepare 0.27.1 release (#839) 2024-12-06 14:23:25 +00:00
Philip K.F. Hölzenspies 15d85b0660 Add release notes for 0.27.1 2024-12-06 13:25:39 +00:00
odenix aeace8bb3c Improve lazy type checking of listings and mappings (#789)
Motivation:
- simplify implementation of lazy type checking
- fix correctness issues of lazy type checking (#785)

Changes:
- implement listing/mapping type cast via amendment (`parent`) instead of delegation (`delegate`)
- handle type checking of *computed* elements/entries in the same way as type checking of computed properties
  - ElementOrEntryNode is the equivalent of TypeCheckedPropertyNode
- remove fields VmListingOrMapping.delegate/typeNodeFrame/cachedMembers/checkedMembers
- fix #785 by executing all type casts between a member's owner and receiver
- fix #823 by storing owner and receiver directly
  instead of storing the mutable frame containing them (typeNodeFrame)
- remove overrides of VmObject methods that are no longer required
  - good for Truffle partial evaluation and JVM inlining
- revert a85a173faa except for added tests
- move `VmUtils.setOwner` and `VmUtils.setReceiver` and make them private
  - these methods aren't generally safe to use

Result:
- simpler code with greater optimization potential
  - VmListingOrMapping can now have both a type node and new members
- fewer changes to surrounding code
- smaller memory footprint
- better performance in some cases
- fixes https://github.com/apple/pkl/issues/785
- fixes https://github.com/apple/pkl/issues/823

Potential future optimizations:
- avoid lazy type checking overhead for untyped listings/mappings
- improve efficiency of forcing a typed listing/mapping
  - currently, lazy type checking will traverse the parent chain once per member,
    reducing the performance benefit of shallow-forcing
	  a listing/mapping over evaluating each member individually
- avoid creating an intermediate untyped listing/mapping in the following cases:
  - `new Listing<X> {...}`
  - amendment of `property: Listing<X>`
2024-12-06 14:00:23 +01:00
Islon Scherer 7b850dd6d9 Fix possible stack overflow in Listing/Mapping type checking (#826) 2024-12-06 13:58:29 +01:00
Daniel Chao c2096f633b Exclude non file-based modules from synthesized *GatherImports task (#821)
This fixes an issue where certain modules tasks fail due to the plugin
attempting to analyze their imports, but the arguments may not actually be
Pkl modules.

For example, the pkldoc task accepts entire packages in its "sourceMoules" property.

This changes the gather imports logic to only analyze file-based modules.
This is also a performance improvement; non file-based modules are unlikely to import
files due to insufficient trust levels.

Also: fix a bug when generating pkldoc on Windows
2024-12-06 13:58:06 +01:00
translatenix d6ba021e12 Fix length of listings with computed index (#797)
Motivation:
The following expression evaluates to 2 instead of 1:
new Listing { "value" } { [0 + 0] = "override" }.length

Changes:
- fix length computation in EntriesLiteralNode
- improve `api/listing` tests
- make snippet test failures diffable in IntelliJ

Result:
- fixes https://github.com/apple/pkl/issues/780
- improved dev experience in IntelliJ
2024-12-06 13:57:19 +01:00
Josh B 077497d9b8 Fix a possible deadlock during external reader process close (#786) (#787)
* Fix a possible deadlock during external reader process close

* Apply spotless

---------

Co-authored-by: Philip K.F. Hölzenspies <holzensp@gmail.com>
2024-11-08 13:18:13 -08:00
Nick Muerdter 552b301451 Fix broken link to documentation site in release notes (#784) 2024-11-05 12:58:43 -08:00
Dan Chao d3ac4b288c Prepare 0.27.0 release 2024-11-05 09:51:48 -08:00
92 changed files with 1347 additions and 653 deletions
+2
View File
@@ -108,10 +108,12 @@ local gradleCheckJobs: Mapping<String, GradleCheckJob> = new {
["gradle-check-jdk17"] { ["gradle-check-jdk17"] {
javaVersion = "17.0" javaVersion = "17.0"
isRelease = false isRelease = false
os = "linux"
} }
["gradle-check-jdk21"] { ["gradle-check-jdk21"] {
javaVersion = "21.0" javaVersion = "21.0"
isRelease = false isRelease = false
os = "linux"
} }
["gradle-check-jdk17-windows"] { ["gradle-check-jdk17-windows"] {
javaVersion = "17.0" javaVersion = "17.0"
+55 -16
View File
@@ -39,7 +39,7 @@ jobs:
&& rm -rf /var/cache/dnf && rm -rf /var/cache/dnf
# install jdk # install jdk
curl -L \ curl -Lf \
https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz
mkdir /jdk \ mkdir /jdk \
@@ -52,7 +52,7 @@ jobs:
# install zlib # install zlib
if [[ ! -f ~/staticdeps/include/zlib.h ]]; then if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
curl -L https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz curl -Lf https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz
mkdir -p /tmp/dep_zlib-1.2.13 \ mkdir -p /tmp/dep_zlib-1.2.13 \
&& cd /tmp/dep_zlib-1.2.13 \ && cd /tmp/dep_zlib-1.2.13 \
@@ -65,7 +65,7 @@ jobs:
# install musl # install musl
if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then
curl -L https://musl.libc.org/releases/musl-1.2.2.tar.gz -o /tmp/musl.tar.gz curl -Lf https://musl.libc.org/releases/musl-1.2.2.tar.gz -o /tmp/musl.tar.gz
mkdir -p /tmp/dep_musl-1.2.2 \ mkdir -p /tmp/dep_musl-1.2.2 \
&& cd /tmp/dep_musl-1.2.2 \ && cd /tmp/dep_musl-1.2.2 \
@@ -135,7 +135,7 @@ jobs:
&& rm -rf /var/cache/dnf && rm -rf /var/cache/dnf
# install jdk # install jdk
curl -L \ curl -Lf \
https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz
mkdir /jdk \ mkdir /jdk \
@@ -148,7 +148,7 @@ jobs:
# install zlib # install zlib
if [[ ! -f ~/staticdeps/include/zlib.h ]]; then if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
curl -L https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz curl -Lf https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz
mkdir -p /tmp/dep_zlib-1.2.13 \ mkdir -p /tmp/dep_zlib-1.2.13 \
&& cd /tmp/dep_zlib-1.2.13 \ && cd /tmp/dep_zlib-1.2.13 \
@@ -196,7 +196,7 @@ jobs:
&& rm -rf /var/cache/dnf && rm -rf /var/cache/dnf
# install jdk # install jdk
curl -L \ curl -Lf \
https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz
mkdir /jdk \ mkdir /jdk \
@@ -209,7 +209,7 @@ jobs:
# install zlib # install zlib
if [[ ! -f ~/staticdeps/include/zlib.h ]]; then if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
curl -L https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz curl -Lf https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz
mkdir -p /tmp/dep_zlib-1.2.13 \ mkdir -p /tmp/dep_zlib-1.2.13 \
&& cd /tmp/dep_zlib-1.2.13 \ && cd /tmp/dep_zlib-1.2.13 \
@@ -222,7 +222,7 @@ jobs:
# install musl # install musl
if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then
curl -L https://musl.libc.org/releases/musl-1.2.2.tar.gz -o /tmp/musl.tar.gz curl -Lf https://musl.libc.org/releases/musl-1.2.2.tar.gz -o /tmp/musl.tar.gz
mkdir -p /tmp/dep_musl-1.2.2 \ mkdir -p /tmp/dep_musl-1.2.2 \
&& cd /tmp/dep_musl-1.2.2 \ && cd /tmp/dep_musl-1.2.2 \
@@ -262,6 +262,18 @@ jobs:
pkl-cli-windows-amd64-release: pkl-cli-windows-amd64-release:
steps: steps:
- checkout - checkout
- run:
command: |-
# install jdk
curl -Lf \
https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9.1/OpenJDK17U-jdk_x64_windows_hotspot_17.0.9_9.zip -o /tmp/jdk.zip
unzip /tmp/jdk.zip -d /tmp/jdk \
&& cd /tmp/jdk/jdk-* \
&& mkdir /jdk \
&& cp -r . /jdk
name: Set up environment
shell: bash.exe
- run: - run:
command: |- command: |-
export PATH=~/staticdeps/bin:$PATH export PATH=~/staticdeps/bin:$PATH
@@ -276,6 +288,7 @@ jobs:
path: ~/test-results path: ~/test-results
environment: environment:
LANG: en_US.UTF-8 LANG: en_US.UTF-8
JAVA_HOME: /jdk
resource_class: windows.large resource_class: windows.large
machine: machine:
image: windows-server-2022-gui:current image: windows-server-2022-gui:current
@@ -315,7 +328,7 @@ jobs:
&& rm -rf /var/cache/dnf && rm -rf /var/cache/dnf
# install jdk # install jdk
curl -L \ curl -Lf \
https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz
mkdir /jdk \ mkdir /jdk \
@@ -328,7 +341,7 @@ jobs:
# install zlib # install zlib
if [[ ! -f ~/staticdeps/include/zlib.h ]]; then if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
curl -L https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz curl -Lf https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz
mkdir -p /tmp/dep_zlib-1.2.13 \ mkdir -p /tmp/dep_zlib-1.2.13 \
&& cd /tmp/dep_zlib-1.2.13 \ && cd /tmp/dep_zlib-1.2.13 \
@@ -341,7 +354,7 @@ jobs:
# install musl # install musl
if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then
curl -L https://musl.libc.org/releases/musl-1.2.2.tar.gz -o /tmp/musl.tar.gz curl -Lf https://musl.libc.org/releases/musl-1.2.2.tar.gz -o /tmp/musl.tar.gz
mkdir -p /tmp/dep_musl-1.2.2 \ mkdir -p /tmp/dep_musl-1.2.2 \
&& cd /tmp/dep_musl-1.2.2 \ && cd /tmp/dep_musl-1.2.2 \
@@ -411,7 +424,7 @@ jobs:
&& rm -rf /var/cache/dnf && rm -rf /var/cache/dnf
# install jdk # install jdk
curl -L \ curl -Lf \
https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz
mkdir /jdk \ mkdir /jdk \
@@ -424,7 +437,7 @@ jobs:
# install zlib # install zlib
if [[ ! -f ~/staticdeps/include/zlib.h ]]; then if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
curl -L https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz curl -Lf https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz
mkdir -p /tmp/dep_zlib-1.2.13 \ mkdir -p /tmp/dep_zlib-1.2.13 \
&& cd /tmp/dep_zlib-1.2.13 \ && cd /tmp/dep_zlib-1.2.13 \
@@ -472,7 +485,7 @@ jobs:
&& rm -rf /var/cache/dnf && rm -rf /var/cache/dnf
# install jdk # install jdk
curl -L \ curl -Lf \
https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz -o /tmp/jdk.tar.gz
mkdir /jdk \ mkdir /jdk \
@@ -485,7 +498,7 @@ jobs:
# install zlib # install zlib
if [[ ! -f ~/staticdeps/include/zlib.h ]]; then if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
curl -L https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz curl -Lf https://github.com/madler/zlib/releases/download/v1.2.13/zlib-1.2.13.tar.gz -o /tmp/zlib.tar.gz
mkdir -p /tmp/dep_zlib-1.2.13 \ mkdir -p /tmp/dep_zlib-1.2.13 \
&& cd /tmp/dep_zlib-1.2.13 \ && cd /tmp/dep_zlib-1.2.13 \
@@ -498,7 +511,7 @@ jobs:
# install musl # install musl
if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then
curl -L https://musl.libc.org/releases/musl-1.2.2.tar.gz -o /tmp/musl.tar.gz curl -Lf https://musl.libc.org/releases/musl-1.2.2.tar.gz -o /tmp/musl.tar.gz
mkdir -p /tmp/dep_musl-1.2.2 \ mkdir -p /tmp/dep_musl-1.2.2 \
&& cd /tmp/dep_musl-1.2.2 \ && cd /tmp/dep_musl-1.2.2 \
@@ -538,6 +551,18 @@ jobs:
pkl-cli-windows-amd64-snapshot: pkl-cli-windows-amd64-snapshot:
steps: steps:
- checkout - checkout
- run:
command: |-
# install jdk
curl -Lf \
https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9.1/OpenJDK17U-jdk_x64_windows_hotspot_17.0.9_9.zip -o /tmp/jdk.zip
unzip /tmp/jdk.zip -d /tmp/jdk \
&& cd /tmp/jdk/jdk-* \
&& mkdir /jdk \
&& cp -r . /jdk
name: Set up environment
shell: bash.exe
- run: - run:
command: |- command: |-
export PATH=~/staticdeps/bin:$PATH export PATH=~/staticdeps/bin:$PATH
@@ -552,6 +577,7 @@ jobs:
path: ~/test-results path: ~/test-results
environment: environment:
LANG: en_US.UTF-8 LANG: en_US.UTF-8
JAVA_HOME: /jdk
resource_class: windows.large resource_class: windows.large
machine: machine:
image: windows-server-2022-gui:current image: windows-server-2022-gui:current
@@ -582,6 +608,18 @@ jobs:
gradle-check-jdk17-windows: gradle-check-jdk17-windows:
steps: steps:
- checkout - checkout
- run:
command: |-
# install jdk
curl -Lf \
https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9.1/OpenJDK17U-jdk_x64_windows_hotspot_17.0.9_9.zip -o /tmp/jdk.zip
unzip /tmp/jdk.zip -d /tmp/jdk \
&& cd /tmp/jdk/jdk-* \
&& mkdir /jdk \
&& cp -r . /jdk
name: Set up environment
shell: bash.exe
- run: - run:
command: ./gradlew --info --stacktrace -DtestReportsDir=${HOME}/test-results check command: ./gradlew --info --stacktrace -DtestReportsDir=${HOME}/test-results check
name: gradle check name: gradle check
@@ -589,6 +627,7 @@ jobs:
path: ~/test-results path: ~/test-results
environment: environment:
LANG: en_US.UTF-8 LANG: en_US.UTF-8
JAVA_HOME: /jdk
resource_class: windows.large resource_class: windows.large
machine: machine:
image: windows-server-2022-gui:current image: windows-server-2022-gui:current
+10 -10
View File
@@ -17,7 +17,6 @@
extends "GradleJob.pkl" extends "GradleJob.pkl"
import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.1.2#/Config.pkl" import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.1.2#/Config.pkl"
import "package://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.uri@1.0.0#/URI.pkl"
/// The architecture to use /// The architecture to use
arch: "amd64"|"aarch64" arch: "amd64"|"aarch64"
@@ -25,13 +24,11 @@ arch: "amd64"|"aarch64"
/// Whether to link to musl. Otherwise, links to glibc. /// Whether to link to musl. Otherwise, links to glibc.
musl: Boolean = false musl: Boolean = false
javaVersion = "17.0"
local setupLinuxEnvironment: Config.RunStep = local setupLinuxEnvironment: Config.RunStep =
let (jdkVersion = "17.0.9+9")
let (muslVersion = "1.2.2") let (muslVersion = "1.2.2")
let (zlibVersion = "1.2.13") let (zlibVersion = "1.2.13")
let (jdkVersionEncoded = URI.encodeComponent(jdkVersion))
let (jdkVersionAlt = jdkVersion.replaceLast("+", "_"))
let (majorJdkVersion = jdkVersion.split(".").first)
new { new {
name = "Set up environment" name = "Set up environment"
shell = "#!/bin/bash -exo pipefail" shell = "#!/bin/bash -exo pipefail"
@@ -43,8 +40,8 @@ local setupLinuxEnvironment: Config.RunStep =
&& rm -rf /var/cache/dnf && rm -rf /var/cache/dnf
# install jdk # install jdk
curl -L \ curl -Lf \
https://github.com/adoptium/temurin\#(majorJdkVersion)-binaries/releases/download/jdk-\#(jdkVersionEncoded)/OpenJDK\#(majorJdkVersion)U-jdk_\#(if (arch == "amd64") "x64" else "aarch64")_linux_hotspot_\#(jdkVersionAlt).tar.gz -o /tmp/jdk.tar.gz https://github.com/adoptium/temurin\#(module.majorJdkVersion)-binaries/releases/download/\#(module.jdkGitHubReleaseName)/OpenJDK\#(module.majorJdkVersion)U-jdk_\#(if (arch == "amd64") "x64" else "aarch64")_linux_hotspot_\#(module.jdkVersionAlt).tar.gz -o /tmp/jdk.tar.gz
mkdir /jdk \ mkdir /jdk \
&& cd /jdk \ && cd /jdk \
@@ -56,7 +53,7 @@ local setupLinuxEnvironment: Config.RunStep =
# install zlib # install zlib
if [[ ! -f ~/staticdeps/include/zlib.h ]]; then if [[ ! -f ~/staticdeps/include/zlib.h ]]; then
curl -L https://github.com/madler/zlib/releases/download/v\#(zlibVersion)/zlib-\#(zlibVersion).tar.gz -o /tmp/zlib.tar.gz curl -Lf https://github.com/madler/zlib/releases/download/v\#(zlibVersion)/zlib-\#(zlibVersion).tar.gz -o /tmp/zlib.tar.gz
mkdir -p /tmp/dep_zlib-\#(zlibVersion) \ mkdir -p /tmp/dep_zlib-\#(zlibVersion) \
&& cd /tmp/dep_zlib-\#(zlibVersion) \ && cd /tmp/dep_zlib-\#(zlibVersion) \
@@ -72,7 +69,7 @@ local setupLinuxEnvironment: Config.RunStep =
#""" #"""
# install musl # install musl
if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then if [[ ! -f ~/staticdeps/bin/x86_64-linux-musl-gcc ]]; then
curl -L https://musl.libc.org/releases/musl-\#(muslVersion).tar.gz -o /tmp/musl.tar.gz curl -Lf https://musl.libc.org/releases/musl-\#(muslVersion).tar.gz -o /tmp/musl.tar.gz
mkdir -p /tmp/dep_musl-\#(muslVersion) \ mkdir -p /tmp/dep_musl-\#(muslVersion) \
&& cd /tmp/dep_musl-\#(muslVersion) \ && cd /tmp/dep_musl-\#(muslVersion) \
@@ -145,7 +142,7 @@ job {
resource_class = "macos.m1.large.gen1" resource_class = "macos.m1.large.gen1"
} }
when (os == "linux") { when (os == "linux") {
docker { docker = new Listing<Config.DockerImage> {
new { new {
image = if (arch == "aarch64") "arm64v8/oraclelinux:8-slim" else "oraclelinux:8-slim" image = if (arch == "aarch64") "arm64v8/oraclelinux:8-slim" else "oraclelinux:8-slim"
} }
@@ -160,5 +157,8 @@ job {
image = "windows-server-2022-gui:current" image = "windows-server-2022-gui:current"
} }
resource_class = "windows.large" resource_class = "windows.large"
environment {
["JAVA_HOME"] = "/jdk"
}
} }
} }
+2 -6
View File
@@ -19,13 +19,9 @@ import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.1.2#/Config.pkl"
local self = this local self = this
command: String javaVersion = "17.0"
job { command: String
docker {
new { image = "cimg/openjdk:17.0" }
}
}
os = "linux" os = "linux"
-20
View File
@@ -17,29 +17,9 @@ extends "GradleJob.pkl"
import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.1.2#/Config.pkl" import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.1.2#/Config.pkl"
javaVersion: "17.0"|"21.0"
os = "linux"
steps { steps {
new Config.RunStep { new Config.RunStep {
name = "gradle check" name = "gradle check"
command = "./gradlew \(module.gradleArgs) check" command = "./gradlew \(module.gradleArgs) check"
} }
} }
job {
when (os == "linux") {
docker {
new {
image = "cimg/openjdk:\(javaVersion)"
}
}
}
when (os == "windows") {
machine {
image = "windows-server-2022-gui:current"
}
resource_class = "windows.large"
}
}
+52
View File
@@ -16,6 +16,7 @@
abstract module GradleJob abstract module GradleJob
import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.1.2#/Config.pkl" import "package://pkg.pkl-lang.org/pkl-pantry/com.circleci.v2@1.1.2#/Config.pkl"
import "package://pkg.pkl-lang.org/pkl-pantry/pkl.experimental.uri@1.0.3#/URI.pkl"
/// Whether this is a release build or not. /// Whether this is a release build or not.
isRelease: Boolean = false isRelease: Boolean = false
@@ -23,6 +24,25 @@ isRelease: Boolean = false
/// The OS to run on /// The OS to run on
os: "macOS"|"linux"|"windows" os: "macOS"|"linux"|"windows"
/// The version of Java to use.
javaVersion: "17.0"|"21.0"
fixed javaVersionFull =
if (javaVersion == "17.0") "17.0.9+9"
else "21.0.5+11"
fixed jdkVersionAlt = javaVersionFull.replaceLast("+", "_")
fixed majorJdkVersion = javaVersionFull.split(".").first
fixed jdkGitHubReleaseName =
let (ver =
// 17.0.9+9 is missing some binaries (see https://github.com/adoptium/adoptium-support/issues/994)
if (javaVersionFull == "17.0.9+9" && os == "windows") "jdk-17.0.9+9.1"
else "jdk-\(javaVersionFull)"
)
URI.encodeComponent(ver)
fixed gradleArgs = new Listing { fixed gradleArgs = new Listing {
"--info" "--info"
"--stacktrace" "--stacktrace"
@@ -37,9 +57,41 @@ steps: Listing<Config.Step>
job: Config.Job = new { job: Config.Job = new {
environment { environment {
["LANG"] = "en_US.UTF-8" ["LANG"] = "en_US.UTF-8"
when (os == "windows") {
["JAVA_HOME"] = "/jdk"
}
}
when (os == "linux") {
docker {
new {
image = "cimg/openjdk:\(javaVersion)"
}
}
}
when (os == "windows") {
machine {
image = "windows-server-2022-gui:current"
}
resource_class = "windows.large"
} }
steps { steps {
"checkout" "checkout"
when (os == "windows") {
new Config.RunStep {
name = "Set up environment"
shell = "bash.exe"
command = #"""
# install jdk
curl -Lf \
https://github.com/adoptium/temurin\#(majorJdkVersion)-binaries/releases/download/\#(jdkGitHubReleaseName)/OpenJDK\#(majorJdkVersion)U-jdk_x64_windows_hotspot_\#(jdkVersionAlt).zip -o /tmp/jdk.zip
unzip /tmp/jdk.zip -d /tmp/jdk \
&& cd /tmp/jdk/jdk-* \
&& mkdir /jdk \
&& cp -r . /jdk
"""#
}
}
...module.steps ...module.steps
new Config.StoreTestResults { new Config.StoreTestResults {
path = "~/test-results" path = "~/test-results"
+2 -6
View File
@@ -23,6 +23,8 @@ command: String
os = "linux" os = "linux"
javaVersion = "17.0"
steps { steps {
new Config.RunStep { new Config.RunStep {
name = module.name name = module.name
@@ -31,9 +33,3 @@ steps {
""" """
} }
} }
job {
docker {
new { image = "cimg/openjdk:17.0" }
}
}
+11 -2
View File
@@ -3,7 +3,8 @@
:uri-jenv: https://www.jenv.be :uri-jenv: https://www.jenv.be
:uri-intellij: https://www.jetbrains.com/idea/download/ :uri-intellij: https://www.jetbrains.com/idea/download/
:uri-jdk: https://adoptopenjdk.net/releases.html?variant=openjdk17 :uri-jdk: https://adoptopenjdk.net/releases.html?variant=openjdk17
:uri-native-prerequisites: https://www.graalvm.org/latest/getting-started/windows/#prerequisites-for-native-image-on-windows :uri-native-prerequisites-linux: https://www.graalvm.org/latest/getting-started/linux/#prerequisites-for-native-image-on-linux
:uri-native-prerequisites-windows: https://www.graalvm.org/latest/getting-started/windows/#prerequisites-for-native-image-on-windows
== Setup == Setup
@@ -25,12 +26,20 @@ Enable _jenv_ plugins for better handling by `gradle`:
jenv enable-plugin gradle jenv enable-plugin gradle
jenv enable-plugin export jenv enable-plugin export
---- ----
. (optional) If you've named the original apple/pkl git repository something other than `origin`, set env var `PKL_ORIGINAL_REMOTE_NAME` to that name in your `.bashrc`, `.zshrc`, `config.fish` or however you manage your local environment.
+
This will allow spotless to pick the correct starting branch when formatting source code files.
Otherwise, you might see that _every_ file has its copyright year updated.
=== Additional Linux Setup
. (optional) To build the native executable (`./gradlew buildNative`),
install {uri-native-prerequisites-linux}[Prerequisites For Native Image on Linux].
=== Additional Windows Setup === Additional Windows Setup
. (optional) Go to `System->For developers` and enable `Developer Mode`. . (optional) Go to `System->For developers` and enable `Developer Mode`.
Otherwise, some tests may fail due to insufficient file system privileges. Otherwise, some tests may fail due to insufficient file system privileges.
. (optional) To build the native executable (`./gradlew buildNative`), . (optional) To build the native executable (`./gradlew buildNative`),
install {uri-native-prerequisites}[Prerequisites For Native Image on Windows]. install {uri-native-prerequisites-windows}[Prerequisites For Native Image on Windows].
== Common Build Commands == Common Build Commands
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright © 2024 Apple Inc. and the Pkl project authors Copyright © 2024-2025 Apple Inc. and the Pkl project authors
Portions of this software were originally based on 'SnakeYAML' developed by Andrey Somov. Portions of this software were originally based on 'SnakeYAML' developed by Andrey Somov.
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -132,7 +132,11 @@ private fun KotlinGradleExtension.configureFormatter() {
licenseHeaderFile(licenseHeaderFile, "([a-zA-Z]|@file|//)") licenseHeaderFile(licenseHeaderFile, "([a-zA-Z]|@file|//)")
} }
val originalRemoteName = System.getenv("PKL_ORIGINAL_REMOTE_NAME") ?: "origin"
spotless { spotless {
ratchetFrom = "$originalRemoteName/main"
// When building root project, format buildSrc files too. // When building root project, format buildSrc files too.
// We need this because buildSrc is not a subproject of the root project, so a top-level // We need this because buildSrc is not a subproject of the root project, so a top-level
// `spotlessApply` will not trigger `buildSrc:spotlessApply`. // `spotlessApply` will not trigger `buildSrc:spotlessApply`.
@@ -1,5 +1,5 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. // Copyright © $YEAR Apple Inc. and the Pkl project authors. All rights reserved.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. * Copyright © $YEAR Apple Inc. and the Pkl project authors. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+2 -2
View File
@@ -1,6 +1,6 @@
name: main name: main
title: Main Project title: Main Project
version: 0.27.0-dev version: 0.27.2
prerelease: true prerelease: false
nav: nav:
- nav.adoc - nav.adoc
@@ -0,0 +1,20 @@
= Evolution and Roadmap
:uri-pkl-roadmap: https://github.com/orgs/apple/projects/12/views/1
:uri-pkl-evolution: https://github.com/apple/pkl-evolution
== Evolution
Sometimes, a change to Pkl is large enough that it makes sense to create a proposal for the change so that it can be discussed in detail and vetted.
Pkl has a process for managing such designs in a repository called {uri-pkl-evolution}[Pkl Evolutiuon].
== Roadmap
To discover what might be coming in future versions, reference the {uri-pkl-roadmap}[Pkl Roadmap] project on GitHub.
The roadmap describes estimates.
The Pkl team aims to cut a release in February, June, and October of each year.
If an item is not complete by the release cutoff date, the roadmap item may be bumped to the next release.
Additionally, as priorities change, it is possible that items can be moved around, or backlogged.
@@ -3,10 +3,10 @@
// the following attributes must be updated immediately before a release // the following attributes must be updated immediately before a release
// pkl version corresponding to current git commit without -dev suffix or git hash // pkl version corresponding to current git commit without -dev suffix or git hash
:pkl-version-no-suffix: 0.27.0 :pkl-version-no-suffix: 0.27.2
// tells whether pkl version corresponding to current git commit // tells whether pkl version corresponding to current git commit
// is a release version (:is-release-version: '') or dev version (:!is-release-version:) // is a release version (:is-release-version: '') or dev version (:!is-release-version:)
:!is-release-version: :is-release-version: ''
// the remaining attributes do not need to be updated regularly // the remaining attributes do not need to be updated regularly
+2 -2
View File
@@ -1,6 +1,6 @@
= Pkl 0.27 Release Notes = Pkl 0.27 Release Notes
:version: 0.27 :version: 0.27
:version-minor: 0.27.0 :version-minor: 0.27.2
:release-date: November 5th, 2024 :release-date: November 5th, 2024
include::ROOT:partial$component-attributes.adoc[] include::ROOT:partial$component-attributes.adoc[]
@@ -377,7 +377,7 @@ These are:
The `String` to `Number` converter methods, for example, {uri-stdlib-StringToInt}[`String.toInt()`], can now handle underscore separators (https://github.com/apple/pkl/pull/578[#578], https://github.com/apple/pkl/pull/580[#580]). The `String` to `Number` converter methods, for example, {uri-stdlib-StringToInt}[`String.toInt()`], can now handle underscore separators (https://github.com/apple/pkl/pull/578[#578], https://github.com/apple/pkl/pull/580[#580]).
This better aligns with the source code representation of file:///Users/danielchao/code/apple/pkl-lang.org/build/local/main/current/language-reference/index.html#integers[number literals]. This better aligns with the source code representation of xref:language-reference:index.adoc#integers[number literals].
[source,pkl] [source,pkl]
---- ----
@@ -1,6 +1,44 @@
= Changelog = Changelog
include::ROOT:partial$component-attributes.adoc[] include::ROOT:partial$component-attributes.adoc[]
[[release-0.27.2]]
== 0.27.2 (2025-01-22)
=== Fixes
* Fixes issues where server mode message decoding might result in null pointer exceptions (https://github.com/apple/pkl/pull/853[#853], https://github.com/apple/pkl/pull/882[#882]).
* Fixes an issue where the test report outputs decimal numbers using local-specific decimals (https://github.com/apple/pkl/pull/868[#868]).
* Fixes an issue where the native executables might not run on some environments, resulting in an error like "Fatal error: Failed to create the main Isolate" (https://github.com/apple/pkl/pull/875[#875]).
=== Contributors ❤️
Thank you to all the contributors for this release!
* link:https://github.com/HT154[@HT154]
* link:https://github.com/StefMa[@StefMa]
[[release-0.27.1]]
== 0.27.1 (2024-12-06)
=== Fixes
- Fixes a broken "number literals" link in the 0.27 release notes (https://github.com/apple/pkl/pull/784[#784]).
- Fixes a possible deadlock during external reader process close (https://github.com/apple/pkl/pull/786[#786]).
- Fixes counting elements with computed indices multiple times in length computation of listings (https://github.com/apple/pkl/pull/797[#797]).
- Fixes non Pkl modules being reported in GatherImports task, leading to plugin failures (https://github.com/apple/pkl/pull/821[#821]).
- Fixes a problem where the delegate chain of type casts for Listing/Mapping get unreasonably big, even though the type nodes are the same, which may lead to a stack overflow or performance degradation (https://github.com/apple/pkl/pull/826[#826]).
- Fixes incorrect scoping of type variables in lazy Listing/Mapping type checking in cross-module typealiases (https://github.com/apple/pkl/pull/789[#789]).
- Fixes regression in type checking logic for Listing/Mapping (https://github.com/apple/pkl/pull/789[#789]).
=== Contributors ❤️
Thank you to all the contributors for this release!
* link:https://github.com/GUI[@GUI]
* link:https://github.com/HT154[@HT154]
* link:https://github.com/odenix[@odenix] (formerly @translatenix)
[[release-0.27.0]] [[release-0.27.0]]
== 0.27.0 (2024-11-05) == 0.27.0 (2024-11-05)
@@ -1,5 +1,7 @@
= Release Notes = Release Notes
The Pkl team aims to release a new version of Pkl in February, June, and October of each year.
* xref:0.27.adoc[0.27 Release Notes] * xref:0.27.adoc[0.27 Release Notes]
* xref:0.26.adoc[0.26 Release Notes] * xref:0.26.adoc[0.26 Release Notes]
* xref:0.25.adoc[0.25 Release Notes] * xref:0.25.adoc[0.25 Release Notes]
+2
View File
@@ -38,6 +38,8 @@
* xref:ROOT:examples.adoc[Examples] * xref:ROOT:examples.adoc[Examples]
* xref:ROOT:evolution-and-roadmap.adoc[Evolution and Roadmap]
* xref:release-notes:index.adoc[Release Notes] * xref:release-notes:index.adoc[Release Notes]
** xref:release-notes:0.27.adoc[0.27 Release Notes] ** xref:release-notes:0.27.adoc[0.27 Release Notes]
** xref:release-notes:0.26.adoc[0.26 Release Notes] ** xref:release-notes:0.26.adoc[0.26 Release Notes]
+1 -1
View File
@@ -1,7 +1,7 @@
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
group=org.pkl-lang group=org.pkl-lang
version=0.27.0 version=0.27.2
# google-java-format requires jdk.compiler exports # google-java-format requires jdk.compiler exports
org.gradle.jvmargs= \ org.gradle.jvmargs= \
+3 -3
View File
@@ -2,15 +2,15 @@
"catalogs": {}, "catalogs": {},
"aliases": { "aliases": {
"pkl": { "pkl": {
"script-ref": "org.pkl-lang:pkl-cli-java:0.26.3", "script-ref": "org.pkl-lang:pkl-cli-java:0.27.2",
"java-agents": [] "java-agents": []
}, },
"pkl-codegen-java": { "pkl-codegen-java": {
"script-ref": "org.pkl-lang:pkl-codegen-java:0.26.3", "script-ref": "org.pkl-lang:pkl-codegen-java:0.27.2",
"java-agents": [] "java-agents": []
}, },
"pkl-codegen-kotlin": { "pkl-codegen-kotlin": {
"script-ref": "org.pkl-lang:pkl-codegen-kotlin:0.26.3", "script-ref": "org.pkl-lang:pkl-codegen-kotlin:0.27.2",
"java-agents": [] "java-agents": []
} }
}, },
+7 -2
View File
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -276,7 +276,12 @@ val linuxExecutableAarch64: TaskProvider<Exec> by
dependsOn(":installGraalVmAarch64") dependsOn(":installGraalVmAarch64")
configureExecutable( configureExecutable(
buildInfo.graalVmAarch64, buildInfo.graalVmAarch64,
layout.buildDirectory.file("executable/pkl-linux-aarch64") layout.buildDirectory.file("executable/pkl-linux-aarch64"),
listOf(
// Ensure compatibility for kernels with page size set to 4k, 16k and 64k
// (e.g. Raspberry Pi 5, Asahi Linux)
"-H:PageSize=65536"
)
) )
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.io.StringWriter
import java.io.Writer import java.io.Writer
import java.net.URI import java.net.URI
import java.nio.file.Path import java.nio.file.Path
import java.util.Locale
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatCode import org.assertj.core.api.Assertions.assertThatCode
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@@ -487,6 +488,50 @@ class CliTestRunnerTest {
) )
} }
@Test
fun `CliTestRunner locale independence test`(@TempDir tempDir: Path) {
val originalLocale = Locale.getDefault()
Locale.setDefault(Locale.GERMANY)
try {
val code =
"""
amends "pkl:test"
facts {
["localeTest"] {
1 == 1
}
}
"""
.trimIndent()
val input = tempDir.resolve("test.pkl").writeString(code).toString()
val out = StringWriter()
val err = StringWriter()
val opts =
CliBaseOptions(sourceModules = listOf(input.toUri()), settings = URI("pkl:settings"))
val testOpts = CliTestOptions()
val runner = CliTestRunner(opts, testOpts, consoleWriter = out, errWriter = err)
runner.run()
assertThat(out.toString().stripFileAndLines(tempDir))
.isEqualTo(
"""
module test
facts
✔ localeTest
100.0% tests pass [1 passed], 100.0% asserts pass [1 passed]
"""
.trimIndent()
)
assertThat(err.toString()).isEqualTo("")
} finally {
Locale.setDefault(originalLocale)
}
}
private fun String.stripFileAndLines(tmpDir: Path): String { private fun String.stripFileAndLines(tmpDir: Path): String {
// handle platform differences in handling of file URIs // handle platform differences in handling of file URIs
// (file:/// on *nix vs. file:/ on Windows) // (file:/// on *nix vs. file:/ on Windows)
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ import java.nio.file.Path
import java.util.stream.Collectors import java.util.stream.Collectors
import kotlin.io.path.* import kotlin.io.path.*
import org.assertj.core.api.Assertions.fail import org.assertj.core.api.Assertions.fail
import org.opentest4j.AssertionFailedError
import org.pkl.commons.* import org.pkl.commons.*
object FileTestUtils { object FileTestUtils {
@@ -110,5 +111,11 @@ data class SnippetOutcome(val expectedOutFile: Path, val actual: String, val suc
} }
private fun failWithDiff(message: String): Nothing = private fun failWithDiff(message: String): Nothing =
throw PklAssertionFailedError(message, expected, actual) if (System.getProperty("sun.java.command", "").contains("intellij")) {
// IntelliJ only shows diffs for AssertionFailedError
throw AssertionFailedError(message, expected, actual)
} else {
// Gradle test logging/report only shows diffs for PklAssertionFailedError
throw PklAssertionFailedError(message, expected, actual)
}
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1221,7 +1221,7 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
member.initConstantValue(constantNode); member.initConstantValue(constantNode);
} else { } else {
member.initMemberNode( member.initMemberNode(
new UntypedObjectMemberNode( ElementOrEntryNodeGen.create(
language, scope.buildFrameDescriptor(), member, elementNode)); language, scope.buildFrameDescriptor(), member, elementNode));
} }
@@ -1278,7 +1278,7 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
member.initConstantValue(constantNode); member.initConstantValue(constantNode);
} else { } else {
member.initMemberNode( member.initMemberNode(
new UntypedObjectMemberNode( ElementOrEntryNodeGen.create(
language, scope.buildFrameDescriptor(), member, valueNode)); language, scope.buildFrameDescriptor(), member, valueNode));
} }
} else { // ["key"] { ... } } else { // ["key"] { ... }
@@ -1287,7 +1287,7 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
objectBodyCtxs, objectBodyCtxs,
new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode())); new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode()));
member.initMemberNode( member.initMemberNode(
new UntypedObjectMemberNode( ElementOrEntryNodeGen.create(
language, scope.buildFrameDescriptor(), member, objectBody)); language, scope.buildFrameDescriptor(), member, objectBody));
} }
@@ -2446,6 +2446,7 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
return new UnresolvedTypeNode.Parameterized( return new UnresolvedTypeNode.Parameterized(
createSourceSection(ctx), createSourceSection(ctx),
language,
doVisitTypeName(idCtx), doVisitTypeName(idCtx),
argCtx.ts.stream().map(this::visitType).toArray(UnresolvedTypeNode[]::new)); argCtx.ts.stream().map(this::visitType).toArray(UnresolvedTypeNode[]::new));
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -103,7 +103,7 @@ public abstract class GeneratorPredicateMemberNode extends GeneratorMemberNode {
var callTarget = member.getCallTarget(); var callTarget = member.getCallTarget();
value = callTarget.call(parent, owner, key); value = callTarget.call(parent, owner, key);
} }
owner.setCachedValue(key, value, member); owner.setCachedValue(key, value);
} }
frame.setAuxiliarySlot(customThisSlot, value); frame.setAuxiliarySlot(customThisSlot, value);
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -43,15 +43,15 @@ public abstract class EntriesLiteralNode extends SpecializedObjectLiteralNode {
@Children private final ExpressionNode[] keyNodes; @Children private final ExpressionNode[] keyNodes;
private final ObjectMember[] values; private final ObjectMember[] values;
public EntriesLiteralNode( protected EntriesLiteralNode(
SourceSection sourceSection, SourceSection sourceSection,
VmLanguage language, VmLanguage language,
// contains local properties and default property (if present)
// does *not* contain entries with constant keys to maintain definition order of entries
String qualifiedScopeName, String qualifiedScopeName,
boolean isCustomThisScope, boolean isCustomThisScope,
@Nullable FrameDescriptor parametersDescriptor, @Nullable FrameDescriptor parametersDescriptor,
UnresolvedTypeNode[] parameterTypes, UnresolvedTypeNode[] parameterTypes,
// contains local properties and default property (if present)
// does *not* contain entries with constant keys to maintain definition order of entries
UnmodifiableEconomicMap<Object, ObjectMember> members, UnmodifiableEconomicMap<Object, ObjectMember> members,
ExpressionNode[] keyNodes, ExpressionNode[] keyNodes,
ObjectMember[] values) { ObjectMember[] values) {
@@ -103,7 +103,8 @@ public abstract class EntriesLiteralNode extends SpecializedObjectLiteralNode {
frame.materialize(), frame.materialize(),
parent, parent,
createListMembers(frame, parent.getLength()), createListMembers(frame, parent.getLength()),
parent.getLength() + keyNodes.length); // `[x] = y` overrides existing element and doesn't increase length
parent.getLength());
} }
@Specialization @Specialization
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -71,7 +71,7 @@ public final class ReadLocalPropertyNode extends ExpressionNode {
if (result == null) { if (result == null) {
result = callNode.call(objReceiver, owner, property.getName()); result = callNode.call(objReceiver, owner, property.getName());
objReceiver.setCachedValue(property, result, property); objReceiver.setCachedValue(property, result);
} }
return result; return result;
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -184,12 +184,12 @@ public final class ResolveVariableNode extends ExpressionNode {
if (member != null) { if (member != null) {
var constantValue = member.getConstantValue(); var constantValue = member.getConstantValue();
if (constantValue != null) { if (constantValue != null) {
baseModule.setCachedValue(variableName, constantValue, member); baseModule.setCachedValue(variableName, constantValue);
return new ConstantValueNode(sourceSection, constantValue); return new ConstantValueNode(sourceSection, constantValue);
} }
var computedValue = member.getCallTarget().call(baseModule, baseModule); var computedValue = member.getCallTarget().call(baseModule, baseModule);
baseModule.setCachedValue(variableName, computedValue, member); baseModule.setCachedValue(variableName, computedValue);
return new ConstantValueNode(sourceSection, computedValue); return new ConstantValueNode(sourceSection, computedValue);
} }
} }
@@ -0,0 +1,73 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.core.ast.member;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.Executed;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.expression.primary.GetReceiverNode;
import org.pkl.core.runtime.VmDynamic;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmListing;
import org.pkl.core.runtime.VmMapping;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.util.Nullable;
/** Equivalent of {@link TypedPropertyNode} for elements/entries. */
public abstract class ElementOrEntryNode extends RegularMemberNode {
@Child @Executed protected ExpressionNode receiverNode = new GetReceiverNode();
protected ElementOrEntryNode(
@Nullable VmLanguage language,
FrameDescriptor descriptor,
ObjectMember member,
ExpressionNode bodyNode) {
super(language, descriptor, member, bodyNode);
}
@Specialization
protected Object evalListing(
VirtualFrame frame,
VmListing receiver,
@Cached("create()") @Shared("callNode") IndirectCallNode callNode) {
var result = executeBody(frame);
return VmUtils.shouldRunTypeCheck(frame)
? receiver.executeTypeCasts(result, VmUtils.getOwner(frame), callNode, null, null)
: result;
}
@Specialization
protected Object evalMapping(
VirtualFrame frame,
VmMapping receiver,
@Cached("create()") @Shared("callNode") IndirectCallNode callNode) {
var result = executeBody(frame);
return VmUtils.shouldRunTypeCheck(frame)
? receiver.executeTypeCasts(result, VmUtils.getOwner(frame), callNode, null, null)
: result;
}
@Specialization
protected Object evalDynamic(VirtualFrame frame, VmDynamic ignored) {
return executeBody(frame);
}
}
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@ import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.util.Nullable; import org.pkl.core.util.Nullable;
/** Performs a typecast on a Mapping entry value, or a Listing element. */ /** Performs a typecast on a Mapping entry value, or a Listing element. */
public class ListingOrMappingTypeCastNode extends PklRootNode { public final class ListingOrMappingTypeCastNode extends PklRootNode {
@Child private TypeNode typeNode; @Child private TypeNode typeNode;
private final String qualifiedName; private final String qualifiedName;
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -73,7 +73,7 @@ public abstract class ResolveDeclaredTypeNode extends ExpressionNode {
var result = module.getCachedValue(importName); var result = module.getCachedValue(importName);
if (result == null) { if (result == null) {
result = callNode.call(member.getCallTarget(), module, module, importName); result = callNode.call(member.getCallTarget(), module, module, importName);
module.setCachedValue(importName, result, member); module.setCachedValue(importName, result);
} }
return (VmTyped) result; return (VmTyped) result;
} }
@@ -94,7 +94,7 @@ public abstract class ResolveDeclaredTypeNode extends ExpressionNode {
var result = module.getCachedValue(typeName); var result = module.getCachedValue(typeName);
if (result == null) { if (result == null) {
result = callNode.call(member.getCallTarget(), module, module, typeName); result = callNode.call(member.getCallTarget(), module, module, typeName);
module.setCachedValue(typeName, result, member); module.setCachedValue(typeName, result);
} }
return result; return result;
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameSlotKind; import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
@@ -161,7 +162,11 @@ public abstract class TypeNode extends PklNode {
} }
/** Tells if this typenode is the same typecheck as the other typenode. */ /** Tells if this typenode is the same typecheck as the other typenode. */
public abstract boolean isEquivalentTo(TypeNode other); public boolean isEquivalentTo(TypeNode other) {
return this == other || doIsEquivalentTo(other);
}
protected abstract boolean doIsEquivalentTo(TypeNode other);
public @Nullable VmClass getVmClass() { public @Nullable VmClass getVmClass() {
return null; return null;
@@ -304,7 +309,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof UnknownTypeNode; return other instanceof UnknownTypeNode;
} }
@@ -371,7 +376,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof NothingTypeNode; return other instanceof NothingTypeNode;
} }
@@ -408,7 +413,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof FinalModuleTypeNode finalModuleTypeNode)) { if (!(other instanceof FinalModuleTypeNode finalModuleTypeNode)) {
return false; return false;
} }
@@ -460,7 +465,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof NonFinalModuleTypeNode nonFinalModuleTypeNode)) { if (!(other instanceof NonFinalModuleTypeNode nonFinalModuleTypeNode)) {
return false; return false;
} }
@@ -496,7 +501,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof StringLiteralTypeNode stringLiteralTypeNode)) { if (!(other instanceof StringLiteralTypeNode stringLiteralTypeNode)) {
return false; return false;
} }
@@ -551,7 +556,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof TypedTypeNode; return other instanceof TypedTypeNode;
} }
@@ -586,7 +591,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof DynamicTypeNode; return other instanceof DynamicTypeNode;
} }
@@ -636,7 +641,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof FinalClassTypeNode finalClassTypeNode)) { if (!(other instanceof FinalClassTypeNode finalClassTypeNode)) {
return false; return false;
} }
@@ -705,7 +710,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof NonFinalClassTypeNode nonFinalClassTypeNode)) { if (!(other instanceof NonFinalClassTypeNode nonFinalClassTypeNode)) {
return false; return false;
} }
@@ -772,7 +777,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof NullableTypeNode nullableTypeNode)) { if (!(other instanceof NullableTypeNode nullableTypeNode)) {
return false; return false;
} }
@@ -842,7 +847,7 @@ public abstract class TypeNode extends PklNode {
@Override @Override
@ExplodeLoop @ExplodeLoop
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof UnionTypeNode unionTypeNode)) { if (!(other instanceof UnionTypeNode unionTypeNode)) {
return false; return false;
} }
@@ -1016,7 +1021,7 @@ public abstract class TypeNode extends PklNode {
@Override @Override
@TruffleBoundary @TruffleBoundary
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof UnionOfStringLiteralsTypeNode unionOfStringLiteralsTypeNode)) { if (!(other instanceof UnionOfStringLiteralsTypeNode unionOfStringLiteralsTypeNode)) {
return false; return false;
} }
@@ -1092,7 +1097,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return false; return false;
} }
@@ -1223,7 +1228,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof ListTypeNode listTypeNode)) { if (!(other instanceof ListTypeNode listTypeNode)) {
return false; return false;
} }
@@ -1268,7 +1273,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof SetTypeNode setTypeNode)) { if (!(other instanceof SetTypeNode setTypeNode)) {
return false; return false;
} }
@@ -1360,7 +1365,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof MapTypeNode mapTypeNode)) { if (!(other instanceof MapTypeNode mapTypeNode)) {
return false; return false;
} }
@@ -1414,8 +1419,9 @@ public abstract class TypeNode extends PklNode {
} }
public static final class ListingTypeNode extends ListingOrMappingTypeNode { public static final class ListingTypeNode extends ListingOrMappingTypeNode {
public ListingTypeNode(SourceSection sourceSection, TypeNode valueTypeNode) { public ListingTypeNode(
super(sourceSection, null, valueTypeNode); SourceSection sourceSection, VmLanguage language, TypeNode valueTypeNode) {
super(sourceSection, language, null, valueTypeNode);
} }
@Override @Override
@@ -1423,7 +1429,17 @@ public abstract class TypeNode extends PklNode {
if (!(value instanceof VmListing vmListing)) { if (!(value instanceof VmListing vmListing)) {
throw typeMismatch(value, BaseModule.getListingClass()); throw typeMismatch(value, BaseModule.getListingClass());
} }
return doTypeCast(frame, vmListing); if (vmListing.isValueTypeKnownSubtypeOf(valueTypeNode)) {
return vmListing;
}
return new VmListing(
vmListing.getEnclosingFrame(),
vmListing,
EconomicMaps.emptyMap(),
vmListing.getLength(),
getValueTypeCastNode(),
VmUtils.getReceiver(frame),
VmUtils.getOwner(frame));
} }
@Override @Override
@@ -1451,7 +1467,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof ListingTypeNode listingTypeNode)) { if (!(other instanceof ListingTypeNode listingTypeNode)) {
return false; return false;
} }
@@ -1466,9 +1482,12 @@ public abstract class TypeNode extends PklNode {
public static final class MappingTypeNode extends ListingOrMappingTypeNode { public static final class MappingTypeNode extends ListingOrMappingTypeNode {
public MappingTypeNode( public MappingTypeNode(
SourceSection sourceSection, TypeNode keyTypeNode, TypeNode valueTypeNode) { SourceSection sourceSection,
VmLanguage language,
TypeNode keyTypeNode,
TypeNode valueTypeNode) {
super(sourceSection, keyTypeNode, valueTypeNode); super(sourceSection, language, keyTypeNode, valueTypeNode);
} }
@Override @Override
@@ -1478,7 +1497,16 @@ public abstract class TypeNode extends PklNode {
} }
// execute type checks on mapping keys // execute type checks on mapping keys
doEagerCheck(frame, vmMapping, false, true); doEagerCheck(frame, vmMapping, false, true);
return doTypeCast(frame, vmMapping); if (vmMapping.isValueTypeKnownSubtypeOf(valueTypeNode)) {
return vmMapping;
}
return new VmMapping(
vmMapping.getEnclosingFrame(),
vmMapping,
EconomicMaps.emptyMap(),
getValueTypeCastNode(),
VmUtils.getReceiver(frame),
VmUtils.getOwner(frame));
} }
@Override @Override
@@ -1509,7 +1537,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof MappingTypeNode mappingTypeNode)) { if (!(other instanceof MappingTypeNode mappingTypeNode)) {
return false; return false;
} }
@@ -1526,17 +1554,22 @@ public abstract class TypeNode extends PklNode {
} }
public abstract static class ListingOrMappingTypeNode extends ObjectSlotTypeNode { public abstract static class ListingOrMappingTypeNode extends ObjectSlotTypeNode {
private final VmLanguage language;
@Child protected @Nullable TypeNode keyTypeNode; @Child protected @Nullable TypeNode keyTypeNode;
@Child protected TypeNode valueTypeNode; @Child protected TypeNode valueTypeNode;
@Child @Nullable protected ListingOrMappingTypeCastNode listingOrMappingTypeCastNode; @Child @Nullable protected ListingOrMappingTypeCastNode valueTypeCastNode;
private final boolean skipKeyTypeChecks; private final boolean skipKeyTypeChecks;
private final boolean skipValueTypeChecks; private final boolean skipValueTypeChecks;
protected ListingOrMappingTypeNode( protected ListingOrMappingTypeNode(
SourceSection sourceSection, @Nullable TypeNode keyTypeNode, TypeNode valueTypeNode) { SourceSection sourceSection,
VmLanguage language,
@Nullable TypeNode keyTypeNode,
TypeNode valueTypeNode) {
super(sourceSection); super(sourceSection);
this.language = language;
this.keyTypeNode = keyTypeNode; this.keyTypeNode = keyTypeNode;
this.valueTypeNode = valueTypeNode; this.valueTypeNode = valueTypeNode;
@@ -1556,17 +1589,14 @@ public abstract class TypeNode extends PklNode {
return valueTypeNode; return valueTypeNode;
} }
protected ListingOrMappingTypeCastNode getListingOrMappingTypeCastNode() { protected ListingOrMappingTypeCastNode getValueTypeCastNode() {
if (listingOrMappingTypeCastNode == null) { if (valueTypeCastNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate(); CompilerDirectives.transferToInterpreterAndInvalidate();
listingOrMappingTypeCastNode = valueTypeCastNode =
new ListingOrMappingTypeCastNode( new ListingOrMappingTypeCastNode(
VmLanguage.get(this), language, new FrameDescriptor(), valueTypeNode, getRootNode().getName());
getRootNode().getFrameDescriptor(),
valueTypeNode,
getRootNode().getName());
} }
return listingOrMappingTypeCastNode; return valueTypeCastNode;
} }
// either (if defaultMemberValue != null): // either (if defaultMemberValue != null):
@@ -1647,15 +1677,6 @@ public abstract class TypeNode extends PklNode {
EconomicMaps.of(Identifier.DEFAULT, defaultMember)); EconomicMaps.of(Identifier.DEFAULT, defaultMember));
} }
protected <T extends VmListingOrMapping<T>> T doTypeCast(VirtualFrame frame, T original) {
// optimization: don't create new object if the original already has the same typecheck, or if
// this typecheck is a no-op.
if (isNoopTypeCheck() || original.hasSameChecksAs(valueTypeNode)) {
return original;
}
return original.withCheckedMembers(getListingOrMappingTypeCastNode(), frame.materialize());
}
protected void doEagerCheck(VirtualFrame frame, VmObject object) { protected void doEagerCheck(VirtualFrame frame, VmObject object) {
doEagerCheck(frame, object, skipKeyTypeChecks, skipValueTypeChecks); doEagerCheck(frame, object, skipKeyTypeChecks, skipValueTypeChecks);
} }
@@ -1700,7 +1721,7 @@ public abstract class TypeNode extends PklNode {
var callTarget = member.getCallTarget(); var callTarget = member.getCallTarget();
memberValue = callTarget.call(object, owner, memberKey); memberValue = callTarget.call(object, owner, memberKey);
} }
object.setCachedValue(memberKey, memberValue, member); object.setCachedValue(memberKey, memberValue);
} }
valueTypeNode.executeEagerly(frame, memberValue); valueTypeNode.executeEagerly(frame, memberValue);
} }
@@ -1748,7 +1769,7 @@ public abstract class TypeNode extends PklNode {
@Override @Override
@ExplodeLoop @ExplodeLoop
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof FunctionTypeNode functionTypeNode)) { if (!(other instanceof FunctionTypeNode functionTypeNode)) {
return false; return false;
} }
@@ -1845,7 +1866,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof FunctionClassTypeNode functionClassTypeNode)) { if (!(other instanceof FunctionClassTypeNode functionClassTypeNode)) {
return false; return false;
} }
@@ -1883,7 +1904,7 @@ public abstract class TypeNode extends PklNode {
@Override @Override
@ExplodeLoop @ExplodeLoop
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof FunctionNClassTypeNode functionNClassTypeNode)) { if (!(other instanceof FunctionNClassTypeNode functionNClassTypeNode)) {
return false; return false;
} }
@@ -1986,7 +2007,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof PairTypeNode pairTypeNode)) { if (!(other instanceof PairTypeNode pairTypeNode)) {
return false; return false;
} }
@@ -2043,7 +2064,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof VarArgsTypeNode varArgsTypeNode)) { if (!(other instanceof VarArgsTypeNode varArgsTypeNode)) {
return false; return false;
} }
@@ -2095,7 +2116,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof TypeVariableNode; return other instanceof TypeVariableNode;
} }
@@ -2135,7 +2156,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof NonNullTypeAliasTypeNode; return other instanceof NonNullTypeAliasTypeNode;
} }
@@ -2184,7 +2205,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof UIntTypeAliasTypeNode aliasTypeNode && mask == aliasTypeNode.mask; return other instanceof UIntTypeAliasTypeNode aliasTypeNode && mask == aliasTypeNode.mask;
} }
@@ -2228,7 +2249,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof Int8TypeAliasTypeNode; return other instanceof Int8TypeAliasTypeNode;
} }
@@ -2272,7 +2293,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof Int16TypeAliasTypeNode; return other instanceof Int16TypeAliasTypeNode;
} }
@@ -2316,7 +2337,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof Int32TypeAliasTypeNode; return other instanceof Int32TypeAliasTypeNode;
} }
@@ -2387,14 +2408,14 @@ public abstract class TypeNode extends PklNode {
public Object execute(VirtualFrame frame, Object value) { public Object execute(VirtualFrame frame, Object value) {
var prevOwner = VmUtils.getOwner(frame); var prevOwner = VmUtils.getOwner(frame);
var prevReceiver = VmUtils.getReceiver(frame); var prevReceiver = VmUtils.getReceiver(frame);
VmUtils.setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame())); setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame()));
VmUtils.setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame())); setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame()));
try { try {
return aliasedTypeNode.execute(frame, value); return aliasedTypeNode.execute(frame, value);
} finally { } finally {
VmUtils.setOwner(frame, prevOwner); setOwner(frame, prevOwner);
VmUtils.setReceiver(frame, prevReceiver); setReceiver(frame, prevReceiver);
} }
} }
@@ -2403,14 +2424,14 @@ public abstract class TypeNode extends PklNode {
public Object executeAndSet(VirtualFrame frame, Object value) { public Object executeAndSet(VirtualFrame frame, Object value) {
var prevOwner = VmUtils.getOwner(frame); var prevOwner = VmUtils.getOwner(frame);
var prevReceiver = VmUtils.getReceiver(frame); var prevReceiver = VmUtils.getReceiver(frame);
VmUtils.setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame())); setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame()));
VmUtils.setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame())); setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame()));
try { try {
return aliasedTypeNode.executeAndSet(frame, value); return aliasedTypeNode.executeAndSet(frame, value);
} finally { } finally {
VmUtils.setOwner(frame, prevOwner); setOwner(frame, prevOwner);
VmUtils.setReceiver(frame, prevReceiver); setReceiver(frame, prevReceiver);
} }
} }
@@ -2419,14 +2440,14 @@ public abstract class TypeNode extends PklNode {
public Object executeEagerlyAndSet(VirtualFrame frame, Object value) { public Object executeEagerlyAndSet(VirtualFrame frame, Object value) {
var prevOwner = VmUtils.getOwner(frame); var prevOwner = VmUtils.getOwner(frame);
var prevReceiver = VmUtils.getReceiver(frame); var prevReceiver = VmUtils.getReceiver(frame);
VmUtils.setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame())); setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame()));
VmUtils.setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame())); setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame()));
try { try {
return aliasedTypeNode.executeEagerlyAndSet(frame, value); return aliasedTypeNode.executeEagerlyAndSet(frame, value);
} finally { } finally {
VmUtils.setOwner(frame, prevOwner); setOwner(frame, prevOwner);
VmUtils.setReceiver(frame, prevReceiver); setReceiver(frame, prevReceiver);
} }
} }
@@ -2471,7 +2492,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
if ((other instanceof TypeAliasTypeNode typeAliasTypeNode)) { if ((other instanceof TypeAliasTypeNode typeAliasTypeNode)) {
return aliasedTypeNode.isEquivalentTo(typeAliasTypeNode.aliasedTypeNode); return aliasedTypeNode.isEquivalentTo(typeAliasTypeNode.aliasedTypeNode);
} }
@@ -2498,6 +2519,22 @@ public abstract class TypeNode extends PklNode {
protected boolean isParametric() { protected boolean isParametric() {
return typeArgumentNodes.length > 0; return typeArgumentNodes.length > 0;
} }
// Note that mutating a frame's receiver and owner argument is very risky
// because any VmObject instantiated within the same root node execution
// holds a reference to (not immutable snapshot of) the frame
// via VmObjectLike.enclosingFrame.
// *Maybe* this works out for TypeAliasTypeNode because an object instantiated
// within a type constraint doesn't escape the constraint expression.
// If mutating receiver and owner can't be avoided, it would be safer
// to have VmObjectLike store them directly instead of storing enclosingFrame.
private static void setReceiver(Frame frame, Object receiver) {
frame.getArguments()[0] = receiver;
}
private static void setOwner(Frame frame, VmObjectLike owner) {
frame.getArguments()[1] = owner;
}
} }
public static final class ConstrainedTypeNode extends TypeNode { public static final class ConstrainedTypeNode extends TypeNode {
@@ -2581,7 +2618,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
// consider constrained types as always different // consider constrained types as always different
return false; return false;
} }
@@ -2622,7 +2659,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof AnyTypeNode; return other instanceof AnyTypeNode;
} }
@@ -2650,7 +2687,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof StringTypeNode; return other instanceof StringTypeNode;
} }
@@ -2714,7 +2751,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof NumberTypeNode; return other instanceof NumberTypeNode;
} }
@@ -2742,7 +2779,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof IntTypeNode; return other instanceof IntTypeNode;
} }
@@ -2787,7 +2824,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof FloatTypeNode; return other instanceof FloatTypeNode;
} }
@@ -2832,7 +2869,7 @@ public abstract class TypeNode extends PklNode {
} }
@Override @Override
public boolean isEquivalentTo(TypeNode other) { public boolean doIsEquivalentTo(TypeNode other) {
return other instanceof BooleanTypeNode; return other instanceof BooleanTypeNode;
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -197,14 +197,17 @@ public abstract class UnresolvedTypeNode extends PklNode {
} }
public static final class Parameterized extends UnresolvedTypeNode { public static final class Parameterized extends UnresolvedTypeNode {
private final VmLanguage language;
@Child private ExpressionNode resolveTypeNode; @Child private ExpressionNode resolveTypeNode;
@Children private final UnresolvedTypeNode[] typeArgumentNodes; @Children private final UnresolvedTypeNode[] typeArgumentNodes;
public Parameterized( public Parameterized(
SourceSection sourceSection, SourceSection sourceSection,
VmLanguage language,
ExpressionNode resolveTypeNode, ExpressionNode resolveTypeNode,
UnresolvedTypeNode[] typeArgumentNodes) { UnresolvedTypeNode[] typeArgumentNodes) {
super(sourceSection); super(sourceSection);
this.language = language;
this.resolveTypeNode = resolveTypeNode; this.resolveTypeNode = resolveTypeNode;
this.typeArgumentNodes = typeArgumentNodes; this.typeArgumentNodes = typeArgumentNodes;
} }
@@ -238,12 +241,13 @@ public abstract class UnresolvedTypeNode extends PklNode {
} }
if (clazz.isListingClass()) { if (clazz.isListingClass()) {
return new ListingTypeNode(sourceSection, typeArgumentNodes[0].execute(frame)); return new ListingTypeNode(sourceSection, language, typeArgumentNodes[0].execute(frame));
} }
if (clazz.isMappingClass()) { if (clazz.isMappingClass()) {
return new MappingTypeNode( return new MappingTypeNode(
sourceSection, sourceSection,
language,
typeArgumentNodes[0].execute(frame), typeArgumentNodes[0].execute(frame),
typeArgumentNodes[1].execute(frame)); typeArgumentNodes[1].execute(frame));
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,17 +17,16 @@ package org.pkl.core.externalreader;
import java.io.IOException; import java.io.IOException;
import java.lang.ProcessBuilder.Redirect; import java.lang.ProcessBuilder.Redirect;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Random; import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import org.pkl.core.Duration;
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings.ExternalReader; import org.pkl.core.evaluatorSettings.PklEvaluatorSettings.ExternalReader;
import org.pkl.core.externalreader.ExternalReaderMessages.*; import org.pkl.core.externalreader.ExternalReaderMessages.*;
import org.pkl.core.messaging.MessageTransport; import org.pkl.core.messaging.MessageTransport;
@@ -108,7 +107,9 @@ final class ExternalReaderProcessImpl implements ExternalReaderProcess {
// This relies on Java/OS behavior around PATH resolution, absolute/relative paths, etc. // This relies on Java/OS behavior around PATH resolution, absolute/relative paths, etc.
var command = new ArrayList<String>(); var command = new ArrayList<String>();
command.add(spec.executable()); command.add(spec.executable());
command.addAll(spec.arguments()); if (spec.arguments() != null) {
command.addAll(spec.arguments());
}
var builder = new ProcessBuilder(command); var builder = new ProcessBuilder(command);
builder.redirectError(Redirect.INHERIT); // inherit stderr from this pkl process builder.redirectError(Redirect.INHERIT); // inherit stderr from this pkl process
@@ -152,40 +153,23 @@ final class ExternalReaderProcessImpl implements ExternalReaderProcess {
@Override @Override
public void close() { public void close() {
synchronized (lock) { synchronized (lock) {
if (closed) return;
closed = true; closed = true;
if (process == null || !process.isAlive()) {
return;
}
try { try {
if (transport != null) { if (transport != null && process != null && process.isAlive()) {
transport.send(new CloseExternalProcess()); transport.send(new CloseExternalProcess());
process.waitFor(CLOSE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
}
} catch (Exception ignored) {
} finally {
if (process != null) {
// no-op unless process is alive
process.destroyForcibly();
}
if (transport != null) {
transport.close(); transport.close();
} }
// forcefully stop the process after the timeout
// note that both transport.close() and process.destroy() are safe to call multiple times
new Timer()
.schedule(
new TimerTask() {
@Override
public void run() {
if (process != null) {
transport.close();
process.destroyForcibly();
}
}
},
CLOSE_TIMEOUT.inWholeMillis());
// block on process exit
process.onExit().get();
} catch (Exception e) {
transport.close();
process.destroyForcibly();
} finally {
process = null;
transport = null;
} }
} }
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -113,7 +113,7 @@ public final class VmFunction extends VmObjectLike {
@Override @Override
@TruffleBoundary @TruffleBoundary
public void setCachedValue(Object key, Object value, ObjectMember objectMember) { public void setCachedValue(Object key, Object value) {
throw new VmExceptionBuilder() throw new VmExceptionBuilder()
.bug("Class `VmFunction` does not support method `setCachedValue()`.") .bug("Class `VmFunction` does not support method `setCachedValue()`.")
.build(); .build();
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@ import org.pkl.core.util.Nullable;
@TruffleLanguage.Registration( @TruffleLanguage.Registration(
id = "pkl", id = "pkl",
name = "Pkl", name = "Pkl",
version = "0.27.0-dev", version = "0.27.2",
characterMimeTypes = VmLanguage.MIME_TYPE, characterMimeTypes = VmLanguage.MIME_TYPE,
contextPolicy = ContextPolicy.SHARED) contextPolicy = ContextPolicy.SHARED)
public final class VmLanguage extends TruffleLanguage<VmContext> { public final class VmLanguage extends TruffleLanguage<VmContext> {
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,14 +19,13 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.MaterializedFrame;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import org.graalvm.collections.UnmodifiableEconomicMap; import org.graalvm.collections.UnmodifiableEconomicMap;
import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ListingOrMappingTypeCastNode;
import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.Nullable; import org.pkl.core.util.Nullable;
public final class VmListing extends VmListingOrMapping<VmListing> { public final class VmListing extends VmListingOrMapping {
private static final class EmptyHolder { private static final class EmptyHolder {
private static final VmListing EMPTY = private static final VmListing EMPTY =
new VmListing( new VmListing(
@@ -47,7 +46,7 @@ public final class VmListing extends VmListingOrMapping<VmListing> {
VmObject parent, VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members, UnmodifiableEconomicMap<Object, ObjectMember> members,
int length) { int length) {
super(enclosingFrame, Objects.requireNonNull(parent), members, null, null, null); super(enclosingFrame, parent, members);
this.length = length; this.length = length;
} }
@@ -56,16 +55,10 @@ public final class VmListing extends VmListingOrMapping<VmListing> {
VmObject parent, VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members, UnmodifiableEconomicMap<Object, ObjectMember> members,
int length, int length,
@Nullable VmListing delegate, ListingOrMappingTypeCastNode typeCastNode,
ListingOrMappingTypeCastNode typeCheckNode, Object typeCheckReceiver,
MaterializedFrame typeNodeFrame) { VmObjectLike typeCheckOwner) {
super( super(enclosingFrame, parent, members, typeCastNode, typeCheckReceiver, typeCheckOwner);
enclosingFrame,
Objects.requireNonNull(parent),
members,
delegate,
typeCheckNode,
typeNodeFrame);
this.length = length; this.length = length;
} }
@@ -117,20 +110,6 @@ public final class VmListing extends VmListingOrMapping<VmListing> {
return converter.convertListing(this, path); return converter.convertListing(this, path);
} }
@Override
public VmListing withCheckedMembers(
ListingOrMappingTypeCastNode typeCheckNode, MaterializedFrame typeNodeFrame) {
return new VmListing(
getEnclosingFrame(),
Objects.requireNonNull(parent),
members,
length,
this,
typeCheckNode,
typeNodeFrame);
}
@Override @Override
@TruffleBoundary @TruffleBoundary
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
@@ -142,10 +121,14 @@ public final class VmListing extends VmListingOrMapping<VmListing> {
force(false); force(false);
other.force(false); other.force(false);
for (var i = 0L; i < length; i++) { var cursor = cachedValues.getEntries();
var value = getCachedValue(i); while (cursor.advance()) {
var key = cursor.getKey();
if (key instanceof Identifier) continue;
var value = cursor.getValue();
assert value != null; assert value != null;
var otherValue = other.getCachedValue(i); var otherValue = other.getCachedValue(key);
if (!value.equals(otherValue)) return false; if (!value.equals(otherValue)) return false;
} }
@@ -156,14 +139,16 @@ public final class VmListing extends VmListingOrMapping<VmListing> {
@TruffleBoundary @TruffleBoundary
public int hashCode() { public int hashCode() {
if (cachedHash != 0) return cachedHash; if (cachedHash != 0) return cachedHash;
// It's possible that the delegate has already computed its hash code.
// If so, we can go ahead and use it.
if (delegate != null && delegate.cachedHash != 0) return delegate.cachedHash;
force(false); force(false);
var result = 0; var result = 0;
for (var i = 0L; i < length; i++) { var cursor = cachedValues.getEntries();
var value = getCachedValue(i);
while (cursor.advance()) {
var key = cursor.getKey();
if (key instanceof Identifier) continue;
var value = cursor.getValue();
assert value != null; assert value != null;
result = 31 * result + value.hashCode(); result = 31 * result + value.hashCode();
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,159 +16,122 @@
package org.pkl.core.runtime; package org.pkl.core.runtime;
import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.nodes.IndirectCallNode; import com.oracle.truffle.api.nodes.IndirectCallNode;
import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.EconomicSet;
import org.graalvm.collections.UnmodifiableEconomicMap; import org.graalvm.collections.UnmodifiableEconomicMap;
import org.pkl.core.PklBugException;
import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ListingOrMappingTypeCastNode;
import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.ast.type.TypeNode; import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.EconomicSets;
import org.pkl.core.util.Nullable; import org.pkl.core.util.Nullable;
public abstract class VmListingOrMapping<SELF extends VmListingOrMapping<SELF>> extends VmObject { public abstract class VmListingOrMapping extends VmObject {
// reified type of listing elements and mapping values
/**
* A Listing or Mapping typecast creates a new object that contains a new typecheck node, and
* delegates member lookups to this delegate.
*/
protected final @Nullable SELF delegate;
private final @Nullable ListingOrMappingTypeCastNode typeCastNode; private final @Nullable ListingOrMappingTypeCastNode typeCastNode;
private final MaterializedFrame typeNodeFrame; private final @Nullable Object typeCheckReceiver;
private final EconomicMap<Object, ObjectMember> cachedMembers = EconomicMaps.create(); private final @Nullable VmObjectLike typeCheckOwner;
private final EconomicSet<Object> checkedMembers = EconomicSets.create();
public VmListingOrMapping(
MaterializedFrame enclosingFrame,
@Nullable VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members) {
super(enclosingFrame, parent, members);
typeCastNode = null;
typeCheckReceiver = null;
typeCheckOwner = null;
}
public VmListingOrMapping( public VmListingOrMapping(
MaterializedFrame enclosingFrame, MaterializedFrame enclosingFrame,
@Nullable VmObject parent, @Nullable VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members, UnmodifiableEconomicMap<Object, ObjectMember> members,
@Nullable SELF delegate, ListingOrMappingTypeCastNode typeCastNode,
@Nullable ListingOrMappingTypeCastNode typeCastNode, Object typeCheckReceiver,
@Nullable MaterializedFrame typeNodeFrame) { VmObjectLike typeCheckOwner) {
super(enclosingFrame, parent, members); super(enclosingFrame, parent, members);
this.delegate = delegate;
this.typeCastNode = typeCastNode; this.typeCastNode = typeCastNode;
this.typeNodeFrame = typeNodeFrame; this.typeCheckReceiver = typeCheckReceiver;
this.typeCheckOwner = typeCheckOwner;
} }
ObjectMember findMember(Object key) { // Recursively executes type casts between `owner` and `this` and returns the resulting value.
var member = EconomicMaps.get(cachedMembers, key); public final Object executeTypeCasts(
if (member != null) { Object value,
return member; VmObjectLike owner,
} IndirectCallNode callNode,
if (delegate != null) { // if non-null, a stack frame for this member is inserted if a type cast fails
return delegate.findMember(key); @Nullable ObjectMember member,
} // Next type cast to be performed by the caller.
// member is guaranteed to exist; this is only called if `getCachedValue()` returns non-null // Avoids repeating the same type cast in some cases.
// and `setCachedValue` will record the object member in `cachedMembers`. @Nullable ListingOrMappingTypeCastNode nextTypeCastNode) {
throw PklBugException.unreachableCode(); var newNextTypeCastNode = typeCastNode != null ? typeCastNode : nextTypeCastNode;
} @SuppressWarnings("DataFlowIssue")
var result =
public @Nullable ListingOrMappingTypeCastNode getTypeCastNode() { this == owner
return typeCastNode; ? value
} : ((VmListingOrMapping) parent)
.executeTypeCasts(value, owner, callNode, member, newNextTypeCastNode);
@Override if (typeCastNode == null || typeCastNode == nextTypeCastNode) return result;
public void setCachedValue(Object key, Object value, ObjectMember objectMember) {
super.setCachedValue(key, value, objectMember);
EconomicMaps.put(cachedMembers, key, objectMember);
}
@Override
public boolean hasCachedValue(Object key) {
return super.hasCachedValue(key) || delegate != null && delegate.hasCachedValue(key);
}
@Override
public @Nullable Object getCachedValue(Object key) {
var myCachedValue = super.getCachedValue(key);
if (myCachedValue != null || delegate == null) {
return myCachedValue;
}
var memberValue = delegate.getCachedValue(key);
// if this object member appears inside `checkedMembers`, we have already checked its type
// and can safely return it.
if (EconomicSets.contains(checkedMembers, key)) {
return memberValue;
}
if (memberValue == null) {
return null;
}
// If a cached value already exists on the delegate, run a typecast on it.
// optimization: don't use `VmUtils.findMember` to avoid iterating over all members
var objectMember = findMember(key);
var ret = typecastObjectMember(objectMember, memberValue, IndirectCallNode.getUncached());
if (ret != memberValue) {
EconomicMaps.put(cachedValues, key, ret);
} else {
// optimization: don't add to own cached values if typecast results in the same value
EconomicSets.add(checkedMembers, key);
}
return ret;
}
@Override
public Object getExtraStorage() {
if (delegate != null) {
return delegate.getExtraStorage();
}
assert extraStorage != null;
return extraStorage;
}
/** Perform a typecast on this member, */
public Object typecastObjectMember(
ObjectMember member, Object memberValue, IndirectCallNode callNode) {
if (!(member.isEntry() || member.isElement()) || typeCastNode == null) {
return memberValue;
}
assert typeNodeFrame != null;
var ret = memberValue;
if (delegate != null) {
ret = delegate.typecastObjectMember(member, ret, callNode);
}
var callTarget = typeCastNode.getCallTarget(); var callTarget = typeCastNode.getCallTarget();
try { try {
return callNode.call( return callNode.call(callTarget, typeCheckReceiver, typeCheckOwner, result);
callTarget, VmUtils.getReceiver(typeNodeFrame), VmUtils.getOwner(typeNodeFrame), ret); } catch (VmException e) {
} catch (VmException vmException) {
CompilerDirectives.transferToInterpreter(); CompilerDirectives.transferToInterpreter();
// treat typecheck as part of the call stack to read the original member if there is a if (member != null) {
// source section for it. VmUtils.insertStackFrame(member, callTarget, e);
var sourceSection = member.getBodySection();
if (!sourceSection.isAvailable()) {
sourceSection = member.getSourceSection();
} }
if (sourceSection.isAvailable()) { throw e;
vmException
.getInsertedStackFrames()
.put(callTarget, VmUtils.createStackFrame(sourceSection, member.getQualifiedName()));
}
throw vmException;
} }
} }
public abstract SELF withCheckedMembers( @Override
ListingOrMappingTypeCastNode typeCastNode, MaterializedFrame typeNodeFrame); @TruffleBoundary
public final @Nullable Object getCachedValue(Object key) {
var result = EconomicMaps.get(cachedValues, key);
// if this object has members, `this[key]` may differ from `parent[key]`, so stop the search
if (result != null || !members.isEmpty()) return result;
/** Tells if this mapping/listing runs the same typechecks as {@code typeNode}. */ // Optimization: Recursively steal value from parent cache to avoid computing it multiple times.
public boolean hasSameChecksAs(TypeNode typeNode) { // The current implementation has the following limitations and drawbacks:
// * It only works if a parent has, coincidentally, already cached `key`.
// * It turns getCachedValue() into an operation that isn't guaranteed to be fast and fail-safe.
// * It requires making VmObject.getCachedValue() non-final,
// which is unfavorable for Truffle partial evaluation and JVM inlining.
// * It may not be worth its cost for constant members and members that are cheap to compute.
assert parent != null; // VmListingOrMapping always has a parent
result = parent.getCachedValue(key);
if (result == null) return null;
if (typeCastNode != null && !(key instanceof Identifier)) {
var callNode = IndirectCallNode.getUncached();
var callTarget = typeCastNode.getCallTarget();
try {
result = callNode.call(callTarget, typeCheckReceiver, typeCheckOwner, result);
} catch (VmException e) {
var member = VmUtils.findMember(parent, key);
assert member != null; // already found the member's cached value
VmUtils.insertStackFrame(member, callTarget, e);
throw e;
}
}
setCachedValue(key, result);
return result;
}
/**
* Tells whether the value type of this listing/mapping is known to be a subtype of {@code
* typeNode}. If {@code true}, type checks of individual values can be elided because
* listings/mappings are covariant in their value type.
*/
public final boolean isValueTypeKnownSubtypeOf(TypeNode typeNode) {
if (typeNode.isNoopTypeCheck()) {
return true;
}
if (typeCastNode == null) { if (typeCastNode == null) {
return false; return false;
} }
if (typeCastNode.getTypeNode().isEquivalentTo(typeNode)) { return typeCastNode.getTypeNode().isEquivalentTo(typeNode);
return true;
}
// we can say the check is the same if the delegate has this check.
// when `Listing<Any>` delegates to `Listing<UInt>`, it has the same checks as a `UInt`
// typenode.
if (delegate != null) {
return delegate.hasSameChecksAs(typeNode);
}
return false;
} }
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.pkl.core.runtime;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.MaterializedFrame;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import org.graalvm.collections.UnmodifiableEconomicMap; import org.graalvm.collections.UnmodifiableEconomicMap;
import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ListingOrMappingTypeCastNode;
@@ -27,7 +26,7 @@ import org.pkl.core.util.CollectionUtils;
import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.LateInit; import org.pkl.core.util.LateInit;
public final class VmMapping extends VmListingOrMapping<VmMapping> { public final class VmMapping extends VmListingOrMapping {
private int cachedEntryCount = -1; private int cachedEntryCount = -1;
@@ -50,24 +49,17 @@ public final class VmMapping extends VmListingOrMapping<VmMapping> {
MaterializedFrame enclosingFrame, MaterializedFrame enclosingFrame,
VmObject parent, VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members) { UnmodifiableEconomicMap<Object, ObjectMember> members) {
super(enclosingFrame, parent, members);
super(enclosingFrame, Objects.requireNonNull(parent), members, null, null, null);
} }
public VmMapping( public VmMapping(
MaterializedFrame enclosingFrame, MaterializedFrame enclosingFrame,
VmObject parent, VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members, UnmodifiableEconomicMap<Object, ObjectMember> members,
VmMapping delegate, ListingOrMappingTypeCastNode typeCastNode,
ListingOrMappingTypeCastNode typeCheckNode, Object typeCheckReceiver,
MaterializedFrame typeNodeFrame) { VmObjectLike typeCheckOwner) {
super( super(enclosingFrame, parent, members, typeCastNode, typeCheckReceiver, typeCheckOwner);
enclosingFrame,
Objects.requireNonNull(parent),
members,
delegate,
typeCheckNode,
typeNodeFrame);
} }
public static boolean isDefaultProperty(Object propertyKey) { public static boolean isDefaultProperty(Object propertyKey) {
@@ -81,16 +73,12 @@ public final class VmMapping extends VmListingOrMapping<VmMapping> {
@TruffleBoundary @TruffleBoundary
public VmSet getAllKeys() { public VmSet getAllKeys() {
if (delegate != null) {
return delegate.getAllKeys();
}
synchronized (this) { synchronized (this) {
if (__allKeys == null) { if (__allKeys == null) {
// building upon parent's `getAllKeys()` should improve at least worst case efficiency // building upon parent's `getAllKeys()` should improve at least worst case efficiency
var parentKeys = var parentKeys = parent instanceof VmMapping mapping ? mapping.getAllKeys() : VmSet.EMPTY;
getParent() instanceof VmMapping mapping ? mapping.getAllKeys() : VmSet.EMPTY;
var builder = VmSet.builder(parentKeys); var builder = VmSet.builder(parentKeys);
for (var cursor = getMembers().getEntries(); cursor.advance(); ) { for (var cursor = members.getEntries(); cursor.advance(); ) {
var member = cursor.getValue(); var member = cursor.getValue();
if (!member.isEntry()) continue; if (!member.isEntry()) continue;
builder.add(cursor.getKey()); builder.add(cursor.getKey());
@@ -133,12 +121,17 @@ public final class VmMapping extends VmListingOrMapping<VmMapping> {
if (this == obj) return true; if (this == obj) return true;
if (!(obj instanceof VmMapping other)) return false; if (!(obj instanceof VmMapping other)) return false;
if (getEntryCount() != other.getEntryCount()) return false;
// could use shallow force, but deep force is cached // could use shallow force, but deep force is cached
force(false); force(false);
other.force(false); other.force(false);
for (var key : getAllKeys()) { if (getEntryCount() != other.getEntryCount()) return false;
var value = getCachedValue(key);
var cursor = cachedValues.getEntries();
while (cursor.advance()) {
Object key = cursor.getKey();
if (key instanceof Identifier) continue;
var value = cursor.getValue();
assert value != null; assert value != null;
var otherValue = other.getCachedValue(key); var otherValue = other.getCachedValue(key);
if (!value.equals(otherValue)) return false; if (!value.equals(otherValue)) return false;
@@ -151,38 +144,34 @@ public final class VmMapping extends VmListingOrMapping<VmMapping> {
@TruffleBoundary @TruffleBoundary
public int hashCode() { public int hashCode() {
if (cachedHash != 0) return cachedHash; if (cachedHash != 0) return cachedHash;
// It's possible that the delegate has already computed its hash code.
// If so, we can go ahead and use it.
if (delegate != null && delegate.cachedHash != 0) return delegate.cachedHash;
force(false); force(false);
var result = 0; var result = 0;
for (var key : getAllKeys()) { var cursor = cachedValues.getEntries();
while (cursor.advance()) {
var key = cursor.getKey();
if (key instanceof Identifier) continue; if (key instanceof Identifier) continue;
var value = getCachedValue(key);
var value = cursor.getValue();
assert value != null; assert value != null;
result += key.hashCode() ^ value.hashCode(); result += key.hashCode() ^ value.hashCode();
} }
cachedHash = result; cachedHash = result;
return result; return result;
} }
// assumes mapping has been forced
public int getEntryCount() { public int getEntryCount() {
if (cachedEntryCount != -1) return cachedEntryCount; if (cachedEntryCount != -1) return cachedEntryCount;
cachedEntryCount = getAllKeys().getLength();
return cachedEntryCount;
}
@Override var result = 0;
@TruffleBoundary for (var key : cachedValues.getKeys()) {
public VmMapping withCheckedMembers( if (key instanceof Identifier) continue;
ListingOrMappingTypeCastNode typeCheckNode, MaterializedFrame typeNodeFrame) { result += 1;
return new VmMapping( }
getEnclosingFrame(), cachedEntryCount = result;
Objects.requireNonNull(getParent()), return result;
getMembers(),
this,
typeCheckNode,
typeNodeFrame);
} }
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -87,12 +87,12 @@ public abstract class VmObject extends VmObjectLike {
} }
@Override @Override
public void setCachedValue(Object key, Object value, ObjectMember objectMember) { public final void setCachedValue(Object key, Object value) {
EconomicMaps.put(cachedValues, key, value); EconomicMaps.put(cachedValues, key, value);
} }
@Override @Override
public boolean hasCachedValue(Object key) { public final boolean hasCachedValue(Object key) {
return EconomicMaps.containsKey(cachedValues, key); return EconomicMaps.containsKey(cachedValues, key);
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -96,7 +96,7 @@ public abstract class VmObjectLike extends VmValue {
* receiver. * receiver.
*/ */
@TruffleBoundary @TruffleBoundary
public abstract void setCachedValue(Object key, Object value, ObjectMember objectMember); public abstract void setCachedValue(Object key, Object value);
/** /**
* Prefer this method over {@link #getCachedValue} if the value is not required. (There is no * Prefer this method over {@link #getCachedValue} if the value is not required. (There is no
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/ */
package org.pkl.core.runtime; package org.pkl.core.runtime;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.Truffle;
@@ -134,10 +135,6 @@ public final class VmUtils {
return result; return result;
} }
public static void setReceiver(Frame frame, Object receiver) {
frame.getArguments()[0] = receiver;
}
public static VmObjectLike getObjectReceiver(Frame frame) { public static VmObjectLike getObjectReceiver(Frame frame) {
return (VmObjectLike) getReceiver(frame); return (VmObjectLike) getReceiver(frame);
} }
@@ -158,10 +155,6 @@ public final class VmUtils {
return result; return result;
} }
public static void setOwner(Frame frame, VmObjectLike owner) {
frame.getArguments()[1] = owner;
}
/** Returns a `ObjectMember`'s key while executing the corresponding `MemberNode`. */ /** Returns a `ObjectMember`'s key while executing the corresponding `MemberNode`. */
public static Object getMemberKey(Frame frame) { public static Object getMemberKey(Frame frame) {
return frame.getArguments()[2]; return frame.getArguments()[2];
@@ -261,17 +254,17 @@ public final class VmUtils {
final var constantValue = member.getConstantValue(); final var constantValue = member.getConstantValue();
if (constantValue != null) { if (constantValue != null) {
var ret = constantValue; var result = constantValue;
// for a property, do a type check // for a property, Listing element, or Mapping value, do a type check
if (member.isProp()) { if (member.isProp()) {
var property = receiver.getVmClass().getProperty(member.getName()); var property = receiver.getVmClass().getProperty(member.getName());
if (property != null && property.getTypeNode() != null) { if (property != null && property.getTypeNode() != null) {
var callTarget = property.getTypeNode().getCallTarget(); var callTarget = property.getTypeNode().getCallTarget();
try { try {
if (checkType) { if (checkType) {
ret = callNode.call(callTarget, receiver, property.getOwner(), constantValue); result = callNode.call(callTarget, receiver, property.getOwner(), constantValue);
} else { } else {
ret = result =
callNode.call( callNode.call(
callTarget, callTarget,
receiver, receiver,
@@ -281,44 +274,52 @@ public final class VmUtils {
} }
} catch (VmException e) { } catch (VmException e) {
CompilerDirectives.transferToInterpreter(); CompilerDirectives.transferToInterpreter();
// A failed property type check looks as follows in the stack trace: insertStackFrame(member, callTarget, e);
// x: Int(isPositive)
// at ...
// x = -10
// at ...
// However, if the value of `x` is a parse-time constant (as in `x = -10`),
// there's no root node for it and hence no stack trace element.
// In this case, insert a stack trace element to make the stack trace look the same
// as in the non-constant case. (Parse-time constants are an internal optimization.)
e.getInsertedStackFrames()
.put(
callTarget,
createStackFrame(member.getBodySection(), member.getQualifiedName()));
throw e; throw e;
} }
} }
} else if (receiver instanceof VmListingOrMapping<?> vmListingOrMapping) { } else if (receiver instanceof VmListingOrMapping listingOrMapping
if (owner != receiver && owner instanceof VmListingOrMapping<?> vmListingOrMappingOwner) { && owner instanceof VmListingOrMapping) {
ret = vmListingOrMappingOwner.typecastObjectMember(member, ret, callNode); // `owner instanceof VmListingOrMapping` guards against
} // PropertiesRenderer amending VmDynamic with VmListing (hack?)
ret = vmListingOrMapping.typecastObjectMember(member, ret, callNode); result = listingOrMapping.executeTypeCasts(constantValue, owner, callNode, member, null);
} }
receiver.setCachedValue(memberKey, ret, member);
return ret; receiver.setCachedValue(memberKey, result);
return result;
} }
var callTarget = member.getCallTarget(); var callTarget = member.getCallTarget();
Object ret; Object result;
if (checkType) { if (checkType) {
ret = callNode.call(callTarget, receiver, owner, memberKey); result = callNode.call(callTarget, receiver, owner, memberKey);
} else { } else {
ret = callNode.call(callTarget, receiver, owner, memberKey, VmUtils.SKIP_TYPECHECK_MARKER); result = callNode.call(callTarget, receiver, owner, memberKey, VmUtils.SKIP_TYPECHECK_MARKER);
} }
if (receiver instanceof VmListingOrMapping<?> vmListingOrMapping) { receiver.setCachedValue(memberKey, result);
ret = vmListingOrMapping.typecastObjectMember(member, ret, callNode); return result;
}
// A failed property type check looks as follows in the stack trace:
// x: Int(isPositive)
// at ...
// x = -10
// at ...
// However, if the value of `x` is a parse-time constant (as in `x = -10`),
// there's no root node for it and hence no stack trace element.
// In this case, insert a stack trace element to make the stack trace look the same
// as in the non-constant case. (Parse-time constants are an internal optimization.)
public static void insertStackFrame(
ObjectMember member, CallTarget location, VmException exception) {
var sourceSection = member.getBodySection();
if (!sourceSection.isAvailable()) {
sourceSection = member.getSourceSection();
}
if (sourceSection.isAvailable()) {
exception
.getInsertedStackFrames()
.put(location, createStackFrame(sourceSection, member.getQualifiedName()));
} }
receiver.setCachedValue(memberKey, ret, member);
return ret;
} }
public static @Nullable ObjectMember findMember(VmObjectLike receiver, Object memberKey) { public static @Nullable ObjectMember findMember(VmObjectLike receiver, Object memberKey) {
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@ import org.pkl.core.runtime.VmDuration;
import org.pkl.core.runtime.VmDynamic; import org.pkl.core.runtime.VmDynamic;
import org.pkl.core.runtime.VmExceptionBuilder; import org.pkl.core.runtime.VmExceptionBuilder;
import org.pkl.core.runtime.VmIntSeq; import org.pkl.core.runtime.VmIntSeq;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmList; import org.pkl.core.runtime.VmList;
import org.pkl.core.runtime.VmListing; import org.pkl.core.runtime.VmListing;
import org.pkl.core.runtime.VmMap; import org.pkl.core.runtime.VmMap;
@@ -573,7 +574,8 @@ public final class RendererNodes {
type = type =
requiresWrapper() requiresWrapper()
? null ? null
: new ListingTypeNode(VmUtils.unavailableSourceSection(), valueType); : new ListingTypeNode(
VmUtils.unavailableSourceSection(), VmLanguage.get(null), valueType);
return type; return type;
} else if (type instanceof MappingTypeNode mappingType) { } else if (type instanceof MappingTypeNode mappingType) {
var keyType = resolveType(mappingType.getKeyTypeNode()); var keyType = resolveType(mappingType.getKeyTypeNode());
@@ -587,7 +589,9 @@ public final class RendererNodes {
} }
var valueType = resolveType(mappingType.getValueTypeNode()); var valueType = resolveType(mappingType.getValueTypeNode());
assert valueType != null : "Incomplete or malformed Mapping type"; assert valueType != null : "Incomplete or malformed Mapping type";
mappingType = new MappingTypeNode(VmUtils.unavailableSourceSection(), keyType, valueType); mappingType =
new MappingTypeNode(
VmUtils.unavailableSourceSection(), VmLanguage.get(null), keyType, valueType);
type = requiresWrapper() ? null : mappingType; type = requiresWrapper() ? null : mappingType;
return type; return type;
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.pkl.core.stdlib.test.report;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.pkl.core.TestResults; import org.pkl.core.TestResults;
import org.pkl.core.TestResults.TestResult; import org.pkl.core.TestResults.TestResult;
@@ -144,7 +145,10 @@ public final class SimpleReport implements TestReport {
sb.append( sb.append(
color, color,
() -> () ->
sb.append(String.format("%.1f%%", passRate)).append(" ").append(kind).append(" pass")); sb.append(String.format(Locale.ROOT, "%.1f%%", passRate))
.append(" ")
.append(kind)
.append(" pass"));
if (isFailed) { if (isFailed) {
sb.append(" [").append(failed).append('/').append(total).append(" failed]"); sb.append(" [").append(failed).append('/').append(total).append(" failed]");
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -30,6 +30,10 @@ import org.graalvm.collections.UnmodifiableMapCursor;
public final class EconomicMaps { public final class EconomicMaps {
private EconomicMaps() {} private EconomicMaps() {}
public static <K, V> UnmodifiableEconomicMap<K, V> emptyMap() {
return EconomicMap.emptyMap();
}
@TruffleBoundary @TruffleBoundary
public static <K, V> EconomicMap<K, V> create() { public static <K, V> EconomicMap<K, V> create() {
return EconomicMap.create(); return EconomicMap.create();
@@ -0,0 +1,3 @@
function isValid(str): Boolean = str.startsWith("a")
foo: Listing<String(isValid(this))>(isDistinct)
@@ -27,12 +27,20 @@ local altered: Listing<Person> = (base) {
[0] { name = "Wood Pigeon" } [0] { name = "Wood Pigeon" }
} }
local computedIndex: Listing<Person> = (base) {
[computeIndex()] { name = "Wood Pigeon" }
}
local function computeIndex() = 0
facts { facts {
["isEmpty"] { ["isEmpty"] {
empty.isEmpty empty.isEmpty
empty2.isEmpty empty2.isEmpty
!base.isEmpty !base.isEmpty
!derived.isEmpty !derived.isEmpty
!altered.isEmpty
!computedIndex.isEmpty
} }
["lastIndex"] { ["lastIndex"] {
@@ -41,6 +49,8 @@ facts {
base.lastIndex == 2 base.lastIndex == 2
derived.lastIndex == 4 derived.lastIndex == 4
duplicate.lastIndex == 5 duplicate.lastIndex == 5
altered.lastIndex == 2
computedIndex.lastIndex == 2
} }
["isDistinct"] { ["isDistinct"] {
@@ -49,6 +59,8 @@ facts {
base.isDistinct base.isDistinct
derived.isDistinct derived.isDistinct
!duplicate.isDistinct !duplicate.isDistinct
altered.isDistinct
computedIndex.isDistinct
} }
["isDistinctBy()"] { ["isDistinctBy()"] {
@@ -57,18 +69,24 @@ facts {
base.isDistinctBy((it) -> it) base.isDistinctBy((it) -> it)
derived.isDistinctBy((it) -> it) derived.isDistinctBy((it) -> it)
!duplicate.isDistinctBy((it) -> it) !duplicate.isDistinctBy((it) -> it)
altered.isDistinctBy((it) -> it)
computedIndex.isDistinctBy((it) -> it)
empty.isDistinctBy((it) -> it.name) empty.isDistinctBy((it) -> it.name)
empty2.isDistinctBy((it) -> it.name) empty2.isDistinctBy((it) -> it.name)
base.isDistinctBy((it) -> it.name) base.isDistinctBy((it) -> it.name)
derived.isDistinctBy((it) -> it.name) derived.isDistinctBy((it) -> it.name)
!duplicate.isDistinctBy((it) -> it.name) !duplicate.isDistinctBy((it) -> it.name)
altered.isDistinctBy((it) -> it.name)
computedIndex.isDistinctBy((it) -> it.name)
empty.isDistinctBy((it) -> it.getClass()) empty.isDistinctBy((it) -> it.getClass())
empty2.isDistinctBy((it) -> it.getClass()) empty2.isDistinctBy((it) -> it.getClass())
!base.isDistinctBy((it) -> it.getClass()) !base.isDistinctBy((it) -> it.getClass())
!derived.isDistinctBy((it) -> it.getClass()) !derived.isDistinctBy((it) -> it.getClass())
!duplicate.isDistinctBy((it) -> it.getClass()) !duplicate.isDistinctBy((it) -> it.getClass())
!altered.isDistinctBy((it) -> it.getClass())
!computedIndex.isDistinctBy((it) -> it.getClass())
} }
["getOrNull"] { ["getOrNull"] {
@@ -85,24 +103,32 @@ facts {
module.catch(() -> empty.first) == "Expected a non-empty Listing." module.catch(() -> empty.first) == "Expected a non-empty Listing."
base.first == base[0] base.first == base[0]
derived.first == base[0] derived.first == base[0]
altered.first != base[0]
computedIndex.first == altered.first
} }
["firstOrNull"] { ["firstOrNull"] {
empty.firstOrNull == null empty.firstOrNull == null
base.firstOrNull == base[0] base.firstOrNull == base[0]
derived.firstOrNull == base[0] derived.firstOrNull == base[0]
altered.firstOrNull != base[0]
computedIndex.firstOrNull == altered.first
} }
["last"] { ["last"] {
module.catch(() -> empty.last) == "Expected a non-empty Listing." module.catch(() -> empty.last) == "Expected a non-empty Listing."
base.last == base[2] base.last == base[2]
derived.last == derived[4] derived.last == derived[4]
altered.last == base[2]
computedIndex.last == base[2]
} }
["lastOrNull"] { ["lastOrNull"] {
empty.lastOrNull == null empty.lastOrNull == null
base.lastOrNull == base[2] base.lastOrNull == base[2]
derived.lastOrNull == derived[4] derived.lastOrNull == derived[4]
altered.lastOrNull == base[2]
computedIndex.lastOrNull == base[2]
} }
["single"] { ["single"] {
@@ -135,6 +161,7 @@ facts {
derived.contains(base[1]) derived.contains(base[1])
derived.contains(derived[3]) derived.contains(derived[3])
!altered.contains(base[0]) !altered.contains(base[0])
!computedIndex.contains(base[0])
} }
} }
@@ -144,6 +171,17 @@ examples {
empty2.length empty2.length
base.length base.length
derived.length derived.length
altered.length
computedIndex.length
local elementsAndEntries = (base) {
new { name = "" }
[2] { name = "" }
[computeIndex()] { name = "" }
new { name = "" }
[1 + 0] { name = "" }
}
elementsAndEntries.length
} }
["toList()"] { ["toList()"] {
@@ -152,6 +190,8 @@ examples {
base.toList() base.toList()
derived.toList() derived.toList()
duplicate.toList() duplicate.toList()
altered.toList()
computedIndex.toList()
} }
["toSet()"] { ["toSet()"] {
@@ -160,6 +200,8 @@ examples {
base.toSet() base.toSet()
derived.toSet() derived.toSet()
duplicate.toSet() duplicate.toSet()
altered.toSet()
computedIndex.toSet()
} }
["distinct"] { ["distinct"] {
@@ -168,6 +210,8 @@ examples {
base.distinct base.distinct
derived.distinct derived.distinct
duplicate.distinct duplicate.distinct
altered.distinct
computedIndex.distinct
} }
["distinctBy()"] { ["distinctBy()"] {
@@ -176,36 +220,48 @@ examples {
base.distinctBy((it) -> it) base.distinctBy((it) -> it)
derived.distinctBy((it) -> it) derived.distinctBy((it) -> it)
duplicate.distinctBy((it) -> it) duplicate.distinctBy((it) -> it)
altered.distinctBy((it) -> it)
computedIndex.distinctBy((it) -> it)
empty.distinctBy((it) -> it.name) empty.distinctBy((it) -> it.name)
empty2.distinctBy((it) -> it.name) empty2.distinctBy((it) -> it.name)
base.distinctBy((it) -> it.name) base.distinctBy((it) -> it.name)
derived.distinctBy((it) -> it.name) derived.distinctBy((it) -> it.name)
duplicate.distinctBy((it) -> it.name) duplicate.distinctBy((it) -> it.name)
altered.distinctBy((it) -> it.name)
computedIndex.distinctBy((it) -> it.name)
empty.distinctBy((it) -> it.getClass()) empty.distinctBy((it) -> it.getClass())
empty2.distinctBy((it) -> it.getClass()) empty2.distinctBy((it) -> it.getClass())
base.distinctBy((it) -> it.getClass()) base.distinctBy((it) -> it.getClass())
derived.distinctBy((it) -> it.getClass()) derived.distinctBy((it) -> it.getClass())
duplicate.distinctBy((it) -> it.getClass()) duplicate.distinctBy((it) -> it.getClass())
altered.distinctBy((it) -> it.getClass())
computedIndex.distinctBy((it) -> it.getClass())
} }
["fold"] { ["fold"] {
empty.fold(List(), (l, e) -> l.add(e)) empty.fold(List(), (l, e) -> l.add(e))
base.fold(List(), (l, e) -> l.add(e)) base.fold(List(), (l, e) -> l.add(e))
derived.fold(List(), (l, e) -> l.add(e)) derived.fold(List(), (l, e) -> l.add(e))
altered.fold(List(), (l, e) -> l.add(e))
computedIndex.fold(List(), (l, e) -> l.add(e))
} }
["foldIndexed"] { ["foldIndexed"] {
empty.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e))) empty.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e)))
base.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e))) base.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e)))
derived.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e))) derived.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e)))
altered.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e)))
computedIndex.foldIndexed(List(), (i, l, e) -> l.add(Pair(i, e)))
} }
local baseNum = new Listing { 1; 2; 3 } local baseNum = new Listing { 1; 2; 3 }
local baseString = new Listing { "Pigeon"; "Barn Owl"; "Parrot" } local baseString = new Listing { "Pigeon"; "Barn Owl"; "Parrot" }
local derivedString = (baseString) { "Albatross"; "Elf Owl" } local derivedString = (baseString) { "Albatross"; "Elf Owl" }
local alteredString = (baseString) { [0] = "Wood Pigeon" }
local computedIndexString = (baseString) { [computeIndex()] = "Wood Pigeon" }
["join"] { ["join"] {
empty.join("") empty.join("")
@@ -215,5 +271,9 @@ examples {
baseString.join("---") baseString.join("---")
derivedString.join("") derivedString.join("")
derivedString.join("\n") derivedString.join("\n")
alteredString.join("")
alteredString.join("\n")
computedIndexString.join("")
computedIndexString.join("\n")
} }
} }
@@ -0,0 +1,6 @@
local a = new Listing { new Listing { 0 } }
local b = a as Listing<Listing<String>>
local c = (b) { new Listing { 1 } }
local d = c as Listing<Listing<Int>>
result = d
@@ -0,0 +1,10 @@
local a = new Listing { new Foo {} }
local b = (a) { new Bar {} }
local c = b as Listing<Bar>
local d = (c) { new Foo {} }
local e = d as Listing<Foo>
result = e
open class Foo
class Bar extends Foo
@@ -0,0 +1,10 @@
local a = new Mapping { [0] = new Foo {} }
local b = (a) { [1] = new Bar {} }
local c = b as Mapping<Int, Bar>
local d = (c) { [2] = new Foo {} }
local e = d as Mapping<Int, Foo>
result = e
open class Foo
class Bar extends Foo
@@ -0,0 +1,7 @@
foo1: Listing<String> = new { "hello" }
foo2: Listing<String|Int> = foo1
res1 = foo1.isDistinct
// steals isDistinct from foo1's VmListing.cachedValues but must not
// perform a String|Int type check because isDistinct is not an element
res2 = foo2.isDistinct
@@ -0,0 +1,8 @@
amends "../../input-helper/listings/cacheStealingTypeCheck.pkl"
// this test covers a regression where the wrong receiver
// and owner was used to typecheck a stolen value
foo {
"abcdx"
"ax"
}
@@ -0,0 +1,5 @@
const local lastName = "Birdo"
typealias Birds = Listing<String(endsWith(lastName))>
typealias Birds2 = Pair<Listing<String(endsWith(lastName))>, Int>
@@ -1,5 +1,7 @@
import "pkl:test" import "pkl:test"
import "helpers/originalTypealias.pkl"
typealias Simple = String typealias Simple = String
const function simple(arg: Simple): Simple = arg const function simple(arg: Simple): Simple = arg
@@ -105,3 +107,8 @@ res19: LocalTypeAlias = "abc"
typealias VeryComposite = Pair<Composite, Composite> typealias VeryComposite = Pair<Composite, Composite>
res20: VeryComposite = Pair(Map("abc", List("def")), Map("abc", List("def"))) res20: VeryComposite = Pair(Map("abc", List("def")), Map("abc", List("def")))
// typealiases should be executed in their original context
res21: originalTypealias.Birds = new { "John Birdo" }
res22: originalTypealias.Birds2 = Pair(new Listing { "John Birdo" }, 0)
@@ -0,0 +1,3 @@
import "helpers/originalTypealias.pkl"
res: originalTypealias.Birds = new { "Jimmy Bird" }
@@ -4,6 +4,8 @@ facts {
true true
true true
true true
true
true
} }
["lastIndex"] { ["lastIndex"] {
true true
@@ -11,6 +13,8 @@ facts {
true true
true true
true true
true
true
} }
["isDistinct"] { ["isDistinct"] {
true true
@@ -18,6 +22,8 @@ facts {
true true
true true
true true
true
true
} }
["isDistinctBy()"] { ["isDistinctBy()"] {
true true
@@ -35,6 +41,12 @@ facts {
true true
true true
true true
true
true
true
true
true
true
} }
["getOrNull"] { ["getOrNull"] {
true true
@@ -49,21 +61,29 @@ facts {
true true
true true
true true
true
true
} }
["firstOrNull"] { ["firstOrNull"] {
true true
true true
true true
true
true
} }
["last"] { ["last"] {
true true
true true
true true
true
true
} }
["lastOrNull"] { ["lastOrNull"] {
true true
true true
true true
true
true
} }
["single"] { ["single"] {
true true
@@ -91,6 +111,7 @@ facts {
true true
true true
true true
true
} }
} }
examples { examples {
@@ -99,6 +120,9 @@ examples {
0 0
3 3
5 5
3
3
5
} }
["toList()"] { ["toList()"] {
List() List()
@@ -134,6 +158,20 @@ examples {
}, new { }, new {
name = "Elf Owl" name = "Elf Owl"
}) })
List(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
List(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
} }
["toSet()"] { ["toSet()"] {
Set() Set()
@@ -167,6 +205,20 @@ examples {
}, new { }, new {
name = "Elf Owl" name = "Elf Owl"
}) })
Set(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
Set(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
} }
["distinct"] { ["distinct"] {
new {} new {}
@@ -216,6 +268,28 @@ examples {
name = "Elf Owl" name = "Elf Owl"
} }
} }
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
} }
["distinctBy()"] { ["distinctBy()"] {
new {} new {}
@@ -265,6 +339,28 @@ examples {
name = "Elf Owl" name = "Elf Owl"
} }
} }
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
new {} new {}
new {} new {}
new { new {
@@ -312,6 +408,28 @@ examples {
name = "Elf Owl" name = "Elf Owl"
} }
} }
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
new {
new {
name = "Wood Pigeon"
}
new {
name = "Barn Owl"
}
new {
name = "Parrot"
}
}
new {} new {}
new {} new {}
new { new {
@@ -329,6 +447,16 @@ examples {
name = "Pigeon" name = "Pigeon"
} }
} }
new {
new {
name = "Wood Pigeon"
}
}
new {
new {
name = "Wood Pigeon"
}
}
} }
["fold"] { ["fold"] {
List() List()
@@ -350,6 +478,20 @@ examples {
}, new { }, new {
name = "Elf Owl" name = "Elf Owl"
}) })
List(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
List(new {
name = "Wood Pigeon"
}, new {
name = "Barn Owl"
}, new {
name = "Parrot"
})
} }
["foldIndexed"] { ["foldIndexed"] {
List() List()
@@ -371,6 +513,20 @@ examples {
}), Pair(4, new { }), Pair(4, new {
name = "Elf Owl" name = "Elf Owl"
})) }))
List(Pair(0, new {
name = "Wood Pigeon"
}), Pair(1, new {
name = "Barn Owl"
}), Pair(2, new {
name = "Parrot"
}))
List(Pair(0, new {
name = "Wood Pigeon"
}), Pair(1, new {
name = "Barn Owl"
}), Pair(2, new {
name = "Parrot"
}))
} }
["join"] { ["join"] {
"" ""
@@ -386,5 +542,17 @@ examples {
Albatross Albatross
Elf Owl Elf Owl
""" """
"Wood PigeonBarn OwlParrot"
"""
Wood Pigeon
Barn Owl
Parrot
"""
"Wood PigeonBarn OwlParrot"
"""
Wood Pigeon
Barn Owl
Parrot
"""
} }
} }
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `String`, but got type `Int`.
Value: 0
x | local b = a as Listing<Listing<String>>
^^^^^^
at listingTypeCheckError8#b (file:///$snippetsDir/input/errors/listingTypeCheckError8.pkl)
x | local a = new Listing { new Listing { 0 } }
^
at listingTypeCheckError8#a[#1][#1] (file:///$snippetsDir/input/errors/listingTypeCheckError8.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `listingTypeCheckError9#Bar`, but got type `listingTypeCheckError9#Foo`.
Value: new Foo {}
x | local c = b as Listing<Bar>
^^^
at listingTypeCheckError9#c (file:///$snippetsDir/input/errors/listingTypeCheckError9.pkl)
x | local a = new Listing { new Foo {} }
^^^^^^^^^^
at listingTypeCheckError9#a[#1] (file:///$snippetsDir/input/errors/listingTypeCheckError9.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `mappingTypeCheckError11#Bar`, but got type `mappingTypeCheckError11#Foo`.
Value: new Foo {}
x | local c = b as Mapping<Int, Bar>
^^^
at mappingTypeCheckError11#c (file:///$snippetsDir/input/errors/mappingTypeCheckError11.pkl)
x | local a = new Mapping { [0] = new Foo {} }
^^^^^^^^^^
at mappingTypeCheckError11#a[0] (file:///$snippetsDir/input/errors/mappingTypeCheckError11.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -0,0 +1,8 @@
foo1 {
"hello"
}
foo2 {
"hello"
}
res1 = true
res2 = true
@@ -0,0 +1,4 @@
foo {
"abcdx"
"ax"
}
@@ -38,3 +38,9 @@ res18 = "Expected value of type `Duration`, but got type `DataSize`. Value: 5.mb
res18b = "Expected value of type `Duration`, but got type `DataSize`. Value: 5.mb" res18b = "Expected value of type `Duration`, but got type `DataSize`. Value: 5.mb"
res19 = "abc" res19 = "abc"
res20 = Pair(Map("abc", List("def")), Map("abc", List("def"))) res20 = Pair(Map("abc", List("def")), Map("abc", List("def")))
res21 {
"John Birdo"
}
res22 = Pair(new {
"John Birdo"
}, 0)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Type constraint `endsWith(lastName)` violated.
Value: "Jimmy Bird"
x | typealias Birds = Listing<String(endsWith(lastName))>
^^^^^^^^^^^^^^^^^^
at typeAliasContext#res (file:///$snippetsDir/input/types/helpers/originalTypealias.pkl)
x | res: originalTypealias.Birds = new { "Jimmy Bird" }
^^^^^^^^^^^^
at typeAliasContext#res[#1] (file:///$snippetsDir/input/types/typeAliasContext.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -117,7 +117,7 @@ class DocGenerator(
} }
private fun DocPackage.deletePackageDir() { private fun DocPackage.deletePackageDir() {
outputDir.resolve("$name/$version").deleteRecursively() outputDir.resolve(IoUtils.encodePath("$name/$version")).deleteRecursively()
} }
private fun createSymlinks(currentPackagesData: List<PackageData>) { private fun createSymlinks(currentPackagesData: List<PackageData>) {
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.pkl.gradle;
import java.io.File; import java.io.File;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -27,6 +28,7 @@ import org.gradle.api.GradleException;
import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Plugin; import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.Transformer;
import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.plugins.Convention; import org.gradle.api.plugins.Convention;
import org.gradle.api.provider.Provider; import org.gradle.api.provider.Provider;
@@ -64,6 +66,7 @@ import org.pkl.gradle.task.PkldocTask;
import org.pkl.gradle.task.ProjectPackageTask; import org.pkl.gradle.task.ProjectPackageTask;
import org.pkl.gradle.task.ProjectResolveTask; import org.pkl.gradle.task.ProjectResolveTask;
import org.pkl.gradle.task.TestTask; import org.pkl.gradle.task.TestTask;
import org.pkl.gradle.utils.PluginUtils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class PklPlugin implements Plugin<Project> { public class PklPlugin implements Plugin<Project> {
@@ -456,6 +459,9 @@ public class PklPlugin implements Plugin<Project> {
private List<File> getTransitiveModules(AnalyzeImportsTask analyzeTask) { private List<File> getTransitiveModules(AnalyzeImportsTask analyzeTask) {
var outputFile = analyzeTask.getOutputFile().get().getAsFile().toPath(); var outputFile = analyzeTask.getOutputFile().get().getAsFile().toPath();
if (!analyzeTask.getOnlyIf().isSatisfiedBy(analyzeTask)) {
return Collections.emptyList();
}
try { try {
var contents = Files.readString(outputFile); var contents = Files.readString(outputFile);
ImportGraph importGraph = ImportGraph.parseFromJson(contents); ImportGraph importGraph = ImportGraph.parseFromJson(contents);
@@ -470,9 +476,16 @@ public class PklPlugin implements Plugin<Project> {
} }
private <T extends ModulesTask, S extends ModulesSpec> void configureModulesTask( private <T extends ModulesTask, S extends ModulesSpec> void configureModulesTask(
T task, S spec, @Nullable TaskProvider<AnalyzeImportsTask> analyzeImportsTask) { T task,
S spec,
@Nullable TaskProvider<AnalyzeImportsTask> analyzeImportsTask,
@Nullable Transformer<List<?>, List<?>> mapSourceModules) {
configureBaseTask(task, spec); configureBaseTask(task, spec);
task.getSourceModules().set(spec.getSourceModules()); if (mapSourceModules != null) {
task.getSourceModules().set(spec.getSourceModules().map(mapSourceModules));
} else {
task.getSourceModules().set(spec.getSourceModules());
}
task.getNoProject().set(spec.getNoProject()); task.getNoProject().set(spec.getNoProject());
task.getProjectDir().set(spec.getProjectDir()); task.getProjectDir().set(spec.getProjectDir());
task.getOmitProjectSettings().set(spec.getOmitProjectSettings()); task.getOmitProjectSettings().set(spec.getOmitProjectSettings());
@@ -484,6 +497,11 @@ public class PklPlugin implements Plugin<Project> {
} }
} }
private <T extends ModulesTask, S extends ModulesSpec> void configureModulesTask(
T task, S spec, @Nullable TaskProvider<AnalyzeImportsTask> analyzeImportsTask) {
configureModulesTask(task, spec, analyzeImportsTask, null);
}
private TaskProvider<AnalyzeImportsTask> createAnalyzeImportsTask(ModulesSpec spec) { private TaskProvider<AnalyzeImportsTask> createAnalyzeImportsTask(ModulesSpec spec) {
var outputFile = var outputFile =
project project
@@ -496,11 +514,26 @@ public class PklPlugin implements Plugin<Project> {
spec.getName() + "GatherImports", spec.getName() + "GatherImports",
AnalyzeImportsTask.class, AnalyzeImportsTask.class,
task -> { task -> {
configureModulesTask(task, spec, null); configureModulesTask(
task,
spec,
null,
(modules) ->
// only need to analyze imports of file-based modules; it's unlikely that a
// non-file-based module will import a file-based module due to security
// manager trust levels (see
// org.pkl.core.SecurityManagers.getDefaultTrustLevel).
modules.stream()
.map(PluginUtils::parseModuleNotationToUri)
.filter(
(it) ->
it.getScheme() == null || it.getScheme().equalsIgnoreCase("file"))
.toList());
task.setDescription("Compute the set of imports declared by input modules"); task.setDescription("Compute the set of imports declared by input modules");
task.setGroup("build"); task.setGroup("build");
task.getOutputFormat().set(OutputFormat.JSON.toString()); task.getOutputFormat().set(OutputFormat.JSON.toString());
task.getOutputFile().set(outputFile); task.getOutputFile().set(outputFile);
task.onlyIf(ignored -> !task.getSourceModules().get().isEmpty());
}); });
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,9 +17,6 @@ package org.pkl.gradle.task;
import java.io.File; import java.io.File;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.Duration; import java.time.Duration;
@@ -31,10 +28,8 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import org.gradle.api.DefaultTask; import org.gradle.api.DefaultTask;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.model.ObjectFactory; import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.MapProperty;
@@ -49,9 +44,9 @@ import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskAction;
import org.pkl.commons.cli.CliBaseOptions; import org.pkl.commons.cli.CliBaseOptions;
import org.pkl.core.evaluatorSettings.Color; import org.pkl.core.evaluatorSettings.Color;
import org.pkl.core.util.IoUtils;
import org.pkl.core.util.LateInit; import org.pkl.core.util.LateInit;
import org.pkl.core.util.Nullable; import org.pkl.core.util.Nullable;
import org.pkl.gradle.utils.PluginUtils;
public abstract class BasePklTask extends DefaultTask { public abstract class BasePklTask extends DefaultTask {
@Input @Input
@@ -74,7 +69,7 @@ public abstract class BasePklTask extends DefaultTask {
@Internal @Internal
public Provider<Object> getParsedSettingsModule() { public Provider<Object> getParsedSettingsModule() {
return getSettingsModule().map(this::parseModuleNotation); return getSettingsModule().map(PluginUtils::parseModuleNotation);
} }
@InputFile @InputFile
@@ -165,7 +160,7 @@ public abstract class BasePklTask extends DefaultTask {
parseModulePath(), parseModulePath(),
getProject().getProjectDir().toPath(), getProject().getProjectDir().toPath(),
mapAndGetOrNull(getEvalRootDirPath(), Paths::get), mapAndGetOrNull(getEvalRootDirPath(), Paths::get),
mapAndGetOrNull(getSettingsModule(), this::parseModuleNotationToUri), mapAndGetOrNull(getSettingsModule(), PluginUtils::parseModuleNotationToUri),
null, null,
getEvalTimeout().getOrNull(), getEvalTimeout().getOrNull(),
mapAndGetOrNull(getModuleCacheDir(), it1 -> it1.getAsFile().toPath()), mapAndGetOrNull(getModuleCacheDir(), it1 -> it1.getAsFile().toPath()),
@@ -199,100 +194,6 @@ public abstract class BasePklTask extends DefaultTask {
return getModulePath().getFiles().stream().map(File::toPath).collect(Collectors.toList()); return getModulePath().getFiles().stream().map(File::toPath).collect(Collectors.toList());
} }
/**
* Parses the specified source module notation into a "parsed" notation which is then used for
* input path tracking and as an argument for the CLI API.
*
* <p>This method accepts the following input types:
*
* <ul>
* <li>{@link URI} - used as is.
* <li>{@link File} - used as is.
* <li>{@link Path} - converted to a {@link File}. This conversion may fail because not all
* {@link Path}s point to the local file system.
* <li>{@link URL} - converted to a {@link URI}. This conversion may fail because {@link URL}
* allows for URLs which are not compliant URIs.
* <li>{@link CharSequence} - first, converted to a string. If this string is "URI-like" (see
* {@link IoUtils#isUriLike(String)}), then we attempt to parse it as a {@link URI}, which
* may fail. Otherwise, we attempt to parse it as a {@link Path}, which is then converted to
* a {@link File} (both of these operations may fail).
* <li>{@link FileSystemLocation} - converted to a {@link File} via the {@link
* FileSystemLocation#getAsFile()} method.
* </ul>
*
* In case the returned value is determined to be a {@link URI}, then this URI is first checked
* for whether its scheme is {@code file}, like {@code file:///example/path}. In such case, this
* method returns a {@link File} corresponding to the file path in the URI. Otherwise, a {@link
* URI} instance is returned.
*
* @throws InvalidUserDataException In case the input is none of the types described above, or
* when the underlying value cannot be parsed correctly.
*/
protected Object parseModuleNotation(Object notation) {
if (notation instanceof URI uri) {
if ("file".equals(uri.getScheme())) {
return new File(uri.getPath());
}
return uri;
} else if (notation instanceof File) {
return notation;
} else if (notation instanceof Path path) {
try {
return path.toFile();
} catch (UnsupportedOperationException e) {
throw new InvalidUserDataException("Failed to parse Pkl module file path: " + notation, e);
}
} else if (notation instanceof URL url) {
try {
return parseModuleNotation(url.toURI());
} catch (URISyntaxException e) {
throw new InvalidUserDataException("Failed to parse Pkl module URI: " + notation, e);
}
} else if (notation instanceof CharSequence) {
var s = notation.toString();
if (IoUtils.isUriLike(s)) {
try {
return parseModuleNotation(IoUtils.toUri(s));
} catch (URISyntaxException e) {
throw new InvalidUserDataException("Failed to parse Pkl module URI: " + s, e);
}
} else {
try {
return Paths.get(s).toFile();
} catch (InvalidPathException | UnsupportedOperationException e) {
throw new InvalidUserDataException("Failed to parse Pkl module file path: " + s, e);
}
}
} else if (notation instanceof FileSystemLocation location) {
return location.getAsFile();
} else {
throw new InvalidUserDataException(
"Unsupported value of type "
+ notation.getClass()
+ " used as a module path: "
+ notation);
}
}
protected URI parseModuleNotationToUri(Object m) {
var parsed1 = parseModuleNotation(m);
return parsedModuleNotationToUri(parsed1);
}
/**
* Converts either a file or a URI to a URI. We convert a file to a URI via the {@link
* IoUtils#createUri(String)} because other ways of conversion can make relative paths into
* absolute URIs, which may break module loading.
*/
private URI parsedModuleNotationToUri(Object notation) {
if (notation instanceof File file) {
return IoUtils.createUri(file.getPath());
} else if (notation instanceof URI uri) {
return uri;
}
throw new IllegalArgumentException("Invalid parsed module notation: " + notation);
}
protected List<Pattern> patternsFromStrings(List<String> patterns) { protected List<Pattern> patternsFromStrings(List<String> patterns) {
return patterns.stream().map(Pattern::compile).collect(Collectors.toList()); return patterns.stream().map(Pattern::compile).collect(Collectors.toList());
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -37,8 +37,8 @@ import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskAction;
import org.pkl.commons.cli.CliBaseOptions; import org.pkl.commons.cli.CliBaseOptions;
import org.pkl.core.evaluatorSettings.Color; import org.pkl.core.evaluatorSettings.Color;
import org.pkl.core.util.IoUtils;
import org.pkl.core.util.Pair; import org.pkl.core.util.Pair;
import org.pkl.gradle.utils.PluginUtils;
public abstract class ModulesTask extends BasePklTask { public abstract class ModulesTask extends BasePklTask {
// We expose the contents of this property as task inputs via the sourceModuleFiles // We expose the contents of this property as task inputs via the sourceModuleFiles
@@ -84,7 +84,7 @@ public abstract class ModulesTask extends BasePklTask {
@Override @Override
protected List<URI> getSourceModulesAsUris() { protected List<URI> getSourceModulesAsUris() {
return getSourceModules().get().stream() return getSourceModules().get().stream()
.map(this::parseModuleNotationToUri) .map(PluginUtils::parseModuleNotationToUri)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@@ -117,7 +117,7 @@ public abstract class ModulesTask extends BasePklTask {
var files = new ArrayList<File>(); var files = new ArrayList<File>();
var uris = new ArrayList<URI>(); var uris = new ArrayList<URI>();
for (var m : modules) { for (var m : modules) {
var parsed = parseModuleNotation(m); var parsed = PluginUtils.parseModuleNotation(m);
if (parsed instanceof File file) { if (parsed instanceof File file) {
files.add(file); files.add(file);
} else if (parsed instanceof URI uri) { } else if (parsed instanceof URI uri) {
@@ -127,28 +127,6 @@ public abstract class ModulesTask extends BasePklTask {
return Pair.of(files, uris); return Pair.of(files, uris);
} }
/**
* Converts either a file or a URI to a URI. We convert a relative file to a URI via the {@link
* IoUtils#createUri(String)} because other ways of conversion can make relative paths into
* absolute URIs, which may break module loading.
*/
private URI parsedModuleNotationToUri(Object notation) {
if (notation instanceof File file) {
if (file.isAbsolute()) {
return file.toPath().toUri();
}
return IoUtils.createUri(IoUtils.toNormalizedPathString(file.toPath()));
} else if (notation instanceof URI uri) {
return uri;
}
throw new IllegalArgumentException("Invalid parsed module notation: " + notation);
}
protected URI parseModuleNotationToUri(Object m) {
var parsed1 = parseModuleNotation(m);
return parsedModuleNotationToUri(parsed1);
}
@TaskAction @TaskAction
@Override @Override
public void runTask() { public void runTask() {
@@ -172,7 +150,7 @@ public abstract class ModulesTask extends BasePklTask {
parseModulePath(), parseModulePath(),
getProject().getProjectDir().toPath(), getProject().getProjectDir().toPath(),
mapAndGetOrNull(getEvalRootDirPath(), Paths::get), mapAndGetOrNull(getEvalRootDirPath(), Paths::get),
mapAndGetOrNull(getSettingsModule(), this::parseModuleNotationToUri), mapAndGetOrNull(getSettingsModule(), PluginUtils::parseModuleNotationToUri),
getProjectDir().isPresent() ? getProjectDir().get().getAsFile().toPath() : null, getProjectDir().isPresent() ? getProjectDir().get().getAsFile().toPath() : null,
getEvalTimeout().getOrNull(), getEvalTimeout().getOrNull(),
mapAndGetOrNull(getModuleCacheDir(), it1 -> it1.getAsFile().toPath()), mapAndGetOrNull(getModuleCacheDir(), it1 -> it1.getAsFile().toPath()),
@@ -0,0 +1,128 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.gradle.utils;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.file.FileSystemLocation;
import org.pkl.core.util.IoUtils;
public class PluginUtils {
private PluginUtils() {}
/**
* Parses the specified source module notation into a "parsed" notation which is then used for
* input path tracking and as an argument for the CLI API.
*
* <p>This method accepts the following input types:
*
* <ul>
* <li>{@link URI} - used as is.
* <li>{@link File} - used as is.
* <li>{@link Path} - converted to a {@link File}. This conversion may fail because not all
* {@link Path}s point to the local file system.
* <li>{@link URL} - converted to a {@link URI}. This conversion may fail because {@link URL}
* allows for URLs which are not compliant URIs.
* <li>{@link CharSequence} - first, converted to a string. If this string is "URI-like" (see
* {@link IoUtils#isUriLike(String)}), then we attempt to parse it as a {@link URI}, which
* may fail. Otherwise, we attempt to parse it as a {@link Path}, which is then converted to
* a {@link File} (both of these operations may fail).
* <li>{@link FileSystemLocation} - converted to a {@link File} via the {@link
* FileSystemLocation#getAsFile()} method.
* </ul>
*
* In case the returned value is determined to be a {@link URI}, then this URI is first checked
* for whether its scheme is {@code file}, like {@code file:///example/path}. In such case, this
* method returns a {@link File} corresponding to the file path in the URI. Otherwise, a {@link
* URI} instance is returned.
*
* @throws InvalidUserDataException In case the input is none of the types described above, or
* when the underlying value cannot be parsed correctly.
*/
public static Object parseModuleNotation(Object notation) {
if (notation instanceof URI uri) {
if ("file".equals(uri.getScheme())) {
return new File(uri.getPath());
}
return uri;
} else if (notation instanceof File) {
return notation;
} else if (notation instanceof Path path) {
try {
return path.toFile();
} catch (UnsupportedOperationException e) {
throw new InvalidUserDataException("Failed to parse Pkl module file path: " + notation, e);
}
} else if (notation instanceof URL url) {
try {
return parseModuleNotation(url.toURI());
} catch (URISyntaxException e) {
throw new InvalidUserDataException("Failed to parse Pkl module URI: " + notation, e);
}
} else if (notation instanceof CharSequence) {
var s = notation.toString();
if (IoUtils.isUriLike(s)) {
try {
return parseModuleNotation(IoUtils.toUri(s));
} catch (URISyntaxException e) {
throw new InvalidUserDataException("Failed to parse Pkl module URI: " + s, e);
}
} else {
try {
return Paths.get(s).toFile();
} catch (InvalidPathException | UnsupportedOperationException e) {
throw new InvalidUserDataException("Failed to parse Pkl module file path: " + s, e);
}
}
} else if (notation instanceof FileSystemLocation location) {
return location.getAsFile();
} else {
throw new InvalidUserDataException(
"Unsupported value of type "
+ notation.getClass()
+ " used as a module path: "
+ notation);
}
}
/**
* Converts either a file or a URI to a URI. We convert a relative file to a URI via the {@link
* IoUtils#createUri(String)} because other ways of conversion can make relative paths into
* absolute URIs, which may break module loading.
*/
public static URI parsedModuleNotationToUri(Object notation) {
if (notation instanceof File file) {
if (file.isAbsolute()) {
return file.toPath().toUri();
}
return IoUtils.createUri(IoUtils.toNormalizedPathString(file.toPath()));
} else if (notation instanceof URI uri) {
return uri;
}
throw new IllegalArgumentException("Invalid parsed module notation: " + notation);
}
public static URI parseModuleNotationToUri(Object m) {
var parsed1 = PluginUtils.parseModuleNotation(m);
return parsedModuleNotationToUri(parsed1);
}
}
@@ -0,0 +1,4 @@
@NonnullByDefault
package org.pkl.gradle.utils;
import org.pkl.core.util.NonnullByDefault;
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,13 +15,17 @@
*/ */
package org.pkl.gradle package org.pkl.gradle
import java.nio.file.Path
import kotlin.io.path.readText import kotlin.io.path.readText
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import org.pkl.commons.test.PackageServer
class PkldocGeneratorsTest : AbstractTest() { class PkldocGeneratorsTest : AbstractTest() {
@Test @Test
fun `generate docs`() { fun `generate docs`(@TempDir tempDir: Path) {
PackageServer.populateCacheDir(tempDir)
writeFile( writeFile(
"build.gradle", "build.gradle",
""" """
@@ -32,7 +36,8 @@ class PkldocGeneratorsTest : AbstractTest() {
pkl { pkl {
pkldocGenerators { pkldocGenerators {
pkldoc { pkldoc {
sourceModules = ["person.pkl", "doc-package-info.pkl"] moduleCacheDir = file("${tempDir.toUri()}")
sourceModules = ["package://localhost:0/birds@0.5.0", "person.pkl", "doc-package-info.pkl"]
outputDir = file("build/pkldoc") outputDir = file("build/pkldoc")
settingsModule = "pkl:settings" settingsModule = "pkl:settings"
} }
@@ -94,6 +99,39 @@ class PkldocGeneratorsTest : AbstractTest() {
checkTextContains(moduleFile.readText(), "<html>", "Person", "Address", "other") checkTextContains(moduleFile.readText(), "<html>", "Person", "Address", "other")
checkTextContains(personFile.readText(), "<html>", "name", "addresses") checkTextContains(personFile.readText(), "<html>", "name", "addresses")
checkTextContains(addressFile.readText(), "<html>", "street", "zip") checkTextContains(addressFile.readText(), "<html>", "street", "zip")
val birdsPackageFile = baseDir.resolve("localhost(3a)0/birds/0.5.0/index.html")
assertThat(birdsPackageFile).exists()
}
@Test
fun `generate docs only for package`(@TempDir tempDir: Path) {
PackageServer.populateCacheDir(tempDir)
writeFile(
"build.gradle",
"""
plugins {
id "org.pkl-lang"
}
pkl {
pkldocGenerators {
pkldoc {
moduleCacheDir = file("${tempDir.toUri()}")
sourceModules = ["package://localhost:0/birds@0.5.0"]
outputDir = file("build/pkldoc")
settingsModule = "pkl:settings"
}
}
}
"""
)
runTask("pkldoc")
val baseDir = testProjectDir.resolve("build/pkldoc")
val birdsPackageFile = baseDir.resolve("localhost(3a)0/birds/0.5.0/index.html")
assertThat(birdsPackageFile).exists()
} }
@Test @Test
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -130,5 +130,5 @@ class ServerMessagePackDecoder(unpacker: MessageUnpacker) : BaseMessagePackDecod
} }
private fun unpackExternalReader(map: Map<Value, Value>): ExternalReader = private fun unpackExternalReader(map: Map<Value, Value>): ExternalReader =
ExternalReader(unpackString(map, "executable"), unpackStringListOrNull(map, "arguments")!!) ExternalReader(unpackString(map, "executable"), unpackStringListOrNull(map, "arguments"))
} }
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@
/// ///
/// Warning: Although this module is ready for initial use, /// Warning: Although this module is ready for initial use,
/// benchmark results may be inaccurate or inconsistent. /// benchmark results may be inaccurate or inconsistent.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.Benchmark module pkl.Benchmark
import "pkl:platform" as _platform import "pkl:platform" as _platform
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@
/// @Deprecated { message = "Use `com.example.Birds.Parrot` instead" } /// @Deprecated { message = "Use `com.example.Birds.Parrot` instead" }
/// amends "pkl:PackageInfo" /// amends "pkl:PackageInfo"
/// ``` /// ```
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.DocPackageInfo module pkl.DocPackageInfo
import "pkl:reflect" import "pkl:reflect"
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
/// ///
/// title = "Title displayed in the header of each page" /// title = "Title displayed in the header of each page"
/// ``` /// ```
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.DocsiteInfo module pkl.DocsiteInfo
import "pkl:reflect" import "pkl:reflect"
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Common settings for Pkl's own evaluator. /// Common settings for Pkl's own evaluator.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
@Since { version = "0.26.0" } @Since { version = "0.26.0" }
module pkl.EvaluatorSettings module pkl.EvaluatorSettings
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -64,7 +64,7 @@
/// value = project /// value = project
/// } /// }
/// ``` /// ```
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.Project module pkl.Project
import "pkl:EvaluatorSettings" as EvaluatorSettingsModule import "pkl:EvaluatorSettings" as EvaluatorSettingsModule
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
/// These tools differentiate from [pkl:reflect][reflect] in that they parse Pkl modules, but do not /// These tools differentiate from [pkl:reflect][reflect] in that they parse Pkl modules, but do not
/// execute any code within these modules. /// execute any code within these modules.
@Since { version = "0.27.0" } @Since { version = "0.27.0" }
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.analyze module pkl.analyze
// used by doc comments // used by doc comments
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
/// Fundamental properties, methods, and classes for writing Pkl programs. /// Fundamental properties, methods, and classes for writing Pkl programs.
/// ///
/// Members of this module are automatically available in every Pkl module. /// Members of this module are automatically available in every Pkl module.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.base module pkl.base
import "pkl:jsonnet" import "pkl:jsonnet"
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// A JSON parser. /// A JSON parser.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.json module pkl.json
/// A JSON parser. /// A JSON parser.
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// A [Jsonnet](https://jsonnet.org) renderer. /// A [Jsonnet](https://jsonnet.org) renderer.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.jsonnet module pkl.jsonnet
/// Constructs an [ImportStr]. /// Constructs an [ImportStr].
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
/// ///
/// Note that some mathematical functions, such as `sign()`, `abs()`, and `round()`, /// Note that some mathematical functions, such as `sign()`, `abs()`, and `round()`,
/// are directly defined in classes [Number], [Int], and [Float]. /// are directly defined in classes [Number], [Int], and [Float].
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.math module pkl.math
/// The minimum [Int] value: `-9223372036854775808`. /// The minimum [Int] value: `-9223372036854775808`.
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Information about the platform that the current program runs on. /// Information about the platform that the current program runs on.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.platform module pkl.platform
/// The platform that the current program runs on. /// The platform that the current program runs on.
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
/// A renderer for [Protocol Buffers](https://developers.google.com/protocol-buffers). /// A renderer for [Protocol Buffers](https://developers.google.com/protocol-buffers).
/// Note: This module is _experimental_ and not ready for production use. /// Note: This module is _experimental_ and not ready for production use.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.protobuf module pkl.protobuf
import "pkl:reflect" import "pkl:reflect"
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@
/// - Documentation generators (such as *Pkldoc*) /// - Documentation generators (such as *Pkldoc*)
/// - Code generators (such as *pkl-codegen-java* and *pkl-codegen-kotlin*) /// - Code generators (such as *pkl-codegen-java* and *pkl-codegen-kotlin*)
/// - Domain-specific schema validators /// - Domain-specific schema validators
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.reflect module pkl.reflect
import "pkl:base" import "pkl:base"
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Information about the Pkl release that the current program runs on. /// Information about the Pkl release that the current program runs on.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.release module pkl.release
import "pkl:semver" import "pkl:semver"
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Parsing, comparison, and manipulation of [semantic version](https://semver.org/spec/v2.0.0.html) numbers. /// Parsing, comparison, and manipulation of [semantic version](https://semver.org/spec/v2.0.0.html) numbers.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.semver module pkl.semver
/// Tells whether [version] is a valid semantic version number. /// Tells whether [version] is a valid semantic version number.
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
/// Every settings file must amend this module. /// Every settings file must amend this module.
/// Unless CLI commands and build tool plugins are explicitly configured with a settings file, /// Unless CLI commands and build tool plugins are explicitly configured with a settings file,
/// they will use `~/.pkl/settings.pkl` or the defaults specified in this module. /// they will use `~/.pkl/settings.pkl` or the defaults specified in this module.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.settings module pkl.settings
import "pkl:EvaluatorSettings" import "pkl:EvaluatorSettings"
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Utilities for generating shell scripts. /// Utilities for generating shell scripts.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.shell module pkl.shell
/// Escapes [str] by enclosing it in single quotes. /// Escapes [str] by enclosing it in single quotes.
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
/// ///
/// To write tests, amend this module and define [facts] or [examples] (or both). /// To write tests, amend this module and define [facts] or [examples] (or both).
/// To run tests, evaluate the amended module. /// To run tests, evaluate the amended module.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
open module pkl.test open module pkl.test
/// Named groups of boolean expressions that are expected to evaluate to [true]. /// Named groups of boolean expressions that are expected to evaluate to [true].
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// An XML renderer. /// An XML renderer.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.xml module pkl.xml
/// Renders values as XML. /// Renders values as XML.
+2 -2
View File
@@ -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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// A YAML 1.2 compliant YAML parser. /// A YAML 1.2 compliant YAML parser.
@ModuleInfo { minPklVersion = "0.27.0" } @ModuleInfo { minPklVersion = "0.27.2" }
module pkl.yaml module pkl.yaml
/// A YAML parser. /// A YAML parser.