Automatically force all-defaulted functions (#3414)

This changes the interpreter to treat functions with all-defaulted args as thunks. Seems to have no performance impact in compiled code.
This commit is contained in:
Marcin Kostrzewa 2022-04-27 19:57:00 +02:00 committed by GitHub
parent bb6a5bac02
commit 96a0c92c8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 236 additions and 461 deletions

View File

@ -185,6 +185,7 @@
- [Fixed compiler issue related to module cache.][3367] - [Fixed compiler issue related to module cache.][3367]
- [Fixed execution of defaulted arguments of Atom Constructors][3358] - [Fixed execution of defaulted arguments of Atom Constructors][3358]
- [Converting Enso Date to java.time.LocalDate and back][3374] - [Converting Enso Date to java.time.LocalDate and back][3374]
- [Functions with all-defaulted arguments now execute automatically][3414]
[3227]: https://github.com/enso-org/enso/pull/3227 [3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248 [3248]: https://github.com/enso-org/enso/pull/3248
@ -195,6 +196,7 @@
[3367]: https://github.com/enso-org/enso/pull/3367 [3367]: https://github.com/enso-org/enso/pull/3367
[3374]: https://github.com/enso-org/enso/pull/3374 [3374]: https://github.com/enso-org/enso/pull/3374
[3412]: https://github.com/enso-org/enso/pull/3412 [3412]: https://github.com/enso-org/enso/pull/3412
[3414]: https://github.com/enso-org/enso/pull/3414
[3417]: https://github.com/enso-org/enso/pull/3417 [3417]: https://github.com/enso-org/enso/pull/3417
# Enso 2.0.0-alpha.18 (2021-10-12) # Enso 2.0.0-alpha.18 (2021-10-12)

View File

@ -18,7 +18,13 @@ public abstract class BaseNode extends Node {
/** Node is in a tail position and marked as a tail call. */ /** Node is in a tail position and marked as a tail call. */
TAIL_LOOP, TAIL_LOOP,
/** Node is not in a tail position. */ /** Node is not in a tail position. */
NOT_TAIL NOT_TAIL;
private static final int NUMBER_OF_VALUES = values().length;
public static int numberOfValues() {
return NUMBER_OF_VALUES;
}
} }
private @CompilationFinal TailStatus tailStatus = TailStatus.NOT_TAIL; private @CompilationFinal TailStatus tailStatus = TailStatus.NOT_TAIL;

View File

@ -5,7 +5,6 @@ import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.api.source.SourceSection; import com.oracle.truffle.api.source.SourceSection;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
@ -17,7 +16,6 @@ import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedConversion;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.Function;

View File

@ -41,10 +41,9 @@ public abstract class IndirectArgumentSorterNode extends Node {
Object state, Object state,
ThunkExecutorNode thunkExecutorNode) { ThunkExecutorNode thunkExecutorNode) {
for (int i = 0; i < mapping.getArgumentShouldExecute().length; i++) { for (int i = 0; i < mapping.getArgumentShouldExecute().length; i++) {
if (TypesGen.isThunk(arguments[i]) && mapping.getArgumentShouldExecute()[i]) { if (mapping.getArgumentShouldExecute()[i]) {
Stateful result = Stateful result =
thunkExecutorNode.executeThunk( thunkExecutorNode.executeThunk(arguments[i], state, BaseNode.TailStatus.NOT_TAIL);
TypesGen.asThunk(arguments[i]), state, BaseNode.TailStatus.NOT_TAIL);
arguments[i] = result.getValue(); arguments[i] = result.getValue();
state = result.getState(); state = result.getState();
} }

View File

@ -45,17 +45,17 @@ public class ReadArgumentNode extends ExpressionNode {
*/ */
@Override @Override
public Object executeGeneric(VirtualFrame frame) { public Object executeGeneric(VirtualFrame frame) {
Object argument = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[index]; Object arguments[] = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments());
if (defaultValue == null) { if (defaultValue == null) {
return argument; return arguments[index];
} }
// Note [Handling Argument Defaults] // Note [Handling Argument Defaults]
if (defaultingProfile.profile(argument == null)) { if (defaultingProfile.profile(arguments.length <= index || arguments[index] == null)) {
return defaultValue.executeGeneric(frame); return defaultValue.executeGeneric(frame);
} else { } else {
return argument; return arguments[index];
} }
} }

View File

@ -4,7 +4,7 @@ import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.argument.Thunk; import org.enso.interpreter.runtime.callable.function.Function;
/** This node is responsible for wrapping a call target in a {@link Thunk} at execution time. */ /** This node is responsible for wrapping a call target in a {@link Thunk} at execution time. */
@NodeInfo(shortName = "CreateThunk", description = "Wraps a call target in a thunk at runtime") @NodeInfo(shortName = "CreateThunk", description = "Wraps a call target in a thunk at runtime")
@ -34,6 +34,6 @@ public class CreateThunkNode extends ExpressionNode {
*/ */
@Override @Override
public Object executeGeneric(VirtualFrame frame) { public Object executeGeneric(VirtualFrame frame) {
return new Thunk(this.callTarget, frame.materialize()); return Function.thunk(this.callTarget, frame.materialize());
} }
} }

View File

@ -7,7 +7,6 @@ import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.state.Stateful;
/** Node responsible for handling user-requested thunks forcing. */ /** Node responsible for handling user-requested thunks forcing. */

View File

@ -7,12 +7,14 @@ import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Constants; import org.enso.interpreter.Constants;
import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.node.callable.dispatch.IndirectInvokeFunctionNode;
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
import org.enso.interpreter.node.callable.dispatch.LoopingCallOptimiserNode; import org.enso.interpreter.node.callable.dispatch.LoopingCallOptimiserNode;
import org.enso.interpreter.runtime.callable.argument.Thunk; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.control.TailCallException; import org.enso.interpreter.runtime.control.TailCallException;
import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.state.Stateful;
import org.enso.interpreter.runtime.type.TypesGen;
/** Node responsible for executing (forcing) thunks passed to it as runtime values. */ /** Node responsible for executing (forcing) thunks passed to it as runtime values. */
@GenerateUncached @GenerateUncached
@ -40,30 +42,25 @@ public abstract class ThunkExecutorNode extends Node {
*/ */
public abstract Stateful executeThunk(Object thunk, Object state, BaseNode.TailStatus isTail); public abstract Stateful executeThunk(Object thunk, Object state, BaseNode.TailStatus isTail);
static boolean isThunk(Object th) { boolean sameCallTarget(DirectCallNode callNode, Function function) {
return TypesGen.isThunk(th); return function.getCallTarget() == callNode.getCallTarget();
}
@Specialization(guards = "!isThunk(thunk)")
Stateful doOther(Object thunk, Object state, BaseNode.TailStatus isTail) {
return new Stateful(state, thunk);
} }
@Specialization( @Specialization(
guards = "callNode.getCallTarget() == thunk.getCallTarget()", guards = {"function.isThunk()", "sameCallTarget(callNode, function)"},
limit = Constants.CacheSizes.THUNK_EXECUTOR_NODE) limit = Constants.CacheSizes.THUNK_EXECUTOR_NODE)
Stateful doCached( Stateful doCached(
Thunk thunk, Function function,
Object state, Object state,
BaseNode.TailStatus isTail, BaseNode.TailStatus isTail,
@Cached("create(thunk.getCallTarget())") DirectCallNode callNode, @Cached("create(function.getCallTarget())") DirectCallNode callNode,
@Cached LoopingCallOptimiserNode loopingCallOptimiserNode) { @Cached LoopingCallOptimiserNode loopingCallOptimiserNode) {
CompilerAsserts.partialEvaluationConstant(isTail); CompilerAsserts.partialEvaluationConstant(isTail);
if (isTail != BaseNode.TailStatus.NOT_TAIL) { if (isTail != BaseNode.TailStatus.NOT_TAIL) {
return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(thunk, state)); return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(function, state));
} else { } else {
try { try {
return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(thunk, state)); return (Stateful) callNode.call(Function.ArgumentsHelper.buildArguments(function, state));
} catch (TailCallException e) { } catch (TailCallException e) {
return loopingCallOptimiserNode.executeDispatch( return loopingCallOptimiserNode.executeDispatch(
e.getFunction(), e.getCallerInfo(), e.getState(), e.getArguments()); e.getFunction(), e.getCallerInfo(), e.getState(), e.getArguments());
@ -71,9 +68,9 @@ public abstract class ThunkExecutorNode extends Node {
} }
} }
@Specialization(replaces = "doCached") @Specialization(replaces = "doCached", guards = "function.isThunk()")
Stateful doUncached( Stateful doUncached(
Thunk thunk, Function function,
Object state, Object state,
BaseNode.TailStatus isTail, BaseNode.TailStatus isTail,
@Cached IndirectCallNode callNode, @Cached IndirectCallNode callNode,
@ -81,16 +78,66 @@ public abstract class ThunkExecutorNode extends Node {
if (isTail != BaseNode.TailStatus.NOT_TAIL) { if (isTail != BaseNode.TailStatus.NOT_TAIL) {
return (Stateful) return (Stateful)
callNode.call( callNode.call(
thunk.getCallTarget(), Function.ArgumentsHelper.buildArguments(thunk, state)); function.getCallTarget(), Function.ArgumentsHelper.buildArguments(function, state));
} else { } else {
try { try {
return (Stateful) return (Stateful)
callNode.call( callNode.call(
thunk.getCallTarget(), Function.ArgumentsHelper.buildArguments(thunk, state)); function.getCallTarget(), Function.ArgumentsHelper.buildArguments(function, state));
} catch (TailCallException e) { } catch (TailCallException e) {
return loopingCallOptimiserNode.executeDispatch( return loopingCallOptimiserNode.executeDispatch(
e.getFunction(), e.getCallerInfo(), e.getState(), e.getArguments()); e.getFunction(), e.getCallerInfo(), e.getState(), e.getArguments());
} }
} }
} }
static InvokeFunctionNode buildInvokeFunctionNode(BaseNode.TailStatus tailStatus) {
var node =
InvokeFunctionNode.build(
new CallArgumentInfo[0],
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.EXECUTE);
node.setTailStatus(tailStatus);
return node;
}
static int numberOfTailStatuses() {
return BaseNode.TailStatus.numberOfValues();
}
@Specialization(
guards = {"!fn.isThunk()", "fn.isFullyApplied()", "isTail == cachedIsTail"},
limit = "numberOfTailStatuses()")
Stateful doCachedFn(
Function fn,
Object state,
BaseNode.TailStatus isTail,
@Cached("isTail") BaseNode.TailStatus cachedIsTail,
@Cached("buildInvokeFunctionNode(cachedIsTail)") InvokeFunctionNode invokeFunctionNode) {
return invokeFunctionNode.execute(fn, null, state, new Object[0]);
}
@Specialization(
guards = {"!fn.isThunk()", "fn.isFullyApplied()"},
replaces = {"doCachedFn"})
Stateful doUncachedFn(
Function fn,
Object state,
BaseNode.TailStatus isTail,
@Cached IndirectInvokeFunctionNode invokeFunctionNode) {
return invokeFunctionNode.execute(
fn,
null,
state,
new Object[0],
new CallArgumentInfo[0],
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.EXECUTE,
isTail);
}
@Fallback
Stateful doOther(Object thunk, Object state, BaseNode.TailStatus isTail) {
return new Stateful(state, thunk);
}
} }

View File

@ -7,7 +7,6 @@ import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.dsl.Suspend;
import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod( @BuiltinMethod(

View File

@ -8,7 +8,6 @@ import org.enso.interpreter.dsl.Suspend;
import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.state.Stateful;

View File

@ -1,32 +0,0 @@
package org.enso.interpreter.node.expression.builtin.function;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(
type = "Function",
name = "call",
description = "Allows function calls to be made explicitly")
public class ExplicitCallFunctionNode extends Node {
private @Child InvokeCallableNode invokeCallableNode;
ExplicitCallFunctionNode() {
invokeCallableNode =
InvokeCallableNode.build(
new CallArgumentInfo[0],
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED);
invokeCallableNode.setTailStatus(BaseNode.TailStatus.TAIL_DIRECT);
}
Stateful execute(VirtualFrame frame, @MonadicState Object state, Function _this) {
return invokeCallableNode.execute(_this, frame, state, new Object[0]);
}
}

View File

@ -5,7 +5,6 @@ import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.data.Array; import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.type.TypesGen;
@BuiltinMethod( @BuiltinMethod(
type = "Meta", type = "Meta",

View File

@ -7,7 +7,6 @@ import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.dsl.Suspend;
import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod( @BuiltinMethod(

View File

@ -6,7 +6,6 @@ import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.dsl.Suspend; import org.enso.interpreter.dsl.Suspend;
import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.control.ThreadInterruptedException; import org.enso.interpreter.runtime.control.ThreadInterruptedException;
import org.enso.interpreter.runtime.state.Stateful; import org.enso.interpreter.runtime.state.Stateful;

View File

@ -15,7 +15,7 @@ import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.callable.CallerInfo;
import org.enso.interpreter.runtime.callable.argument.Thunk; import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.scope.LocalScope; import org.enso.interpreter.runtime.scope.LocalScope;
import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.interpreter.runtime.scope.ModuleScope;
@ -98,7 +98,7 @@ public abstract class EvalNode extends BaseNode {
"parseExpression(callerInfo.getLocalScope(), callerInfo.getModuleScope(), expressionStr)") "parseExpression(callerInfo.getLocalScope(), callerInfo.getModuleScope(), expressionStr)")
RootCallTarget cachedCallTarget, RootCallTarget cachedCallTarget,
@Cached("build()") ThunkExecutorNode thunkExecutorNode) { @Cached("build()") ThunkExecutorNode thunkExecutorNode) {
Thunk thunk = new Thunk(cachedCallTarget, callerInfo.getFrame()); Function thunk = Function.thunk(cachedCallTarget, callerInfo.getFrame());
return thunkExecutorNode.executeThunk(thunk, state, getTailStatus()); return thunkExecutorNode.executeThunk(thunk, state, getTailStatus());
} }
@ -114,7 +114,7 @@ public abstract class EvalNode extends BaseNode {
callerInfo.getLocalScope(), callerInfo.getLocalScope(),
callerInfo.getModuleScope(), callerInfo.getModuleScope(),
toJavaStringNode.execute(expression)); toJavaStringNode.execute(expression));
Thunk thunk = new Thunk(callTarget, callerInfo.getFrame()); Function thunk = Function.thunk(callTarget, callerInfo.getFrame());
return thunkExecutorNode.executeThunk(thunk, state, getTailStatus()); return thunkExecutorNode.executeThunk(thunk, state, getTailStatus());
} }
} }

View File

@ -16,7 +16,6 @@ import org.enso.interpreter.node.expression.builtin.error.CatchPanicMethodGen;
import org.enso.interpreter.node.expression.builtin.error.CaughtPanicConvertToDataflowErrorMethodGen; import org.enso.interpreter.node.expression.builtin.error.CaughtPanicConvertToDataflowErrorMethodGen;
import org.enso.interpreter.node.expression.builtin.error.GetAttachedStackTraceMethodGen; import org.enso.interpreter.node.expression.builtin.error.GetAttachedStackTraceMethodGen;
import org.enso.interpreter.node.expression.builtin.error.ThrowPanicMethodGen; import org.enso.interpreter.node.expression.builtin.error.ThrowPanicMethodGen;
import org.enso.interpreter.node.expression.builtin.function.ExplicitCallFunctionMethodGen;
import org.enso.interpreter.node.expression.builtin.interop.java.AddToClassPathMethodGen; import org.enso.interpreter.node.expression.builtin.interop.java.AddToClassPathMethodGen;
import org.enso.interpreter.node.expression.builtin.interop.java.LookupClassMethodGen; import org.enso.interpreter.node.expression.builtin.interop.java.LookupClassMethodGen;
import org.enso.interpreter.node.expression.builtin.io.GetCwdMethodGen; import org.enso.interpreter.node.expression.builtin.io.GetCwdMethodGen;
@ -191,8 +190,6 @@ public class Builtins {
scope.registerMethod(debug, MethodNames.Debug.EVAL, DebugEvalMethodGen.makeFunction(language)); scope.registerMethod(debug, MethodNames.Debug.EVAL, DebugEvalMethodGen.makeFunction(language));
scope.registerMethod(debug, "breakpoint", DebugBreakpointMethodGen.makeFunction(language)); scope.registerMethod(debug, "breakpoint", DebugBreakpointMethodGen.makeFunction(language));
scope.registerMethod(function, "call", ExplicitCallFunctionMethodGen.makeFunction(language));
scope.registerMethod(any, "to_text", AnyToTextMethodGen.makeFunction(language)); scope.registerMethod(any, "to_text", AnyToTextMethodGen.makeFunction(language));
scope.registerMethod(any, "to_display_text", AnyToDisplayTextMethodGen.makeFunction(language)); scope.registerMethod(any, "to_display_text", AnyToDisplayTextMethodGen.makeFunction(language));

View File

@ -4,7 +4,7 @@ import java.util.Optional;
import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.ExpressionNode;
/** Tracks the specifics about how arguments are defined at the callable definition site. */ /** Tracks the specifics about how arguments are defined at the callable definition site. */
public class ArgumentDefinition { public final class ArgumentDefinition {
/** Represents the mode of passing this argument to the function. */ /** Represents the mode of passing this argument to the function. */
public enum ExecutionMode { public enum ExecutionMode {

View File

@ -3,7 +3,7 @@ package org.enso.interpreter.runtime.callable.argument;
import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.ExpressionNode;
/** Tracks the specifics about how arguments are specified at a call site. */ /** Tracks the specifics about how arguments are specified at a call site. */
public class CallArgument { public final class CallArgument {
private final String name; private final String name;
private final ExpressionNode expression; private final ExpressionNode expression;

View File

@ -13,7 +13,7 @@ import java.util.stream.IntStream;
* Tracks simple information about call-site arguments, used to make processing of caller argument * Tracks simple information about call-site arguments, used to make processing of caller argument
* lists much more simple. * lists much more simple.
*/ */
public class CallArgumentInfo { public final class CallArgumentInfo {
private final String name; private final String name;
/** /**
@ -228,10 +228,10 @@ public class CallArgumentInfo {
* A class that represents the partitioned mapping of the arguments applied to a given callable. * A class that represents the partitioned mapping of the arguments applied to a given callable.
*/ */
public static class ArgumentMapping { public static class ArgumentMapping {
private @CompilationFinal(dimensions = 1) int[] appliedArgumentMapping; private final @CompilationFinal(dimensions = 1) int[] appliedArgumentMapping;
private @CompilationFinal(dimensions = 1) int[] oversaturatedArgumentMapping; private final @CompilationFinal(dimensions = 1) int[] oversaturatedArgumentMapping;
private @CompilationFinal(dimensions = 1) boolean[] isValidAppliedArg; private final @CompilationFinal(dimensions = 1) boolean[] isValidAppliedArg;
private @CompilationFinal(dimensions = 1) boolean[] argumentShouldExecute; private final @CompilationFinal(dimensions = 1) boolean[] argumentShouldExecute;
private final FunctionSchema postApplicationSchema; private final FunctionSchema postApplicationSchema;
/** /**
@ -245,7 +245,7 @@ public class CallArgumentInfo {
* the callable * the callable
* @param postApplicationSchema the schema resulting from applying this mapping * @param postApplicationSchema the schema resulting from applying this mapping
*/ */
public ArgumentMapping( private ArgumentMapping(
int[] appliedArgumentMapping, int[] appliedArgumentMapping,
int[] oversaturatedArgumentMapping, int[] oversaturatedArgumentMapping,
boolean[] isAppliedFlags, boolean[] isAppliedFlags,

View File

@ -1,40 +0,0 @@
package org.enso.interpreter.runtime.callable.argument;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.frame.MaterializedFrame;
/** Runtime representation of a suspended function argument. */
public class Thunk {
private final RootCallTarget callTarget;
private final MaterializedFrame scope;
/**
* Creates a runtime thunk.
*
* @param callTarget the {@link CallTarget} representing the argument's expression
* @param scope the caller scope used for evaluating the {@code callTarget}
*/
public Thunk(RootCallTarget callTarget, MaterializedFrame scope) {
this.callTarget = callTarget;
this.scope = scope;
}
/**
* Returns the call target representing the argument's expression.
*
* @return the call target representing the argument's expression.
*/
public CallTarget getCallTarget() {
return callTarget;
}
/**
* Returns the caller scope.
*
* @return the caller scope used for evaluating this thunk.
*/
public MaterializedFrame getScope() {
return scope;
}
}

View File

@ -24,7 +24,6 @@ import org.enso.interpreter.runtime.callable.CallerInfo;
import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedConversion;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
import org.enso.interpreter.runtime.state.data.EmptyMap; import org.enso.interpreter.runtime.state.data.EmptyMap;
@ -79,6 +78,10 @@ public final class Function implements TruffleObject {
this(callTarget, scope, schema, null, null); this(callTarget, scope, schema, null, null);
} }
public static Function thunk(RootCallTarget callTarget, MaterializedFrame scope) {
return new Function(callTarget, scope, FunctionSchema.THUNK);
}
/** /**
* Creates a Function object from a {@link BuiltinRootNode} and argument definitions. * Creates a Function object from a {@link BuiltinRootNode} and argument definitions.
* *
@ -308,7 +311,7 @@ public final class Function implements TruffleObject {
* @param state the state to execute the thunk with * @param state the state to execute the thunk with
* @return an array containing the necessary information to call an Enso thunk * @return an array containing the necessary information to call an Enso thunk
*/ */
public static Object[] buildArguments(Thunk thunk, Object state) { public static Object[] buildArguments(Function thunk, Object state) {
return new Object[] {thunk.getScope(), null, state, new Object[0]}; return new Object[] {thunk.getScope(), null, state, new Object[0]};
} }
@ -456,4 +459,12 @@ public final class Function implements TruffleObject {
return function; return function;
} }
} }
public boolean isThunk() {
return schema == FunctionSchema.THUNK;
}
public boolean isFullyApplied() {
return schema.isFullyApplied();
}
} }

View File

@ -10,7 +10,7 @@ import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
* Holds the definition site argument information together with information on the partially applied * Holds the definition site argument information together with information on the partially applied
* arguments positions. * arguments positions.
*/ */
public class FunctionSchema { public final class FunctionSchema {
/** Denotes the caller frame access functions with this schema require to run properly. */ /** Denotes the caller frame access functions with this schema require to run properly. */
public enum CallerFrameAccess { public enum CallerFrameAccess {
/** Requires full access to the (materialized) caller frame. */ /** Requires full access to the (materialized) caller frame. */
@ -29,6 +29,8 @@ public class FunctionSchema {
} }
} }
public static final FunctionSchema THUNK = new FunctionSchema();
private final @CompilationFinal(dimensions = 1) ArgumentDefinition[] argumentInfos; private final @CompilationFinal(dimensions = 1) ArgumentDefinition[] argumentInfos;
private final @CompilationFinal(dimensions = 1) boolean[] hasPreApplied; private final @CompilationFinal(dimensions = 1) boolean[] hasPreApplied;
private final @CompilationFinal(dimensions = 1) CallArgumentInfo[] oversaturatedArguments; private final @CompilationFinal(dimensions = 1) CallArgumentInfo[] oversaturatedArguments;
@ -36,6 +38,8 @@ public class FunctionSchema {
private final boolean hasOversaturatedArguments; private final boolean hasOversaturatedArguments;
private final CallerFrameAccess callerFrameAccess; private final CallerFrameAccess callerFrameAccess;
private final boolean isFullyApplied;
/** /**
* Creates an {@link FunctionSchema} instance. * Creates an {@link FunctionSchema} instance.
* *
@ -66,6 +70,7 @@ public class FunctionSchema {
this.hasAnyPreApplied = hasAnyPreApplied; this.hasAnyPreApplied = hasAnyPreApplied;
this.hasOversaturatedArguments = this.oversaturatedArguments.length > 0; this.hasOversaturatedArguments = this.oversaturatedArguments.length > 0;
this.isFullyApplied = isFullyApplied(InvokeCallableNode.DefaultsExecutionMode.EXECUTE);
} }
/** /**
@ -211,4 +216,8 @@ public class FunctionSchema {
} }
return functionIsFullyApplied; return functionIsFullyApplied;
} }
public boolean isFullyApplied() {
return isFullyApplied;
}
} }

View File

@ -3,10 +3,8 @@ package org.enso.interpreter.runtime.type;
import com.oracle.truffle.api.dsl.TypeSystem; import com.oracle.truffle.api.dsl.TypeSystem;
import com.oracle.truffle.api.interop.ArityException; import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.UnsupportedTypeException; import com.oracle.truffle.api.interop.UnsupportedTypeException;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedConversion;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.Function;
@ -40,7 +38,6 @@ import org.enso.polyglot.data.TypeGraph;
Function.class, Function.class,
Atom.class, Atom.class,
AtomConstructor.class, AtomConstructor.class,
Thunk.class,
DataflowError.class, DataflowError.class,
UnresolvedConversion.class, UnresolvedConversion.class,
UnresolvedSymbol.class, UnresolvedSymbol.class,
@ -124,8 +121,6 @@ public class Types {
return TypesGen.asAtom(value).getConstructor().getQualifiedName().toString(); return TypesGen.asAtom(value).getConstructor().getQualifiedName().toString();
} else if (TypesGen.isAtomConstructor(value)) { } else if (TypesGen.isAtomConstructor(value)) {
return TypesGen.asAtomConstructor(value).getQualifiedName().toString(); return TypesGen.asAtomConstructor(value).getQualifiedName().toString();
} else if (TypesGen.isThunk(value)) {
return Constants.THUNK;
} else if (TypesGen.isDataflowError(value)) { } else if (TypesGen.isDataflowError(value)) {
return Constants.ERROR; return Constants.ERROR;
} else if (TypesGen.isUnresolvedSymbol(value) || TypesGen.isUnresolvedConversion(value)) { } else if (TypesGen.isUnresolvedSymbol(value) || TypesGen.isUnresolvedConversion(value)) {

View File

@ -470,23 +470,6 @@ type Function
@Builtin_Type @Builtin_Type
type Function type Function
## Forces evaluation of a fully-applied but not evaluated function.
This is particularly useful when you want to call a function that is
fully applied with default arguments.
This is usually _not_ the correct solution to a problem, and so should be
used sparingly.
> Example
Evaluate a fully applied function to get 4.
example_call =
f (a = 2) = a * a
f.call
call : Any
call = @Builtin_Method "Function.call"
## Generic utilities for interacting with other languages. ## Generic utilities for interacting with other languages.
type Polyglot type Polyglot

View File

@ -1316,6 +1316,8 @@ class IrToTruffle(
*/ */
def processApplication(application: IR.Application): RuntimeExpression = def processApplication(application: IR.Application): RuntimeExpression =
application match { application match {
case IR.Application.Prefix(fn, Nil, true, _, _, _) =>
run(fn)
case IR.Application.Prefix(fn, args, hasDefaultsSuspended, loc, _, _) => case IR.Application.Prefix(fn, args, hasDefaultsSuspended, loc, _, _) =>
val callArgFactory = new CallArgumentProcessor(scope, scopeName) val callArgFactory = new CallArgumentProcessor(scope, scopeName)
@ -1407,7 +1409,6 @@ class IrToTruffle(
name, name,
value, value,
_, _,
shouldBeSuspended,
_, _,
_ _
) => ) =>
@ -1422,12 +1423,7 @@ class IrToTruffle(
case _: IR.Name => false case _: IR.Name => false
case _: IR.Literal.Text => false case _: IR.Literal.Text => false
case _: IR.Literal.Number => false case _: IR.Literal.Number => false
case _ => case _ => true
shouldBeSuspended.getOrElse(
throw new CompilerError(
"Demand analysis information missing from call argument."
)
)
} }
val childScope = if (shouldSuspend) { val childScope = if (shouldSuspend) {

View File

@ -5310,15 +5310,6 @@ object IR {
/** The expression of the argument, if present. */ /** The expression of the argument, if present. */
val value: Expression val value: Expression
/** Whether or not the argument should be suspended at code generation time.
*
* A value of `Some(true)` implies that code generation should suspend the
* argument. A value of `Some(false)` implies that code generation should
* _not_ suspend the argument. A value of [[None]] implies that the
* information is missing.
*/
val shouldBeSuspended: Option[Boolean]
/** @inheritdoc */ /** @inheritdoc */
override def mapExpressions(fn: Expression => Expression): CallArgument override def mapExpressions(fn: Expression => Expression): CallArgument
@ -5340,21 +5331,18 @@ object IR {
* A [[CallArgument]] where the `value` is an [[IR.Name.Blank]] is a * A [[CallArgument]] where the `value` is an [[IR.Name.Blank]] is a
* representation of a lambda shorthand argument. * representation of a lambda shorthand argument.
* *
* @param name the name of the argument being called, if present * @param name the name of the argument being called, if present
* @param value the expression being passed as the argument's value * @param value the expression being passed as the argument's value
* @param location the source location that the node corresponds to * @param location the source location that the node corresponds to
* @param shouldBeSuspended whether or not the argument should be passed * @param passData the pass metadata associated with this node
* suspended
* @param passData the pass metadata associated with this node
* @param diagnostics compiler diagnostics for this node * @param diagnostics compiler diagnostics for this node
*/ */
sealed case class Specified( sealed case class Specified(
override val name: Option[IR.Name], override val name: Option[IR.Name],
override val value: Expression, override val value: Expression,
override val location: Option[IdentifiedLocation], override val location: Option[IdentifiedLocation],
override val shouldBeSuspended: Option[Boolean] = None, override val passData: MetadataStorage = MetadataStorage(),
override val passData: MetadataStorage = MetadataStorage(), override val diagnostics: DiagnosticStorage = DiagnosticStorage()
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
) extends CallArgument ) extends CallArgument
with IRKind.Primitive { with IRKind.Primitive {
override protected var id: Identifier = randomId override protected var id: Identifier = randomId
@ -5375,7 +5363,6 @@ object IR {
name: Option[IR.Name] = name, name: Option[IR.Name] = name,
value: Expression = value, value: Expression = value,
location: Option[IdentifiedLocation] = location, location: Option[IdentifiedLocation] = location,
shouldBeSuspended: Option[Boolean] = shouldBeSuspended,
passData: MetadataStorage = passData, passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics, diagnostics: DiagnosticStorage = diagnostics,
id: Identifier = id id: Identifier = id
@ -5384,7 +5371,6 @@ object IR {
name, name,
value, value,
location, location,
shouldBeSuspended,
passData, passData,
diagnostics diagnostics
) )
@ -5439,7 +5425,6 @@ object IR {
|name = $name, |name = $name,
|value = $value, |value = $value,
|location = $location, |location = $location,
|shouldBeSuspended = $shouldBeSuspended,
|passData = ${this.showPassData}, |passData = ${this.showPassData},
|diagnostics = $diagnostics, |diagnostics = $diagnostics,
|id = $id |id = $id

View File

@ -493,7 +493,7 @@ case object AliasAnalysis extends IRPass {
graph: AliasAnalysis.Graph, graph: AliasAnalysis.Graph,
parentScope: AliasAnalysis.Graph.Scope parentScope: AliasAnalysis.Graph.Scope
): List[IR.CallArgument] = { ): List[IR.CallArgument] = {
args.map { case arg @ IR.CallArgument.Specified(_, expr, _, _, _, _) => args.map { case arg @ IR.CallArgument.Specified(_, expr, _, _, _) =>
val currentScope = expr match { val currentScope = expr match {
case _: IR.Literal => parentScope case _: IR.Literal => parentScope
case _ => parentScope.addChild() case _ => parentScope.addChild()

View File

@ -692,7 +692,7 @@ case object DataflowAnalysis extends IRPass {
info: DependencyInfo info: DependencyInfo
): IR.CallArgument = { ): IR.CallArgument = {
argument match { argument match {
case spec @ IR.CallArgument.Specified(name, value, _, _, _, _) => case spec @ IR.CallArgument.Specified(name, value, _, _, _) =>
val specDep = asStatic(spec) val specDep = asStatic(spec)
val valueDep = asStatic(value) val valueDep = asStatic(value)
info.dependents.updateAt(valueDep, Set(specDep)) info.dependents.updateAt(valueDep, Set(specDep))

View File

@ -169,51 +169,31 @@ case object DemandAnalysis extends IRPass {
name: IR.Name, name: IR.Name,
isInsideCallArgument: Boolean isInsideCallArgument: Boolean
): IR.Expression = { ): IR.Expression = {
val usesLazyTerm = isUsageOfSuspendedTerm(name)
if (isInsideCallArgument) { if (isInsideCallArgument) {
name name
} else { } else {
if (usesLazyTerm) { name match {
val forceLocation = name.location case lit: IR.Name.Literal if isDefined(lit) =>
val newNameLocation = name.location.map(l => l.copy(id = None)) val forceLocation = name.location
val newNameLocation = name.location.map(l => l.copy(id = None))
val newName = name match { val newName = lit.copy(location = newNameLocation)
case lit: IR.Name.Literal => lit.copy(location = newNameLocation) IR.Application.Force(newName, forceLocation)
case ths: IR.Name.This => ths.copy(location = newNameLocation) case _ => name
case here: IR.Name.Here => here.copy(location = newNameLocation)
case special: IR.Name.Special =>
special.copy(location = newNameLocation)
case _: IR.Name.Annotation =>
throw new CompilerError(
"Annotations should not be present by the time demand analysis" +
" runs."
)
case _: IR.Name.MethodReference =>
throw new CompilerError(
"Method references should not be present by the time demand " +
"analysis runs."
)
case _: IR.Name.Qualified =>
throw new CompilerError(
"Qualified names should not be present by the time demand " +
"analysis runs."
)
case err: IR.Error.Resolution => err
case err: IR.Error.Conversion => err
case _: IR.Name.Blank =>
throw new CompilerError(
"Blanks should not be present by the time demand analysis runs."
)
}
IR.Application.Force(newName, forceLocation)
} else {
name
} }
} }
} }
private def isDefined(name: IR.Name): Boolean = {
val aliasInfo = name
.unsafeGetMetadata(
AliasAnalysis,
"Missing alias occurrence information for a name usage"
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
aliasInfo.graph.defLinkFor(aliasInfo.id).isDefined
}
/** Performs demand analysis on an application. /** Performs demand analysis on an application.
* *
* @param application the function application to perform demand analysis on * @param application the function application to perform demand analysis on
@ -227,11 +207,12 @@ case object DemandAnalysis extends IRPass {
): IR.Application = ): IR.Application =
application match { application match {
case pref @ IR.Application.Prefix(fn, args, _, _, _, _) => case pref @ IR.Application.Prefix(fn, args, _, _, _, _) =>
val newFun = fn match {
case n: IR.Name => n
case e => analyseExpression(e, isInsideCallArgument = false)
}
pref.copy( pref.copy(
function = analyseExpression( function = newFun,
fn,
isInsideCallArgument = false
),
arguments = args.map(analyseCallArgument) arguments = args.map(analyseCallArgument)
) )
case force @ IR.Application.Force(target, _, _, _) => case force @ IR.Application.Force(target, _, _, _) =>
@ -261,43 +242,6 @@ case object DemandAnalysis extends IRPass {
) )
} }
/** Determines whether a particular piece of IR represents the usage of a
* suspended term (and hence requires forcing).
*
* @param expr the expression to check
* @return `true` if `expr` represents the usage of a suspended term, `false`
* otherwise
*/
def isUsageOfSuspendedTerm(expr: IR.Expression): Boolean = {
expr match {
case name: IR.Name =>
val aliasInfo = name
.unsafeGetMetadata(
AliasAnalysis,
"Missing alias occurrence information for a name usage"
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
aliasInfo.graph
.defLinkFor(aliasInfo.id)
.flatMap(link => {
aliasInfo.graph
.getOccurrence(link.target)
.getOrElse(
throw new CompilerError(
s"Malformed aliasing link with target ${link.target}"
)
) match {
case AliasAnalysis.Graph.Occurrence.Def(_, _, _, _, isLazy) =>
if (isLazy) Some(true) else None
case _ => None
}
})
.isDefined
case _ => false
}
}
/** Performs demand analysis on a function call argument. /** Performs demand analysis on a function call argument.
* *
* In keeping with the requirement by the runtime to pass all function * In keeping with the requirement by the runtime to pass all function
@ -309,13 +253,12 @@ case object DemandAnalysis extends IRPass {
*/ */
def analyseCallArgument(arg: IR.CallArgument): IR.CallArgument = { def analyseCallArgument(arg: IR.CallArgument): IR.CallArgument = {
arg match { arg match {
case spec @ IR.CallArgument.Specified(_, expr, _, _, _, _) => case spec @ IR.CallArgument.Specified(_, expr, _, _, _) =>
spec.copy( spec.copy(
value = analyseExpression( value = analyseExpression(
expr, expr,
isInsideCallArgument = true isInsideCallArgument = true
), )
shouldBeSuspended = Some(!isUsageOfSuspendedTerm(expr))
) )
} }
} }

View File

@ -271,7 +271,7 @@ case object TailCall extends IRPass {
*/ */
def analyseCallArg(argument: IR.CallArgument): IR.CallArgument = { def analyseCallArg(argument: IR.CallArgument): IR.CallArgument = {
argument match { argument match {
case arg @ IR.CallArgument.Specified(_, expr, _, _, _, _) => case arg @ IR.CallArgument.Specified(_, expr, _, _, _) =>
arg arg
.copy( .copy(
// Note [Call Argument Tail Position] // Note [Call Argument Tail Position]

View File

@ -186,7 +186,7 @@ case object LambdaShorthandToLambda extends IRPass {
args args
.zip(argIsUnderscore) .zip(argIsUnderscore)
.map(updateShorthandArg(_, freshNameSupply)) .map(updateShorthandArg(_, freshNameSupply))
.map { case s @ IR.CallArgument.Specified(_, value, _, _, _, _) => .map { case s @ IR.CallArgument.Specified(_, value, _, _, _) =>
s.copy(value = desugarExpression(value, freshNameSupply)) s.copy(value = desugarExpression(value, freshNameSupply))
} }
@ -301,7 +301,7 @@ case object LambdaShorthandToLambda extends IRPass {
* position is lambda shorthand, otherwise `false` * position is lambda shorthand, otherwise `false`
*/ */
def determineLambdaShorthand(args: List[IR.CallArgument]): List[Boolean] = { def determineLambdaShorthand(args: List[IR.CallArgument]): List[Boolean] = {
args.map { case IR.CallArgument.Specified(_, value, _, _, _, _) => args.map { case IR.CallArgument.Specified(_, value, _, _, _) =>
value match { value match {
case _: IR.Name.Blank => true case _: IR.Name.Blank => true
case _ => false case _ => false
@ -325,7 +325,7 @@ case object LambdaShorthandToLambda extends IRPass {
val isShorthand = argAndIsShorthand._2 val isShorthand = argAndIsShorthand._2
arg match { arg match {
case s @ IR.CallArgument.Specified(_, value, _, _, _, _) => case s @ IR.CallArgument.Specified(_, value, _, _, _) =>
if (isShorthand) { if (isShorthand) {
val newName = freshNameSupply val newName = freshNameSupply
.newName() .newName()
@ -354,7 +354,7 @@ case object LambdaShorthandToLambda extends IRPass {
): Option[IR.DefinitionArgument] = { ): Option[IR.DefinitionArgument] = {
if (isShorthand) { if (isShorthand) {
arg match { arg match {
case IR.CallArgument.Specified(_, value, _, _, passData, diagnostics) => case IR.CallArgument.Specified(_, value, _, passData, diagnostics) =>
// Note [Safe Casting to IR.Name.Literal] // Note [Safe Casting to IR.Name.Literal]
val defArgName = val defArgName =
IR.Name.Literal( IR.Name.Literal(

View File

@ -105,7 +105,7 @@ case object SectionsToBinOp extends IRPass {
case Section.Left(arg, op, loc, passData, diagnostics) => case Section.Left(arg, op, loc, passData, diagnostics) =>
val rightArgName = freshNameSupply.newName() val rightArgName = freshNameSupply.newName()
val rightCallArg = val rightCallArg =
IR.CallArgument.Specified(None, rightArgName, None, None) IR.CallArgument.Specified(None, rightArgName, None)
val rightDefArg = IR.DefinitionArgument.Specified( val rightDefArg = IR.DefinitionArgument.Specified(
rightArgName.duplicate(), rightArgName.duplicate(),
None, None,
@ -117,7 +117,7 @@ case object SectionsToBinOp extends IRPass {
if (arg.value.isInstanceOf[IR.Name.Blank]) { if (arg.value.isInstanceOf[IR.Name.Blank]) {
val leftArgName = freshNameSupply.newName() val leftArgName = freshNameSupply.newName()
val leftCallArg = val leftCallArg =
IR.CallArgument.Specified(None, leftArgName, None, None) IR.CallArgument.Specified(None, leftArgName, None)
val leftDefArg = IR.DefinitionArgument.Specified( val leftDefArg = IR.DefinitionArgument.Specified(
leftArgName.duplicate(), leftArgName.duplicate(),
None, None,
@ -165,7 +165,7 @@ case object SectionsToBinOp extends IRPass {
case Section.Sides(op, loc, passData, diagnostics) => case Section.Sides(op, loc, passData, diagnostics) =>
val leftArgName = freshNameSupply.newName() val leftArgName = freshNameSupply.newName()
val leftCallArg = val leftCallArg =
IR.CallArgument.Specified(None, leftArgName, None, None) IR.CallArgument.Specified(None, leftArgName, None)
val leftDefArg = IR.DefinitionArgument.Specified( val leftDefArg = IR.DefinitionArgument.Specified(
leftArgName.duplicate(), leftArgName.duplicate(),
None, None,
@ -176,7 +176,7 @@ case object SectionsToBinOp extends IRPass {
val rightArgName = freshNameSupply.newName() val rightArgName = freshNameSupply.newName()
val rightCallArg = val rightCallArg =
IR.CallArgument.Specified(None, rightArgName, None, None) IR.CallArgument.Specified(None, rightArgName, None)
val rightDefArg = IR.DefinitionArgument.Specified( val rightDefArg = IR.DefinitionArgument.Specified(
rightArgName.duplicate(), rightArgName.duplicate(),
None, None,
@ -228,7 +228,7 @@ case object SectionsToBinOp extends IRPass {
case Section.Right(op, arg, loc, passData, diagnostics) => case Section.Right(op, arg, loc, passData, diagnostics) =>
val leftArgName = freshNameSupply.newName() val leftArgName = freshNameSupply.newName()
val leftCallArg = val leftCallArg =
IR.CallArgument.Specified(None, leftArgName, None, None) IR.CallArgument.Specified(None, leftArgName, None)
val leftDefArg = val leftDefArg =
IR.DefinitionArgument.Specified( IR.DefinitionArgument.Specified(
leftArgName.duplicate(), leftArgName.duplicate(),
@ -242,7 +242,7 @@ case object SectionsToBinOp extends IRPass {
// Note [Blanks in Sections] // Note [Blanks in Sections]
val rightArgName = freshNameSupply.newName() val rightArgName = freshNameSupply.newName()
val rightCallArg = val rightCallArg =
IR.CallArgument.Specified(None, rightArgName, None, None) IR.CallArgument.Specified(None, rightArgName, None)
val rightDefArg = IR.DefinitionArgument.Specified( val rightDefArg = IR.DefinitionArgument.Specified(
rightArgName.duplicate(), rightArgName.duplicate(),
None, None,

View File

@ -196,7 +196,7 @@ case object TypeFunctions extends IRPass {
*/ */
def resolveCallArgument(arg: IR.CallArgument): IR.CallArgument = { def resolveCallArgument(arg: IR.CallArgument): IR.CallArgument = {
arg match { arg match {
case spec @ IR.CallArgument.Specified(_, value, _, _, _, _) => case spec @ IR.CallArgument.Specified(_, value, _, _, _) =>
spec.copy( spec.copy(
value = resolveExpression(value) value = resolveExpression(value)
) )
@ -218,8 +218,8 @@ case object TypeFunctions extends IRPass {
*/ */
def isValidCallArg(arg: IR.CallArgument): Boolean = { def isValidCallArg(arg: IR.CallArgument): Boolean = {
arg match { arg match {
case IR.CallArgument.Specified(name, _, _, susp, _, _) => case IR.CallArgument.Specified(name, _, _, _, _) =>
name.isEmpty && (susp.isEmpty || susp.get) name.isEmpty
} }
} }
} }

View File

@ -122,7 +122,7 @@ case object VectorLiterals extends IRPass {
vec.duplicate(), vec.duplicate(),
List( List(
IR.CallArgument IR.CallArgument
.Specified(None, trans.copy(location = None), None, None) .Specified(None, trans.copy(location = None), None)
), ),
false, false,
trans.location trans.location

View File

@ -936,9 +936,8 @@ class DataflowAnalysisTest extends CompilerTest {
fn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified] fn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
val fnArgY = val fnArgY =
fn.arguments(1).asInstanceOf[IR.DefinitionArgument.Specified] fn.arguments(1).asInstanceOf[IR.DefinitionArgument.Specified]
val fnArgYDefault = fnArgY.defaultValue.get.asInstanceOf[IR.Name.Literal] val fnBody = fn.body.asInstanceOf[IR.Application.Prefix]
val fnBody = fn.body.asInstanceOf[IR.Application.Prefix] val plusFn = fnBody.function.asInstanceOf[IR.Name.Literal]
val plusFn = fnBody.function.asInstanceOf[IR.Name.Literal]
val plusArgX = val plusArgX =
fnBody.arguments.head.asInstanceOf[IR.CallArgument.Specified] fnBody.arguments.head.asInstanceOf[IR.CallArgument.Specified]
val plusArgXExpr = plusArgX.value.asInstanceOf[IR.Name.Literal] val plusArgXExpr = plusArgX.value.asInstanceOf[IR.Name.Literal]
@ -947,16 +946,15 @@ class DataflowAnalysisTest extends CompilerTest {
val plusArgYExpr = plusArgY.value.asInstanceOf[IR.Name.Literal] val plusArgYExpr = plusArgY.value.asInstanceOf[IR.Name.Literal]
// Identifiers // Identifiers
val fnId = mkStaticDep(fn.getId) val fnId = mkStaticDep(fn.getId)
val fnArgXId = mkStaticDep(fnArgX.getId) val fnArgXId = mkStaticDep(fnArgX.getId)
val fnArgYId = mkStaticDep(fnArgY.getId) val fnArgYId = mkStaticDep(fnArgY.getId)
val fnArgYDefaultId = mkStaticDep(fnArgYDefault.getId) val fnBodyId = mkStaticDep(fnBody.getId)
val fnBodyId = mkStaticDep(fnBody.getId) val plusFnId = mkStaticDep(plusFn.getId)
val plusFnId = mkStaticDep(plusFn.getId) val plusArgXId = mkStaticDep(plusArgX.getId)
val plusArgXId = mkStaticDep(plusArgX.getId) val plusArgXExprId = mkStaticDep(plusArgXExpr.getId)
val plusArgXExprId = mkStaticDep(plusArgXExpr.getId) val plusArgYId = mkStaticDep(plusArgY.getId)
val plusArgYId = mkStaticDep(plusArgY.getId) val plusArgYExprId = mkStaticDep(plusArgYExpr.getId)
val plusArgYExprId = mkStaticDep(plusArgYExpr.getId)
// Dynamic Symbols // Dynamic Symbols
val plusSym = mkDynamicDep("+") val plusSym = mkDynamicDep("+")
@ -967,11 +965,7 @@ class DataflowAnalysisTest extends CompilerTest {
// The Tests for dependents // The Tests for dependents
dependents.getDirect(fnId) should not be defined dependents.getDirect(fnId) should not be defined
dependents.getDirect(fnArgXId) shouldEqual Some(
Set(plusArgXExprId, fnArgYDefaultId)
)
dependents.getDirect(fnArgYId) shouldEqual Some(Set(plusArgYExprId)) dependents.getDirect(fnArgYId) shouldEqual Some(Set(plusArgYExprId))
dependents.getDirect(fnArgYDefaultId) shouldEqual Some(Set(fnArgYId))
dependents.getDirect(fnBodyId) shouldEqual Some(Set(fnId)) dependents.getDirect(fnBodyId) shouldEqual Some(Set(fnId))
dependents.getDirect(plusSym) shouldEqual Some(Set(plusFnId)) dependents.getDirect(plusSym) shouldEqual Some(Set(plusFnId))
dependents.getDirect(plusArgXId) shouldEqual Some(Set(fnBodyId)) dependents.getDirect(plusArgXId) shouldEqual Some(Set(fnBodyId))
@ -982,8 +976,6 @@ class DataflowAnalysisTest extends CompilerTest {
// The Tests for dependencies // The Tests for dependencies
dependencies.getDirect(fnId) shouldEqual Some(Set(fnBodyId)) dependencies.getDirect(fnId) shouldEqual Some(Set(fnBodyId))
dependencies.getDirect(fnArgXId) shouldEqual None dependencies.getDirect(fnArgXId) shouldEqual None
dependencies.getDirect(fnArgYId) shouldEqual Some(Set(fnArgYDefaultId))
dependencies.getDirect(fnArgYDefaultId) shouldEqual Some(Set(fnArgXId))
dependencies.getDirect(fnBodyId) shouldEqual Some( dependencies.getDirect(fnBodyId) shouldEqual Some(
Set(plusFnId, plusArgXId, plusArgYId) Set(plusFnId, plusArgXId, plusArgYId)
) )
@ -1100,25 +1092,15 @@ class DataflowAnalysisTest extends CompilerTest {
val lam = ir.asInstanceOf[IR.Function.Lambda] val lam = ir.asInstanceOf[IR.Function.Lambda]
val argX = val argX =
lam.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified] lam.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
val xUse = lam.body.asInstanceOf[IR.Name.Literal]
// The IDs // The IDs
val lamId = mkStaticDep(lam.getId)
val argXId = mkStaticDep(argX.getId) val argXId = mkStaticDep(argX.getId)
val xUseId = mkStaticDep(xUse.getId)
// The info // The info
val dependents = depInfo.dependents
val dependencies = depInfo.dependencies val dependencies = depInfo.dependencies
// The test for dependents
dependents.getDirect(argXId) shouldEqual Some(Set(xUseId))
dependents.getDirect(xUseId) shouldEqual Some(Set(lamId))
// The test for dependencies // The test for dependencies
dependencies.getDirect(argXId) shouldEqual None dependencies.getDirect(argXId) shouldEqual None
dependencies.getDirect(xUseId) shouldEqual Some(Set(argXId))
dependencies.getDirect(lamId) shouldEqual Some(Set(xUseId))
} }
"work properly for blocks" in { "work properly for blocks" in {
@ -1136,14 +1118,12 @@ class DataflowAnalysisTest extends CompilerTest {
val xBind = block.expressions.head.asInstanceOf[IR.Expression.Binding] val xBind = block.expressions.head.asInstanceOf[IR.Expression.Binding]
val xBindName = xBind.name.asInstanceOf[IR.Name.Literal] val xBindName = xBind.name.asInstanceOf[IR.Name.Literal]
val xBindExpr = xBind.expression.asInstanceOf[IR.Literal.Number] val xBindExpr = xBind.expression.asInstanceOf[IR.Literal.Number]
val xUse = block.returnValue.asInstanceOf[IR.Name.Literal]
// The IDs // The IDs
val blockId = mkStaticDep(block.getId) val blockId = mkStaticDep(block.getId)
val xBindId = mkStaticDep(xBind.getId) val xBindId = mkStaticDep(xBind.getId)
val xBindNameId = mkStaticDep(xBindName.getId) val xBindNameId = mkStaticDep(xBindName.getId)
val xBindExprId = mkStaticDep(xBindExpr.getId) val xBindExprId = mkStaticDep(xBindExpr.getId)
val xUseId = mkStaticDep(xUse.getId)
// The info // The info
val dependents = depInfo.dependents val dependents = depInfo.dependents
@ -1151,14 +1131,10 @@ class DataflowAnalysisTest extends CompilerTest {
// The test for dependents // The test for dependents
dependents.getDirect(blockId) should not be defined dependents.getDirect(blockId) should not be defined
dependents.getDirect(xBindId) shouldEqual Some(Set(xUseId))
dependents.getDirect(xBindNameId) shouldEqual Some(Set(xBindId)) dependents.getDirect(xBindNameId) shouldEqual Some(Set(xBindId))
dependents.getDirect(xBindExprId) shouldEqual Some(Set(xBindId)) dependents.getDirect(xBindExprId) shouldEqual Some(Set(xBindId))
dependents.getDirect(xUseId) shouldEqual Some(Set(blockId))
// The test for dependencies // The test for dependencies
dependencies.getDirect(blockId) shouldEqual Some(Set(xUseId))
dependencies.getDirect(xUseId) shouldEqual Some(Set(xBindId))
dependencies.getDirect(xBindId) shouldEqual Some( dependencies.getDirect(xBindId) shouldEqual Some(
Set(xBindNameId, xBindExprId) Set(xBindNameId, xBindExprId)
) )
@ -1202,47 +1178,6 @@ class DataflowAnalysisTest extends CompilerTest {
dependencies.getDirect(bindingExprId) shouldEqual None dependencies.getDirect(bindingExprId) shouldEqual None
} }
"work properly for undefined variables" in {
implicit val inlineContext: InlineContext = mkInlineContext
val ir =
"""
|x = undefined
|""".stripMargin.preprocessExpression.get.analyse
val depInfo = ir.getMetadata(DataflowAnalysis).get
val binding = ir.asInstanceOf[IR.Expression.Binding]
val bindingName = binding.name.asInstanceOf[IR.Name.Literal]
val bindingExpr = binding.expression.asInstanceOf[IR.Error.Resolution]
val undefinedName = bindingExpr.originalName
// The IDs
val bindingId = mkStaticDep(binding.getId)
val bindingNameId = mkStaticDep(bindingName.getId)
val bindingExprId = mkStaticDep(bindingExpr.getId)
val undefinedNameId = mkDynamicDep(undefinedName.name)
// The info
val dependents = depInfo.dependents
val dependencies = depInfo.dependencies
// The tests for dependents
dependents.getDirect(bindingId) should not be defined
dependents.getDirect(bindingNameId) shouldEqual Some(Set(bindingId))
dependents.getDirect(bindingExprId) shouldEqual Some(Set(bindingId))
dependents.getDirect(undefinedNameId) shouldEqual Some(Set(bindingExprId))
// The tests for dependencies
dependencies.getDirect(bindingId) shouldEqual Some(
Set(bindingNameId, bindingExprId)
)
dependencies.getDirect(bindingNameId) shouldEqual None
dependencies.getDirect(bindingExprId) shouldEqual Some(
Set(undefinedNameId)
)
}
"work properly for undefined variables in expressions" in { "work properly for undefined variables in expressions" in {
implicit val inlineContext: InlineContext = mkInlineContext implicit val inlineContext: InlineContext = mkInlineContext
@ -1331,7 +1266,6 @@ class DataflowAnalysisTest extends CompilerTest {
val vector = callArg.value val vector = callArg.value
.asInstanceOf[IR.Application.Literal.Sequence] .asInstanceOf[IR.Application.Literal.Sequence]
val xDefId = mkStaticDep(ir.arguments(0).getId)
val xUseId = mkStaticDep(vector.items(0).getId) val xUseId = mkStaticDep(vector.items(0).getId)
val yId = mkStaticDep(vector.items(1).getId) val yId = mkStaticDep(vector.items(1).getId)
val litId = mkStaticDep(vector.items(2).getId) val litId = mkStaticDep(vector.items(2).getId)
@ -1345,7 +1279,6 @@ class DataflowAnalysisTest extends CompilerTest {
val dependencies = depInfo.dependencies val dependencies = depInfo.dependencies
// Tests for dependents // Tests for dependents
dependents.getDirect(xDefId) shouldEqual Some(Set(xUseId))
dependents.getDirect(xUseId) shouldEqual Some(Set(vecId)) dependents.getDirect(xUseId) shouldEqual Some(Set(vecId))
dependents.getDirect(yId) shouldEqual Some(Set(vecId)) dependents.getDirect(yId) shouldEqual Some(Set(vecId))
dependents.getDirect(litId) shouldEqual Some(Set(vecId)) dependents.getDirect(litId) shouldEqual Some(Set(vecId))
@ -1397,9 +1330,7 @@ class DataflowAnalysisTest extends CompilerTest {
caseBinding.expression.asInstanceOf[IR.Application.Prefix] caseBinding.expression.asInstanceOf[IR.Application.Prefix]
val caseBindingName = caseBinding.name.asInstanceOf[IR.Name.Literal] val caseBindingName = caseBinding.name.asInstanceOf[IR.Name.Literal]
val caseExpr = caseBlock.returnValue.asInstanceOf[IR.Case.Expr] val caseExpr = caseBlock.returnValue.asInstanceOf[IR.Case.Expr]
val scrutinee = caseExpr.scrutinee.asInstanceOf[IR.Name.Literal]
val consBranch = caseExpr.branches.head val consBranch = caseExpr.branches.head
val catchAllbranch = caseExpr.branches(1)
val consBranchPattern = val consBranchPattern =
consBranch.pattern.asInstanceOf[Pattern.Constructor] consBranch.pattern.asInstanceOf[Pattern.Constructor]
@ -1432,9 +1363,7 @@ class DataflowAnalysisTest extends CompilerTest {
val caseBindingExprId = mkStaticDep(caseBindingExpr.getId) val caseBindingExprId = mkStaticDep(caseBindingExpr.getId)
val caseBindingNameId = mkStaticDep(caseBindingName.getId) val caseBindingNameId = mkStaticDep(caseBindingName.getId)
val caseExprId = mkStaticDep(caseExpr.getId) val caseExprId = mkStaticDep(caseExpr.getId)
val scrutineeId = mkStaticDep(scrutinee.getId)
val consBranchId = mkStaticDep(consBranch.getId) val consBranchId = mkStaticDep(consBranch.getId)
val catchAllBranchId = mkStaticDep(catchAllbranch.getId)
val consBranchPatternId = mkStaticDep(consBranchPattern.getId) val consBranchPatternId = mkStaticDep(consBranchPattern.getId)
val consBranchPatternConsId = mkStaticDep(consBranchPatternCons.getId) val consBranchPatternConsId = mkStaticDep(consBranchPatternCons.getId)
@ -1457,8 +1386,6 @@ class DataflowAnalysisTest extends CompilerTest {
// Tests for dependents // Tests for dependents
dependents.getDirect(caseBlockId) should not be defined dependents.getDirect(caseBlockId) should not be defined
dependents.getDirect(caseExprId) shouldEqual Some(Set(caseBlockId)) dependents.getDirect(caseExprId) shouldEqual Some(Set(caseBlockId))
dependents.getDirect(scrutineeId) shouldEqual Some(Set(caseExprId))
dependents.getDirect(caseBindingId) shouldEqual Some(Set(scrutineeId))
dependents.getDirect(caseBindingExprId) shouldEqual Some( dependents.getDirect(caseBindingExprId) shouldEqual Some(
Set(caseBindingId) Set(caseBindingId)
) )
@ -1499,10 +1426,6 @@ class DataflowAnalysisTest extends CompilerTest {
dependencies.getDirect(caseBindingId) shouldEqual Some( dependencies.getDirect(caseBindingId) shouldEqual Some(
Set(caseBindingNameId, caseBindingExprId) Set(caseBindingNameId, caseBindingExprId)
) )
dependencies.getDirect(caseExprId) shouldEqual Some(
Set(scrutineeId, consBranchId, catchAllBranchId)
)
dependencies.getDirect(scrutineeId) shouldEqual Some(Set(caseBindingId))
dependencies.getDirect(consBranchId) shouldEqual Some( dependencies.getDirect(consBranchId) shouldEqual Some(
Set(consBranchPatternId, consBranchExpressionId) Set(consBranchPatternId, consBranchExpressionId)
) )

View File

@ -157,79 +157,9 @@ class DemandAnalysisTest extends CompilerTest {
vec.items(0) shouldBe an[IR.Application.Force] vec.items(0) shouldBe an[IR.Application.Force]
vec.items(1) shouldBe an[IR.Application.Force] vec.items(1) shouldBe an[IR.Application.Force]
vec.items(2) shouldBe an[IR.Name] vec.items(2) shouldBe an[IR.Application.Force]
} }
"be marked as not to suspend during codegen when passed to a function" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
|~x -> ~y -> z -> foo x y z
|""".stripMargin.preprocessExpression.get.analyse
val app = ir
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Application.Prefix]
app.arguments.head
.asInstanceOf[IR.CallArgument.Specified]
.shouldBeSuspended shouldEqual Some(false)
app
.arguments(1)
.asInstanceOf[IR.CallArgument.Specified]
.shouldBeSuspended shouldEqual Some(false)
}
}
"Non-suspended arguments" should {
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""x -> y ->
| a = x
| foo x a
|""".stripMargin.preprocessExpression.get.analyse
val body = ir
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Expression.Block]
"be left alone by demand analysis" in {
body.expressions.head
.asInstanceOf[IR.Expression.Binding]
.expression shouldBe an[IR.Name]
body.returnValue
.asInstanceOf[IR.Application.Prefix]
.arguments
.head
.asInstanceOf[IR.CallArgument.Specified]
.value shouldBe an[IR.Name]
}
"be marked for suspension during codegen when passed to a function" in {
val xArg = body.returnValue
.asInstanceOf[IR.Application.Prefix]
.arguments
.head
.asInstanceOf[IR.CallArgument.Specified]
val aArg = body.returnValue
.asInstanceOf[IR.Application.Prefix]
.arguments(1)
.asInstanceOf[IR.CallArgument.Specified]
xArg.value shouldBe an[IR.Name]
xArg.shouldBeSuspended shouldEqual Some(true)
aArg.value shouldBe an[IR.Name]
aArg.shouldBeSuspended shouldEqual Some(true)
}
} }
"Suspended blocks" should { "Suspended blocks" should {
@ -280,28 +210,6 @@ class DemandAnalysisTest extends CompilerTest {
.value shouldBe an[IR.Name] .value shouldBe an[IR.Name]
} }
"be marked as not to suspend during codegen when passed to a function" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
|x ->
| blck =
| foo a b
| bar blck
|""".stripMargin.preprocessExpression.get.analyse
ir.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Expression.Block]
.returnValue
.asInstanceOf[IR.Application.Prefix]
.arguments
.head
.asInstanceOf[IR.CallArgument.Specified]
.shouldBeSuspended shouldEqual Some(false)
}
"force terms in blocks passed directly as arguments" in { "force terms in blocks passed directly as arguments" in {
implicit val ctx: ModuleContext = mkModuleContext implicit val ctx: ModuleContext = mkModuleContext

View File

@ -32,8 +32,7 @@ class CurryingTest extends InterpreterTest {
| fn1 = fn ... | fn1 = fn ...
| fn2 = fn1 1 2 ... | fn2 = fn1 1 2 ...
| fn3 = fn2 3 ... | fn3 = fn2 3 ...
| | fn3
| fn3.call
|""".stripMargin |""".stripMargin
eval(code) shouldEqual 26 eval(code) shouldEqual 26
@ -45,7 +44,7 @@ class CurryingTest extends InterpreterTest {
|main = |main =
| fn = w -> x -> (y = 10) -> (z = 20) -> w + x + y + z | fn = w -> x -> (y = 10) -> (z = 20) -> w + x + y + z
| |
| fn.call 1 2 (z = 10) | fn 1 2 (z = 10)
|""".stripMargin |""".stripMargin
eval(code) shouldEqual 23 eval(code) shouldEqual 23
@ -74,11 +73,63 @@ class CurryingTest extends InterpreterTest {
| fn1 = Nothing.fn ... | fn1 = Nothing.fn ...
| fn2 = fn1 1 2 ... | fn2 = fn1 1 2 ...
| fn3 = fn2 3 ... | fn3 = fn2 3 ...
| | fn3
| fn3.call
|""".stripMargin |""".stripMargin
eval(code) shouldEqual 26 eval(code) shouldEqual 26
} }
"automatically force functions with all-defaulted arguments" in {
val code =
"""main =
| foo (x=1) = x
| foo + 1
|""".stripMargin
eval(code) shouldEqual 2
}
"allow to pass suspended functions in arguments with `...`" in {
val code =
"""main =
| foo f = f 2
| bar x=1 = x + 1
| foo (bar ...)
|""".stripMargin
eval(code) shouldEqual 3
}
"allow to pass suspended functions in arguments with `...` but still auto-execute them" in {
val code =
"""main =
| foo f = f
| bar x=1 = x + 1
| foo (bar ...)
|""".stripMargin
eval(code) shouldEqual 2
}
"should handle a defaulted-suspended combo" in {
val code =
"""main =
| foo ~f = f
| bar x=1 = x + 1
| foo (bar ...)
|""".stripMargin
eval(code) shouldEqual 2
}
"should make `...` an identity on Atom Constructors" in {
val code =
"""
|type My_Atom x=1
|
|My_Atom.my_static = "hello"
|
|main =
| (My_Atom ...).my_static
|""".stripMargin
eval(code) shouldEqual "hello"
}
} }
} }

View File

@ -85,7 +85,7 @@ class LambdaShorthandArgsTest extends InterpreterTest {
""" """
|main = |main =
| f = (x = _) -> x | f = (x = _) -> x
| g = f.call | g = f
| h = _ | h = _
| res1 = g 10 | res1 = g 10
| res2 = h 10 | res2 = h 10