Compare commits

...

8 Commits

Author SHA1 Message Date
Daniel Chao 1bf00b84ea Revert "Reject abstract members in non-abstract classes" (#1688)
The changes made in this commit are good, but we're going to kick this out
to the next release.
This is because:

1. There's a couple more issues around the `abstract` modifier that is not
implemented yet, and need design considerations
2. These are breaking changes, and we want to minimize the amount of breakages
for users.
3. The main branch is still the develop branch for Pkl 0.32

We will apply a re-revert of this commit after Pkl 0.32 is released.
2026-06-23 09:46:14 -07:00
Daniel Chao 15f089b275 Implement toString() on VmReference (#1692)
This is still needed right now, because some methods call through to
`toString()`.
2026-06-23 09:45:13 -07:00
Jen Basch 8a43e51e6b SPICE-0020: Deferred, type-safe references (#1354) 2026-06-23 15:26:06 +02:00
Vladimir Matveev b3015a09cc Catching exceptions during property evaluation (#1684)
The eval task, unlike other tasks, creates the evaluator when the property is evaluated, not just when the task is executed (in the `getEffective*` properties). The problem here is that property evaluation can and will happen before the task is executed, because Gradle needs property values for caching and sometimes task dependency information.

Therefore, if due to misconfiguration or due to intentional configuration the evaluator cannot be created - for example, when the `PklProject` module doesn't exist, but the project directory is configured - then the task will fail with an exception which looks _very_ similar to a regular execution exception, but which actually is not, because it is thrown not during task execution, but during preparation for the execution.

This distinction matters a lot when you rely on conditional task execution. If you use the `onlyIf` predicate on tasks to define a condition for the task execution, this predicate will be evaluated before the task actions are run, but *after* properties are evaluated. Thus, if property evaluation fails with an exception, it will *look* as if the `onlyIf` predicate is completely ignored and not even evaluated, and the task action is run regardless of the predicate.

This error mode is extremely confusing and is actually wrong: if I use `onlyIf` to gate the task execution, I don't want *any* of its logic to run. In fact, in my case I specifically use `onlyIf` on the evaluation task to only execute it if a prerequisite is met, and the same predicate guards a bunch of other tasks which prepare the Pkl project, so my Pkl project isn't even generated if the predicate fails. But due to this behavior of properties evaluation which are not controlled by `onlyIf`, the project is still attempted to be evaluated, and this results in a very unexpected build failure.

The solution is to ignore the evaluation exception, because for all intents and purposes, if the project can't be properly evaluated, it will fail during the task execution as it should, so the values of output properties don't really matter as the task will never be run.
2026-06-22 14:30:27 -07:00
dependabot[bot] ffc755f9c1 Bump errorProne from 2.49.0 to 2.50.0 (#1685)
Bumps `errorProne` from 2.49.0 to 2.50.0.

Updates `com.google.errorprone:error_prone_core` from 2.49.0 to 2.50.0
- [Release notes](https://github.com/google/error-prone/releases)
- [Commits](https://github.com/google/error-prone/compare/v2.49.0...v2.50.0)

Updates `com.google.errorprone:error_prone_annotations` from 2.49.0 to 2.50.0
- [Release notes](https://github.com/google/error-prone/releases)
- [Commits](https://github.com/google/error-prone/compare/v2.49.0...v2.50.0)

---
updated-dependencies:
- dependency-name: com.google.errorprone:error_prone_core
  dependency-version: 2.50.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.google.errorprone:error_prone_annotations
  dependency-version: 2.50.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 10:33:28 -07:00
dependabot[bot] 112a89468f Bump com.uber.nullaway:nullaway from 0.13.4 to 0.13.6 (#1686)
Bumps [com.uber.nullaway:nullaway](https://github.com/uber/NullAway) from 0.13.4 to 0.13.6.
- [Release notes](https://github.com/uber/NullAway/releases)
- [Changelog](https://github.com/uber/NullAway/blob/master/CHANGELOG.md)
- [Commits](uber/NullAway@v0.13.4...v0.13.6)

---
updated-dependencies:
- dependency-name: com.uber.nullaway:nullaway
  dependency-version: 0.13.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 10:31:12 -07:00
Islon Scherer bfac0d66ed Reject relative package URIs (#1683) 2026-06-17 15:39:03 +02:00
Daniel Chao fc8fe86e5a Remove incorrectly placed index methods on Collection (#1681)
These methods aren't implemented in `Set`, and don't really make sense because `Set` types can't be accessed by index.

Note: although this removes methods, this actually isn't a breaking change:
1. Calling `Set.findIndex()` currently throws an error around "cannot invoke abstract method"
2. `List` and `Set` are the only subclasses of `Collection`.

The following code isn't breaking at runtime, although static analysis tooling (like our IDE plugins) will now flag this as an error:

```pkl
myCollection: Collection

idx = myCollection.indexOf(1)
```
2026-06-15 08:56:55 -07:00
101 changed files with 2757 additions and 370 deletions
@@ -69,6 +69,7 @@ endif::[]
:uri-stdlib-baseModule: {uri-pkl-stdlib-docs}/base
:uri-stdlib-CommandModule: {uri-pkl-stdlib-docs}/Command
:uri-stdlib-refModule: {uri-pkl-stdlib-docs}/ref
:uri-stdlib-analyzeModule: {uri-pkl-stdlib-docs}/analyze
:uri-stdlib-jsonnetModule: {uri-pkl-stdlib-docs}/jsonnet
:uri-stdlib-reflectModule: {uri-pkl-stdlib-docs}/reflect
@@ -160,6 +161,8 @@ endif::[]
:uri-stdlib-Command-CountedFlag: {uri-stdlib-CommandModule}/CountedFlag
:uri-stdlib-Command-Argument: {uri-stdlib-CommandModule}/Argument
:uri-stdlib-Command-Import: {uri-stdlib-CommandModule}/Import
:uri-stdlib-ref-Reference: {uri-stdlib-baseModule}/Reference
:uri-stdlib-ref-Access: {uri-stdlib-baseModule}/Access
:uri-messagepack: https://msgpack.org/index.html
:uri-messagepack-spec: https://github.com/msgpack/msgpack/blob/master/spec.md
@@ -187,7 +187,15 @@ The array's length is the number of slots that are filled. For example, xref:{ur
|
|
|
|===
|link:{uri-stdlib-ref-Reference}[Reference]
|`0x20`
|`<value>` (Typed)
|Domain
|`<value>`
|Data
|link:{uri-messagepack-array}[array]
|Array of link:{uri-stdlib-ref-Access}[`pkl.ref#Access`] values
[[type-name-encoding]]
[NOTE]
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -5,7 +5,7 @@ checksumPlugin = "1.4.0"
clikt = "5.0.3"
commonMark = "0.28.0"
downloadTaskPlugin = "5.7.0"
errorProne = "2.49.0"
errorProne = "2.50.0"
errorPronePlugin = "5.1.0"
geantyref = "2.0.1"
#noinspection UnusedVersionCatalogEntry
@@ -54,7 +54,7 @@ ktfmt = "0.62"
log4j = "2.17.1"
msgpack = "0.9.12"
nexusPublishPlugin = "2.0.0"
nullaway = "0.13.4"
nullaway = "0.13.6"
nullawayPlugin = "3.0.0"
nuValidator = "26.5.29"
paguro = "3.10.3"
@@ -27,6 +27,7 @@ public final class PClass extends Member implements Value {
private final List<TypeParameter> typeParameters;
private final Map<String, Property> properties;
private final Map<String, Method> methods;
private final @Nullable PClass moduleClass;
private @Nullable PType supertype;
private @Nullable PClass superclass;
@@ -42,12 +43,14 @@ public final class PClass extends Member implements Value {
PClassInfo<?> classInfo,
List<TypeParameter> typeParameters,
Map<String, Property> properties,
Map<String, Method> methods) {
Map<String, Method> methods,
@Nullable PClass moduleClass) {
super(docComment, sourceLocation, modifiers, annotations, classInfo.getSimpleName());
this.classInfo = classInfo;
this.typeParameters = typeParameters;
this.properties = properties;
this.methods = methods;
this.moduleClass = moduleClass;
}
public void initSupertype(PType supertype, PClass superclass) {
@@ -57,7 +60,7 @@ public final class PClass extends Member implements Value {
/**
* Returns the name of the module that this class is declared in. Note that a module name is not
* guaranteed to be unique, especially if it not declared but inferred from the module URI.
* guaranteed to be unique, especially if it is not declared but inferred from the module URI.
*/
public String getModuleName() {
return classInfo.getModuleName();
@@ -119,6 +122,11 @@ public final class PClass extends Member implements Value {
return allMethods;
}
/** Returns the class's containing module's class, or this class if it is a module class. */
public PClass getModuleClass() {
return moduleClass != null ? moduleClass : this;
}
@Override
public void accept(ValueVisitor visitor) {
visitor.visitClass(this);
@@ -138,6 +146,10 @@ public final class PClass extends Member implements Value {
return getDisplayName();
}
public boolean isSubclassOf(PClass other) {
return this == other || getSuperclass() != null && getSuperclass().isSubclassOf(other);
}
public abstract static class ClassMember extends Member {
@Serial private static final long serialVersionUID = 0L;
@@ -39,6 +39,7 @@ public final class PClassInfo<T> implements Serializable {
public static final URI pklSemverUri = URI.create("pkl:semver");
public static final URI pklSettingsUri = URI.create("pkl:settings");
public static final URI pklProjectUri = URI.create("pkl:Project");
public static final URI pklRefUri = URI.create("pkl:ref");
public static final PClassInfo<Void> Any = pklBaseClassInfo("Any", Void.class);
public static final PClassInfo<PNull> Null = pklBaseClassInfo("Null", PNull.class);
@@ -82,9 +83,11 @@ public final class PClassInfo<T> implements Serializable {
public static final PClassInfo<PObject> Version =
new PClassInfo<>("pkl.semver", "Version", PObject.class, pklSemverUri);
public static final PClassInfo<PObject> Project =
new PClassInfo<>("pkl.Project", "ModuleClass", PObject.class, pklProjectUri);
new PClassInfo<>("pkl.Project", MODULE_CLASS_NAME, PObject.class, pklProjectUri);
public static final PClassInfo<PObject> Settings =
new PClassInfo<>("pkl.settings", "ModuleClass", PObject.class, pklSettingsUri);
new PClassInfo<>("pkl.settings", MODULE_CLASS_NAME, PObject.class, pklSettingsUri);
public static final PClassInfo<Reference> Reference =
new PClassInfo<>("pkl.ref", "Reference", Reference.class, pklRefUri);
public static final PClassInfo<Object> Unavailable =
new PClassInfo<>("unavailable", "unavailable", Object.class, URI.create("pkl:unavailable"));
+81 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2026 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.
@@ -18,6 +18,7 @@ package org.pkl.core;
import java.io.Serial;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
/** A Pkl type as used in type annotations. */
public abstract class PType implements Serializable {
@@ -27,18 +28,33 @@ public abstract class PType implements Serializable {
public static final PType UNKNOWN =
new PType() {
@Serial private static final long serialVersionUID = 0L;
@Override
public String toString() {
return "unknown";
}
};
/** The bottom type. */
public static final PType NOTHING =
new PType() {
@Serial private static final long serialVersionUID = 0L;
@Override
public String toString() {
return "nothing";
}
};
/** The type of the enclosing module. */
public static final PType MODULE =
new PType() {
@Serial private static final long serialVersionUID = 0L;
@Override
public String toString() {
return "module";
}
};
private PType() {}
@@ -59,6 +75,11 @@ public abstract class PType implements Serializable {
public String getLiteral() {
return literal;
}
@Override
public String toString() {
return ValueFormatter.basic().formatStringValue(literal, "");
}
}
public static final class Class extends PType {
@@ -92,6 +113,18 @@ public abstract class PType implements Serializable {
public List<PType> getTypeArguments() {
return typeArguments;
}
@Override
public String toString() {
var result = pClass.getDisplayName();
if (!typeArguments.isEmpty()) {
result +=
"<"
+ typeArguments.stream().map(Object::toString).collect(Collectors.joining(", "))
+ ">";
}
return result;
}
}
public static final class Nullable extends PType {
@@ -106,6 +139,13 @@ public abstract class PType implements Serializable {
public PType getBaseType() {
return baseType;
}
@Override
public String toString() {
return baseType instanceof Function || baseType instanceof Union
? "(" + baseType + ")?"
: baseType + "?";
}
}
public static final class Constrained extends PType {
@@ -126,6 +166,16 @@ public abstract class PType implements Serializable {
public List<String> getConstraints() {
return constraints;
}
@Override
public String toString() {
return (baseType instanceof Function || baseType instanceof Union
? "(" + baseType + ")"
: baseType)
+ "("
+ String.join(", ", constraints)
+ ")";
}
}
public static final class Alias extends PType {
@@ -161,6 +211,18 @@ public abstract class PType implements Serializable {
public PType getAliasedType() {
return aliasedType;
}
@Override
public String toString() {
var result = typeAlias.getDisplayName();
if (!typeArguments.isEmpty()) {
result +=
"<"
+ typeArguments.stream().map(Object::toString).collect(Collectors.joining(", "))
+ ">";
}
return result;
}
}
public static final class Function extends PType {
@@ -181,6 +243,14 @@ public abstract class PType implements Serializable {
public PType getReturnType() {
return returnType;
}
@Override
public String toString() {
return "("
+ parameterTypes.stream().map(Object::toString).collect(Collectors.joining(", "))
+ ") -> "
+ returnType;
}
}
public static final class Union extends PType {
@@ -195,6 +265,11 @@ public abstract class PType implements Serializable {
public List<PType> getElementTypes() {
return elementTypes;
}
@Override
public String toString() {
return elementTypes.stream().map(Object::toString).collect(Collectors.joining(" | "));
}
}
public static final class TypeVariable extends PType {
@@ -213,5 +288,10 @@ public abstract class PType implements Serializable {
public TypeParameter getTypeParameter() {
return typeParameter;
}
@Override
public String toString() {
return typeParameter.getName();
}
}
}
@@ -177,6 +177,13 @@ final class PropertiesRenderer implements ValueRenderer {
"Values of type `Regex` cannot be rendered as Properties. Value: %s", value));
}
@Override
public String convertReference(Reference value) {
throw new RendererException(
String.format(
"Values of type `Reference` cannot be rendered as Properties. Value: %s", value));
}
private void doVisitMap(@Nullable String keyPrefix, Map<?, ?> map) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
doVisitKeyAndValue(keyPrefix, entry.getKey(), entry.getValue());
@@ -0,0 +1,101 @@
/*
* Copyright © 2026 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;
import java.io.Serial;
import java.util.List;
public class Reference implements Value {
@Serial private static final long serialVersionUID = 0L;
private final Composite domain;
private final Object data;
private final List<Composite> path;
private final PType referentType;
/** Constructs a reference. */
public Reference(Composite domain, Object data, List<Composite> path, PType referentType) {
this.domain = domain;
this.data = data;
this.path = path;
this.referentType = referentType;
}
/** Returns the domain object of this reference. */
public Composite getDomain() {
return domain;
}
/** Returns the data object of this reference. */
public Object getData() {
return data;
}
/**
* Returns the access path of this reference.
*
* <p>All elements are exported {@code pkl.ref#Access} instances.
*/
public List<Composite> getPath() {
return path;
}
/** Returns the referent type of this reference. */
public PType getReferentType() {
return referentType;
}
@Override
public void accept(ValueVisitor visitor) {
visitor.visitReference(this);
}
@Override
public <T> T accept(ValueConverter<T> converter) {
return converter.convertReference(this);
}
@Override
public final boolean equals(Object o) {
if (!(o instanceof Reference reference)) {
return false;
}
return domain.equals(reference.domain)
&& data.equals(reference.data)
&& path.equals(reference.path)
&& referentType.equals(reference.referentType);
}
@Override
public PClassInfo<?> getClassInfo() {
return PClassInfo.Reference;
}
@Override
public int hashCode() {
int result = domain.hashCode();
result = 31 * result + data.hashCode();
result = 31 * result + path.hashCode();
result = 31 * result + referentType.hashCode();
return result;
}
@Override
public String toString() {
return super.toString();
}
}
@@ -28,6 +28,7 @@ public final class TypeAlias extends Member implements Value {
private final String moduleName;
private final String qualifiedName;
private final List<TypeParameter> typeParameters;
private final PClass moduleClass;
@LateInit private PType aliasedType;
@@ -39,11 +40,13 @@ public final class TypeAlias extends Member implements Value {
String simpleName,
String moduleName,
String qualifiedName,
List<TypeParameter> typeParameters) {
List<TypeParameter> typeParameters,
PClass moduleClass) {
super(docComment, sourceLocation, modifiers, annotations, simpleName);
this.moduleName = moduleName;
this.qualifiedName = qualifiedName;
this.typeParameters = typeParameters;
this.moduleClass = moduleClass;
}
public void initAliasedType(PType type) {
@@ -79,6 +82,10 @@ public final class TypeAlias extends Member implements Value {
return typeParameters;
}
public PClass getModuleClass() {
return moduleClass;
}
/** Returns the type that this type alias stands for. */
public PType getAliasedType() {
//noinspection ConstantValue
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2026 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.
@@ -57,6 +57,8 @@ public interface ValueConverter<T> {
T convertRegex(Pattern value);
T convertReference(Reference value);
default T convert(Object value) {
if (value instanceof Value v) {
return (v.accept(this));
@@ -93,6 +93,10 @@ public interface ValueVisitor {
visitDefault(value);
}
default void visitReference(Reference value) {
visitDefault(value);
}
default void visit(Object value) {
if (value instanceof Value v) {
v.accept(this);
@@ -1476,8 +1476,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var scope = (ModuleScope) symbolTable.getCurrentScope();
scope.setModifiers(modifiers);
checkAbstractMembersAllowed(modifiers, mod.getProperties(), mod.getMethods());
// visit imports first so that we already have the object member name available
var imports = mod.getImports();
var importMembers = new ObjectMember[imports.size()];
@@ -1722,7 +1720,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
List<ClassProperty> properties = bodyNode != null ? bodyNode.getProperties() : List.of();
List<ClassMethod> methods = bodyNode != null ? bodyNode.getMethods() : List.of();
registerClassScopeNames(scope, properties, methods);
checkAbstractMembersAllowed(modifiers, properties, methods);
var supertypeCtx = clazz.getSuperClass();
@@ -1813,30 +1810,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
};
}
private void checkAbstractMembersAllowed(
int enclosingModifiers, List<ClassProperty> properties, List<ClassMethod> methods) {
if (VmModifier.isAbstract(enclosingModifiers)) {
return;
}
for (var property : properties) {
checkMemberNotAbstract(property.getModifiers());
}
for (var method : methods) {
checkMemberNotAbstract(method.getModifiers());
}
}
private void checkMemberNotAbstract(List<Modifier> modifiers) {
for (var modifier : modifiers) {
if (modifier.getValue() == ModifierValue.ABSTRACT) {
throw exceptionBuilder()
.evalError("abstractMemberInNonAbstractClass")
.withSourceSection(createSourceSection(modifier.span()))
.build();
}
}
}
private UnresolvedPropertyNode[] doVisitClassProperties(
List<ClassProperty> propertyContexts, Set<String> propertyNames) {
var propertyNodes = new UnresolvedPropertyNode[propertyContexts.size()];
@@ -100,6 +100,20 @@ public abstract class SubscriptNode extends BinaryExpressionNode {
return readMember(dynamic, key, callNode);
}
@Specialization
protected VmReference eval(VmReference reference, Object key) {
var result = reference.withSubscriptAccess(key);
if (result != null) return result;
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError(
"operatorNotDefined2", getShortName(), reference.exportType(), VmUtils.getClass(key))
.withProgramValue("Left operand", reference)
.withProgramValue("Right operand", key)
.build();
}
@Specialization
protected long eval(VmBytes receiver, long index) {
if (index < 0 || index >= receiver.getLength()) {
@@ -61,6 +61,18 @@ public abstract class ReadPropertyNode extends ExpressionNode {
this(sourceSection, propertyName, MemberLookupMode.EXPLICIT_RECEIVER, false);
}
@Specialization
protected VmReference evalReference(VmReference receiver) {
assert lookupMode == MemberLookupMode.EXPLICIT_RECEIVER;
var result = receiver.withPropertyAccess(propertyName);
if (result != null) return result;
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("cannotFindPropertyInReference", propertyName, receiver.exportType())
.build();
}
// This method effectively covers `VmObject receiver` but is implemented in a more
// efficient way. See:
// https://www.graalvm.org/22.0/graalvm-as-a-platform/language-implementation-framework/TruffleLibraries/#strategy-2-java-interfaces
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2026 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.
@@ -20,6 +20,7 @@ import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.MemberLookupMode;
@@ -62,10 +63,18 @@ public abstract class ToStringNode extends UnaryExpressionNode {
VmTyped value,
@Cached(value = "createInvokeNode()", neverDefault = true)
InvokeMethodVirtualNode invokeNode) {
return (String) invokeNode.executeWith(frame, value, value.getVmClass());
}
@Specialization
protected String evalReference(
VirtualFrame frame,
VmReference value,
@Cached(value = "createReferenceCallNode(value)", neverDefault = true)
DirectCallNode callNode) {
return (String) callNode.call(value, value.getVmClass().getPrototype());
}
@Fallback
@Override
@TruffleBoundary
@@ -74,7 +83,6 @@ public abstract class ToStringNode extends UnaryExpressionNode {
}
protected InvokeMethodVirtualNode createInvokeNode() {
//noinspection ConstantConditions
return InvokeMethodVirtualNodeGen.create(
sourceSection,
Identifier.TO_STRING,
@@ -83,4 +91,10 @@ public abstract class ToStringNode extends UnaryExpressionNode {
null,
null);
}
protected DirectCallNode createReferenceCallNode(VmReference reference) {
var toStringMethod = reference.getVmClass().getDeclaredMethod(Identifier.TO_STRING);
assert toStringMethod != null;
return DirectCallNode.create(toStringMethod.getCallTarget());
}
}
@@ -2127,6 +2127,125 @@ public abstract class TypeNode extends PklNode {
}
}
public abstract static class ReferenceTypeNode extends ObjectSlotTypeNode {
@Child private TypeNode domainTypeNode;
@Child private TypeNode referentTypeNode;
@Child private ExpressionNode getModuleNode;
public ReferenceTypeNode(
SourceSection sourceSection, TypeNode domainTypeNode, TypeNode referentTypeNode) {
super(sourceSection);
this.domainTypeNode = domainTypeNode;
this.referentTypeNode = referentTypeNode;
this.getModuleNode = new GetModuleNode(sourceSection);
validateTypeArguments(sourceSection);
}
@Specialization
protected Object eval(VirtualFrame frame, VmReference value) {
if (domainTypeNode.isNoopTypeCheck() && referentTypeNode.isNoopTypeCheck()) {
return value;
}
try {
domainTypeNode.execute(frame, value.getDomain());
} catch (VmTypeMismatchException e) {
CompilerDirectives.transferToInterpreter();
throw new VmTypeMismatchException.Reference(
sourceSection,
value,
TypeNode.export(domainTypeNode),
TypeNode.export(referentTypeNode));
}
var module = (VmTyped) getModuleNode.executeGeneric(frame);
return doEval(value, module);
}
@TruffleBoundary
private Object doEval(VmReference value, VmTyped module) {
var referentType = TypeNode.export(referentTypeNode);
if (value.referentTypeIsSubtypeOf(referentType, module.getVmClass().export())) {
return value;
}
throw new VmTypeMismatchException.Reference(
sourceSection, value, TypeNode.export(domainTypeNode), referentType);
}
public void validateTypeArguments(@Nullable SourceSection aliasSourceSection) {
// constraints may not be used in Reference type annotation referents
// walk the type and throw if any part of the referent is constrained
// TODO improve error message when this type node and/or referent constraint are behind type
// aliases
referentTypeNode.acceptTypeNode(
true,
(typeNode) -> {
if (typeNode instanceof ConstrainedTypeNode) {
CompilerDirectives.transferToInterpreter();
var err =
exceptionBuilder().evalError("invalidReferenceTypeAnnotationWithConstraint");
if (aliasSourceSection != null) {
err.withSourceSection(aliasSourceSection);
}
throw err.build();
}
return true;
});
}
@Fallback
protected Object fallback(Object value) {
throw typeMismatch(value, RefModule.getReferenceClass());
}
@Override
protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer) {
if (visitTypeArguments)
return consumer.accept(this)
&& consumer.accept(domainTypeNode)
&& consumer.accept(referentTypeNode);
return consumer.accept(this);
}
@Override
public VmClass getVmClass() {
return RefModule.getReferenceClass();
}
@Override
public VmList getTypeArgumentMirrors() {
return VmList.of(domainTypeNode.getMirror(), referentTypeNode.getMirror());
}
@Override
protected boolean doIsEquivalentTo(TypeNode other) {
if (!(other instanceof ReferenceTypeNode referenceTypeNode)) {
return false;
}
return referentTypeNode.isEquivalentTo(referenceTypeNode.referentTypeNode);
}
@Override
public boolean isNoopTypeCheck() {
return domainTypeNode.isNoopTypeCheck() && referentTypeNode.isNoopTypeCheck();
}
@Override
protected PType doExport() {
return new PType.Class(
RefModule.getReferenceClass().export(),
domainTypeNode.doExport(),
referentTypeNode.doExport());
}
@Override
protected boolean isParametric() {
return true;
}
}
public static final class PairTypeNode extends ObjectSlotTypeNode {
@Child private TypeNode firstTypeNode;
@Child private TypeNode secondTypeNode;
@@ -2584,7 +2703,7 @@ public abstract class TypeNode extends PklNode {
this.typeAlias = typeAlias;
this.typeArgumentNodes = typeArgumentNodes;
aliasedTypeNode = typeAlias.instantiate(typeArgumentNodes);
aliasedTypeNode = typeAlias.instantiate(typeArgumentNodes, sourceSection);
}
public TypeNode getAliasedTypeNode() {
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2026 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.
@@ -287,6 +287,13 @@ public abstract class UnresolvedTypeNode extends PklNode {
return new VarArgsTypeNode(sourceSection, typeArgumentNodes[0].execute(frame));
}
if (clazz.isReferenceClass()) {
return ReferenceTypeNodeGen.create(
sourceSection,
typeArgumentNodes[0].execute(frame),
typeArgumentNodes[1].execute(frame));
}
throw exceptionBuilder()
.evalError("notAParameterizableClass", clazz.getDisplayName())
.withSourceSection(typeArgumentNodes[0].sourceSection)
@@ -23,6 +23,7 @@ import com.oracle.truffle.api.source.SourceSection;
import java.util.*;
import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
import org.pkl.core.PType;
import org.pkl.core.StackFrame;
import org.pkl.core.ValueFormatter;
import org.pkl.core.ast.type.TypeNode.UnionTypeNode;
@@ -333,4 +334,44 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
return false;
}
}
public static final class Reference extends VmTypeMismatchException {
private final PType expectedDomainType;
private final PType expectedReferentType;
public Reference(
SourceSection sourceSection,
VmReference actualValue,
PType expectedDomainType,
PType expectedReferentType) {
super(sourceSection, actualValue);
this.expectedDomainType = expectedDomainType;
this.expectedReferentType = expectedReferentType;
}
@Override
public void buildMessage(
AnsiStringBuilder builder, String indent, boolean withPowerAssertions) {
builder
.append(
ErrorMessages.createIndented(
"typeMismatch",
indent,
new PType.Class(
RefModule.getReferenceClass().export(),
expectedDomainType,
expectedReferentType),
((VmReference) actualValue).exportType()))
.append("\n")
.append(indent)
.append("Value: ")
.append(VmValueRenderer.singleLine(80 - indent.length()).render(actualValue));
}
@Override
protected Boolean hasHint() {
return false;
}
}
}
@@ -45,6 +45,7 @@ import java.util.stream.StreamSupport;
import java.util.zip.ZipInputStream;
import org.graalvm.collections.EconomicMap;
import org.jspecify.annotations.Nullable;
import org.pkl.core.PklBugException;
import org.pkl.core.SecurityManager;
import org.pkl.core.SecurityManagerException;
import org.pkl.core.http.HttpClient;
@@ -456,10 +457,17 @@ final class PackageResolvers {
}
private Path getRelativePath(PackageUri uri) {
return Path.of(
CACHE_DIR_PREFIX,
IoUtils.encodePath(uri.getUri().getAuthority()),
getEffectivePackageUriPath(uri));
var relativePath =
Path.of(
CACHE_DIR_PREFIX,
IoUtils.encodePath(uri.getUri().getAuthority()),
getEffectivePackageUriPath(uri));
// ensure the derived path cannot escape the cache directory
var resolved = cacheDir.resolve(relativePath).normalize();
if (!resolved.startsWith(cacheDir.normalize())) {
throw new PklBugException("Package URI escapes the cache directory");
}
return relativePath;
}
private String getLastSegmentName(PackageUri packageUri) {
@@ -61,6 +61,13 @@ public final class PackageUri {
throw new URISyntaxException(
uri.toString(), ErrorMessages.create("missingPathInPackageUri", uri));
}
// reject `..` segments, percent-encoded or not
for (var segment : path.split("/", -1)) {
if (segment.equals("..")) {
throw new URISyntaxException(
uri.toString(), ErrorMessages.create("invalidRelativePathInPackageUri"));
}
}
var versionIdx = path.lastIndexOf('@');
if (versionIdx == -1) {
throw new URISyntaxException(
@@ -165,6 +165,10 @@ public final class Identifier implements Comparable<Identifier> {
public static final Identifier ILLEGAL = get("`");
// members of pkl.ref
public static final Identifier PROPERTY = get("property");
public static final Identifier KEY = get("key");
// common in lambdas etc
public static final Identifier IT = get("it");
@@ -104,6 +104,8 @@ public final class ModuleCache {
return PlatformModule.getModule();
case "project":
return ProjectModule.getModule();
case "ref":
return RefModule.getModule();
case "reflect":
return ReflectModule.getModule();
case "release":
@@ -0,0 +1,53 @@
/*
* Copyright © 2026 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.runtime;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import java.net.URI;
public class RefModule extends StdLibModule {
private static final VmTyped instance = VmUtils.createEmptyModule();
static {
loadModule(URI.create("pkl:ref"), instance);
}
public static VmClass getReferenceClass() {
return ReferenceClass.instance;
}
public static VmClass getAccessClass() {
return AccessClass.instance;
}
private static final class ReferenceClass {
static final VmClass instance = loadClass("Reference");
}
private static final class AccessClass {
static final VmClass instance = loadClass("Access");
}
@TruffleBoundary
private static VmClass loadClass(String className) {
var theModule = getModule();
return (VmClass) VmUtils.readMember(theModule, Identifier.get(className));
}
public static VmTyped getModule() {
return instance;
}
}
@@ -445,6 +445,11 @@ public final class VmClass extends VmValue {
return isClass(BaseModule.getVarArgsClass(), "pkl.base#VarArgs");
}
@Idempotent
public boolean isReferenceClass() {
return isClass(RefModule.getReferenceClass(), "pkl.ref#Reference");
}
private boolean isClass(@Nullable VmClass clazz, String qualifiedClassName) {
// may be null during evaluation of base module
return clazz != null ? this == clazz : getQualifiedName().equals(qualifiedClassName);
@@ -621,49 +626,62 @@ public final class VmClass extends VmValue {
public PClass export() {
synchronized (pClassLock) {
//noinspection ConstantValue
if (__pClass == null) {
var exportedAnnotations = new ArrayList<PObject>();
var properties =
CollectionUtils.<String, PClass.Property>newLinkedHashMap(
EconomicMaps.size(declaredProperties));
var methods =
CollectionUtils.<String, PClass.Method>newLinkedHashMap(
EconomicMaps.size(declaredMethods));
if (__pClass != null) {
return __pClass;
}
// set pClass before exporting class members to prevent
// infinite recursion in case of cyclic references
__pClass =
new PClass(
VmUtils.exportDocComment(docComment),
new SourceLocation(headerSection.getStartLine(), sourceSection.getEndLine()),
VmModifier.export(modifiers, true),
exportedAnnotations,
classInfo,
typeParameters,
properties,
methods);
// if this is not a module class, export this class's module's class first to break the cycle
PClass moduleClass = null;
if (!classInfo.isModuleClass()) {
moduleClass = getModule().getVmClass().export();
}
// then if the cached value is still null, initialize it
if (__pClass != null) {
return __pClass;
}
for (var parameter : typeParameters) {
parameter.initOwner(__pClass);
var exportedAnnotations = new ArrayList<PObject>();
var properties =
CollectionUtils.<String, PClass.Property>newLinkedHashMap(
EconomicMaps.size(declaredProperties));
var methods =
CollectionUtils.<String, PClass.Method>newLinkedHashMap(
EconomicMaps.size(declaredMethods));
// set pClass before exporting class members to prevent
// infinite recursion in case of cyclic references
__pClass =
new PClass(
VmUtils.exportDocComment(docComment),
new SourceLocation(headerSection.getStartLine(), sourceSection.getEndLine()),
VmModifier.export(modifiers, true),
exportedAnnotations,
classInfo,
typeParameters,
properties,
methods,
moduleClass);
for (var parameter : typeParameters) {
parameter.initOwner(__pClass);
}
if (supertypeNode != null) {
assert superclass != null;
__pClass.initSupertype(TypeNode.export(supertypeNode), superclass.export());
}
VmUtils.exportAnnotations(annotations, exportedAnnotations);
for (var property : EconomicMaps.getValues(declaredProperties)) {
if (isClassPropertyDefinition(property)) {
properties.put(property.getName().toString(), property.export(__pClass));
}
}
if (supertypeNode != null) {
assert superclass != null;
__pClass.initSupertype(TypeNode.export(supertypeNode), superclass.export());
}
VmUtils.exportAnnotations(annotations, exportedAnnotations);
for (var property : EconomicMaps.getValues(declaredProperties)) {
if (isClassPropertyDefinition(property)) {
properties.put(property.getName().toString(), property.export(__pClass));
}
}
for (var method : EconomicMaps.getValues(declaredMethods)) {
if (method.isLocal()) continue;
methods.put(method.getName().toString(), method.export(__pClass));
}
for (var method : EconomicMaps.getValues(declaredMethods)) {
if (method.isLocal()) continue;
methods.put(method.getName().toString(), method.export(__pClass));
}
return __pClass;
@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2026 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.
@@ -87,4 +87,9 @@ public final class VmObjectBuilder {
members,
elementCount);
}
public VmTyped toTyped(VmClass clazz) {
return new VmTyped(
VmUtils.createEmptyMaterializedFrame(), clazz.getPrototype(), clazz, members);
}
}
@@ -324,6 +324,22 @@ public class VmPklBinaryEncoder extends AbstractRenderer {
}
}
@Override
public void visitReference(VmReference value) {
try {
packer.packArrayHeader(4);
packCode(PklBinaryCode.REFERENCE);
visit(value.getDomain());
visit(value.getData());
packer.packArrayHeader(value.getPath().size());
for (var access : value.getPath()) {
visit(access);
}
} catch (IOException e) {
throw PklBugException.unreachableCode();
}
}
@Override
protected void visitEntryKey(Object key, boolean isFirst) {
visit(key);
@@ -0,0 +1,484 @@
/*
* Copyright © 2025-2026 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.runtime;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.DirectCallNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.pkl.core.Composite;
import org.pkl.core.PClass;
import org.pkl.core.PClassInfo;
import org.pkl.core.PType;
import org.pkl.core.Reference;
import org.pkl.core.TypeAlias;
import org.pkl.core.util.paguro.RrbTree;
import org.pkl.core.util.paguro.RrbTree.ImRrbt;
public final class VmReference extends VmValue {
private final VmTyped domain;
private final Object data;
private final ImRrbt<VmTyped> path;
// candidate types can only be: PType.Class, PType.Alias (only preservedAliasTypes),
// PType.StringLiteral, PType.UNKNOWN, or PType.Union (containing only the previous; flattened)
private final PType referentType;
private boolean forced = false;
private static VmTyped newAccess(@Nullable String property, @Nullable Object key) {
return new VmObjectBuilder()
.addProperty(Identifier.PROPERTY, property == null ? VmNull.withoutDefault() : property)
.addProperty(Identifier.KEY, key == null ? VmNull.withoutDefault() : key)
.toTyped(RefModule.getAccessClass());
}
@TruffleBoundary
public VmReference(VmTyped domain, VmClass clazz, Object data) {
this(
domain,
data,
RrbTree.empty(),
normalizeTypes(new PType.Class(clazz.export()), clazz.getModule().getVmClass().export()));
}
public VmReference(VmTyped domain, Object data, ImRrbt<VmTyped> path, PType referentType) {
this.domain = domain;
this.data = data;
this.referentType = referentType;
this.path = path;
}
public VmTyped getDomain() {
return domain;
}
public Object getData() {
return data;
}
public List<VmTyped> getPath() {
return path;
}
public PType getReferentType() {
return referentType;
}
// simplifies a type by:
// * erasing constraints
// * transforming T? into T|Null
// * dereferencing aliases (except for well-known stdlib alias types)
// * flattening unions
// * when moduleClass is supplied, replace PType.MODULE with appropriate PType.Class
// * drop PType.Function and PType.TypeVariable
private static PType normalizeTypes(PType type, PClass moduleClass) {
var types = new HashSet<PType>();
normalizeTypes(type, moduleClass, types);
return minimizeTypes(types);
}
private static PType minimizeTypes(Set<PType> types) {
if (types.size() == 1) return types.iterator().next();
// optimization: unknown allows all references, erase all candidates to only unknown
if (types.contains(PType.UNKNOWN)) return PType.UNKNOWN;
// optimization: All allows all references, erase all candidates to only All
if (containsClass(types, BaseModule.getAnyClass().export()))
return new PType.Class(BaseModule.getAnyClass().export());
var typesList = new ArrayList<>(types);
typesList.sort(Comparator.comparing(Object::toString));
return new PType.Union(typesList);
}
private static void normalizeTypes(PType type, PClass moduleClass, Set<PType> result) {
if (type == PType.UNKNOWN || type == PType.NOTHING || type instanceof PType.StringLiteral) {
result.add(type);
} else if (type instanceof PType.Class clazz) {
if (clazz.getTypeArguments().isEmpty()) {
// if a generic type is used without type arguments, it needs to be normalized so all args
// are unknown; i.e. with bare List/Map/etc. type annotations (via FinalClassTypeNode).
var typeParameterCount = clazz.getPClass().getTypeParameters().size();
result.add(
typeParameterCount == 0
? clazz
: new PType.Class(
clazz.getPClass(), Collections.nCopies(typeParameterCount, PType.UNKNOWN)));
} else {
var typeArgs = new ArrayList<PType>(clazz.getTypeArguments().size());
for (var arg : clazz.getTypeArguments()) {
typeArgs.add(normalizeTypes(arg, moduleClass));
}
result.add(new PType.Class(clazz.getPClass(), typeArgs));
}
} else if (type instanceof PType.Nullable nullable) {
normalizeTypes(nullable.getBaseType(), moduleClass, result);
result.add(new PType.Class(BaseModule.getNullClass().export()));
} else if (type instanceof PType.Constrained constrained) {
normalizeTypes(constrained.getBaseType(), moduleClass, result);
} else if (type instanceof PType.Alias alias) {
if (isPreservedTypeAlias(alias.getTypeAlias())) {
result.add(alias);
} else {
normalizeTypes(alias.getAliasedType(), alias.getTypeAlias().getModuleClass(), result);
}
} else if (type instanceof PType.Union union) {
for (var t : union.getElementTypes()) {
normalizeTypes(t, moduleClass, result);
}
} else if (type == PType.MODULE) {
result.add(new PType.Class(moduleClass));
}
}
private static Iterable<PType> iterateTypes(PType t) {
if (t instanceof PType.Union union) return union.getElementTypes();
return Collections.singleton(t);
}
public @Nullable VmReference withPropertyAccess(Identifier property) {
var propString = property.toString();
return withAccess(
(t, candidates) -> getCandidatePropertyType(t, propString, candidates),
() -> newAccess(property.toString(), null));
}
public @Nullable VmReference withSubscriptAccess(Object key) {
return withAccess(
(t, candidates) -> getCandidateSubscriptType(t, key, candidates),
() -> newAccess(null, key));
}
@TruffleBoundary
private @Nullable VmReference withAccess(
BiConsumer<PType, Set<PType>> checkCandidate, Supplier<VmTyped> makeAccess) {
Set<PType> candidates = new HashSet<>();
for (var t : iterateTypes(referentType)) {
checkCandidate.accept(t, candidates);
}
if (candidates.isEmpty()) {
return null; // no valid access found
}
return new VmReference(domain, data, path.append(makeAccess.get()), minimizeTypes(candidates));
}
@SuppressWarnings("DuplicatedCode")
private static void getCandidatePropertyType(PType type, String property, Set<PType> result) {
if (type == PType.UNKNOWN) {
result.add(type);
return;
}
// restriction: only class types can have their properties referenced
if (!(type instanceof PType.Class clazz)) return;
// restriction: cannot reference properties of external classes
if (clazz.getPClass().isExternal()) return;
if (clazz.getPClass().getInfo() == PClassInfo.Dynamic) {
// restriction: cannot reference Dynamic.default
if (!property.equals("default")) result.add(PType.UNKNOWN);
return;
}
// restriction: cannot reference Listing/Mapping.default
if (clazz.getPClass().getInfo() == PClassInfo.Listing
|| clazz.getPClass().getInfo() == PClassInfo.Mapping) {
return;
}
// restriction: cannot reference Module.output.
// generalized: properties originally defined in external classes; the only extant example.
// This is implemented specifically because this is the only case where an external class
// containing a property can be subclassed.
// And this can't check prop.getOwner().isExternal() because fully overriding the property with
// a new type annotation means the owner isn't Module.
if (clazz.getPClass().isSubclassOf(BaseModule.getModuleClass().export())
&& property.equals("output")) return;
var prop = clazz.getPClass().getAllProperties().get(property);
// restriction: cannot reference external properties
if (prop == null || prop.isExternal()) {
return;
}
normalizeTypes(prop.getType(), clazz.getPClass().getModuleClass(), result);
}
@SuppressWarnings("DuplicatedCode")
private static void getCandidateSubscriptType(PType type, Object key, Set<PType> result) {
if (type == PType.UNKNOWN) {
result.add(type);
return;
}
if (!(type instanceof PType.Class clazz)) {
return;
}
if (clazz.getPClass().getInfo() == PClassInfo.Dynamic) {
result.add(PType.UNKNOWN);
return;
}
if (clazz.getPClass().getInfo() == PClassInfo.Listing
|| clazz.getPClass().getInfo() == PClassInfo.List) {
if (key instanceof Long) {
normalizeTypes(clazz.getTypeArguments().get(0), clazz.getPClass().getModuleClass(), result);
}
return;
}
if (clazz.getPClass().getInfo() == PClassInfo.Mapping
|| clazz.getPClass().getInfo() == PClassInfo.Map) {
var typeArgs = clazz.getTypeArguments();
var keyTypes = normalizeTypes(typeArgs.get(0), clazz.getPClass().getModuleClass());
for (var kt : iterateTypes(keyTypes)) {
if (kt == PType.UNKNOWN
|| (kt instanceof PType.Class klazz
&& klazz.getPClass().getInfo() == PClassInfo.forValue(VmValue.export(key)))
|| (kt instanceof PType.StringLiteral stringLiteral
&& stringLiteral.getLiteral().equals(key))) {
normalizeTypes(typeArgs.get(1), clazz.getPClass().getModuleClass(), result);
return;
}
}
}
}
/**
* Tells if this reference's referent type is a subtype of {@code type}. Does not check domain.
*/
public boolean referentTypeIsSubtypeOf(PType type, PClass moduleClass) {
// fast path: if referent is unknown it can match any type check
if (referentType == PType.UNKNOWN) {
return true;
}
var checkType = normalizeTypes(type, moduleClass);
// fast path: short circuit if any referent is accepted
if (checkType == PType.UNKNOWN || isClass(checkType, BaseModule.getAnyClass().export())) {
return true;
}
// fast path: short circuit if nothing is accepted
if (checkType == PType.NOTHING) {
return false;
}
return isSubtype(referentType, checkType);
}
private static boolean containsClass(Set<PType> types, PClass pClass) {
for (var t : types) {
if (isClass(t, pClass)) return true;
}
return false;
}
private static boolean isClass(PType t, PClass pClass) {
return t instanceof PType.Class clazz && clazz.getPClass() == pClass;
}
private static boolean isSubtype(PType a, PType b) {
// checks if A is a subtype of B
// cases (A -> B)
// * A == B
// * StringLiteral -> StringLiteral: if literals are the same
// * StringLiteral -> Class: B is String
// * Char Alias -> Char Alias, StringLiteral (known single character)
// * Int Alias -> Class: B is a subtype of Number (Int|Float|Number)
// * Int Alias -> Alias
// * same alias
// * Int8 is Int16|Int32
// * Int16 is Int32
// * UInt8 is Int16|Int32|Uint16|UInt32|UInt
// * UInt16 is Int32|UInt32|UInt
// * UInt32 is UInt
// * Class -> Class: if same class or A is a subclass of B
// * if type args are present, must have equal number of them
// * for each pair of type args, check variance
// * invariant: A_i must be identical to B_i
// * covariant: A_i must be a subtype of B_i
// * contravariant: B_i must be a subtype of A_i
// * Union -> Union: Each elem of A must be a subtype of at least one elem of B
// * Non-union -> Union: A must be a subtype of at least one elem of B
if (a == b) return true;
if (a instanceof PType.StringLiteral aStr) {
if (b instanceof PType.StringLiteral bStr) {
return aStr.getLiteral().equals(bStr.getLiteral());
} else if (b instanceof PType.Class bClass) {
return bClass.getPClass() == BaseModule.getStringClass().export();
}
} else if (a instanceof PType.Alias aAlias) {
var aa = aAlias.getTypeAlias();
if (isIntTypeAlias(aa)) {
// special casing for stdlib Int typealiases
if (b instanceof PType.Class bClass) {
// A is an int alias, B is a Number (sub)class
return bClass.getPClass().isSubclassOf(BaseModule.getNumberClass().export());
} else if (b instanceof PType.Alias bAlias) {
var bb = bAlias.getTypeAlias();
if (aa == bb) {
return true;
}
if (aa == BaseModule.getInt8TypeAlias().export()) {
return bb == BaseModule.getInt16TypeAlias().export()
|| bb == BaseModule.getInt32TypeAlias().export();
} else if (aa == BaseModule.getInt16TypeAlias().export()) {
return bb == BaseModule.getInt32TypeAlias().export();
} else if (aa == BaseModule.getUInt8TypeAlias().export()) {
return bb == BaseModule.getInt16TypeAlias().export()
|| bb == BaseModule.getInt32TypeAlias().export()
|| bb == BaseModule.getUInt16TypeAlias().export()
|| bb == BaseModule.getUInt32TypeAlias().export()
|| bb == BaseModule.getUIntTypeAlias().export();
} else if (aa == BaseModule.getUInt16TypeAlias().export()) {
return bb == BaseModule.getInt32TypeAlias().export()
|| bb == BaseModule.getUInt32TypeAlias().export()
|| bb == BaseModule.getUIntTypeAlias().export();
} else if (aa == BaseModule.getUInt32TypeAlias().export()) {
return bb == BaseModule.getUIntTypeAlias().export();
}
}
}
} else if (a instanceof PType.Class aClass && b instanceof PType.Class bClass) {
if (!aClass.getPClass().isSubclassOf(bClass.getPClass())) {
return false;
}
var aArgs = aClass.getTypeArguments();
var bArgs = bClass.getTypeArguments();
var bParams = bClass.getPClass().getTypeParameters();
if (aArgs.size() != bArgs.size()) {
return false;
}
// check variance of type args pairwise
for (var i = 0; i < aArgs.size(); i++) {
if (!switch (bParams.get(i).getVariance()) {
case INVARIANT -> aArgs.get(i) == bArgs.get(i);
case COVARIANT -> isSubtype(aArgs.get(i), bArgs.get(i));
case CONTRAVARIANT -> isSubtype(bArgs.get(i), aArgs.get(i));
}) {
return false;
}
}
return true;
} else if (b instanceof PType.Union bUnion) {
if (a instanceof PType.Union aUnion) {
a:
for (var aElem : aUnion.getElementTypes()) {
for (var bElem : bUnion.getElementTypes()) {
if (isSubtype(aElem, bElem)) continue a;
}
return false;
}
return true;
} else {
for (var bElem : bUnion.getElementTypes()) {
if (isSubtype(a, bElem)) return true;
}
}
}
return false;
}
private static boolean isIntTypeAlias(TypeAlias t) {
return t == BaseModule.getInt8TypeAlias().export()
|| t == BaseModule.getInt16TypeAlias().export()
|| t == BaseModule.getInt32TypeAlias().export()
|| t == BaseModule.getUInt8TypeAlias().export()
|| t == BaseModule.getUInt16TypeAlias().export()
|| t == BaseModule.getUInt32TypeAlias().export()
|| t == BaseModule.getUIntTypeAlias().export();
}
private static boolean isPreservedTypeAlias(TypeAlias t) {
return isIntTypeAlias(t);
}
@Override
public VmClass getVmClass() {
return RefModule.getReferenceClass();
}
@Override
public void force(boolean allowUndefinedValues) {
if (forced) return;
forced = true;
domain.force(allowUndefinedValues);
VmValue.force(data, allowUndefinedValues);
for (var elem : path) {
elem.force(allowUndefinedValues);
}
}
@Override
public Reference export() {
var pathList = new ArrayList<Composite>(path.size());
for (var elem : path) {
pathList.add(elem.export());
}
return new Reference(domain.export(), VmValue.export(data), pathList, getReferentType());
}
public PType exportType() {
return new PType.Class(
RefModule.getReferenceClass().export(),
new PType.Class(domain.getVmClass().export()),
getReferentType());
}
@Override
public void accept(VmValueVisitor visitor) {
visitor.visitReference(this);
}
@Override
public <T> T accept(VmValueConverter<T> converter, Iterable<Object> path) {
return converter.convertReference(this, path);
}
@Override
public boolean equals(@Nullable Object o) {
if (o == this) return true;
if (!(o instanceof VmReference that)) {
return false;
}
return domain.equals(that.domain)
&& data.equals(that.data)
&& path.equals(that.path)
&& referentType.equals(that.referentType);
}
@Override
public int hashCode() {
int result = domain.hashCode();
result = 31 * result + data.hashCode();
result = 31 * result + path.hashCode();
result = 31 * result + referentType.hashCode();
return result;
}
// in-language calls _should_ all go through `ToStringNode`.
// however, some calls escape through to here currently (e.g. `Listing.join`).
@Override
public String toString() {
var toStringMethod = getVmClass().getDeclaredMethod(Identifier.TO_STRING);
assert toStringMethod != null;
var callNode = DirectCallNode.create(toStringMethod.getCallTarget());
return (String) callNode.call(this, getVmClass().getPrototype());
}
}
@@ -32,6 +32,7 @@ import org.pkl.core.TypeParameter;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.TypeNode.ConstrainedTypeNode;
import org.pkl.core.ast.type.TypeNode.ReferenceTypeNode;
import org.pkl.core.ast.type.TypeNode.TypeVariableNode;
import org.pkl.core.ast.type.TypeNode.UnknownTypeNode;
import org.pkl.core.util.LateInit;
@@ -177,7 +178,8 @@ public final class VmTypeAlias extends VmValue {
}
@TruffleBoundary
public TypeNode instantiate(TypeNode[] typeArgumentNodes) {
public TypeNode instantiate(
TypeNode[] typeArgumentNodes, SourceSection typeAliasTypeNodeSourceSection) {
// Cloning the type node means that the entire type check remains within a single root node,
// which should be good for interpreted and compiled performance alike:
// * Fewer root nodes to call
@@ -199,6 +201,13 @@ public final class VmTypeAlias extends VmValue {
}
return true;
});
clone.accept(
node -> {
if (node instanceof ReferenceTypeNode referenceTypeNode) {
referenceTypeNode.validateTypeArguments(typeAliasTypeNodeSourceSection);
}
return true;
});
return clone;
}
@@ -228,7 +237,8 @@ public final class VmTypeAlias extends VmValue {
simpleName,
getModuleName(),
qualifiedName,
typeParameters);
typeParameters,
module.getVmClass().export());
for (var parameter : typeParameters) {
parameter.initOwner(__pTypeAlias);
@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2026 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.
@@ -40,6 +40,7 @@ import com.oracle.truffle.api.dsl.TypeSystem;
VmRegex.class,
VmTypeAlias.class,
VmObjectLike.class,
VmValue.class
VmReference.class,
VmValue.class,
})
public class VmTypes {}
@@ -85,6 +85,8 @@ public interface VmValueConverter<T> {
T convertFunction(VmFunction value, Iterable<Object> path);
T convertReference(VmReference value, Iterable<Object> path);
/** Returns with an empty identifier if the second value is a RenderDirective */
Pair<Identifier, T> convertProperty(ClassProperty property, Object value, Iterable<Object> path);
@@ -271,6 +271,30 @@ public final class VmValueRenderer {
append("null");
}
@Override
public void visitReference(VmReference value) {
contexts.push(Context.EXPLICIT);
append("Reference(");
visit(value.getDomain());
append(", ");
append(value.getReferentType());
append(", ");
visit(value.getData());
append(")");
for (var elem : value.getPath()) {
var property = VmUtils.readMember(elem, Identifier.PROPERTY);
if (property instanceof String propName) {
append(".");
writeIdentifier(propName);
} else {
append("[");
visit(VmUtils.readMember(elem, Identifier.KEY));
append("]");
}
}
contexts.pop();
}
private void append(Object value) {
builder.append(value);
checkLengthLimit();
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2026 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.
@@ -60,6 +60,8 @@ public interface VmValueVisitor {
void visitFunction(VmFunction value);
void visitReference(VmReference value);
default void visit(Object value) {
Objects.requireNonNull(value, "Value to be visited must be non-null.");
@@ -34,6 +34,7 @@ import org.pkl.core.runtime.VmListing;
import org.pkl.core.runtime.VmMap;
import org.pkl.core.runtime.VmMapping;
import org.pkl.core.runtime.VmNull;
import org.pkl.core.runtime.VmReference;
import org.pkl.core.runtime.VmSet;
import org.pkl.core.runtime.VmTypeAlias;
import org.pkl.core.runtime.VmTyped;
@@ -397,6 +398,11 @@ public abstract class AbstractRenderer implements VmValueVisitor {
currSourceSection = prevSourceSection;
}
@Override
public void visitReference(VmReference value) {
cannotRenderTypeAddConverter(value);
}
protected void cannotRenderTypeAddConverter(VmValue value) {
var builder =
new VmExceptionBuilder()
@@ -46,6 +46,7 @@ public final class PklConverter implements VmValueConverter<Object> {
private final @Nullable VmFunction nullConverter;
private final @Nullable VmFunction classConverter;
private final @Nullable VmFunction typeAliasConverter;
private final @Nullable VmFunction referenceConverter;
private PklConverter(
VmMapping converters, VmMapping convertPropertyTransformers, Object rendererOrParser) {
@@ -76,6 +77,7 @@ public final class PklConverter implements VmValueConverter<Object> {
nullConverter = typeConverters.get(BaseModule.getNullClass());
classConverter = typeConverters.get(BaseModule.getClassClass());
typeAliasConverter = typeConverters.get(BaseModule.getTypeAliasClass());
referenceConverter = typeConverters.get(RefModule.getReferenceClass());
}
public static final PklConverter NOOP =
@@ -199,6 +201,11 @@ public final class PklConverter implements VmValueConverter<Object> {
return doConvert(value, path, nullConverter);
}
@Override
public Object convertReference(VmReference value, Iterable<Object> path) {
return doConvert(value, path, referenceConverter);
}
@Override
public Pair<Identifier, Object> convertProperty(
ClassProperty property, Object value, Iterable<Object> path) {
@@ -59,6 +59,7 @@ import org.pkl.core.runtime.VmMap;
import org.pkl.core.runtime.VmMapping;
import org.pkl.core.runtime.VmNull;
import org.pkl.core.runtime.VmPair;
import org.pkl.core.runtime.VmReference;
import org.pkl.core.runtime.VmRegex;
import org.pkl.core.runtime.VmSet;
import org.pkl.core.runtime.VmTyped;
@@ -554,6 +555,12 @@ public final class RendererNodes {
builder.append(value);
}
@Override
public void visitReference(VmReference value) {
writePropertyName();
builder.append(value);
}
/**
* Resolves types for the purpose of protobuf rendering. "Sees through" nullable types and type
* aliases, simplifies variations of {@code Int} and {@code String} types (literate string
@@ -0,0 +1,33 @@
/*
* Copyright © 2026 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.stdlib.ref;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.pkl.core.runtime.VmClass;
import org.pkl.core.runtime.VmReference;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.stdlib.ExternalMethod3Node;
public class RefNodes {
public abstract static class Reference extends ExternalMethod3Node {
@Specialization
protected VmReference eval(
VirtualFrame frame, VmTyped self, VmTyped domain, VmClass clazz, Object data) {
return new VmReference(domain, clazz, data);
}
}
}
@@ -0,0 +1,46 @@
/*
* Copyright © 2026 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.stdlib.ref;
import com.oracle.truffle.api.dsl.Specialization;
import org.pkl.core.runtime.VmList;
import org.pkl.core.runtime.VmReference;
import org.pkl.core.stdlib.ExternalMethod0Node;
public class ReferenceNodes {
private ReferenceNodes() {}
public abstract static class getDomain extends ExternalMethod0Node {
@Specialization
protected Object eval(VmReference self) {
return self.getDomain();
}
}
public abstract static class getData extends ExternalMethod0Node {
@Specialization
protected Object eval(VmReference self) {
return self.getData();
}
}
public abstract static class getPath extends ExternalMethod0Node {
@Specialization
protected VmList eval(VmReference self) {
return VmList.create(self.getPath());
}
}
}
@@ -0,0 +1,4 @@
@NullMarked
package org.pkl.core.stdlib.ref;
import org.jspecify.annotations.NullMarked;
@@ -33,6 +33,7 @@ public enum PklBinaryCode {
TYPEALIAS((byte) 0x0D),
FUNCTION((byte) 0x0E),
BYTES((byte) 0x0F),
REFERENCE((byte) 0x20),
PROPERTY((byte) 0x10),
ENTRY((byte) 0x11),
@@ -65,6 +66,7 @@ public enum PklBinaryCode {
case 0x0D -> PklBinaryCode.TYPEALIAS;
case 0x0E -> PklBinaryCode.FUNCTION;
case 0x0F -> PklBinaryCode.BYTES;
case 0x20 -> PklBinaryCode.REFERENCE;
case 0x10 -> PklBinaryCode.PROPERTY;
case 0x11 -> PklBinaryCode.ENTRY;
@@ -286,11 +286,6 @@ External members cannot have a body.
abstractMemberCannotHaveBody=\
Abstract members cannot have a body.
abstractMemberInNonAbstractClass=\
Cannot define an abstract member in a non-abstract class.\n\
\n\
A member can only be `abstract` if its enclosing class is also `abstract`.
methodNotDefined1=\
Method `{0}` is not defined for argument type `{1}`.
@@ -390,6 +385,9 @@ Cannot find property `{0}` in object of type `{1}`.\n\
Did you mean any of the following?\n\
{2}
cannotFindPropertyInReference=\
Cannot find property `{0}` in object of type `{1}`.
cannotFindMethodInScope=\
Cannot find method `{0}`.
@@ -861,6 +859,9 @@ Package URIs must have an authority component.\n\
\n\
For example, `example.com` in URI `project://example.com/my/package@1.0.0`.
invalidRelativePathInPackageUri=\
Package URIs cannot contain `..` segments in the URI's "path" component.
unexpectedChecksumInPackageUri=\
Did not expect to find a checksum component in this package URI.
@@ -1176,3 +1177,6 @@ Cannot follow redirect from ''https:'' URL to ''http:'' URL.\
\n\
HTTP Request: `GET {0}`\n\
Redirected to: `{1}`
invalidReferenceTypeAnnotationWithConstraint=\
`Reference` referent type argument may not include type constraints.
@@ -0,0 +1 @@
foo: String
@@ -0,0 +1,7 @@
open module ReferencedModuleWithOutputOverride
foo: String
hidden output: ModuleOutput = new {
text = foo
}
@@ -0,0 +1,134 @@
import "pkl:math"
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String =
let (data = reference.getData())
let (root = if (data is Resource) data.name else data.toString())
let (
path =
reference
.getPath()
.map((elem) -> if (elem.isProperty) ".\(elem.property)" else "[\(elem.key)]")
)
"${\(root)\(path.join(""))}"
}
local const d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
abstract class Resource {
name: String
hidden fixed $: Ref<Resource>
}
class A extends Resource {
id: String
hidden outputs: AProperties
hidden fixed $: Ref<A> = ref.Reference(d, A, this)
}
/// Test doc comment
class AProperties {
foo: Int
someMapping: Mapping<String, Int?>
someMap: Map<MapKey, Int>
someListing: Listing<Int>
someList: List<Int>
nonInt: String
}
class B extends Resource {
id: String
hidden outputs: BProperties
hidden fixed $: Ref<B> = ref.Reference(d, B, this)
}
class BProperties {
foo: String
someMapping: Mapping<String, String?>
someMap: Map<MapKey, String>
someListing: Listing<String>
someList: List<String>
nonString: Int
}
class MapKey {
k: Int
function toString(): String = "key:\(k)"
}
class K {
aId: String | *Ref<String>?
bId: String | *Ref<String>?
aProperties: AProperties | *Ref<AProperties>?
bProperties: BProperties | *Ref<BProperties>?
aValues: Listing<Int | Ref<Int?>>?
bValues: Listing<String | Ref<String?>>?
splitUnion: Ref<Listing<String> | Listing<Int>>?
}
a: A = new {
name = "a"
id = "some-a-value"
}
b: B = new {
name = "b"
id = "some-b-value"
}
aOrB: A | B = a
aRef: Ref<A> = a.$
bRef: Ref<B> = b.$
unknownRef: Ref<A | B> = aRef
unknownRef2: Ref<A> | Ref<B> = aRef
unknownRef3: Ref<B | A> = aOrB.$
k: K = new {
aId = aRef.id
bId = bRef.id
aProperties = aRef.outputs
bProperties = bRef.outputs
aValues {
aRef.outputs.foo
aRef.outputs.someMapping["key"]
aRef.outputs.someMap[new MapKey { k = 123 }]
aRef.outputs.someListing[0]
aRef.outputs.someList[math.maxInt]
bRef.outputs.nonString
}
bValues {
bRef.outputs.foo
bRef.outputs.someMapping["key"]
bRef.outputs.someMap[new MapKey { k = 123 }]
bRef.outputs.someListing[0]
bRef.outputs.someList[math.maxInt]
aRef.outputs.nonInt
}
}
j: K = new {
local aRef2 = unknownRef as Ref<A>
aProperties = aRef2.outputs
splitUnion = unknownRef.outputs.someListing
}
refInterpolation = "\(aRef.outputs.someListing[1])"
kInterpolation = "\(k)"
aValuesJoined = k.aValues.join("\n")
// ensure that type arguments that are unions are handled correctly
typeArgs = ref.Reference(d, TypeHolder, null).prop as Ref<Listing<Number | Boolean | String>>
class TypeHolder {
prop: Listing<String | Boolean | Number>
}
output {
renderer {
converters {
[ref.Reference] = (it) -> it.toString()
}
}
}
@@ -1,5 +0,0 @@
class Foo {
abstract bar: Int
}
res = new Foo { bar = 5 }
@@ -1 +0,0 @@
abstract foo: Int
@@ -1,5 +0,0 @@
class Foo {
abstract function bar(): Int
}
res = new Foo {}
@@ -0,0 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
test = ref.Reference(d, String, "") as Ref<Int>
@@ -0,0 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
typealias Ref<T> = ref.Reference<D, T>
local d: D = new {}
test = ref.Reference(d, String, "") as Ref<Listing<String(true)>>
@@ -0,0 +1,12 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
typealias Ref<T> = ref.Reference<D, T>
local d: D = new {}
test = ref.Reference(d, String, "") as Ref<Alias1?>
typealias Alias1 = Int | Alias2?
typealias Alias2 = String(length < 5)
@@ -0,0 +1,12 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
typealias RefAlias1 = ref.Reference<D, Alias1?>
local d: D = new {}
test = ref.Reference(d, String, "") as RefAlias1
typealias Alias1 = Int | Alias2?
typealias Alias2 = String(length < 5)
@@ -0,0 +1,8 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, Mapping, "").default
@@ -0,0 +1,8 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, Listing, "").default
@@ -0,0 +1,8 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, Dynamic, "").default
@@ -0,0 +1,10 @@
import "pkl:ref"
import ".../input-helper/errors/ReferencedModule.pkl"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, ReferencedModule.getClass(), "").output
@@ -0,0 +1,10 @@
import "pkl:ref"
import ".../input-helper/errors/ReferencedModuleWithOutputOverride.pkl"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, ReferencedModuleWithOutputOverride.getClass(), "").output
@@ -0,0 +1,12 @@
import "pkl:ref"
import ".../input-helper/errors/ReferencedModuleWithOutputOverride.pkl"
class ModuleSubclass extends ReferencedModuleWithOutputOverride
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, ModuleSubclass, "").output
@@ -0,0 +1,12 @@
import "pkl:ref"
class A {
foo: String | Int | Boolean
}
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, A, "").foo as ref.Reference<D, Int | Boolean>
@@ -0,0 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
test = ref.Reference(d, String, "") as Ref<Boolean | Int>
@@ -0,0 +1,15 @@
import "pkl:ref"
class A {
foo: String | Int | Boolean
}
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
local test = ref.Reference(d, A, "").foo
testInterpolation = "test:\(test)"
// this tests that the interpolation appears in the output when referenceToString throws
@@ -0,0 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
test = ref.Reference(d, String, "") as Ref<Listing<String>>
@@ -0,0 +1,12 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
test = ref.Reference(d, String, "") as Ref<Alias1?>
typealias Alias1 = Int | Alias2?
typealias Alias2 = Boolean
@@ -0,0 +1,14 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
class D2 extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d2: D2 = new {}
test = ref.Reference(d2, String, "") as Ref<String>
@@ -0,0 +1,13 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
class A {
b: String
}
test = ref.Reference(d, String, new A {}).c
@@ -0,0 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
test = ref.Reference(d, List, List())["hi"]
@@ -0,0 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
typealias Ref<T> = ref.Reference<D, T>
local d: D = new {}
test = ref.Reference(d, String, "") as Ref<String(true)>
@@ -0,0 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
}
typealias Ref<T> = ref.Reference<D, T>
local d: D = new {}
test = ref.Reference(d, String, "") as Ref<String(true) | Int>
@@ -0,0 +1,2 @@
// relative path in package URI
res = import("package://localhost:0/%2e%2e/birds@0.5.0#/catalog/Bird.pkl")
@@ -0,0 +1,60 @@
a {
name = "a"
id = "some-a-value"
}
b {
name = "b"
id = "some-b-value"
}
aOrB {
name = "a"
id = "some-a-value"
}
aRef = "${a}"
bRef = "${b}"
unknownRef = "${a}"
unknownRef2 = "${a}"
unknownRef3 = "${a}"
k {
aId = "${a.id}"
bId = "${b.id}"
aProperties = "${a.outputs}"
bProperties = "${b.outputs}"
aValues {
"${a.outputs.foo}"
"${a.outputs.someMapping[key]}"
"${a.outputs.someMap[key:123]}"
"${a.outputs.someListing[0]}"
"${a.outputs.someList[9223372036854775807]}"
"${b.outputs.nonString}"
}
bValues {
"${b.outputs.foo}"
"${b.outputs.someMapping[key]}"
"${b.outputs.someMap[key:123]}"
"${b.outputs.someListing[0]}"
"${b.outputs.someList[9223372036854775807]}"
"${a.outputs.nonInt}"
}
splitUnion = null
}
j {
aId = null
bId = null
aProperties = "${a.outputs}"
bProperties = null
aValues = null
bValues = null
splitUnion = "${a.outputs.someListing}"
}
refInterpolation = "${a.outputs.someListing[1]}"
kInterpolation = "new K { aId = Reference(new D {}, String, new A { name = \"a\"; id = \"some-a-value\" }).id; bId = Reference(new D {}, String, new B { name = \"b\"; id = \"some-b-value\" }).id; aProperties = Reference(new D {}, reference#AProperties, new A { name = \"a\"; id = \"some-a-value\" }).outputs; bProperties = Reference(new D {}, reference#BProperties, new B { name = \"b\"; id = \"some-b-value\" }).outputs; aValues { Reference(new D {}, Int, new A { name = \"a\"; id = \"some-a-value\" }).outputs.foo; Reference(new D {}, Int | Null, new A { name = \"a\"; id = \"some-a-value\" }).outputs.someMapping[\"key\"]; Reference(new D {}, Int, new A { name = \"a\"; id = \"some-a-value\" }).outputs.someMap[new MapKey { k = 123 }]; Reference(new D {}, Int, new A { name = \"a\"; id = \"some-a-value\" }).outputs.someListing[0]; Reference(new D {}, Int, new A { name = \"a\"; id = \"some-a-value\" }).outputs.someList[9223372036854775807]; Reference(new D {}, Int, new B { name = \"b\"; id = \"some-b-value\" }).outputs.nonString }; bValues { Reference(new D {}, String, new B { name = \"b\"; id = \"some-b-value\" }).outputs.foo; Reference(new D {}, Null | String, new B { name = \"b\"; id = \"some-b-value\" }).outputs.someMapping[\"key\"]; Reference(new D {}, String, new B { name = \"b\"; id = \"some-b-value\" }).outputs.someMap[new MapKey { k = 123 }]; Reference(new D {}, String, new B { name = \"b\"; id = \"some-b-value\" }).outputs.someListing[0]; Reference(new D {}, String, new B { name = \"b\"; id = \"some-b-value\" }).outputs.someList[9223372036854775807]; Reference(new D {}, String, new A { name = \"a\"; id = \"some-a-value\" }).outputs.nonInt }; splitUnion = null }"
aValuesJoined = """
${a.outputs.foo}
${a.outputs.someMapping[key]}
${a.outputs.someMap[key:123]}
${a.outputs.someListing[0]}
${a.outputs.someList[9223372036854775807]}
${b.outputs.nonString}
"""
typeArgs = "${null.prop}"
@@ -1,8 +0,0 @@
–– Pkl Error ––
Cannot define an abstract member in a non-abstract class.
x | abstract bar: Int
^^^^^^^^
at abstractMemberInNonAbstractClass#Foo (file:///$snippetsDir/input/errors/abstractMemberInNonAbstractClass.pkl)
A member can only be `abstract` if its enclosing class is also `abstract`.
@@ -1,8 +0,0 @@
–– Pkl Error ––
Cannot define an abstract member in a non-abstract class.
x | abstract foo: Int
^^^^^^^^
at abstractMemberInNonAbstractModule (file:///$snippetsDir/input/errors/abstractMemberInNonAbstractModule.pkl)
A member can only be `abstract` if its enclosing class is also `abstract`.
@@ -1,8 +0,0 @@
–– Pkl Error ––
Cannot define an abstract member in a non-abstract class.
x | abstract function bar(): Int
^^^^^^^^
at abstractMethodInNonAbstractClass#Foo (file:///$snippetsDir/input/errors/abstractMethodInNonAbstractClass.pkl)
A member can only be `abstract` if its enclosing class is also `abstract`.
@@ -20,6 +20,7 @@ pkl:pklbinary
pkl:platform
pkl:Project
pkl:protobuf
pkl:ref
pkl:reflect
pkl:release
pkl:semver
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `pkl.ref#Reference<reference1#D, Int>`, but got type `pkl.ref#Reference<reference1#D, String>`.
Value: Reference(new D {}, String, "")
x | typealias Ref<T> = ref.Reference<D, T>
^^^^^^^^^^^^^^^^^^^
at reference1#test (file:///$snippetsDir/input/errors/reference1.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
`Reference` referent type argument may not include type constraints.
x | test = ref.Reference(d, String, "") as Ref<Listing<String(true)>>
^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference10#test (file:///$snippetsDir/input/errors/reference10.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
`Reference` referent type argument may not include type constraints.
x | test = ref.Reference(d, String, "") as Ref<Alias1?>
^^^^^^^^^^^^
at reference11#test (file:///$snippetsDir/input/errors/reference11.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
`Reference` referent type argument may not include type constraints.
x | typealias RefAlias1 = ref.Reference<D, Alias1?>
^^^^^^^^^^^^^^^^^^^^^^^^^
at reference12#RefAlias1 (file:///$snippetsDir/input/errors/reference12.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
Cannot find property `default` in object of type `pkl.ref#Reference<reference13#D, Mapping<unknown, unknown>>`.
x | test = ref.Reference(d, Mapping, "").default
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference13#test (file:///$snippetsDir/input/errors/reference13.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
Cannot find property `default` in object of type `pkl.ref#Reference<reference14#D, Listing<unknown>>`.
x | test = ref.Reference(d, Listing, "").default
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference14#test (file:///$snippetsDir/input/errors/reference14.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
Cannot find property `default` in object of type `pkl.ref#Reference<reference15#D, Dynamic>`.
x | test = ref.Reference(d, Dynamic, "").default
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference15#test (file:///$snippetsDir/input/errors/reference15.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
Cannot find property `output` in object of type `pkl.ref#Reference<reference16#D, ReferencedModule>`.
xx | test = ref.Reference(d, ReferencedModule.getClass(), "").output
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference16#test (file:///$snippetsDir/input/errors/reference16.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
Cannot find property `output` in object of type `pkl.ref#Reference<reference17#D, ReferencedModuleWithOutputOverride>`.
xx | test = ref.Reference(d, ReferencedModuleWithOutputOverride.getClass(), "").output
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference17#test (file:///$snippetsDir/input/errors/reference17.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
Cannot find property `output` in object of type `pkl.ref#Reference<reference18#D, reference18#ModuleSubclass>`.
xx | test = ref.Reference(d, ModuleSubclass, "").output
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference18#test (file:///$snippetsDir/input/errors/reference18.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `pkl.ref#Reference<reference19#D, Int | Boolean>`, but got type `pkl.ref#Reference<reference19#D, Boolean | Int | String>`.
Value: Reference(new D {}, Boolean | Int | String, "").foo
xx | test = ref.Reference(d, A, "").foo as ref.Reference<D, Int | Boolean>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference19#test (file:///$snippetsDir/input/errors/reference19.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `pkl.ref#Reference<reference2#D, Boolean | Int>`, but got type `pkl.ref#Reference<reference2#D, String>`.
Value: Reference(new D {}, String, "")
x | typealias Ref<T> = ref.Reference<D, T>
^^^^^^^^^^^^^^^^^^^
at reference2#test (file:///$snippetsDir/input/errors/reference2.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,22 @@
–– Pkl Error ––
not supported
x | function renderReference(reference: ref.Reference<D, Any>): String = throw("not supported")
^^^^^^^^^^^^^^^^^^^^^^
at reference20#D.renderReference (file:///$snippetsDir/input/errors/reference20.pkl)
xxx | function toString(): String = getDomain().renderReference(this)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.ref#Reference.toString (pkl:ref)
xx | testInterpolation = "test:\(test)"
^^^^^^^
at reference20#testInterpolation (file:///$snippetsDir/input/errors/reference20.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `pkl.ref#Reference<reference3#D, Listing<String>>`, but got type `pkl.ref#Reference<reference3#D, String>`.
Value: Reference(new D {}, String, "")
x | typealias Ref<T> = ref.Reference<D, T>
^^^^^^^^^^^^^^^^^^^
at reference3#test (file:///$snippetsDir/input/errors/reference3.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `pkl.ref#Reference<reference4#D, reference4#Alias1?>`, but got type `pkl.ref#Reference<reference4#D, String>`.
Value: Reference(new D {}, String, "")
x | typealias Ref<T> = ref.Reference<D, T>
^^^^^^^^^^^^^^^^^^^
at reference4#test (file:///$snippetsDir/input/errors/reference4.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `pkl.ref#Reference<reference5#D, String>`, but got type `pkl.ref#Reference<reference5#D2, String>`.
Value: Reference(new D2 {}, String, "")
x | typealias Ref<T> = ref.Reference<D, T>
^^^^^^^^^^^^^^^^^^^
at reference5#test (file:///$snippetsDir/input/errors/reference5.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
Cannot find property `c` in object of type `pkl.ref#Reference<reference6#D, String>`.
xx | test = ref.Reference(d, String, new A {}).c
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference6#test (file:///$snippetsDir/input/errors/reference6.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,16 @@
–– Pkl Error ––
Operator `[]` is not defined for operand types `pkl.ref#Reference<reference7#D, List<unknown>>` and `String`.
Left operand : Reference(new D {}, List<unknown>, List())
Right operand: "hi"
x | test = ref.Reference(d, List, List())["hi"]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at reference7#test (file:///$snippetsDir/input/errors/reference7.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
`Reference` referent type argument may not include type constraints.
x | test = ref.Reference(d, String, "") as Ref<String(true)>
^^^^^^^^^^^^^^^^^
at reference8#test (file:///$snippetsDir/input/errors/reference8.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,14 @@
–– Pkl Error ––
`Reference` referent type argument may not include type constraints.
x | test = ref.Reference(d, String, "") as Ref<String(true) | Int>
^^^^^^^^^^^^^^^^^^^^^^^
at reference9#test (file:///$snippetsDir/input/errors/reference9.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,16 @@
–– Pkl Error ––
Module URI `package://localhost:0/%2e%2e/birds@0.5.0#/catalog/Bird.pkl` has invalid syntax.
x | res = import("package://localhost:0/%2e%2e/birds@0.5.0#/catalog/Bird.pkl")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at badImport12.error#res (file:///$snippetsDir/input/packages/badImport12.error.pkl)
Package URIs cannot contain `..` segments in the URI's "path" component.
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,55 @@
/*
* Copyright © 2024-2026 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.packages
import java.net.URISyntaxException
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatCode
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
class PackageUriTest {
@Test
fun `rejects percent-encoded dot-dot path segments`() {
val err =
assertThrows<URISyntaxException> {
PackageUri("package://attacker.com/%2e%2e/legit.example.com/legit@1.2.3")
}
assertThat(err).hasMessageContaining("..")
}
@Test
fun `rejects literal dot-dot path segments`() {
assertThrows<URISyntaxException> { PackageUri("package://attacker.com/../legit@1.2.3") }
}
@Test
fun `rejects trailing dot-dot segment`() {
assertThrows<URISyntaxException> { PackageUri("package://attacker.com/foo@1.2.3/%2e%2e") }
}
@Test
fun `accepts a valid package URI`() {
assertThatCode { PackageUri("package://example.com/my/package@1.0.0") }
.doesNotThrowAnyException()
}
@Test
fun `does not reject path segments that merely contain dots`() {
assertThatCode { PackageUri("package://example.com/my..pkg/..foo/bar..@1.0.0") }
.doesNotThrowAnyException()
}
}
@@ -1,5 +1,5 @@
/// Module methods with different modifiers.
abstract module com.package1.moduleMethodModifiers
module com.package1.moduleMethodModifiers
/// Method with `abstract` modifier.
abstract function method1(arg: String): Boolean
@@ -32,7 +32,7 @@
</ul>
<div id="_overview" class="anchor"> </div>
<div id="_declaration" class="member">
<div class="member-signature">abstract module <span class="name-decl">com.package1.moduleMethodModifiers</span></div>
<div class="member-signature">module <span class="name-decl">com.package1.moduleMethodModifiers</span></div>
<div class="doc-comment"><p>Module methods with different modifiers.</p></div>
<dl class="member-info">
<dt class="">Module URI:</dt>
@@ -32,7 +32,7 @@
</ul>
<div id="_overview" class="anchor"> </div>
<div id="_declaration" class="member">
<div class="member-signature">abstract module <span class="name-decl">com.package1.moduleMethodModifiers</span></div>
<div class="member-signature">module <span class="name-decl">com.package1.moduleMethodModifiers</span></div>
<div class="doc-comment"><p>Module methods with different modifiers.</p></div>
<dl class="member-info">
<dt class="">Module URI:</dt>
@@ -20,6 +20,7 @@ import static org.pkl.gradle.utils.PluginUtils.mapAndGetOrNull;
import java.io.File;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
@@ -74,7 +75,12 @@ public abstract class EvalTask extends ModulesTask {
public FileCollection getEffectiveOutputFiles() {
return getObjects()
.fileCollection()
.from(getProviders().provider(() -> nullToEmpty(createCliEvaluator().getOutputFiles())));
.from(
getProviders()
.provider(
() ->
exceptionToEmpty(
() -> nullToEmpty(createCliEvaluator().getOutputFiles()))));
}
@OutputDirectories
@@ -84,13 +90,24 @@ public abstract class EvalTask extends ModulesTask {
.fileCollection()
.from(
getProviders()
.provider(() -> nullToEmpty(createCliEvaluator().getOutputDirectories())));
.provider(
() ->
exceptionToEmpty(
() -> nullToEmpty(createCliEvaluator().getOutputDirectories()))));
}
private static <T> Set<T> nullToEmpty(@Nullable Set<T> set) {
return set == null ? Collections.emptySet() : set;
}
private static <T> Set<T> exceptionToEmpty(Callable<? extends Set<T>> provider) {
try {
return provider.call();
} catch (Exception e) {
return Collections.emptySet();
}
}
@Override
protected void doRunTask() {
//noinspection ResultOfMethodCallIgnored
+54 -66
View File
@@ -2547,36 +2547,6 @@ abstract external class Collection<out Element> extends Any {
predicate: (Element) -> Boolean,
): Pair<Collection<Element>, Collection<Element>>
/// The zero-based index of the first occurrence of [element] in this collection.
///
/// Throws if this collection does not contains [element].
///
/// Facts:
/// ```
/// List(1, 2, 3).indexOf(2) == 1
/// List(1, 2, 2).indexOf(2) == 1
/// import("pkl:test").catch(() -> List(1, 2, 3).indexOf(4))
/// ```
abstract function indexOf(element: Any): Int
/// Same as [indexOf()] but returns [null] if this collection does not contain [element].
abstract function indexOfOrNull(element: Any): Int?
/// The zero-based index of the last occurrence of [element] in this collection.
///
/// Throws if this collection does not contain [element].
///
/// Facts:
/// ```
/// List(1, 2, 3).lastIndexOf(2) == 1
/// List(1, 2, 2).lastIndexOf(2) == 2
/// import("pkl:test").catch(() -> List(1, 2, 3).lastIndexOf(4))
/// ```
abstract function lastIndexOf(element: Any): Int
/// Same as [lastIndexOf()] but returns [null] if this collection does not contain [element].
abstract function lastIndexOfOrNull(element: Any): Int?
/// The first element for which [predicate] returns [true].
///
/// Throws if [predicate] does not hold for any element in this collection.
@@ -2609,42 +2579,6 @@ abstract external class Collection<out Element> extends Any {
/// collection.
abstract function findLastOrNull(predicate: (Element) -> Boolean): Element?
/// The index of the first element for which [predicate] returns [true].
///
/// Throws if [predicate] does not hold for any element in this collection.
///
/// Facts:
/// ```
/// List(5, 6, 7).findIndex((n) -> n.isEven) == 1
/// List(4, 6, 8).findIndex((n) -> n.isEven) == 0
/// import("pkl:test").catch(() -> List(5, 7, 9).findLast((n) -> n.isEven))
/// ```
@AlsoKnownAs { names { "indexWhere" } }
abstract function findIndex(predicate: (Element) -> Boolean): Int
/// Same as [findIndex()] but returns [null] if [predicate] does not hold for any element in this
/// collection.
@AlsoKnownAs { names { "indexWhere" } }
abstract function findIndexOrNull(predicate: (Element) -> Boolean): Int?
/// The index of the last element for which [predicate] returns [true].
///
/// Throws if [predicate] does not hold for any element in this collection.
///
/// Facts:
/// ```
/// List(5, 6, 7).findLastIndex((n) -> n.isEven) == 1
/// List(4, 6, 8).findLastIndex((n) -> n.isEven) == 2
/// import("pkl:test").catch(() -> List(5, 7, 9).findLastIndex((n) -> n.isEven))
/// ```
@AlsoKnownAs { names { "lastIndexWhere" } }
abstract function findLastIndex(predicate: (Element) -> Boolean): Int
/// Same as [findLastIndex()] but returns [null] if [predicate] does not hold for any element in
/// this collection.
@AlsoKnownAs { names { "lastIndexWhere" } }
abstract function findLastIndexOrNull(predicate: (Element) -> Boolean): Int?
/// The number of elements for which [predicate] returns [true].
///
/// Facts:
@@ -3317,16 +3251,70 @@ external class List<out Element> extends Collection<Element> {
external function findLast(predicate: (Element) -> Boolean): Element
external function findLastOrNull(predicate: (Element) -> Boolean): Element?
/// The index of the first element for which [predicate] returns [true].
///
/// Throws if [predicate] does not hold for any element in this collection.
///
/// Facts:
/// ```
/// List(5, 6, 7).findIndex((n) -> n.isEven) == 1
/// List(4, 6, 8).findIndex((n) -> n.isEven) == 0
/// import("pkl:test").catch(() -> List(5, 7, 9).findLast((n) -> n.isEven))
/// ```
@AlsoKnownAs { names { "indexWhere" } }
external function findIndex(predicate: (Element) -> Boolean): Int
/// Same as [findIndex()] but returns [null] if [predicate] does not hold for any element in this
/// collection.
@AlsoKnownAs { names { "indexWhere" } }
external function findIndexOrNull(predicate: (Element) -> Boolean): Int?
/// The index of the last element for which [predicate] returns [true].
///
/// Throws if [predicate] does not hold for any element in this collection.
///
/// Facts:
/// ```
/// List(5, 6, 7).findLastIndex((n) -> n.isEven) == 1
/// List(4, 6, 8).findLastIndex((n) -> n.isEven) == 2
/// import("pkl:test").catch(() -> List(5, 7, 9).findLastIndex((n) -> n.isEven))
/// ```
@AlsoKnownAs { names { "lastIndexWhere" } }
external function findLastIndex(predicate: (Element) -> Boolean): Int
/// Same as [findLastIndex()] but returns [null] if [predicate] does not hold for any element in
/// this collection.
@AlsoKnownAs { names { "lastIndexWhere" } }
external function findLastIndexOrNull(predicate: (Element) -> Boolean): Int?
/// The zero-based index of the first occurrence of [element] in this collection.
///
/// Throws if this collection does not contains [element].
///
/// Facts:
/// ```
/// List(1, 2, 3).indexOf(2) == 1
/// List(1, 2, 2).indexOf(2) == 1
/// import("pkl:test").catch(() -> List(1, 2, 3).indexOf(4))
/// ```
external function indexOf(element: Any): Int
/// Same as [indexOf()] but returns [null] if this collection does not contain [element].
external function indexOfOrNull(element: Any): Int?
/// The zero-based index of the last occurrence of [element] in this collection.
///
/// Throws if this collection does not contain [element].
///
/// Facts:
/// ```
/// List(1, 2, 3).lastIndexOf(2) == 1
/// List(1, 2, 2).lastIndexOf(2) == 2
/// import("pkl:test").catch(() -> List(1, 2, 3).lastIndexOf(4))
/// ```
external function lastIndexOf(element: Any): Int
/// Same as [lastIndexOf()] but returns [null] if this collection does not contain [element].
external function lastIndexOfOrNull(element: Any): Int?
external function count(predicate: (Element) -> Boolean): Int

Some files were not shown because too many files have changed in this diff Show More