Methods on Any type and pattern matches on arbitrary values (#259)

This commit is contained in:
Marcin Kostrzewa 2019-10-24 16:15:50 +02:00 committed by GitHub
parent 6569cc5cb0
commit 698a9425ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 423 additions and 130 deletions

View File

@ -11,6 +11,7 @@ public class Constants {
public static final String THIS_ARGUMENT_NAME = "this"; public static final String THIS_ARGUMENT_NAME = "this";
public static final String SCOPE_SEPARATOR = "."; public static final String SCOPE_SEPARATOR = ".";
public static final String ANY_TYPE_NAME = "Any";
/** Cache sizes for different AST nodes. */ /** Cache sizes for different AST nodes. */
public static class CacheSizes { public static class CacheSizes {

View File

@ -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.argument.ReadArgumentNode;
import org.enso.interpreter.node.callable.function.CreateFunctionNode; import org.enso.interpreter.node.callable.function.CreateFunctionNode;
import org.enso.interpreter.node.callable.function.FunctionBodyNode; import org.enso.interpreter.node.callable.function.FunctionBodyNode;
import org.enso.interpreter.node.controlflow.CaseNode; import org.enso.interpreter.node.controlflow.*;
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.expression.constant.ConstructorNode; import org.enso.interpreter.node.expression.constant.ConstructorNode;
import org.enso.interpreter.node.expression.constant.DynamicSymbolNode; import org.enso.interpreter.node.expression.constant.DynamicSymbolNode;
import org.enso.interpreter.node.expression.literal.IntegerLiteralNode; 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))) .map(fb -> (CaseNode) new FallbackNode(fb.visit(this)))
.orElseGet(DefaultFallbackNode::new); .orElseGet(DefaultFallbackNode::new);
return new MatchNode(targetNode, cases, fallbackNode); return MatchNodeGen.create(cases, fallbackNode, targetNode);
} }
/* Note [Pattern Match Fallbacks] /* Note [Pattern Match Fallbacks]

View File

@ -90,10 +90,6 @@ public class ModuleScopeExpressionFactory implements AstGlobalScopeVisitor<Expre
}); });
for (AstMethodDef method : bindings) { for (AstMethodDef method : bindings) {
AtomConstructor constructor =
moduleScope
.getConstructor(method.typeName())
.orElseThrow(() -> new VariableDoesNotExistException(method.typeName()));
ExpressionFactory expressionFactory = ExpressionFactory expressionFactory =
new ExpressionFactory( new ExpressionFactory(
language, language,
@ -105,7 +101,16 @@ public class ModuleScopeExpressionFactory implements AstGlobalScopeVisitor<Expre
funNode.markTail(); funNode.markTail();
Function function = Function function =
new Function(funNode.getCallTarget(), null, new ArgumentSchema(funNode.getArgs())); 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); ExpressionFactory factory = new ExpressionFactory(this.language, moduleScope);

View File

@ -44,8 +44,9 @@ public class ApplicationNode extends ExpressionNode {
Arrays.stream(callArguments).map(CallArgumentInfo::new).toArray(CallArgumentInfo[]::new); Arrays.stream(callArguments).map(CallArgumentInfo::new).toArray(CallArgumentInfo[]::new);
this.callable = callable; this.callable = callable;
boolean argumentsArePreExecuted = false;
this.invokeCallableNode = this.invokeCallableNode =
InvokeCallableNodeGen.create(argSchema, hasDefaultsSuspended); InvokeCallableNodeGen.create(argSchema, hasDefaultsSuspended, argumentsArePreExecuted);
} }
/** /**

View File

@ -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.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.argument.Thunk;
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;
import org.enso.interpreter.runtime.error.MethodDoesNotExistException;
import org.enso.interpreter.runtime.error.NotInvokableException; 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 * 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 boolean canApplyThis;
private final int thisArgumentPosition; private final int thisArgumentPosition;
private final boolean argumentsArePreExecuted;
private final ConditionProfile methodCalledOnNonAtom = ConditionProfile.createCountingProfile(); 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 schema a description of the arguments being applied to the callable
* @param hasDefaultsSuspended whether or not the invocation has the callable's default arguments * @param hasDefaultsSuspended whether or not the invocation has the callable's default arguments
* (if any) suspended * (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; boolean appliesThis = false;
int idx = 0; int idx = 0;
for (; idx < schema.length; idx++) { for (; idx < schema.length; idx++) {
@ -61,7 +63,8 @@ public abstract class InvokeCallableNode extends BaseNode {
this.canApplyThis = appliesThis; this.canApplyThis = appliesThis;
this.thisArgumentPosition = idx; this.thisArgumentPosition = idx;
boolean argumentsArePreExecuted = false; this.argumentsArePreExecuted = argumentsArePreExecuted;
this.argumentSorter = this.argumentSorter =
ArgumentSorterNodeGen.create(schema, hasDefaultsSuspended, argumentsArePreExecuted); ArgumentSorterNodeGen.create(schema, hasDefaultsSuspended, argumentsArePreExecuted);
this.methodResolverNode = MethodResolverNodeGen.create(); this.methodResolverNode = MethodResolverNodeGen.create();
@ -102,21 +105,16 @@ public abstract class InvokeCallableNode extends BaseNode {
@Specialization @Specialization
public Object invokeDynamicSymbol(UnresolvedSymbol symbol, Object[] arguments) { public Object invokeDynamicSymbol(UnresolvedSymbol symbol, Object[] arguments) {
if (canApplyThis) { if (canApplyThis) {
if (thisExecutor == null) { Object selfArgument = arguments[thisArgumentPosition];
CompilerDirectives.transferToInterpreterAndInvalidate(); if (!argumentsArePreExecuted) {
thisExecutor = ThunkExecutorNodeGen.create(false); if (thisExecutor == null) {
} CompilerDirectives.transferToInterpreterAndInvalidate();
thisExecutor = ThunkExecutorNodeGen.create(false);
Object selfArgument = }
thisExecutor.executeThunk(((Thunk) arguments[thisArgumentPosition])); selfArgument = thisExecutor.executeThunk((Thunk) selfArgument);
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);
} }
Function function = methodResolverNode.execute(symbol, selfArgument);
return this.argumentSorter.execute(function, arguments);
} else { } else {
throw new RuntimeException("Currying without `this` argument is not yet supported."); throw new RuntimeException("Currying without `this` argument is not yet supported.");
} }

View File

@ -14,45 +14,56 @@ import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.error.MethodDoesNotExistException; import org.enso.interpreter.runtime.error.MethodDoesNotExistException;
/** /**
* A node performing lookups of method definitions. Uses a polymorphic inline cache to ensure the * A node performing lookups of method definitions.
* best performance. *
* <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 { 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. * Entry point for this node.
* *
* @param symbol Method name to resolve. * @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. * @return Resolved method.
*/ */
public abstract Function execute(UnresolvedSymbol symbol, Atom atom); public abstract Function execute(UnresolvedSymbol symbol, Object self);
/** @Specialization(guards = "isValidAtomCache(symbol, cachedSymbol, atom, cachedConstructor)")
* Handles the actual method lookup. Not for manual use. Function resolveAtomCached(
* UnresolvedSymbol symbol,
* @param cons Type for which to resolve the method. Atom atom,
* @param symbol symbol representing the method to resolve @CachedContext(Language.class) TruffleLanguage.ContextReference<Context> contextRef,
* @return Resolved method definition. @Cached("symbol") UnresolvedSymbol cachedSymbol,
*/ @Cached("atom.getConstructor()") AtomConstructor cachedConstructor,
public Function resolveMethod( @Cached("resolveAtomMethod(cachedConstructor, cachedSymbol)") Function function) {
AtomConstructor cons, return function;
UnresolvedSymbol symbol) { }
@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); Function result = symbol.resolveFor(cons);
if (result == null) { if (result == null) {
throw new MethodDoesNotExistException(cons, symbol.getName(), this); throw new MethodDoesNotExistException(cons, symbol.getName(), this);
@ -60,12 +71,27 @@ public abstract class MethodResolverNode extends Node {
return result; return result;
} }
/** Function resolveNumberMethod(UnresolvedSymbol symbol) {
* Checks the cache validity. For use by the DSL. The cache entry is valid if it's resolved for Function result = symbol.resolveForNumber();
* the same method name and this argument type. Not for manual use. if (result == null) {
*/ throw new MethodDoesNotExistException("Number", symbol.getName(), this);
public boolean isValidCache( }
UnresolvedSymbol symbol, UnresolvedSymbol cachedSymbol, Atom atom, AtomConstructor cachedConstructor) { 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); return (symbol == cachedSymbol) && (atom.getConstructor() == cachedConstructor);
} }
} }

View File

@ -40,8 +40,8 @@ public class CachedArgumentSorterNode extends BaseNode {
* @param schema information on the calling argument * @param schema information on the calling argument
* @param hasDefaultsSuspended whether or not the function to which these arguments are applied * @param hasDefaultsSuspended whether or not the function to which these arguments are applied
* has its defaults suspended. * has its defaults suspended.
* @param ignoresArgumentExecution whether this node assumes all arguments are pre-executed and not * @param ignoresArgumentExecution whether this node assumes all arguments are pre-executed and
* passed in a {@link Thunk}. * not passed in a {@link Thunk}.
* @param isTail whether this node is called from a tail call position. * @param isTail whether this node is called from a tail call position.
*/ */
public CachedArgumentSorterNode( public CachedArgumentSorterNode(
@ -72,7 +72,9 @@ public class CachedArgumentSorterNode extends BaseNode {
if (postApplicationSchema.hasOversaturatedArgs()) { if (postApplicationSchema.hasOversaturatedArgs()) {
oversaturatedCallableNode = oversaturatedCallableNode =
InvokeCallableNodeGen.create( InvokeCallableNodeGen.create(
postApplicationSchema.getOversaturatedArguments(), hasDefaultsSuspended); postApplicationSchema.getOversaturatedArguments(),
hasDefaultsSuspended,
ignoresArgumentExecution);
oversaturatedCallableNode.setTail(isTail); oversaturatedCallableNode.setTail(isTail);
} }
@ -149,8 +151,7 @@ public class CachedArgumentSorterNode extends BaseNode {
return optimiser.executeDispatch(function, mappedAppliedArguments); return optimiser.executeDispatch(function, mappedAppliedArguments);
} }
} else { } else {
Object evaluatedVal = Object evaluatedVal = optimiser.executeDispatch(function, mappedAppliedArguments);
optimiser.executeDispatch(function, mappedAppliedArguments);
return this.oversaturatedCallableNode.execute( return this.oversaturatedCallableNode.execute(
evaluatedVal, generateOversaturatedArguments(function, arguments)); evaluatedVal, generateOversaturatedArguments(function, arguments));

View File

@ -4,16 +4,40 @@ import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.nodes.UnexpectedResultException;
import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.function.Function;
/** An abstract representation of a case expression. */ /** An abstract representation of a case expression. */
public abstract class CaseNode extends BaseNode { 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 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 * @throws UnexpectedResultException when the result of desctructuring {@code target} can't be
* represented as a value of the expected return type * 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;
} }

View File

@ -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.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;
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 { public class ConstructorCaseNode extends CaseNode {
@Child private ExpressionNode matcher; @Child private ExpressionNode matcher;
@Child private ExpressionNode branch; @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 * <p>The atom's constructor is checked and if it matches the conditional branch is executed with
* the correct result back to the parent of the case expression. * all the atom's fields as arguments.
* *
* @param frame the stack frame in which to execute * @param frame the stack frame in which to execute
* @param target the constructor to destructure * @param target the atom to destructure
* @throws UnexpectedResultException when the result of desctructuring {@code target} can't be * @throws UnexpectedResultException
* represented as a value of the expected return type
*/ */
public void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException { @Override
public void executeAtom(VirtualFrame frame, Atom target) throws UnexpectedResultException {
AtomConstructor matcherVal = matcher.executeAtomConstructor(frame); AtomConstructor matcherVal = matcher.executeAtomConstructor(frame);
if (profile.profile(matcherVal == target.getConstructor())) { if (profile.profile(matcherVal == target.getConstructor())) {
Function function = branch.executeFunction(frame); Function function = branch.executeFunction(frame);
throw new BranchSelectedException(executeCallNode.executeCall(function, target.getFields())); 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) {}
} }

View File

@ -1,7 +1,9 @@
package org.enso.interpreter.node.controlflow; package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.frame.VirtualFrame; 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.atom.Atom;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.error.InexhaustivePatternMatchException; import org.enso.interpreter.runtime.error.InexhaustivePatternMatchException;
/** /**
@ -11,13 +13,49 @@ import org.enso.interpreter.runtime.error.InexhaustivePatternMatchException;
public class DefaultFallbackNode extends CaseNode { 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 frame the stack frame in which to execute
* @param target the constructor to destructure * @param target the constructor to destructure
*/ */
@Override private void execute(VirtualFrame frame, Object target) {
public void execute(VirtualFrame frame, Atom target) {
throw new InexhaustivePatternMatchException(this.getParent()); 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);
}
} }

View File

@ -25,6 +25,11 @@ public class FallbackNode extends CaseNode {
this.functionNode = functionNode; 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. * 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 * represented as a value of the expected return type
*/ */
@Override @Override
public void execute(VirtualFrame frame, Atom target) throws UnexpectedResultException { public void executeAtom(VirtualFrame frame, Atom target) throws UnexpectedResultException {
Function function = functionNode.executeFunction(frame); execute(frame, target);
throw new BranchSelectedException(executeCallNode.executeCall(function, new Object[0])); }
/**
* 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);
} }
} }

View File

@ -1,30 +1,31 @@
package org.enso.interpreter.node.controlflow; package org.enso.interpreter.node.controlflow;
import com.oracle.truffle.api.CompilerDirectives; 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.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.error.TypeError; import org.enso.interpreter.runtime.error.TypeError;
/** A node representing a pattern match on an Atom. */ /**
public class MatchNode extends ExpressionNode { * A node representing a pattern match on an arbitrary runtime value.
@Child private ExpressionNode target; *
* <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; @Children private final CaseNode[] cases;
@Child private CaseNode fallback; @Child private CaseNode fallback;
private final BranchProfile typeErrorProfile = BranchProfile.create(); private final BranchProfile typeErrorProfile = BranchProfile.create();
/** MatchNode(CaseNode[] cases, CaseNode fallback) {
* 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;
this.cases = cases; this.cases = cases;
this.fallback = fallback; this.fallback = fallback;
} }
@ -43,21 +44,62 @@ public class MatchNode extends ExpressionNode {
fallback.setTail(isTail); fallback.setTail(isTail);
} }
/** // TODO[MK]: The atom, number and function cases are very repetitive and should be refactored.
* Executes the pattern match. // 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.
* @param frame the stack frame for execution // Both attempts resulted in a performance drop.
* @return the result of the pattern match
*/
@ExplodeLoop @ExplodeLoop
@Override @Specialization
public Object executeGeneric(VirtualFrame frame) { Object doAtom(VirtualFrame frame, Atom atom) {
try { try {
Atom atom = target.executeAtom(frame);
for (CaseNode caseNode : cases) { 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(); CompilerDirectives.transferToInterpreter();
throw new RuntimeException("Impossible behavior."); throw new RuntimeException("Impossible behavior.");

View File

@ -1,5 +1,6 @@
package org.enso.interpreter.runtime.callable; package org.enso.interpreter.runtime.callable;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.TruffleObject;
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;
@ -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 * <p>All names for dynamic symbols are interned, making it safe to compare symbol names using the
* standard {@code ==} equality operator. * standard {@code ==} equality operator.
* *
* @return the name of this symbol. * @return the name of this symbol
*/ */
public String getName() { public String getName() {
return name; return name;
@ -40,6 +41,26 @@ public class UnresolvedSymbol implements TruffleObject {
* @return the resolved function definition, or null if not found * @return the resolved function definition, or null if not found
*/ */
public Function resolveFor(AtomConstructor cons) { 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);
} }
} }

View File

@ -1,7 +1,6 @@
package org.enso.interpreter.runtime.scope; package org.enso.interpreter.runtime.scope;
import org.enso.interpreter.runtime.Builtins; import com.oracle.truffle.api.CompilerDirectives;
import org.enso.interpreter.runtime.Module;
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;
@ -12,6 +11,7 @@ public class ModuleScope {
private final Map<String, AtomConstructor> constructors = new HashMap<>(); private final Map<String, AtomConstructor> constructors = new HashMap<>();
private final Map<AtomConstructor, Map<String, Function>> methods = 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> imports = new HashSet<>();
private final Set<ModuleScope> transitiveImports = new HashSet<>(); private final Set<ModuleScope> transitiveImports = new HashSet<>();
@ -57,38 +57,80 @@ public class ModuleScope {
/** /**
* Registers a method defined for a given type. * Registers a method defined for a given type.
* *
* @param atom type the method was defined for. * @param atom type the method was defined for
* @param method method name. * @param method method name
* @param function the {@link Function} associated with this definition. * @param function the {@link Function} associated with this definition
*/ */
public void registerMethod(AtomConstructor atom, String method, Function function) { public void registerMethod(AtomConstructor atom, String method, Function function) {
getMethodMapFor(atom).put(method, function); getMethodMapFor(atom).put(method, function);
} }
/** /**
* Looks up the definition for a given type and method name. The resolution algorithm is first * Registers a method for the {@code Any} type.
* 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 * @param methodName the name of the method to register
* dependencies of this module. * @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 atom type to lookup the method for.
* @param name the method name. * @param name the method name.
* @return the matching method definition or null if not found. * @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); Function definedWithAtom = atom.getDefinitionScope().getMethodMapFor(atom).get(name);
if (definedWithAtom != null) { if (definedWithAtom != null) {
return definedWithAtom; return Optional.of(definedWithAtom);
} }
Function definedHere = getMethodMapFor(atom).get(name); Function definedHere = getMethodMapFor(atom).get(name);
if (definedHere != null) { if (definedHere != null) {
return definedHere; return Optional.of(definedHere);
} }
return transitiveImports.stream() return transitiveImports.stream()
.map(scope -> scope.getMethodMapFor(atom).get(name)) .map(scope -> scope.getMethodMapFor(atom).get(name))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.findFirst() .findFirst();
.orElse(null);
} }
/** /**
@ -96,10 +138,14 @@ public class ModuleScope {
* *
* @return a set of all the transitive dependencies of this module * @return a set of all the transitive dependencies of this module
*/ */
protected Set<ModuleScope> getTransitiveImports() { private Set<ModuleScope> getTransitiveImports() {
return transitiveImports; return transitiveImports;
} }
private Map<String, Function> getMethodsOfAny() {
return anyMethods;
}
/** /**
* Adds a dependency for this module. * Adds a dependency for this module.
* *

View File

@ -33,4 +33,15 @@ class InteropTest extends LanguageTest {
val fun = eval(code) val fun = eval(code)
fun.call(1).call(2).call(3) shouldEqual 6 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
}
} }

View File

@ -42,6 +42,34 @@ class MethodsTest extends LanguageTest {
""" """
|@foo [7] |@foo [7]
|""".stripMargin |""".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")
} }
} }