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 SCOPE_SEPARATOR = ".";
public static final String ANY_TYPE_NAME = "Any";
/** Cache sizes for different AST nodes. */
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.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]

View File

@ -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);

View File

@ -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);
}
/**

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.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.");
}

View File

@ -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);
}
}

View File

@ -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));

View File

@ -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;
}

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.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) {}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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.");

View File

@ -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);
}
}

View File

@ -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.
*

View File

@ -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
}
}

View File

@ -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")
}
}