diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java index 22836aaa9..d769acbef 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java @@ -109,6 +109,7 @@ 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.InferParentWithinMethodArgumentNode; import org.pkl.core.ast.expression.member.InferParentWithinMethodNode; import org.pkl.core.ast.expression.member.InferParentWithinObjectMethodNode; import org.pkl.core.ast.expression.member.InferParentWithinPropertyNodeGen; @@ -998,6 +999,17 @@ private ExpressionNode doVisitNewExprWithInferredParent(NewExpr expr) { .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 constructorts: 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 = + new InferParentWithinMethodArgumentNode(sourceSection, language, argIndex); } else { throw exceptionBuilder() .evalError("cannotInferParent") diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalMethodNode.java index bb019be8e..195bd77c6 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalMethodNode.java @@ -21,7 +21,8 @@ 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; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalOrQualifiedMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalOrQualifiedMethodNode.java new file mode 100644 index 000000000..952dd8e4d --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalOrQualifiedMethodNode.java @@ -0,0 +1,96 @@ +/* + * 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.nodes.ExplodeLoop; +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. + * + *

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; + @Children private ExpressionNode[] argumentNodes; + @Child private @Nullable DirectCallNode callNode; + @CompilationFinal protected boolean isConstChecked; + + protected AbstractInvokeLexicalOrQualifiedMethodNode( + SourceSection sourceSection, + Identifier methodName, + ExpressionNode[] argumentNodes, + boolean needsConst) { + super(sourceSection); + this.methodName = methodName; + this.argumentNodes = argumentNodes; + this.needsConst = needsConst; + this.isConstChecked = false; + } + + @ExplodeLoop + protected final Object invoke(VirtualFrame frame, VmObjectLike owner, Object receiver) { + checkConst(owner); + var method = getMethod(owner); + var args = new Object[2 + argumentNodes.length]; + args[0] = receiver; + args[1] = owner; + frame.setAuxiliarySlot(getMethodSlot(frame), method); + for (var i = 0; i < argumentNodes.length; i++) { + args[2 + i] = argumentNodes[i].executeGeneric(frame); + } + 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; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeMethodNode.java index a30432eda..4243b4342 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeMethodNode.java @@ -15,79 +15,31 @@ */ 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.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; -/** - * A non-virtual (statically dispatched) method call. - * - *

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; + public static final Object METHOD_FRAME_SLOT_ID = + new Object() { + @Override + public String toString() { + return "method"; + } + }; - protected AbstractInvokeMethodNode( - SourceSection sourceSection, - Identifier methodName, - ExpressionNode[] argumentNodes, - boolean needsConst) { + public AbstractInvokeMethodNode(SourceSection sourceSection) { super(sourceSection); - this.methodName = methodName; - this.argumentNodes = argumentNodes; - this.needsConst = needsConst; - this.isConstChecked = false; - } - - @ExplodeLoop - protected final Object invoke(VirtualFrame frame, VmObjectLike owner, Object receiver) { - checkConst(owner); - 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); - } - return getCallNode(owner).call(args); } - private void checkConst(VmObjectLike owner) { - if (!needsConst || isConstChecked) { - return; - } + protected int getMethodSlot(VirtualFrame frame) { + // can't store the slot id as this node may be called from different root nodes + // (see constraints14 snippet) + var customThisSlot = frame.getFrameDescriptor().getAuxiliarySlots().get(METHOD_FRAME_SLOT_ID); + if (customThisSlot != null) return customThisSlot; 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 frame.getFrameDescriptor().findOrAddAuxiliarySlot(METHOD_FRAME_SLOT_ID); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeQualifiedMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeQualifiedMethodNode.java index 4fb3694e4..e86001b43 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeQualifiedMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeQualifiedMethodNode.java @@ -21,7 +21,8 @@ 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; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodArgumentNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodArgumentNode.java new file mode 100644 index 000000000..f87ca08d1 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodArgumentNode.java @@ -0,0 +1,75 @@ +/* + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.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.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.VmLanguage; + +public final class InferParentWithinMethodArgumentNode extends ExpressionNode { + private final VmLanguage language; + private final int argIndex; + @CompilationFinal private @Nullable Object inferredParent; + + public InferParentWithinMethodArgumentNode( + SourceSection sourceSection, VmLanguage language, int argIndex) { + super(sourceSection); + this.language = language; + this.argIndex = argIndex; + } + + @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(); + + var methodSlot = + frame + .getFrameDescriptor() + .getAuxiliarySlots() + .get(AbstractInvokeLexicalOrQualifiedMethodNode.METHOD_FRAME_SLOT_ID); + if (methodSlot == null) { + // used in intrinsic constructor e.g. pkl.base#List() + throw exceptionBuilder().evalError("cannotInferParent").build(); + } + + var method = (Method) frame.getAuxiliarySlot(methodSlot); + if (method == null) { + // used in FunctionN.apply() + throw exceptionBuilder().evalError("cannotInferParent").build(); + } + + var parentTypeNode = method.getParameterTypeNode(argIndex); + var parameterTypeDefaultValue = + parentTypeNode.createDefaultValue( + frame, language, method.getHeaderSection(), method.getQualifiedName()); + if (parameterTypeDefaultValue != null) { + inferredParent = parameterTypeDefaultValue; + return inferredParent; + } + + throw exceptionBuilder().evalError("cannotInferParent").build(); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalClassMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalClassMethodNode.java index 32e0480c6..3e331bd1d 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalClassMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalClassMethodNode.java @@ -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 @@ protected void doCheckConst(VmObjectLike owner) { } @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; } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalObjectMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalObjectMethodNode.java index 5040d8c0b..b80518e98 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalObjectMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeLexicalObjectMethodNode.java @@ -15,10 +15,10 @@ */ 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.runtime.Identifier; import org.pkl.core.runtime.VmObjectLike; @@ -43,9 +43,9 @@ protected void doCheckConst(VmObjectLike owner) { } @Override - protected CallTarget getCallTarget(VmObjectLike owner) { + protected Method getMethod(VmObjectLike owner) { var method = owner.getMember(methodName); assert method != null && method.isLocal(); - return (CallTarget) method.getCallTarget().call(owner, owner); + return method; } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodDirectNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodDirectNode.java index c11d33b29..89561ede3 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodDirectNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodDirectNode.java @@ -24,7 +24,8 @@ 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; @@ -38,6 +39,7 @@ public InvokeMethodDirectNode( ExpressionNode[] argumentNodes) { super(sourceSection); + this.method = method; this.owner = method.getOwner(); this.receiverNode = receiverNode; this.argumentNodes = argumentNodes; @@ -51,6 +53,7 @@ public Object executeGeneric(VirtualFrame frame) { var args = new Object[2 + argumentNodes.length]; args[0] = receiverNode.executeGeneric(frame); args[1] = owner; + frame.setAuxiliarySlot(getMethodSlot(frame), method); for (var i = 0; i < argumentNodes.length; i++) { args[2 + i] = argumentNodes[i].executeGeneric(frame); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java index 9b7de94c9..333de9b95 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java @@ -43,7 +43,7 @@ @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; @@ -91,6 +91,7 @@ protected Object evalFunctionCached( var args = new Object[2 + argumentNodes.length]; args[0] = receiver.getThisValue(); args[1] = receiver; + frame.setAuxiliarySlot(getMethodSlot(frame), null); for (var i = 0; i < argumentNodes.length; i++) { args[2 + i] = argumentNodes[i].executeGeneric(frame); } @@ -110,6 +111,7 @@ protected Object evalFunction( var args = new Object[2 + argumentNodes.length]; args[0] = receiver.getThisValue(); args[1] = receiver; + frame.setAuxiliarySlot(getMethodSlot(frame), null); for (var i = 0; i < argumentNodes.length; i++) { args[2 + i] = argumentNodes[i].executeGeneric(frame); } @@ -130,6 +132,7 @@ protected Object evalCached( var args = new Object[2 + argumentNodes.length]; args[0] = receiver; args[1] = method.getOwner(); + frame.setAuxiliarySlot(getMethodSlot(frame), method); for (var i = 0; i < argumentNodes.length; i++) { args[2 + i] = argumentNodes[i].executeGeneric(frame); } @@ -149,6 +152,7 @@ protected Object eval( var args = new Object[2 + argumentNodes.length]; args[0] = receiver; args[1] = method.getOwner(); + frame.setAuxiliarySlot(getMethodSlot(frame), method); for (var i = 0; i < argumentNodes.length; i++) { args[2 + i] = argumentNodes[i].executeGeneric(frame); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedClassMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedClassMethodNode.java index e739072e7..4cee5e410 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedClassMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedClassMethodNode.java @@ -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 @@ protected void doCheckConst(VmObjectLike owner) { } @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; } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedObjectMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedObjectMethodNode.java index e31849907..a2e6ced81 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedObjectMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeQualifiedObjectMethodNode.java @@ -15,10 +15,10 @@ */ 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.runtime.Identifier; import org.pkl.core.runtime.VmObjectLike; @@ -43,9 +43,9 @@ protected void doCheckConst(VmObjectLike owner) { } @Override - protected CallTarget getCallTarget(VmObjectLike owner) { + protected Method getMethod(VmObjectLike owner) { var method = owner.getMember(methodName); assert method != null && method.isLocal(); - return (CallTarget) method.getCallTarget().call(owner, owner); + return method; } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java index fffc4ed58..ce07caea1 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeSuperMethodNode.java @@ -22,13 +22,15 @@ 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.NonNull; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.ClassMethod; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmFunction; +import org.pkl.core.runtime.VmTyped; 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; @@ -57,7 +59,8 @@ protected Object eval( var args = new Object[2 + argumentNodes.length]; args[0] = VmUtils.getReceiverOrNull(frame); - args[1] = supermethod.getOwner(); + args[1] = getOwner(supermethod); + frame.setAuxiliarySlot(getMethodSlot(frame), supermethod); for (int i = 0; i < argumentNodes.length; i++) { args[2 + i] = argumentNodes[i].executeGeneric(frame); } @@ -65,6 +68,10 @@ protected Object eval( return callNode.call(args); } + private static @NonNull VmTyped getOwner(ClassMethod supermethod) { + return supermethod.getOwner(); + } + protected ClassMethod findSupermethod(VirtualFrame frame) { var owner = VmUtils.getOwner(frame); while (owner instanceof VmFunction) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java index fb236f4e6..1559f5e23 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassMethod.java @@ -28,7 +28,7 @@ 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 typeParameters; // null = not deprecated, "" = no/empty message in the @Deprecated body @@ -91,6 +91,11 @@ public CallTarget getCallTarget(SourceSection callSite) { return functionNode.getCallTarget(); } + @Override + public CallTarget getCallTarget(SourceSection callSite, VmObjectLike owner) { + return getCallTarget(callSite); + } + public int getParameterCount() { return functionNode.getParameterCount(); } @@ -131,4 +136,8 @@ public VmTyped getReturnTypeMirror() { public PClass.Method export(PClass owner) { return functionNode.export(owner, docComment, annotations, modifiers, typeParameters); } + + public TypeNode getParameterTypeNode(int idx) { + return functionNode.getParameterTypeNodes()[idx]; + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java index 49ab5394a..1264b3252 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java @@ -81,6 +81,10 @@ public int getParameterCount() { return paramCount; } + public TypeNode[] getParameterTypeNodes() { + return parameterTypeNodes; + } + public @Nullable TypeNode getReturnTypeNode() { return returnTypeNode; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/Method.java b/pkl-core/src/main/java/org/pkl/core/ast/member/Method.java new file mode 100644 index 000000000..17103ae48 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/Method.java @@ -0,0 +1,31 @@ +/* + * 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.source.SourceSection; +import org.pkl.core.ast.type.TypeNode; +import org.pkl.core.runtime.VmObjectLike; + +public interface Method { + CallTarget getCallTarget(SourceSection callSite, VmObjectLike owner); + + TypeNode getParameterTypeNode(int idx); + + SourceSection getHeaderSection(); + + String getQualifiedName(); +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMember.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMember.java index c645bef43..ddde4c922 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMember.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMember.java @@ -15,6 +15,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.RootCallTarget; import com.oracle.truffle.api.frame.VirtualFrame; @@ -23,11 +24,13 @@ import org.pkl.core.ast.ConstantNode; import org.pkl.core.ast.MemberNode; import org.pkl.core.ast.VmModifier; +import org.pkl.core.ast.type.TypeNode; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmDynamic; +import org.pkl.core.runtime.VmObjectLike; import org.pkl.core.runtime.VmUtils; -public final class ObjectMember extends Member { +public final class ObjectMember extends Member implements Method { @CompilationFinal private @Nullable Object constantValue; @CompilationFinal private @Nullable MemberNode memberNode; @@ -103,6 +106,17 @@ assert getMemberNode() != null return callTarget; } + public CallTarget getCallTarget(SourceSection callSite, VmObjectLike owner) { + assert memberNode instanceof ObjectMethodNode; + return (CallTarget) getCallTarget().call(owner, owner); + } + + @Override + public TypeNode getParameterTypeNode(int idx) { + assert memberNode instanceof ObjectMethodNode; + return ((ObjectMethodNode) memberNode).getParameterTypeNode(idx); + } + public boolean isUndefined(VirtualFrame frame) { return getMemberNode() != null && getMemberNode().isUndefined(frame); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMethodNode.java index e45bb4b99..c36a18adf 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMethodNode.java @@ -57,6 +57,11 @@ public ObjectMethodNode( return functionNode.getReturnTypeNode(); } + public TypeNode getParameterTypeNode(int idx) { + assert functionNode != null; + return functionNode.getParameterTypeNodes()[idx]; + } + @Override protected CallTarget executeImpl(VirtualFrame frame) { if (functionNode == null) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java index 933188582..0c803a62d 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java @@ -2854,25 +2854,22 @@ public TypeNode initWriteSlotNode(int slot) { return this; } + private int getCustomThisSlot(VirtualFrame frame) { + var customThisSlot = + frame.getFrameDescriptor().getAuxiliarySlots().get(CustomThisScope.FRAME_SLOT_ID); + if (customThisSlot != null) return customThisSlot; + CompilerDirectives.transferToInterpreterAndInvalidate(); + return frame.getFrameDescriptor().findOrAddAuxiliarySlot(CustomThisScope.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), value); try { for (var node : constraintNodes) { node.execute(frame); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/methods/inferParameterType.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/inferParameterType.pkl new file mode 100644 index 000000000..b020f3e3f --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/inferParameterType.pkl @@ -0,0 +1,30 @@ +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 + +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 }) + +intrinsicConstructor = test.catch(() -> List(new {})) +genericMethod = test.catch(() -> Pair(new {}, new {})) +fnApply = test.catch(() -> quux.apply(new {})) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/methods/inferParameterType.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/inferParameterType.pcf new file mode 100644 index 000000000..a4de96367 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/inferParameterType.pcf @@ -0,0 +1,7 @@ +qualified = 1 +unqualifiedLexical = 1 +unqualifiedThis = 8 +`super` = 16 +intrinsicConstructor = "Cannot tell which parent to amend." +genericMethod = "Cannot tell which parent to amend." +fnApply = "Cannot tell which parent to amend."