Add inference for implicitly typed new in method arguments (#1768)

This commit is contained in:
Jen Basch
2026-09-02 23:55:24 +00:00
committed by GitHub
parent 92b26aca17
commit 7f5ae2a712
34 changed files with 678 additions and 323 deletions
@@ -109,8 +109,9 @@ import org.pkl.core.ast.expression.literal.MapLiteralNode;
import org.pkl.core.ast.expression.literal.PropertiesLiteralNodeGen;
import org.pkl.core.ast.expression.literal.SetLiteralNode;
import org.pkl.core.ast.expression.literal.TrueLiteralNode;
import org.pkl.core.ast.expression.member.InferParentWithinMethodNode;
import org.pkl.core.ast.expression.member.InferParentWithinObjectMethodNode;
import org.pkl.core.ast.expression.member.InferParentWithinMethodArgumentNodeGen;
import org.pkl.core.ast.expression.member.InferParentWithinMethodNodeGen;
import org.pkl.core.ast.expression.member.InferParentWithinObjectMethodNodeGen;
import org.pkl.core.ast.expression.member.InferParentWithinPropertyNodeGen;
import org.pkl.core.ast.expression.member.InvokeLexicalClassMethodNode;
import org.pkl.core.ast.expression.member.InvokeLexicalObjectMethodNode;
@@ -168,7 +169,7 @@ import org.pkl.core.ast.member.UnresolvedFunctionNode;
import org.pkl.core.ast.member.UnresolvedMethodNode;
import org.pkl.core.ast.member.UnresolvedPropertyNode;
import org.pkl.core.ast.member.UntypedObjectMemberNode;
import org.pkl.core.ast.type.GetParentForTypeNode;
import org.pkl.core.ast.type.GetParentForTypeNodeGen;
import org.pkl.core.ast.type.ResolveDeclaredTypeNode;
import org.pkl.core.ast.type.ResolveQualifiedDeclaredTypeNode;
import org.pkl.core.ast.type.ResolveSimpleDeclaredTypeNode;
@@ -1034,8 +1035,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var expr =
doVisitObjectBody(
newExpr.getBody(),
new GetParentForTypeNode(
GetParentForTypeNodeGen.create(
createSourceSection(newExpr),
language,
parentType,
symbolTable.getCurrentScope().getQualifiedName()));
if (type instanceof DeclaredType declaredType && declaredType.getArgs() != null) {
@@ -1079,9 +1081,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
org.pkl.core.runtime.Identifier scopeName = scope.getName();
inferredParentNode =
isObjectMethod
? new InferParentWithinObjectMethodNode(
? InferParentWithinObjectMethodNodeGen.create(
createSourceSection(expr.newSpan()), language, scopeName, new GetOwnerNode())
: new InferParentWithinMethodNode(
: InferParentWithinMethodNodeGen.create(
createSourceSection(expr.newSpan()), language, scopeName, new GetOwnerNode());
} else if (parent instanceof LetExpr letExpr && letExpr.getBindingExpr() == child) {
// TODO correctly infer parent, e.g. `let (x: Person = new {}) ...`
@@ -1089,6 +1091,16 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
.evalError("cannotInferParent")
.withSourceSection(createSourceSection(expr.newSpan()))
.build();
} else if (parent instanceof ArgumentList argumentList) {
// cases we can't cover currently
// - FunctionN.apply: the parameter type nodes are not stored
// - pkl.base intrinsic constructors: List(), Set(), Map(), Bytes()
// - generic methods: pkl.base#Pair(), etc.
// these will throw cannotInferParent at runtime
var sourceSection = createSourceSection(expr.newSpan());
var argIndex = argumentList.getArguments().indexOf(child);
inferredParentNode =
InferParentWithinMethodArgumentNodeGen.create(sourceSection, language, argIndex);
} else {
throw exceptionBuilder()
.evalError("cannotInferParent")
@@ -40,6 +40,7 @@ import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.ModuleInfo;
import org.pkl.core.runtime.VmDataSize;
import org.pkl.core.runtime.VmDuration;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.util.ArrayUtils;
import org.pkl.core.util.LateInit;
import org.pkl.parser.Lexer;
@@ -1066,16 +1067,9 @@ public final class SymbolTable {
* A scope where {@code this} has a special meaning (type constraint, object member predicate).
*
* <p>Technically, a scope where {@code this} isn't {@code frame.getArguments()[0]}, but the value
* at an auxiliary slot identified by {@link CustomThisScope#FRAME_SLOT_ID}.
* at an auxiliary slot identified by {@link VmUtils#CUSTOM_THIS_FRAME_SLOT_ID}.
*/
public static final class CustomThisScope extends Scope {
public static final Object FRAME_SLOT_ID =
new Object() {
@Override
public String toString() {
return "customThisSlot";
}
};
public CustomThisScope(Scope parent, FrameDescriptorBuilder frameDescriptorBuilder) {
super(
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.*;
import org.pkl.core.util.EconomicMaps;
@@ -132,7 +131,7 @@ public abstract class GeneratorPredicateMemberNode extends GeneratorMemberNode {
CompilerDirectives.transferToInterpreterAndInvalidate();
// deferred until execution time s.t. nodes of inlined type aliases get the right frame slot
customThisSlot =
frame.getFrameDescriptor().findOrAddAuxiliarySlot(CustomThisScope.FRAME_SLOT_ID);
frame.getFrameDescriptor().findOrAddAuxiliarySlot(VmUtils.CUSTOM_THIS_FRAME_SLOT_ID);
}
}
}
@@ -0,0 +1,41 @@
/*
* 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.expression.member;
import com.oracle.truffle.api.dsl.Idempotent;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.member.ObjectMethodNode;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.runtime.VmLanguage;
public abstract class AbstractInferParentFromMethodNode extends AbstractInferParentNode {
public AbstractInferParentFromMethodNode(SourceSection sourceSection, VmLanguage language) {
super(sourceSection, language);
}
protected abstract Method getMethod(VirtualFrame frame);
protected abstract @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method);
@Idempotent
protected boolean isFinalType(Method method, @Nullable TypeNode typeNode) {
return method instanceof ObjectMethodNode || (typeNode != null && typeNode.isFinalType());
}
}
@@ -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.expression.member;
import com.oracle.truffle.api.CompilerDirectives;
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.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.VmUtils;
public abstract class AbstractInferParentNode extends ExpressionNode {
protected final VmLanguage language;
public AbstractInferParentNode(SourceSection sourceSection, VmLanguage language) {
super(sourceSection);
this.language = language;
}
protected final Object getDefaultValue(
VirtualFrame frame,
@Nullable TypeNode typeNode,
SourceSection headerSection,
String qualifiedName) {
if (typeNode == null || typeNode instanceof UnknownTypeNode) {
return VmDynamic.empty();
}
var defaultValue = typeNode.createDefaultValue(frame, language, headerSection, qualifiedName);
if (defaultValue != null) {
return defaultValue;
}
CompilerDirectives.transferToInterpreter();
if (typeNode instanceof TypeVariableNode) {
throw exceptionBuilder().evalError("cannotInferParent").build();
}
// try to produce a more specific error message than "cannotInstantiateType"
var clazz = typeNode.getVmClass();
if (clazz != null) {
VmUtils.checkIsInstantiable(clazz, typeNode);
}
throw exceptionBuilder()
.evalError("cannotInstantiateType", typeNode.getSourceSection().getCharacters())
.build();
}
}
@@ -21,7 +21,8 @@ import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmUtils;
public abstract sealed class AbstractInvokeLexicalMethodNode extends AbstractInvokeMethodNode
public abstract sealed class AbstractInvokeLexicalMethodNode
extends AbstractInvokeLexicalOrQualifiedMethodNode
permits InvokeLexicalClassMethodNode, InvokeLexicalObjectMethodNode {
private final int levelsUp;
@@ -0,0 +1,86 @@
/*
* 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.expression.member;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
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.Method;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
/**
* A non-virtual (statically dispatched) method call.
*
* <p>Subclasses differ only in how they obtain the {@code owner}/{@code receiver} that the method
* is invoked on: either by walking the frame chain ({@link InvokeLexicalClassMethodNode}, {@link
* InvokeLexicalObjectMethodNode}), or off of an explicit receiver expression ({@link
* InvokeQualifiedClassMethodNode}, {@link InvokeQualifiedObjectMethodNode}).
*/
public abstract sealed class AbstractInvokeLexicalOrQualifiedMethodNode
extends AbstractInvokeMethodNode
permits AbstractInvokeQualifiedMethodNode, AbstractInvokeLexicalMethodNode {
protected final Identifier methodName;
private final boolean needsConst;
@Child private @Nullable DirectCallNode callNode;
@CompilationFinal protected boolean isConstChecked;
protected AbstractInvokeLexicalOrQualifiedMethodNode(
SourceSection sourceSection,
Identifier methodName,
ExpressionNode[] argumentNodes,
boolean needsConst) {
super(sourceSection, argumentNodes);
this.methodName = methodName;
this.needsConst = needsConst;
this.isConstChecked = false;
}
protected final Object invoke(VirtualFrame frame, VmObjectLike owner, Object receiver) {
checkConst(owner);
var method = getMethod(owner);
var args = evalArgs(frame, method, owner, receiver);
return getCallNode(method, owner).call(args);
}
private void checkConst(VmObjectLike owner) {
if (!needsConst || isConstChecked) {
return;
}
CompilerDirectives.transferToInterpreterAndInvalidate();
doCheckConst(owner);
isConstChecked = true;
}
protected abstract Method getMethod(VmObjectLike owner);
protected abstract void doCheckConst(VmObjectLike owner);
protected DirectCallNode getCallNode(Method method, VmObjectLike owner) {
if (callNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
callNode = DirectCallNode.create(method.getCallTarget(getSourceSection(), owner));
insert(callNode);
}
assert callNode != null;
return callNode;
}
}
@@ -15,79 +15,52 @@
*/
package org.pkl.core.ast.expression.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.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
import org.pkl.core.ast.member.Method;
import org.pkl.core.runtime.VmUtils;
/**
* A non-virtual (statically dispatched) method call.
*
* <p>Subclasses differ only in how they obtain the {@code owner}/{@code receiver} that the method
* is invoked on: either by walking the frame chain ({@link InvokeLexicalClassMethodNode}, {@link
* InvokeLexicalObjectMethodNode}), or off of an explicit receiver expression ({@link
* InvokeQualifiedClassMethodNode}, {@link InvokeQualifiedObjectMethodNode}).
*/
public abstract sealed class AbstractInvokeMethodNode extends ExpressionNode
permits AbstractInvokeQualifiedMethodNode, AbstractInvokeLexicalMethodNode {
public abstract class AbstractInvokeMethodNode extends ExpressionNode {
protected final Identifier methodName;
private final boolean needsConst;
@Children private ExpressionNode[] argumentNodes;
@Child private @Nullable DirectCallNode callNode;
@CompilationFinal protected boolean isConstChecked;
@Children protected final ExpressionNode[] argumentNodes;
protected AbstractInvokeMethodNode(
SourceSection sourceSection,
Identifier methodName,
ExpressionNode[] argumentNodes,
boolean needsConst) {
public AbstractInvokeMethodNode(SourceSection sourceSection, ExpressionNode[] argumentNodes) {
super(sourceSection);
this.methodName = methodName;
this.argumentNodes = argumentNodes;
this.needsConst = needsConst;
this.isConstChecked = false;
}
@TruffleBoundary
private int getMethodSlot(FrameDescriptor frameDescriptor) {
// can't store the slot id as this node may be called from different root nodes
// (see constraints14 snippet)
return frameDescriptor.findOrAddAuxiliarySlot(VmUtils.METHOD_FRAME_SLOT_ID);
}
@ExplodeLoop
protected final Object invoke(VirtualFrame frame, VmObjectLike owner, Object receiver) {
checkConst(owner);
protected Object[] evalArgs(
VirtualFrame frame, @Nullable Method method, Object owner, @Nullable Object receiver) {
// TODO: optimize this away when the call does not contain any implicit new args
var methodSlot = getMethodSlot(frame.getFrameDescriptor());
var prevMethod = frame.getAuxiliarySlot(methodSlot);
frame.setAuxiliarySlot(methodSlot, method);
var args = new Object[2 + argumentNodes.length];
args[0] = receiver;
args[1] = owner;
for (var i = 0; i < argumentNodes.length; i++) {
args[2 + i] = argumentNodes[i].executeGeneric(frame);
try {
for (var i = 0; i < argumentNodes.length; i++) {
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
} finally {
frame.setAuxiliarySlot(methodSlot, prevMethod);
}
return getCallNode(owner).call(args);
}
private void checkConst(VmObjectLike owner) {
if (!needsConst || isConstChecked) {
return;
}
CompilerDirectives.transferToInterpreterAndInvalidate();
doCheckConst(owner);
isConstChecked = true;
}
protected abstract CallTarget getCallTarget(VmObjectLike owner);
protected abstract void doCheckConst(VmObjectLike owner);
protected DirectCallNode getCallNode(VmObjectLike owner) {
if (callNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
callNode = DirectCallNode.create(getCallTarget(owner));
insert(callNode);
}
assert callNode != null;
return callNode;
return args;
}
}
@@ -21,7 +21,8 @@ import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmTyped;
public abstract sealed class AbstractInvokeQualifiedMethodNode extends AbstractInvokeMethodNode
public abstract sealed class AbstractInvokeQualifiedMethodNode
extends AbstractInvokeLexicalOrQualifiedMethodNode
permits InvokeQualifiedClassMethodNode, InvokeQualifiedObjectMethodNode {
@Child private ExpressionNode getReceiverNode;
@@ -0,0 +1,88 @@
/*
* 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.expression.member;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
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.VmUtils;
public abstract class InferParentWithinMethodArgumentNode
extends AbstractInferParentFromMethodNode {
private final int argIndex;
public InferParentWithinMethodArgumentNode(
SourceSection sourceSection, VmLanguage language, int argIndex) {
super(sourceSection, language);
this.argIndex = argIndex;
}
@TruffleBoundary
private int getMethodSlot(FrameDescriptor frameDescriptor) {
var methodSlot = frameDescriptor.getAuxiliarySlots().get(VmUtils.METHOD_FRAME_SLOT_ID);
if (methodSlot == null) {
// used in intrinsic constructor e.g. pkl.base#List()
throw exceptionBuilder().evalError("cannotInferParent").build();
}
return methodSlot;
}
@Override
protected Method getMethod(VirtualFrame frame) {
var method = (Method) frame.getAuxiliarySlot(getMethodSlot(frame.getFrameDescriptor()));
if (method == null) {
// used in FunctionN.apply()
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder().evalError("cannotInferParent").build();
}
return method;
}
@Override
protected @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method) {
return method.getParameterTypeNode(frame, argIndex);
}
// keep specializations in sync with other AbstractInferParentFromMethodNode subclasses
@Specialization(
guards = {"getMethod(frame) == cachedMethod", "isFinalType(cachedMethod, typeNode)"})
protected final Object evalCached(
@SuppressWarnings("unused") VirtualFrame frame,
@Cached("getMethod(frame)") @SuppressWarnings("unused") Method cachedMethod,
@Cached("getTypeNode(frame, cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode,
@Cached(
"getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName())")
Object defaultValue) {
return defaultValue;
}
@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());
}
}
@@ -15,66 +15,65 @@
*/
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.dsl.Cached;
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.ExpressionNode;
import org.pkl.core.ast.type.TypeNode.UnknownTypeNode;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.runtime.*;
/** Infers the parent to amend in `function createPerson(): Person = new { ... }`. */
public final class InferParentWithinMethodNode extends ExpressionNode {
private final VmLanguage language;
public abstract class InferParentWithinMethodNode extends AbstractInferParentFromMethodNode {
private final Identifier methodName;
@Child private @Nullable ExpressionNode ownerNode;
@CompilationFinal private @Nullable Object inferredParent;
@Child private ExpressionNode ownerNode;
public InferParentWithinMethodNode(
protected InferParentWithinMethodNode(
SourceSection sourceSection,
VmLanguage language,
Identifier methodName,
ExpressionNode ownerNode) {
super(sourceSection);
this.language = language;
super(sourceSection, language);
this.methodName = methodName;
this.ownerNode = ownerNode;
}
@Override
public Object executeGeneric(VirtualFrame frame) {
if (inferredParent != null) return inferredParent;
// remaining code only runs first time this node is executed
// (assuming evaluation isn't continued despite errors)
CompilerDirectives.transferToInterpreter();
assert ownerNode != null;
protected Method getMethod(VirtualFrame frame) {
var owner = (VmObjectLike) ownerNode.executeGeneric(frame);
assert owner.isPrototype();
var method = owner.getVmClass().getDeclaredMethod(methodName);
assert method != null;
return method;
}
var returnTypeNode = method.getReturnTypeNode();
if (returnTypeNode == null || returnTypeNode instanceof UnknownTypeNode) {
inferredParent = VmDynamic.empty();
ownerNode = null;
return inferredParent;
}
@Override
protected @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method) {
return method.getReturnTypeNode(frame);
}
var returnTypeDefaultValue =
returnTypeNode.createDefaultValue(
frame, language, method.getHeaderSection(), method.getQualifiedName());
if (returnTypeDefaultValue != null) {
inferredParent = returnTypeDefaultValue;
ownerNode = null;
return inferredParent;
}
// keep specializations in sync with other AbstractInferParentFromMethodNode subclasses
throw exceptionBuilder().evalError("cannotInferParent").build();
@Specialization(
guards = {"getMethod(frame) == cachedMethod", "isFinalType(cachedMethod, typeNode)"})
protected final Object evalCached(
@SuppressWarnings("unused") VirtualFrame frame,
@Cached("getMethod(frame)") @SuppressWarnings("unused") Method cachedMethod,
@Cached("getTypeNode(frame, cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode,
@Cached(
"getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName())")
Object defaultValue) {
return defaultValue;
}
@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());
}
}
@@ -15,34 +15,31 @@
*/
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.dsl.Cached;
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.ExpressionNode;
import org.pkl.core.ast.member.Method;
import org.pkl.core.ast.member.ObjectMethodNode;
import org.pkl.core.ast.type.TypeNode.UnknownTypeNode;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmDynamic;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmObjectLike;
/** Infers the parent to amend in `obj { local function createPerson(): Person = new { ... } }`. */
public final class InferParentWithinObjectMethodNode extends ExpressionNode {
private final VmLanguage language;
public abstract class InferParentWithinObjectMethodNode extends AbstractInferParentFromMethodNode {
private final Identifier localMethodName;
@Child private @Nullable ExpressionNode ownerNode;
@CompilationFinal private @Nullable Object inferredParent;
@Child private ExpressionNode ownerNode;
public InferParentWithinObjectMethodNode(
protected InferParentWithinObjectMethodNode(
SourceSection sourceSection,
VmLanguage language,
Identifier localMethodName,
ExpressionNode ownerNode) {
super(sourceSection);
this.language = language;
super(sourceSection, language);
this.localMethodName = localMethodName;
this.ownerNode = ownerNode;
@@ -50,15 +47,7 @@ public final class InferParentWithinObjectMethodNode extends ExpressionNode {
}
@Override
public Object executeGeneric(VirtualFrame frame) {
if (inferredParent != null) return inferredParent;
// remaining code only runs first time this node is executed
// (assuming evaluation isn't continued despite errors)
CompilerDirectives.transferToInterpreter();
assert ownerNode != null;
protected Method getMethod(VirtualFrame frame) {
var owner = (VmObjectLike) ownerNode.executeGeneric(frame);
var member = owner.getMember(localMethodName);
@@ -66,23 +55,32 @@ public final class InferParentWithinObjectMethodNode extends ExpressionNode {
var methodNode = (ObjectMethodNode) member.getMemberNode();
assert methodNode != null;
return methodNode;
}
var returnTypeNode = methodNode.getReturnTypeNode();
if (returnTypeNode == null || returnTypeNode instanceof UnknownTypeNode) {
inferredParent = VmDynamic.empty();
ownerNode = null;
return inferredParent;
}
@Override
protected @Nullable TypeNode getTypeNode(VirtualFrame frame, Method method) {
return method.getReturnTypeNode(frame);
}
Object defaultReturnTypeValue =
returnTypeNode.createDefaultValue(
frame, language, member.getHeaderSection(), member.getQualifiedName());
if (defaultReturnTypeValue != null) {
inferredParent = defaultReturnTypeValue;
ownerNode = null;
return inferredParent;
}
// keep specializations in sync with other AbstractInferParentFromMethodNode subclasses
throw exceptionBuilder().evalError("cannotInferParent").build();
@Specialization(
guards = {"getMethod(frame) == cachedMethod", "isFinalType(cachedMethod, typeNode)"})
protected final Object evalCached(
@SuppressWarnings("unused") VirtualFrame frame,
@Cached("getMethod(frame)") @SuppressWarnings("unused") Method cachedMethod,
@Cached("getTypeNode(frame, cachedMethod)") @SuppressWarnings("unused") TypeNode typeNode,
@Cached(
"getDefaultValue(frame, typeNode, cachedMethod.getHeaderSection(), cachedMethod.getQualifiedName())")
Object defaultValue) {
return defaultValue;
}
@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());
}
}
@@ -15,9 +15,9 @@
*/
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.Method;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
@@ -45,9 +45,9 @@ public final class InvokeLexicalClassMethodNode extends AbstractInvokeLexicalMet
}
@Override
protected CallTarget getCallTarget(VmObjectLike owner) {
protected Method getMethod(VmObjectLike owner) {
var method = owner.getVmClass().getDeclaredMethod(methodName);
assert method != null;
return method.getCallTarget(getSourceSection());
return method;
}
}
@@ -15,10 +15,11 @@
*/
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.source.SourceSection;
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.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
@@ -43,9 +44,11 @@ public final class InvokeLexicalObjectMethodNode extends AbstractInvokeLexicalMe
}
@Override
protected CallTarget getCallTarget(VmObjectLike owner) {
var method = owner.getMember(methodName);
assert method != null && method.isLocal();
return (CallTarget) method.getCallTarget().call(owner, owner);
protected Method getMethod(VmObjectLike owner) {
var member = owner.getMember(methodName);
assert member != null && member.isLocal();
var method = (ObjectMethodNode) member.getMemberNode();
assert method != null;
return method;
}
}
@@ -17,17 +17,16 @@ 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.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.ClassMethod;
import org.pkl.core.runtime.VmObjectLike;
/** A non-virtual ("direct") method call. Used only for methods on {@code pkl:base}. */
public final class InvokeMethodDirectNode extends ExpressionNode {
public final class InvokeMethodDirectNode extends AbstractInvokeMethodNode {
private final ClassMethod method;
private final VmObjectLike owner;
@Child private ExpressionNode receiverNode;
@Children private final ExpressionNode[] argumentNodes;
@Child private DirectCallNode callNode;
@@ -37,24 +36,17 @@ public final class InvokeMethodDirectNode extends ExpressionNode {
ExpressionNode receiverNode,
ExpressionNode[] argumentNodes) {
super(sourceSection);
super(sourceSection, argumentNodes);
this.method = method;
this.owner = method.getOwner();
this.receiverNode = receiverNode;
this.argumentNodes = argumentNodes;
callNode = DirectCallNode.create(method.getCallTarget(sourceSection));
}
@Override
@ExplodeLoop
public Object executeGeneric(VirtualFrame frame) {
var args = new Object[2 + argumentNodes.length];
args[0] = receiverNode.executeGeneric(frame);
args[1] = owner;
for (var i = 0; i < argumentNodes.length; i++) {
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
var args = evalArgs(frame, method, owner, receiverNode.executeGeneric(frame));
return callNode.call(args);
}
}
@@ -26,7 +26,6 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.GenerateWrapper;
import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
@@ -43,9 +42,8 @@ import org.pkl.core.runtime.VmFunction;
@NodeChild(value = "receiverNode", type = ExpressionNode.class)
@NodeChild(value = "receiverClassNode", type = GetClassNode.class, executeWith = "receiverNode")
@GenerateWrapper
public abstract class InvokeMethodVirtualNode extends ExpressionNode {
public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode {
protected final Identifier methodName;
@Children private final ExpressionNode[] argumentNodes;
private final MemberLookupMode lookupMode;
private final boolean needsConst;
@@ -56,9 +54,8 @@ public abstract class InvokeMethodVirtualNode extends ExpressionNode {
MemberLookupMode lookupMode,
boolean needsConst) {
super(sourceSection);
super(sourceSection, argumentNodes);
this.methodName = methodName;
this.argumentNodes = argumentNodes;
this.lookupMode = lookupMode;
this.needsConst = needsConst;
}
@@ -78,7 +75,6 @@ public abstract class InvokeMethodVirtualNode extends ExpressionNode {
public abstract Object executeWith(VirtualFrame frame, Object value, VmClass clazz);
/** Intrinsifies `FunctionN.apply()` calls. */
@ExplodeLoop
@Specialization(guards = {"methodName == APPLY", "receiver.getCallTarget() == cachedCallTarget"})
protected Object evalFunctionCached(
VirtualFrame frame,
@@ -87,37 +83,21 @@ public abstract class InvokeMethodVirtualNode extends ExpressionNode {
@Cached("receiver.getCallTarget()") @SuppressWarnings("unused")
RootCallTarget cachedCallTarget,
@Cached("create(cachedCallTarget)") DirectCallNode callNode) {
var args = new Object[2 + argumentNodes.length];
args[0] = receiver.getThisValue();
args[1] = receiver;
for (var i = 0; i < argumentNodes.length; i++) {
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
var args = evalArgs(frame, null, receiver, receiver.getThisValue());
return callNode.call(args);
}
/** Intrinsifies `FunctionN.apply()` calls. */
@ExplodeLoop
@Specialization(guards = "methodName == APPLY", replaces = "evalFunctionCached")
protected Object evalFunction(
VirtualFrame frame,
VmFunction receiver,
@SuppressWarnings("unused") VmClass receiverClass,
@Exclusive @Cached("create()") IndirectCallNode callNode) {
var args = new Object[2 + argumentNodes.length];
args[0] = receiver.getThisValue();
args[1] = receiver;
for (var i = 0; i < argumentNodes.length; i++) {
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
var args = evalArgs(frame, null, receiver, receiver.getThisValue());
return callNode.call(receiver.getCallTarget(), args);
}
@ExplodeLoop
@Specialization(guards = "receiverClass == cachedReceiverClass")
protected Object evalCached(
VirtualFrame frame,
@@ -126,32 +106,18 @@ public abstract class InvokeMethodVirtualNode extends ExpressionNode {
@Cached("receiverClass") @SuppressWarnings("unused") VmClass cachedReceiverClass,
@Cached("resolveMethod(receiverClass)") ClassMethod method,
@Cached("create(method.getCallTarget(sourceSection))") DirectCallNode callNode) {
var args = new Object[2 + argumentNodes.length];
args[0] = receiver;
args[1] = method.getOwner();
for (var i = 0; i < argumentNodes.length; i++) {
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
var args = evalArgs(frame, method, method.getOwner(), receiver);
return callNode.call(args);
}
@ExplodeLoop
@Specialization(replaces = "evalCached")
protected Object eval(
VirtualFrame frame,
Object receiver,
VmClass receiverClass,
@Exclusive @Cached("create()") IndirectCallNode callNode) {
var method = resolveMethod(receiverClass);
var args = new Object[2 + argumentNodes.length];
args[0] = receiver;
args[1] = method.getOwner();
for (var i = 0; i < argumentNodes.length; i++) {
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
var args = evalArgs(frame, method, method.getOwner(), receiver);
// Deprecation should not report here (getCallTarget(sourceSection)), as this happens for each
// and every call.
@@ -15,9 +15,9 @@
*/
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.Method;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
@@ -42,9 +42,9 @@ public final class InvokeQualifiedClassMethodNode extends AbstractInvokeQualifie
}
@Override
protected CallTarget getCallTarget(VmObjectLike owner) {
protected Method getMethod(VmObjectLike owner) {
var method = owner.getVmClass().getDeclaredMethod(methodName);
assert method != null;
return method.getCallTarget(getSourceSection());
return method;
}
}
@@ -15,10 +15,11 @@
*/
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.source.SourceSection;
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.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
@@ -43,9 +44,11 @@ public final class InvokeQualifiedObjectMethodNode extends AbstractInvokeQualifi
}
@Override
protected CallTarget getCallTarget(VmObjectLike owner) {
var method = owner.getMember(methodName);
assert method != null && method.isLocal();
return (CallTarget) method.getCallTarget().call(owner, owner);
protected Method getMethod(VmObjectLike owner) {
var member = owner.getMember(methodName);
assert member != null && member.isLocal();
var method = (ObjectMethodNode) member.getMemberNode();
assert method != null;
return method;
}
}
@@ -20,7 +20,6 @@ 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.ExplodeLoop;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.ClassMethod;
@@ -28,9 +27,8 @@ import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmFunction;
import org.pkl.core.runtime.VmUtils;
public abstract class InvokeSuperMethodNode extends ExpressionNode {
public abstract class InvokeSuperMethodNode extends AbstractInvokeMethodNode {
private final Identifier methodName;
@Children private final ExpressionNode[] argumentNodes;
private final boolean needsConst;
protected InvokeSuperMethodNode(
@@ -39,29 +37,21 @@ public abstract class InvokeSuperMethodNode extends ExpressionNode {
ExpressionNode[] argumentNodes,
boolean needsConst) {
super(sourceSection);
super(sourceSection, argumentNodes);
this.needsConst = needsConst;
assert !methodName.isLocalMethod();
this.methodName = methodName;
this.argumentNodes = argumentNodes;
}
@ExplodeLoop
@Specialization
protected Object eval(
VirtualFrame frame,
@Cached(value = "findSupermethod(frame)", neverDefault = true) ClassMethod supermethod,
@Cached("create(supermethod.getCallTarget(sourceSection))") DirectCallNode callNode) {
var args = new Object[2 + argumentNodes.length];
args[0] = VmUtils.getReceiverOrNull(frame);
args[1] = supermethod.getOwner();
for (int i = 0; i < argumentNodes.length; i++) {
args[2 + i] = argumentNodes[i].executeGeneric(frame);
}
var args =
evalArgs(frame, supermethod, supermethod.getOwner(), VmUtils.getReceiverOrNull(frame));
return callNode.call(args);
}
@@ -21,7 +21,6 @@ import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.SimpleRootNode;
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmUtils;
@@ -54,7 +53,7 @@ public final class ExecuteCustomThisWithRootNode extends ExpressionNode {
int[] parameterSlots) {
super(sourceSection);
this.expressionNode = expressionNode;
frameDescriptor.findOrAddAuxiliarySlot(CustomThisScope.FRAME_SLOT_ID);
frameDescriptor.findOrAddAuxiliarySlot(VmUtils.CUSTOM_THIS_FRAME_SLOT_ID);
var rootNode =
new SimpleRootNode(
VmLanguage.get(this),
@@ -18,6 +18,7 @@ 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;
@@ -28,7 +29,7 @@ import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.runtime.*;
import org.pkl.core.util.LateInit;
public final class ClassMethod extends ClassMember {
public final class ClassMethod extends ClassMember implements Method {
private final List<TypeParameter> typeParameters;
// null = not deprecated, "" = no/empty message in the @Deprecated body
@@ -91,6 +92,11 @@ public final class ClassMethod extends ClassMember {
return functionNode.getCallTarget();
}
@Override
public CallTarget getCallTarget(SourceSection callSite, VmObjectLike owner) {
return getCallTarget(callSite);
}
public int getParameterCount() {
return functionNode.getParameterCount();
}
@@ -99,6 +105,11 @@ public final class ClassMethod extends ClassMember {
return functionNode.getReturnTypeNode();
}
@Override
public @Nullable TypeNode getReturnTypeNode(VirtualFrame frame) {
return functionNode.getReturnTypeNode();
}
@Override
public String getCallSignature() {
return functionNode.getCallSignature();
@@ -131,4 +142,8 @@ public final class ClassMethod extends ClassMember {
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);
}
}
@@ -81,6 +81,11 @@ public final class FunctionNode extends RegularMemberNode {
return paramCount;
}
public @Nullable TypeNode getParameterTypeNode(int idx) {
if (idx >= paramCount) return null;
return parameterTypeNodes[idx];
}
public @Nullable TypeNode getReturnTypeNode() {
return returnTypeNode;
}
@@ -176,7 +181,7 @@ public final class FunctionNode extends RegularMemberNode {
assert argCount != paramCount;
return exceptionBuilder()
.evalError("wrongFunctionArgumentCount", paramCount, argCount)
.evalError("wrongFunctionArgumentCount", paramCount, argCount, paramCount == 1 ? "" : "s")
.withSourceSection(member.getHeaderSection())
.build();
}
@@ -0,0 +1,35 @@
/*
* 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.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);
@Nullable TypeNode getParameterTypeNode(VirtualFrame frame, int idx);
@Nullable TypeNode getReturnTypeNode(VirtualFrame frame);
SourceSection getHeaderSection();
String getQualifiedName();
}
@@ -20,13 +20,14 @@ import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
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.*;
public final class ObjectMethodNode extends RegularMemberNode {
public final class ObjectMethodNode extends RegularMemberNode implements Method {
private final VmLanguage language;
private final int parameterCount;
@Children private final @Nullable UnresolvedTypeNode[] unresolvedParameterTypeNodes;
@@ -57,29 +58,50 @@ public final class ObjectMethodNode extends RegularMemberNode {
return functionNode.getReturnTypeNode();
}
@Override
public @Nullable TypeNode getReturnTypeNode(VirtualFrame frame) {
return getFunctionNode(frame, true).getReturnTypeNode();
}
@Override
public CallTarget getCallTarget(SourceSection callSite, VmObjectLike owner) {
return (CallTarget) getCallTarget().call(owner, owner);
}
@Override
public @Nullable TypeNode getParameterTypeNode(VirtualFrame frame, int idx) {
return getFunctionNode(frame, true).getParameterTypeNode(idx);
}
@Override
protected CallTarget executeImpl(VirtualFrame frame) {
if (functionNode == null) {
CompilerDirectives.transferToInterpreter();
return getFunctionNode(frame, false).getCallTarget();
}
var parameterTypeNodes =
VmUtils.resolveParameterTypes(frame, getFrameDescriptor(), unresolvedParameterTypeNodes);
private FunctionNode getFunctionNode(VirtualFrame frame, boolean isPreInit) {
if (functionNode != null) return functionNode;
CompilerDirectives.transferToInterpreterAndInvalidate();
var returnTypeNode =
unresolvedReturnTypeNode != null ? unresolvedReturnTypeNode.execute(frame) : null;
functionNode =
new FunctionNode(
language,
getFrameDescriptor(),
member,
parameterCount,
parameterTypeNodes,
returnTypeNode,
true,
bodyNode);
if (isPreInit) {
// TODO: with VmType/CreateDefaultValueNode this may be removable
adoptChildren();
}
var parameterTypeNodes =
VmUtils.resolveParameterTypes(frame, getFrameDescriptor(), unresolvedParameterTypeNodes);
return functionNode.getCallTarget();
var returnTypeNode =
unresolvedReturnTypeNode != null ? unresolvedReturnTypeNode.execute(frame) : null;
functionNode =
new FunctionNode(
language,
getFrameDescriptor(),
member,
parameterCount,
parameterTypeNodes,
returnTypeNode,
true,
bodyNode);
return functionNode;
}
}
@@ -65,15 +65,15 @@ public final class PropertyTypeNode extends PklRootNode {
}
public @Nullable Object getDefaultValue(VirtualFrame frame) {
if (!defaultValueInitialized) {
defaultValue =
typeNode.createDefaultValue(
frame, VmLanguage.get(this), getSourceSection(), qualifiedPropertyName);
// can't cache default value for `module` type in a non-final module because it's a self-type
// (the default value changes when inherited).
if (typeNode.isFinalType()) {
defaultValueInitialized = true;
}
if (defaultValueInitialized) return defaultValue;
defaultValue =
typeNode.createDefaultValue(
frame, VmLanguage.get(this), getSourceSection(), qualifiedPropertyName);
// can't cache default value for `module` type in a non-final module because it's a self-type
// (the default value changes when inherited).
if (typeNode.isFinalType()) {
defaultValueInitialized = true;
}
return defaultValue;
}
@@ -16,31 +16,34 @@
package org.pkl.core.ast.type;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.dsl.Bind;
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.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.expression.member.AbstractInferParentNode;
import org.pkl.core.runtime.*;
import org.pkl.core.util.LateInit;
/** Resolves `<type>` to the type's default value in `new <type> { ... }`. */
public final class GetParentForTypeNode extends ExpressionNode {
@Child private UnresolvedTypeNode unresolvedTypeNode;
public abstract class GetParentForTypeNode extends AbstractInferParentNode {
@Child private @Nullable UnresolvedTypeNode unresolvedTypeNode;
@Child private @Nullable TypeNode typeNode;
private final String qualifiedName;
protected final String qualifiedName;
@CompilationFinal @LateInit Object defaultValue;
public GetParentForTypeNode(
SourceSection sourceSection, UnresolvedTypeNode unresolvedTypeNode, String qualifiedName) {
super(sourceSection);
protected GetParentForTypeNode(
SourceSection sourceSection,
VmLanguage language,
UnresolvedTypeNode unresolvedTypeNode,
String qualifiedName) {
super(sourceSection, language);
this.unresolvedTypeNode = unresolvedTypeNode;
this.qualifiedName = qualifiedName;
}
private TypeNode getTypeNode(VirtualFrame frame) {
protected TypeNode getTypeNode(VirtualFrame frame) {
if (typeNode == null) {
assert unresolvedTypeNode != null;
CompilerDirectives.transferToInterpreterAndInvalidate();
typeNode = unresolvedTypeNode.execute(frame);
adoptChildren();
@@ -48,33 +51,19 @@ public final class GetParentForTypeNode extends ExpressionNode {
return typeNode;
}
@Override
public Object executeGeneric(VirtualFrame frame) {
//noinspection ConstantValue
if (defaultValue != null) return defaultValue;
CompilerDirectives.transferToInterpreterAndInvalidate();
@Specialization(guards = {"typeNode.isFinalType()"})
protected final Object evalCached(
@SuppressWarnings("unused") VirtualFrame frame,
@Bind("getTypeNode(frame)") @SuppressWarnings("unused") TypeNode typeNode,
@Cached(
value = "getDefaultValue(frame, typeNode, sourceSection, qualifiedName)",
neverDefault = true)
Object defaultValue) {
return defaultValue;
}
var typeNode = getTypeNode(frame);
var defaultValue =
typeNode.createDefaultValue(frame, VmLanguage.get(this), sourceSection, qualifiedName);
// can't cache default value for `module`/`this` types in a non-final modules/classes because
// they're self types (the default value changes when inherited).
if (typeNode.isFinalType() && defaultValue != null) {
unresolvedTypeNode = null;
this.defaultValue = defaultValue;
}
if (defaultValue != null) {
return defaultValue;
}
// try to produce a more specific error message than "cannotInstantiateType"
var clazz = typeNode.getVmClass();
if (clazz != null) VmUtils.checkIsInstantiable(clazz, typeNode);
throw exceptionBuilder()
.evalError("cannotInstantiateType", typeNode.getSourceSection().getCharacters())
.build();
@Specialization(replaces = "evalCached")
protected final Object eval(VirtualFrame frame, @Bind("getTypeNode(frame)") TypeNode typeNode) {
return getDefaultValue(frame, typeNode, sourceSection, qualifiedName);
}
}
@@ -57,7 +57,7 @@ public final class IdentityMixinNode extends PklRootNode {
if (arguments.length != 3) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("wrongFunctionArgumentCount", 1, arguments.length - 2)
.evalError("wrongFunctionArgumentCount", 1, arguments.length - 2, "")
.withSourceSection(sourceSection)
.build();
}
@@ -44,7 +44,6 @@ 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.builder.SymbolTable.CustomThisScope;
import org.pkl.core.ast.expression.primary.GetModuleNode;
import org.pkl.core.ast.expression.primary.GetReceiverClassNode;
import org.pkl.core.ast.expression.primary.GetReceiverNode;
@@ -2906,25 +2905,21 @@ public abstract class TypeNode extends PklNode {
return this;
}
@TruffleBoundary
private int getCustomThisSlot(FrameDescriptor frameDescriptor) {
// can't store the slot id as this node may be called from different root nodes
// (see constraints14 snippet)
return frameDescriptor.findOrAddAuxiliarySlot(VmUtils.CUSTOM_THIS_FRAME_SLOT_ID);
}
@ExplodeLoop
protected Object executeLazily(VirtualFrame frame, Object value) {
int customThisSlot;
var numberOfAuxiliarySlots = frame.getFrameDescriptor().getNumberOfAuxiliarySlots();
if (numberOfAuxiliarySlots == 0) {
CompilerDirectives.transferToInterpreterAndInvalidate();
customThisSlot =
frame.getFrameDescriptor().findOrAddAuxiliarySlot(CustomThisScope.FRAME_SLOT_ID);
} else {
// assertion: we only use auxiliary slots for custom `this`.
assert numberOfAuxiliarySlots == 1;
customThisSlot = 0;
}
var ret = childNode.executeLazily(frame, value);
var localContext = language.localContext.get();
var prevShouldTypeCheck = localContext.shouldEagerTypecheck();
localContext.shouldEagerTypecheck(true);
frame.setAuxiliarySlot(customThisSlot, value);
frame.setAuxiliarySlot(getCustomThisSlot(frame.getFrameDescriptor()), value);
try {
for (var node : constraintNodes) {
node.execute(frame);
@@ -52,7 +52,6 @@ import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.SimpleRootNode;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.builder.AstBuilder;
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
import org.pkl.core.ast.expression.primary.CustomThisNode;
import org.pkl.core.ast.expression.primary.ThisNode;
import org.pkl.core.ast.member.*;
@@ -77,6 +76,22 @@ public final class VmUtils {
public static final URI REPL_TEXT_URI = URI.create(REPL_TEXT);
public static final Object CUSTOM_THIS_FRAME_SLOT_ID =
new Object() {
@Override
public String toString() {
return "customThisSlot";
}
};
public static final Object METHOD_FRAME_SLOT_ID =
new Object() {
@Override
public String toString() {
return "method";
}
};
private static final Engine PKL_ENGINE =
Engine.newBuilder("pkl").option("engine.WarnInterpreterOnly", "false").build();
@@ -1066,7 +1081,7 @@ public final class VmUtils {
}
public static int findCustomThisSlot(VirtualFrame frame) {
var result = frame.getFrameDescriptor().getAuxiliarySlots().get(CustomThisScope.FRAME_SLOT_ID);
var result = frame.getFrameDescriptor().getAuxiliarySlots().get(CUSTOM_THIS_FRAME_SLOT_ID);
assert result != null;
return result;
}
@@ -310,7 +310,7 @@ cannotDefineExternalMember=\
External members can only be defined by standard library modules.
wrongFunctionArgumentCount=\
Expected {0} function arguments but got {1}.
Expected {0} function argument{2} but got {1}.
noOuterScope=\
Top-level scope does not have an outer scope.
@@ -0,0 +1,50 @@
open module inferParameterType
import "pkl:test"
hidden x: Int
open class Foo extends module {
function bar(baz: Foo): Int = super.bar(baz) * this.x
function superBar(baz: Foo): Int = super.bar(new { x = 5 + baz.x }) * this.x
function thisBar(baz: Foo): Int = this.x + baz.x
}
class Qux extends Foo {
function bar(baz: Foo): Int = thisBar(new { x = baz.x })
}
function bar(baz: Foo): Int = baz.x
const function outerMethod(a: Int, b: Qux) = a + b.x * 2
local quux = (a: Foo) -> a.x
qualified = this.bar(new { x = 1 })
unqualifiedLexical = bar(new { x = 1 })
unqualifiedThis = new Qux { x = 7 }.bar(new { x = 1 })
`super` = new Foo { x = 2 }.superBar(new Foo { x = 3 })
nestedMethodCalls = outerMethod(bar(new { x = 8 }), new { x = 9 })
objectMethod {
local function corge(prop: Foo) = prop
call = corge(new { x = 0 }).x
tooManyParams = test.catch(() -> corge(new {}, new {}))
}
intrinsicConstructor = test.catch(() -> List(new {}))
genericMethod = test.catch(() -> Pair(new {}, new {}))
fnApply = test.catch(() -> quux.apply(new {}))
tooManyParams = test.catch(() -> bar(new {}, new {}))
class Bar1 { x: Int = 1 }
class Bar2 { x: Int = 2 }
class C1 { function f(p: Bar1) = p.x }
class C2 { function f(p: Bar2) = p.x * 10 }
local function call(a) = a.f(new {}) // poly call
polyCall1 = call(new C1 {})
polyCall2 = call(new C2 {})
@@ -139,18 +139,18 @@ one {
res31 {
name = "Other"
}
res32 = "Expected 1 function arguments but got 2."
res32 = "Expected 1 function argument but got 2."
res33 {
name = "Override"
}
res34 = "Expected 1 function arguments but got 2."
res34 = "Expected 1 function argument but got 2."
res35 {
name = "Other"
}
res36 = "Expected 1 function arguments but got 2."
res36 = "Expected 1 function argument but got 2."
res37 = "Expected value of type `new#Person`, but got type `Int`. Value: 1"
res38 {
name = "Override"
}
res39 = "Expected 1 function arguments but got 2."
res39 = "Expected 1 function argument but got 2."
res40 = "Expected value of type `new#Person`, but got type `Int`. Value: 1"
@@ -63,20 +63,20 @@ res8 {
res9 {
name = "Other"
}
res9b = "Expected 1 function arguments but got 2."
res9b = "Expected 1 function argument but got 2."
res9c {
name = "Override"
}
res9d = "Expected 1 function arguments but got 2."
res9d = "Expected 1 function argument but got 2."
res10 {
name = "Other"
}
res10b = "Expected 1 function arguments but got 2."
res10b = "Expected 1 function argument but got 2."
res10c = "Expected value of type `newType#Person`, but got type `Int`. Value: 1"
res10d {
name = "Override"
}
res10e = "Expected 1 function arguments but got 2."
res10e = "Expected 1 function argument but got 2."
res10f = "Expected value of type `newType#Person`, but got type `Int`. Value: 1"
res11 {
name = "Pigeon"
@@ -0,0 +1,15 @@
qualified = 1
unqualifiedLexical = 1
unqualifiedThis = 8
`super` = 16
nestedMethodCalls = 26
objectMethod {
call = 0
tooManyParams = "Expected 1 function argument but got 2."
}
intrinsicConstructor = "Cannot tell which parent to amend."
genericMethod = "Cannot tell which parent to amend."
fnApply = "Cannot tell which parent to amend."
tooManyParams = "Expected 1 function argument but got 2."
polyCall1 = 1
polyCall2 = 20