Add user generic methods (#1849)

This commit is contained in:
Jen Basch authored and GitHub committed 2026-10-27 07:17:08 -07:00
1 parent 939e844c03
commit 21d278eba5
160 files changed
+2597 -880

No files matched your search

@@ -2100,6 +2100,8 @@ The xref:blog:ROOT:class-as-a-function.adoc[Class-as-a-function] pattern may be
TIP: In most cases, methods without parameters should not be defined.
Instead, use <<fixed-properties,`fixed` properties>> on the module or class.
Methods may also be <<generic-methods,generic>>.
[[modules]]
== Modules
@@ -3282,7 +3284,8 @@ This section discusses language features that are generally more relevant to tem
<<projects,Projects>> +
<<external-readers,External Readers>> +
<<mirroring_packages,Mirroring packages>> +
<<references,References>>
<<references,References>> +
<<generic-methods,Generic Methods>>
[[meaning-of-new]]
=== Meaning of `new`
@@ -5994,32 +5997,13 @@ There are some restrictions on properties that may be referenced; attempting to
* Properties originally defined in `external` classes (this only includes `Module.output`)
pkldoc:Reference[pkl:ref] instances are created using the constructor method pkldoc:#Reference()[pkl:ref].
This method accepts three parameters:
This method accepts two type arguments and two parameters:
* `domain` - An instance of a `ref.Domain` subclass; its type becomes the `D` type argument of the resulting `Reference` instance.
* `class` - A `Class` instance that will be the referent type of the returned `Reference`; it is the `T` type argument of the resulting instance.
* `D` - The type of the reference's domain. Must be a subclass of `ref.Domain`.
* `Referent` - The type the reference refers to.
* `domain` - An instance of `D` that becomes the domain resulting `Reference` instance.
* `data` - A domain-specific value used to identify the root or context of the reference.
[NOTE]
====
It is only possible to construct new references with a referent type that is a simple (non-generic) class type.
To obtain a reference to values of other types (generic, typealias, nullable, union, etc.), libraries must wrap those types in a class:
[source%parsed,pkl]
----
import "pkl:ref"
class TypeHolder {
$: *Listing<String>? | Mapping<UInt, String> // <1>
}
local r: ref.Reference<MyDomain, Listing<String>? | Mapping<UInt, String>> =
ref.Reference(myDomain, TypeHolder, null).$ // <2>
----
<1> Define a "holder" class with a property of the desired type.
<2> Access the `$` property of the reference to get a reference to the desired non-class type.
====
Here's a realistic example of how references might be used:
[source,pkl]
@@ -6027,7 +6011,7 @@ Here's a realistic example of how references might be used:
----
import "pkl:ref"
class Domain extends ref.Domain { // <1>
class Domain extends ref.AbstractDomain { // <1>
function renderReference(reference: ref.Reference<Domain, Any>): String =
if (reference.getData() is Task)
let (path = reference.getPath().map((access) -> access.property ?? access.key.toString()))
@@ -6038,18 +6022,20 @@ class Domain extends ref.Domain { // <1>
typealias Ref<T> = ref.Reference<Domain, T> // <2>
abstract class Task { // <3>
const function Ref<T>(data: Any): Ref<T> = ref.Reference::<Domain, T>(new Domain {}, data) // <3>
abstract class Task { // <4>
name: String
}
class TaskA extends Task {
input1: String | Ref<String> // <4>
input1: String | Ref<String> // <5>
input2: Int | Ref<Int>
hidden fixed outputs: Ref<TaskAOutputs> = ref.Reference(new Domain {}, TaskAOutputs, this) // <5>
hidden fixed outputs: Ref<TaskAOutputs> = Ref::<TaskAOutputs>(this) // <6>
}
abstract class TaskAOutputs { // <6>
abstract class TaskAOutputs { // <7>
foo: String
bar: Int
}
@@ -6059,24 +6045,25 @@ class TaskB extends Task {
inputY: Int | Ref<Int>
}
tasks: Listing<Task>(isDistinctBy((it) -> it.name)) // <7>
tasks: Listing<Task>(isDistinctBy((it) -> it.name)) // <8>
output {
renderer {
converters {
[ref.Reference] = (it) -> it.toString() // <8>
[ref.Reference] = (it) -> it.toString() // <9>
}
}
}
----
<1> Define a `ref.Domain` subclass that determines how `Reference` instances are rendered as strings.
<2> For convenience, create a type alias for references of this domain.
<3> Create a new base class that can be referenced. `Task` instances will be identified by their `name` value.
<4> In a specific `Task` subclass, input values accept both literal values and references to the same type.
<5> `Task` subclasses that provide output values define an `outputs` property where the referent type is `TaskAOutputs`.
<6> The `TaskAOutputs` class defines the possible runtime outputs of `TaskA` tasks. It is `abstract` and never instantiated, only used for producing references.
<7> Users provide `Task` instances with unique `name` values.
<8> Delegate to `Domain` for rendering `Reference` values.
<3> For convenience, wrap the `Reference` constructor to pass appropriate the domain type and value.
<4> Create a new base class that can be referenced. `Task` instances will be identified by their `name` value.
<5> In a specific `Task` subclass, input values accept both literal values and references to the same type.
<6> `Task` subclasses that provide output values define an `outputs` property where the referent type is `TaskAOutputs`.
<7> The `TaskAOutputs` class defines the possible runtime outputs of `TaskA` tasks. It is `abstract` and never instantiated, only used for producing references.
<8> Users provide `Task` instances with unique `name` values.
<9> Delegate to `Domain` for rendering `Reference` values.
[source,pkl]
.myWorkflow.pkl
@@ -6119,3 +6106,37 @@ tasks {
}
}
----
[[generic-methods]]
=== Generic Methods
Methods may be declared as "generic", with types that are themselves parameters.
Method type parameters may be used within the method anywhere else a type may be used: in a parameter's type, in the return type, or within the method body (e.g. <<defining-objects,object instantiation>>, <<type-tests,type tests>>, <<type-casts,type casts>>).
When calling a generic method, a caller may manually specify type arguments by writing `methodName::<TypeArg1, TypeArg2>(arg1, arg2)`.
When no type arguments are provided, type checks of parameter types behave like <<unknown-type,`unknown`>> and always succeed.
[source,pkl]
----
class Pigeon {
name: String
}
class Hawk {
name: String
}
function makeFlock<Bird>(names: Listing<String>): Listing<Bird> = new {
for (n in names) {
new { name = n }
}
}
pigeonFlock = makeFlock::<Pigeon>(new { "Alice"; "Bill"; "Ruth" }) // <1>
hawkFlock = makeFlock::<Hawk>(new { "Florence"; "Holly"; "Jim" }) // <2>
----
<1> Result: a `Listing` where every element is a `Pigeon`.
<2> Result: a `Listing` where every element is a `Hawk`.
NOTE: The `::<>` syntax is colloquially known as the "turbofish", after https://doc.rust-lang.org/reference/glossary.html#turbofish[the same syntax in Rust].
Pkl requires this syntax for the same reason as Rust: to avoid parser ambiguity.
+1 -1
View File
@@ -62,7 +62,7 @@ Library authors describe types as `ref.Reference<D, T>`, where `D` is the _domai
----
import "pkl:ref"
class MyDomain extends ref.Domain { // <1>
class MyDomain extends ref.AbstractDomain { // <1>
function renderReference(ref: ref.Reference<MyDomain, Any>): String =
"${ " + ref.getData() + "." + ref.getPath().join(".") + " }"
}
@@ -29,8 +29,18 @@ import org.pkl.core.runtime.*;
@NodeInfo(language = "Pkl")
@TypeSystemReference(VmTypes.class)
public abstract class PklRootNode extends RootNode {
private final boolean propagateTypeMismatchExceptions;
protected PklRootNode(@Nullable VmLanguage language, FrameDescriptor descriptor) {
this(language, descriptor, false);
}
protected PklRootNode(
@Nullable VmLanguage language,
FrameDescriptor descriptor,
boolean propagateTypeMismatchExceptions) {
super(language, descriptor);
this.propagateTypeMismatchExceptions = propagateTypeMismatchExceptions;
}
public abstract SourceSection getSourceSection();
@@ -46,7 +56,7 @@ public abstract class PklRootNode extends RootNode {
return executeImpl(frame);
} catch (VmTypeMismatchException e) {
CompilerDirectives.transferToInterpreter();
throw e.toVmException();
throw propagateTypeMismatchExceptions ? e : e.toVmException();
} catch (VmException e) {
CompilerDirectives.transferToInterpreter();
throw e;
@@ -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.
@@ -31,8 +31,7 @@ public final class SimpleRootNode extends PklRootNode {
SourceSection sourceSection,
String qualifiedName,
ExpressionNode bodyNode) {
super(language, descriptor);
super(language, descriptor, false);
this.sourceSection = sourceSection;
this.qualifiedName = qualifiedName;
this.bodyNode = bodyNode;
@@ -36,7 +36,6 @@ import org.jspecify.annotations.Nullable;
import org.pkl.core.PClassInfo;
import org.pkl.core.PklBugException;
import org.pkl.core.SecurityManagerException;
import org.pkl.core.TypeParameter;
import org.pkl.core.TypeParameter.Variance;
import org.pkl.core.ast.ByteConstantValueNode;
import org.pkl.core.ast.ConstantNode;
@@ -189,6 +188,7 @@ import org.pkl.core.runtime.FrameDescriptorBuilder;
import org.pkl.core.runtime.FrameSlotVariable;
import org.pkl.core.runtime.ModuleInfo;
import org.pkl.core.runtime.ModuleResolver;
import org.pkl.core.runtime.TypeParameter;
import org.pkl.core.runtime.VmBytes;
import org.pkl.core.runtime.VmClass;
import org.pkl.core.runtime.VmContext;
@@ -285,6 +285,7 @@ import org.pkl.parser.syntax.Type.UnionType;
import org.pkl.parser.syntax.Type.UnknownType;
import org.pkl.parser.syntax.TypeAlias;
import org.pkl.parser.syntax.TypeAnnotation;
import org.pkl.parser.syntax.TypeArgumentList;
import org.pkl.parser.syntax.TypeParameterList;
public class AstBuilder extends AbstractAstBuilder<Object> {
@@ -487,9 +488,13 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
if (args == null) {
if (identifier.getIdentifiers().size() == 1) {
var text = identifier.getIdentifiers().get(0).getValue();
var typeParameter = symbolTable.findTypeParameter(text);
if (typeParameter != null) {
return new UnresolvedTypeNode.TypeVariable(createSourceSection(type), typeParameter);
var typeParameterResolution = symbolTable.getCurrentScope().resolveTypeParameter(text);
if (typeParameterResolution != null) {
return new UnresolvedTypeNode.TypeVariable(
createSourceSection(type),
typeParameterResolution.typeParameter(),
typeParameterResolution.ownerType(),
typeParameterResolution.levelsUp());
}
}
@@ -872,8 +877,20 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
}
}
private UnresolvedTypeNode @Nullable [] doVisitMethodTypeArguments(
@Nullable TypeArgumentList typeArgumentList) {
if (typeArgumentList == null) return null;
var types = typeArgumentList.getTypes();
var res = new UnresolvedTypeNode[types.size()];
for (var i = 0; i < res.length; i++) {
res[i] = visitType(types.get(i));
}
return res;
}
private ExpressionNode resolvedMethodCall(UnqualifiedAccessExpr expr, ArgumentList argList) {
var name = expr.getIdentifier().getValue();
var typeArgs = doVisitMethodTypeArguments(expr.getTypeArgumentList());
var scope = symbolTable.getCurrentScope();
var sourceSection = createSourceSection(expr);
var constLevel = scope.getConstLevel();
@@ -897,45 +914,67 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
return new InvokeQualifiedObjectMethodNode(
sourceSection,
identifier,
typeArgs,
argInfo.arguments,
needsConst,
getModuleNode,
argInfo.methodSlot);
argInfo.methodSlot,
scope.getQualifiedName());
}
if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) {
return new InvokeQualifiedClassMethodNode(
sourceSection,
identifier,
typeArgs,
argInfo.arguments,
needsConst,
getModuleNode,
argInfo.methodSlot);
argInfo.methodSlot,
scope.getQualifiedName());
}
return InvokeMethodVirtualNodeGen.create(
sourceSection,
identifier,
typeArgs,
argInfo.arguments,
MemberLookupMode.IMPLICIT_LEXICAL,
needsConst,
argInfo.methodSlot,
scope.getQualifiedName(),
getModuleNode,
GetClassNodeGen.create(null));
}
if (method.isObjectMethod()) {
return new InvokeLexicalObjectMethodNode(
sourceSection, identifier, levelsUp, argInfo.arguments, needsConst, argInfo.methodSlot);
sourceSection,
identifier,
levelsUp,
typeArgs,
argInfo.arguments,
needsConst,
argInfo.methodSlot,
scope.getQualifiedName());
}
if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) {
return new InvokeLexicalClassMethodNode(
sourceSection, identifier, levelsUp, argInfo.arguments, needsConst, argInfo.methodSlot);
sourceSection,
identifier,
levelsUp,
typeArgs,
argInfo.arguments,
needsConst,
argInfo.methodSlot,
scope.getQualifiedName());
}
return InvokeMethodVirtualNodeGen.create(
sourceSection,
identifier,
typeArgs,
argInfo.arguments,
MemberLookupMode.IMPLICIT_LEXICAL,
needsConst,
argInfo.methodSlot,
scope.getQualifiedName(),
levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp),
GetClassNodeGen.create(null));
} else if (resolution instanceof ImplicitBaseMethod) {
@@ -962,8 +1001,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
createSourceSection(expr),
method,
new ConstantValueNode(baseModule),
typeArgs,
argInfo.arguments,
argInfo.methodSlot);
argInfo.methodSlot,
scope.getQualifiedName());
}
} else if (resolution instanceof ImplicitThisMethod) {
var isCustomThis = scope.isCustomThisScope();
@@ -974,10 +1015,12 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
return InvokeMethodVirtualNodeGen.create(
sourceSection,
org.pkl.core.runtime.Identifier.get(name),
typeArgs,
arguments,
MemberLookupMode.IMPLICIT_THIS,
needsConst,
methodSlot,
scope.getQualifiedName(),
VmUtils.createThisNode(VmUtils.unavailableSourceSection(), isCustomThis),
GetClassNodeGen.create(null));
} else {
@@ -1154,9 +1197,16 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
.build();
}
var typeArgs = doVisitMethodTypeArguments(expr.getTypeArgumentList());
var argInfo = visitArgumentList(argCtx);
return InvokeSuperMethodNodeGen.create(
sourceSection, memberName, argInfo.arguments, needsConst, argInfo.methodSlot);
sourceSection,
memberName,
typeArgs,
argInfo.arguments,
needsConst,
argInfo.methodSlot,
currentScope.getQualifiedName());
}
// superproperty call
@@ -1318,10 +1368,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
public ExpressionNode visitFunctionLiteralExpr(FunctionLiteralExpr expr) {
var sourceSection = createSourceSection(expr);
var params = expr.getParameterList();
var descriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(params);
var descriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(params, null);
var paramCount = params.getParameters().size();
var descriptorBuilder = descriptorBuilderAndBindings.first;
var bindings = descriptorBuilderAndBindings.second;
var descriptorBuilder = descriptorBuilderAndBindings.builder;
var bindings = descriptorBuilderAndBindings.bindings;
if (paramCount > 5) {
throw exceptionBuilder()
.evalError("tooManyFunctionParameters")
@@ -1688,7 +1738,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
modifiers,
PClassInfo.forModuleClass(
moduleInfo.getModuleName(), moduleInfo.getModuleKey().getUri()),
List.of(),
new TypeParameter[0],
moduleInfo,
supertypeNode,
moduleProperties,
@@ -1855,10 +1905,18 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
@Override
public ObjectMember visitClass(Class clazz) {
var sourceSection = createSourceSection(clazz);
var headerSection = createSourceSection(clazz.getHeaderSpan());
var typeParameters = visitTypeParameterList(clazz.getTypeParameterList());
var typeParameterList = clazz.getTypeParameterList();
if (clazz.getTypeParameterList() != null && !isStdLibModule) {
throw exceptionBuilder()
.evalError("cannotDeclareTypeParameter")
.withSourceSection(createSourceSection(typeParameterList.getParameters().get(0)))
.build();
}
var typeParameters = visitTypeParameterList(typeParameterList, null);
var modifiers =
doVisitModifiers(
@@ -2108,8 +2166,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
public UnresolvedMethodNode visitClassMethod(ClassMethod entry) {
var headerSection = createSourceSection(entry.getHeaderSpan());
var typeParameters = visitTypeParameterList(entry.getTypeParameterList());
var modifiers =
doVisitModifiers(
entry.getModifiers(), VmModifier.VALID_METHOD_MODIFIERS, "invalidMethodModifier");
@@ -2119,11 +2175,15 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var bodyContext = entry.getExpr();
var paramListCtx = entry.getParameterList();
var descriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(paramListCtx);
var descriptorBuilderAndBindings =
createFrameDescriptorBuilderAndSlotVariables(paramListCtx, entry.getTypeParameterList());
var paramCount = paramListCtx.getParameters().size();
var descriptorBuilder = descriptorBuilderAndBindings.first;
var bindings = descriptorBuilderAndBindings.second;
var descriptorBuilder = descriptorBuilderAndBindings.builder;
var bindings = descriptorBuilderAndBindings.bindings;
var typeParameters =
visitTypeParameterList(
entry.getTypeParameterList(), descriptorBuilderAndBindings.typeBindings);
var annotations = doVisitAnnotations(entry.getAnnotations(), methodName);
@@ -2204,7 +2264,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var isLocal = VmModifier.isLocal(modifiers);
var name = org.pkl.core.runtime.Identifier.property(typeAlias.getName().getValue(), isLocal);
var typeParameters = visitTypeParameterList(typeAlias.getTypeParameterList());
var typeParameters = visitTypeParameterList(typeAlias.getTypeParameterList(), null);
var objectMemberModifiers =
isLocal ? VmModifier.LOCAL_TYPEALIAS_OBJECT_MEMBER : VmModifier.TYPEALIAS_OBJECT_MEMBER;
@@ -2274,41 +2334,43 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
return (ExpressionNode) expr.accept(this);
}
@Override
public List<TypeParameter> visitTypeParameterList(@Nullable TypeParameterList ctx) {
if (ctx == null) return List.of();
if (!(ctx.parent() instanceof TypeAlias) && !isStdLibModule) {
throw exceptionBuilder()
.evalError("cannotDeclareTypeParameter")
.withSourceSection(createSourceSection(ctx.getParameters().get(0)))
.build();
}
public TypeParameter[] visitTypeParameterList(
@Nullable TypeParameterList ctx, FrameSlotVariable @Nullable [] typeBindings) {
if (ctx == null) return new TypeParameter[0];
var params = ctx.getParameters();
var size = params.size();
var result = new ArrayList<TypeParameter>(size);
var result = new TypeParameter[size];
for (var i = 0; i < size; i++) {
var paramCtx = params.get(i);
Variance variance;
var nodeVariance = paramCtx.getVariance();
if (nodeVariance == null) {
variance = TypeParameter.Variance.INVARIANT;
variance = org.pkl.core.TypeParameter.Variance.INVARIANT;
} else {
variance =
switch (nodeVariance) {
case IN -> TypeParameter.Variance.CONTRAVARIANT;
case OUT -> TypeParameter.Variance.COVARIANT;
case IN -> org.pkl.core.TypeParameter.Variance.CONTRAVARIANT;
case OUT -> org.pkl.core.TypeParameter.Variance.COVARIANT;
};
}
var parameterName = paramCtx.getIdentifier().getValue();
if (result.stream().anyMatch(it -> it.getName().equals(parameterName))) {
throw exceptionBuilder()
.evalError("duplicateTypeParameter", parameterName)
.withSourceSection(createSourceSection(paramCtx))
.build();
for (var j = 0; j < i; j++) {
if (result[j].name().equals(parameterName)) {
throw exceptionBuilder()
.evalError("duplicateTypeParameter", parameterName)
.withSourceSection(createSourceSection(paramCtx))
.build();
}
}
result.add(new TypeParameter(variance, parameterName, i));
result[i] =
new TypeParameter(
createSourceSection(paramCtx),
typeBindings != null ? typeBindings[i].slot() : -1,
i,
parameterName,
variance);
}
return result;
}
@@ -2417,9 +2479,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var bindings =
parametersDescriptorAndBindings == null
? new FrameSlotVariable[0]
: parametersDescriptorAndBindings.second;
: parametersDescriptorAndBindings.bindings;
var parametersFrameDescriptorBuilder =
parametersDescriptorAndBindings == null ? null : parametersDescriptorAndBindings.first;
parametersDescriptorAndBindings == null ? null : parametersDescriptorAndBindings.builder;
return symbolTable.enterObjectScope(
bindings,
@@ -2437,9 +2499,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
// constraints (e.g. containing `let` expressions) may add further slots to it
var parameterTypes = doVisitParameterTypes(body);
var parametersDescriptor =
parametersDescriptorAndBindings == null
parametersFrameDescriptorBuilder == null
? null
: parametersDescriptorAndBindings.first.build();
: parametersFrameDescriptorBuilder.build();
var members = EconomicMaps.<Object, ObjectMember>create();
var elements = new ArrayList<ObjectMember>();
@@ -2782,6 +2844,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
Expr expr,
@Nullable TypeAnnotation typeAnnotation,
boolean isModuleMethod) {
var modifiers =
doVisitModifiers(
modifierNodes, VmModifier.VALID_OBJECT_MEMBER_MODIFIERS, "invalidObjectMemberModifier");
@@ -2795,25 +2858,21 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var methodName = org.pkl.core.runtime.Identifier.method(identifier.getValue(), true);
var frameDescriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(paramList);
var frameDescriptorBuilderAndBindings =
createFrameDescriptorBuilderAndSlotVariables(paramList, typeParamList);
var frameDescriptorBuilder = frameDescriptorBuilderAndBindings.first;
var bindings = frameDescriptorBuilderAndBindings.second;
var frameDescriptorBuilder = frameDescriptorBuilderAndBindings.builder;
var bindings = frameDescriptorBuilderAndBindings.bindings;
var typeParameters =
visitTypeParameterList(typeParamList, frameDescriptorBuilderAndBindings.typeBindings);
return symbolTable.enterMethod(
methodName,
getConstLevel(modifiers),
bindings,
frameDescriptorBuilder,
List.of(),
typeParameters,
scope -> {
if (typeParamList != null) {
throw exceptionBuilder()
.evalError("cannotDeclareTypeParameter")
.withSourceSection(createSourceSection(typeParamList))
.build();
}
var member =
new ObjectMember(
createSourceSection(method),
@@ -2831,6 +2890,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
member,
body,
paramList.getParameters().size(),
typeParameters,
parameterTypeNodes,
typeNode);
@@ -2919,9 +2979,11 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
private ExpressionNode doVisitMethodAccessExpr(QualifiedAccessExpr expr, ArgumentList argList) {
var sourceSection = createSourceSection(expr);
var functionName = toIdentifier(expr.getIdentifier().getValue());
var typeArgs = doVisitMethodTypeArguments(expr.getTypeArgumentList());
var receiver = visitExpr(expr.getExpr());
var needsConst = needsConst(receiver);
var argInfo = visitArgumentList(argList);
var currentScope = symbolTable.getCurrentScope();
if (expr.isNullable()) {
//noinspection ConstantConditions
@@ -2930,10 +2992,12 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
InvokeMethodVirtualNodeGen.create(
sourceSection,
functionName,
typeArgs,
argInfo.arguments,
MemberLookupMode.EXPLICIT_RECEIVER,
needsConst,
argInfo.methodSlot,
currentScope.getQualifiedName(),
PropagateNullReceiverNodeGen.create(unavailableSourceSection(), receiver),
GetClassNodeGen.create(null)));
}
@@ -2942,10 +3006,12 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
return InvokeMethodVirtualNodeGen.create(
sourceSection,
functionName,
typeArgs,
argInfo.arguments,
MemberLookupMode.EXPLICIT_RECEIVER,
needsConst,
argInfo.methodSlot,
currentScope.getQualifiedName(),
receiver,
GetClassNodeGen.create(null));
}
@@ -3140,18 +3206,44 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
return slotVariables;
}
private Pair<FrameDescriptorBuilder, FrameSlotVariable[]>
createFrameDescriptorBuilderAndSlotVariables(ParameterList params) {
var builder = new FrameDescriptorBuilder(params.getParameters().size());
return Pair.of(builder, getSlotVariables(params.getParameters(), builder));
private FrameSlotVariable[] getTypeSlotVariables(
List<org.pkl.parser.syntax.TypeParameter> typeParams,
FrameDescriptorBuilder frameDescriptorBuilder) {
var slotVariables = new FrameSlotVariable[typeParams.size()];
for (var i = 0; i < typeParams.size(); i++) {
var param = typeParams.get(i);
slotVariables[i] =
frameDescriptorBuilder.addSlot(
FrameSlotKind.Illegal, toIdentifier(param.getIdentifier().getValue()), null);
}
return slotVariables;
}
private @Nullable Pair<@Nullable FrameDescriptorBuilder, FrameSlotVariable[]>
createFrameDescriptorBuilderAndSlotVariables(ObjectBody body) {
private record FrameBindings(
FrameDescriptorBuilder builder,
FrameSlotVariable[] bindings,
FrameSlotVariable[] typeBindings) {}
private FrameBindings createFrameDescriptorBuilderAndSlotVariables(
ParameterList params, @Nullable TypeParameterList typeParamList) {
List<org.pkl.parser.syntax.TypeParameter> typeParams =
typeParamList != null ? typeParamList.getParameters() : List.of();
var builder = new FrameDescriptorBuilder(params.getParameters().size() + typeParams.size());
// NB: param slots are allocated before type param slots intentionally
// despite type args coming before args in FunctionNode calls
// this is because many places assume that param index == slot
// if desired, we could change this in the future since it's just the internal frame layout
var paramSlots = getSlotVariables(params.getParameters(), builder);
var typeParamSlots = getTypeSlotVariables(typeParams, builder);
return new FrameBindings(builder, paramSlots, typeParamSlots);
}
private @Nullable FrameBindings createFrameDescriptorBuilderAndSlotVariables(ObjectBody body) {
if (body.getParameters().isEmpty()) return null;
var builder = new FrameDescriptorBuilder(body.getParameters().size());
return Pair.of(builder, getSlotVariables(body.getParameters(), builder));
return new FrameBindings(
builder, getSlotVariables(body.getParameters(), builder), new FrameSlotVariable[0]);
}
private void checkNotInsideForGenerator(Node ctx, String errorMessageKey) {
@@ -21,7 +21,7 @@ import com.oracle.truffle.api.frame.FrameDescriptor;
import java.util.*;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.pkl.core.TypeParameter;
import org.pkl.core.PklBugException;
import org.pkl.core.ast.ConstantNode;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.VmModifier;
@@ -38,6 +38,7 @@ import org.pkl.core.runtime.FrameDescriptorBuilder;
import org.pkl.core.runtime.FrameSlotVariable;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.ModuleInfo;
import org.pkl.core.runtime.TypeParameter;
import org.pkl.core.runtime.VmDataSize;
import org.pkl.core.runtime.VmDuration;
import org.pkl.core.runtime.VmUtils;
@@ -59,19 +60,10 @@ public final class SymbolTable {
return currentScope;
}
public @Nullable TypeParameter findTypeParameter(String name) {
TypeParameter result;
for (var scope = currentScope; scope != null; scope = scope.getParent()) {
result = scope.getTypeParameter(name);
if (result != null) return result;
}
return null;
}
public ObjectMember enterClass(
Identifier name,
int modifiers,
List<TypeParameter> typeParameters,
TypeParameter[] typeParameters,
Function<ClassScope, ObjectMember> nodeFactory) {
return doEnter(
new ClassScope(
@@ -86,7 +78,7 @@ public final class SymbolTable {
public ObjectMember enterTypeAlias(
Identifier name,
List<TypeParameter> typeParameters,
TypeParameter[] typeParameters,
Function<TypeAliasScope, ObjectMember> nodeFactory) {
try {
this.isInTypeAliasScope = true;
@@ -108,7 +100,7 @@ public final class SymbolTable {
ConstLevel constLevel,
FrameSlotVariable[] bindings,
FrameDescriptorBuilder frameDescriptorBuilder,
List<TypeParameter> typeParameters,
TypeParameter[] typeParameters,
Function<MethodScope, T> nodeFactory) {
return doEnter(
new MethodScope(
@@ -256,6 +248,7 @@ public final class SymbolTable {
protected final Map<String, Member> properties = new HashMap<>();
// The methods defined on this (lexical) scope
protected final Map<String, Member> methods = new HashMap<>();
protected final Set<String> knownTypeParameterNames;
static int[] getSlots(FrameSlotVariable[] bindings) {
if (bindings.length == 0) {
@@ -275,7 +268,8 @@ public final class SymbolTable {
ConstLevel constLevel,
FrameDescriptorBuilder frameDescriptorBuilder,
int[] forGeneratorSlots,
int[] parameterSlots) {
int[] parameterSlots,
Set<String> knownTypeParameterNames) {
this.parent = parent;
this.name = name;
this.qualifiedName = qualifiedName;
@@ -290,6 +284,7 @@ public final class SymbolTable {
: constLevel;
this.forGeneratorSlots = forGeneratorSlots;
this.parameterSlots = parameterSlots;
this.knownTypeParameterNames = knownTypeParameterNames;
}
public final @Nullable Scope getParent() {
@@ -509,7 +504,8 @@ public final class SymbolTable {
}
public final VariableResolution resolveVariable(String name) {
var resolved = resolveLexical((scope, levelUp) -> scope.doResolveProperty(name, levelUp));
var resolved =
resolveLexical((scope, levelUp) -> scope.doResolveProperty(name, levelUp), null);
if (resolved != null) {
return resolved;
}
@@ -522,7 +518,7 @@ public final class SymbolTable {
}
public final MethodResolution resolveMethod(String name) {
var resolved = resolveLexical((scope, levelUp) -> scope.doResolveMethod(name, levelUp));
var resolved = resolveLexical((scope, levelUp) -> scope.doResolveMethod(name, levelUp), null);
if (resolved != null) {
return resolved;
}
@@ -534,13 +530,29 @@ public final class SymbolTable {
return new ImplicitThisMethod();
}
public @Nullable TypeParameterResolution resolveTypeParameter(String name) {
if (!knownTypeParameterNames.contains(name)) {
return null;
}
return resolveLexical(
(scope, levelUp) -> ((Scope) scope).doResolveTypeParameter(name, levelUp),
(scope, levelUp) -> scope.doResolveTypeParameter(name, levelUp));
}
protected @Nullable TypeParameterResolution doResolveTypeParameter(String name, int levelsUp) {
return null;
}
@FunctionalInterface
private interface ResolutionFunction<T> {
@Nullable T apply(LexicalScope scope, int levelUp);
private interface ResolutionFunction<Scope, T> {
@Nullable T apply(Scope scope, int levelUp);
}
@SuppressWarnings("unchecked")
private @Nullable <R> R resolveLexical(ResolutionFunction<R> fun) {
private @Nullable <R> R resolveLexical(
ResolutionFunction<LexicalScope, R> fun,
@Nullable ResolutionFunction<TypeAliasScope, R> aliasFun) {
var levelsUp = 0;
var shouldSkip = false;
var skippedObjectScope = false;
@@ -554,7 +566,11 @@ public final class SymbolTable {
if (scope instanceof AnnotationScope && scope.getParent() instanceof ClassScope) {
levelsUp--;
}
if (scope instanceof LexicalScope lex) {
if (aliasFun != null && scope instanceof TypeAliasScope typeAliasScope) {
// when resolving type params, also visit TypeAliasScopes with aliasFun
var result = aliasFun.apply(typeAliasScope, levelsUp);
if (result != null) return result;
} else if (scope instanceof LexicalScope lex) {
if (shouldSkip && !(scope instanceof ForGeneratorScope)) {
if (scope instanceof ObjectScope objectScope && objectScope.hasParams()) {
// skipping the object scope should now see the object body params, because the params
@@ -638,7 +654,8 @@ public final class SymbolTable {
ConstLevel.NONE,
frameDescriptorBuilder,
parent.forGeneratorSlots,
EMPTY_INT_ARRAY);
EMPTY_INT_ARRAY,
parent.knownTypeParameterNames);
this.bindings = bindings;
}
@@ -676,7 +693,7 @@ public final class SymbolTable {
}
public abstract static class TypeParameterizableScope extends Scope {
private final List<TypeParameter> typeParameters;
private final TypeParameter[] typeParameters;
public TypeParameterizableScope(
Scope parent,
@@ -684,7 +701,7 @@ public final class SymbolTable {
String qualifiedName,
ConstLevel constLevel,
FrameDescriptorBuilder frameDescriptorBuilder,
List<TypeParameter> typeParameters,
TypeParameter[] typeParameters,
int[] forGeneratorSlots,
int[] parameterSlots) {
super(
@@ -694,14 +711,27 @@ public final class SymbolTable {
constLevel,
frameDescriptorBuilder,
forGeneratorSlots,
parameterSlots);
parameterSlots,
extendTypeParams(parent.knownTypeParameterNames, typeParameters));
this.typeParameters = typeParameters;
}
private static Set<String> extendTypeParams(Set<String> base, TypeParameter[] typeParams) {
var res = new HashSet<>(base);
for (var typeParam : typeParams) {
res.add(typeParam.name());
}
return res;
}
public int getTypeParameterCount() {
return typeParameters.length;
}
@Override
public @Nullable TypeParameter getTypeParameter(String name) {
for (var param : typeParameters) {
if (name.equals(param.getName())) return param;
if (name.equals(param.name())) return param;
}
return null;
}
@@ -721,7 +751,8 @@ public final class SymbolTable {
ConstLevel.NONE,
new FrameDescriptorBuilder(),
EMPTY_INT_ARRAY,
EMPTY_INT_ARRAY);
EMPTY_INT_ARRAY,
Set.of());
this.isBaseModule = isBaseModule;
this.moduleInfo = moduleInfo;
this.isAmend = moduleInfo.isAmend();
@@ -759,7 +790,7 @@ public final class SymbolTable {
ConstLevel constLevel,
FrameSlotVariable[] bindings,
FrameDescriptorBuilder frameDescriptorBuilder,
List<TypeParameter> typeParameters) {
TypeParameter[] typeParameters) {
super(
parent,
name,
@@ -781,6 +812,16 @@ public final class SymbolTable {
public @Nullable MethodResolution doResolveMethod(String name, int levelsUp) {
return null;
}
public @Nullable TypeParameterResolution doResolveTypeParameter(String name, int levelsUp) {
var result = getTypeParameter(name);
if (result == null) return null;
if (result.variance() != org.pkl.core.TypeParameter.Variance.INVARIANT) {
throw new PklBugException(
"found type parameter variance modifier found on a method declaration");
}
return new TypeParameterResolution(result, TypeParameter.OwnerType.METHOD, levelsUp);
}
}
public static final class LambdaScope extends Scope implements LexicalScope {
@@ -798,7 +839,8 @@ public final class SymbolTable {
parent.getConstLevel(),
frameDescriptorBuilder,
EMPTY_INT_ARRAY,
getSlots(bindings));
getSlots(bindings),
parent.knownTypeParameterNames);
this.bindings = bindings;
}
@@ -841,7 +883,8 @@ public final class SymbolTable {
parent.getConstLevel(),
parent.frameDescriptorBuilder,
parent.forGeneratorSlots,
getMyParameterSlots(parent, binding));
getMyParameterSlots(parent, binding),
parent.knownTypeParameterNames);
this.binding = binding;
}
@@ -893,7 +936,8 @@ public final class SymbolTable {
ConstLevel.NONE,
getFrameDescriptorBuilder(parent),
getForGeneratorSlots(parent),
getParameterSlots(parent));
getParameterSlots(parent),
parent.knownTypeParameterNames);
}
}
@@ -951,7 +995,8 @@ public final class SymbolTable {
ConstLevel.NONE,
frameDescriptorBuilder,
getMyForGeneratorSlots(parent, keyBinding, valueBinding),
getMyParameterSlots(parent));
getMyParameterSlots(parent),
parent.knownTypeParameterNames);
this.keyBinding = keyBinding;
this.valueBinding = valueBinding;
}
@@ -996,7 +1041,8 @@ public final class SymbolTable {
// object members inherit the for-generator slots of the parent for-generator, if it
// exists.
parent instanceof ForGeneratorScope ? parent.forGeneratorSlots : EMPTY_INT_ARRAY,
parent.parameterSlots);
parent.parameterSlots,
parent.knownTypeParameterNames);
}
}
@@ -1010,7 +1056,8 @@ public final class SymbolTable {
ConstLevel.NONE,
frameDescriptorBuilder,
parent instanceof ForGeneratorScope ? parent.forGeneratorSlots : EMPTY_INT_ARRAY,
parent.parameterSlots);
parent.parameterSlots,
parent.knownTypeParameterNames);
}
}
@@ -1023,7 +1070,7 @@ public final class SymbolTable {
String qualifiedName,
int modifiers,
FrameDescriptorBuilder frameDescriptorBuilder,
List<TypeParameter> typeParameters) {
TypeParameter[] typeParameters) {
super(
parent,
name,
@@ -1049,6 +1096,12 @@ public final class SymbolTable {
if (member == null) return null;
return new LexicalMethod(false, isClosed, false, member.modifiers, levelsUp);
}
public @Nullable TypeParameterResolution doResolveTypeParameter(String name, int levelsUp) {
var result = getTypeParameter(name);
if (result == null) return null;
return new TypeParameterResolution(result, TypeParameter.OwnerType.CLASS, -1);
}
}
public static final class TypeAliasScope extends TypeParameterizableScope {
@@ -1057,7 +1110,7 @@ public final class SymbolTable {
Identifier name,
String qualifiedName,
FrameDescriptorBuilder frameDescriptorBuilder,
List<TypeParameter> typeParameters) {
TypeParameter[] typeParameters) {
super(
parent,
name,
@@ -1068,6 +1121,12 @@ public final class SymbolTable {
parent.forGeneratorSlots,
parent.parameterSlots);
}
public @Nullable TypeParameterResolution doResolveTypeParameter(String name, int levelsUp) {
var result = getTypeParameter(name);
if (result == null) return null;
return new TypeParameterResolution(result, TypeParameter.OwnerType.TYPEALIAS, -1);
}
}
/**
@@ -1086,7 +1145,8 @@ public final class SymbolTable {
ConstLevel.NONE,
frameDescriptorBuilder,
parent.forGeneratorSlots,
parent.parameterSlots);
parent.parameterSlots,
parent.knownTypeParameterNames);
}
}
@@ -1100,7 +1160,8 @@ public final class SymbolTable {
ConstLevel.MODULE,
frameDescriptorBuilder,
EMPTY_INT_ARRAY,
EMPTY_INT_ARRAY);
EMPTY_INT_ARRAY,
parent.knownTypeParameterNames);
}
}
@@ -0,0 +1,21 @@
/*
* 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.ast.builder;
import org.pkl.core.runtime.TypeParameter;
public record TypeParameterResolution(
TypeParameter typeParameter, TypeParameter.OwnerType ownerType, int levelsUp) {}
@@ -31,6 +31,7 @@ import org.pkl.core.ast.frame.ReadExactFrameSlotNodeGen;
import org.pkl.core.ast.member.FunctionNode;
import org.pkl.core.ast.member.Lambda;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.TypeParameterNode;
import org.pkl.core.runtime.*;
public final class AmendFunctionNode extends PklNode {
@@ -87,6 +88,7 @@ public final class AmendFunctionNode extends PklNode {
parameterTypeNodes,
null,
true,
new TypeParameterNode[0],
new AmendFunctionBodyNode(
sourceSection,
hostNode.copy(
@@ -32,7 +32,7 @@ public abstract class AbstractInferParentFromMethodNode extends AbstractInferPar
protected abstract Method getMethod(VirtualFrame frame);
protected abstract @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method);
protected abstract @Nullable TypeNode getTypeNode(Method method);
@Idempotent
protected boolean isFinalType(Method method, @Nullable TypeNode typeNode) {
@@ -25,6 +25,7 @@ import org.pkl.core.ast.type.TypeNode.TypeVariableNode;
import org.pkl.core.ast.type.TypeNode.UnknownTypeNode;
import org.pkl.core.runtime.VmDynamic;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmTypeArgument;
import org.pkl.core.runtime.VmUtils;
public abstract class AbstractInferParentNode extends ExpressionNode {
@@ -41,11 +42,23 @@ public abstract class AbstractInferParentNode extends ExpressionNode {
@Nullable TypeNode typeNode,
SourceSection headerSection,
String qualifiedName) {
return getDefaultValue(frame, typeNode, headerSection, qualifiedName, false, null);
}
protected Object getDefaultValue(
VirtualFrame frame,
@Nullable TypeNode typeNode,
SourceSection headerSection,
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
if (typeNode == null || typeNode instanceof UnknownTypeNode) {
return VmDynamic.empty();
}
var defaultValue = typeNode.createDefaultValue(frame, language, headerSection, qualifiedName);
var defaultValue =
typeNode.createDefaultValue(
frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides);
if (defaultValue != null) {
return defaultValue;
}
@@ -17,23 +17,33 @@ package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmUtils;
public abstract sealed class AbstractInvokeLexicalMethodNode
extends AbstractInvokeLexicalOrQualifiedMethodNode
permits InvokeLexicalClassMethodNode, InvokeLexicalObjectMethodNode {
public abstract class AbstractInvokeLexicalMethodNode
extends AbstractInvokeLexicalOrQualifiedMethodNode {
private final int levelsUp;
public AbstractInvokeLexicalMethodNode(
SourceSection sourceSection,
Identifier methodName,
int levelsUp,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
boolean needsConst,
int methodSlot) {
super(sourceSection, methodName, argumentNodes, needsConst, methodSlot);
int methodSlot,
String qualifiedName) {
super(
sourceSection,
methodName,
unresolvedTypeArgumentNodes,
argumentNodes,
needsConst,
methodSlot,
qualifiedName);
this.levelsUp = levelsUp;
}
@@ -23,6 +23,7 @@ import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
@@ -34,9 +35,7 @@ import org.pkl.core.runtime.VmObjectLike;
* InvokeLexicalObjectMethodNode}), or off of an explicit receiver expression ({@link
* InvokeQualifiedClassMethodNode}, {@link InvokeQualifiedObjectMethodNode}).
*/
public abstract sealed class AbstractInvokeLexicalOrQualifiedMethodNode
extends AbstractInvokeMethodNode
permits AbstractInvokeQualifiedMethodNode, AbstractInvokeLexicalMethodNode {
public abstract class AbstractInvokeLexicalOrQualifiedMethodNode extends AbstractInvokeMethodNode {
protected final Identifier methodName;
private final boolean needsConst;
@@ -46,10 +45,12 @@ public abstract sealed class AbstractInvokeLexicalOrQualifiedMethodNode
protected AbstractInvokeLexicalOrQualifiedMethodNode(
SourceSection sourceSection,
Identifier methodName,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
boolean needsConst,
int methodSlot) {
super(sourceSection, argumentNodes, methodSlot);
int methodSlot,
String qualifiedName) {
super(sourceSection, unresolvedTypeArgumentNodes, argumentNodes, methodSlot, qualifiedName);
this.methodName = methodName;
this.needsConst = needsConst;
this.isConstChecked = false;
@@ -15,6 +15,8 @@
*/
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.TruffleSafepoint;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
@@ -22,36 +24,115 @@ import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.type.TypeArgumentTypeNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmObjectLike;
import org.pkl.core.runtime.VmTypeArgument;
public abstract class AbstractInvokeMethodNode extends ExpressionNode {
@Children protected final ExpressionNode[] argumentNodes;
protected final int methodSlot;
protected final String qualifiedName;
public AbstractInvokeMethodNode(
SourceSection sourceSection, ExpressionNode[] argumentNodes, int methodSlot) {
@Children protected UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes;
@CompilationFinal(dimensions = 1)
protected TypeArgumentTypeNode @Nullable [] typeArgumentRootNodes;
@CompilationFinal boolean typeArgumentsNeedMaterializedFrame = false;
protected AbstractInvokeMethodNode(
SourceSection sourceSection,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
int methodSlot,
String qualifiedName) {
super(sourceSection);
this.unresolvedTypeArgumentNodes = unresolvedTypeArgumentNodes;
this.argumentNodes = argumentNodes;
this.methodSlot = methodSlot;
this.qualifiedName = qualifiedName;
}
protected TypeArgumentTypeNode @Nullable [] getTypeArgumentRootNodes(VirtualFrame frame) {
if (typeArgumentRootNodes == null) {
if (unresolvedTypeArgumentNodes != null) {
var language = VmLanguage.get(this);
var frameDescriptor = frame.getFrameDescriptor();
CompilerDirectives.transferToInterpreterAndInvalidate();
var rootNodes = new TypeArgumentTypeNode[unresolvedTypeArgumentNodes.length];
for (var i = 0; i < rootNodes.length; i++) {
var typeNode = unresolvedTypeArgumentNodes[i].execute(frame);
rootNodes[i] =
new TypeArgumentTypeNode(
language,
frameDescriptor,
sourceSection,
qualifiedName + ".<typearg#" + (i + 1) + ">",
typeNode);
typeArgumentsNeedMaterializedFrame =
typeArgumentsNeedMaterializedFrame || typeNode.getTypeArgumentRequiresFrame();
}
typeArgumentRootNodes = rootNodes;
unresolvedTypeArgumentNodes = null;
} else {
return null;
}
}
return typeArgumentRootNodes;
}
protected VmTypeArgument @Nullable [] getTypeArguments(
VirtualFrame frame, @Nullable Method method) {
var rootNodes = getTypeArgumentRootNodes(frame);
if (rootNodes == null) return null;
var typeParameterCount = method == null ? 0 : method.getTypeParameterCount();
if (rootNodes.length != typeParameterCount) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("wrongTypeArgumentCount", typeParameterCount, rootNodes.length)
.build();
}
var argFrame = typeArgumentsNeedMaterializedFrame ? frame.materialize() : null;
var typeArgs = new VmTypeArgument[rootNodes.length];
for (var i = 0; i < typeArgs.length; i++) {
var rootNode = rootNodes[i];
typeArgs[i] = new VmTypeArgument(rootNode, argFrame);
}
return typeArgs;
}
@ExplodeLoop
protected Object[] evalArgs(
VirtualFrame frame, @Nullable Method method, Object owner, @Nullable Object receiver) {
VirtualFrame frame, @Nullable Method method, VmObjectLike owner, @Nullable Object receiver) {
Object prevMethod = null;
var typeArgs = getTypeArguments(frame, method);
if (methodSlot > -1) {
if (method != null) {
method.ensureInitialized(owner);
}
prevMethod = frame.getObject(methodSlot);
frame.setObject(methodSlot, method);
frame.setObject(methodSlot, new MethodCall(method, typeArgs));
}
var args = new Object[2 + argumentNodes.length];
var typeParameterCount = method != null ? method.getTypeParameterCount() : 0;
var args = new Object[2 + argumentNodes.length + typeParameterCount];
args[0] = receiver;
args[1] = owner;
if (typeArgs != null) {
System.arraycopy(typeArgs, 0, args, 2, typeParameterCount);
}
try {
for (var i = 0; i < argumentNodes.length; i++) {
TruffleSafepoint.poll(this);
args[2 + i] = argumentNodes[i].executeGeneric(frame);
args[2 + typeParameterCount + i] = argumentNodes[i].executeGeneric(frame);
}
} finally {
if (methodSlot > -1) {
@@ -61,4 +142,6 @@ public abstract class AbstractInvokeMethodNode extends ExpressionNode {
return args;
}
public record MethodCall(@Nullable Method method, VmTypeArgument @Nullable [] typeArguments) {}
}
@@ -17,7 +17,9 @@ package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmTyped;
@@ -29,11 +31,20 @@ public abstract sealed class AbstractInvokeQualifiedMethodNode
protected AbstractInvokeQualifiedMethodNode(
SourceSection sourceSection,
Identifier methodName,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
boolean needsConst,
ExpressionNode getReceiverNode,
int methodSlot) {
super(sourceSection, methodName, argumentNodes, needsConst, methodSlot);
int methodSlot,
String qualifiedName) {
super(
sourceSection,
methodName,
unresolvedTypeArgumentNodes,
argumentNodes,
needsConst,
methodSlot,
qualifiedName);
this.getReceiverNode = getReceiverNode;
}
@@ -21,9 +21,11 @@ import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.expression.member.AbstractInvokeMethodNode.MethodCall;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmTypeArgument;
public abstract class InferParentWithinMethodArgumentNode
extends AbstractInferParentFromMethodNode {
@@ -39,9 +41,14 @@ public abstract class InferParentWithinMethodArgumentNode
@Override
protected Method getMethod(VirtualFrame frame) {
var method = (Method) frame.getObject(methodSlot);
var methodCall = (MethodCall) frame.getObject(methodSlot);
if (methodCall == null) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder().evalError("cannotInferParent").build();
}
var method = methodCall.method();
if (method == null) {
// used in FunctionN.apply()
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder().evalError("cannotInferParent").build();
}
@@ -49,9 +56,18 @@ public abstract class InferParentWithinMethodArgumentNode
return method;
}
protected VmTypeArgument @Nullable [] getTypeArgumentOverrides(VirtualFrame frame) {
var methodCall = (MethodCall) frame.getObject(methodSlot);
if (methodCall == null) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder().evalError("cannotInferParent").build();
}
return methodCall.typeArguments();
}
@Override
protected @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method) {
return method.getParameterTypeNode(frame, argIndex);
protected @Nullable TypeNode getTypeNode(Method method) {
return method.getFunctionNode().getParameterTypeNode(argIndex);
}
// keep specializations in sync with other AbstractInferParentFromMethodNode subclasses
@@ -61,9 +77,9 @@ public abstract class InferParentWithinMethodArgumentNode
protected final Object evalCached(
@SuppressWarnings("unused") VirtualFrame frame,
@Cached("getMethod(frame)") @SuppressWarnings("unused") Method cachedMethod,
@Cached("getTypeNode(frame, cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode,
@Cached("getTypeNode(cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode,
@Cached(
"getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName())")
"getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName(), true, getTypeArgumentOverrides(frame))")
Object defaultValue) {
return defaultValue;
}
@@ -71,21 +87,30 @@ public abstract class InferParentWithinMethodArgumentNode
@Specialization(replaces = "evalCached")
protected final Object eval(VirtualFrame frame) {
var method = getMethod(frame);
var typeNode = getTypeNode(frame, method);
return getDefaultValue(frame, typeNode, method.getHeaderSection(), method.getQualifiedName());
var typeNode = getTypeNode(method);
return getDefaultValue(
frame,
typeNode,
method.getHeaderSection(),
method.getQualifiedName(),
true,
getTypeArgumentOverrides(frame));
}
@Override
protected Object getDefaultValue(
protected final Object getDefaultValue(
VirtualFrame frame,
@Nullable TypeNode typeNode,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
if (typeNode != null && typeNode.isSelfType()) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder().evalError("cannotInferParent").build();
}
return super.getDefaultValue(frame, typeNode, headerSection, qualifiedName);
return super.getDefaultValue(
frame, typeNode, headerSection, qualifiedName, isPreCall, typeArgumentOverrides);
}
}
@@ -52,8 +52,8 @@ public abstract class InferParentWithinMethodNode extends AbstractInferParentFro
}
@Override
protected @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method) {
return method.getReturnTypeNode(frame);
protected @Nullable TypeNode getTypeNode(Method method) {
return method.getFunctionNode().getReturnTypeNode();
}
// keep specializations in sync with other AbstractInferParentFromMethodNode subclasses
@@ -63,7 +63,7 @@ public abstract class InferParentWithinMethodNode extends AbstractInferParentFro
protected final Object evalCached(
@SuppressWarnings("unused") VirtualFrame frame,
@Cached("getMethod(frame)") @SuppressWarnings("unused") Method cachedMethod,
@Cached("getTypeNode(frame, cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode,
@Cached("getTypeNode(cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode,
@Cached(
"getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName())")
Object defaultValue) {
@@ -73,7 +73,7 @@ public abstract class InferParentWithinMethodNode extends AbstractInferParentFro
@Specialization(replaces = "evalCached")
protected final Object eval(VirtualFrame frame) {
var method = getMethod(frame);
var typeNode = getTypeNode(frame, method);
var typeNode = getTypeNode(method);
return getDefaultValue(frame, typeNode, method.getHeaderSection(), method.getQualifiedName());
}
}
@@ -59,8 +59,8 @@ public abstract class InferParentWithinObjectMethodNode extends AbstractInferPar
}
@Override
protected @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method) {
return method.getReturnTypeNode(frame);
protected @Nullable TypeNode getTypeNode(Method method) {
return method.getFunctionNode().getReturnTypeNode();
}
// keep specializations in sync with other AbstractInferParentFromMethodNode subclasses
@@ -70,7 +70,7 @@ public abstract class InferParentWithinObjectMethodNode extends AbstractInferPar
protected final Object evalCached(
@SuppressWarnings("unused") VirtualFrame frame,
@Cached("getMethod(frame)") @SuppressWarnings("unused") Method cachedMethod,
@Cached("getTypeNode(frame, cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode,
@Cached("getTypeNode(cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode,
@Cached(
"getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName())")
Object defaultValue) {
@@ -80,7 +80,7 @@ public abstract class InferParentWithinObjectMethodNode extends AbstractInferPar
@Specialization(replaces = "evalCached")
protected final Object eval(VirtualFrame frame) {
var method = getMethod(frame);
var typeNode = getTypeNode(frame, method);
var typeNode = getTypeNode(method);
return getDefaultValue(frame, typeNode, method.getHeaderSection(), method.getQualifiedName());
}
}
@@ -16,8 +16,10 @@
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
@@ -30,10 +32,20 @@ public final class InvokeLexicalClassMethodNode extends AbstractInvokeLexicalMet
SourceSection sourceSection,
Identifier methodName,
int levelsUp,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
boolean needsConst,
int methodSlot) {
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, methodSlot);
int methodSlot,
String qualifiedName) {
super(
sourceSection,
methodName,
levelsUp,
unresolvedTypeArgumentNodes,
argumentNodes,
needsConst,
methodSlot,
qualifiedName);
}
@Override
@@ -16,10 +16,12 @@
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.member.ObjectMethodNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
@@ -29,10 +31,20 @@ public final class InvokeLexicalObjectMethodNode extends AbstractInvokeLexicalMe
SourceSection sourceSection,
Identifier methodName,
int levelsUp,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
boolean needsConst,
int methodSlot) {
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, methodSlot);
int methodSlot,
String qualifiedName) {
super(
sourceSection,
methodName,
levelsUp,
unresolvedTypeArgumentNodes,
argumentNodes,
needsConst,
methodSlot,
qualifiedName);
}
@Override
@@ -18,8 +18,10 @@ package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.ClassMethod;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.VmObjectLike;
/** A non-virtual ("direct") method call. Used only for methods on {@code pkl:base}. */
@@ -34,9 +36,11 @@ public final class InvokeMethodDirectNode extends AbstractInvokeMethodNode {
SourceSection sourceSection,
ClassMethod method,
ExpressionNode receiverNode,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
int methodSlot) {
super(sourceSection, argumentNodes, methodSlot);
int methodSlot,
String qualifiedName) {
super(sourceSection, unresolvedTypeArgumentNodes, argumentNodes, methodSlot, qualifiedName);
this.method = method;
this.owner = method.getOwner();
this.receiverNode = receiverNode;
@@ -28,10 +28,12 @@ import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.MemberLookupMode;
import org.pkl.core.ast.internal.GetClassNode;
import org.pkl.core.ast.member.ClassMethod;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmClass;
import org.pkl.core.runtime.VmFunction;
@@ -50,11 +52,13 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode {
protected InvokeMethodVirtualNode(
SourceSection sourceSection,
Identifier methodName,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
MemberLookupMode lookupMode,
boolean needsConst,
int methodSlot) {
super(sourceSection, argumentNodes, methodSlot);
int methodSlot,
String qualifiedName) {
super(sourceSection, unresolvedTypeArgumentNodes, argumentNodes, methodSlot, qualifiedName);
this.methodName = methodName;
this.lookupMode = lookupMode;
this.needsConst = needsConst;
@@ -63,10 +67,20 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode {
protected InvokeMethodVirtualNode(
SourceSection sourceSection,
Identifier methodName,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
MemberLookupMode lookupMode,
int methodSlot) {
this(sourceSection, methodName, argumentNodes, lookupMode, false, methodSlot);
int methodSlot,
String qualifiedName) {
this(
sourceSection,
methodName,
unresolvedTypeArgumentNodes,
argumentNodes,
lookupMode,
false,
methodSlot,
qualifiedName);
}
/**
@@ -146,7 +160,16 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode {
@Override
public WrapperNode createWrapper(ProbeNode probe) {
return new InvokeMethodVirtualNodeWrapper(
sourceSection, methodName, argumentNodes, lookupMode, needsConst, methodSlot, this, probe);
sourceSection,
methodName,
unresolvedTypeArgumentNodes,
argumentNodes,
lookupMode,
needsConst,
methodSlot,
qualifiedName,
this,
probe);
}
private void checkConst(ClassMethod method) {
@@ -16,8 +16,10 @@
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
@@ -26,11 +28,21 @@ public final class InvokeQualifiedClassMethodNode extends AbstractInvokeQualifie
public InvokeQualifiedClassMethodNode(
SourceSection sourceSection,
Identifier methodName,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
boolean needsConst,
ExpressionNode getReceiverNode,
int methodSlot) {
super(sourceSection, methodName, argumentNodes, needsConst, getReceiverNode, methodSlot);
int methodSlot,
String qualifiedName) {
super(
sourceSection,
methodName,
unresolvedTypeArgumentNodes,
argumentNodes,
needsConst,
getReceiverNode,
methodSlot,
qualifiedName);
}
@Override
@@ -16,10 +16,12 @@
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.member.ObjectMethodNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
@@ -28,11 +30,21 @@ public final class InvokeQualifiedObjectMethodNode extends AbstractInvokeQualifi
public InvokeQualifiedObjectMethodNode(
SourceSection sourceSection,
Identifier methodName,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
boolean needsConst,
ExpressionNode getReceiverNode,
int methodSlot) {
super(sourceSection, methodName, argumentNodes, needsConst, getReceiverNode, methodSlot);
int methodSlot,
String qualifiedName) {
super(
sourceSection,
methodName,
unresolvedTypeArgumentNodes,
argumentNodes,
needsConst,
getReceiverNode,
methodSlot,
qualifiedName);
}
@Override
@@ -21,8 +21,10 @@ 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.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.ClassMethod;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmFunction;
import org.pkl.core.runtime.VmUtils;
@@ -34,10 +36,12 @@ public abstract class InvokeSuperMethodNode extends AbstractInvokeMethodNode {
protected InvokeSuperMethodNode(
SourceSection sourceSection,
Identifier methodName,
UnresolvedTypeNode @Nullable [] unresolvedTypeArgumentNodes,
ExpressionNode[] argumentNodes,
boolean needsConst,
int methodSlot) {
super(sourceSection, argumentNodes, methodSlot);
int methodSlot,
String qualifiedName) {
super(sourceSection, unresolvedTypeArgumentNodes, argumentNodes, methodSlot, qualifiedName);
this.needsConst = needsConst;
assert !methodName.isLocalMethod();
@@ -35,6 +35,12 @@ public final class GetModuleNode extends ExpressionNode {
public Object executeGeneric(VirtualFrame frame) {
CompilerDirectives.transferToInterpreter();
var levelsUp = getLevelsUp(frame);
return replace(levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp))
.executeGeneric(frame);
}
public static int getLevelsUp(VirtualFrame frame) {
var levelsUp = -1;
for (var current = VmUtils.getOwner(frame);
current != null;
@@ -43,8 +49,6 @@ public final class GetModuleNode extends ExpressionNode {
levelsUp += 1;
}
}
return replace(levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp))
.executeGeneric(frame);
return levelsUp;
}
}
@@ -90,9 +90,11 @@ public abstract class ToStringNode extends UnaryExpressionNode {
return InvokeMethodVirtualNodeGen.create(
sourceSection,
Identifier.TO_STRING,
null,
new ExpressionNode[] {},
MemberLookupMode.EXPLICIT_RECEIVER,
-1,
"",
null,
null);
}
@@ -18,19 +18,17 @@ package org.pkl.core.ast.member;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.pkl.core.PClass;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.runtime.*;
import org.pkl.core.runtime.TypeParameter;
import org.pkl.core.util.LateInit;
public final class ClassMethod extends ClassMember implements Method {
private final List<TypeParameter> typeParameters;
private final TypeParameter[] typeParameters;
// null = not deprecated, "" = no/empty message in the @Deprecated body
private final @Nullable String deprecation;
@@ -46,7 +44,7 @@ public final class ClassMethod extends ClassMember implements Method {
SourceSection @Nullable [] docComment,
List<VmTyped> annotations,
VmTyped owner,
List<TypeParameter> typeParameters,
TypeParameter[] typeParameters,
@Nullable String deprecation) {
super(
@@ -68,6 +66,14 @@ public final class ClassMethod extends ClassMember implements Method {
this.functionNode = functionNode;
}
@Override
public FunctionNode getFunctionNode(@Nullable SourceSection callSite) {
if (callSite != null && deprecation != null) {
reportDeprecation(callSite);
}
return functionNode;
}
public CallTarget getCallTarget() {
return functionNode.getCallTarget();
}
@@ -101,13 +107,14 @@ public final class ClassMethod extends ClassMember implements Method {
return functionNode.getParameterCount();
}
public @Nullable TypeNode getReturnTypeNode() {
return functionNode.getReturnTypeNode();
@Override
public int getTypeParameterCount() {
return functionNode.getTypeParameterCount();
}
@Override
public @Nullable TypeNode getReturnTypeNode(VirtualFrame frame) {
return functionNode.getReturnTypeNode();
public void ensureInitialized(VmObjectLike owner) {
// do nothing; class methods are always initialized
}
@Override
@@ -140,10 +147,7 @@ public final class ClassMethod extends ClassMember implements Method {
}
public PClass.Method export(PClass owner) {
return functionNode.export(owner, docComment, annotations, modifiers, typeParameters);
}
public @Nullable TypeNode getParameterTypeNode(VirtualFrame frame, int idx) {
return functionNode.getParameterTypeNode(idx);
return functionNode.export(
owner, docComment, annotations, modifiers, TypeParameter.export(typeParameters));
}
}
@@ -21,15 +21,14 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.source.SourceSection;
import java.util.ArrayList;
import java.util.List;
import org.graalvm.collections.EconomicMap;
import org.jspecify.annotations.Nullable;
import org.pkl.core.PClassInfo;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.*;
import org.pkl.core.runtime.TypeParameter;
import org.pkl.core.util.LateInit;
@NodeInfo(shortName = "class")
@@ -39,7 +38,7 @@ public final class ClassNode extends ExpressionNode {
@Children private final ExpressionNode[] annotationNodes;
private final int modifiers;
private final PClassInfo<?> classInfo;
private final List<TypeParameter> typeParameters;
private final TypeParameter[] typeParameters;
private final @Nullable ModuleInfo moduleInfo;
// null iff this class is pkl.base#Any
@Child private @Nullable UnresolvedTypeNode unresolvedSupertypeNode;
@@ -57,7 +56,7 @@ public final class ClassNode extends ExpressionNode {
ExpressionNode[] annotationNodes,
int modifiers,
PClassInfo<?> classInfo,
List<TypeParameter> typeParameters,
TypeParameter[] typeParameters,
@Nullable ModuleInfo moduleInfo,
@Nullable UnresolvedTypeNode unresolvedSupertypeNode,
EconomicMap<Object, ObjectMember> prototypeMembers,
@@ -26,10 +26,10 @@ import org.jspecify.annotations.Nullable;
import org.pkl.core.Member.SourceLocation;
import org.pkl.core.PClass;
import org.pkl.core.PType;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.TypeParameterNode;
import org.pkl.core.runtime.*;
import org.pkl.core.runtime.VmType.UnknownType;
import org.pkl.core.util.CollectionUtils;
@@ -37,21 +37,25 @@ import org.pkl.core.util.Pair;
public final class FunctionNode extends RegularMemberNode {
// Every function (and property) call passes two implicit arguments at positions
// frame.getArguments()[0] and [1]:
// frame.getArguments()[0], [1]:
// - the receiver (target) of the call, of type Object (see VmUtils.getReceiver())
// - the owner (lexically enclosing object) of the function/property definition, of type VmTyped
// (see VmUtils.getOwner())
// For VmObject receivers, the owner is the same as or an ancestor of the receiver.
// For other receivers, the owner is the prototype of the receiver's class.
// The chain of enclosing owners forms a function/property's lexical scope.
//
// After the implicit args, the method's type args are passed, in order.
// If the call does not contain type arguments, these args are null.
//
// Finally, the method's arguments are passed, in order.
private static final int IMPLICIT_PARAM_COUNT = 2;
private final int paramCount;
private final int typeParamCount;
private final int totalParamCount;
@Children private TypeParameterNode[] typeParameterNodes;
@Children private final TypeNode[] parameterTypeNodes;
@Child private @Nullable TypeNode checkedReturnTypeNode;
private @Nullable TypeNode returnTypeNode;
private @Nullable TypeNode checkedReturnTypeNode;
@Child private @Nullable TypeNode returnTypeNode;
@TruffleBoundary
public FunctionNode(
@@ -62,6 +66,7 @@ public final class FunctionNode extends RegularMemberNode {
TypeNode[] parameterTypeNodes,
@Nullable TypeNode returnTypeNode,
boolean isReturnTypeChecked,
TypeParameterNode[] typeParameterNodes,
ExpressionNode bodyNode) {
super(language, descriptor, member, bodyNode);
@@ -74,14 +79,21 @@ public final class FunctionNode extends RegularMemberNode {
this.parameterTypeNodes = parameterTypeNodes;
this.checkedReturnTypeNode = isReturnTypeChecked ? returnTypeNode : null;
this.returnTypeNode = returnTypeNode;
this.typeParameterNodes = typeParameterNodes;
totalParamCount = Math.addExact(IMPLICIT_PARAM_COUNT, paramCount);
typeParamCount = typeParameterNodes.length;
totalParamCount =
Math.addExact(IMPLICIT_PARAM_COUNT, Math.addExact(paramCount, typeParamCount));
}
public int getParameterCount() {
return paramCount;
}
public int getTypeParameterCount() {
return typeParameterNodes.length;
}
public @Nullable TypeNode getParameterTypeNode(int idx) {
if (idx >= paramCount) return null;
return parameterTypeNodes[idx];
@@ -112,14 +124,20 @@ public final class FunctionNode extends RegularMemberNode {
@Override
@ExplodeLoop
protected Object executeImpl(VirtualFrame frame) {
var totalArgCount = frame.getArguments().length;
var args = frame.getArguments();
var totalArgCount = args.length;
if (totalArgCount != totalParamCount) {
CompilerDirectives.transferToInterpreter();
throw wrongArgumentCount(totalArgCount - IMPLICIT_PARAM_COUNT);
throw wrongArgumentCount(totalArgCount - typeParamCount - IMPLICIT_PARAM_COUNT);
}
for (var i = 0; i < typeParamCount; i++) {
var typeArg = args[IMPLICIT_PARAM_COUNT + i];
typeParameterNodes[i].executeAndSet(frame, typeArg);
}
for (var i = 0; i < parameterTypeNodes.length; i++) {
var argument = frame.getArguments()[IMPLICIT_PARAM_COUNT + i];
var argument = args[IMPLICIT_PARAM_COUNT + typeParamCount + i];
parameterTypeNodes[i].executeAndSet(frame, argument);
}
@@ -127,6 +145,19 @@ public final class FunctionNode extends RegularMemberNode {
if (checkedReturnTypeNode != null) {
return checkedReturnTypeNode.execute(frame, result);
} else if (returnTypeNode != null && typeParamCount > 0 && args[IMPLICIT_PARAM_COUNT] != null) {
// Return types are not typically checked for stdlib methods (isReturnTypeChecked == false)
// but for generic stdlib methods (e.g. `List.map()`) when they are called with type args,
// this behavior needs to be overridden so the type arg check on return type still runs.
// otherwise, this code would succeed instead of failing:
// ```
// res = List("foo", "hello", "a").map::<String>((it) -> it.length)
// ```
// Assumption: this does not need to account for type args in enclosing frames because
// no type parameters exist in the stdlib that would require this.
// Assumption-breaking pattern example: snippet `callWithTypeArguments.pkl` property
// `lexical.nestedInMethodParam`.
return returnTypeNode.execute(frame, result);
}
return result;
@@ -153,7 +184,7 @@ public final class FunctionNode extends RegularMemberNode {
SourceSection @Nullable [] docComment,
List<VmTyped> annotations,
int modifiers,
List<TypeParameter> typeParameters) {
List<org.pkl.core.TypeParameter> typeParameters) {
var parameters = CollectionUtils.<String, PType>newLinkedHashMap(paramCount);
for (var i = 0; i < paramCount; i++) {
@@ -16,6 +16,7 @@
package org.pkl.core.ast.member;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.PklRootNode;
@@ -51,6 +52,7 @@ public final class ListingOrMappingTypeCastNode extends PklRootNode {
@Override
protected Object executeImpl(VirtualFrame frame) {
return typeNode.execute(frame, frame.getArguments()[2]);
var enclosingFrame = (MaterializedFrame) frame.getArguments()[2];
return typeNode.execute(enclosingFrame, frame.getArguments()[3]);
}
}
@@ -16,20 +16,24 @@
package org.pkl.core.ast.member;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.runtime.VmObjectLike;
public interface Method {
CallTarget getCallTarget(SourceSection callSite, VmObjectLike owner);
FunctionNode getFunctionNode(@Nullable SourceSection callSite);
@Nullable TypeNode getParameterTypeNode(VirtualFrame frame, int idx);
@Nullable TypeNode getReturnTypeNode(VirtualFrame frame);
default FunctionNode getFunctionNode() {
return getFunctionNode(null);
}
SourceSection getHeaderSection();
String getQualifiedName();
void ensureInitialized(VmObjectLike owner);
int getTypeParameterCount();
CallTarget getCallTarget(SourceSection callSite, VmObjectLike owner);
}
@@ -18,18 +18,20 @@ package org.pkl.core.ast.member;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.*;
import org.pkl.core.runtime.TypeParameter;
public final class ObjectMethodNode extends RegularMemberNode implements Method {
private final VmLanguage language;
private final int parameterCount;
private final TypeParameter[] typeParameters;
@Children private final @Nullable UnresolvedTypeNode[] unresolvedParameterTypeNodes;
@Child private @Nullable UnresolvedTypeNode unresolvedReturnTypeNode;
@@ -41,26 +43,38 @@ public final class ObjectMethodNode extends RegularMemberNode implements Method
ObjectMember member,
ExpressionNode bodyNode,
int parameterCount,
TypeParameter[] typeParameters,
@Nullable UnresolvedTypeNode[] unresolvedParameterTypeNodes,
@Nullable UnresolvedTypeNode unresolvedReturnTypeNode) {
super(language, descriptor, member, bodyNode);
this.language = language;
this.parameterCount = parameterCount;
this.typeParameters = typeParameters;
this.unresolvedParameterTypeNodes = unresolvedParameterTypeNodes;
this.unresolvedReturnTypeNode = unresolvedReturnTypeNode;
}
public @Nullable TypeNode getReturnTypeNode() {
// this method is only called from child nodes
assert functionNode != null;
return functionNode.getReturnTypeNode();
@Override
public FunctionNode getFunctionNode(@Nullable SourceSection callSite) {
assert isInitialized() : "getFunctionNode() called when not initialized";
return functionNode;
}
@Override
public @Nullable TypeNode getReturnTypeNode(VirtualFrame frame) {
return getFunctionNode(frame, true).getReturnTypeNode();
protected CallTarget executeImpl(VirtualFrame frame) {
return getFunctionNode(frame).getCallTarget();
}
@Override
public void ensureInitialized(VmObjectLike owner) {
if (!isInitialized()) {
getCallTarget().call(owner, owner);
}
}
public boolean isInitialized() {
return functionNode != null;
}
@Override
@@ -69,28 +83,20 @@ public final class ObjectMethodNode extends RegularMemberNode implements Method
}
@Override
public @Nullable TypeNode getParameterTypeNode(VirtualFrame frame, int idx) {
return getFunctionNode(frame, true).getParameterTypeNode(idx);
@TruffleBoundary
public int getTypeParameterCount() {
return typeParameters.length;
}
@Override
protected CallTarget executeImpl(VirtualFrame frame) {
return getFunctionNode(frame, false).getCallTarget();
}
private FunctionNode getFunctionNode(VirtualFrame frame, boolean isPreInit) {
public FunctionNode getFunctionNode(VirtualFrame frame) {
if (functionNode != null) return functionNode;
CompilerDirectives.transferToInterpreterAndInvalidate();
if (isPreInit) {
// TODO: with VmType/CreateDefaultValueNode this may be removable
adoptChildren();
}
var parameterTypeNodes =
VmUtils.resolveParameterTypes(frame, getFrameDescriptor(), unresolvedParameterTypeNodes);
var returnTypeNode =
unresolvedReturnTypeNode != null ? unresolvedReturnTypeNode.execute(frame) : null;
var typeParamterNodes = VmUtils.resolveTypeParameters(typeParameters);
functionNode =
new FunctionNode(
@@ -101,6 +107,7 @@ public final class ObjectMethodNode extends RegularMemberNode implements Method
parameterTypeNodes,
returnTypeNode,
true,
typeParamterNodes,
bodyNode);
return functionNode;
}
@@ -21,13 +21,12 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.expression.primary.ExecuteCustomThisWithRootNode;
import org.pkl.core.ast.expression.primary.GetTypeAliasModuleNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.TypeParameter;
import org.pkl.core.runtime.VmTypeAlias;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
@@ -39,7 +38,7 @@ public final class TypeAliasNode extends ExpressionNode {
private final int modifiers;
private final String simpleName;
private final String qualifiedName;
private final List<TypeParameter> typeParameters;
private final TypeParameter[] typeParameters;
private @Child UnresolvedTypeNode typeAnnotationNode;
// use same caching scheme as ClassNode
@@ -53,7 +52,7 @@ public final class TypeAliasNode extends ExpressionNode {
int modifiers,
String simpleName,
String qualifiedName,
List<TypeParameter> typeParameters,
TypeParameter[] typeParameters,
UnresolvedTypeNode typeAnnotationNode) {
super(sourceSection);
this.headerSection = headerSection;
@@ -21,6 +21,7 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.PklNode;
import org.pkl.core.ast.type.TypeParameterNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.*;
@@ -69,6 +70,7 @@ public final class UnresolvedFunctionNode extends PklNode {
parameterTypeNodes,
returnTypeNode,
true,
new TypeParameterNode[0],
bodyNode);
}
}
@@ -19,17 +19,16 @@ import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.*;
import org.pkl.core.runtime.TypeParameter;
public final class UnresolvedMethodNode extends UnresolvedClassMemberNode {
private final int parameterCount;
private final List<TypeParameter> typeParameters;
private final TypeParameter[] typeParameters;
@Children private final @Nullable UnresolvedTypeNode[] unresolvedParameterTypeNodes;
@Child private @Nullable UnresolvedTypeNode unresolvedReturnTypeNode;
private final boolean isReturnTypeChecked;
@@ -46,7 +45,7 @@ public final class UnresolvedMethodNode extends UnresolvedClassMemberNode {
Identifier name,
String qualifiedName,
int parameterCount,
List<TypeParameter> typeParameters,
TypeParameter[] typeParameters,
@Nullable UnresolvedTypeNode[] unresolvedParameterTypeNodes,
@Nullable UnresolvedTypeNode unresolvedReturnTypeNode,
boolean isReturnTypeChecked,
@@ -92,6 +91,7 @@ public final class UnresolvedMethodNode extends UnresolvedClassMemberNode {
VmUtils.resolveParameterTypes(frame, descriptor, unresolvedParameterTypeNodes);
var returnTypeNode =
unresolvedReturnTypeNode != null ? unresolvedReturnTypeNode.execute(frame) : null;
var typeParameterNodes = VmUtils.resolveTypeParameters(typeParameters);
String deprecation = null;
for (var annotation : annotations) {
@@ -124,6 +124,7 @@ public final class UnresolvedMethodNode extends UnresolvedClassMemberNode {
parameterTypeNodes,
returnTypeNode,
isReturnTypeChecked,
typeParameterNodes,
bodyNode);
method.initFunctionNode(functionNode);
@@ -0,0 +1,69 @@
/*
* 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.ast.type;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.dsl.Cached;
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.nodes.IndirectCallNode;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.PklNode;
import org.pkl.core.runtime.VmTypeArgument;
public abstract class CallTypeArgumentNode extends PklNode {
public CallTypeArgumentNode(SourceSection sourceSection) {
super(sourceSection);
}
public abstract Object execute(
VirtualFrame frame, VmTypeArgument typeArgument, Object value, boolean lazy);
@Specialization(guards = {"typeArgument.getCallTarget() == cachedCallTarget"})
protected Object evalDirect(
@SuppressWarnings("unused") VmTypeArgument typeArgument,
Object value,
boolean lazy,
@Cached("typeArgument.getCallTarget()") @SuppressWarnings("unused")
CallTarget cachedCallTarget,
@Cached("create(cachedCallTarget)") DirectCallNode callNode) {
var enclosingFrame = typeArgument.getEnclosingFrame();
if (enclosingFrame == null) {
return callNode.call(null, null, null, value, lazy);
}
var arguments = enclosingFrame.getArguments();
return callNode.call(arguments[0], arguments[1], enclosingFrame, value, lazy);
}
@Specialization(replaces = "evalDirect")
protected Object eval(
VmTypeArgument typeArgument,
Object value,
boolean lazy,
@Cached("create()") IndirectCallNode callNode) {
var enclosingFrame = typeArgument.getEnclosingFrame();
if (enclosingFrame == null) {
return callNode.call(typeArgument.getCallTarget(), null, null, null, value, lazy);
}
var arguments = enclosingFrame.getArguments();
return callNode.call(
typeArgument.getCallTarget(), arguments[0], arguments[1], enclosingFrame, value, lazy);
}
}
@@ -0,0 +1,69 @@
/*
* 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.ast.type;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.PklRootNode;
import org.pkl.core.runtime.VmLanguage;
public final class TypeArgumentTypeNode extends PklRootNode {
private final SourceSection sourceSection;
private final String qualifiedName;
@Child private TypeNode typeNode;
public TypeArgumentTypeNode(
VmLanguage language,
FrameDescriptor frameDescriptor,
SourceSection sourceSection,
String qualifiedName,
TypeNode typeNode) {
super(language, frameDescriptor, true);
this.sourceSection = sourceSection;
this.qualifiedName = qualifiedName;
this.typeNode = typeNode;
}
@Override
public SourceSection getSourceSection() {
return sourceSection;
}
@Override
public String getName() {
return qualifiedName;
}
public TypeNode getTypeNode() {
return typeNode;
}
@Override
protected Object executeImpl(VirtualFrame frame) {
var enclosingFrame = (MaterializedFrame) frame.getArguments()[2];
var value = frame.getArguments()[3];
var lazy = (boolean) frame.getArguments()[4];
if (enclosingFrame == null) {
return lazy ? typeNode.executeLazily(frame, value) : typeNode.executeEagerly(frame, value);
}
return lazy
? typeNode.executeLazily(enclosingFrame, value)
: typeNode.executeEagerly(enclosingFrame, value);
}
}
@@ -30,10 +30,8 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.LoopNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeUtil;
import com.oracle.truffle.api.profiles.LoopConditionProfile;
import com.oracle.truffle.api.source.SourceSection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -43,8 +41,6 @@ import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.pkl.core.PType;
import org.pkl.core.PklBugException;
import org.pkl.core.StackFrame;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.*;
import org.pkl.core.ast.expression.primary.GetModuleNode;
import org.pkl.core.ast.expression.primary.GetReceiverClassNode;
@@ -57,12 +53,12 @@ import org.pkl.core.ast.member.ListingOrMappingTypeCastNode;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.ast.member.UntypedObjectMemberNode;
import org.pkl.core.runtime.*;
import org.pkl.core.runtime.TypeParameter.OwnerType;
import org.pkl.core.stdlib.VmObjectFactory;
import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.EconomicSets;
import org.pkl.core.util.LateInit;
import org.pkl.core.util.MutableBoolean;
import org.pkl.core.util.MutableReference;
public abstract class TypeNode extends PklNode {
@CompilationFinal private @Nullable VmType type;
@@ -167,10 +163,23 @@ public abstract class TypeNode extends PklNode {
// header section of the property or method that carries the type annotation
SourceSection headerSection,
// qualified name of the property or method that carries the type annotation
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return null;
}
// method arguments are used when default value contains a root node
public final @Nullable Object createDefaultValue(
VirtualFrame frame,
VmLanguage language,
// header section of the property or method that carries the type annotation
SourceSection headerSection,
// qualified name of the property or method that carries the type annotation
String qualifiedName) {
return createDefaultValue(frame, language, headerSection, qualifiedName, false, null);
}
@Idempotent
public final boolean isFinalType() {
var ret = new MutableBoolean(true);
@@ -178,7 +187,9 @@ public abstract class TypeNode extends PklNode {
true,
typeNode -> {
// assumption: don't need to worry about `NonFinalClassTypeNode`
if (typeNode instanceof NonFinalSelfTypeNode) {
if (typeNode instanceof NonFinalSelfTypeNode
|| (typeNode instanceof TypeVariableNode typeVariable
&& typeVariable.ownerType == TypeParameter.OwnerType.METHOD)) {
ret.set(false);
return false;
}
@@ -201,6 +212,24 @@ public abstract class TypeNode extends PklNode {
return ret.get();
}
public final boolean getTypeArgumentRequiresFrame() {
var ret = new MutableBoolean(false);
acceptTypeNode(
true,
typeNode -> {
if (typeNode instanceof ConstrainedTypeNode
|| typeNode instanceof NonFinalSelfTypeNode
|| typeNode instanceof ListingOrMappingTypeNode // lazy checks need captured frame
|| (typeNode instanceof TypeVariableNode typeVar
&& typeVar.ownerType == TypeParameter.OwnerType.METHOD)) {
ret.set(true);
return false;
}
return true;
});
return ret.get();
}
/** Visit child type nodes of this type. */
protected abstract boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer);
@@ -451,6 +480,7 @@ public abstract class TypeNode extends PklNode {
return mirrorFactory.create(null);
}
@Override
protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer) {
return consumer.accept(this);
}
@@ -460,7 +490,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return TypeNode.createDefaultValue(clazz);
}
}
@@ -530,7 +562,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
var clazz = ((VmObjectLike) getTargetNode.executeGeneric(frame)).getVmClass();
return TypeNode.createDefaultValue(clazz);
}
@@ -571,7 +605,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return literal;
}
@@ -625,7 +661,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return VmDynamic.empty();
}
@@ -672,7 +710,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return TypeNode.createDefaultValue(clazz);
}
@@ -736,7 +776,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return TypeNode.createDefaultValue(clazz);
}
@@ -777,9 +819,12 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return VmNull.withDefault(
elementTypeNode.createDefaultValue(frame, language, headerSection, qualifiedName));
elementTypeNode.createDefaultValue(
frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides));
}
@Override
@@ -849,12 +894,14 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return defaultIndex == -1
? null
: elementTypeNodes[defaultIndex].createDefaultValue(
frame, language, headerSection, qualifiedName);
frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides);
}
/**
@@ -1190,7 +1237,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
if (defaultIndex == -1) {
return null;
}
@@ -1247,7 +1296,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return VmList.EMPTY;
}
@@ -1330,7 +1381,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return VmList.EMPTY;
}
@@ -1401,7 +1454,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return VmSet.EMPTY;
}
@@ -1481,7 +1536,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return VmMap.EMPTY;
}
@@ -1554,7 +1611,7 @@ public abstract class TypeNode extends PklNode {
return vmListing;
}
return new VmListing(
vmListing.getEnclosingFrame(),
frame.materialize(),
vmListing,
EconomicMaps.emptyMap(),
vmListing.getLength(),
@@ -1613,7 +1670,7 @@ public abstract class TypeNode extends PklNode {
return vmMapping;
}
return new VmMapping(
vmMapping.getEnclosingFrame(),
frame.materialize(),
vmMapping,
EconomicMaps.emptyMap(),
getValueTypeCastNode(frame.getFrameDescriptor()),
@@ -1775,14 +1832,17 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
if (valueTypeNode instanceof UnknownTypeNode) {
return newEmptyListingOrMapping();
}
var defaultMemberValue =
valueTypeNode.createDefaultValue(frame, language, headerSection, qualifiedName);
valueTypeNode.createDefaultValue(
frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides);
var defaultMember = createDefaultMember(headerSection, qualifiedName, defaultMemberValue);
@@ -1984,7 +2044,7 @@ public abstract class TypeNode extends PklNode {
}
}
public abstract static class ReferenceTypeNode extends ValidatingObjectSlotTypeNode {
public abstract static class ReferenceTypeNode extends ObjectSlotTypeNode {
@Child private TypeNode domainTypeNode;
@Child private TypeNode referentTypeNode;
@Child private ExpressionNode getReceiverClassNode;
@@ -1997,7 +2057,6 @@ public abstract class TypeNode extends PklNode {
this.referentTypeNode = referentTypeNode;
this.getReceiverClassNode = new GetReceiverClassNode(sourceSection);
this.getModuleNode = new GetModuleNode(sourceSection);
validate();
}
@Override
@@ -2006,55 +2065,29 @@ public abstract class TypeNode extends PklNode {
RefModule.getReferenceClass(), domainTypeNode.getType(), referentTypeNode.getType());
}
@Override
public final String getValidationErrorKey() {
return "invalidReferenceTypeAnnotationWithConstraint";
}
@Override
protected final @Nullable Node getViolatingNode() {
// constraints may not be used in Reference type annotation referents
// walk the type and throw if any part of the referent is constrained
var violation = new MutableReference<Node>(null);
referentTypeNode.acceptTypeNode(
true,
(typeNode) -> {
if (typeNode instanceof ConstrainedTypeNode) {
violation.set(typeNode);
return false;
}
return true;
});
return violation.getOrNull();
}
@Override
protected final boolean isIncludedInTrace(Node node) {
return node instanceof ReferenceTypeNode || node instanceof ConstrainedTypeNode;
}
@Specialization
protected Object eval(VirtualFrame frame, VmReference value) {
if (domainTypeNode.isNoopTypeCheck() && referentTypeNode.isNoopTypeCheck()) {
return value;
}
var realType = (VmType.ClassType) getType().reify(frame);
try {
domainTypeNode.execute(frame, value.getDomain());
} catch (VmTypeMismatchException e) {
CompilerDirectives.transferToInterpreter();
throw typeMismatch(value, getType());
throw typeMismatch(value, realType);
}
var module = (VmTyped) getModuleNode.executeGeneric(frame);
if (value.referentTypeIsSubtypeOf(
referentTypeNode.getType(),
realType.getTypeArguments()[1],
(VmClass) getReceiverClassNode.executeGeneric(frame),
module.getVmClass())) {
return value;
}
throw typeMismatch(value, getType());
throw typeMismatch(value, realType);
}
@Fallback
@@ -2151,7 +2184,9 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("internalStdLibClass", "VarArgs")
@@ -2173,24 +2208,34 @@ public abstract class TypeNode extends PklNode {
public static final class TypeVariableNode extends WriteFrameSlotTypeNode {
private final TypeParameter typeParameter;
private final int levelsUp;
private final TypeParameter.OwnerType ownerType;
@Child private CallTypeArgumentNode callTypeArgumentNode;
public TypeVariableNode(SourceSection sourceSection, TypeParameter typeParameter) {
public TypeVariableNode(
SourceSection sourceSection,
TypeParameter typeParameter,
TypeParameter.OwnerType ownerType,
int levelsUp) {
super(sourceSection);
this.typeParameter = typeParameter;
this.ownerType = ownerType;
this.levelsUp = levelsUp;
callTypeArgumentNode = CallTypeArgumentNodeGen.create(sourceSection);
}
@Override
protected VmType doGetType() {
return new VmType.TypeVariableType(typeParameter);
return new VmType.TypeVariableType(typeParameter, ownerType, levelsUp);
}
public int getTypeParameterIndex() {
return typeParameter.getIndex();
public TypeParameter getTypeParameter() {
return typeParameter;
}
@Override
public boolean isNoopTypeCheck() {
return true;
return levelsUp < 0 || ownerType != TypeParameter.OwnerType.METHOD;
}
@Override
@@ -2202,16 +2247,77 @@ public abstract class TypeNode extends PklNode {
return MirrorFactories.typeParameterFactory.create(typeParameter);
}
@Override
public Object executeEagerly(VirtualFrame frame, Object value) {
return doExecute(frame, value, false);
}
@Override
protected Object executeLazily(VirtualFrame frame, Object value) {
// do nothing
return value;
return doExecute(frame, value, true);
}
private Object doExecute(VirtualFrame frame, Object value, boolean lazy) {
if (ownerType != OwnerType.METHOD) return value;
var typeArg = VmUtils.getTypeArgumentOrNull(frame, typeParameter.slot(), levelsUp);
if (typeArg == null) return value;
try {
return callTypeArgumentNode.execute(frame, typeArg, value, lazy);
} catch (VmTypeMismatchException e) {
CompilerDirectives.transferToInterpreter();
if (!VmUtils.sourceSectionContains(sourceSection, e.sourceSection)) {
var rootNode = getRootNode();
e.putInsertedStackFrame(
rootNode.getCallTarget(),
VmUtils.createStackFrame(sourceSection, rootNode.getName()));
}
throw e;
}
}
@Override
protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer) {
return consumer.accept(this);
}
private @Nullable VmTypeArgument getTypeArgumentForDefaultValue(
VirtualFrame frame, boolean isPreCall, VmTypeArgument @Nullable [] typeArgumentOverrides) {
// when creating default value for a method argument, the type node is 1 level _lower_ than
// what was resolved lexically.
//
// edge case: if `levelsUp == 0`, we need to resolve the type argument from
// `typeArgumentOverrides` rather than the frame.
//
// e.g.
// ```
// function foo<T>(arg: T)
// res = foo::<Person>(new {}) // `Person` is passed in via `typeArgumentOverrides`
// ```
if (levelsUp == 0 && isPreCall) {
return typeArgumentOverrides == null ? null : typeArgumentOverrides[typeParameter.index()];
}
return VmUtils.getTypeArgumentOrNull(frame, typeParameter.slot(), levelsUp);
}
@Override
public @Nullable Object createDefaultValue(
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
if (ownerType != OwnerType.METHOD) return null;
var typeArg = getTypeArgumentForDefaultValue(frame, isPreCall, typeArgumentOverrides);
if (typeArg != null) {
return typeArg.createDefaultValue(language, headerSection, qualifiedName);
}
return null;
}
}
public static final class NonNullTypeAliasTypeNode extends WriteFrameSlotTypeNode {
@@ -2418,13 +2524,6 @@ public abstract class TypeNode extends PklNode {
this.typeAlias = typeAlias;
this.typeArgumentNodes = typeArgumentNodes;
aliasedTypeNode = typeAlias.instantiate(typeArgumentNodes);
aliasedTypeNode.accept(
node -> {
if (node instanceof ValidatingObjectSlotTypeNode typeNode) {
typeNode.validate(this);
}
return true;
});
}
@Override
@@ -2521,12 +2620,15 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
if (typeAlias == BaseModule.getMixinTypeAlias()) {
return newMixin(language, qualifiedName);
}
return aliasedTypeNode.createDefaultValue(frame, language, headerSection, qualifiedName);
return aliasedTypeNode.createDefaultValue(
frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides);
}
@Override
@@ -2613,9 +2715,11 @@ public abstract class TypeNode extends PklNode {
VirtualFrame frame,
VmLanguage language,
SourceSection headerSection,
String qualifiedName) {
return childNode.createDefaultValue(frame, language, headerSection, qualifiedName);
String qualifiedName,
boolean isPreCall,
VmTypeArgument @Nullable [] typeArgumentOverrides) {
return childNode.createDefaultValue(
frame, language, headerSection, qualifiedName, isPreCall, typeArgumentOverrides);
}
public SourceSection getBaseTypeSection() {
@@ -2914,78 +3018,6 @@ public abstract class TypeNode extends PklNode {
}
}
public abstract static class ValidatingObjectSlotTypeNode extends ObjectSlotTypeNode {
protected ValidatingObjectSlotTypeNode(SourceSection sourceSection) {
super(sourceSection);
}
protected abstract String getValidationErrorKey();
protected abstract @Nullable Node getViolatingNode();
protected final void validate() {
var violation = getViolatingNode();
if (violation == null) return;
throw exceptionBuilder()
.evalError(getValidationErrorKey())
.withLeadingStackFrames(buildLeadingFrames(violation, getSourceSection(), null))
.build();
}
public final void validate(TypeAliasTypeNode outermostAliasNode) {
var violation = getViolatingNode();
if (violation == null) return;
throw exceptionBuilder()
.withLocation(outermostAliasNode)
.evalError(getValidationErrorKey())
.withLeadingStackFrames(
buildLeadingFrames(
violation,
outermostAliasNode.getSourceSection(),
outermostAliasNode.getTypeAlias()))
.build();
}
protected abstract boolean isIncludedInTrace(Node node);
private List<StackFrame> buildLeadingFrames(
Node violatingNode, SourceSection usageSection, @Nullable VmTypeAlias outermostAlias) {
var frames = new ArrayList<StackFrame>();
for (var node = violatingNode; node != null; node = node.getParent()) {
if (!(node instanceof TypeAliasTypeNode || isIncludedInTrace(node))) continue;
var section = node.getSourceSection();
if (section == null || !section.isAvailable() || isWithin(usageSection, section)) {
continue;
}
var owner = ownerAlias(node, outermostAlias);
if (owner != null) {
frames.add(VmUtils.createStackFrame(section, owner.getQualifiedName()));
}
}
return frames;
}
/**
* The type alias whose body contains {@code node}: the nearest enclosing alias, else the
* outermost alias being instantiated (which is {@code null} when no alias is used).
*/
@SuppressWarnings("DataFlowIssue")
private static @Nullable VmTypeAlias ownerAlias(
Node node, @Nullable VmTypeAlias outermostAlias) {
var parent = NodeUtil.findParent(node, TypeAliasTypeNode.class);
//noinspection ConstantValue
return parent != null ? parent.getTypeAlias() : outermostAlias;
}
private static boolean isWithin(SourceSection outer, SourceSection inner) {
return inner.getSource().equals(outer.getSource())
&& inner.getCharIndex() >= outer.getCharIndex()
&& inner.getCharEndIndex() <= outer.getCharEndIndex();
}
}
public static VmType[] toTypes(TypeNode[] typeNodes) {
return toTypes(typeNodes, typeNodes.length);
}
@@ -0,0 +1,34 @@
/*
* 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.ast.type;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.pkl.core.ast.PklNode;
import org.pkl.core.runtime.TypeParameter;
public final class TypeParameterNode extends PklNode {
private final TypeParameter typeParameter;
public TypeParameterNode(TypeParameter typeParameter) {
super(typeParameter.sourceSection());
this.typeParameter = typeParameter;
}
public void executeAndSet(VirtualFrame frame, Object arg) {
frame.setObject(typeParameter.slot(), arg);
}
}
@@ -22,7 +22,6 @@ import com.oracle.truffle.api.source.SourceSection;
import java.util.ArrayList;
import java.util.List;
import org.pkl.core.PklBugException;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.PklNode;
import org.pkl.core.ast.expression.primary.GetModuleOwnerNode;
@@ -499,21 +498,29 @@ public abstract class UnresolvedTypeNode extends PklNode {
public static final class TypeVariable extends UnresolvedTypeNode {
private final TypeParameter typeParameter;
private final int levelsUp;
private final TypeParameter.OwnerType ownerType;
public TypeVariable(SourceSection sourceSection, TypeParameter typeParameter) {
public TypeVariable(
SourceSection sourceSection,
TypeParameter typeParameter,
TypeParameter.OwnerType ownerType,
int levelsUp) {
super(sourceSection);
this.typeParameter = typeParameter;
this.ownerType = ownerType;
this.levelsUp = levelsUp;
}
public int getTypeParameterIndex() {
return typeParameter.getIndex();
public TypeParameter getTypeParameter() {
return typeParameter;
}
@Override
public TypeNode execute(VirtualFrame frame) {
CompilerDirectives.transferToInterpreter();
return new TypeVariableNode(sourceSection, typeParameter);
return new TypeVariableNode(sourceSection, typeParameter, ownerType, levelsUp);
}
}
@@ -46,6 +46,7 @@ import org.pkl.core.repl.ReplResponse.EvalSuccess;
import org.pkl.core.repl.ReplResponse.InvalidRequest;
import org.pkl.core.resource.ResourceReader;
import org.pkl.core.runtime.*;
import org.pkl.core.runtime.TypeParameter;
import org.pkl.core.util.AnsiStringBuilder;
import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.IoUtils;
@@ -478,7 +479,7 @@ public class ReplServer implements AutoCloseable {
List.of(),
VmModifier.NONE,
classInfo,
List.of(),
new TypeParameter[0],
module);
if (parent != null) {
var superclass = parent.getVmClass();
@@ -16,7 +16,6 @@
package org.pkl.core.runtime;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.member.ClassMethod;
import org.pkl.core.ast.member.ClassProperty;
import org.pkl.core.ast.type.TypeNode;
@@ -209,11 +208,11 @@ public final class MirrorFactories {
.addTypedProperty("type", Pair::getSecond);
typeParameterFactory
.addStringProperty("name", TypeParameter::getName)
.addStringProperty("name", TypeParameter::name)
.addProperty(
"variance",
typeParameter ->
switch (typeParameter.getVariance()) {
switch (typeParameter.variance()) {
case COVARIANT -> "out";
case CONTRAVARIANT -> "in";
default -> VmNull.withoutDefault();
@@ -0,0 +1,38 @@
/*
* 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.source.SourceSection;
import java.util.Arrays;
import java.util.List;
import org.pkl.core.TypeParameter.Variance;
public record TypeParameter(
SourceSection sourceSection, int slot, int index, String name, Variance variance) {
public org.pkl.core.TypeParameter export() {
return new org.pkl.core.TypeParameter(variance, name, index);
}
public static List<org.pkl.core.TypeParameter> export(TypeParameter[] typeParameters) {
return Arrays.stream(typeParameters).map(TypeParameter::export).toList();
}
public enum OwnerType {
CLASS,
METHOD,
TYPEALIAS
}
}
@@ -29,7 +29,6 @@ import org.pkl.core.Member.SourceLocation;
import org.pkl.core.PClass;
import org.pkl.core.PClassInfo;
import org.pkl.core.PObject;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.*;
import org.pkl.core.ast.member.*;
import org.pkl.core.ast.type.TypeNode;
@@ -51,7 +50,7 @@ public final class VmClass extends VmValue {
private final List<VmTyped> annotations;
private final int modifiers;
private final PClassInfo<?> classInfo;
private final List<TypeParameter> typeParameters;
private final TypeParameter[] typeParameters;
private final VmTyped prototype;
private final EconomicMap<Identifier, ClassProperty> declaredProperties = EconomicMaps.create();
@@ -127,7 +126,7 @@ public final class VmClass extends VmValue {
List<VmTyped> annotations,
int modifiers,
PClassInfo<?> classInfo,
List<TypeParameter> typeParameters,
TypeParameter[] typeParameters,
VmTyped prototype) {
this.sourceSection = sourceSection;
@@ -190,6 +189,41 @@ public final class VmClass extends VmValue {
return result;
}
@TruffleBoundary
private void checkMethodOverrides() {
var methodCursor = declaredMethods.getEntries();
while (methodCursor.advance()) {
var method = methodCursor.getValue();
if (method.isLocal()) continue;
var name = methodCursor.getKey();
ClassMethod parent = null;
for (var clazz = superclass; clazz != null; clazz = clazz.superclass) {
parent = clazz.getDeclaredMethod(name);
if (parent != null) break;
}
if (parent == null) continue;
checkMethodOverride(method, parent);
}
}
private void checkMethodOverride(ClassMethod method, ClassMethod parent) {
var typeParamCount = method.getTypeParameterCount();
var parentTypeParamCount = parent.getTypeParameterCount();
if (typeParamCount != parentTypeParamCount) {
throw new VmExceptionBuilder()
.evalError(
"methodTypeParameterCountMismatch",
method.getQualifiedName(),
parent.getQualifiedName(),
parentTypeParamCount,
typeParamCount)
.withSourceSection(method.getHeaderSection())
.build();
}
}
@TruffleBoundary
public void addProperty(ClassProperty property) {
prototype.addProperty(property.getInitializer());
@@ -242,14 +276,15 @@ public final class VmClass extends VmValue {
/** Called when the entire class hierarchy is completely initialized, including superclasses. */
public void onFullyInitialized() {
checkAbstractMethods();
checkMethodOverrides();
}
@TruffleBoundary
public int getTypeParameterCount() {
return typeParameters.size();
return typeParameters.length;
}
public List<TypeParameter> getTypeParameters() {
public TypeParameter[] getTypeParameters() {
return typeParameters;
}
@@ -723,12 +758,12 @@ public final class VmClass extends VmValue {
VmModifier.export(modifiers, true),
exportedAnnotations,
classInfo,
typeParameters,
TypeParameter.export(typeParameters),
properties,
methods,
moduleClass);
for (var parameter : typeParameters) {
for (var parameter : __pClass.getTypeParameters()) {
parameter.initOwner(__pClass);
}
@@ -71,7 +71,13 @@ public abstract class VmListingOrMapping extends VmObject {
if (obj.typeCastNode != null && obj.typeCastNode != prevTypeCastNode) {
var callTarget = obj.typeCastNode.getCallTarget();
try {
result = callNode.call(callTarget, obj.typeCheckReceiver, obj.typeCheckOwner, result);
result =
callNode.call(
callTarget,
VmUtils.getReceiver(enclosingFrame),
VmUtils.getOwner(enclosingFrame),
obj.enclosingFrame,
result);
prevTypeCastNode = obj.typeCastNode;
} catch (VmException e) {
CompilerDirectives.transferToInterpreter();
@@ -109,7 +115,8 @@ public abstract class VmListingOrMapping extends VmObject {
var callNode = IndirectCallNode.getUncached();
var callTarget = typeCastNode.getCallTarget();
try {
result = callNode.call(callTarget, typeCheckReceiver, typeCheckOwner, result);
result =
callNode.call(callTarget, typeCheckReceiver, typeCheckOwner, enclosingFrame, result);
} catch (VmException e) {
var member = VmUtils.findMember(parent, key);
assert member != null; // already found the member's cached value
@@ -133,6 +140,9 @@ public abstract class VmListingOrMapping extends VmObject {
if (typeCastNode == null) {
return false;
}
return typeCastNode.getTypeNode().getType().equals(typeNode.getType());
var castTypeNode = typeCastNode.getTypeNode();
// if cast type is not final, we can't know if typeNode is a subtype
if (!castTypeNode.isFinalType()) return false;
return castTypeNode.getType().equals(typeNode.getType());
}
}
@@ -56,12 +56,8 @@ public final class VmReference extends VmValue {
.toTyped(RefModule.getAccessClass());
}
public VmReference(VmTyped domain, VmClass clazz, Object data) {
this(
domain,
data,
RrbTree.empty(),
normalizeTypes(new VmType.ClassType(clazz), clazz, clazz.getModuleClass()));
public VmReference(VmTyped domain, VmType referentType, Object data) {
this(domain, data, RrbTree.empty(), normalizeTypes(referentType, null, null));
}
public VmReference(VmTyped domain, Object data, ImRrbt<VmTyped> path, VmType referentType) {
@@ -95,7 +91,8 @@ public final class VmReference extends VmValue {
// * replace VmType.ModuleType with appropriate VmType.ClassType
// * drop VmType.FunctionType and VmType.TypeVariableType
@TruffleBoundary
private static VmType normalizeTypes(VmType type, VmClass thisClass, VmClass moduleClass) {
private static VmType normalizeTypes(
VmType type, @Nullable VmClass thisClass, @Nullable VmClass moduleClass) {
var types = new HashSet<VmType>();
normalizeTypes(type, thisClass, moduleClass, types);
return minimizeTypes(types);
@@ -115,7 +112,7 @@ public final class VmReference extends VmValue {
}
private static void normalizeTypes(
VmType type, VmClass thisClass, VmClass moduleClass, Set<VmType> result) {
VmType type, @Nullable VmClass thisClass, @Nullable VmClass moduleClass, Set<VmType> result) {
if (type == UnknownType.INSTANCE
|| type == NothingType.INSTANCE
|| type instanceof VmType.StringLiteralType) {
@@ -154,17 +151,19 @@ public final class VmReference extends VmValue {
}
} else if (type instanceof NonFinalThisType) {
// there are 4 entrypoints here:
// 1. init via the Reference constructor can only normalize an unparameterized PType.Class
// 2. typecheck via ReferenceTypeNode erases self types to their actual PType.Class
// 1. init via the Reference constructor, which pre-reifies the VmType
// 2. typecheck via ReferenceTypeNode, which pre-reifies the VmType
// 3. subscript access can only be achieved by first performing property access, at which time
// self types are erased
// 4. property access uses the enclosing receiver's class to substitute for these self types
// only property access and typecheck can produce THIS or MODULE.
// getCandidatePropertyType and referentTypeIsSubtypeOf always pass non-null `thisClass`.
// getCandidatePropertyType always passes non-null `thisClass`.
assert thisClass != null;
result.add(new VmType.ClassType(thisClass));
} else if (type instanceof NonFinalModuleType) {
// this can be incorrect for usage of the module type in a class's property type annotation,
// which is deprecated!!
assert moduleClass != null;
result.add(new VmType.ClassType(moduleClass));
} else {
// remaining types: PType.Function, PType.TypeVariable. no normalizing needed; TypeVariable
@@ -15,6 +15,7 @@
*/
package org.pkl.core.runtime;
import com.oracle.truffle.api.frame.VirtualFrame;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
@@ -25,8 +26,8 @@ import org.pkl.core.PType.Constrained;
import org.pkl.core.PType.StringLiteral;
import org.pkl.core.PType.TypeVariable;
import org.pkl.core.PType.Union;
import org.pkl.core.TypeParameter;
import org.pkl.core.ValueFormatter;
import org.pkl.core.ast.expression.primary.GetModuleNode;
public abstract sealed class VmType {
@@ -73,6 +74,15 @@ public abstract sealed class VmType {
|| (other instanceof ConstrainedType ct && isSupertypeOf(ct.baseType));
}
/**
* Where possible, substitute types for their "real" type. Self types become {@link ClassType}.
* Where possible, {@link TypeVariableType} instances are replaced with the corresponding type
* argument type.
*
* <p>Contract: when no substitutions were made, returns the identical type (same object).
*/
public abstract VmType reify(VirtualFrame frame);
public static final class UnknownType extends VmType {
public static final UnknownType INSTANCE = new UnknownType();
@@ -99,6 +109,11 @@ public abstract sealed class VmType {
public String toString() {
return "unknown";
}
@Override
public VmType reify(VirtualFrame frame) {
return this;
}
}
public static final class NothingType extends VmType {
@@ -127,6 +142,11 @@ public abstract sealed class VmType {
public String toString() {
return "nothing";
}
@Override
public VmType reify(VirtualFrame frame) {
return this;
}
}
public static final class StringLiteralType extends VmType {
@@ -166,6 +186,11 @@ public abstract sealed class VmType {
public int hashCode() {
return 31 * literal.hashCode();
}
@Override
public VmType reify(VirtualFrame frame) {
return this;
}
}
public abstract static sealed class AbstractClassType extends VmType {
@@ -265,7 +290,7 @@ public abstract sealed class VmType {
var newState = cSupertype.typeArguments.clone();
for (var i = 0; i < newState.length; i++) {
if (!(newState[i] instanceof TypeVariableType tvt)) continue;
newState[i] = state[tvt.typeParameter.getIndex()];
newState[i] = state[tvt.typeParameter.index()];
}
state = newState;
}
@@ -274,7 +299,7 @@ public abstract sealed class VmType {
assert goalState.length == state.length;
var params = clazz.getTypeParameters();
for (var i = 0; i < goalState.length; i++) {
if (!switch (params.get(i).getVariance()) {
if (!switch (params[i].variance()) {
case INVARIANT -> state[i].equals(goalState[i]);
case COVARIANT -> state[i].isSubtypeOf(goalState[i]);
case CONTRAVARIANT -> state[i].isSupertypeOf(goalState[i]);
@@ -340,6 +365,18 @@ public abstract sealed class VmType {
return appendTypeArguments(new StringBuilder(clazz.getDisplayName()), typeArguments)
.toString();
}
@Override
public VmType reify(VirtualFrame frame) {
var typeArgs = new VmType[typeArguments.length];
var changed = false;
for (var i = 0; i < typeArgs.length; i++) {
typeArgs[i] = typeArguments[i].reify(frame);
changed = changed || typeArguments[i] != typeArgs[i];
}
return changed ? new ClassType(clazz, typeArgs) : this;
}
}
public static final class FinalModuleType extends AbstractClassType {
@@ -356,6 +393,11 @@ public abstract sealed class VmType {
public String toString() {
return "module";
}
@Override
public VmType reify(VirtualFrame frame) {
return this;
}
}
public static final class FinalThisType extends AbstractClassType {
@@ -372,6 +414,11 @@ public abstract sealed class VmType {
public String toString() {
return "this";
}
@Override
public VmType reify(VirtualFrame frame) {
return this;
}
}
private abstract static sealed class NonFinalSelfType extends VmType {
@@ -416,6 +463,14 @@ public abstract sealed class VmType {
public String toString() {
return "module";
}
@Override
public VmType reify(VirtualFrame frame) {
var levelsUp = GetModuleNode.getLevelsUp(frame);
return new ClassType(
VmUtils.getClass(
levelsUp == 0 ? VmUtils.getReceiver(frame) : VmUtils.getReceiver(frame, levelsUp)));
}
}
public static final class NonFinalThisType extends NonFinalSelfType {
@@ -432,6 +487,11 @@ public abstract sealed class VmType {
public String toString() {
return "this";
}
@Override
public VmType reify(VirtualFrame frame) {
return new ClassType(VmUtils.getClass(VmUtils.getReceiver(frame)));
}
}
public static final class NullableType extends VmType {
@@ -473,6 +533,12 @@ public abstract sealed class VmType {
public int hashCode() {
return 31 * elementType.hashCode();
}
@Override
public VmType reify(VirtualFrame frame) {
var elem = elementType.reify(frame);
return elem == elementType ? this : new NullableType(elem);
}
}
public static final class ConstrainedType extends VmType {
@@ -522,6 +588,12 @@ public abstract sealed class VmType {
public int hashCode() {
return 31 * identity;
}
@Override
public VmType reify(VirtualFrame frame) {
var base = baseType.reify(frame);
return base == baseType ? this : new ConstrainedType(base, constraints, identity);
}
}
public static final class AliasType extends VmType {
@@ -554,6 +626,18 @@ public abstract sealed class VmType {
this.aliasedType = aliasedType;
}
/** For reification */
private AliasType(
VmTypeAlias typeAlias,
@Nullable VmClass clazz,
VmType[] typeArguments,
VmType aliasedType) {
this.typeAlias = typeAlias;
this.typeArguments = typeArguments;
this.clazz = clazz;
this.aliasedType = aliasedType;
}
public VmTypeAlias getVmTypeAlias() {
return typeAlias;
}
@@ -618,6 +702,14 @@ public abstract sealed class VmType {
public int hashCode() {
return aliasedType.hashCode();
}
@Override
public VmType reify(VirtualFrame frame) {
var aliased = aliasedType.reify(frame);
return aliased == aliasedType
? this
: new AliasType(typeAlias, clazz, typeArguments, aliased);
}
}
public static final class UnionType extends VmType {
@@ -692,13 +784,30 @@ public abstract sealed class VmType {
}
return true;
}
@Override
public VmType reify(VirtualFrame frame) {
var elems = new VmType[elementTypes.length];
var changed = false;
for (var i = 0; i < elems.length; i++) {
elems[i] = elementTypes[i].reify(frame);
changed = changed || elementTypes[i] != elems[i];
}
return changed ? new UnionType(defaultIndex, elems) : this;
}
}
public static final class TypeVariableType extends VmType {
private final TypeParameter typeParameter;
private final TypeParameter.OwnerType ownerType;
private final int levelsUp;
public TypeVariableType(TypeParameter typeParameter) {
public TypeVariableType(
TypeParameter typeParameter, TypeParameter.OwnerType ownerType, int levelsUp) {
this.typeParameter = typeParameter;
this.ownerType = ownerType;
this.levelsUp = levelsUp;
}
public TypeParameter getTypeParameter() {
@@ -717,18 +826,30 @@ public abstract sealed class VmType {
@Override
public PType export() {
return new TypeVariable(typeParameter);
return new TypeVariable(typeParameter.export());
}
@Override
public String toString() {
return typeParameter.getName();
return typeParameter.name();
}
@Override
public int hashCode() {
return -318019;
}
@Override
public VmType reify(VirtualFrame frame) {
if (ownerType != TypeParameter.OwnerType.METHOD) return this;
var typeArg = VmUtils.getTypeArgumentOrNull(frame, typeParameter.slot(), levelsUp);
if (typeArg != null) {
return typeArg.reify();
}
return this;
}
}
private static boolean typesEquals(VmType[] a, VmType[] b) {
@@ -29,7 +29,6 @@ import org.pkl.core.Member.SourceLocation;
import org.pkl.core.PClassInfo;
import org.pkl.core.PObject;
import org.pkl.core.TypeAlias;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.expression.primary.ExecuteCustomThisWithRootNode;
import org.pkl.core.ast.type.TypeNode;
@@ -47,7 +46,7 @@ public final class VmTypeAlias extends VmValue {
private final String simpleName;
private final VmTyped module;
private final String qualifiedName;
private final List<TypeParameter> typeParameters;
private final TypeParameter[] typeParameters;
private final MaterializedFrame enclosingFrame;
private @Nullable TypeNode typeNode;
@@ -71,7 +70,7 @@ public final class VmTypeAlias extends VmValue {
String simpleName,
VmTyped module,
String qualifiedName,
List<TypeParameter> typeParameters,
TypeParameter[] typeParameters,
MaterializedFrame enclosingFrame) {
this.sourceSection = sourceSection;
this.headerSection = headerSection;
@@ -170,7 +169,7 @@ public final class VmTypeAlias extends VmValue {
}
public int getTypeParameterCount() {
return typeParameters.size();
return typeParameters.length;
}
public TypeNode getTypeNode() {
@@ -190,14 +189,14 @@ public final class VmTypeAlias extends VmValue {
// * Fewer root nodes to call
// * ControlFlowException used to implement union types doesn't escape root node
if (typeParameters.isEmpty()) return deepCopy(typeNode);
if (typeParameters.length == 0) return deepCopy(typeNode);
// handle if alias root is itself a type variable (https://github.com/apple/pkl/issues/1711)
if (typeNode instanceof TypeVariableNode typeVarNode) {
// no need to run validation since the arg itself has already been checked
return typeArgumentNodes.length == 0
? new UnknownTypeNode(sourceSection)
: typeArgumentNodes[typeVarNode.getTypeParameterIndex()];
: typeArgumentNodes[typeVarNode.getTypeParameter().index()];
}
var clone = (TypeNode) typeNode.deepCopy();
@@ -206,7 +205,7 @@ public final class VmTypeAlias extends VmValue {
@Override
public boolean visit(com.oracle.truffle.api.nodes.Node node) {
if (node instanceof TypeVariableNode typeVarNode) {
var index = typeVarNode.getTypeParameterIndex();
var index = typeVarNode.getTypeParameter().index();
node.replace(
typeArgumentNodes.length == 0
? new UnknownTypeNode(sourceSection)
@@ -215,7 +214,7 @@ public final class VmTypeAlias extends VmValue {
// Type variables inside constraint expressions (e.g. `every((it) -> it is T)`)
// are still unresolved at instantiation time. Replace them with a resolved
// unresolved type node that returns the concrete type argument.
var index = unresolvedTypeVar.getTypeParameterIndex();
var index = unresolvedTypeVar.getTypeParameter().index();
node.replace(
typeArgumentNodes.length == 0
? new UnresolvedTypeNode.Unknown(sourceSection)
@@ -258,10 +257,10 @@ public final class VmTypeAlias extends VmValue {
simpleName,
getModuleName(),
qualifiedName,
typeParameters,
TypeParameter.export(typeParameters),
module.getVmClass().export());
for (var parameter : typeParameters) {
for (var parameter : __pTypeAlias.getTypeParameters()) {
parameter.initOwner(__pTypeAlias);
}
@@ -0,0 +1,63 @@
/*
* 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.CallTarget;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.member.FunctionNode;
import org.pkl.core.ast.type.TypeArgumentTypeNode;
import org.pkl.core.ast.type.TypeNode;
/**
* Type argument information passed from a type argument (in a method call) as an argument to a
* {@link FunctionNode}.
*/
public class VmTypeArgument {
private final @Nullable MaterializedFrame enclosingFrame;
private final TypeArgumentTypeNode rootNode;
public VmTypeArgument(TypeArgumentTypeNode rootNode, @Nullable MaterializedFrame enclosingFrame) {
this.enclosingFrame = enclosingFrame;
this.rootNode = rootNode;
}
public @Nullable MaterializedFrame getEnclosingFrame() {
return enclosingFrame;
}
public CallTarget getCallTarget() {
return rootNode.getCallTarget();
}
private TypeNode getTypeNode() {
return rootNode.getTypeNode();
}
public VmType reify() {
var type = getTypeNode().getType();
var frame = enclosingFrame != null ? enclosingFrame : VmUtils.createEmptyMaterializedFrame();
return type.reify(frame);
}
public @Nullable Object createDefaultValue(
VmLanguage language, SourceSection headerSection, String qualifiedName) {
var frame = enclosingFrame != null ? enclosingFrame : VmUtils.createEmptyMaterializedFrame();
return getTypeNode().createDefaultValue(frame, language, headerSection, qualifiedName);
}
}
@@ -57,6 +57,7 @@ import org.pkl.core.ast.expression.primary.CustomThisNode;
import org.pkl.core.ast.expression.primary.ThisNode;
import org.pkl.core.ast.member.*;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.TypeParameterNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.module.ModuleKey;
import org.pkl.core.module.ModuleKeys;
@@ -182,6 +183,21 @@ public final class VmUtils {
return (VmTyped) getReceiver(frame);
}
/** Returns the type arguments of the currently executing code. */
public static @Nullable VmTypeArgument getTypeArgumentOrNull(Frame frame, int slot) {
if (slot < 0) return null;
return (VmTypeArgument) frame.getObject(slot);
}
public static @Nullable VmTypeArgument getTypeArgumentOrNull(
Frame frame, int slot, int levelsUp) {
if (slot < 0) return null;
if (levelsUp == 0) {
return getTypeArgumentOrNull(frame, slot);
}
return getTypeArgumentOrNull(getEnclosingFrame(getOwner(frame), levelsUp), slot);
}
/** Returns the owner of the currently executing code. */
public static @Nullable VmObjectLike getOwnerOrNull(Frame frame) {
return (VmObjectLike) frame.getArguments()[1];
@@ -903,6 +919,14 @@ public final class VmUtils {
return resolvedNodes;
}
public static TypeParameterNode[] resolveTypeParameters(TypeParameter[] typeParameters) {
var typeParameterNodes = new TypeParameterNode[typeParameters.length];
for (var i = 0; i < typeParameters.length; i++) {
typeParameterNodes[i] = new TypeParameterNode(typeParameters[i]);
}
return typeParameterNodes;
}
public static void checkIsInstantiable(VmClass parentClass, Node parentNode, Node callerNode) {
if (parentClass.isInstantiable()) return;
@@ -17,17 +17,21 @@ 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.VmType;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.stdlib.ExternalMethod3Node;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.stdlib.ExternalMethod2Node;
public class RefNodes {
public abstract static class Reference extends ExternalMethod3Node {
public abstract static class Reference extends ExternalMethod2Node {
@Specialization
protected VmReference eval(
VirtualFrame frame, VmTyped self, VmTyped domain, VmClass clazz, Object data) {
return new VmReference(domain, clazz, data);
VirtualFrame frame, @SuppressWarnings("unused") VmTyped self, VmTyped domain, Object data) {
var referentTypeArg = VmUtils.getTypeArgumentOrNull(frame, 3);
var referentType =
referentTypeArg == null ? VmType.UnknownType.INSTANCE : referentTypeArg.reify();
return new VmReference(domain, referentType, data);
}
}
}
@@ -189,7 +189,7 @@ cannotSpreadObject=\
Cannot spread value of type `{0}` into object of type `{1}`.
cannotDeclareTypeParameter=\
Only standard library members can have type parameters.
Only standard library classes can have type parameters.
duplicateTypeParameter=\
Duplicate type parameter `{0}`.
@@ -1223,9 +1223,6 @@ Cannot follow redirect from ''https:'' URL to ''http:'' URL.\
HTTP Request: `GET {0}`\n\
Redirected to: `{1}`
invalidReferenceTypeAnnotationWithConstraint=\
`Reference` referent type argument may not include type constraints.
parserError=\
Could not parse Pkl source.
@@ -1254,6 +1251,9 @@ noImplementationForAbstractMethods=\
Class `{0}` should either be declared `abstract`, or should implement the following methods:\n\
{1}
methodTypeParameterCountMismatch=\
Expected method `{0}` overriding `{1}` to have {2} type parameter{2,choice,0#s|1#|1<s}, but got {3}.
invalidModuleTypeInProperty=\
Cannot reference `module` type from const property `{0}`.
@@ -11,7 +11,7 @@ facts {
isVerbose = false
}
local result = benchmark.run()
result.iterations == 3
result.repetitions >= 1
result.samples == null
@@ -21,6 +21,19 @@ facts {
result.mean <= result.max
}
["run microbenchmark (in generic method)"] {
local function run<T>() =
new Benchmark.Microbenchmark {
expression = 123 is T
iterations = 3
iterationTime = 1.ms
isVerbose = false
}.run()
run::<Int>().iterations == 3
run::<String>().iterations == 3
}
["run output benchmark (in verbose mode)"] {
local benchmark = new Benchmark.OutputBenchmark {
sourceModule = import("../../input-helper/api/benchmark/moduleToBenchmark.pkl")
@@ -84,6 +97,7 @@ facts {
local text = benchmark.output.text
text.contains("[\"micro\"]")
text.contains("[\"generic method\"]")
text.contains("[\"output\"]")
text.contains("[\"parser\"]")
}
@@ -2,7 +2,7 @@ import "pkl:math"
import "pkl:ref"
import "pkl:test"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String =
let (data = reference.getData())
let (root = if (data is Resource) data.name else data.toString())
@@ -17,10 +17,11 @@ class D extends ref.Domain {
local const d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
const function Ref<T>(data: Any): Ref<T> = ref.Reference::<D, T>(d, data)
abstract class Resource {
name: String
hidden fixed $: Ref<this> = ref.Reference(d, getClass(), this)
hidden fixed $: Ref<this> = Ref::<this>(this)
}
class A extends Resource {
@@ -122,28 +123,32 @@ 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>>
typeArgs = Ref::<TypeHolder>(null).prop as Ref<Listing<Number | Boolean | String>>
class TypeHolder {
prop: Listing<String | Boolean | Number>
mappingAnyKey: Mapping<Any, Int>
mappingTypeArgKey: Mapping<Listing<Int>, Int>
}
typeArgs2 = Ref::<Listing<Number | Boolean | String>>(null) as Ref<Listing<Number | Boolean | String>>
class BadResource extends Resource {
// return a reference with referent that isn't `this`
fixed $ = ref.Reference(d, A, this)
fixed $ = Ref::<A>(this)
}
badThisRef = test.catch(() -> new BadResource { name = "bad" }.$.toString())
// test that a bare function type toStrings/exports okay
bareFunctionType = test.catch(() -> ref.Reference(d, Function4, null) as Ref<String>)
bareFunctionType = test.catch(() -> ref.Reference::<D, Function4>(d, null) as Ref<String>)
mappingAnyKey = ref.Reference(d, TypeHolder, null).mappingAnyKey["foo"]
// test for allowing constraints during reference construction
constraintInConstructor = Ref::<String(length > 5)>(null)
mappingAnyKey = Ref::<TypeHolder>(null).mappingAnyKey["foo"]
// this one should be an editor error but can't be caught in the runtime
// because VmListing/VmMapping don't track their type args
mappingTypeArgKey = ref.Reference(d, TypeHolder, null).mappingTypeArgKey[new Listing<String> { "foo" }]
mappingTypeArgKey = Ref ::<TypeHolder>(null).mappingTypeArgKey[new Listing<String> { "foo" }]
output {
renderer {
@@ -2,9 +2,9 @@
// module type in a final module
import "pkl:ref"
import "reference30.pkl"
import "referenceWithFinalModuleType.pkl"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(r: ref.Reference<this, Any>): String =
r.getPath().map((it) -> it.property ?? it.key.toString()).join(".")
}
@@ -12,9 +12,9 @@ class D extends ref.Domain {
hidden parent: module?
hidden children: List<module>
typealias R = ref.Reference<D, reference30?>
typealias R = ref.Reference<D, referenceWithFinalModuleType?>
res0 = ref.Reference(new D {}, getClass(), null)
res0 = ref.Reference::<D, module>(new D {}, null)
res1: R = res0.parent
res2: R = res0.parent.parent
res3: R = res0.children[0]
@@ -1,12 +1,12 @@
open module reference29
open module referenceWithOpenModuleType
// interactions between references and self types
// module type in an open module
import "pkl:ref"
import "reference29.pkl"
import "referenceWithOpenModuleType.pkl"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(r: ref.Reference<this, Any>): String =
r.getPath().map((it) -> it.property ?? it.key.toString()).join(".")
}
@@ -14,9 +14,9 @@ class D extends ref.Domain {
hidden parent: module?
hidden children: List<module>
typealias R = ref.Reference<D, reference29?>
typealias R = ref.Reference<D, referenceWithOpenModuleType?>
res0 = ref.Reference(new D {}, getClass(), null)
res0 = ref.Reference::<D, module>(new D {}, null)
res1: R = res0.parent
res2: R = res0.parent.parent
res3: R = res0.children[0]
@@ -2,7 +2,7 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(r: ref.Reference<this, Any>): String =
r.getPath().map((it) -> it.property ?? it.key.toString()).join(".")
}
@@ -22,14 +22,14 @@ open class Node2 {
typealias R = ref.Reference<D, Node?>
typealias R2 = ref.Reference<D, Node2?>
res0: R = ref.Reference(new D {}, Node, null)
res0: R = ref.Reference::<D, Node>(new D {}, null)
res1: R = res0.parent
res2: R = res0.parent.parent
res3: R = res0.children[0]
res4: R = res0.children[0].parent
res5: R = res0.children[0].children[0]
res0a: R2 = ref.Reference(new D {}, Node2, null)
res0a: R2 = ref.Reference::<D, Node2>(new D {}, null)
res1a: R2 = res0a.parent
res2a: R2 = res0a.parent.parent
res3a: R2 = res0a.children[0]
@@ -2,7 +2,7 @@ amends "../snippetTest.pkl"
import "pkl:ref"
local class D extends ref.Domain {
local class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = reference.getData().toString()
}
@@ -25,9 +25,9 @@ local class OtherHolder {
constrainedType: String(isEmpty)
}
local holderRef: Ref<Holder> = ref.Reference(d, Holder, null)
local holderRef2: Ref<Holder> = ref.Reference(d, Holder, null)
local otherHolderRef: Ref<OtherHolder> = ref.Reference(d, OtherHolder, null)
local holderRef: Ref<Holder> = ref.Reference::<D, Holder>(d, null)
local holderRef2: Ref<Holder> = ref.Reference::<D, Holder>(d, null)
local otherHolderRef: Ref<OtherHolder> = ref.Reference::<D, OtherHolder>(d, null)
facts {
["equality"] {
@@ -0,0 +1 @@
function foo<in T>() = null
@@ -0,0 +1,3 @@
obj {
local function foo<in T>() = null
}
@@ -1 +0,0 @@
local function x<T>() = x
@@ -0,0 +1,7 @@
open class A {
function foo<A>() = null
}
class B extends A {
function foo<A, B>() = null
}
@@ -1,9 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
test = ref.Reference(d, String, "") as Ref<Int>
test = ref.Reference::<D, String>(d, "") as Ref<Int>
@@ -1,9 +0,0 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<this, 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)>>
@@ -1,12 +0,0 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<this, 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)
@@ -1,12 +0,0 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<this, 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)
@@ -1,8 +1,8 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, Mapping, "").default
test = ref.Reference::<D, Mapping>(d, "").default
@@ -1,8 +1,8 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, Listing, "").default
test = ref.Reference::<D, Listing>(d, "").default
@@ -1,8 +1,8 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, Dynamic, "").default
test = ref.Reference::<D, Dynamic>(d, "").default
@@ -2,9 +2,9 @@ import "pkl:ref"
import ".../input-helper/errors/ReferencedModule.pkl"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, ReferencedModule.getClass(), "").output
test = ref.Reference::<D, ReferencedModule>(d, "").output
@@ -2,9 +2,9 @@ import "pkl:ref"
import ".../input-helper/errors/ReferencedModuleWithOutputOverride.pkl"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, ReferencedModuleWithOutputOverride.getClass(), "").output
test = ref.Reference::<D, ReferencedModuleWithOutputOverride>(d, "").output
@@ -4,9 +4,9 @@ import ".../input-helper/errors/ReferencedModuleWithOutputOverride.pkl"
class ModuleSubclass extends ReferencedModuleWithOutputOverride
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, ModuleSubclass, "").output
test = ref.Reference::<D, ModuleSubclass>(d, "").output
@@ -4,9 +4,9 @@ class A {
foo: String | Int | Boolean
}
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
test = ref.Reference(d, A, "").foo as ref.Reference<D, Int | Boolean>
test = ref.Reference::<D, A>(d, "").foo as ref.Reference<D, Int | Boolean>
@@ -1,9 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, 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>
test = ref.Reference::<D, String>(d, "") as Ref<Boolean | Int>
@@ -4,12 +4,12 @@ class A {
foo: String | Int | Boolean
}
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
local test = ref.Reference(d, A, "").foo
local test = ref.Reference::<D, A>(d, "").foo
testInterpolation = "test:\(test)"
// this tests that the interpolation appears in the output when renderReference throws
@@ -1,7 +0,0 @@
import "pkl:ref"
typealias Ref<T> = ref.Reference<ref.Domain, T>
typealias Ref2<T> = Ref<T>
foo: Ref2<String(isEmpty)>
@@ -1,6 +0,0 @@
import "pkl:ref"
typealias Ref<T> = ref.Reference<ref.Domain, T>
typealias Ref2<T> = Ref<T(isEmpty)>
foo: Ref2<String>
@@ -1,7 +0,0 @@
import "pkl:ref"
typealias Foo1 = Foo2
typealias Foo2 = String(isEmpty)
foo: ref.Reference<ref.Domain, Foo1>
@@ -1,6 +1,6 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(_): String = throw("not implemented")
}
@@ -8,10 +8,6 @@ class Bird {
name: String
}
class Holder {
$: Bird | Int
}
myRef: ref.Reference<D, Bird | Int> = ref.Reference(new D {}, Holder, null).$
myRef: ref.Reference<D, Bird | Int> = ref.Reference::<D, Bird | Int>(new D {}, null)
res = myRef.name
@@ -1,13 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(_): String = throw("not implemented")
}
class Holder {
$: Duration | Listing<String>
}
local myRef = ref.Reference(new D {}, Holder, null).$
local myRef = ref.Reference::<D, Duration | Listing<String>>(new D {}, null)
res = myRef[0]
@@ -1,9 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(_): String = throw("not implemented")
}
local myRef = ref.Reference(new D {}, Listing, null)
local myRef = ref.Reference::<D, Listing>(new D {}, null)
res = myRef.name
@@ -1,13 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(_): String = throw("not implemented")
}
class Holder {
func: (Int) -> Boolean
}
local myRef = ref.Reference(new D {}, Holder, null).func
local myRef = ref.Reference::<D, (Int) -> Boolean>(new D {}, null)
res = myRef.name
@@ -1,9 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, 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>>
test = ref.Reference::<D, String>(d, "") as Ref<Listing<String>>
@@ -1,12 +1,12 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
test = ref.Reference(d, String, "") as Ref<Alias1>
test = ref.Reference::<D, String>(d, "") as Ref<Alias1>
typealias Alias1 = Int | Alias2
typealias Alias2 = Boolean
@@ -1,14 +1,14 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
class D2 extends ref.Domain {
class D2 extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d2: D2 = new {}
test = ref.Reference(d2, String, "") as Ref<String>
test = ref.Reference::<D2, String>(d2, "") as Ref<String>
@@ -1,6 +1,6 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
@@ -10,4 +10,4 @@ class A {
b: String
}
test = ref.Reference(d, String, new A {}).c
test = ref.Reference::<D, String>(d, new A {}).c
@@ -1,9 +1,9 @@
import "pkl:ref"
class D extends ref.Domain {
class D extends ref.AbstractDomain {
function renderReference(reference: ref.Reference<this, Any>): String = throw("not supported")
}
local d: D = new {}
typealias Ref<T> = ref.Reference<D, T>
test = ref.Reference(d, List, List())["hi"]
test = ref.Reference::<D, List>(d, List())["hi"]
@@ -1,9 +0,0 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<this, 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)>
@@ -1,9 +0,0 @@
import "pkl:ref"
class D extends ref.Domain {
function renderReference(reference: ref.Reference<this, 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>
@@ -1,3 +0,0 @@
class DoIt {
function doit<A>(a: A): A = a
}
@@ -0,0 +1,144 @@
open module callWithTypeArguments
import "pkl:test"
import "pkl:ref"
class Domain extends ref.AbstractDomain {
function renderReference(reference: ref.Reference) = "<ref>"
}
directCall {
pair = Pair::<List<String>, Set<String>>(List("foo"), Set("bar"))
pairSelf = Pair::<module, module>(new module {}, new module {}).first.getClass().toString()
pairErr = test.catch(() -> Pair::<List<String>, Set<String>>(List(0), Set("bar")))
pairSelfErr = test.catch(() -> Pair::<module, module>(List(), new module {}))
listMap = List("hi", "foo", "a").map::<Int>((it) -> it.length)
listMapErr = test.catch(() -> List("hi", "foo", "a").map::<String>((it) -> it.length))
wrongTypeArgCount = test.catch(() -> Pair::<String, Int, Boolean> ("foo", 10))
nonGenericMethod = test.catch(() -> Regex::<String> ("foo"))
}
open class A {
function doIt<T>(arg: List): Boolean = arg.first is T
}
class B extends A {
function doIt<T>(arg: List): Boolean = super.doIt::<T>(arg) || super.doIt::<List<T>>(arg)
}
superCall {
local function doIt(a: A, arg: List): Boolean =
a.doIt::<String>(arg)
// should be true:
doIt(new A {}, List(""))
doIt(new A {}, List(""))
doIt(new B {}, List(""))
doIt(new B {}, List(List("")))
// should be false:
doIt(new A {}, List(0))
doIt(new A {}, List(List(0)))
doIt(new B {}, List(List(0)))
}
local num = 5
objectMethod {
local function foo<T>(): T = "hi"
res = test.catch(() -> foo::<String(length > num)>())
}
function lazy1<T>(it): Listing<T> = new Listing { it }
local function lazy2<T>(it): Listing<T> = new Listing { it }
lazyTypeCheck {
local function lazy3<T>(it): Listing<T> = new Listing { it }
res1 = test.catch(() -> lazy1::<String>(5))
res2 = test.catch(() -> lazy2::<String>(5))
res3 = test.catch(() -> lazy3::<String>(5))
eager {
local myListing: Listing<String> = new { "Hello, world!" }
local function myFunc<T>() = new {
result = myListing is T
}
// should be false:
res = myFunc::<Listing<Duration>>().result
}
closure {
local myListing: Listing<String> = new { "Hello, world!" }
local function myFunc<T>() = new {
result = myListing as T
}
res = test.catch(() -> myFunc::<Listing<Duration>>())
}
}
class Person {
name: String
function greet<A, B>() = "hi \(name)"
}
typeDefaults {
local function makeNamed<T>(val: String): T = new { name = val }
local function makeNamedListing<T>(val: String): Listing<T> = new {
new { name = val }
}
local function makeFromParam<T>(val: T): Listing<T> = new { val }
res1 = makeNamed::<Person>("bob")
res1a = res1.getClass().toString()
res2 = makeNamedListing::<Person>("bob")
res2a = res2.first.getClass().toString()
res3 = makeFromParam::<Person>(new { name = "bob" })
res3a = res3.first.getClass().toString()
res3b = test.catch(() -> makeFromParam::<Person>(new Bird { name = "joe" }))
}
class Bird {
name: String
function greet<A>() = "hi \(name)"
}
polymorphism {
// test that method type parameters are considered non-final
local function make<T>(): T = new T { name = "fred" }
res1 = make::<Person>()
res2 = make::<Bird>()
local function call(obj: Bird | Person) = obj.greet::<Int, String>()
res3 = call(new Person { name = "fred" })
res4 = test.catch(() -> call(new Bird { name = "pigeon" }))
res5 = call(new Person { name = "bob" })
}
typeCast {
local function cast<U>(l: Listing<Any>): Listing<U> = l
res1 = cast::<String(startsWith("Bob"))>(new Listing { "Bobbert" })
res2 = test.catch(() -> cast::<String(length > 10)>(res1).toString())
}
constraintReadsFrameSlot {
local function id<T>(x) = x is T
local function f(n: Int) = id::<String(length > n)>("hi")
res1 = f(1)
res2 = let (n = 1) id::<String(length > n)>("hi")
res3 = ((n) -> id::<String(length > n)>("hi")).apply(1)
res4 = new Listing {
for (n in List(1)) {
id::<String(length > n)>("hi")
}
}
}
output {
renderer {
converters {
[ref.Reference] = (it) -> it.toString()
}
}
}
@@ -0,0 +1,29 @@
const function foo<T>(val): Boolean = val is T
local prefix1 = "hi"
hidden prefix2 = "hi"
function prefix3() = "hi"
local function prefix4() = "hi"
class Bar {
x: Int = 10
function baz<T>(val): Boolean = foo::<T>(val)
function qux<T>(val): Boolean = foo::<T(length < x)>(val)
}
res {
res1a = foo::<String(startsWith(prefix1))>("high five")
res1b = foo::<String(startsWith(prefix2))>("high five")
res1c = foo::<String(startsWith(prefix3()))>("high five")
res1d = foo::<String(startsWith(prefix4()))>("high five")
res2a = new Bar {}.baz::<String(startsWith(prefix1))>("high five")
res2b = new Bar {}.baz::<String(startsWith(prefix2))>("high five")
res2c = new Bar {}.baz::<String(startsWith(prefix3()))>("high five")
res2d = new Bar {}.baz::<String(startsWith(prefix4()))>("high five")
res3a = new Bar {}.qux::<String(startsWith(prefix1))>("high five")
res3b = new Bar {}.qux::<String(startsWith(prefix2))>("high five")
res3c = new Bar {}.qux::<String(startsWith(prefix3()))>("high five")
res3d = new Bar {}.qux::<String(startsWith(prefix4()))>("high five")
}
@@ -0,0 +1,96 @@
open module callWithTypeArguments
import "pkl:test"
import "pkl:ref"
class Domain extends ref.AbstractDomain {
function renderReference(reference: ref.Reference) = "<ref>"
}
class Person {
name: String
function greet<A, B>() = "hi \(name)"
}
lexical {
local function f<T>(): Dynamic = new { res = 5 as T }
res1 = test.catch(() -> f::<String>().res)
local function g<T>(): Dynamic = new {
local myProp: T = 5
res = myProp
}
res2 = test.catch(() -> g::<String>().res)
local function nested<T>() = new Dynamic {
local function inner<U>(x: Listing<T>, y: U) = x
local function inner2<U>(x: T, y: U) = x
local function inner3(x: T) = x
local function inner4<U>() = new Dynamic {
local function innest1<V>(x: T, y: U, z: V) = x
local function innest2(x: T) = x
res1 = innest1::<String>(new { name = "x" }, 1, "hi")
res2 = innest1(new { name = "x" }, 1, "hi")
res3 = test.catch(() -> innest2(1))
}
res1 = inner::<Int>(new { new { name = "x" } }, 1)
res2 = inner2::<Int>(new { name = "x" }, 1)
res3 = inner(new { new { name = "x" } }, 1)
res4 = inner2(new { name = "x" }, 1)
res5 = test.catch(() -> inner3(1))
nested = inner4::<Int>()
}
nested = nested::<Person>()
inEagerScope {
local function k1<T>() = new {
[let (elem = "key" as T) elem] = "value"
}
test.catch(() -> k1::<Int>())
local function k2<T>() = new {
for (i in List(1, 2, 3)) {
[let (elem = "key".repeat(i) as T) elem as T] = "value"
}
}
k2::<String(length < 10)>()
}
reify {
local function makeMyObj<T>() = new {
res = ref.Reference::<Domain, T>(new {}, null)
}
res = makeMyObj::<Person>().res.name
}
}
local function nested<T>(foo) = new Dynamic {
local function inner<U>(x: Listing<T>, y: U) = x
local function inner2<U>(x: T, y: U) = x
local function inner3(x: T) = x
local function inner4<U>() = new Dynamic {
local function innest1<V>(x: T, y: U, z: V) = x
local function innest2(x: T) = x
res1 = innest1::<String>(new { name = "x" }, 1, "hi")
res2 = innest1(new { name = "x" }, 1, "hi")
res3 = test.catch(() -> innest2(1))
}
res1 = inner::<Int>(new { new { name = "x" } }, foo)
res2 = inner2::<Int>(new { name = "x" }, 1)
res3 = inner(new { new { name = "x" } }, 1)
res4 = inner2(new { name = "x" }, 1)
res5 = test.catch(() -> inner3(1))
nested = inner4::<Int>()
}
nested = nested::<Person>(1)
output {
renderer {
converters {
[ref.Reference] = (it) -> it.toString()
}
}
}
@@ -0,0 +1,8 @@
local function make<T>(): Listing<T> = new Listing<T> { 1; 2 }
local function amend<T, U>(l: Listing<Any>): Listing<U> = (l) { 3 } as Listing<U>
local parent = make::<Int>()
local child = amend::<String, Int>(parent)
res = child[0]
@@ -0,0 +1,55 @@
open class A {
function foo<T, U, V>(val): List<Boolean> = List(val is T, val is U, val is V)
function a<T, U>(val): List<Boolean> = this.foo::<T, U, this>(val)
}
open class B extends A {
function b1<T>(val): List<Boolean> = this.a::<T, this>(val)
function b2<T>(val): List<Boolean> = super.a::<T, this>(val)
}
class C extends B {
function c1(val): List<Boolean> = this.b1::<this>(val)
function c2(val): List<Boolean> = super.b1::<this>(val)
function c3(val): List<Boolean> = this.b2::<this>(val)
function c4(val): List<Boolean> = super.b2::<this>(val)
}
resAaA = new A {}.a(new A {})
resAaB = new A {}.a(new B {})
resAaC = new A {}.a(new C {})
resBaA = new B {}.a(new A {})
resBaB = new B {}.a(new B {})
resBaC = new B {}.a(new C {})
resBb1A = new B {}.b1(new A {})
resBb1B = new B {}.b1(new B {})
resBb1C = new B {}.b1(new C {})
resBb2A = new B {}.b2(new A {})
resBb2B = new B {}.b2(new B {})
resBb2C = new B {}.b2(new C {})
resCaA = new C {}.a(new A {})
resCaB = new C {}.a(new B {})
resCaC = new C {}.a(new C {})
resCb1A = new C {}.b1(new A {})
resCb1B = new C {}.b1(new B {})
resCb1C = new C {}.b1(new C {})
resCb2A = new C {}.b2(new A {})
resCb2B = new C {}.b2(new B {})
resCb2C = new C {}.b2(new C {})
resCc1A = new C {}.c1(new A {})
resCc1B = new C {}.c1(new B {})
resCc1C = new C {}.c1(new C {})
resCc2A = new C {}.c2(new A {})
resCc2B = new C {}.c2(new B {})
resCc2C = new C {}.c2(new C {})
resCc3A = new C {}.c3(new A {})
resCc3B = new C {}.c3(new B {})
resCc3C = new C {}.c3(new C {})
resCc4A = new C {}.c4(new A {})
resCc4B = new C {}.c4(new B {})
resCc4C = new C {}.c4(new C {})
@@ -0,0 +1,5 @@
function foo<T>(bar) = bar as T
function baz<T>(qux) = foo::<List<T>>(qux)
res = baz::<String(throw("oops"))>(List("a"))
@@ -0,0 +1,7 @@
foo = 5
function bar<T>() = new {
prop = foo as T
}
res = bar::<Duration>()
@@ -0,0 +1,16 @@
// when no function override (parent or child are local) then varying number of type params is okay
open class P {
function f<T>(x) = x
local function g<T>(x) = x
a = f(1)
b = g(2)
}
class C extends P {
local function f(x) = x
function g(x) = x
c = f(2)
d = g(3)
}
ok = true
@@ -8,6 +8,10 @@ facts {
true
true
}
["run microbenchmark (in generic method)"] {
true
true
}
["run output benchmark (in verbose mode)"] {
true
true
@@ -29,6 +33,7 @@ facts {
}
["evaluate entire benchmark module"] {
true
false
true
true
}
@@ -60,7 +60,9 @@ aValuesJoined = """
${b.outputs.nonString}
"""
typeArgs = "${null.prop}"
badThisRef = "Expected value of type `pkl.ref#Reference<reference#D, this>`, but got type `pkl.ref#Reference<reference#D, reference#A>`. Value: Reference(new reference#D {}, reference#A, new reference#BadResource { name =..."
typeArgs2 = "${null}"
badThisRef = "Expected value of type `pkl.ref#Reference<reference#D, reference#BadResource>`, but got type `pkl.ref#Reference<reference#D, reference#A>`. Value: Reference(new reference#D {}, reference#A, new reference#BadResource { name =..."
bareFunctionType = "Expected value of type `pkl.ref#Reference<reference#D, String>`, but got type `pkl.ref#Reference<reference#D, Function4>`. Value: Reference(new reference#D {}, Function4, null)"
constraintInConstructor = "${null}"
mappingAnyKey = "${null.mappingAnyKey[foo]}"
mappingTypeArgKey = "${null.mappingTypeArgKey[new Listing { \"foo\" }]}"
Loaded 100 of 160 files, more files were not shown because too many files have changed in this diff. Show more