mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 13:02:07 +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 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 {
|
||||||
|
@ -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]
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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) {}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.");
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user