From 7f5ae2a712481df36f9fd42dc83814f1635b0644 Mon Sep 17 00:00:00 2001 From: Jen Basch Date: Wed, 2 Sep 2026 16:55:24 -0700 Subject: [PATCH] Add inference for implicitly typed `new` in method arguments (#1768) --- .../org/pkl/core/ast/builder/AstBuilder.java | 24 +++-- .../org/pkl/core/ast/builder/SymbolTable.java | 10 +-- .../GeneratorPredicateMemberNode.java | 5 +- .../AbstractInferParentFromMethodNode.java | 41 +++++++++ .../member/AbstractInferParentNode.java | 69 +++++++++++++++ .../AbstractInvokeLexicalMethodNode.java | 3 +- ...actInvokeLexicalOrQualifiedMethodNode.java | 86 ++++++++++++++++++ .../member/AbstractInvokeMethodNode.java | 85 ++++++------------ .../AbstractInvokeQualifiedMethodNode.java | 3 +- .../InferParentWithinMethodArgumentNode.java | 88 +++++++++++++++++++ .../member/InferParentWithinMethodNode.java | 67 +++++++------- .../InferParentWithinObjectMethodNode.java | 68 +++++++------- .../member/InvokeLexicalClassMethodNode.java | 6 +- .../member/InvokeLexicalObjectMethodNode.java | 13 +-- .../member/InvokeMethodDirectNode.java | 18 ++-- .../member/InvokeMethodVirtualNode.java | 46 ++-------- .../InvokeQualifiedClassMethodNode.java | 6 +- .../InvokeQualifiedObjectMethodNode.java | 13 +-- .../member/InvokeSuperMethodNode.java | 18 +--- .../ExecuteCustomThisWithRootNode.java | 3 +- .../org/pkl/core/ast/member/ClassMethod.java | 17 +++- .../org/pkl/core/ast/member/FunctionNode.java | 7 +- .../java/org/pkl/core/ast/member/Method.java | 35 ++++++++ .../pkl/core/ast/member/ObjectMethodNode.java | 60 +++++++++---- .../pkl/core/ast/member/PropertyTypeNode.java | 18 ++-- .../core/ast/type/GetParentForTypeNode.java | 67 ++++++-------- .../pkl/core/ast/type/IdentityMixinNode.java | 2 +- .../java/org/pkl/core/ast/type/TypeNode.java | 21 ++--- .../java/org/pkl/core/runtime/VmUtils.java | 19 +++- .../org/pkl/core/errorMessages.properties | 2 +- .../input/methods/inferParameterType.pkl | 50 +++++++++++ .../LanguageSnippetTests/output/basic/new.pcf | 8 +- .../output/basic/newType.pcf | 8 +- .../output/methods/inferParameterType.pcf | 15 ++++ 34 files changed, 678 insertions(+), 323 deletions(-) create mode 100644 pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentFromMethodNode.java create mode 100644 pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java create mode 100644 pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalOrQualifiedMethodNode.java create mode 100644 pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodArgumentNode.java create mode 100644 pkl-core/src/main/java/org/pkl/core/ast/member/Method.java create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/methods/inferParameterType.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/methods/inferParameterType.pcf 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 7e8c4b5c0..4e4f55671 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,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 { 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 { 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 { .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") diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java index 2f082ad5e..507b2593d 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java @@ -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). * *

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( diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java index 5d390934f..92315806e 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java @@ -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); } } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentFromMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentFromMethodNode.java new file mode 100644 index 000000000..7ce6c6695 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentFromMethodNode.java @@ -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()); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java new file mode 100644 index 000000000..bf16fd239 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java @@ -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(); + } +} 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 c27306e88..b5bf90f72 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.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; 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..ea399bd85 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInvokeLexicalOrQualifiedMethodNode.java @@ -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. + * + *

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; + } +} 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..2fb6093f9 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,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. - * - *

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; } } 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.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; 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..8b3469106 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodArgumentNode.java @@ -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()); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodNode.java index c886236e5..ddf4a2231 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinMethodNode.java @@ -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()); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinObjectMethodNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinObjectMethodNode.java index f9a241a8c..7f0151127 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinObjectMethodNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinObjectMethodNode.java @@ -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()); } } 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 @@ 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; } } 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..b1b9a976d 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,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; } } 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..181c1bb39 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 @@ -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); } } 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..d11067813 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 @@ -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. 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 @@ 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; } } 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..1b9f4007b 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,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; } } 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..0bd1794dd 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 @@ -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); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ExecuteCustomThisWithRootNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ExecuteCustomThisWithRootNode.java index b533de937..0c187ff4c 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ExecuteCustomThisWithRootNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ExecuteCustomThisWithRootNode.java @@ -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), 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..0f51712e9 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 @@ -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 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); + } } 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..2aa7e19b0 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,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(); } 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..4ca4d0bf6 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/Method.java @@ -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(); +} 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..34d91f9ca 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 @@ -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; } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/PropertyTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/PropertyTypeNode.java index 229df4f7e..74a1d585e 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/PropertyTypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/PropertyTypeNode.java @@ -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; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/GetParentForTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/GetParentForTypeNode.java index 59e9567a5..116eef648 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/GetParentForTypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/GetParentForTypeNode.java @@ -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 `` to the type's default value in `new { ... }`. */ -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); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/IdentityMixinNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/IdentityMixinNode.java index fc91c26e8..b171ec3aa 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/IdentityMixinNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/IdentityMixinNode.java @@ -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(); } 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 715f08f01..7c9859fc9 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 @@ -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); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java index 9dcf29610..ce3940590 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java @@ -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; } diff --git a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties index 061387f80..9a3834801 100644 --- a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties +++ b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties @@ -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. 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..f959f2aaf --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/methods/inferParameterType.pkl @@ -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 {}) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/new.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/new.pcf index d0e102e51..a33c206df 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/new.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/new.pcf @@ -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" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/newType.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/newType.pcf index 42b6e0802..6c0c8ed49 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/newType.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/newType.pcf @@ -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" 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..796f04467 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/methods/inferParameterType.pcf @@ -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