Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialize AtomConstructor's fields via local vars #3330

Merged
merged 13 commits into from
Mar 21, 2022
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,12 @@
- [Added overloaded `from` conversions.][3227]
- [Upgraded to Graal VM 21.3.0][3258]
- [Added the ability to decorate values with warnings.][3248]
- [Fixed issues related to constructors' default arguments][3330]

[3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248
[3258]: https://github.com/enso-org/enso/pull/3258
[3330]: https://github.com/enso-org/enso/pull/3330

# Enso 2.0.0-alpha.18 (2021-10-12)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,5 @@ protected boolean isInstrumentable() {
public boolean isCloningAllowed() {
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class InstantiateNode extends ExpressionNode {
* Creates an instance of this node.
*
* @param constructor the {@link AtomConstructor} this node will be instantiating
* @param arguments the expressions for field values
* @param arguments the expressions that produce field values
* @return a node that instantiates {@code constructor}
*/
public static InstantiateNode build(AtomConstructor constructor, ExpressionNode[] arguments) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.node.ClosureRootNode;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.callable.argument.ReadArgumentNode;
import org.enso.interpreter.node.callable.function.BlockNode;
import org.enso.interpreter.node.expression.atom.GetFieldNode;
import org.enso.interpreter.node.expression.atom.InstantiateNode;
import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode;
import org.enso.interpreter.node.expression.builtin.InstantiateAtomNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
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;

Expand All @@ -39,7 +41,7 @@ public final class AtomConstructor implements TruffleObject {

/**
* Creates a new Atom constructor for a given name. The constructor is not valid until {@link
* AtomConstructor#initializeFields(ArgumentDefinition...)} is called.
* AtomConstructor#initializeFields(LocalScope,ExpressionNode[],ExpressionNode[],ArgumentDefinition...)} is called.
*
* @param name the name of the Atom constructor
* @param definitionScope the scope in which this constructor was defined
Expand All @@ -49,15 +51,37 @@ public AtomConstructor(String name, ModuleScope definitionScope) {
this.definitionScope = definitionScope;
}


/**
* Generates a constructor function for this {@link AtomConstructor}.
* Note that such manually constructed argument definitions must not have default arguments.
*
* @return {@code this}, for convenience
*/
public AtomConstructor initializeFields(ArgumentDefinition... args) {
ExpressionNode[] reads = new ExpressionNode[args.length];
for (int i=0; i<args.length; i++) {
reads[i] = ReadArgumentNode.build(i, null);
}
return initializeFields(LocalScope.root(), new ExpressionNode[0], reads, args);
}

/**
* Sets the fields of this {@link AtomConstructor} and generates a constructor function.
*
* @param localScope a description of the local scope
* @param assignments the expressions that evaluate and assign constructor arguments to local vars
* @param varReads the expressions that read field values from local vars
* @param args the arguments this constructor will take
* @return {@code this}, for convenience
*/
public AtomConstructor initializeFields(ArgumentDefinition... args) {
public AtomConstructor initializeFields(
LocalScope localScope,
ExpressionNode[] assignments,
ExpressionNode[] varReads,
ArgumentDefinition... args) {
CompilerDirectives.transferToInterpreterAndInvalidate();
this.constructorFunction = buildConstructorFunction(args);
this.constructorFunction = buildConstructorFunction(localScope, assignments, varReads, args);
generateMethods(args);
if (args.length == 0) {
cachedInstance = new Atom(this);
Expand All @@ -69,20 +93,27 @@ public AtomConstructor initializeFields(ArgumentDefinition... args) {

/**
* Generates a constructor function to be used for object instantiation from other Enso code.
* Building constructor function involves storing the argument in a local var and then reading
* it again on purpose. That way default arguments can refer to previously defined constructor arguments.
*
* @param localScope a description of the local scope
* @param assignments the expressions that evaluate and assign constructor arguments to local vars
* @param varReads the expressions that read field values from previously evaluated local vars
* @param args the argument definitions for the constructor function to take
* @return a {@link Function} taking the specified arguments and returning an instance for this
* {@link AtomConstructor}
*/
private Function buildConstructorFunction(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);
private Function buildConstructorFunction(
LocalScope localScope,
ExpressionNode[] assignments,
ExpressionNode[] varReads,
ArgumentDefinition[] args) {

ExpressionNode instantiateNode = InstantiateNode.build(this, varReads);
BlockNode instantiateBlock = BlockNode.build(assignments, instantiateNode);
RootNode rootNode =
InstantiateAtomNode.build(
null, definitionScope.getModule().getName().item() + "." + name, instantiateNode);
ClosureRootNode.build(null, localScope, definitionScope, instantiateBlock,
instantiateNode.getSourceSection(), definitionScope.getModule().getName().item() + "." + name);
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);
return new Function(callTarget, null, new FunctionSchema(args));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,24 +159,41 @@ 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)
val argumentExpressions = new ArrayBuffer[(RuntimeExpression, RuntimeExpression)]

for (idx <- atomDefn.arguments.indices) {
argDefs(idx) = argFactory.run(atomDefn.arguments(idx), idx)
val unprocessedArg = atomDefn.arguments(idx)
val arg = argFactory.run(unprocessedArg, idx)
val occInfo = unprocessedArg
.unsafeGetMetadata(
AliasAnalysis,
"No occurrence on an argument definition."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please run scalafmt on this file

val slot = localScope.createVarSlot(occInfo.id)
argDefs(idx) = arg
val readArg = ReadArgumentNode.build(idx, arg.getDefaultValue.orElse(null))
val assignmentArg = AssignmentNode.build(readArg, slot)
val argRead = ReadLocalVariableNode.build(new FramePointer(0, slot))
argumentExpressions.append((assignmentArg, argRead))
}

atomCons.initializeFields(argDefs: _*)
val (assignments, reads) = argumentExpressions.unzip

atomCons.initializeFields(localScope, assignments.toArray, reads.toArray, argDefs: _*)
}

// Register the method definitions in scope
Expand Down
43 changes: 43 additions & 0 deletions test/Tests/src/Semantic/Default_Args_Spec.enso
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from Standard.Base import all
import Standard.Test

type Box
type Foo (v : Bool = True)

type Bar (a : Integer = 1) (b : Box = (Foo False)) (c : Boolean = b.v)

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)
d.b.c.should_equal 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