Switch inferred new {} expressions in method args to using frame slots (#1857)
This changes the logic for inferring `new {}` expressions in method
arguments to using regular frame slots, instead of auxiliary slots.
Overall, this is a performance improvement: no looking up auxiliary slot
numbers, which makes this code much more PE friendly.
* At parse time, get or create a frame slot that holds the method
* Introduce `ArgInfo` record (preserve primitive int) over using `Pair`
* Remove unneeded `isImplicitNewExpr` method; this is not needed because
we can just keep track of the method slot on the frame descriptor
builder.
This commit is contained in:
1 parent
7ed88e250b
commit
d5a7dc021e
40 files changed
+422
-202
No files matched your search
@@ -528,14 +528,21 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
var exprs = type.getExprs();
|
||||
var constraints = new TypeConstraintNode[exprs.size()];
|
||||
for (var i = 0; i < constraints.length; i++) {
|
||||
var currentFrameDescriptorSize = scope.frameDescriptorBuilder.getSize();
|
||||
var initialState = scope.frameDescriptorBuilder.state();
|
||||
var expr = visitExpr(exprs.get(i));
|
||||
var writesFrameSlotVars =
|
||||
scope.frameDescriptorBuilder.getSize() > currentFrameDescriptorSize;
|
||||
// if a constraint expression writes to frame slots (only known case: is a `let` expr),
|
||||
// create a new root node and execute the constraint within this root node
|
||||
// e.g. String(let (x = this) x.length > 5)
|
||||
if (writesFrameSlotVars) {
|
||||
var needsFrameSlots = scope.frameDescriptorBuilder.state() > initialState;
|
||||
// If a constraint expression writes to frame slots, create a new root node and execute
|
||||
// the constraint within this root node.
|
||||
//
|
||||
// This can happen if the constraint contains a let expression, or if it contains a
|
||||
// method call argument with an inferred `new {}`, e.g.
|
||||
//
|
||||
// * String(let (x = this) x.length > 5)
|
||||
// * String(isFoo(new {}))
|
||||
//
|
||||
// We only need to do this if we are in a typealias because they get inlined into their
|
||||
// usage site (they don't influence the frame descriptor of where they are inlined).
|
||||
if (needsFrameSlots && symbolTable.isInTypeAliasScope) {
|
||||
expr = getExprWithinCustomThis(scope, expr);
|
||||
}
|
||||
constraints[i] = TypeConstraintNodeGen.create(expr.getSourceSection(), expr);
|
||||
@@ -894,55 +901,45 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
return new InvokeQualifiedObjectMethodNode(
|
||||
sourceSection,
|
||||
identifier,
|
||||
argInfo.getFirst(),
|
||||
argInfo.arguments,
|
||||
needsConst,
|
||||
getModuleNode,
|
||||
argInfo.getSecond());
|
||||
argInfo.methodSlot);
|
||||
}
|
||||
if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) {
|
||||
return new InvokeQualifiedClassMethodNode(
|
||||
sourceSection,
|
||||
identifier,
|
||||
argInfo.getFirst(),
|
||||
argInfo.arguments,
|
||||
needsConst,
|
||||
getModuleNode,
|
||||
argInfo.getSecond());
|
||||
argInfo.methodSlot);
|
||||
}
|
||||
return InvokeMethodVirtualNodeGen.create(
|
||||
sourceSection,
|
||||
identifier,
|
||||
argInfo.getFirst(),
|
||||
argInfo.arguments,
|
||||
MemberLookupMode.IMPLICIT_LEXICAL,
|
||||
needsConst,
|
||||
argInfo.getSecond(),
|
||||
argInfo.methodSlot,
|
||||
getModuleNode,
|
||||
GetClassNodeGen.create(null));
|
||||
}
|
||||
if (method.isObjectMethod()) {
|
||||
return new InvokeLexicalObjectMethodNode(
|
||||
sourceSection,
|
||||
identifier,
|
||||
levelsUp,
|
||||
argInfo.getFirst(),
|
||||
needsConst,
|
||||
argInfo.getSecond());
|
||||
sourceSection, identifier, levelsUp, argInfo.arguments, needsConst, argInfo.methodSlot);
|
||||
}
|
||||
if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) {
|
||||
return new InvokeLexicalClassMethodNode(
|
||||
sourceSection,
|
||||
identifier,
|
||||
levelsUp,
|
||||
argInfo.getFirst(),
|
||||
needsConst,
|
||||
argInfo.getSecond());
|
||||
sourceSection, identifier, levelsUp, argInfo.arguments, needsConst, argInfo.methodSlot);
|
||||
}
|
||||
return InvokeMethodVirtualNodeGen.create(
|
||||
sourceSection,
|
||||
identifier,
|
||||
argInfo.getFirst(),
|
||||
argInfo.arguments,
|
||||
MemberLookupMode.IMPLICIT_LEXICAL,
|
||||
needsConst,
|
||||
argInfo.getSecond(),
|
||||
argInfo.methodSlot,
|
||||
levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp),
|
||||
GetClassNodeGen.create(null));
|
||||
} else if (resolution instanceof ImplicitBaseMethod) {
|
||||
@@ -969,20 +966,22 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
createSourceSection(expr),
|
||||
method,
|
||||
new ConstantValueNode(baseModule),
|
||||
argInfo.getFirst(),
|
||||
argInfo.getSecond());
|
||||
argInfo.arguments,
|
||||
argInfo.methodSlot);
|
||||
}
|
||||
} else if (resolution instanceof ImplicitThisMethod) {
|
||||
var isCustomThis = scope.isCustomThisScope();
|
||||
var needsConst = constLevel == ConstLevel.ALL && constDepth == -1 && !isCustomThis;
|
||||
var argInfo = visitArgumentList(argList);
|
||||
var arguments = argInfo.arguments;
|
||||
var methodSlot = argInfo.methodSlot;
|
||||
return InvokeMethodVirtualNodeGen.create(
|
||||
sourceSection,
|
||||
org.pkl.core.runtime.Identifier.get(name),
|
||||
argInfo.getFirst(),
|
||||
arguments,
|
||||
MemberLookupMode.IMPLICIT_THIS,
|
||||
needsConst,
|
||||
argInfo.getSecond(),
|
||||
methodSlot,
|
||||
VmUtils.createThisNode(VmUtils.unavailableSourceSection(), isCustomThis),
|
||||
GetClassNodeGen.create(null));
|
||||
} else {
|
||||
@@ -1081,7 +1080,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
var parent = expr.parent();
|
||||
var scope = symbolTable.getCurrentScope();
|
||||
|
||||
// keep in sync with isImplicitNewExpr
|
||||
while (parent instanceof IfExpr ifExpr
|
||||
&& (ifExpr.getThen() == child || ifExpr.getEls() == child)
|
||||
|| parent instanceof TraceExpr
|
||||
@@ -1122,8 +1120,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
// - generic methods: pkl.base#Pair(), etc.
|
||||
// these will throw cannotInferParent at runtime
|
||||
var argIndex = argumentList.getArguments().indexOf(child);
|
||||
var methodSlot = symbolTable.getCurrentScope().frameDescriptorBuilder.getOrAddMethodSlot();
|
||||
inferredParentNode =
|
||||
InferParentWithinMethodArgumentNodeGen.create(sourceSection, language, argIndex);
|
||||
InferParentWithinMethodArgumentNodeGen.create(
|
||||
sourceSection, language, argIndex, methodSlot);
|
||||
} else {
|
||||
throw exceptionBuilder()
|
||||
.evalError("cannotInferParent")
|
||||
@@ -1160,7 +1160,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
|
||||
var argInfo = visitArgumentList(argCtx);
|
||||
return InvokeSuperMethodNodeGen.create(
|
||||
sourceSection, memberName, argInfo.getFirst(), needsConst, argInfo.getSecond());
|
||||
sourceSection, memberName, argInfo.arguments, needsConst, argInfo.methodSlot);
|
||||
}
|
||||
|
||||
// superproperty call
|
||||
@@ -1340,13 +1340,14 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
descriptorBuilder,
|
||||
scope -> {
|
||||
var exprNode = visitExpr(expr.getExpr());
|
||||
var parameterTypeNodes = doVisitParameterTypes(params);
|
||||
var functionNode =
|
||||
new UnresolvedFunctionNode(
|
||||
language,
|
||||
scope.buildFrameDescriptor(),
|
||||
new Lambda(sourceSection, scope.getQualifiedName()),
|
||||
paramCount,
|
||||
doVisitParameterTypes(params),
|
||||
parameterTypeNodes,
|
||||
null,
|
||||
exprNode);
|
||||
|
||||
@@ -1479,18 +1480,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
public GeneratorMemberNode visitMemberPredicate(MemberPredicate ctx) {
|
||||
var keyNode =
|
||||
symbolTable.enterEagerGenerator(
|
||||
(scp) ->
|
||||
symbolTable.enterCustomThisScope(
|
||||
scope -> {
|
||||
var currentFrameDescriptorSize = scope.frameDescriptorBuilder.getSize();
|
||||
var expr = visitExpr(ctx.getPred());
|
||||
var writesFrameSlotVars =
|
||||
scope.frameDescriptorBuilder.getSize() > currentFrameDescriptorSize;
|
||||
if (writesFrameSlotVars) {
|
||||
return getExprWithinCustomThis(scope, expr);
|
||||
}
|
||||
return expr;
|
||||
}));
|
||||
(scp) -> symbolTable.enterCustomThisScope(scope -> visitExpr(ctx.getPred())));
|
||||
var member =
|
||||
doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.getExpr(), ctx.getBodyList());
|
||||
var isFrameStored =
|
||||
@@ -2179,6 +2169,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
.build();
|
||||
}
|
||||
}
|
||||
var parameterTypes = doVisitParameterTypes(paramListCtx);
|
||||
var typeAnnotationNode = visitTypeAnnotation(entry.getTypeAnnotation());
|
||||
|
||||
return new UnresolvedMethodNode(
|
||||
language,
|
||||
@@ -2192,8 +2184,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
scope.getQualifiedName(),
|
||||
paramCount,
|
||||
typeParameters,
|
||||
doVisitParameterTypes(paramListCtx),
|
||||
visitTypeAnnotation(entry.getTypeAnnotation()),
|
||||
parameterTypes,
|
||||
typeAnnotationNode,
|
||||
isMethodReturnTypeChecked,
|
||||
bodyNode);
|
||||
});
|
||||
@@ -2329,32 +2321,21 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<ExpressionNode[], Boolean> visitArgumentList(ArgumentList argumentList) {
|
||||
public ArgInfo visitArgumentList(ArgumentList argumentList) {
|
||||
var args = argumentList.getArguments();
|
||||
var res = new ExpressionNode[args.size()];
|
||||
var argsRequireInference = false;
|
||||
for (int i = 0; i < res.length; i++) {
|
||||
var arguments = new ExpressionNode[args.size()];
|
||||
var frameDescriptorBuilder = symbolTable.getCurrentScope().frameDescriptorBuilder;
|
||||
var initialState = frameDescriptorBuilder.state();
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var expr = args.get(i);
|
||||
res[i] = visitExpr(expr);
|
||||
argsRequireInference = argsRequireInference || isImplicitNewExpr(expr);
|
||||
arguments[i] = visitExpr(expr);
|
||||
}
|
||||
return Pair.of(res, argsRequireInference);
|
||||
}
|
||||
|
||||
private static boolean isImplicitNewExpr(Expr expr) {
|
||||
// keep in sync with doVisitNewExprWithInferredParent
|
||||
if (expr instanceof NewExpr newExpr && newExpr.getType() == null) {
|
||||
return true;
|
||||
} else if (expr instanceof IfExpr ifExpr) {
|
||||
return isImplicitNewExpr(ifExpr.getThen()) || isImplicitNewExpr(ifExpr.getEls());
|
||||
} else if (expr instanceof TraceExpr traceExpr) {
|
||||
return isImplicitNewExpr(traceExpr.getExpr());
|
||||
} else if (expr instanceof ParenthesizedExpr parenthesizedExpr) {
|
||||
return isImplicitNewExpr(parenthesizedExpr.getExpr());
|
||||
} else if (expr instanceof LetExpr letExpr) {
|
||||
return isImplicitNewExpr(letExpr.getExpr());
|
||||
}
|
||||
return false;
|
||||
var argsRequiresInference = frameDescriptorBuilder.state() > initialState;
|
||||
var methodSlot =
|
||||
argsRequiresInference
|
||||
? symbolTable.getCurrentScope().frameDescriptorBuilder.getMethodSlot()
|
||||
: -1;
|
||||
return new ArgInfo(arguments, methodSlot);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2439,9 +2420,12 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
parametersDescriptorAndBindings == null
|
||||
? new FrameSlotVariable[0]
|
||||
: parametersDescriptorAndBindings.second;
|
||||
var parametersFrameDescriptorBuilder =
|
||||
parametersDescriptorAndBindings == null ? null : parametersDescriptorAndBindings.first;
|
||||
|
||||
return symbolTable.enterObjectScope(
|
||||
bindings,
|
||||
parametersFrameDescriptorBuilder,
|
||||
(scope) -> {
|
||||
addObjectNamesToScope(scope, body);
|
||||
var objectMembers = body.getMembers();
|
||||
@@ -2451,11 +2435,13 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
}
|
||||
var sourceSection = createSourceSection(body.parent());
|
||||
|
||||
// must visit parameter types before building the parameters frame descriptor, since
|
||||
// constraints (e.g. containing `let` expressions) may add further slots to it
|
||||
var parameterTypes = doVisitParameterTypes(body);
|
||||
var parametersDescriptor =
|
||||
parametersDescriptorAndBindings == null
|
||||
? null
|
||||
: parametersDescriptorAndBindings.first.build();
|
||||
var parameterTypes = doVisitParameterTypes(body);
|
||||
|
||||
var members = EconomicMaps.<Object, ObjectMember>create();
|
||||
var elements = new ArrayList<ObjectMember>();
|
||||
@@ -2496,7 +2482,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
|| memberCtx instanceof ObjectSpread;
|
||||
// bail out and create GeneratorObjectLiteralNode instead
|
||||
// (but can't we easily reuse members/elements/keyNodes/values?)
|
||||
return doVisitGeneratorObjectBody(body, parentNode);
|
||||
return doVisitGeneratorObjectBody(
|
||||
body, parentNode, parametersDescriptor, parameterTypes);
|
||||
}
|
||||
|
||||
var currentScope = symbolTable.getCurrentScope();
|
||||
@@ -2677,6 +2664,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
.build();
|
||||
}
|
||||
}
|
||||
var typeAnnotation = visitTypeAnnotation(typeAnn);
|
||||
|
||||
ExpressionNode bodyNode;
|
||||
if (body != null && !body.isEmpty()) { // foo { ... }
|
||||
@@ -2713,7 +2701,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
scope.buildFrameDescriptor(),
|
||||
modifiers,
|
||||
bodyNode,
|
||||
visitTypeAnnotation(typeAnn))
|
||||
typeAnnotation)
|
||||
: VmUtils.createObjectProperty(
|
||||
language,
|
||||
sourceSection,
|
||||
@@ -2836,6 +2824,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
scope.getName(),
|
||||
scope.getQualifiedName());
|
||||
var body = visitExpr(expr);
|
||||
var typeNode = visitTypeAnnotation(typeAnnotation);
|
||||
var parameterTypeNodes = doVisitParameterTypes(paramList);
|
||||
var node =
|
||||
new ObjectMethodNode(
|
||||
language,
|
||||
@@ -2843,8 +2833,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
member,
|
||||
body,
|
||||
paramList.getParameters().size(),
|
||||
doVisitParameterTypes(paramList),
|
||||
visitTypeAnnotation(typeAnnotation));
|
||||
parameterTypeNodes,
|
||||
typeNode);
|
||||
|
||||
member.initMemberNode(node);
|
||||
return member;
|
||||
@@ -2852,10 +2842,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
}
|
||||
|
||||
private GeneratorObjectLiteralNode doVisitGeneratorObjectBody(
|
||||
ObjectBody body, ExpressionNode parentNode) {
|
||||
var parametersDescriptorBuilderAndFrameSlotVariables =
|
||||
createFrameDescriptorBuilderAndSlotVariables(body);
|
||||
var parameterTypes = doVisitParameterTypes(body);
|
||||
ObjectBody body,
|
||||
ExpressionNode parentNode,
|
||||
@Nullable FrameDescriptor parametersDescriptor,
|
||||
UnresolvedTypeNode[] parameterTypes) {
|
||||
var memberNodes = doVisitGeneratorMemberNodes(body.getMembers());
|
||||
var currentScope = symbolTable.getCurrentScope();
|
||||
//noinspection ConstantConditions
|
||||
@@ -2864,9 +2854,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
language,
|
||||
currentScope.getQualifiedName(),
|
||||
currentScope.isCustomThisScope(),
|
||||
parametersDescriptorBuilderAndFrameSlotVariables == null
|
||||
? null
|
||||
: parametersDescriptorBuilderAndFrameSlotVariables.first.build(),
|
||||
parametersDescriptor,
|
||||
parameterTypes,
|
||||
memberNodes,
|
||||
parentNode);
|
||||
@@ -2944,10 +2932,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
InvokeMethodVirtualNodeGen.create(
|
||||
sourceSection,
|
||||
functionName,
|
||||
argInfo.getFirst(),
|
||||
argInfo.arguments,
|
||||
MemberLookupMode.EXPLICIT_RECEIVER,
|
||||
needsConst,
|
||||
argInfo.getSecond(),
|
||||
argInfo.methodSlot,
|
||||
PropagateNullReceiverNodeGen.create(unavailableSourceSection(), receiver),
|
||||
GetClassNodeGen.create(null)));
|
||||
}
|
||||
@@ -2956,10 +2944,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
return InvokeMethodVirtualNodeGen.create(
|
||||
sourceSection,
|
||||
functionName,
|
||||
argInfo.getFirst(),
|
||||
argInfo.arguments,
|
||||
MemberLookupMode.EXPLICIT_RECEIVER,
|
||||
needsConst,
|
||||
argInfo.getSecond(),
|
||||
argInfo.methodSlot,
|
||||
receiver,
|
||||
GetClassNodeGen.create(null));
|
||||
}
|
||||
@@ -3340,4 +3328,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public record ArgInfo(ExpressionNode[] arguments, int methodSlot) {}
|
||||
}
|
||||
@@ -211,9 +211,14 @@ public final class SymbolTable {
|
||||
}
|
||||
|
||||
public <T> T enterObjectScope(
|
||||
FrameSlotVariable[] bindings, Function<ObjectScope, T> nodeFactory) {
|
||||
return doEnter(
|
||||
new ObjectScope(currentScope, bindings, currentScope.frameDescriptorBuilder), nodeFactory);
|
||||
FrameSlotVariable[] bindings,
|
||||
@Nullable FrameDescriptorBuilder parametersFrameDescriptorBuilder,
|
||||
Function<ObjectScope, T> nodeFactory) {
|
||||
var frameDescriptorBuilder =
|
||||
parametersFrameDescriptorBuilder != null
|
||||
? parametersFrameDescriptorBuilder
|
||||
: currentScope.frameDescriptorBuilder;
|
||||
return doEnter(new ObjectScope(currentScope, bindings, frameDescriptorBuilder), nodeFactory);
|
||||
}
|
||||
|
||||
private <T, S extends Scope> T doEnter(S scope, Function<S, T> nodeFactory) {
|
||||
@@ -861,6 +866,9 @@ public final class SymbolTable {
|
||||
// A generator scope that is resolved eagerly and one level above
|
||||
public static final class EagerGeneratorScope extends Scope {
|
||||
private static FrameDescriptorBuilder getFrameDescriptorBuilder(Scope parent) {
|
||||
if (parent instanceof ObjectScope objectScope && objectScope.hasParams()) {
|
||||
return parent.frameDescriptorBuilder;
|
||||
}
|
||||
var grandParent = parent.parent;
|
||||
assert grandParent != null;
|
||||
return grandParent.frameDescriptorBuilder;
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ public abstract sealed class AbstractInvokeLexicalMethodNode
|
||||
int levelsUp,
|
||||
ExpressionNode[] argumentNodes,
|
||||
boolean needsConst,
|
||||
boolean argsRequireInference) {
|
||||
super(sourceSection, methodName, argumentNodes, needsConst, argsRequireInference);
|
||||
int methodSlot) {
|
||||
super(sourceSection, methodName, argumentNodes, needsConst, methodSlot);
|
||||
this.levelsUp = levelsUp;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -48,8 +48,8 @@ public abstract sealed class AbstractInvokeLexicalOrQualifiedMethodNode
|
||||
Identifier methodName,
|
||||
ExpressionNode[] argumentNodes,
|
||||
boolean needsConst,
|
||||
boolean argsRequireInference) {
|
||||
super(sourceSection, argumentNodes, argsRequireInference);
|
||||
int methodSlot) {
|
||||
super(sourceSection, argumentNodes, methodSlot);
|
||||
this.methodName = methodName;
|
||||
this.needsConst = needsConst;
|
||||
this.isConstChecked = false;
|
||||
|
||||
+8
-20
@@ -15,44 +15,32 @@
|
||||
*/
|
||||
package org.pkl.core.ast.expression.member;
|
||||
|
||||
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.ExplodeLoop;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.ast.member.Method;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
|
||||
public abstract class AbstractInvokeMethodNode extends ExpressionNode {
|
||||
|
||||
@Children protected final ExpressionNode[] argumentNodes;
|
||||
protected final boolean argsRequireInference;
|
||||
protected final int methodSlot;
|
||||
|
||||
public AbstractInvokeMethodNode(
|
||||
SourceSection sourceSection, ExpressionNode[] argumentNodes, boolean argsRequireInference) {
|
||||
SourceSection sourceSection, ExpressionNode[] argumentNodes, int methodSlot) {
|
||||
super(sourceSection);
|
||||
this.argumentNodes = argumentNodes;
|
||||
this.argsRequireInference = argsRequireInference;
|
||||
}
|
||||
|
||||
@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);
|
||||
this.methodSlot = methodSlot;
|
||||
}
|
||||
|
||||
@ExplodeLoop
|
||||
protected Object[] evalArgs(
|
||||
VirtualFrame frame, @Nullable Method method, Object owner, @Nullable Object receiver) {
|
||||
int methodSlot = -1;
|
||||
Object prevMethod = null;
|
||||
if (argsRequireInference) {
|
||||
methodSlot = getMethodSlot(frame.getFrameDescriptor());
|
||||
prevMethod = frame.getAuxiliarySlot(methodSlot);
|
||||
frame.setAuxiliarySlot(methodSlot, method);
|
||||
if (methodSlot > -1) {
|
||||
prevMethod = frame.getObject(methodSlot);
|
||||
frame.setObject(methodSlot, method);
|
||||
}
|
||||
|
||||
var args = new Object[2 + argumentNodes.length];
|
||||
@@ -64,8 +52,8 @@ public abstract class AbstractInvokeMethodNode extends ExpressionNode {
|
||||
args[2 + i] = argumentNodes[i].executeGeneric(frame);
|
||||
}
|
||||
} finally {
|
||||
if (argsRequireInference) {
|
||||
frame.setAuxiliarySlot(methodSlot, prevMethod);
|
||||
if (methodSlot > -1) {
|
||||
frame.setObject(methodSlot, prevMethod);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ public abstract sealed class AbstractInvokeQualifiedMethodNode
|
||||
ExpressionNode[] argumentNodes,
|
||||
boolean needsConst,
|
||||
ExpressionNode getReceiverNode,
|
||||
boolean argsRequireInference) {
|
||||
super(sourceSection, methodName, argumentNodes, needsConst, argsRequireInference);
|
||||
int methodSlot) {
|
||||
super(sourceSection, methodName, argumentNodes, needsConst, methodSlot);
|
||||
this.getReceiverNode = getReceiverNode;
|
||||
}
|
||||
|
||||
|
||||
+4
-15
@@ -16,41 +16,30 @@
|
||||
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;
|
||||
private final int methodSlot;
|
||||
|
||||
public InferParentWithinMethodArgumentNode(
|
||||
SourceSection sourceSection, VmLanguage language, int argIndex) {
|
||||
SourceSection sourceSection, VmLanguage language, int argIndex, int methodSlot) {
|
||||
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;
|
||||
this.methodSlot = methodSlot;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Method getMethod(VirtualFrame frame) {
|
||||
var method = (Method) frame.getAuxiliarySlot(getMethodSlot(frame.getFrameDescriptor()));
|
||||
var method = (Method) frame.getObject(methodSlot);
|
||||
if (method == null) {
|
||||
// used in FunctionN.apply()
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ public final class InvokeLexicalClassMethodNode extends AbstractInvokeLexicalMet
|
||||
int levelsUp,
|
||||
ExpressionNode[] argumentNodes,
|
||||
boolean needsConst,
|
||||
boolean argsRequireInference) {
|
||||
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, argsRequireInference);
|
||||
int methodSlot) {
|
||||
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, methodSlot);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -31,8 +31,8 @@ public final class InvokeLexicalObjectMethodNode extends AbstractInvokeLexicalMe
|
||||
int levelsUp,
|
||||
ExpressionNode[] argumentNodes,
|
||||
boolean needsConst,
|
||||
boolean argsRequireInference) {
|
||||
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, argsRequireInference);
|
||||
int methodSlot) {
|
||||
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst, methodSlot);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -35,8 +35,8 @@ public final class InvokeMethodDirectNode extends AbstractInvokeMethodNode {
|
||||
ClassMethod method,
|
||||
ExpressionNode receiverNode,
|
||||
ExpressionNode[] argumentNodes,
|
||||
boolean argsRequireInference) {
|
||||
super(sourceSection, argumentNodes, argsRequireInference);
|
||||
int methodSlot) {
|
||||
super(sourceSection, argumentNodes, methodSlot);
|
||||
this.method = method;
|
||||
this.owner = method.getOwner();
|
||||
this.receiverNode = receiverNode;
|
||||
|
||||
+5
-12
@@ -53,8 +53,8 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode {
|
||||
ExpressionNode[] argumentNodes,
|
||||
MemberLookupMode lookupMode,
|
||||
boolean needsConst,
|
||||
boolean argsRequireInference) {
|
||||
super(sourceSection, argumentNodes, argsRequireInference);
|
||||
int methodSlot) {
|
||||
super(sourceSection, argumentNodes, methodSlot);
|
||||
this.methodName = methodName;
|
||||
this.lookupMode = lookupMode;
|
||||
this.needsConst = needsConst;
|
||||
@@ -65,8 +65,8 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode {
|
||||
Identifier methodName,
|
||||
ExpressionNode[] argumentNodes,
|
||||
MemberLookupMode lookupMode,
|
||||
boolean argsRequireInference) {
|
||||
this(sourceSection, methodName, argumentNodes, lookupMode, false, argsRequireInference);
|
||||
int methodSlot) {
|
||||
this(sourceSection, methodName, argumentNodes, lookupMode, false, methodSlot);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,14 +146,7 @@ public abstract class InvokeMethodVirtualNode extends AbstractInvokeMethodNode {
|
||||
@Override
|
||||
public WrapperNode createWrapper(ProbeNode probe) {
|
||||
return new InvokeMethodVirtualNodeWrapper(
|
||||
sourceSection,
|
||||
methodName,
|
||||
argumentNodes,
|
||||
lookupMode,
|
||||
needsConst,
|
||||
argsRequireInference,
|
||||
this,
|
||||
probe);
|
||||
sourceSection, methodName, argumentNodes, lookupMode, needsConst, methodSlot, this, probe);
|
||||
}
|
||||
|
||||
private void checkConst(ClassMethod method) {
|
||||
|
||||
+2
-8
@@ -29,14 +29,8 @@ public final class InvokeQualifiedClassMethodNode extends AbstractInvokeQualifie
|
||||
ExpressionNode[] argumentNodes,
|
||||
boolean needsConst,
|
||||
ExpressionNode getReceiverNode,
|
||||
boolean argsRequireInference) {
|
||||
super(
|
||||
sourceSection,
|
||||
methodName,
|
||||
argumentNodes,
|
||||
needsConst,
|
||||
getReceiverNode,
|
||||
argsRequireInference);
|
||||
int methodSlot) {
|
||||
super(sourceSection, methodName, argumentNodes, needsConst, getReceiverNode, methodSlot);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-8
@@ -31,14 +31,8 @@ public final class InvokeQualifiedObjectMethodNode extends AbstractInvokeQualifi
|
||||
ExpressionNode[] argumentNodes,
|
||||
boolean needsConst,
|
||||
ExpressionNode getReceiverNode,
|
||||
boolean argsRequireInference) {
|
||||
super(
|
||||
sourceSection,
|
||||
methodName,
|
||||
argumentNodes,
|
||||
needsConst,
|
||||
getReceiverNode,
|
||||
argsRequireInference);
|
||||
int methodSlot) {
|
||||
super(sourceSection, methodName, argumentNodes, needsConst, getReceiverNode, methodSlot);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -36,8 +36,8 @@ public abstract class InvokeSuperMethodNode extends AbstractInvokeMethodNode {
|
||||
Identifier methodName,
|
||||
ExpressionNode[] argumentNodes,
|
||||
boolean needsConst,
|
||||
boolean argsRequireInference) {
|
||||
super(sourceSection, argumentNodes, argsRequireInference);
|
||||
int methodSlot) {
|
||||
super(sourceSection, argumentNodes, methodSlot);
|
||||
this.needsConst = needsConst;
|
||||
|
||||
assert !methodName.isLocalMethod();
|
||||
|
||||
@@ -86,7 +86,7 @@ public abstract class ToStringNode extends UnaryExpressionNode {
|
||||
Identifier.TO_STRING,
|
||||
new ExpressionNode[] {},
|
||||
MemberLookupMode.EXPLICIT_RECEIVER,
|
||||
false,
|
||||
-1,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
@@ -1401,7 +1401,7 @@ public abstract class TypeNode extends PklNode {
|
||||
vmListing,
|
||||
EconomicMaps.emptyMap(),
|
||||
vmListing.getLength(),
|
||||
getValueTypeCastNode(),
|
||||
getValueTypeCastNode(frame.getFrameDescriptor()),
|
||||
VmUtils.getReceiver(frame),
|
||||
VmUtils.getOwner(frame));
|
||||
}
|
||||
@@ -1459,7 +1459,7 @@ public abstract class TypeNode extends PklNode {
|
||||
vmMapping.getEnclosingFrame(),
|
||||
vmMapping,
|
||||
EconomicMaps.emptyMap(),
|
||||
getValueTypeCastNode(),
|
||||
getValueTypeCastNode(frame.getFrameDescriptor()),
|
||||
VmUtils.getReceiver(frame),
|
||||
VmUtils.getOwner(frame));
|
||||
}
|
||||
@@ -1518,12 +1518,12 @@ public abstract class TypeNode extends PklNode {
|
||||
return valueTypeNode;
|
||||
}
|
||||
|
||||
protected ListingOrMappingTypeCastNode getValueTypeCastNode() {
|
||||
protected ListingOrMappingTypeCastNode getValueTypeCastNode(FrameDescriptor descriptor) {
|
||||
if (valueTypeCastNode == null) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
valueTypeCastNode =
|
||||
new ListingOrMappingTypeCastNode(
|
||||
language, new FrameDescriptor(), valueTypeNode, getRootNode().getName());
|
||||
language, descriptor, valueTypeNode, getRootNode().getName());
|
||||
}
|
||||
return valueTypeCastNode;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ import com.oracle.truffle.api.frame.FrameSlotKind;
|
||||
import java.util.Arrays;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/** A wrapper for Truffle's {@link FrameDescriptor.Builder}, but also gives us the current size. */
|
||||
/**
|
||||
* A wrapper for Truffle's {@link FrameDescriptor.Builder}, but also helps track size, identifiers,
|
||||
* and other metadata.
|
||||
*/
|
||||
public class FrameDescriptorBuilder {
|
||||
|
||||
private @Nullable Identifier[] names;
|
||||
@@ -29,6 +32,8 @@ public class FrameDescriptorBuilder {
|
||||
private final FrameDescriptor.Builder underlying;
|
||||
|
||||
private static final int DEFAULT_CAPACITY = 8;
|
||||
private int methodSlot = -1;
|
||||
private int state = 0;
|
||||
|
||||
public FrameDescriptorBuilder() {
|
||||
this(DEFAULT_CAPACITY);
|
||||
@@ -50,10 +55,36 @@ public class FrameDescriptorBuilder {
|
||||
ensureCapacity(1);
|
||||
names[size] = name;
|
||||
size++;
|
||||
state++;
|
||||
var slot = underlying.addSlot(kind, name, info);
|
||||
return new FrameSlotVariable(name.toString(), slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* A monotonically increasing number representing the "virtual" state of the frame descriptor; a
|
||||
* higher number means that something tried to modify the descriptor (but did not necessarily
|
||||
* change the actual descriptor).
|
||||
*/
|
||||
public int state() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public int getOrAddMethodSlot() {
|
||||
state++;
|
||||
if (methodSlot != -1) {
|
||||
return methodSlot;
|
||||
}
|
||||
ensureCapacity(1);
|
||||
names[size] = null;
|
||||
size++;
|
||||
methodSlot = underlying.addSlot(FrameSlotKind.Object, null, null);
|
||||
return methodSlot;
|
||||
}
|
||||
|
||||
public int getMethodSlot() {
|
||||
return methodSlot;
|
||||
}
|
||||
|
||||
public FrameDescriptor build() {
|
||||
return underlying.build();
|
||||
}
|
||||
|
||||
@@ -84,14 +84,6 @@ public final class VmUtils {
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
// let expr inside custom this scopes
|
||||
typealias Foo = String(let (x = this) this == x)
|
||||
|
||||
typealias Foo2 = String(let (x = "foo") this.startsWith(x), let (y = "o") this.endsWith(y))
|
||||
|
||||
typealias Foo3 = String(let (x = "foo") this.startsWith(x))(let (y = "o") this.endsWith(y))
|
||||
|
||||
res1: Foo = "foo"
|
||||
|
||||
res2: Foo2 = "foo"
|
||||
|
||||
res3: Foo3 = "foo"
|
||||
|
||||
hidden bar {
|
||||
new {
|
||||
name = "fooey"
|
||||
}
|
||||
new {
|
||||
name = "quxxey"
|
||||
}
|
||||
}
|
||||
|
||||
res4 = (bar) {
|
||||
res1 = (bar) {
|
||||
[[let (x = "foo") this.name.startsWith(x)]] {
|
||||
name = "bob"
|
||||
}
|
||||
[[let (y = "qux") this.name.startsWith(y)]] {
|
||||
name = "bob"
|
||||
}
|
||||
}
|
||||
|
||||
res5 = (bar) {
|
||||
res2 = (bar) {
|
||||
[[let (x = this) x.name.startsWith("foo")]] {
|
||||
name = "bob"
|
||||
}
|
||||
}
|
||||
|
||||
function isPositive(num: Int(let (q = 0) this > q)) = true
|
||||
|
||||
res3: Int(isPositive(this)) = 5
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
foo {
|
||||
new {
|
||||
prop = 1
|
||||
}
|
||||
}
|
||||
|
||||
res = (foo) {
|
||||
[[this.prop is Int(let (q = 0) this > q)]] {
|
||||
prop = 2
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
res1 {
|
||||
local x: Int(let (q = 0) this > q) = 5
|
||||
z = x
|
||||
}
|
||||
|
||||
res2 = f()
|
||||
|
||||
function f(): Int(let (q = 0) this > q) = 5
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
local f = (a, b) -> new Dynamic {
|
||||
x = a
|
||||
y = b
|
||||
}
|
||||
|
||||
res =
|
||||
(f) { p, q: Int(let (z = 0) this > z) ->
|
||||
x = p
|
||||
y = q
|
||||
}.apply("hi", 9)
|
||||
|
||||
local g = (a, b: Int(let (z = 0) this > z)) -> new Dynamic {
|
||||
x = a
|
||||
y = b
|
||||
}
|
||||
|
||||
res2 = g.apply("hi", 9)
|
||||
|
||||
res3 = new Dynamic {
|
||||
local function h(a, b: Int(let (z = 0) this > z)) = new Dynamic {
|
||||
x = a
|
||||
y = b
|
||||
}
|
||||
`out` = h("hi", 9)
|
||||
}
|
||||
|
||||
res4 =
|
||||
(f) { p, q: Int(let (z = 0) this > z) ->
|
||||
when (true) {
|
||||
x = p
|
||||
y = q
|
||||
}
|
||||
}.apply("hi", 9)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// let exprs inside typealiases turn into their own root node
|
||||
typealias Foo = String(let (x = this) this == x)
|
||||
|
||||
typealias Foo2 = String(let (x = "foo") this.startsWith(x), let (y = "o") this.endsWith(y))
|
||||
|
||||
typealias Foo3 = String(let (x = "foo") this.startsWith(x))(let (y = "o") this.endsWith(y))
|
||||
|
||||
res1: Foo = "foo"
|
||||
|
||||
res2: Foo2 = "foo"
|
||||
|
||||
res3: Foo3 = "foo"
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
class Foo {
|
||||
bar: Int
|
||||
}
|
||||
|
||||
const function chk2(i: Int): Boolean = i > 0
|
||||
|
||||
const function mk(f: Foo): Listing<Int> = new {
|
||||
f.bar
|
||||
}
|
||||
|
||||
class C {
|
||||
xs: Listing<Int(chk2(this))> = mk(new { bar = 2 })
|
||||
}
|
||||
|
||||
c: C = new {}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
class Foo {
|
||||
bar: Int
|
||||
}
|
||||
|
||||
const function isOk(f: Foo): Boolean = f.bar > 0
|
||||
const function isPos(i: Int): Boolean = i > 0
|
||||
|
||||
typealias T = Int(isOk(new { bar = 1 }), isPos(this), isOk(new { bar = 2 }))
|
||||
|
||||
x: T = 5
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
class Foo {
|
||||
bar: Int
|
||||
}
|
||||
const function isOk(f: Foo): Boolean = f.bar > 0
|
||||
const function and(a: Boolean, b: Boolean) = a && b
|
||||
|
||||
function m(p: Int(isOk(new { bar = 1 }))): Int = p
|
||||
|
||||
function n(
|
||||
// outer method call does not have implicit new, but inner method call does
|
||||
p: Int(
|
||||
and(
|
||||
isOk(new { bar = 1 }),
|
||||
true,
|
||||
),
|
||||
),
|
||||
): Int = p
|
||||
|
||||
res1 = m(3)
|
||||
res2 = n(3)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
class Foo {
|
||||
bar: Int
|
||||
}
|
||||
const function isOk(f: Foo): Boolean = f.bar > 0
|
||||
const function isPos(num: Int) = num > 0
|
||||
|
||||
foo {
|
||||
new {
|
||||
prop = 1
|
||||
}
|
||||
}
|
||||
|
||||
res = (foo) {
|
||||
[[this.prop is Int(isOk(new { bar = 1 }), isPos(this), isOk(new { bar = 2 }))]] {
|
||||
prop = 2
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
class Foo {
|
||||
bar: Int
|
||||
}
|
||||
const function isOk(f: Foo): Boolean = f.bar > 0
|
||||
const function and(a: Boolean, b: Boolean) = a && b
|
||||
|
||||
typealias Okay = Int(isOk(new { bar = 1 }))
|
||||
|
||||
typealias Okay2 =
|
||||
Int(
|
||||
// no inferred new arg
|
||||
and(
|
||||
// nested call has inferred new arg
|
||||
isOk(new { bar = 1 }),
|
||||
true,
|
||||
),
|
||||
)
|
||||
|
||||
typealias Okay3 =
|
||||
Int(
|
||||
// no inferred new arg
|
||||
isOk(new Foo { bar = 1 }),
|
||||
// inferred new arg
|
||||
isOk(new { bar = 1 }),
|
||||
)
|
||||
|
||||
res1: Okay = 3
|
||||
res2: Okay2 = 3
|
||||
res3: Okay3 = 3
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
class Foo {
|
||||
bar: Int
|
||||
}
|
||||
const function isOk2(f: Foo, n: Int): Boolean = f.bar > 0 && n > 0
|
||||
local f = (a, b) -> new Dynamic { y = a; w = b }
|
||||
|
||||
res =
|
||||
(f) { p, q ->
|
||||
when (isOk2(new { bar = 1 }, p)) {
|
||||
z = p
|
||||
}
|
||||
}.apply(1, 2)
|
||||
|
||||
res2 =
|
||||
(f) {
|
||||
when (isOk2(new { bar = 1 }, 2)) {
|
||||
z = 2
|
||||
}
|
||||
}.apply(1, 2)
|
||||
@@ -1,13 +1,17 @@
|
||||
res1 = "foo"
|
||||
res2 = "foo"
|
||||
res3 = "foo"
|
||||
res4 {
|
||||
res1 {
|
||||
new {
|
||||
name = "bob"
|
||||
}
|
||||
new {
|
||||
name = "bob"
|
||||
}
|
||||
}
|
||||
res5 {
|
||||
res2 {
|
||||
new {
|
||||
name = "bob"
|
||||
}
|
||||
new {
|
||||
name = "quxxey"
|
||||
}
|
||||
}
|
||||
res3 = 5
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
foo {
|
||||
new {
|
||||
prop = 1
|
||||
}
|
||||
}
|
||||
res {
|
||||
new {
|
||||
prop = 2
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
res1 {
|
||||
z = 5
|
||||
}
|
||||
res2 = 5
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
res {
|
||||
x = "hi"
|
||||
y = 9
|
||||
}
|
||||
res2 {
|
||||
x = "hi"
|
||||
y = 9
|
||||
}
|
||||
res3 {
|
||||
`out` {
|
||||
x = "hi"
|
||||
y = 9
|
||||
}
|
||||
}
|
||||
res4 {
|
||||
x = "hi"
|
||||
y = 9
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
res1 = "foo"
|
||||
res2 = "foo"
|
||||
res3 = "foo"
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
c {
|
||||
xs {
|
||||
2
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
x = 5
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
res1 = 3
|
||||
res2 = 3
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
foo {
|
||||
new {
|
||||
prop = 1
|
||||
}
|
||||
}
|
||||
res {
|
||||
new {
|
||||
prop = 2
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
res1 = 3
|
||||
res2 = 3
|
||||
res3 = 3
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
res {
|
||||
y = 1
|
||||
w = 2
|
||||
z = 1
|
||||
}
|
||||
res2 {
|
||||
y = 1
|
||||
w = 2
|
||||
z = 2
|
||||
}
|
||||
Reference in new issue
Block a user