diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java index 66c82b3dc6528..a147f0874dc79 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java @@ -13,6 +13,8 @@ import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.pkg.QualifiedName; +import java.util.Optional; + /** A common base class for all kinds of root node in Enso. */ @NodeInfo(shortName = "Root", description = "A root node for Enso computations") public abstract class EnsoRootNode extends RootNode { @@ -138,4 +140,13 @@ protected boolean isInstrumentable() { public boolean isCloningAllowed() { return true; } + + /** + * Gets the frame slot containing the already evaluated arguments. + * + * @return the arguments frame slot + */ + public Optional getArgsFrameSlot() { + return Optional.empty(); + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java index 534b3091c2aaf..ee3dad7cb61a5 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java @@ -1,15 +1,22 @@ package org.enso.interpreter.node.expression.atom; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.api.frame.FrameSlot; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.ConditionProfile; +import org.enso.interpreter.node.EnsoRootNode; import org.enso.interpreter.node.ExpressionNode; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.type.TypesGen; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + /** * A node instantiating a constant {@link AtomConstructor} with values computed based on the * children nodes. @@ -54,10 +61,15 @@ public static InstantiateNode build(AtomConstructor constructor, ExpressionNode[ @ExplodeLoop public Object executeGeneric(VirtualFrame frame) { Object[] argumentValues = new Object[arguments.length]; + FrameSlot argsFrameSlot = ((EnsoRootNode) getRootNode()).getArgsFrameSlot().orElseThrow(() -> new IllegalStateException("invalid RootNode when instantiating constructor")); + ArgumentDefinition[] fields = constructor.getFields(); + Map evaluatedArgs = new HashMap<>(); for (int i = 0; i < arguments.length; i++) { ConditionProfile profile = profiles[i]; BranchProfile sentinelProfile = sentinelProfiles[i]; Object argument = arguments[i].executeGeneric(frame); + evaluatedArgs.put(fields[i].getName(), argument); + frame.setObject(argsFrameSlot, evaluatedArgs); if (profile.profile(TypesGen.isDataflowError(argument))) { return argument; } else if (TypesGen.isPanicSentinel(argument)) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java index 8a8fa58c73f1a..0c9fcdb2ad4fc 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java @@ -1,23 +1,37 @@ package org.enso.interpreter.node.expression.builtin; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.frame.FrameSlot; +import com.oracle.truffle.api.frame.FrameSlotKind; +import com.oracle.truffle.api.frame.FrameUtil; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.NodeInfo; -import com.oracle.truffle.api.nodes.RootNode; import org.enso.interpreter.Language; +import org.enso.interpreter.node.EnsoRootNode; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.scope.LocalScope; +import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.interpreter.runtime.state.Stateful; +import java.util.Optional; + /** This node represents the process of instantiating an atom at runtime. */ @NodeInfo(shortName = "constructor::", description = "An atom instantiation at runtime.") -public class InstantiateAtomNode extends RootNode { +public class InstantiateAtomNode extends EnsoRootNode { private @Child ExpressionNode instantiator; - private final String name; + private final FrameSlot argsFrameSlot; - private InstantiateAtomNode(Language language, String name, ExpressionNode instantiator) { - super(language); - this.name = name; + private InstantiateAtomNode( + Language language, + LocalScope localScope, + ModuleScope moduleScope, + String name, + ExpressionNode instantiator) { + super(language, localScope, moduleScope, name, instantiator.getSourceSection()); this.instantiator = instantiator; + this.argsFrameSlot = + localScope.frameDescriptor().findOrAddFrameSlot("<>", FrameSlotKind.Object); } /** @@ -28,9 +42,14 @@ private InstantiateAtomNode(Language language, String name, ExpressionNode insta */ @Override public Stateful execute(VirtualFrame frame) { - return new Stateful( - Function.ArgumentsHelper.getState(frame.getArguments()), - instantiator.executeGeneric(frame)); + if (CompilerDirectives.inCompilationRoot() || CompilerDirectives.inInterpreter()) { + com.oracle.truffle.api.TruffleSafepoint.poll(this); + } + Object state = Function.ArgumentsHelper.getState(frame.getArguments()); + frame.setObject(this.getStateFrameSlot(), state); + Object result = instantiator.executeGeneric(frame); + state = FrameUtil.getObjectSafe(frame, this.getStateFrameSlot()); + return new Stateful(state, result); } /** @@ -40,7 +59,7 @@ public Stateful execute(VirtualFrame frame) { */ @Override public String getName() { - return "constructor::" + name; + return "constructor::" + getName(); } /** @@ -52,7 +71,20 @@ public String getName() { * @return an instance of this node */ public static InstantiateAtomNode build( - Language language, String name, ExpressionNode instantiator) { - return new InstantiateAtomNode(language, name, instantiator); + Language language, LocalScope localScope, + ModuleScope moduleScope, + String name, + ExpressionNode instantiator) { + return new InstantiateAtomNode(language,localScope, moduleScope, name, instantiator); + } + + /** + * Gets the frame slot containing the already evaluated arguments, if any.. + * + * @return the arguments frame slot + */ + @Override + public Optional getArgsFrameSlot() { + return Optional.of(argsFrameSlot); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/DynamicSymbolNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/DynamicSymbolNode.java index 3e2058695d0c2..a960a6aca302c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/DynamicSymbolNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/constant/DynamicSymbolNode.java @@ -1,10 +1,16 @@ package org.enso.interpreter.node.expression.constant; +import com.oracle.truffle.api.frame.FrameSlot; +import com.oracle.truffle.api.frame.FrameUtil; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.NodeInfo; +import org.enso.interpreter.node.EnsoRootNode; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import java.util.Map; +import java.util.Optional; + /** Simple constant node that always results in the same {@link UnresolvedSymbol}. */ @NodeInfo(shortName = "DynamicSym") public class DynamicSymbolNode extends ExpressionNode { @@ -32,6 +38,16 @@ public static DynamicSymbolNode build(UnresolvedSymbol symbol) { */ @Override public Object executeGeneric(VirtualFrame frame) { - return unresolvedSymbol; + Optional args = ((EnsoRootNode) getRootNode()).getArgsFrameSlot(); + return args.flatMap(fs -> extractArgFromFrameSlot(FrameUtil.getObjectSafe(frame, fs), unresolvedSymbol.getName())).orElse(unresolvedSymbol); + } + + private Optional extractArgFromFrameSlot(Object argMap, String symbolName) { + if (argMap != null ) { + Map mapping = (Map)argMap; + Object v = mapping.get(symbolName); + if (v != null) return Optional.of(v); + } + return Optional.empty(); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java index 0abc7f645b6f2..579235208b4d7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java @@ -26,6 +26,7 @@ import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; +import org.enso.interpreter.runtime.scope.LocalScope; import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.pkg.QualifiedName; @@ -51,6 +52,7 @@ public AtomConstructor(String name, ModuleScope definitionScope) { this.definitionScope = definitionScope; } + /** * Sets the fields of this {@link AtomConstructor} and generates a constructor function. * @@ -58,8 +60,19 @@ public AtomConstructor(String name, ModuleScope definitionScope) { * @return {@code this}, for convenience */ public AtomConstructor initializeFields(ArgumentDefinition... args) { + return initializeFieldsWithScope(LocalScope.root(), args); + } + + /** + * Sets the fields of this {@link AtomConstructor} and generates a constructor function. + * + * @param localScope a description of the local scope + * @param args the arguments this constructor will take + * @return {@code this}, for convenience + */ + public AtomConstructor initializeFieldsWithScope(LocalScope localScope, ArgumentDefinition... args) { CompilerDirectives.transferToInterpreterAndInvalidate(); - this.constructorFunction = buildConstructorFunction(args); + this.constructorFunction = buildConstructorFunction(localScope, args); generateMethods(args); if (args.length == 0) { cachedInstance = new Atom(this); @@ -76,13 +89,13 @@ public AtomConstructor initializeFields(ArgumentDefinition... args) { * @return a {@link Function} taking the specified arguments and returning an instance for this * {@link AtomConstructor} */ - private Function buildConstructorFunction(ArgumentDefinition[] args) { + private Function buildConstructorFunction(LocalScope localScope, ArgumentDefinition[] args) { ExpressionNode[] argumentReaders = new ExpressionNode[args.length]; for (int i = 0; i < args.length; i++) { argumentReaders[i] = ReadArgumentNode.build(i, args[i].getDefaultValue().orElse(null)); } ExpressionNode instantiateNode = InstantiateNode.build(this, argumentReaders); - RootNode rootNode = InstantiateAtomNode.build(null, name, instantiateNode); + RootNode rootNode = InstantiateAtomNode.build(null, localScope, definitionScope, name, instantiateNode); RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode); return new Function(callTarget, null, new FunctionSchema(args)); } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index a6f5f757bf2bc..59c20a088d591 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -159,15 +159,16 @@ class IrToTruffle( DataflowAnalysis, "No dataflow information associated with an atom." ) + val localScope = new LocalScope( + None, + scopeInfo.graph, + scopeInfo.graph.rootScope, + dataflowInfo + ) val argFactory = new DefinitionArgumentProcessor( - scope = new LocalScope( - None, - scopeInfo.graph, - scopeInfo.graph.rootScope, - dataflowInfo - ) + scope = localScope ) val argDefs = new Array[ArgumentDefinition](atomDefn.arguments.size) @@ -176,7 +177,7 @@ class IrToTruffle( argDefs(idx) = argFactory.run(atomDefn.arguments(idx), idx) } - atomCons.initializeFields(argDefs: _*) + atomCons.initializeFieldsWithScope(localScope, argDefs: _*) } // Register the method definitions in scope diff --git a/test/Tests/src/Semantic/Default_Args_Spec.enso b/test/Tests/src/Semantic/Default_Args_Spec.enso new file mode 100644 index 0000000000000..dad889ea60446 --- /dev/null +++ b/test/Tests/src/Semantic/Default_Args_Spec.enso @@ -0,0 +1,42 @@ +from Standard.Base import all +import Standard.Test + +type Box + type Foo (v : Bool = True) + +type Bar (a : Integer = 1) (b : Box = (Foo False)) + +type A a=0 b=1 +type B a=2 b=(Foo True) +type C a=3 b=Foo +type D a=4 b=(Bar 1) +type E a=5 b=a c=(b+1) +type F a=6 b=(Foo False) c=(b.v) +#type D a=4 b=Bar // will crash + +spec = + Test.group "Atom Constructors" <| + Test.specify "should be allowed to use primitive default arguments" <| + x = A 1 + x.b.should_equal 1 + y = A 1 + y.b.should_equal 1 + + Test.specify "should be allowed to use non-primitive default arguments" <| + a = B 1 (Foo False) + a.b.should_equal (Foo False) + b = B 1 + b.b.should_equal (Foo True) + c = C 1 + c.b.should_equal (Foo) + d = D 1 + d.b.b.should_equal (Foo False) + + Test.specify "should be allowed to use default arguments that refer to previous parameters" <| + e = E 1 + e.b.should_equal 1 + e.c.should_equal 2 + f = F 1 + f.c.should_equal False + +main = Test.Suite.run_main here.spec \ No newline at end of file