Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <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;
@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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <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;
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,6 +39,7 @@ public InvokeMethodDirectNode(
ExpressionNode[] argumentNodes) {

super(sourceSection);
this.method = method;
this.owner = method.getOwner();
this.receiverNode = receiverNode;
this.argumentNodes = argumentNodes;
Expand All @@ -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);
}
Expand Down
Loading
Loading