mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 09:22:41 +03:00
Methods on Any type and pattern matches on arbitrary values (#259)
This commit is contained in:
parent
6569cc5cb0
commit
698a9425ab
@ -11,6 +11,7 @@ public class Constants {
|
||||
|
||||
public static final String THIS_ARGUMENT_NAME = "this";
|
||||
public static final String SCOPE_SEPARATOR = ".";
|
||||
public static final String ANY_TYPE_NAME = "Any";
|
||||
|
||||
/** Cache sizes for different AST nodes. */
|
||||
public static class CacheSizes {
|
||||
|
@ -26,12 +26,7 @@ import org.enso.interpreter.node.callable.ForceNodeGen;
|
||||
import org.enso.interpreter.node.callable.argument.ReadArgumentNode;
|
||||
import org.enso.interpreter.node.callable.function.CreateFunctionNode;
|
||||
import org.enso.interpreter.node.callable.function.FunctionBodyNode;
|
||||
import org.enso.interpreter.node.controlflow.CaseNode;
|
||||
import org.enso.interpreter.node.controlflow.ConstructorCaseNode;
|
||||
import org.enso.interpreter.node.controlflow.DefaultFallbackNode;
|
||||
import org.enso.interpreter.node.controlflow.FallbackNode;
|
||||
import org.enso.interpreter.node.controlflow.IfZeroNode;
|
||||
import org.enso.interpreter.node.controlflow.MatchNode;
|
||||
import org.enso.interpreter.node.controlflow.*;
|
||||
import org.enso.interpreter.node.expression.constant.ConstructorNode;
|
||||
import org.enso.interpreter.node.expression.constant.DynamicSymbolNode;
|
||||
import org.enso.interpreter.node.expression.literal.IntegerLiteralNode;
|
||||
@ -395,7 +390,7 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
|
||||
.map(fb -> (CaseNode) new FallbackNode(fb.visit(this)))
|
||||
.orElseGet(DefaultFallbackNode::new);
|
||||
|
||||
return new MatchNode(targetNode, cases, fallbackNode);
|
||||
return MatchNodeGen.create(cases, fallbackNode, targetNode);
|
||||
}
|
||||
|
||||
/* Note [Pattern Match Fallbacks]
|
||||
|
@ -90,10 +90,6 @@ public class ModuleScopeExpressionFactory implements AstGlobalScopeVisitor<Expre
|
||||
});
|
||||
|
||||
for (AstMethodDef method : bindings) {
|
||||
AtomConstructor constructor =
|
||||
moduleScope
|
||||
.getConstructor(method.typeName())
|
||||
.orElseThrow(() -> new VariableDoesNotExistException(method.typeName()));
|
||||
ExpressionFactory expressionFactory =
|
||||
new ExpressionFactory(
|
||||
language,
|
||||
@ -105,7 +101,16 @@ public class ModuleScopeExpressionFactory implements AstGlobalScopeVisitor<Expre
|
||||
funNode.markTail();
|
||||
Function function =
|
||||
new Function(funNode.getCallTarget(), null, new ArgumentSchema(funNode.getArgs()));
|
||||
moduleScope.registerMethod(constructor, method.methodName(), function);
|
||||
|
||||
if (method.typeName().equals(Constants.ANY_TYPE_NAME)) {
|
||||
moduleScope.registerMethodForAny(method.methodName(), function);
|
||||
} else {
|
||||
AtomConstructor constructor =
|
||||
moduleScope
|
||||
.getConstructor(method.typeName())
|
||||
.orElseThrow(() -> new VariableDoesNotExistException(method.typeName()));
|
||||
moduleScope.registerMethod(constructor, method.methodName(), function);
|
||||
}
|
||||
}
|
||||
|
||||
ExpressionFactory factory = new ExpressionFactory(this.language, moduleScope);
|
||||
|
@ -44,8 +44,9 @@ public class ApplicationNode extends ExpressionNode {
|
||||
Arrays.stream(callArguments).map(CallArgumentInfo::new).toArray(CallArgumentInfo[]::new);
|
||||
|
||||
this.callable = callable;
|
||||
boolean argumentsArePreExecuted = false;
|
||||
this.invokeCallableNode =
|
||||
InvokeCallableNodeGen.create(argSchema, hasDefaultsSuspended);
|
||||
InvokeCallableNodeGen.create(argSchema, hasDefaultsSuspended, argumentsArePreExecuted);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,12 +13,9 @@ import org.enso.interpreter.node.callable.argument.sorter.ArgumentSorterNodeGen;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||
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.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.error.MethodDoesNotExistException;
|
||||
import org.enso.interpreter.runtime.error.NotInvokableException;
|
||||
import org.enso.interpreter.runtime.type.TypesGen;
|
||||
|
||||
/**
|
||||
* This class is responsible for performing the actual invocation of a given callable with its
|
||||
@ -36,6 +33,8 @@ public abstract class InvokeCallableNode extends BaseNode {
|
||||
private final boolean canApplyThis;
|
||||
private final int thisArgumentPosition;
|
||||
|
||||
private final boolean argumentsArePreExecuted;
|
||||
|
||||
private final ConditionProfile methodCalledOnNonAtom = ConditionProfile.createCountingProfile();
|
||||
|
||||
/**
|
||||
@ -44,8 +43,11 @@ public abstract class InvokeCallableNode extends BaseNode {
|
||||
* @param schema a description of the arguments being applied to the callable
|
||||
* @param hasDefaultsSuspended whether or not the invocation has the callable's default arguments
|
||||
* (if any) suspended
|
||||
* @param argumentsArePreExecuted whether the arguments will be provided as thunks or fully
|
||||
* computed values
|
||||
*/
|
||||
public InvokeCallableNode(CallArgumentInfo[] schema, boolean hasDefaultsSuspended) {
|
||||
public InvokeCallableNode(
|
||||
CallArgumentInfo[] schema, boolean hasDefaultsSuspended, boolean argumentsArePreExecuted) {
|
||||
boolean appliesThis = false;
|
||||
int idx = 0;
|
||||
for (; idx < schema.length; idx++) {
|
||||
@ -61,7 +63,8 @@ public abstract class InvokeCallableNode extends BaseNode {
|
||||
this.canApplyThis = appliesThis;
|
||||
this.thisArgumentPosition = idx;
|
||||
|
||||
boolean argumentsArePreExecuted = false;
|
||||
this.argumentsArePreExecuted = argumentsArePreExecuted;
|
||||
|
||||
this.argumentSorter =
|
||||
ArgumentSorterNodeGen.create(schema, hasDefaultsSuspended, argumentsArePreExecuted);
|
||||
this.methodResolverNode = MethodResolverNodeGen.create();
|
||||
@ -102,21 +105,16 @@ public abstract class InvokeCallableNode extends BaseNode {
|
||||
@Specialization
|
||||
public Object invokeDynamicSymbol(UnresolvedSymbol symbol, Object[] arguments) {
|
||||
if (canApplyThis) {
|
||||
if (thisExecutor == null) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
thisExecutor = ThunkExecutorNodeGen.create(false);
|
||||
}
|
||||
|
||||
Object selfArgument =
|
||||
thisExecutor.executeThunk(((Thunk) arguments[thisArgumentPosition]));
|
||||
|
||||
if (methodCalledOnNonAtom.profile(TypesGen.isAtom(selfArgument))) {
|
||||
Atom self = (Atom) selfArgument;
|
||||
Function function = methodResolverNode.execute(symbol, self);
|
||||
return this.argumentSorter.execute(function, arguments);
|
||||
} else {
|
||||
throw new MethodDoesNotExistException(selfArgument, symbol.getName(), this);
|
||||
Object selfArgument = arguments[thisArgumentPosition];
|
||||
if (!argumentsArePreExecuted) {
|
||||
if (thisExecutor == null) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
thisExecutor = ThunkExecutorNodeGen.create(false);
|
||||
}
|
||||
selfArgument = thisExecutor.executeThunk((Thunk) selfArgument);
|
||||
}
|
||||
Function function = methodResolverNode.execute(symbol, selfArgument);
|
||||
return this.argumentSorter.execute(function, arguments);
|
||||
} else {
|
||||
throw new RuntimeException("Currying without `this` argument is not yet supported.");
|
||||
}
|
||||
|
@ -14,45 +14,56 @@ import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.error.MethodDoesNotExistException;
|
||||
|
||||
/**
|
||||
* A node performing lookups of method definitions. Uses a polymorphic inline cache to ensure the
|
||||
* best performance.
|
||||
* A node performing lookups of method definitions.
|
||||
*
|
||||
* <p>Uses a polymorphic inline cache to ensure the best performance.
|
||||
*
|
||||
* <p>The dispatch algorithm works by matching the kind of value the method is requested for and
|
||||
* delegating to the proper lookup method of {@link UnresolvedSymbol}.
|
||||
*/
|
||||
public abstract class MethodResolverNode extends Node {
|
||||
|
||||
/**
|
||||
* DSL method to generate the actual cached code. Uses Cached arguments and performs all the logic
|
||||
* through the DSL. Not for manual use.
|
||||
*/
|
||||
@Specialization(guards = "isValidCache(symbol, cachedSymbol, atom, cachedConstructor)")
|
||||
public Function resolveCached(
|
||||
UnresolvedSymbol symbol,
|
||||
Atom atom,
|
||||
@CachedContext(Language.class) TruffleLanguage.ContextReference<Context> contextRef,
|
||||
@Cached("symbol") UnresolvedSymbol cachedSymbol,
|
||||
@Cached("atom.getConstructor()") AtomConstructor cachedConstructor,
|
||||
@Cached("resolveMethod(cachedConstructor, cachedSymbol)") Function function) {
|
||||
return function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for this node.
|
||||
*
|
||||
* @param symbol Method name to resolve.
|
||||
* @param atom Object for which to resolve the method.
|
||||
* @param self Object for which to resolve the method.
|
||||
* @return Resolved method.
|
||||
*/
|
||||
public abstract Function execute(UnresolvedSymbol symbol, Atom atom);
|
||||
public abstract Function execute(UnresolvedSymbol symbol, Object self);
|
||||
|
||||
/**
|
||||
* Handles the actual method lookup. Not for manual use.
|
||||
*
|
||||
* @param cons Type for which to resolve the method.
|
||||
* @param symbol symbol representing the method to resolve
|
||||
* @return Resolved method definition.
|
||||
*/
|
||||
public Function resolveMethod(
|
||||
AtomConstructor cons,
|
||||
UnresolvedSymbol symbol) {
|
||||
@Specialization(guards = "isValidAtomCache(symbol, cachedSymbol, atom, cachedConstructor)")
|
||||
Function resolveAtomCached(
|
||||
UnresolvedSymbol symbol,
|
||||
Atom atom,
|
||||
@CachedContext(Language.class) TruffleLanguage.ContextReference<Context> contextRef,
|
||||
@Cached("symbol") UnresolvedSymbol cachedSymbol,
|
||||
@Cached("atom.getConstructor()") AtomConstructor cachedConstructor,
|
||||
@Cached("resolveAtomMethod(cachedConstructor, cachedSymbol)") Function function) {
|
||||
return function;
|
||||
}
|
||||
|
||||
@Specialization(guards = "cachedSymbol == symbol")
|
||||
Function resolveNumberCached(
|
||||
UnresolvedSymbol symbol,
|
||||
long self,
|
||||
@CachedContext(Language.class) TruffleLanguage.ContextReference<Context> contextReference,
|
||||
@Cached("symbol") UnresolvedSymbol cachedSymbol,
|
||||
@Cached("resolveNumberMethod(cachedSymbol)") Function function) {
|
||||
return function;
|
||||
}
|
||||
|
||||
@Specialization(guards = "cachedSymbol == symbol")
|
||||
Function resolveFunctionCached(
|
||||
UnresolvedSymbol symbol,
|
||||
Function self,
|
||||
@CachedContext(Language.class) TruffleLanguage.ContextReference<Context> contextReference,
|
||||
@Cached("symbol") UnresolvedSymbol cachedSymbol,
|
||||
@Cached("resolveFunctionMethod(cachedSymbol)") Function function) {
|
||||
return function;
|
||||
}
|
||||
|
||||
Function resolveAtomMethod(AtomConstructor cons, UnresolvedSymbol symbol) {
|
||||
Function result = symbol.resolveFor(cons);
|
||||
if (result == null) {
|
||||
throw new MethodDoesNotExistException(cons, symbol.getName(), this);
|
||||
@ -60,12 +71,27 @@ public abstract class MethodResolverNode extends Node {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the cache validity. For use by the DSL. The cache entry is valid if it's resolved for
|
||||
* the same method name and this argument type. Not for manual use.
|
||||
*/
|
||||
public boolean isValidCache(
|
||||
UnresolvedSymbol symbol, UnresolvedSymbol cachedSymbol, Atom atom, AtomConstructor cachedConstructor) {
|
||||
Function resolveNumberMethod(UnresolvedSymbol symbol) {
|
||||
Function result = symbol.resolveForNumber();
|
||||
if (result == null) {
|
||||
throw new MethodDoesNotExistException("Number", symbol.getName(), this);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Function resolveFunctionMethod(UnresolvedSymbol symbol) {
|
||||
Function result = symbol.resolveForFunction();
|
||||
if (result == null) {
|
||||
throw new MethodDoesNotExistException("Function", symbol.getName(), this);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
boolean isValidAtomCache(
|
||||
UnresolvedSymbol symbol,
|
||||
UnresolvedSymbol cachedSymbol,
|
||||
Atom atom,
|
||||
AtomConstructor cachedConstructor) {
|
||||
return (symbol == cachedSymbol) && (atom.getConstructor() == cachedConstructor);
|
||||
}
|
||||
}
|
||||
|
@ -40,8 +40,8 @@ public class CachedArgumentSorterNode extends BaseNode {
|
||||
* @param schema information on the calling argument
|
||||
* @param hasDefaultsSuspended whether or not the function to which these arguments are applied
|
||||
* has its defaults suspended.
|
||||
* @param ignoresArgumentExecution whether this node assumes all arguments are pre-executed and not
|
||||
* passed in a {@link Thunk}.
|
||||
* @param ignoresArgumentExecution whether this node assumes all arguments are pre-executed and
|
||||
* not passed in a {@link Thunk}.
|
||||
* @param isTail whether this node is called from a tail call position.
|
||||
*/
|
||||
public CachedArgumentSorterNode(
|
||||
@ -72,7 +72,9 @@ public class CachedArgumentSorterNode extends BaseNode {
|
||||
if (postApplicationSchema.hasOversaturatedArgs()) {
|
||||
oversaturatedCallableNode =
|
||||
InvokeCallableNodeGen.create(
|
||||
postApplicationSchema.getOversaturatedArguments(), hasDefaultsSuspended);
|
||||
postApplicationSchema.getOversaturatedArguments(),
|
||||
hasDefaultsSuspended,
|
||||
ignoresArgumentExecution);
|
||||
oversaturatedCallableNode.setTail(isTail);
|
||||
}
|
||||
|
||||
@ -149,8 +151,7 @@ public class CachedArgumentSorterNode extends BaseNode {
|
||||
return optimiser.executeDispatch(function, mappedAppliedArguments);
|
||||
}
|
||||
} else {
|
||||
Object evaluatedVal =
|
||||
optimiser.executeDispatch(function, mappedAppliedArguments);
|
||||
Object evaluatedVal = optimiser.executeDispatch(function, mappedAppliedArguments);
|
||||
|
||||
return this.oversaturatedCallableNode.execute(
|
||||
evaluatedVal, generateOversaturatedArguments(function, arguments));
|
||||
|
@ -4,16 +4,40 @@ import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.UnexpectedResultException;
|
||||
import org.enso.interpreter.node.BaseNode;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
|
||||
/** An abstract representation of a case expression. */
|
||||
public abstract class CaseNode extends BaseNode {
|
||||
/**
|
||||
* Executes the case expression.
|
||||
* Executes the case expression with an atom scrutinee.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the constructor to destructure
|
||||
* @param target the atom to match and destructure
|
||||
* @throws UnexpectedResultException when the result of desctructuring {@code target} can't be
|
||||
* represented as a value of the expected return type
|
||||
*/
|
||||
public abstract void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException;
|
||||
public abstract void executeAtom(VirtualFrame frame, Atom target)
|
||||
throws UnexpectedResultException;
|
||||
|
||||
/**
|
||||
* Executes the case expression with a function scrutinee.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the function to match
|
||||
* @throws UnexpectedResultException when the result of desctructuring {@code target} can't be
|
||||
* represented as a value of the expected return type
|
||||
*/
|
||||
public abstract void executeFunction(VirtualFrame frame, Function target)
|
||||
throws UnexpectedResultException;
|
||||
|
||||
/**
|
||||
* * Executes the case expression with a number scrutinee.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the number to match
|
||||
* @throws UnexpectedResultException when the result of desctructuring {@code target} can't be
|
||||
* represented as a value of the expected return type
|
||||
*/
|
||||
public abstract void executeNumber(VirtualFrame frame, long target)
|
||||
throws UnexpectedResultException;
|
||||
}
|
||||
|
@ -9,8 +9,9 @@ import org.enso.interpreter.node.callable.ExecuteCallNodeGen;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.type.TypesGen;
|
||||
|
||||
/** An implementation of the case expression specialised to working on explicit constructors. */
|
||||
/** An implementation of the case expression specialised to working on constructors. */
|
||||
public class ConstructorCaseNode extends CaseNode {
|
||||
@Child private ExpressionNode matcher;
|
||||
@Child private ExpressionNode branch;
|
||||
@ -39,21 +40,39 @@ public class ConstructorCaseNode extends CaseNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the case expression.
|
||||
* Handles the atom scrutinee case.
|
||||
*
|
||||
* <p>It has no direct return value and instead uses a {@link BranchSelectedException} to signal
|
||||
* the correct result back to the parent of the case expression.
|
||||
* <p>The atom's constructor is checked and if it matches the conditional branch is executed with
|
||||
* all the atom's fields as arguments.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the constructor to destructure
|
||||
* @throws UnexpectedResultException when the result of desctructuring {@code target} can't be
|
||||
* represented as a value of the expected return type
|
||||
* @param target the atom to destructure
|
||||
* @throws UnexpectedResultException
|
||||
*/
|
||||
public void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException {
|
||||
@Override
|
||||
public void executeAtom(VirtualFrame frame, Atom target) throws UnexpectedResultException {
|
||||
AtomConstructor matcherVal = matcher.executeAtomConstructor(frame);
|
||||
if (profile.profile(matcherVal == target.getConstructor())) {
|
||||
Function function = branch.executeFunction(frame);
|
||||
throw new BranchSelectedException(executeCallNode.executeCall(function, target.getFields()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the function scrutinee case, by not matching it at all.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the function to match
|
||||
*/
|
||||
@Override
|
||||
public void executeFunction(VirtualFrame frame, Function target) {}
|
||||
|
||||
/**
|
||||
* Handles the number scrutinee case, by not matching it at all.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the function to match
|
||||
*/
|
||||
@Override
|
||||
public void executeNumber(VirtualFrame frame, long target) {}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package org.enso.interpreter.node.controlflow;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.UnexpectedResultException;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.error.InexhaustivePatternMatchException;
|
||||
|
||||
/**
|
||||
@ -11,13 +13,49 @@ import org.enso.interpreter.runtime.error.InexhaustivePatternMatchException;
|
||||
public class DefaultFallbackNode extends CaseNode {
|
||||
|
||||
/**
|
||||
* Executes the case expression's error case.
|
||||
* Executes the case expression's error case, by throwing a {@link
|
||||
* InexhaustivePatternMatchException}.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the constructor to destructure
|
||||
*/
|
||||
@Override
|
||||
public void execute(VirtualFrame frame, Atom target) {
|
||||
private void execute(VirtualFrame frame, Object target) {
|
||||
throw new InexhaustivePatternMatchException(this.getParent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the case expression's error case, by throwing a {@link
|
||||
* InexhaustivePatternMatchException}.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the atom to match and destructure
|
||||
*/
|
||||
@Override
|
||||
public void executeAtom(VirtualFrame frame, Atom target) {
|
||||
execute(frame, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the case expression's error case, by throwing a {@link
|
||||
* InexhaustivePatternMatchException}.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the function to match
|
||||
*/
|
||||
@Override
|
||||
public void executeFunction(VirtualFrame frame, Function target) {
|
||||
execute(frame, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the case expression's error case, by throwing a {@link
|
||||
* InexhaustivePatternMatchException}.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the number to match
|
||||
*/
|
||||
@Override
|
||||
public void executeNumber(VirtualFrame frame, long target) {
|
||||
execute(frame, target);
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,11 @@ public class FallbackNode extends CaseNode {
|
||||
this.functionNode = functionNode;
|
||||
}
|
||||
|
||||
private void execute(VirtualFrame frame, Object target) throws UnexpectedResultException {
|
||||
Function function = functionNode.executeFunction(frame);
|
||||
throw new BranchSelectedException(executeCallNode.executeCall(function, new Object[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the case expression catch-all case.
|
||||
*
|
||||
@ -37,8 +42,40 @@ public class FallbackNode extends CaseNode {
|
||||
* represented as a value of the expected return type
|
||||
*/
|
||||
@Override
|
||||
public void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException {
|
||||
Function function = functionNode.executeFunction(frame);
|
||||
throw new BranchSelectedException(executeCallNode.executeCall(function, new Object[0]));
|
||||
public void executeAtom(VirtualFrame frame, Atom target) throws UnexpectedResultException {
|
||||
execute(frame, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the case expression catch-all case.
|
||||
*
|
||||
* <p>It has no direct return value and instead uses a {@link BranchSelectedException} to signal
|
||||
* the correct result back to the parent of the case expression.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the constructor to destructure
|
||||
* @throws UnexpectedResultException when the result of desctructuring {@code target} can't be
|
||||
* represented as a value of the expected return type
|
||||
*/
|
||||
@Override
|
||||
public void executeFunction(VirtualFrame frame, Function target)
|
||||
throws UnexpectedResultException {
|
||||
execute(frame, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the case expression catch-all case.
|
||||
*
|
||||
* <p>It has no direct return value and instead uses a {@link BranchSelectedException} to signal
|
||||
* the correct result back to the parent of the case expression.
|
||||
*
|
||||
* @param frame the stack frame in which to execute
|
||||
* @param target the constructor to destructure
|
||||
* @throws UnexpectedResultException when the result of desctructuring {@code target} can't be
|
||||
* represented as a value of the expected return type
|
||||
*/
|
||||
@Override
|
||||
public void executeNumber(VirtualFrame frame, long target) throws UnexpectedResultException {
|
||||
execute(frame, target);
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,31 @@
|
||||
package org.enso.interpreter.node.controlflow;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.NodeChild;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||
import com.oracle.truffle.api.nodes.UnexpectedResultException;
|
||||
import com.oracle.truffle.api.profiles.BranchProfile;
|
||||
import org.enso.interpreter.node.ExpressionNode;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.error.TypeError;
|
||||
|
||||
/** A node representing a pattern match on an Atom. */
|
||||
public class MatchNode extends ExpressionNode {
|
||||
@Child private ExpressionNode target;
|
||||
/**
|
||||
* A node representing a pattern match on an arbitrary runtime value.
|
||||
*
|
||||
* <p>Has a scrutinee node and a collection of {@link CaseNode}s. The case nodes get executed one by
|
||||
* one, until one throws an {@link BranchSelectedException}, the value of which becomes the result
|
||||
* of this pattern match.
|
||||
*/
|
||||
@NodeChild(value = "scrutinee", type = ExpressionNode.class)
|
||||
public abstract class MatchNode extends ExpressionNode {
|
||||
@Children private final CaseNode[] cases;
|
||||
@Child private CaseNode fallback;
|
||||
private final BranchProfile typeErrorProfile = BranchProfile.create();
|
||||
|
||||
/**
|
||||
* Creates a node that pattern matches on an Atom.
|
||||
*
|
||||
* @param target the atom to pattern match on
|
||||
* @param cases the branches of the pattern match
|
||||
* @param fallback the fallback case of the pattern match
|
||||
*/
|
||||
public MatchNode(ExpressionNode target, CaseNode[] cases, CaseNode fallback) {
|
||||
this.target = target;
|
||||
MatchNode(CaseNode[] cases, CaseNode fallback) {
|
||||
this.cases = cases;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
@ -43,21 +44,62 @@ public class MatchNode extends ExpressionNode {
|
||||
fallback.setTail(isTail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the pattern match.
|
||||
*
|
||||
* @param frame the stack frame for execution
|
||||
* @return the result of the pattern match
|
||||
*/
|
||||
// TODO[MK]: The atom, number and function cases are very repetitive and should be refactored.
|
||||
// It poses some engineering challenge – the approaches tried so far included passing the only
|
||||
// changing line as a lambda and introducing a separate node between this and the CaseNodes.
|
||||
// Both attempts resulted in a performance drop.
|
||||
|
||||
@ExplodeLoop
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
@Specialization
|
||||
Object doAtom(VirtualFrame frame, Atom atom) {
|
||||
try {
|
||||
Atom atom = target.executeAtom(frame);
|
||||
for (CaseNode caseNode : cases) {
|
||||
caseNode.execute(frame, atom);
|
||||
caseNode.executeAtom(frame, atom);
|
||||
}
|
||||
fallback.execute(frame, atom);
|
||||
fallback.executeAtom(frame, atom);
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw new RuntimeException("Impossible behavior.");
|
||||
|
||||
} catch (BranchSelectedException e) {
|
||||
// Note [Branch Selection Control Flow]
|
||||
return e.getResult();
|
||||
|
||||
} catch (UnexpectedResultException e) {
|
||||
typeErrorProfile.enter();
|
||||
throw new TypeError("Expected an Atom.", this);
|
||||
}
|
||||
}
|
||||
|
||||
@ExplodeLoop
|
||||
@Specialization
|
||||
Object doFunction(VirtualFrame frame, Function function) {
|
||||
try {
|
||||
for (CaseNode caseNode : cases) {
|
||||
caseNode.executeFunction(frame, function);
|
||||
}
|
||||
fallback.executeFunction(frame, function);
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw new RuntimeException("Impossible behavior.");
|
||||
|
||||
} catch (BranchSelectedException e) {
|
||||
// Note [Branch Selection Control Flow]
|
||||
return e.getResult();
|
||||
|
||||
} catch (UnexpectedResultException e) {
|
||||
typeErrorProfile.enter();
|
||||
throw new TypeError("Expected an Atom.", this);
|
||||
}
|
||||
}
|
||||
|
||||
@ExplodeLoop
|
||||
@Specialization
|
||||
Object doNumber(VirtualFrame frame, long number) {
|
||||
try {
|
||||
|
||||
for (CaseNode caseNode : cases) {
|
||||
caseNode.executeNumber(frame, number);
|
||||
}
|
||||
fallback.executeNumber(frame, number);
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw new RuntimeException("Impossible behavior.");
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.enso.interpreter.runtime.callable;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.interop.TruffleObject;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
@ -27,7 +28,7 @@ public class UnresolvedSymbol implements TruffleObject {
|
||||
* <p>All names for dynamic symbols are interned, making it safe to compare symbol names using the
|
||||
* standard {@code ==} equality operator.
|
||||
*
|
||||
* @return the name of this symbol.
|
||||
* @return the name of this symbol
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
@ -40,6 +41,26 @@ public class UnresolvedSymbol implements TruffleObject {
|
||||
* @return the resolved function definition, or null if not found
|
||||
*/
|
||||
public Function resolveFor(AtomConstructor cons) {
|
||||
return scope.lookupMethodDefinition(cons, name);
|
||||
return scope.lookupMethodDefinitionForAtom(cons, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the symbol for a number.
|
||||
*
|
||||
* @return the resolved function definition, or null if not found
|
||||
*/
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
public Function resolveForNumber() {
|
||||
return scope.lookupMethodDefinitionForAny(name).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the symbol for a function.
|
||||
*
|
||||
* @return the resolved function definition, or null if not found
|
||||
*/
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
public Function resolveForFunction() {
|
||||
return scope.lookupMethodDefinitionForAny(name).orElse(null);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.enso.interpreter.runtime.scope;
|
||||
|
||||
import org.enso.interpreter.runtime.Builtins;
|
||||
import org.enso.interpreter.runtime.Module;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
|
||||
@ -12,6 +11,7 @@ public class ModuleScope {
|
||||
|
||||
private final Map<String, AtomConstructor> constructors = new HashMap<>();
|
||||
private final Map<AtomConstructor, Map<String, Function>> methods = new HashMap<>();
|
||||
private final Map<String, Function> anyMethods = new HashMap<>();
|
||||
private final Set<ModuleScope> imports = new HashSet<>();
|
||||
private final Set<ModuleScope> transitiveImports = new HashSet<>();
|
||||
|
||||
@ -57,38 +57,80 @@ public class ModuleScope {
|
||||
/**
|
||||
* Registers a method defined for a given type.
|
||||
*
|
||||
* @param atom type the method was defined for.
|
||||
* @param method method name.
|
||||
* @param function the {@link Function} associated with this definition.
|
||||
* @param atom type the method was defined for
|
||||
* @param method method name
|
||||
* @param function the {@link Function} associated with this definition
|
||||
*/
|
||||
public void registerMethod(AtomConstructor atom, String method, Function function) {
|
||||
getMethodMapFor(atom).put(method, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the definition for a given type and method name. The resolution algorithm is first
|
||||
* looking for methods defined at the constructor definition site (i.e. non-overloads), then looks
|
||||
* for methods defined in this scope and finally tries to resolve the method in all transitive
|
||||
* dependencies of this module.
|
||||
* Registers a method for the {@code Any} type.
|
||||
*
|
||||
* @param methodName the name of the method to register
|
||||
* @param function the {@link Function} associated with this definition
|
||||
*/
|
||||
public void registerMethodForAny(String methodName, Function function) {
|
||||
anyMethods.put(methodName, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the definition for a given type and method name.
|
||||
*
|
||||
* <p>The resolution algorithm is first looking for methods defined at the constructor definition
|
||||
* site (i.e. non-overloads), then looks for methods defined in this scope and finally tries to
|
||||
* resolve the method in all transitive dependencies of this module.
|
||||
*
|
||||
* <p>If the specific search fails, methods defined for any type are searched, first looking at
|
||||
* locally defined methods and then all the transitive imports.
|
||||
*
|
||||
* @param atom type to lookup the method for.
|
||||
* @param name the method name.
|
||||
* @return the matching method definition or null if not found.
|
||||
*/
|
||||
public Function lookupMethodDefinition(AtomConstructor atom, String name) {
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
public Function lookupMethodDefinitionForAtom(AtomConstructor atom, String name) {
|
||||
return lookupSpecificMethodDefinitionForAtom(atom, name)
|
||||
.orElseGet(() -> lookupMethodDefinitionForAny(name).orElse(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a method definition by-name, for methods defined on the type Any.
|
||||
*
|
||||
* <p>The resolution algorithm prefers methods defined locally over any other method. The
|
||||
* definitions are imported into scope transitively.
|
||||
*
|
||||
* @param name the name of the method to look up
|
||||
* @return {@code Optional.of(resultMethod)} if the method existed, {@code Optional.empty()}
|
||||
* otherwise
|
||||
*/
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
public Optional<Function> lookupMethodDefinitionForAny(String name) {
|
||||
Function definedHere = anyMethods.get(name);
|
||||
if (definedHere != null) {
|
||||
return Optional.of(definedHere);
|
||||
}
|
||||
return transitiveImports.stream()
|
||||
.map(scope -> scope.getMethodsOfAny().get(name))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private Optional<Function> lookupSpecificMethodDefinitionForAtom(
|
||||
AtomConstructor atom, String name) {
|
||||
Function definedWithAtom = atom.getDefinitionScope().getMethodMapFor(atom).get(name);
|
||||
if (definedWithAtom != null) {
|
||||
return definedWithAtom;
|
||||
return Optional.of(definedWithAtom);
|
||||
}
|
||||
Function definedHere = getMethodMapFor(atom).get(name);
|
||||
if (definedHere != null) {
|
||||
return definedHere;
|
||||
return Optional.of(definedHere);
|
||||
}
|
||||
return transitiveImports.stream()
|
||||
.map(scope -> scope.getMethodMapFor(atom).get(name))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,10 +138,14 @@ public class ModuleScope {
|
||||
*
|
||||
* @return a set of all the transitive dependencies of this module
|
||||
*/
|
||||
protected Set<ModuleScope> getTransitiveImports() {
|
||||
private Set<ModuleScope> getTransitiveImports() {
|
||||
return transitiveImports;
|
||||
}
|
||||
|
||||
private Map<String, Function> getMethodsOfAny() {
|
||||
return anyMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dependency for this module.
|
||||
*
|
||||
|
@ -33,4 +33,15 @@ class InteropTest extends LanguageTest {
|
||||
val fun = eval(code)
|
||||
fun.call(1).call(2).call(3) shouldEqual 6
|
||||
}
|
||||
|
||||
"Interop library" should "work with oversaturated calls on unresolved methods returned from functions" in {
|
||||
val code =
|
||||
"""
|
||||
|Any.method = this
|
||||
|
|
||||
|{ |x| method }
|
||||
|""".stripMargin
|
||||
val fun = eval(code)
|
||||
fun.call(1, 2) shouldEqual 2
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,34 @@ class MethodsTest extends LanguageTest {
|
||||
"""
|
||||
|@foo [7]
|
||||
|""".stripMargin
|
||||
the[PolyglotException] thrownBy eval(code) should have message "Object 7 does not define method foo."
|
||||
the[PolyglotException] thrownBy eval(code) should have message "Object Number does not define method foo."
|
||||
}
|
||||
|
||||
"Methods defined on Any type" should "be callable for any type" in {
|
||||
val code =
|
||||
"""
|
||||
|type Foo;
|
||||
|type Bar;
|
||||
|type Baz;
|
||||
|
|
||||
|Any.method = { match this <
|
||||
| Foo ~ { 1 };
|
||||
| Bar ~ { 2 };
|
||||
| Baz ~ { 3 };
|
||||
| { 0 };
|
||||
|>}
|
||||
|
|
||||
|@{
|
||||
| @println [@IO, @method [@Foo]];
|
||||
| @println [@IO, @method [@Bar]];
|
||||
| @println [@IO, @method [@Baz]];
|
||||
| @println [@IO, @method [@Unit]];
|
||||
| @println [@IO, @method [123]];
|
||||
| @println [@IO, @method [{|x| x }]];
|
||||
| 0
|
||||
|}
|
||||
|""".stripMargin
|
||||
eval(code)
|
||||
consumeOut shouldEqual List("1", "2", "3", "0", "0", "0")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user