Improve evaluation of typealiases (#1796)

This simplifies the resolution of names in typealias constraints.
This removes the existing logic around swapping out the frame's
owner/receiver, and instead favors resolving the variable at parse time.

* During variable resolution, create read variable nodes that read off of
  a receiver node
* Introduce `GetTypeAliasModuleNode` that provides the typealias's
  enclosing module
* Inject the enclosing module during typealias instantiation
This commit is contained in:
Daniel Chao
2026-07-30 17:22:45 +00:00
committed by GitHub
parent 2ca2aaf9ec
commit a55a3d7c33
39 changed files with 634 additions and 139 deletions
@@ -112,14 +112,17 @@ 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.InferParentWithinPropertyNodeGen;
import org.pkl.core.ast.expression.member.InvokeClassMethodNode;
import org.pkl.core.ast.expression.member.InvokeLexicalClassMethodNode;
import org.pkl.core.ast.expression.member.InvokeLexicalObjectMethodNode;
import org.pkl.core.ast.expression.member.InvokeMethodDirectNode;
import org.pkl.core.ast.expression.member.InvokeMethodVirtualNodeGen;
import org.pkl.core.ast.expression.member.InvokeObjectMethodNode;
import org.pkl.core.ast.expression.member.InvokeQualifiedClassMethodNode;
import org.pkl.core.ast.expression.member.InvokeQualifiedObjectMethodNode;
import org.pkl.core.ast.expression.member.InvokeSuperMethodNodeGen;
import org.pkl.core.ast.expression.member.ReadAmbiguousLocalityPropertyNode;
import org.pkl.core.ast.expression.member.ReadLocalPropertyNode;
import org.pkl.core.ast.expression.member.ReadLexicalLocalPropertyNode;
import org.pkl.core.ast.expression.member.ReadPropertyNodeGen;
import org.pkl.core.ast.expression.member.ReadQualifiedLocalPropertyNode;
import org.pkl.core.ast.expression.member.ReadSuperEntryNode;
import org.pkl.core.ast.expression.member.ReadSuperPropertyNode;
import org.pkl.core.ast.expression.primary.ExecuteCustomThisWithRootNode;
@@ -128,6 +131,7 @@ import org.pkl.core.ast.expression.primary.GetMemberKeyNode;
import org.pkl.core.ast.expression.primary.GetModuleNode;
import org.pkl.core.ast.expression.primary.GetOwnerNode;
import org.pkl.core.ast.expression.primary.GetReceiverNode;
import org.pkl.core.ast.expression.primary.GetTypeAliasModuleNode;
import org.pkl.core.ast.expression.primary.OuterNode;
import org.pkl.core.ast.expression.primary.ThisNode;
import org.pkl.core.ast.expression.ternary.IfElseNode;
@@ -526,7 +530,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
}
@Override
public GetModuleNode visitModuleExpr(ModuleExpr expr) {
public ExpressionNode visitModuleExpr(ModuleExpr expr) {
var currentScope = symbolTable.getCurrentScope();
// cannot use unqualified `module` in a const context
if (currentScope.getConstLevel().isConst() && !(expr.parent() instanceof QualifiedAccessExpr)) {
@@ -549,7 +553,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
.withSourceSection(createSourceSection(expr))
.build();
}
return new GetModuleNode(createSourceSection(expr));
return symbolTable.isInTypeAliasScope
? new GetTypeAliasModuleNode(createSourceSection(expr))
: new GetModuleNode(createSourceSection(expr));
}
@Override
@@ -687,12 +693,30 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
case MODULE -> p.isModuleScope();
case ALL -> p.levelsUp() > constDepth;
};
// Assumption: typealiases can only be declared on the module.
// If we ever allow typealiases in classes, this code needs to change.
if (symbolTable.isInTypeAliasScope && p.isModuleScope()) {
var getModuleNode = new GetTypeAliasModuleNode(sourceSection);
if (p.isLocal()) {
return new ReadQualifiedLocalPropertyNode(
sourceSection,
org.pkl.core.runtime.Identifier.localProperty(name),
needsConst,
getModuleNode);
}
return ReadPropertyNodeGen.create(
sourceSection,
org.pkl.core.runtime.Identifier.get(name),
MemberLookupMode.IMPLICIT_LEXICAL,
needsConst,
getModuleNode);
}
if (p.isAmbiguousLocality()) {
return new ReadAmbiguousLocalityPropertyNode(
sourceSection, org.pkl.core.runtime.Identifier.get(name), p.levelsUp(), needsConst);
}
if (p.isLocal()) {
return new ReadLocalPropertyNode(
return new ReadLexicalLocalPropertyNode(
sourceSection,
org.pkl.core.runtime.Identifier.localProperty(name),
p.levelsUp(),
@@ -769,11 +793,34 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
case MODULE -> method.isModuleScope();
case ALL -> method.levelsUp() > constDepth;
};
// Assumption: typealiases can only be declared on the module.
// If we ever allow typealiases in classes, this code needs to change.
if (symbolTable.isInTypeAliasScope && method.isModuleScope()) {
var getModuleNode = new GetTypeAliasModuleNode(sourceSection);
if (method.isObjectMethod()) {
return new InvokeQualifiedObjectMethodNode(
sourceSection, identifier, args, needsConst, getModuleNode);
}
if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) {
return new InvokeQualifiedClassMethodNode(
sourceSection, identifier, args, needsConst, getModuleNode);
}
return InvokeMethodVirtualNodeGen.create(
sourceSection,
identifier,
args,
MemberLookupMode.IMPLICIT_LEXICAL,
needsConst,
getModuleNode,
GetClassNodeGen.create(null));
}
if (method.isObjectMethod()) {
return new InvokeObjectMethodNode(sourceSection, identifier, levelsUp, args, needsConst);
return new InvokeLexicalObjectMethodNode(
sourceSection, identifier, levelsUp, args, needsConst);
}
if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) {
return new InvokeClassMethodNode(sourceSection, identifier, levelsUp, args, needsConst);
return new InvokeLexicalClassMethodNode(
sourceSection, identifier, levelsUp, args, needsConst);
}
return InvokeMethodVirtualNodeGen.create(
sourceSection,
@@ -47,6 +47,8 @@ import org.pkl.parser.Lexer;
public final class SymbolTable {
private Scope currentScope;
// consider having each scope keep track of this individually rather than set on SymbolTable.
public boolean isInTypeAliasScope;
public SymbolTable(ModuleInfo moduleInfo, boolean isBaseModule) {
currentScope = new ModuleScope(moduleInfo, isBaseModule);
@@ -85,14 +87,19 @@ public final class SymbolTable {
Identifier name,
List<TypeParameter> typeParameters,
Function<TypeAliasScope, ObjectMember> nodeFactory) {
return doEnter(
new TypeAliasScope(
currentScope,
name,
toQualifiedName(name),
new FrameDescriptorBuilder(),
typeParameters),
nodeFactory);
try {
this.isInTypeAliasScope = true;
return doEnter(
new TypeAliasScope(
currentScope,
name,
toQualifiedName(name),
new FrameDescriptorBuilder(),
typeParameters),
nodeFactory);
} finally {
this.isInTypeAliasScope = false;
}
}
public <T> T enterMethod(
@@ -0,0 +1,45 @@
/*
* 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.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
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
permits InvokeLexicalClassMethodNode, InvokeLexicalObjectMethodNode {
private final int levelsUp;
public AbstractInvokeLexicalMethodNode(
SourceSection sourceSection,
Identifier methodName,
int levelsUp,
ExpressionNode[] argumentNodes,
boolean needsConst) {
super(sourceSection, methodName, argumentNodes, needsConst);
this.levelsUp = levelsUp;
}
@Override
public final Object executeGeneric(VirtualFrame frame) {
var capturedFrame = VmUtils.getFrame(frame, levelsUp);
var owner = VmUtils.getOwner(capturedFrame);
var receiver = VmUtils.getReceiver(capturedFrame);
return invoke(frame, owner, receiver);
}
}
@@ -26,40 +26,40 @@ 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.runtime.VmUtils;
public abstract sealed class AbstractInvokeMethodLexicalNode extends ExpressionNode
permits InvokeObjectMethodNode, InvokeClassMethodNode {
/**
* 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 {
protected final Identifier methodName;
protected final int levelsUp;
private final boolean needsConst;
@Children private ExpressionNode[] argumentNodes;
@Child private @Nullable DirectCallNode callNode;
@CompilationFinal protected boolean isConstChecked;
protected AbstractInvokeMethodLexicalNode(
protected AbstractInvokeMethodNode(
SourceSection sourceSection,
Identifier methodName,
int levelsUp,
ExpressionNode[] argumentNodes,
boolean needsConst) {
super(sourceSection);
this.methodName = methodName;
this.levelsUp = levelsUp;
this.argumentNodes = argumentNodes;
this.needsConst = needsConst;
this.isConstChecked = false;
}
@Override
@ExplodeLoop
public final Object executeGeneric(VirtualFrame frame) {
var args = new Object[2 + argumentNodes.length];
var capturedFrame = VmUtils.getFrame(frame, levelsUp);
var owner = VmUtils.getOwner(capturedFrame);
var receiver = VmUtils.getReceiver(capturedFrame);
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++) {
@@ -81,10 +81,6 @@ public abstract sealed class AbstractInvokeMethodLexicalNode extends ExpressionN
protected abstract void doCheckConst(VmObjectLike owner);
protected final VmObjectLike getOwner(VirtualFrame frame) {
return VmUtils.getOwner(frame, levelsUp);
}
protected DirectCallNode getCallNode(VmObjectLike owner) {
if (callNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
@@ -0,0 +1,43 @@
/*
* 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.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
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
permits InvokeQualifiedClassMethodNode, InvokeQualifiedObjectMethodNode {
@Child private ExpressionNode getReceiverNode;
protected AbstractInvokeQualifiedMethodNode(
SourceSection sourceSection,
Identifier methodName,
ExpressionNode[] argumentNodes,
boolean needsConst,
ExpressionNode getReceiverNode) {
super(sourceSection, methodName, argumentNodes, needsConst);
this.getReceiverNode = getReceiverNode;
}
@Override
public final Object executeGeneric(VirtualFrame frame) {
var receiver = (VmTyped) getReceiverNode.executeGeneric(frame);
return invoke(frame, receiver, receiver);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
* 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.
@@ -15,69 +15,38 @@
*/
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.CompilerAsserts;
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.PklBugException;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
import org.pkl.core.runtime.VmUtils;
/** Reads a local non-constant property that is known to exist in the lexical scope of this node. */
public final class ReadLocalPropertyNode extends ExpressionNode {
public abstract class AbstractReadLocalPropertyNode extends ExpressionNode {
private final Identifier name;
private final int levelsUp;
private final boolean needsConst;
@Child private @Nullable DirectCallNode callNode;
@CompilationFinal @Nullable private ObjectMember property;
public ReadLocalPropertyNode(
SourceSection sourceSection, Identifier name, int levelsUp, boolean needsConst) {
public AbstractReadLocalPropertyNode(
SourceSection sourceSection, Identifier name, boolean needsConst) {
super(sourceSection);
CompilerAsserts.neverPartOfCompilation();
this.name = name;
this.levelsUp = levelsUp;
this.needsConst = needsConst;
}
@Override
@ExplodeLoop
public Object executeGeneric(VirtualFrame frame) {
var owner = VmUtils.getOwner(frame, levelsUp);
var property = getProperty(owner);
var constantValue = property.getConstantValue();
if (constantValue != null) {
return constantValue;
}
var receiver = (VmObjectLike) VmUtils.getReceiver(frame, levelsUp);
var result = receiver.getCachedValue(property);
if (result == null) {
result = getCallNode(property).call(receiver, owner, property.getName());
receiver.setCachedValue(property, result);
}
return result;
}
private ObjectMember getProperty(VmObjectLike owner) {
protected ObjectMember getProperty(VmObjectLike owner) {
if (property == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
property = owner.getMember(name);
if (property == null) {
// should never happen
CompilerDirectives.transferToInterpreter();
throw new PklBugException("Couldn't find local variable `" + name + "`.");
throw exceptionBuilder().bug("Couldn't find local variable `" + name + "`.").build();
}
if (needsConst && !property.isConst()) {
throw exceptionBuilder().evalError("propertyMustBeConst", name.toString()).build();
@@ -86,7 +55,7 @@ public final class ReadLocalPropertyNode extends ExpressionNode {
return property;
}
public DirectCallNode getCallNode(ObjectMember property) {
protected DirectCallNode getCallNode(ObjectMember property) {
if (callNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
callNode = DirectCallNode.create(property.getCallTarget());
@@ -24,11 +24,9 @@ import org.pkl.core.runtime.VmObjectLike;
/**
* A non-virtual call of closed methods (methods whose enclosing class/module is not open nor
* abstract, and is lexically scoped).
*
* <p>For local methods, use {@link InvokeObjectMethodNode}.
*/
public final class InvokeClassMethodNode extends AbstractInvokeMethodLexicalNode {
public InvokeClassMethodNode(
public final class InvokeLexicalClassMethodNode extends AbstractInvokeLexicalMethodNode {
public InvokeLexicalClassMethodNode(
SourceSection sourceSection,
Identifier methodName,
int levelsUp,
@@ -22,9 +22,9 @@ import org.pkl.core.ast.VmModifier;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
/** A non-virtual call of a local method. */
public final class InvokeObjectMethodNode extends AbstractInvokeMethodLexicalNode {
public InvokeObjectMethodNode(
/** A non-virtual call of an object method that is lexically scoped. */
public final class InvokeLexicalObjectMethodNode extends AbstractInvokeLexicalMethodNode {
public InvokeLexicalObjectMethodNode(
SourceSection sourceSection,
Identifier methodName,
int levelsUp,
@@ -33,6 +33,7 @@ public final class InvokeObjectMethodNode extends AbstractInvokeMethodLexicalNod
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst);
}
@Override
protected void doCheckConst(VmObjectLike owner) {
var member = owner.getMember(methodName);
assert member != null;
@@ -0,0 +1,50 @@
/*
* 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.CallTarget;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
/** A non-virtual call of a closed class method, invoked off of an explicit receiver. */
public final class InvokeQualifiedClassMethodNode extends AbstractInvokeQualifiedMethodNode {
public InvokeQualifiedClassMethodNode(
SourceSection sourceSection,
Identifier methodName,
ExpressionNode[] argumentNodes,
boolean needsConst,
ExpressionNode getReceiverNode) {
super(sourceSection, methodName, argumentNodes, needsConst, getReceiverNode);
}
@Override
protected void doCheckConst(VmObjectLike owner) {
var method = owner.getVmClass().getDeclaredMethod(methodName);
assert method != null;
if (!method.isConst()) {
throw exceptionBuilder().evalError("methodMustBeConst", methodName).build();
}
}
@Override
protected CallTarget getCallTarget(VmObjectLike owner) {
var method = owner.getVmClass().getDeclaredMethod(methodName);
assert method != null;
return method.getCallTarget(getSourceSection());
}
}
@@ -0,0 +1,51 @@
/*
* 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.CallTarget;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
/** A non-virtual call of an object method, invoked off of an explicit receiver. */
public final class InvokeQualifiedObjectMethodNode extends AbstractInvokeQualifiedMethodNode {
public InvokeQualifiedObjectMethodNode(
SourceSection sourceSection,
Identifier methodName,
ExpressionNode[] argumentNodes,
boolean needsConst,
ExpressionNode getReceiverNode) {
super(sourceSection, methodName, argumentNodes, needsConst, getReceiverNode);
}
@Override
protected void doCheckConst(VmObjectLike owner) {
var member = owner.getMember(methodName);
assert member != null;
if (!VmModifier.isConst(member.getModifiers())) {
throw exceptionBuilder().evalError("methodMustBeConst", methodName).build();
}
}
@Override
protected CallTarget getCallTarget(VmObjectLike owner) {
var method = owner.getMember(methodName);
assert method != null && method.isLocal();
return (CallTarget) method.getCallTarget().call(owner, owner);
}
}
@@ -67,7 +67,7 @@ public final class ReadAmbiguousLocalityPropertyNode extends ExpressionNode {
CompilerDirectives.transferToInterpreterAndInvalidate();
readLocalPropertyNode =
insert(
new ReadLocalPropertyNode(
new ReadLexicalLocalPropertyNode(
sourceSection, name.toLocalProperty(), levelsUp, needsConst));
}
return readLocalPropertyNode;
@@ -0,0 +1,55 @@
/*
* 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.CompilerAsserts;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmObjectLike;
import org.pkl.core.runtime.VmUtils;
/** Reads a local non-constant property that is known to exist in the lexical scope of this node. */
public final class ReadLexicalLocalPropertyNode extends AbstractReadLocalPropertyNode {
private final int levelsUp;
public ReadLexicalLocalPropertyNode(
SourceSection sourceSection, Identifier name, int levelsUp, boolean needsConst) {
super(sourceSection, name, needsConst);
CompilerAsserts.neverPartOfCompilation();
this.levelsUp = levelsUp;
}
@Override
@ExplodeLoop
public Object executeGeneric(VirtualFrame frame) {
var owner = VmUtils.getOwner(frame, levelsUp);
var property = getProperty(owner);
var constantValue = property.getConstantValue();
if (constantValue != null) {
return constantValue;
}
var receiver = (VmObjectLike) VmUtils.getReceiver(frame, levelsUp);
var result = receiver.getCachedValue(property);
if (result == null) {
result = getCallNode(property).call(receiver, owner, property.getName());
receiver.setCachedValue(property, result);
}
return result;
}
}
@@ -0,0 +1,53 @@
/*
* 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.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmTyped;
/** Reads a local property off of the receiver node. */
public final class ReadQualifiedLocalPropertyNode extends AbstractReadLocalPropertyNode {
@Child private ExpressionNode getReceiverNode;
public ReadQualifiedLocalPropertyNode(
SourceSection sourceSection,
Identifier identifier,
boolean needsConst,
ExpressionNode getReceiverNode) {
super(sourceSection, identifier, needsConst);
this.getReceiverNode = getReceiverNode;
}
@Override
public Object executeGeneric(VirtualFrame frame) {
var receiver = (VmTyped) getReceiverNode.executeGeneric(frame);
var property = getProperty(receiver);
var constantValue = property.getConstantValue();
if (constantValue != null) {
return constantValue;
}
var result = receiver.getCachedValue(property);
if (result == null) {
result = getCallNode(property).call(receiver, receiver, property.getName());
receiver.setCachedValue(property, result);
}
return result;
}
}
@@ -0,0 +1,52 @@
/*
* 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.primary;
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.runtime.VmTyped;
public final class GetTypeAliasModuleNode extends ExpressionNode {
@CompilationFinal private @Nullable VmTyped module;
public GetTypeAliasModuleNode(SourceSection sourceSection) {
super(sourceSection);
}
@Override
public boolean isInstrumentable() {
return false;
}
public void lateInitModule(VmTyped module) {
// must only set this once; the first typealias initialization wins.
// guards against nested typealiases; e.g. `typealias A = module; typealias B = A`
if (this.module != null) return;
CompilerDirectives.transferToInterpreterAndInvalidate();
this.module = module;
}
@Override
public Object executeGeneric(VirtualFrame frame) {
assert module != null;
return module;
}
}
@@ -24,6 +24,7 @@ import java.util.List;
import org.jspecify.annotations.Nullable;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.expression.primary.GetTypeAliasModuleNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.VmTypeAlias;
import org.pkl.core.runtime.VmTyped;
@@ -86,7 +87,15 @@ public final class TypeAliasNode extends ExpressionNode {
frame.materialize());
VmUtils.evaluateAnnotations(frame, annotationNodes, annotations);
cachedTypeAlias.initTypeCheckNode(typeAnnotationNode.execute(frame));
var bodyTypeNode = typeAnnotationNode.execute(frame);
bodyTypeNode.accept(
node -> {
if (node instanceof GetTypeAliasModuleNode getTypeAliasModuleNode) {
getTypeAliasModuleNode.lateInitModule(module);
}
return true;
});
cachedTypeAlias.initTypeCheckNode(bodyTypeNode);
return cachedTypeAlias;
}
@@ -21,7 +21,6 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.frame.VirtualFrame;
@@ -2735,56 +2734,18 @@ public abstract class TypeNode extends PklNode {
return getMirrors(typeArgumentNodes);
}
/**
* A typealias body is effectively inlined into the type node, and not executed in its own
* frame.
*
* <p>Before executing the typealias body, use the owner and receiver of the original frame
* where the typealias was declared, so that we preserve its original scope.
*/
protected Object executeLazily(VirtualFrame frame, Object value) {
var prevOwner = VmUtils.getOwner(frame);
var prevReceiver = VmUtils.getReceiver(frame);
setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame()));
setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame()));
try {
return aliasedTypeNode.executeLazily(frame, value);
} finally {
setOwner(frame, prevOwner);
setReceiver(frame, prevReceiver);
}
return aliasedTypeNode.executeLazily(frame, value);
}
@Override
public Object executeEagerly(VirtualFrame frame, Object value) {
var prevOwner = VmUtils.getOwner(frame);
var prevReceiver = VmUtils.getReceiver(frame);
setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame()));
setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame()));
try {
return aliasedTypeNode.executeEagerly(frame, value);
} finally {
setOwner(frame, prevOwner);
setReceiver(frame, prevReceiver);
}
return aliasedTypeNode.executeEagerly(frame, value);
}
/** See docstring on {@link TypeAliasTypeNode#executeLazily}. */
@Override
public Object executeAndSet(VirtualFrame frame, Object value) {
var prevOwner = VmUtils.getOwner(frame);
var prevReceiver = VmUtils.getReceiver(frame);
setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame()));
setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame()));
try {
return aliasedTypeNode.executeAndSet(frame, value);
} finally {
setOwner(frame, prevOwner);
setReceiver(frame, prevReceiver);
}
return aliasedTypeNode.executeAndSet(frame, value);
}
@TruffleBoundary
@@ -2859,22 +2820,6 @@ public abstract class TypeNode extends PklNode {
protected boolean isParametric() {
return typeArgumentNodes.length > 0;
}
// Note that mutating a frame's receiver and owner argument is very risky
// because any VmObject instantiated within the same root node execution
// holds a reference to (not immutable snapshot of) the frame
// via VmObjectLike.enclosingFrame.
// *Maybe* this works out for TypeAliasTypeNode because an object instantiated
// within a type constraint doesn't escape the constraint expression.
// If mutating receiver and owner can't be avoided, it would be safer
// to have VmObjectLike store them directly instead of storing enclosingFrame.
private static void setReceiver(Frame frame, Object receiver) {
frame.getArguments()[0] = receiver;
}
private static void setOwner(Frame frame, VmObjectLike owner) {
frame.getArguments()[1] = owner;
}
}
public static final class ConstrainedTypeNode extends TypeNode {
@@ -0,0 +1 @@
typealias Id<T> = T
@@ -0,0 +1 @@
name: String = "base"
@@ -0,0 +1,9 @@
amends "typeAliasAmendBase.pkl"
local const function isValid(s: String): Boolean = s.startsWith("Bob")
local typealias ValidString4 = String(isValid(this))
local myProp: ValidString4 = "Bob Marley"
name = myProp
@@ -0,0 +1,3 @@
const function isValid(s: String): Boolean = s.startsWith("Bob")
typealias ValidString2 = String(isValid(this))
@@ -0,0 +1,3 @@
local const function isValid(s: String): Boolean = s.startsWith("Bob")
typealias ValidString3 = String(isValid(this))
@@ -0,0 +1 @@
typealias MyModule = module
@@ -0,0 +1,5 @@
open module typeAliasOpenModule
const function isValid(s: String): Boolean = s.startsWith("Bob")
typealias ValidString = String(isValid(this))
@@ -0,0 +1,3 @@
const name = "Bob"
typealias Inner = String(startsWith(name))
@@ -0,0 +1,5 @@
import "typealias1.pkl"
const name = "Alice"
typealias Outer = typealias1.Inner
@@ -0,0 +1,8 @@
import "../../input-helper/types/idType.pkl"
hidden modules: Listing<idType.Id<module>> = new {
new module {}
new module {}
}
res = modules.length
@@ -0,0 +1,3 @@
import "../../input-helper/types/typeAliasModule.pkl"
res: typeAliasModule = module
@@ -0,0 +1,3 @@
import "../../input-helper/types/typealias2.pkl"
myProp: typealias2.Outer = "Alice Jenkins"
@@ -0,0 +1,5 @@
import "../../input-helper/types/typeAliasOpenModule.pkl"
const function isValid(s: String): Boolean = s.startsWith("Alice")
myProp: typeAliasOpenModule.ValidString = "Alice Jenkins"
@@ -0,0 +1,5 @@
import "../../input-helper/types/typeAliasClosedModule.pkl"
const function isValid(s: String): Boolean = s.startsWith("Alice")
myProp: typeAliasClosedModule.ValidString2 = "Alice Jenkins"
@@ -0,0 +1,5 @@
import "../../input-helper/types/typeAliasLocalMethod.pkl"
local const function isValid(s: String): Boolean = s.startsWith("Alice")
myProp: typeAliasLocalMethod.ValidString3 = "Alice Jenkins"
@@ -0,0 +1,5 @@
import "../../input-helper/types/typeAliasAmendModule.pkl"
local const function isValid(s: String): Boolean = s.startsWith("Alice")
res = typeAliasAmendModule.name
@@ -0,0 +1 @@
res = 2
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `typeAliasModule`, but got type `typeAlias6`.
Value: new ModuleClass { res = ? }
x | res: typeAliasModule = module
^^^^^^^^^^^^^^^
at typeAlias6#res (file:///$snippetsDir/input/types/typeAlias6.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,23 @@
–– Pkl Error ––
Type constraint `startsWith(name)` violated.
Value: "Alice Jenkins"
startsWith(name)
│ │
false "Bob"
x | typealias Inner = String(startsWith(name))
^^^^^^^^^^^^^^^^
at typeAliasConstraint4#myProp (file:///$snippetsDir/input-helper/types/typealias1.pkl)
x | myProp: typealias2.Outer = "Alice Jenkins"
^^^^^^^^^^^^^^^
at typeAliasConstraint4#myProp (file:///$snippetsDir/input/types/typeAliasConstraint4.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,23 @@
–– Pkl Error ––
Type constraint `isValid(this)` violated.
Value: "Alice Jenkins"
isValid(this)
│ │
false "Alice Jenkins"
x | typealias ValidString = String(isValid(this))
^^^^^^^^^^^^^
at typeAliasConstraint5#myProp (file:///$snippetsDir/input-helper/types/typeAliasOpenModule.pkl)
x | myProp: typeAliasOpenModule.ValidString = "Alice Jenkins"
^^^^^^^^^^^^^^^
at typeAliasConstraint5#myProp (file:///$snippetsDir/input/types/typeAliasConstraint5.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,23 @@
–– Pkl Error ––
Type constraint `isValid(this)` violated.
Value: "Alice Jenkins"
isValid(this)
│ │
false "Alice Jenkins"
x | typealias ValidString2 = String(isValid(this))
^^^^^^^^^^^^^
at typeAliasConstraint6#myProp (file:///$snippetsDir/input-helper/types/typeAliasClosedModule.pkl)
x | myProp: typeAliasClosedModule.ValidString2 = "Alice Jenkins"
^^^^^^^^^^^^^^^
at typeAliasConstraint6#myProp (file:///$snippetsDir/input/types/typeAliasConstraint6.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1,23 @@
–– Pkl Error ––
Type constraint `isValid(this)` violated.
Value: "Alice Jenkins"
isValid(this)
│ │
false "Alice Jenkins"
x | typealias ValidString3 = String(isValid(this))
^^^^^^^^^^^^^
at typeAliasConstraint7#myProp (file:///$snippetsDir/input-helper/types/typeAliasLocalMethod.pkl)
x | myProp: typeAliasLocalMethod.ValidString3 = "Alice Jenkins"
^^^^^^^^^^^^^^^
at typeAliasConstraint7#myProp (file:///$snippetsDir/input/types/typeAliasConstraint7.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)
@@ -0,0 +1 @@
res = "Bob Marley"