diff --git a/RELEASES.md b/RELEASES.md index 2d807e86c50..276bcbfab27 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,6 +10,12 @@ - Updated the Scala compiler and dependencies ([#3214](https://github.com/enso-org/enso/pull/3214)). +## Interpreter/Runtime + +- Added support for overloaded conversions. This allows the `from` method to be + implemented with several overloads. + ([#3227](https://github.com/enso-org/enso/pull/3227)) + ## Enso 0.2.31 (2021-10-01) ## Interpreter/Runtime diff --git a/app/gui/CHANGELOG.md b/app/gui/CHANGELOG.md index ddace656c2d..e210f2faa65 100644 --- a/app/gui/CHANGELOG.md +++ b/app/gui/CHANGELOG.md @@ -44,6 +44,12 @@ [3230]: https://github.com/enso-org/enso/pull/3230 [3240]: https://github.com/enso-org/enso/pull/3240 +#### Enso Compiler + +- [Added overloaded `from` conversions.][3227] + +[3227]: https://github.com/enso-org/enso/pull/3227 + # Enso 2.0.0-alpha.18 (2021-10-12)
![New Features](/docs/assets/tags/new_features.svg) diff --git a/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/Data/Any/Extensions.enso b/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/Data/Any/Extensions.enso index 17d365e21f4..11f8ee7fe4f 100644 --- a/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/Data/Any/Extensions.enso +++ b/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/Data/Any/Extensions.enso @@ -48,6 +48,8 @@ Any.== that = if Meta.is_same_object this that then True else Cons (Meta.Polyglot o_1) (Meta.Polyglot o_2) -> langs_match = (this_meta.get_language == Meta.Java) && (that_meta.get_language == Meta.Java) if langs_match.not then False else o_1.equals o_2 + Cons (Meta.Unresolved_Symbol _) (Meta.Unresolved_Symbol _) -> + (this_meta.name == that_meta.name) && (this_meta.scope == that_meta.scope) ## Constructor comparison is covered by the identity equality. Primitive objects should define their own equality. Therefore, there are no more cases to handle in this method. diff --git a/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/Data/Map/Internal.enso b/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/Data/Map/Internal.enso index fdffb15c2ca..28a9f9403f8 100644 --- a/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/Data/Map/Internal.enso +++ b/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/Data/Map/Internal.enso @@ -148,7 +148,7 @@ delta = 3 Arguments: - m: The map to get the size of. -size: Map -> Integer +size : Map -> Integer size m = case m of Bin s _ _ _ _ -> s _ -> 0 diff --git a/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/System/Platform.enso b/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/System/Platform.enso index 6e16c97b632..bff9c2b449d 100644 --- a/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/System/Platform.enso +++ b/distribution/lib/Standard/Base/0.2.32-SNAPSHOT/src/System/Platform.enso @@ -31,7 +31,7 @@ os = here.from_text System.os ## PRIVATE Create an Os object from text. -from_text: Text -> Os +from_text : Text -> Os from_text os = if os == "linux" then Linux else if os == "macos" then MacOS else diff --git a/distribution/lib/Standard/Examples/0.2.32-SNAPSHOT/src/Main.enso b/distribution/lib/Standard/Examples/0.2.32-SNAPSHOT/src/Main.enso index 5b00ac0b47c..15ace8bc4d4 100644 --- a/distribution/lib/Standard/Examples/0.2.32-SNAPSHOT/src/Main.enso +++ b/distribution/lib/Standard/Examples/0.2.32-SNAPSHOT/src/Main.enso @@ -214,7 +214,7 @@ simple_table_json = ## The headers for the columns in the JSON table `here.simple_table_json`. simple_table_json_headers : Vector Text -simple_Table_json_headers = ["foo", "bar", "baz"] +simple_table_json_headers = ["foo", "bar", "baz"] ## Some simple GeoJSON. geo_json : Enso_Json.Json diff --git a/distribution/lib/Standard/Table/0.2.32-SNAPSHOT/src/Data/Table.enso b/distribution/lib/Standard/Table/0.2.32-SNAPSHOT/src/Data/Table.enso index c1e378c9b17..996149f20e4 100644 --- a/distribution/lib/Standard/Table/0.2.32-SNAPSHOT/src/Data/Table.enso +++ b/distribution/lib/Standard/Table/0.2.32-SNAPSHOT/src/Data/Table.enso @@ -1283,8 +1283,9 @@ pad txt len = Adds ANSI bold escape sequences to text if the feature is enabled. Arguments: + - enabled: will insert ANSI sequences only if this flag is true and we are not on Windows. - txt: The text to possibly bold. -ansi_bold_enabled : Text -> Text +ansi_bold : Boolean -> Text -> Text ansi_bold enabled txt = case Platform.os of ## Output formatting for Windows is not currently supported. diff --git a/distribution/lib/Standard/Test/0.2.32-SNAPSHOT/src/Main.enso b/distribution/lib/Standard/Test/0.2.32-SNAPSHOT/src/Main.enso index 0ab1bf51819..396b047b212 100644 --- a/distribution/lib/Standard/Test/0.2.32-SNAPSHOT/src/Main.enso +++ b/distribution/lib/Standard/Test/0.2.32-SNAPSHOT/src/Main.enso @@ -122,7 +122,7 @@ specify label ~behavior pending=Nothing = Arguments: - verb: The property (see `Verbs`) being asserted - argument: The argument to the verb. -Anu.should : (Verbs -> Any -> Any) -> Any -> Assertion +Any.should : (Verbs -> Any -> Any) -> Any -> Assertion Any.should verb argument = verb Verbs this argument ## Fail a test with the given message. diff --git a/engine/runtime/src/main/java/org/enso/interpreter/Constants.java b/engine/runtime/src/main/java/org/enso/interpreter/Constants.java index 4c5c2318de3..f8027b06233 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/Constants.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/Constants.java @@ -7,7 +7,9 @@ public class Constants { /** Names for different language elements. */ public static class Names { public static final String THIS_ARGUMENT = "this"; + public static final String THAT_ARGUMENT = "that"; public static final String CURRENT_MODULE = "here"; + public static final String FROM_MEMBER = "from"; } /** Cache sizes for different AST nodes. */ diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java index eba0c34559b..7c52feea4f0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java @@ -20,7 +20,6 @@ import org.enso.interpreter.runtime.state.Stateful; */ @NodeInfo(shortName = "App", description = "Executes function") public class ApplicationNode extends ExpressionNode { - private @Children ExpressionNode[] argExpressions; @Child private InvokeCallableNode invokeCallableNode; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeConversionNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeConversionNode.java new file mode 100644 index 00000000000..3014370748f --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeConversionNode.java @@ -0,0 +1,218 @@ +package org.enso.interpreter.node.callable; + +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.dsl.*; +import com.oracle.truffle.api.frame.MaterializedFrame; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.ExplodeLoop; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.BranchProfile; +import com.oracle.truffle.api.profiles.ConditionProfile; +import org.enso.interpreter.Language; +import org.enso.interpreter.node.BaseNode; +import org.enso.interpreter.node.callable.dispatch.IndirectInvokeFunctionNode; +import org.enso.interpreter.node.callable.resolver.AnyResolverNode; +import org.enso.interpreter.node.callable.resolver.DataflowErrorResolverNode; +import org.enso.interpreter.node.callable.resolver.HostMethodCallNode; +import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; +import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.DataflowError; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.error.PanicSentinel; +import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; +import org.enso.interpreter.runtime.state.Stateful; + +@GenerateUncached +@ReportPolymorphism +@ImportStatic({HostMethodCallNode.PolyglotCallType.class, HostMethodCallNode.class}) +public abstract class IndirectInvokeConversionNode extends Node { + + /** @return a new indirect method invocation node */ + public static IndirectInvokeConversionNode build() { + return IndirectInvokeConversionNodeGen.create(); + } + + public abstract Stateful execute( + MaterializedFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + Object that, + Object[] arguments, + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + BaseNode.TailStatus isTail); + + @Specialization(guards = "dispatch.canConvertFrom(that)") + Stateful doConvertFrom( + MaterializedFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + Object that, + Object[] arguments, + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + BaseNode.TailStatus isTail, + @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch, + @CachedContext(Language.class) TruffleLanguage.ContextReference ctx, + @Cached ConditionProfile atomProfile, + @Cached ConditionProfile atomConstructorProfile, + @Cached IndirectInvokeFunctionNode indirectInvokeFunctionNode) { + try { + Function function = + dispatch.getConversionFunction( + that, + InvokeConversionNode.extractConstructor( + this, _this, ctx, atomConstructorProfile, atomProfile), + conversion); + return indirectInvokeFunctionNode.execute( + function, + frame, + state, + arguments, + schema, + defaultsExecutionMode, + argumentsExecutionMode, + isTail); + } catch (MethodDispatchLibrary.NoSuchConversionException e) { + throw new PanicException( + ctx.get().getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), this); + } + } + + @Specialization + Stateful doDataflowError( + MaterializedFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + DataflowError that, + Object[] arguments, + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + BaseNode.TailStatus isTail, + @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch, + @CachedContext(Language.class) TruffleLanguage.ContextReference ctx, + @Cached BranchProfile profile, + @Cached ConditionProfile atomProfile, + @Cached ConditionProfile atomConstructorProfile, + @Cached IndirectInvokeFunctionNode indirectInvokeFunctionNode) { + try { + Function function = + dispatch.getConversionFunction( + that, + InvokeConversionNode.extractConstructor( + this, _this, ctx, atomConstructorProfile, atomProfile), + conversion); + return indirectInvokeFunctionNode.execute( + function, + frame, + state, + arguments, + schema, + defaultsExecutionMode, + argumentsExecutionMode, + isTail); + } catch (MethodDispatchLibrary.NoSuchConversionException e) { + profile.enter(); + return new Stateful(state, that); + } + } + + @Specialization + Stateful doPanicSentinel( + MaterializedFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + PanicSentinel that, + Object[] arguments, + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + BaseNode.TailStatus isTail) { + throw that; + } + + @Specialization(guards = "interop.isString(that)") + Stateful doConvertText( + MaterializedFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + Object that, + Object[] arguments, + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + BaseNode.TailStatus isTail, + @CachedLibrary(limit = "10") MethodDispatchLibrary methods, + @CachedLibrary(limit = "1") MethodDispatchLibrary textDispatch, + @CachedLibrary(limit = "10") InteropLibrary interop, + @Cached ConditionProfile atomProfile, + @Cached ConditionProfile atomConstructorProfile, + @CachedContext(Language.class) TruffleLanguage.ContextReference ctx, + @Cached IndirectInvokeFunctionNode indirectInvokeFunctionNode) { + try { + String str = interop.asString(that); + Text txt = Text.create(str); + Function function = + textDispatch.getConversionFunction( + txt, + InvokeConversionNode.extractConstructor( + this, _this, ctx, atomConstructorProfile, atomProfile), + conversion); + arguments[0] = txt; + return indirectInvokeFunctionNode.execute( + function, + frame, + state, + arguments, + schema, + defaultsExecutionMode, + argumentsExecutionMode, + isTail); + } catch (UnsupportedMessageException e) { + throw new IllegalStateException("Impossible, that is guaranteed to be a string."); + } catch (MethodDispatchLibrary.NoSuchConversionException e) { + throw new PanicException( + ctx.get().getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), this); + } + } + + @Specialization( + guards = { + "!methods.canConvertFrom(that)", + "!interop.isString(that)", + "!methods.hasSpecialConversion(that)" + }) + Stateful doFallback( + MaterializedFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + Object that, + Object[] arguments, + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + BaseNode.TailStatus isTail, + @CachedLibrary(limit = "10") MethodDispatchLibrary methods, + @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedContext(Language.class) Context ctx) { + throw new PanicException( + ctx.getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), this); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropConversionCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropConversionCallNode.java new file mode 100644 index 00000000000..e4ce0958362 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropConversionCallNode.java @@ -0,0 +1,102 @@ +package org.enso.interpreter.node.callable; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.nodes.ExplodeLoop; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.NodeInfo; +import org.enso.interpreter.Constants; +import org.enso.interpreter.Language; +import org.enso.interpreter.node.BaseNode.TailStatus; +import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode; +import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode; +import org.enso.interpreter.node.callable.InvokeConversionNode; +import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; +import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; + +/** A helper node to handle conversion application for the interop library. */ +@GenerateUncached +@NodeInfo(description = "Helper node to handle conversion application through the interop library.") +public abstract class InteropConversionCallNode extends Node { + + public static InteropConversionCallNode build() { + return InteropConversionCallNodeGen.create(); + } + + public abstract Object execute(UnresolvedConversion conversion, Object state, Object[] arguments) + throws ArityException; + + @CompilerDirectives.TruffleBoundary + CallArgumentInfo[] buildSchema(int length) { + CallArgumentInfo[] args = new CallArgumentInfo[length]; + for (int i = 0; i < length; i++) { + args[i] = new CallArgumentInfo(); + } + return args; + } + + @CompilerDirectives.TruffleBoundary + InvokeConversionNode buildInvoker(int length) { + CallArgumentInfo[] args = buildSchema(length); + return InvokeConversionNode.build( + args, + DefaultsExecutionMode.EXECUTE, + ArgumentsExecutionMode.PRE_EXECUTED); + } + + @Specialization( + guards = {"!context.isInlineCachingDisabled()", "arguments.length == cachedArgsLength"}, + limit = Constants.CacheSizes.FUNCTION_INTEROP_LIBRARY) + @ExplodeLoop + Object callCached( + UnresolvedConversion conversion, + Object state, + Object[] arguments, + @CachedContext(Language.class) Context context, + @Cached("arguments.length") int cachedArgsLength, + @Cached("buildInvoker(cachedArgsLength)") InvokeConversionNode invokerNode, + @Cached("build()") HostValueToEnsoNode hostValueToEnsoNode) + throws ArityException { + Object[] args = new Object[cachedArgsLength]; + for (int i = 0; i < cachedArgsLength; i++) { + args[i] = hostValueToEnsoNode.execute(arguments[i]); + } + if (cachedArgsLength < 2) throw ArityException.create(2, cachedArgsLength); + return invokerNode.execute(null, state, conversion, args[0], args[1], args).getValue(); + } + + @Specialization(replaces = "callCached") + Object callUncached( + UnresolvedConversion conversion, + Object state, + Object[] arguments, + @Cached IndirectInvokeConversionNode indirectInvokeConversionNode, + @Cached("build()") HostValueToEnsoNode hostValueToEnsoNode) + throws ArityException { + Object[] args = new Object[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + args[i] = hostValueToEnsoNode.execute(arguments[i]); + } + if (arguments.length < 2) throw ArityException.create(2, arguments.length); + return indirectInvokeConversionNode + .execute( + null, + state, + conversion, + args[0], + args[1], + args, + buildSchema(arguments.length), + DefaultsExecutionMode.EXECUTE, + ArgumentsExecutionMode.PRE_EXECUTED, + TailStatus.NOT_TAIL) + .getValue(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java index 0293934ee9d..9f5591ea874 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeCallableNode.java @@ -15,6 +15,7 @@ import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.argument.Thunk; @@ -82,11 +83,15 @@ public abstract class InvokeCallableNode extends BaseNode { @Child private InvokeFunctionNode invokeFunctionNode; @Child private InvokeMethodNode invokeMethodNode; + @Child private InvokeConversionNode invokeConversionNode; @Child private ThunkExecutorNode thisExecutor; + @Child private ThunkExecutorNode thatExecutor; private final ConditionProfile functionErrorProfile = ConditionProfile.createCountingProfile(); private final boolean canApplyThis; + private final boolean canApplyThat; private final int thisArgumentPosition; + private final int thatArgumentPosition; private final ArgumentsExecutionMode argumentsExecutionMode; @@ -95,9 +100,12 @@ public abstract class InvokeCallableNode extends BaseNode { DefaultsExecutionMode defaultsExecutionMode, ArgumentsExecutionMode argumentsExecutionMode) { Integer thisArg = thisArgumentPosition(schema); - this.canApplyThis = thisArg != null; - this.thisArgumentPosition = thisArg == null ? 0 : thisArg; + this.thisArgumentPosition = thisArg == null ? -1 : thisArg; + + Integer thatArg = thatArgumentPosition(schema, thisArgumentPosition); + this.canApplyThat = thatArg != null; + this.thatArgumentPosition = thatArg == null ? -1 : thatArg; this.argumentsExecutionMode = argumentsExecutionMode; @@ -105,6 +113,8 @@ public abstract class InvokeCallableNode extends BaseNode { InvokeFunctionNode.build(schema, defaultsExecutionMode, argumentsExecutionMode); this.invokeMethodNode = InvokeMethodNode.build(schema, defaultsExecutionMode, argumentsExecutionMode); + this.invokeConversionNode = + InvokeConversionNode.build(schema, defaultsExecutionMode, argumentsExecutionMode); } public static Integer thisArgumentPosition(CallArgumentInfo[] schema) { @@ -120,7 +130,22 @@ public abstract class InvokeCallableNode extends BaseNode { return null; } + public static Integer thatArgumentPosition(CallArgumentInfo[] schema, int thisArgumentPosition) { + int idx = 0; + for (; idx < schema.length; idx++) { + CallArgumentInfo arg = schema[idx]; + + boolean isNamedThat = arg.isNamed() && arg.getName().equals(Constants.Names.THAT_ARGUMENT); + if ((arg.isPositional() && thisArgumentPosition != idx) || isNamedThat) { + return idx; + } + } + return null; + } + + /** + * * Creates a new instance of this node. * * @param schema a description of the arguments being applied to the callable @@ -158,6 +183,52 @@ public abstract class InvokeCallableNode extends BaseNode { throw sentinel; } + @Specialization + public Stateful invokeConversion( + UnresolvedConversion conversion, VirtualFrame callerFrame, Object state, Object[] arguments) { + if (canApplyThis && canApplyThat) { + Object selfArgument = arguments[thisArgumentPosition]; + Object thatArgument = arguments[thatArgumentPosition]; + if (argumentsExecutionMode.shouldExecute()) { + if (thisExecutor == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + Lock lock = getLock(); + lock.lock(); + try { + if (thisExecutor == null) { + thisExecutor = insert(ThunkExecutorNode.build()); + } + } finally { + lock.unlock(); + } + } + if (thatExecutor == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + Lock lock = getLock(); + lock.lock(); + try { + if (thatExecutor == null) { + thatExecutor = insert(ThunkExecutorNode.build()); + } + } finally { + lock.unlock(); + } + } + Stateful selfResult = thisExecutor.executeThunk(selfArgument, state, TailStatus.NOT_TAIL); + Stateful thatResult = thatExecutor.executeThunk(thatArgument, selfResult.getState(), TailStatus.NOT_TAIL); + selfArgument = selfResult.getValue(); + thatArgument = thatResult.getValue(); + state = thatResult.getState(); + arguments[thisArgumentPosition] = selfArgument; + arguments[thatArgumentPosition] = thatArgument; + } + return invokeConversionNode.execute(callerFrame, state, conversion, selfArgument, thatArgument, arguments); + } else { + throw new RuntimeException("Conversion currying without `this` or `that` argument is not supported."); + } + } + + @Specialization public Stateful invokeDynamicSymbol( UnresolvedSymbol symbol, VirtualFrame callerFrame, Object state, Object[] arguments) { @@ -217,6 +288,7 @@ public abstract class InvokeCallableNode extends BaseNode { super.setTailStatus(isTail); invokeFunctionNode.setTailStatus(isTail); invokeMethodNode.setTailStatus(isTail); + invokeConversionNode.setTailStatus(isTail); } /** @return the source section for this node. */ @@ -234,5 +306,6 @@ public abstract class InvokeCallableNode extends BaseNode { public void setId(UUID id) { invokeFunctionNode.setId(id); invokeMethodNode.setId(id); + invokeConversionNode.setId(id); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeConversionNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeConversionNode.java new file mode 100644 index 00000000000..22ab57c5f98 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeConversionNode.java @@ -0,0 +1,207 @@ +package org.enso.interpreter.node.callable; + +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.dsl.*; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.BranchProfile; +import com.oracle.truffle.api.profiles.ConditionProfile; +import com.oracle.truffle.api.source.SourceSection; +import org.enso.interpreter.Language; +import org.enso.interpreter.node.BaseNode; +import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode; +import org.enso.interpreter.node.callable.resolver.AnyResolverNode; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; +import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; +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.data.text.Text; +import org.enso.interpreter.runtime.error.DataflowError; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.error.PanicSentinel; +import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; +import org.enso.interpreter.runtime.state.Stateful; + +import java.util.UUID; + +public abstract class InvokeConversionNode extends BaseNode { + private @Child InvokeFunctionNode invokeFunctionNode; + private final ConditionProfile atomProfile = ConditionProfile.createCountingProfile(); + private final ConditionProfile atomConstructorProfile = ConditionProfile.createCountingProfile(); + + /** + * Creates a new node for method invocation. + * + * @param schema a description of the arguments being applied to the callable + * @param defaultsExecutionMode the defaulted arguments handling mode for this call + * @param argumentsExecutionMode the arguments execution mode for this call + * @return a new invoke method node + */ + public static InvokeConversionNode build( + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode) { + return InvokeConversionNodeGen.create(schema, defaultsExecutionMode, argumentsExecutionMode); + } + + InvokeConversionNode( + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode) { + this.invokeFunctionNode = + InvokeFunctionNode.build(schema, defaultsExecutionMode, argumentsExecutionMode); + } + + @Override + public void setTailStatus(TailStatus tailStatus) { + super.setTailStatus(tailStatus); + this.invokeFunctionNode.setTailStatus(tailStatus); + } + + public abstract Stateful execute( + VirtualFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + Object that, + Object[] arguments); + + static AtomConstructor extractConstructor( + Node thisNode, + Object _this, + TruffleLanguage.ContextReference ctx, + ConditionProfile atomConstructorProfile, + ConditionProfile atomProfile) { + if (atomConstructorProfile.profile(_this instanceof AtomConstructor)) { + return (AtomConstructor) _this; + } else if (atomProfile.profile(_this instanceof Atom)) { + return ((Atom) _this).getConstructor(); + } else { + throw new PanicException( + ctx.get().getBuiltins().error().makeInvalidConversionTargetError(_this), thisNode); + } + } + + AtomConstructor extractConstructor(Object _this, TruffleLanguage.ContextReference ctx) { + return extractConstructor(this, _this, ctx, atomConstructorProfile, atomProfile); + } + + @Specialization(guards = "dispatch.canConvertFrom(that)") + Stateful doConvertFrom( + VirtualFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + Object that, + Object[] arguments, + @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch, + @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + try { + Function function = + dispatch.getConversionFunction(that, extractConstructor(_this, ctx), conversion); + return invokeFunctionNode.execute(function, frame, state, arguments); + } catch (MethodDispatchLibrary.NoSuchConversionException e) { + throw new PanicException( + ctx.get().getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), this); + } + } + + @Specialization + Stateful doDataflowError( + VirtualFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + DataflowError that, + Object[] arguments, + @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch, + @Cached BranchProfile profile, + @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + try { + Function function = + dispatch.getConversionFunction(that, extractConstructor(_this, ctx), conversion); + return invokeFunctionNode.execute(function, frame, state, arguments); + } catch (MethodDispatchLibrary.NoSuchConversionException e) { + profile.enter(); + return new Stateful(state, that); + } + } + + @Specialization + Stateful doPanicSentinel( + VirtualFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + PanicSentinel that, + Object[] arguments) { + throw that; + } + + @Specialization(guards = "interop.isString(that)") + Stateful doConvertText( + VirtualFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + Object that, + Object[] arguments, + @CachedLibrary(limit = "10") MethodDispatchLibrary methods, + @CachedLibrary(limit = "1") MethodDispatchLibrary textDispatch, + @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + try { + String str = interop.asString(that); + Text txt = Text.create(str); + Function function = + textDispatch.getConversionFunction(txt, extractConstructor(_this, ctx), conversion); + arguments[0] = txt; + return invokeFunctionNode.execute(function, frame, state, arguments); + } catch (UnsupportedMessageException e) { + throw new IllegalStateException("Impossible, that is guaranteed to be a string."); + } catch (MethodDispatchLibrary.NoSuchConversionException e) { + throw new PanicException( + ctx.get().getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), this); + } + } + + @Specialization( + guards = { + "!methods.canConvertFrom(that)", + "!interop.isString(that)", + "!methods.hasSpecialConversion(that)" + }) + Stateful doFallback( + VirtualFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + Object that, + Object[] arguments, + @CachedLibrary(limit = "10") MethodDispatchLibrary methods, + @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedContext(Language.class) Context ctx) { + throw new PanicException( + ctx.getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), this); + } + + @Override + public SourceSection getSourceSection() { + Node parent = getParent(); + return parent == null ? null : parent.getSourceSection(); + } + + /** + * Sets the expression ID of this node. + * + * @param id the expression ID to assign this node. + */ + public void setId(UUID id) { + invokeFunctionNode.setId(id); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java index abe121ff456..690927386f9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java @@ -32,7 +32,6 @@ import org.enso.interpreter.runtime.state.Stateful; public abstract class InvokeMethodNode extends BaseNode { private @Child InvokeFunctionNode invokeFunctionNode; private final ConditionProfile errorReceiverProfile = ConditionProfile.createCountingProfile(); - private final BranchProfile polyglotArgumentErrorProfile = BranchProfile.create(); private final int argumentCount; /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/InvalidConversionTargetErrorToDisplayTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/InvalidConversionTargetErrorToDisplayTextNode.java new file mode 100644 index 00000000000..73acf6aa167 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/InvalidConversionTargetErrorToDisplayTextNode.java @@ -0,0 +1,42 @@ +package org.enso.interpreter.node.expression.builtin.error.displaytext; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.text.util.TypeToDisplayTextNode; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.data.text.Text; + +@BuiltinMethod(type = "Invalid_Conversion_Target_Error", name = "to_display_text") +public abstract class InvalidConversionTargetErrorToDisplayTextNode extends Node { + static InvalidConversionTargetErrorToDisplayTextNode build() { + return InvalidConversionTargetErrorToDisplayTextNodeGen.create(); + } + + abstract Text execute(Object _this); + + @Specialization + Text doAtom( + Atom _this, + @CachedLibrary(limit="10") InteropLibrary interopLibrary, + @Cached TypeToDisplayTextNode fallback) { + String fieldRep; + Object target = _this.getFields()[0]; + try { + fieldRep = interopLibrary.asString(interopLibrary.toDisplayString(target)); + } catch (UnsupportedMessageException e) { + fieldRep = fallback.execute(target); + } + return Text.create(fieldRep).add(" is not a valid conversion target. Expected a type."); + } + + @Specialization + Text doConstructor(AtomConstructor _this) { + return Text.create("Invalid conversion target type."); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/NoSuchConversionErrorToDisplayTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/NoSuchConversionErrorToDisplayTextNode.java new file mode 100644 index 00000000000..e5627c3175d --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/NoSuchConversionErrorToDisplayTextNode.java @@ -0,0 +1,35 @@ +package org.enso.interpreter.node.expression.builtin.error.displaytext; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.UnexpectedResultException; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.text.util.TypeToDisplayTextNode; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.type.TypesGen; + +@BuiltinMethod(type = "No_Such_Method_Error", name = "to_display_text") +public abstract class NoSuchConversionErrorToDisplayTextNode extends Node { + static NoSuchConversionErrorToDisplayTextNode build() { + return NoSuchConversionErrorToDisplayTextNodeGen.create(); + } + + abstract Text execute(Object _this); + + @Specialization + Text doAtom(Atom _this, @Cached TypeToDisplayTextNode displayTypeNode) { + return Text.create("Could not find a conversion from `") + .add(displayTypeNode.execute(_this.getFields()[1])) + .add("` to `") + .add(displayTypeNode.execute(_this.getFields()[0])) + .add("`"); + } + + @Specialization + Text doConstructor(AtomConstructor _this) { + return Text.create("Conversion could not be found."); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java index b7314af2758..c92e9ed325f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/CreateUnresolvedSymbolNode.java @@ -1,9 +1,11 @@ package org.enso.interpreter.node.expression.builtin.meta; import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.Constants; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.scope.ModuleScope; @@ -15,7 +17,12 @@ import org.enso.interpreter.runtime.scope.ModuleScope; public class CreateUnresolvedSymbolNode extends Node { private @Child ExpectStringNode expectStringNode = ExpectStringNode.build(); - UnresolvedSymbol execute(Object _this, Object name, ModuleScope scope) { - return UnresolvedSymbol.build(expectStringNode.execute(name), scope); + Object execute(Object _this, Object name, ModuleScope scope) { + String result = expectStringNode.execute(name); + if (result.equals(Constants.Names.FROM_MEMBER)) { + return UnresolvedConversion.build(scope); + } else { + return UnresolvedSymbol.build(result, scope); + } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetUnresolvedSymbolNameNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetUnresolvedSymbolNameNode.java index 3d04487d899..e967674a726 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetUnresolvedSymbolNameNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetUnresolvedSymbolNameNode.java @@ -1,16 +1,47 @@ package org.enso.interpreter.node.expression.builtin.meta; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.Constants; +import org.enso.interpreter.Language; import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.builtin.Builtins; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.PanicException; @BuiltinMethod( type = "Meta", name = "get_unresolved_symbol_name", description = "Gets the name of an unresolved symbol") -public class GetUnresolvedSymbolNameNode extends Node { - Text execute(Object _this, UnresolvedSymbol symbol) { +public abstract class GetUnresolvedSymbolNameNode extends Node { + static GetUnresolvedSymbolNameNode build() { + return GetUnresolvedSymbolNameNodeGen.create(); + } + + public static Text fromText = Text.create(Constants.Names.FROM_MEMBER); + + abstract Text execute(Object _this, Object symbol); + + @Specialization + Text doSymbol(Object _this, UnresolvedSymbol symbol) { return Text.create(symbol.getName()); } + + @Specialization + Text doConversion(Object _this, UnresolvedConversion symbol) { + return fromText; + } + + @Fallback + Text doFallback(Object _this, Object symbol) { + Builtins builtins = lookupContextReference(Language.class).get().getBuiltins(); + throw new PanicException( + builtins.error().makeTypeError("Unresolved_Symbol", symbol, "symbol"), this); + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetUnresolvedSymbolScopeNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetUnresolvedSymbolScopeNode.java index cff3981543f..7f2b2a6ed3d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetUnresolvedSymbolScopeNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetUnresolvedSymbolScopeNode.java @@ -1,17 +1,42 @@ package org.enso.interpreter.node.expression.builtin.meta; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.Language; import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.builtin.Builtins; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.PanicException; import org.enso.interpreter.runtime.scope.ModuleScope; @BuiltinMethod( type = "Meta", name = "get_unresolved_symbol_scope", description = "Gets the scope of an unresolved symbol") -public class GetUnresolvedSymbolScopeNode extends Node { - ModuleScope execute(Object _this, UnresolvedSymbol symbol) { +public abstract class GetUnresolvedSymbolScopeNode extends Node { + static GetUnresolvedSymbolScopeNode build() { + return GetUnresolvedSymbolScopeNodeGen.create(); + } + + abstract ModuleScope execute(Object _this, Object symbol); + + @Specialization + ModuleScope doSymbol(Object _this, UnresolvedSymbol symbol) { return symbol.getScope(); } + + @Specialization + ModuleScope doConversion(Object _this, UnresolvedConversion symbol) { + return symbol.getScope(); + } + + @Fallback + ModuleScope doFallback(Object _this, Object symbol) { + Builtins builtins = lookupContextReference(Language.class).get().getBuiltins(); + throw new PanicException( + builtins.error().makeTypeError("Unresolved_Symbol", symbol, "symbol"), this); + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsUnresolvedSymbolNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsUnresolvedSymbolNode.java index 68f01c55459..7d0f8426647 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsUnresolvedSymbolNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsUnresolvedSymbolNode.java @@ -11,6 +11,6 @@ import org.enso.interpreter.runtime.type.TypesGen; description = "Checks if the argument is an unresolved symbol.") public class IsUnresolvedSymbolNode extends Node { boolean execute(Object _this, @AcceptsError Object value) { - return TypesGen.isUnresolvedSymbol(value); + return TypesGen.isUnresolvedSymbol(value) || TypesGen.isUnresolvedConversion(value); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java index 3355011ec8c..1c2b5f0698a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java @@ -2,6 +2,7 @@ package org.enso.interpreter.runtime.builtin; import org.enso.interpreter.Language; import org.enso.interpreter.node.expression.builtin.error.displaytext.*; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.atom.Atom; @@ -18,6 +19,7 @@ public class Error { private final AtomConstructor inexhaustivePatternMatchError; private final AtomConstructor uninitializedState; private final AtomConstructor noSuchMethodError; + private final AtomConstructor noSuchConversionError; private final AtomConstructor polyglotError; private final AtomConstructor moduleNotInPackageError; private final AtomConstructor arithmeticError; @@ -26,6 +28,7 @@ public class Error { private final AtomConstructor unsupportedArgumentsError; private final AtomConstructor moduleDoesNotExistError; private final AtomConstructor notInvokableError; + private final AtomConstructor invalidConversionTargetError; private final Atom arithmeticErrorShiftTooBig; private final Atom arithmeticErrorDivideByZero; @@ -67,6 +70,19 @@ public class Error { .initializeFields( new ArgumentDefinition(0, "target", ArgumentDefinition.ExecutionMode.EXECUTE), new ArgumentDefinition(1, "symbol", ArgumentDefinition.ExecutionMode.EXECUTE)); + + noSuchConversionError = + new AtomConstructor("No_Such_Conversion_Error", scope) + .initializeFields( + new ArgumentDefinition(0, "target", ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition(1, "that", ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition(2, "conversion", ArgumentDefinition.ExecutionMode.EXECUTE)); + + invalidConversionTargetError = + new AtomConstructor("Invalid_Conversion_Target_Error", scope) + .initializeFields( + new ArgumentDefinition(0, "target", ArgumentDefinition.ExecutionMode.EXECUTE)); + polyglotError = new AtomConstructor("Polyglot_Error", scope) .initializeFields( @@ -130,6 +146,19 @@ public class Error { noSuchMethodError, "to_display_text", NoSuchMethodErrorToDisplayTextMethodGen.makeFunction(language)); + + scope.registerConstructor(noSuchConversionError); + scope.registerMethod( + noSuchConversionError, + "to_display_text", + NoSuchConversionErrorToDisplayTextMethodGen.makeFunction(language)); + + scope.registerConstructor(invalidConversionTargetError); + scope.registerMethod( + invalidConversionTargetError, + "to_display_text", + InvalidConversionTargetErrorToDisplayTextMethodGen.makeFunction(language)); + scope.registerConstructor(polyglotError); scope.registerMethod( polyglotError, @@ -203,6 +232,15 @@ public class Error { return noSuchMethodError.newInstance(target, symbol); } + public Atom makeNoSuchConversionError( + Object target, Object that, UnresolvedConversion conversion) { + return noSuchConversionError.newInstance(target, that, conversion); + } + + public Atom makeInvalidConversionTargetError(Object target) { + return invalidConversionTargetError.newInstance(target); + } + /** * Creates an instance of the runtime representation of a {@code Type_Error}. * diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConversion.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConversion.java new file mode 100644 index 00000000000..978e3f3f261 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConversion.java @@ -0,0 +1,98 @@ +package org.enso.interpreter.runtime.callable; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.ImportStatic; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.*; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import org.enso.interpreter.Constants; +import org.enso.interpreter.node.callable.InteropConversionCallNode; +import org.enso.interpreter.node.callable.InteropMethodCallNode; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.scope.ModuleScope; +import org.enso.interpreter.runtime.state.data.EmptyMap; + +/** Simple runtime value representing a yet-unresolved by-name symbol. */ +@ExportLibrary(InteropLibrary.class) +public class UnresolvedConversion implements TruffleObject { + private final ModuleScope scope; + + /** + * Creates a new unresolved conversion. + * + * @param scope the scope in which this conversion was created + */ + private UnresolvedConversion(ModuleScope scope) { + this.scope = scope; + } + + /** @return the scope this symbol was used in. */ + public ModuleScope getScope() { + return scope; + } + + /** + * Resolves the symbol for a given hierarchy of constructors. + * + *

The constructors are checked in the first to last order, and the first match for this symbol + * is returned. This is useful for certain subtyping relations, such as "any constructor is a + * subtype of Any" or "Nat is a subtype of Int, is a subtype of Number". + * + * @param constructors the constructors hierarchy for which this symbol should be resolved + * @return the resolved function definition, or null if not found + */ + public Function resolveFor(AtomConstructor into, AtomConstructor... constructors) { + for (AtomConstructor constructor : constructors) { + Function candidate = scope.lookupConversionDefinition(constructor, into); + if (candidate != null) { + return candidate; + } + } + return null; + } + + @Override + public String toString() { return "UnresolvedConversion"; } + + @ExportMessage + String toDisplayString(boolean allowSideEffects) { + return this.toString(); + } + + /** + * Creates an instance of this node. + * + * @param name the name that is unresolved + * @param scope the scope in which the lookup will occur + * @return a node representing an unresolved symbol {@code name} in {@code scope} + */ + public static UnresolvedConversion build(ModuleScope scope) { + return new UnresolvedConversion(scope); + } + + /** + * Marks this object as executable through the interop library. + * + * @return always true + */ + @ExportMessage + public boolean isExecutable() { + return true; + } + + /** Implements the logic of executing {@link UnresolvedConversion} through the interop library. */ + @ExportMessage + @ImportStatic(Constants.CacheSizes.class) + public static class Execute { + @Specialization + static Object doDispatch( + UnresolvedConversion conversion, + Object[] arguments, + @Cached InteropConversionCallNode interopConversionCallNode) + throws ArityException, UnsupportedTypeException, UnsupportedMessageException { + return interopConversionCallNode.execute(conversion, EmptyMap.create(), arguments); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java index f2a6ce0dda9..410155e3515 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java @@ -12,6 +12,7 @@ import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.UnexpectedResultException; import org.enso.interpreter.Language; import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.data.Array; @@ -242,4 +243,60 @@ public class Atom implements TruffleObject { return function; } } + + @ExportMessage + boolean canConvertFrom() { + return true; + } + + @ExportMessage + static class GetConversionFunction { + + static final int CACHE_SIZE = 10; + + @CompilerDirectives.TruffleBoundary + static Function doResolve( + Context context, + AtomConstructor cons, + AtomConstructor target, + UnresolvedConversion conversion) { + return conversion.resolveFor(target, cons, context.getBuiltins().any()); + } + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedConversion == conversion", + "cachedTarget == target", + "_this.constructor == cachedConstructor", + "function != null" + }, + limit = "CACHE_SIZE") + static Function resolveCached( + Atom _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("_this.constructor") AtomConstructor cachedConstructor, + @Cached("target") AtomConstructor cachedTarget, + @Cached("doResolve(context, cachedConstructor, cachedTarget, cachedConversion)") + Function function) { + return function; + } + + @Specialization(replaces = "resolveCached") + static Function resolve( + Atom _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context) + throws MethodDispatchLibrary.NoSuchConversionException { + Function function = doResolve(context, _this.constructor, target, conversion); + if (function == null) { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } + return function; + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java index a20de00ba3f..0abc7f645b6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java @@ -20,6 +20,7 @@ import org.enso.interpreter.node.expression.atom.InstantiateNode; import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode; import org.enso.interpreter.node.expression.builtin.InstantiateAtomNode; import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.function.Function; @@ -231,8 +232,7 @@ public final class AtomConstructor implements TruffleObject { static final int CACHE_SIZE = 10; @CompilerDirectives.TruffleBoundary - static Function doResolve( - Context context, AtomConstructor cons, UnresolvedSymbol symbol) { + static Function doResolve(Context context, AtomConstructor cons, UnresolvedSymbol symbol) { return symbol.resolveFor(cons, context.getBuiltins().any()); } @@ -250,8 +250,7 @@ public final class AtomConstructor implements TruffleObject { @CachedContext(Language.class) Context context, @Cached("symbol") UnresolvedSymbol cachedSymbol, @Cached("_this") AtomConstructor cachedConstructor, - @Cached("doResolve(context, cachedConstructor, cachedSymbol)") - Function function) { + @Cached("doResolve(context, cachedConstructor, cachedSymbol)") Function function) { return function; } @@ -268,4 +267,58 @@ public final class AtomConstructor implements TruffleObject { return function; } } + + @ExportMessage + boolean canConvertFrom() { + return true; + } + + @ExportMessage + static class GetConversionFunction { + static final int CACHE_SIZE = 10; + + @CompilerDirectives.TruffleBoundary + static Function doResolve( + Context context, + AtomConstructor cons, + AtomConstructor target, + UnresolvedConversion conversion) { + return conversion.resolveFor(target, cons, context.getBuiltins().any()); + } + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedConversion == conversion", + "cachedTarget == target", + "_this == cachedConstructor", + "function != null" + }, + limit = "CACHE_SIZE") + static Function resolveCached( + AtomConstructor _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("target") AtomConstructor cachedTarget, + @Cached("_this") AtomConstructor cachedConstructor, + @Cached("doResolve(context, cachedConstructor, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization(replaces = "resolveCached") + static Function resolve( + AtomConstructor _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context) + throws MethodDispatchLibrary.NoSuchConversionException { + Function function = doResolve(context, _this, target, conversion); + if (function == null) { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } + return function; + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java index 763b2baf9ca..899c7d9a20b 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java @@ -24,9 +24,11 @@ import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode; import org.enso.interpreter.node.expression.builtin.BuiltinRootNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.CallerInfo; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.argument.Thunk; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; import org.enso.interpreter.runtime.state.data.EmptyMap; import org.enso.interpreter.runtime.data.Array; @@ -405,4 +407,53 @@ public final class Function implements TruffleObject { return function; } } + + @ExportMessage + boolean canConvertFrom() { + return true; + } + + @ExportMessage + static class GetConversionFunction { + + static final int CACHE_SIZE = 10; + + @CompilerDirectives.TruffleBoundary + static Function doResolve(Context context, AtomConstructor target, UnresolvedConversion conversion) { + return conversion.resolveFor(target, context.getBuiltins().function(), context.getBuiltins().any()); + } + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedTarget == target", + "cachedConversion == conversion", + "function != null" + }, + limit = "CACHE_SIZE") + static Function resolveCached( + Function _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("target") AtomConstructor cachedTarget, + @Cached("doResolve(context, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization(replaces = "resolveCached") + static Function resolve( + Function _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context) + throws MethodDispatchLibrary.NoSuchConversionException { + Function function = doResolve(context, target, conversion); + if (function == null) { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } + return function; + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java index f4ef811e7bd..8c86a76a814 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java @@ -11,7 +11,9 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import org.enso.interpreter.Language; import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; @@ -160,4 +162,54 @@ public class Array implements TruffleObject { return function; } } + + @ExportMessage + static boolean canConvertFrom(Array receiver) { + return true; + } + + @ExportMessage + static class GetConversionFunction { + static final int CACHE_SIZE = 10; + + @CompilerDirectives.TruffleBoundary + static Function doResolve( + Context context, AtomConstructor target, UnresolvedConversion conversion) { + return conversion.resolveFor( + target, context.getBuiltins().mutable().array(), context.getBuiltins().any()); + } + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedConversion == conversion", + "cachedTarget == target", + "function != null" + }, + limit = "CACHE_SIZE") + static Function resolveCached( + Array _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("target") AtomConstructor cachedTarget, + @Cached("doResolve(context, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization(replaces = "resolveCached") + static Function resolve( + Array _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context) + throws MethodDispatchLibrary.NoSuchConversionException { + Function function = doResolve(context, target, conversion); + if (function == null) { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } + return function; + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/text/Text.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/text/Text.java index d9051b2bcd6..311093cd7a5 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/text/Text.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/text/Text.java @@ -12,7 +12,9 @@ import org.enso.interpreter.Language; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.builtin.Number; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; @@ -220,4 +222,60 @@ public class Text implements TruffleObject { return function; } } + + @ExportMessage + public static boolean canConvertFrom(Text receiver) { + return true; + } + + @ExportMessage + public static boolean hasSpecialConversion(Text receiver) { + return false; + } + + @ExportMessage + static class GetConversionFunction { + + static final int CACHE_SIZE = 10; + + @CompilerDirectives.TruffleBoundary + static Function doResolve( + Context context, AtomConstructor target, UnresolvedConversion conversion) { + return conversion.resolveFor( + target, context.getBuiltins().text().getText(), context.getBuiltins().any()); + } + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedTarget == target", + "cachedConversion == conversion", + "function != null" + }, + limit = "CACHE_SIZE") + static Function resolveCached( + Text _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("target") AtomConstructor cachedTarget, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("doResolve(context, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization(replaces = "resolveCached") + static Function resolve( + Text _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context) + throws MethodDispatchLibrary.NoSuchConversionException { + Function function = doResolve(context, target, conversion); + if (function == null) { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } + return function; + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java index cdcbd7d4041..229cbf96fd7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java @@ -1,13 +1,23 @@ package org.enso.interpreter.runtime.error; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.TruffleStackTrace; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.exception.AbstractTruffleException; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.library.GenerateLibrary; import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.Language; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; /** @@ -93,4 +103,53 @@ public class DataflowError extends AbstractTruffleException { boolean hasSpecialDispatch() { return true; } + + @ExportMessage + boolean hasSpecialConversion() { + return true; + } + + @ExportMessage + static class GetConversionFunction { + + static final int CACHE_SIZE = 10; + + @CompilerDirectives.TruffleBoundary + static Function doResolve(Context context, AtomConstructor target, UnresolvedConversion conversion) { + return conversion.resolveFor(target, context.getBuiltins().dataflowError().constructor()); + } + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedTarget == target", + "cachedConversion == conversion", + "function != null" + }, + limit = "CACHE_SIZE") + static Function resolveCached( + DataflowError _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("target") AtomConstructor cachedTarget, + @Cached("doResolve(context, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization(replaces = "resolveCached") + static Function resolve( + DataflowError _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context) + throws MethodDispatchLibrary.NoSuchConversionException { + Function function = doResolve(context, target, conversion); + if (function == null) { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } + return function; + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicSentinel.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicSentinel.java index da0ab563dbf..c00c14cd39a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicSentinel.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicSentinel.java @@ -49,4 +49,9 @@ public class PanicSentinel extends AbstractTruffleException { boolean hasSpecialDispatch() { return true; } + + @ExportMessage + boolean hasSpecialConversion() { + return true; + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/RedefinedConversionException.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/RedefinedConversionException.java new file mode 100644 index 00000000000..0c7b800efe2 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/RedefinedConversionException.java @@ -0,0 +1,17 @@ +package org.enso.interpreter.runtime.error; + +import com.oracle.truffle.api.exception.AbstractTruffleException; + +/** An exception thrown when the program tries to redefine an already-defined method */ +public class RedefinedConversionException extends AbstractTruffleException { + + /** + * Creates a new error. + * + * @param atom the name of the atom you are converting to + * @param source the name of the atom you are converting from + */ + public RedefinedConversionException(String atom, String source) { + super("You have already overloaded conversion from " + source + " to " + atom); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultBooleanExports.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultBooleanExports.java index 7be7ac46b57..9d41a1f39af 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultBooleanExports.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultBooleanExports.java @@ -9,13 +9,21 @@ import com.oracle.truffle.api.library.ExportMessage; import org.enso.interpreter.Language; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.builtin.Bool; -import org.enso.interpreter.runtime.builtin.Number; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; @ExportLibrary(value = MethodDispatchLibrary.class, receiverType = Boolean.class) public class DefaultBooleanExports { + + static final int CACHE_SIZE = 10; + + static boolean unbox(Boolean b) { + return b; + } + + @ExportMessage static boolean hasFunctionalDispatch(Boolean receiver) { return true; @@ -42,11 +50,6 @@ public class DefaultBooleanExports { return symbol.resolveFor(cons, bool.getBool(), context.getBuiltins().any()); } - static final int CACHE_SIZE = 10; - - static boolean unbox(Boolean b) { - return b; - } @Specialization( guards = { @@ -111,4 +114,111 @@ public class DefaultBooleanExports { return function; } } + + @ExportMessage + public static boolean canConvertFrom(Boolean receiver) { + return true; + } + + @ExportMessage + public static boolean hasSpecialConversion(Boolean receiver) { + return false; + } + + @ExportMessage + static class GetConversionFunction { + @CompilerDirectives.TruffleBoundary + static Function resolveMethodOnPrimBoolean(Context context, AtomConstructor target, UnresolvedConversion conversion) { + Bool bool = context.getBuiltins().bool(); + if (conversion.resolveFor(target, bool.getFalse()) != null) { + return null; + } + if (conversion.resolveFor(target, bool.getTrue()) != null) { + return null; + } + return conversion.resolveFor(target, bool.getBool(), context.getBuiltins().any()); + } + + @CompilerDirectives.TruffleBoundary + static Function resolveMethodOnBool(Context context, boolean self, AtomConstructor target, UnresolvedConversion conversion) { + Bool bool = context.getBuiltins().bool(); + AtomConstructor cons = self ? bool.getTrue() : bool.getFalse(); + return conversion.resolveFor(target, cons, bool.getBool(), context.getBuiltins().any()); + } + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedConversion == conversion", + "cachedTarget == target", + "function != null" + }, + limit = "CACHE_SIZE") + static Function resolveCached( + Boolean _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("target") AtomConstructor cachedTarget, + @Cached("resolveMethodOnPrimBoolean(context, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedConversion == conversion", + "cachedTarget == target", + "unbox(_this)", + "function != null" + }, + limit = "CACHE_SIZE", + replaces = "resolveCached") + static Function resolveTrueCached( + Boolean _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("target") AtomConstructor cachedTarget, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("resolveMethodOnBool(context, _this, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedConversion == conversion", + "cachedTarget == target", + "!unbox(_this)", + "function != null" + }, + limit = "CACHE_SIZE", + replaces = "resolveCached") + static Function resolveFalseCached( + Boolean _this, + AtomConstructor target, + UnresolvedConversion conversion, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("target") AtomConstructor cachedTarget, + @CachedContext(Language.class) Context context, + @Cached("resolveMethodOnBool(context, _this, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization(replaces = {"resolveTrueCached", "resolveFalseCached"}) + static Function resolve( + Boolean _this, + AtomConstructor target, + UnresolvedConversion symbol, + @CachedContext(Language.class) Context context) + throws MethodDispatchLibrary.NoSuchConversionException { + Function function = resolveMethodOnBool(context, _this, target, symbol); + if (function == null) { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } + return function; + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultDoubleExports.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultDoubleExports.java index f9e4b388006..1375685cb1c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultDoubleExports.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultDoubleExports.java @@ -6,10 +6,13 @@ import com.oracle.truffle.api.dsl.CachedContext; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.library.GenerateLibrary; import org.enso.interpreter.Language; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.builtin.Number; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; @ExportLibrary(value = MethodDispatchLibrary.class, receiverType = Double.class) @@ -57,4 +60,60 @@ public class DefaultDoubleExports { return function; } } + + @ExportMessage + public static boolean canConvertFrom(Double receiver) { + return true; + } + + @ExportMessage + public static boolean hasSpecialConversion(Double receiver) { + return false; + } + + @ExportMessage + static class GetConversionFunction { + @CompilerDirectives.TruffleBoundary + static Function doResolve( + Context context, AtomConstructor target, UnresolvedConversion conversion) { + Number number = context.getBuiltins().number(); + return conversion.resolveFor( + target, number.getDecimal(), number.getNumber(), context.getBuiltins().any()); + } + + static final int CACHE_SIZE = 10; + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedConversion == conversion", + "cachedTarget == target", + "function != null" + }, + limit = "CACHE_SIZE") + static Function resolveCached( + Double _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("target") AtomConstructor cachedTarget, + @Cached("doResolve(context, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization(replaces = "resolveCached") + static Function resolve( + Double _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context) + throws MethodDispatchLibrary.NoSuchConversionException { + Function function = doResolve(context, target, conversion); + if (function == null) { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } + return function; + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultLongExports.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultLongExports.java index 391d3449d05..949a0869255 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultLongExports.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/DefaultLongExports.java @@ -9,7 +9,9 @@ import com.oracle.truffle.api.library.ExportMessage; import org.enso.interpreter.Language; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.builtin.Number; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; @ExportLibrary(value = MethodDispatchLibrary.class, receiverType = Long.class) @@ -60,4 +62,63 @@ public class DefaultLongExports { return function; } } + + @ExportMessage + public static boolean canConvertFrom(Long receiver) { + return true; + } + + @ExportMessage + public static boolean hasSpecialConversion(Long receiver) { + return false; + } + + @ExportMessage + static class GetConversionFunction { + @CompilerDirectives.TruffleBoundary + static Function doResolve( + Context context, AtomConstructor target, UnresolvedConversion conversion) { + Number number = context.getBuiltins().number(); + return conversion.resolveFor(target, + number.getSmallInteger(), + number.getInteger(), + number.getNumber(), + context.getBuiltins().any()); + } + + static final int CACHE_SIZE = 10; + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedConversion == conversion", + "cachedTarget == target", + "function != null" + }, + limit = "CACHE_SIZE") + static Function resolveCached( + Long _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("target") AtomConstructor cachedTarget, + @Cached("doResolve(context, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization(replaces = "resolveCached") + static Function resolve( + Long _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context) + throws MethodDispatchLibrary.NoSuchConversionException { + Function function = doResolve(context, target, conversion); + if (function == null) { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } + return function; + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/MethodDispatchLibrary.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/MethodDispatchLibrary.java index e221530e3c8..27cafbd7563 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/MethodDispatchLibrary.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/library/dispatch/MethodDispatchLibrary.java @@ -3,7 +3,9 @@ package org.enso.interpreter.runtime.library.dispatch; import com.oracle.truffle.api.library.GenerateLibrary; import com.oracle.truffle.api.library.Library; import com.oracle.truffle.api.library.LibraryFactory; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; /** @@ -75,4 +77,25 @@ public abstract class MethodDispatchLibrary extends Library { throws NoSuchMethodException { throw new NoSuchMethodException(); } + + /* * Conversions */ + + /** An exception thrown when the library cannot lookup the conversion definition. */ + public static class NoSuchConversionException extends Exception {} + + //@GenerateLibrary.Abstract(ifExported = {"getConversionFunction"}) + public boolean canConvertFrom(Object receiver) { + return false; + } + + public boolean hasSpecialConversion(Object receiver) { + return false; + } + + @GenerateLibrary.Abstract(ifExported = {"canConvertFrom"}) + public Function getConversionFunction( + Object receiver, AtomConstructor target, UnresolvedConversion symbol) + throws MethodDispatchLibrary.NoSuchConversionException { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/number/EnsoBigInteger.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/number/EnsoBigInteger.java index fe8aa13642a..edc0679427c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/number/EnsoBigInteger.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/number/EnsoBigInteger.java @@ -12,7 +12,9 @@ import com.oracle.truffle.api.library.ExportMessage; import org.enso.interpreter.Language; import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.builtin.Number; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; @@ -98,4 +100,60 @@ public class EnsoBigInteger implements TruffleObject { return function; } } + + @ExportMessage + public static boolean canConvertFrom(EnsoBigInteger receiver) { + return true; + } + + @ExportMessage + static class GetConversionFunction { + + static final int CACHE_SIZE = 10; + + @CompilerDirectives.TruffleBoundary + static Function doResolve( + Context context, AtomConstructor target, UnresolvedConversion conversion) { + Number number = context.getBuiltins().number(); + return conversion.resolveFor( + target, + number.getBigInteger(), + number.getInteger(), + number.getNumber(), + context.getBuiltins().any()); + } + + @Specialization( + guards = { + "!context.isInlineCachingDisabled()", + "cachedTarget == target", + "cachedConversion == conversion", + "function != null" + }, + limit = "CACHE_SIZE") + static Function resolveCached( + EnsoBigInteger _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context, + @Cached("conversion") UnresolvedConversion cachedConversion, + @Cached("target") AtomConstructor cachedTarget, + @Cached("doResolve(context, cachedTarget, cachedConversion)") Function function) { + return function; + } + + @Specialization(replaces = "resolveCached") + static Function resolve( + EnsoBigInteger _this, + AtomConstructor target, + UnresolvedConversion conversion, + @CachedContext(Language.class) Context context) + throws MethodDispatchLibrary.NoSuchConversionException { + Function function = doResolve(context, target, conversion); + if (function == null) { + throw new MethodDispatchLibrary.NoSuchConversionException(); + } + return function; + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java index baf3bcab847..965624314f3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java @@ -1,18 +1,16 @@ package org.enso.interpreter.runtime.scope; +import com.google.common.base.Joiner; import com.oracle.truffle.api.CompilerDirectives; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; + +import java.util.*; import com.oracle.truffle.api.interop.TruffleObject; import org.enso.interpreter.runtime.Module; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.error.RedefinedMethodException; +import org.enso.interpreter.runtime.error.RedefinedConversionException; /** A representation of Enso's per-file top-level scope. */ public class ModuleScope implements TruffleObject { @@ -21,6 +19,7 @@ public class ModuleScope implements TruffleObject { private Map polyglotSymbols = new HashMap<>(); private Map constructors = new HashMap<>(); private Map> methods = new HashMap<>(); + private Map> conversions = new HashMap<>(); private Set imports = new HashSet<>(); private Set exports = new HashSet<>(); @@ -123,6 +122,43 @@ public class ModuleScope implements TruffleObject { } } + /** + * Returns a list of the conversion methods defined in this module for a given constructor. + * + * @param cons the constructor for which method map is requested + * @return a list containing all the defined conversions in definition order + */ + private Map ensureConversionsFor(AtomConstructor cons) { + //var methods = ensureMethodMapFor(cons); + //methods. + return conversions.computeIfAbsent(cons, k -> new HashMap<>()); + } + + private Map getConversionsFor(AtomConstructor cons) { + Map result = conversions.get(cons); + if (result == null) { + return new HashMap<>(); + } + return result; + } + + + /** + * Registers a conversion method for a given type + * + * @param toType type the conversion was defined to + * @param fromType type the conversion was defined from + * @param function the {@link Function} associated with this definition + */ + public void registerConversionMethod(AtomConstructor toType, AtomConstructor fromType, Function function) { + Map sourceMap = ensureConversionsFor(toType); + if (sourceMap.containsKey(fromType)) { + throw new RedefinedConversionException(toType.getName(), fromType.getName()); + } else { + sourceMap.put(fromType, function); + } + } + /** * Registers a new symbol in the polyglot namespace. * @@ -172,6 +208,24 @@ public class ModuleScope implements TruffleObject { .orElse(null); } + @CompilerDirectives.TruffleBoundary + public Function lookupConversionDefinition(AtomConstructor atom, AtomConstructor target) { + Function definedWithAtom = atom.getDefinitionScope().getConversionsFor(target).get(atom); + if (definedWithAtom != null) { + return definedWithAtom; + } + Function definedHere = getConversionsFor(target).get(atom); + if (definedHere != null) { + return definedHere; + } + return imports.stream() + .map(scope -> scope.getExportedConversion(atom, target)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + + } + private Function getExportedMethod(AtomConstructor atom, String name) { Function here = getMethodMapFor(atom).get(name); if (here != null) { @@ -184,6 +238,18 @@ public class ModuleScope implements TruffleObject { .orElse(null); } + private Function getExportedConversion(AtomConstructor atom, AtomConstructor target) { + Function here = getConversionsFor(target).get(atom); + if (here != null) { + return here; + } + return exports.stream() + .map(scope -> scope.getConversionsFor(target).get(atom)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + /** * Adds a dependency for this module. * @@ -211,6 +277,11 @@ public class ModuleScope implements TruffleObject { return methods; } + /** @return the raw conversions map held by this module */ + public Map> getConversions() { + return conversions; + } + /** @return the polyglot symbols imported into this scope. */ public Map getPolyglotSymbols() { return polyglotSymbols; @@ -221,6 +292,7 @@ public class ModuleScope implements TruffleObject { exports = new HashSet<>(); methods = new HashMap<>(); constructors = new HashMap<>(); + conversions = new HashMap<>(); polyglotSymbols = new HashMap<>(); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java index 8f2aa41a4dc..808b54772ef 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java @@ -4,6 +4,7 @@ import com.oracle.truffle.api.dsl.TypeSystem; import com.oracle.truffle.api.interop.ArityException; import com.oracle.truffle.api.interop.UnsupportedTypeException; import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.Thunk; import org.enso.interpreter.runtime.callable.atom.Atom; @@ -41,6 +42,7 @@ import org.yaml.snakeyaml.scanner.Constant; AtomConstructor.class, Thunk.class, DataflowError.class, + UnresolvedConversion.class, UnresolvedSymbol.class, Array.class, EnsoBigInteger.class, @@ -125,7 +127,7 @@ public class Types { return Constants.THUNK; } else if (TypesGen.isDataflowError(value)) { return Constants.ERROR; - } else if (TypesGen.isUnresolvedSymbol(value)) { + } else if (TypesGen.isUnresolvedSymbol(value) || TypesGen.isUnresolvedConversion(value)) { return Constants.UNRESOLVED_SYMBOL; } else if (TypesGen.isManagedResource(value)) { return Constants.MANAGED_RESOURCE; diff --git a/engine/runtime/src/main/resources/Builtins.enso b/engine/runtime/src/main/resources/Builtins.enso index 9249aaf301b..64619260432 100644 --- a/engine/runtime/src/main/resources/Builtins.enso +++ b/engine/runtime/src/main/resources/Builtins.enso @@ -618,7 +618,7 @@ type Meta Arguments: - name: The name of the unresolved symbol. - scope: The scope in which the symbol name is unresolved. - create_unresolved_symbol : Text -> Module_Sope -> Unresolved_Symbol + create_unresolved_symbol : Text -> Module_Scope -> Unresolved_Symbol create_unresolved_symbol name scope = @Builtin_Method "Meta.create_unresolved_symbol" @@ -781,8 +781,8 @@ type Meta Arguments: - value: the value to get the type of. - get_qualified_type_name : Any -> Text - get_qualified_type_name value = @Builtin_Method "Meta.get_qualified_type_name" + get_qualified_type_name : Any -> Text + get_qualified_type_name value = @Builtin_Method "Meta.get_qualified_type_name" ## Utilities for working with primitive arrays. type Array diff --git a/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala b/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala index b476714835e..5a6c49bbaf0 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala @@ -611,12 +611,8 @@ class Compiler( ): Unit = { if (context.isStrictErrors) { val diagnostics = modules.flatMap { module => - if (module == builtins.getModule) { - List() - } else { - val errors = gatherDiagnostics(module) - List((module, errors)) - } + val errors = gatherDiagnostics(module) + List((module, errors)) } if (reportDiagnostics(diagnostics)) { throw new CompilationAbortedException diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala index 2e2ec56ff63..153851b759d 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala @@ -39,13 +39,12 @@ object AstToIr { * @param inputAST the [[AST]] representing the program to translate * @return the [[IR]] representation of `inputAST` */ - def translate(inputAST: AST): Module = { + def translate(inputAST: AST): Module = inputAST match { case AST.Module.any(inputAST) => translateModule(inputAST) case _ => throw new UnhandledEntity(inputAST, "translate") } - } /** Translates an inline program expression represented in the parser [[AST]] * into the compiler's [[IR]] representation. @@ -255,21 +254,24 @@ object AstToIr { ) case AST.Comment.any(comment) => translateComment(comment) case AstView.TypeAscription(typed, sig) => - typed match { - case AST.Ident.any(ident) => - val typeName = Name.Here(None) - val methodName = buildName(ident) - val methodReference = Name.MethodReference( - typeName, - methodName, - methodName.location - ) + def buildAscription(ident: AST.Ident): IR.Type.Ascription = { + val typeName = Name.Here(None) + val methodName = buildName(ident) + val methodReference = Name.MethodReference( + typeName, + methodName, + methodName.location + ) - IR.Type.Ascription( - methodReference, - translateExpression(sig, insideTypeSignature = true), - getIdentifiedLocation(inputAst) - ) + IR.Type.Ascription( + methodReference, + translateExpression(sig, insideTypeSignature = true), + getIdentifiedLocation(inputAst) + ) + } + typed match { + case AST.Ident.any(ident) => buildAscription(ident) + case AST.App.Section.Sides(opr) => buildAscription(opr) case AstView.MethodReference(_, _) => IR.Type.Ascription( translateMethodReference(typed), @@ -347,8 +349,13 @@ object AstToIr { getIdentifiedLocation(inputAst) ) case AstView.TypeAscription(typed, sig) => + val typedIdent = typed match { + case AST.App.Section.Sides(opr) => buildName(opr) + case AST.Ident.any(ident) => buildName(ident) + case other => translateExpression(other) + } IR.Type.Ascription( - translateExpression(typed), + typedIdent, translateExpression(sig, insideTypeSignature = true), getIdentifiedLocation(inputAst) ) @@ -721,6 +728,20 @@ object AstToIr { ) } + /** Translates an arbitrary expression, making sure to properly recognize + * qualified names. Qualified names should, probably, at some point be + * handled deeper in the compiler pipeline. + */ + private def translateQualifiedNameOrExpression(arg: AST): IR.Expression = + arg match { + case AstView.QualifiedName(segments) => + IR.Name.Qualified( + segments.map(buildName(_)), + getIdentifiedLocation(arg) + ) + case _ => translateExpression(arg) + } + /** Translates an argument definition from [[AST]] into [[IR]]. * * @param arg the argument to translate @@ -738,7 +759,7 @@ object AstToIr { case name: IR.Name => DefinitionArgument.Specified( name, - Some(translateExpression(ascType)), + Some(translateQualifiedNameOrExpression(ascType)), mValue.map(translateExpression(_)), isSuspended, getIdentifiedLocation(arg) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index 4cfe79f2411..a6f5f757bf2 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -10,91 +10,55 @@ import org.enso.compiler.data.{BindingsMap, CompilerConfig} import org.enso.compiler.exception.{BadPatternMatch, CompilerError} import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{Scope => AliasScope} import org.enso.compiler.pass.analyse.AliasAnalysis.{Graph => AliasGraph} -import org.enso.compiler.pass.analyse.{ - AliasAnalysis, - BindingAnalysis, - DataflowAnalysis, - TailCall -} +import org.enso.compiler.pass.analyse.{AliasAnalysis, BindingAnalysis, DataflowAnalysis, TailCall} import org.enso.compiler.pass.optimise.ApplicationSaturation -import org.enso.compiler.pass.resolve.{ - MethodDefinitions, - Patterns, - UppercaseNames -} +import org.enso.compiler.pass.resolve.{MethodDefinitions, Patterns, UppercaseNames} import org.enso.interpreter.epb.EpbParser import org.enso.interpreter.node.callable.argument.ReadArgumentNode -import org.enso.interpreter.node.callable.function.{ - BlockNode, - CreateFunctionNode -} +import org.enso.interpreter.node.callable.function.{BlockNode, CreateFunctionNode} import org.enso.interpreter.node.callable.thunk.{CreateThunkNode, ForceNode} -import org.enso.interpreter.node.callable.{ - ApplicationNode, - InvokeCallableNode, - SequenceLiteralNode -} +import org.enso.interpreter.node.callable.{ApplicationNode, InvokeCallableNode, SequenceLiteralNode} import org.enso.interpreter.node.controlflow.caseexpr._ import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode import org.enso.interpreter.node.expression.constant._ import org.enso.interpreter.node.expression.foreign.ForeignMethodCallNode -import org.enso.interpreter.node.expression.literal.{ - BigIntegerLiteralNode, - DecimalLiteralNode, - IntegerLiteralNode, - TextLiteralNode -} +import org.enso.interpreter.node.expression.literal.{BigIntegerLiteralNode, DecimalLiteralNode, IntegerLiteralNode, TextLiteralNode} import org.enso.interpreter.node.scope.{AssignmentNode, ReadLocalVariableNode} -import org.enso.interpreter.node.{ - BaseNode, - ClosureRootNode, - MethodRootNode, - ExpressionNode => RuntimeExpression -} +import org.enso.interpreter.node.{BaseNode, ClosureRootNode, MethodRootNode, ExpressionNode => RuntimeExpression} import org.enso.interpreter.runtime.Context -import org.enso.interpreter.runtime.callable.UnresolvedSymbol -import org.enso.interpreter.runtime.callable.argument.{ - ArgumentDefinition, - CallArgument -} +import org.enso.interpreter.runtime.callable.{UnresolvedConversion, UnresolvedSymbol} +import org.enso.interpreter.runtime.callable.argument.{ArgumentDefinition, CallArgument} import org.enso.interpreter.runtime.callable.atom.AtomConstructor -import org.enso.interpreter.runtime.callable.function.{ - FunctionSchema, - Function => RuntimeFunction -} +import org.enso.interpreter.runtime.callable.function.{FunctionSchema, Function => RuntimeFunction} import org.enso.interpreter.runtime.data.text.Text -import org.enso.interpreter.runtime.scope.{ - FramePointer, - LocalScope, - ModuleScope -} +import org.enso.interpreter.runtime.scope.{FramePointer, LocalScope, ModuleScope} import org.enso.interpreter.{Constants, Language} -import java.math.BigInteger +import java.math.BigInteger import org.enso.compiler.core.IR.Name.Special import scala.collection.mutable import scala.collection.mutable.ArrayBuffer /** This is an implementation of a codegeneration pass that lowers the Enso - * [[IR]] into the truffle [[org.enso.compiler.core.Core.Node]] structures that - * are actually executed. - * - * It should be noted that, as is, there is no support for cross-module links, - * with each lowering pass operating solely on a single module. - * - * @param context the language context instance for which this is executing - * @param source the source code that corresponds to the text for which code - * is being generated - * @param moduleScope the scope of the module for which code is being generated - * @param compilerConfig the configuration for the compiler - */ + * [[IR]] into the truffle [[org.enso.compiler.core.Core.Node]] structures that + * are actually executed. + * + * It should be noted that, as is, there is no support for cross-module links, + * with each lowering pass operating solely on a single module. + * + * @param context the language context instance for which this is executing + * @param source the source code that corresponds to the text for which code + * is being generated + * @param moduleScope the scope of the module for which code is being generated + * @param compilerConfig the configuration for the compiler + */ class IrToTruffle( - val context: Context, - val source: Source, - val moduleScope: ModuleScope, - val compilerConfig: CompilerConfig -) { + val context: Context, + val source: Source, + val moduleScope: ModuleScope, + val compilerConfig: CompilerConfig + ) { val language: Language = context.getLanguage @@ -103,31 +67,31 @@ class IrToTruffle( // ========================================================================== /** Executes the codegen pass on the input [[IR]]. - * - * Please note that the IR passed to this function should not contain _any_ - * errors (members of [[IR.Error]]). These must be dealt with and reported - * before codegen runs, as they will cause a compiler error. - * - * In future, this restriction will be relaxed to admit errors that are - * members of [[IR.Diagnostic.Kind.Interactive]], such that we can display - * these to users during interactive execution. - * - * @param ir the IR to generate code for - */ + * + * Please note that the IR passed to this function should not contain _any_ + * errors (members of [[IR.Error]]). These must be dealt with and reported + * before codegen runs, as they will cause a compiler error. + * + * In future, this restriction will be relaxed to admit errors that are + * members of [[IR.Diagnostic.Kind.Interactive]], such that we can display + * these to users during interactive execution. + * + * @param ir the IR to generate code for + */ def run(ir: IR.Module): Unit = processModule(ir) /** Executes the codegen pass on an inline input. - * - * @param ir the IR to generate code for - * @param localScope the scope in which the inline input exists - * @param scopeName the name of `localScope` - * @return an truffle expression representing `ir` - */ + * + * @param ir the IR to generate code for + * @param localScope the scope in which the inline input exists + * @param scopeName the name of `localScope` + * @return an truffle expression representing `ir` + */ def runInline( - ir: IR.Expression, - localScope: LocalScope, - scopeName: String - ): RuntimeExpression = { + ir: IR.Expression, + localScope: LocalScope, + scopeName: String + ): RuntimeExpression = { new ExpressionProcessor(localScope, scopeName).runInline(ir) } @@ -136,13 +100,13 @@ class IrToTruffle( // ========================================================================== /** Generates truffle nodes from the top-level definitions of an Enso module - * and registers these definitions in scope in the compiler. - * - * It does not directly return any constructs, but instead registers these - * constructs for later access in the compiler and language context. - * - * @param module the module for which code should be generated - */ + * and registers these definitions in scope in the compiler. + * + * It does not directly return any constructs, but instead registers these + * constructs for later access in the compiler and language context. + * + * @param module the module for which code should be generated + */ private def processModule(module: IR.Module): Unit = { generateMethods() generateReExportBindings(module) @@ -162,14 +126,10 @@ class IrToTruffle( val methodDefs = module.bindings.collect { case method: IR.Module.Scope.Definition.Method.Explicit => method } - val conversionDefs = module.bindings.collect { - case conversion: IR.Module.Scope.Definition.Method.Conversion => - conversion - } // Register the imports in scope imports.foreach { - case poly @ Import.Polyglot(i: Import.Polyglot.Java, _, _, _, _) => + case poly@Import.Polyglot(i: Import.Polyglot.Java, _, _, _, _) => this.moduleScope.registerPolyglotSymbol( poly.getVisibleName, context.getEnvironment.lookupHostSymbol(i.getJavaName) @@ -225,7 +185,7 @@ class IrToTruffle( .unsafeGetMetadata( AliasAnalysis, s"Missing scope information for method " + - s"`${methodDef.typeName.name}.${methodDef.methodName.name}`." + s"`${methodDef.typeName.name}.${methodDef.methodName.name}`." ) .unsafeAs[AliasAnalysis.Info.Scope.Root] val dataflowInfo = methodDef.unsafeGetMetadata( @@ -293,13 +253,18 @@ class IrToTruffle( } }) + val conversionDefs = module.bindings.collect { + case conversion: IR.Module.Scope.Definition.Method.Conversion => + conversion + } + // Register the conversion definitions in scope conversionDefs.foreach(methodDef => { val scopeInfo = methodDef .unsafeGetMetadata( AliasAnalysis, s"Missing scope information for conversion " + - s"`${methodDef.typeName.name}.${methodDef.methodName.name}`." + s"`${methodDef.typeName.name}.${methodDef.methodName.name}`." ) .unsafeAs[AliasAnalysis.Info.Scope.Root] val dataflowInfo = methodDef.unsafeGetMetadata( @@ -307,33 +272,11 @@ class IrToTruffle( "Method definition missing dataflow information." ) - val consOpt = - methodDef.methodReference.typePointer - .getMetadata(MethodDefinitions) - .map { res => - res.target match { - case BindingsMap.ResolvedModule(module) => - module.unsafeAsModule().getScope.getAssociatedType - case BindingsMap.ResolvedConstructor(definitionModule, cons) => - definitionModule - .unsafeAsModule() - .getScope - .getConstructors - .get(cons.name) - case BindingsMap.ResolvedPolyglotSymbol(_, _) => - throw new CompilerError( - "Impossible polyglot symbol, should be caught by MethodDefinitions pass." - ) - case _: BindingsMap.ResolvedMethod => - throw new CompilerError( - "Impossible here, should be caught by MethodDefinitions pass." - ) - } - } - - consOpt.foreach { cons => + val toOpt = getConstructorResolution(methodDef.methodReference.typePointer) + val fromOpt = getConstructorResolution(methodDef.sourceTypeName) + toOpt.zip(fromOpt).foreach { case (toType, fromType) => val expressionProcessor = new ExpressionProcessor( - cons.getName ++ Constants.SCOPE_SEPARATOR ++ methodDef.methodName.name, + toType.getName ++ Constants.SCOPE_SEPARATOR ++ methodDef.methodName.name, scopeInfo.graph, scopeInfo.graph.rootScope, dataflowInfo @@ -349,7 +292,7 @@ class IrToTruffle( moduleScope, body, makeSection(methodDef.location), - cons, + toType, methodDef.methodName.name ) val callTarget = Truffle.getRuntime.createCallTarget(rootNode) @@ -363,7 +306,7 @@ class IrToTruffle( "Conversion bodies must be functions at the point of codegen." ) } - moduleScope.registerMethod(cons, methodDef.methodName.name, function) + moduleScope.registerConversionMethod(toType, fromType, function) } }) } @@ -373,21 +316,43 @@ class IrToTruffle( // ========================================================================== /** Creates a source section from a given location in the code. - * - * @param location the location to turn into a section - * @return the source section corresponding to `location` - */ + * + * @param location the location to turn into a section + * @return the source section corresponding to `location` + */ private def makeSection( - location: Option[IdentifiedLocation] - ): SourceSection = { + location: Option[IdentifiedLocation] + ): SourceSection = { location .map(loc => source.createSection(loc.start, loc.length)) .getOrElse(source.createUnavailableSection()) } + private def getConstructorResolution(expr: IR): Option[AtomConstructor] = + expr.getMetadata(MethodDefinitions).map { res => + res.target match { + case BindingsMap.ResolvedModule(module) => + module.unsafeAsModule().getScope.getAssociatedType + case BindingsMap.ResolvedConstructor(definitionModule, cons) => + definitionModule + .unsafeAsModule() + .getScope + .getConstructors + .get(cons.name) + case BindingsMap.ResolvedPolyglotSymbol(_, _) => + throw new CompilerError( + "Impossible polyglot symbol, should be caught by MethodDefinitions pass." + ) + case _: BindingsMap.ResolvedMethod => + throw new CompilerError( + "Impossible here, should be caught by MethodDefinitions pass." + ) + } + } + private def getTailStatus( - expression: IR.Expression - ): BaseNode.TailStatus = { + expression: IR.Expression + ): BaseNode.TailStatus = { val isTailPosition = expression.getMetadata(TailCall).contains(TailCall.TailPosition.Tail) val isTailAnnotated = TailCall.isTailAnnotated(expression) @@ -403,17 +368,17 @@ class IrToTruffle( } /** Sets the source section for a given expression node to the provided - * location. - * - * @param expr the expression to set the location for - * @param location the location to assign to `expr` - * @tparam T the type of `expr` - * @return `expr` with its location set to `location` - */ + * location. + * + * @param expr the expression to set the location for + * @param location the location to assign to `expr` + * @tparam T the type of `expr` + * @return `expr` with its location set to `location` + */ private def setLocation[T <: RuntimeExpression]( - expr: T, - location: Option[IdentifiedLocation] - ): T = { + expr: T, + location: Option[IdentifiedLocation] + ): T = { location.foreach { loc => expr.setSourceLocation(loc.start, loc.length) loc.id.foreach { id => expr.setId(id) } @@ -427,7 +392,7 @@ class IrToTruffle( private def generateEnsoProjectMethod(): Unit = { val name = BindingsMap.Generated.ensoProjectMethodName - val pkg = context.getPackageOf(moduleScope.getModule.getSourceFile) + val pkg = context.getPackageOf(moduleScope.getModule.getSourceFile) val body = Truffle.getRuntime.createCallTarget( new EnsoProjectNode(language, context, pkg) ) @@ -510,31 +475,31 @@ class IrToTruffle( // ========================================================================== /** This class is responsible for performing codegen of [[IR]] constructs that - * are Enso program expressions. - * - * @param scope the scope in which the code generation is occurring - * @param scopeName the name of `scope` - */ + * are Enso program expressions. + * + * @param scope the scope in which the code generation is occurring + * @param scopeName the name of `scope` + */ sealed private class ExpressionProcessor( - val scope: LocalScope, - val scopeName: String - ) { + val scope: LocalScope, + val scopeName: String + ) { private var currentVarName = "" // === Construction ======================================================= /** Constructs an [[ExpressionProcessor]] instance with a defaulted local - * scope. - * - * @param scopeName the name to attribute to the default local scope. - */ + * scope. + * + * @param scopeName the name to attribute to the default local scope. + */ def this( - scopeName: String, - graph: AliasGraph, - scope: AliasScope, - dataflowInfo: DataflowAnalysis.Metadata - ) = { + scopeName: String, + graph: AliasGraph, + scope: AliasScope, + dataflowInfo: DataflowAnalysis.Metadata + ) = { this( new LocalScope(None, graph, scope, dataflowInfo), scopeName @@ -542,35 +507,35 @@ class IrToTruffle( } /** Creates an instance of [[ExpressionProcessor]] that operates in a child - * scope of `this`. - * - * @param name the name of the child scope - * @return an expression processor operating on a child scope - */ + * scope of `this`. + * + * @param name the name of the child scope + * @return an expression processor operating on a child scope + */ def createChild( - name: String, - scope: AliasScope - ): ExpressionProcessor = { + name: String, + scope: AliasScope + ): ExpressionProcessor = { new ExpressionProcessor(this.scope.createChild(scope), name) } // === Runner ============================================================= /** Runs the code generation process on the provided piece of [[IR]]. - * - * @param ir the IR to generate code for - * @return a truffle expression that represents the same program as `ir` - */ + * + * @param ir the IR to generate code for + * @return a truffle expression that represents the same program as `ir` + */ def run(ir: IR.Expression): RuntimeExpression = { val runtimeExpression = ir match { - case block: IR.Expression.Block => processBlock(block) - case literal: IR.Literal => processLiteral(literal) - case app: IR.Application => processApplication(app) - case name: IR.Name => processName(name) - case function: IR.Function => processFunction(function) + case block: IR.Expression.Block => processBlock(block) + case literal: IR.Literal => processLiteral(literal) + case app: IR.Application => processApplication(app) + case name: IR.Name => processName(name) + case function: IR.Function => processFunction(function) case binding: IR.Expression.Binding => processBinding(binding) - case caseExpr: IR.Case => processCase(caseExpr) - case typ: IR.Type => processType(typ) + case caseExpr: IR.Case => processCase(caseExpr) + case typ: IR.Type => processType(typ) case _: IR.Empty => throw new CompilerError( "Empty IR nodes should not exist during code generation." @@ -591,11 +556,11 @@ class IrToTruffle( } /** Executes the expression processor on a piece of code that has been - * written inline. - * - * @param ir the IR to generate code for - * @return a truffle expression that represents the same program as `ir` - */ + * written inline. + * + * @param ir the IR to generate code for + * @return a truffle expression that represents the same program as `ir` + */ def runInline(ir: IR.Expression): RuntimeExpression = { val expression = run(ir) expression @@ -604,10 +569,10 @@ class IrToTruffle( // === Processing ========================================================= /** Performs code generation for an Enso block expression. - * - * @param block the block to generate code for - * @return the truffle nodes corresponding to `block` - */ + * + * @param block the block to generate code for + * @return the truffle nodes corresponding to `block` + */ def processBlock(block: IR.Expression.Block): RuntimeExpression = { if (block.suspended) { val scopeInfo = block @@ -618,7 +583,7 @@ class IrToTruffle( .unsafeAs[AliasAnalysis.Info.Scope.Child] val childFactory = this.createChild("suspended-block", scopeInfo.scope) - val childScope = childFactory.scope + val childScope = childFactory.scope val blockNode = childFactory.processBlock(block.copy(suspended = false)) @@ -635,7 +600,7 @@ class IrToTruffle( setLocation(CreateThunkNode.build(callTarget), block.location) } else { val statementExprs = block.expressions.map(this.run).toArray - val retExpr = this.run(block.returnValue) + val retExpr = this.run(block.returnValue) val blockNode = BlockNode.build(statementExprs, retExpr) setLocation(blockNode, block.location) @@ -643,10 +608,10 @@ class IrToTruffle( } /** Performs code generation for an Enso type operator. - * - * @param value the type operation to generate code for - * @return the truffle nodes corresponding to `value` - */ + * + * @param value the type operation to generate code for + * @return the truffle nodes corresponding to `value` + */ def processType(value: IR.Type): RuntimeExpression = { setLocation( ErrorNode.build( @@ -664,16 +629,16 @@ class IrToTruffle( } /** Performs code generation for an Enso case expression. - * - * @param caseExpr the case expression to generate code for - * @return the truffle nodes corresponding to `caseExpr` - */ + * + * @param caseExpr the case expression to generate code for + * @return the truffle nodes corresponding to `caseExpr` + */ def processCase(caseExpr: IR.Case): RuntimeExpression = caseExpr match { case IR.Case.Expr(scrutinee, branches, location, _, _) => val scrutineeNode = this.run(scrutinee) - val maybeCases = branches.map(processCaseBranch) + val maybeCases = branches.map(processCaseBranch) val allCasesValid = maybeCases.forall(_.isRight) if (allCasesValid) { @@ -708,14 +673,14 @@ class IrToTruffle( } /** Performs code generation for an Enso case branch. - * - * @param branch the case branch to generate code for - * @return the truffle nodes correspondingg to `caseBranch` or an error if - * the match is invalid - */ + * + * @param branch the case branch to generate code for + * @return the truffle nodes correspondingg to `caseBranch` or an error if + * the match is invalid + */ def processCaseBranch( - branch: IR.Case.Branch - ): Either[BadPatternMatch, BranchNode] = { + branch: IR.Case.Branch + ): Either[BadPatternMatch, BranchNode] = { val scopeInfo = branch .unsafeGetMetadata( AliasAnalysis, @@ -726,7 +691,7 @@ class IrToTruffle( val childProcessor = this.createChild("case_branch", scopeInfo.scope) branch.pattern match { - case named @ Pattern.Name(_, _, _, _) => + case named@Pattern.Name(_, _, _, _) => val arg = List(genArgFromMatchField(named)) val branchCodeNode = childProcessor.processFunctionBody( @@ -739,11 +704,11 @@ class IrToTruffle( CatchAllBranchNode.build(branchCodeNode.getCallTarget) Right(branchNode) - case cons @ Pattern.Constructor(constructor, _, _, _, _) => + case cons@Pattern.Constructor(constructor, _, _, _, _) => if (!cons.isDesugared) { throw new CompilerError( "Nested patterns desugaring must have taken place by the " + - "point of code generation." + "point of code generation." ) } @@ -755,37 +720,37 @@ class IrToTruffle( case None => Left(BadPatternMatch.NonVisibleConstructor(constructor.name)) case Some( - BindingsMap.Resolution(BindingsMap.ResolvedModule(mod)) - ) => + BindingsMap.Resolution(BindingsMap.ResolvedModule(mod)) + ) => Right(mod.unsafeAsModule().getScope.getAssociatedType) case Some( - BindingsMap.Resolution( - BindingsMap.ResolvedConstructor(mod, cons) - ) - ) => + BindingsMap.Resolution( + BindingsMap.ResolvedConstructor(mod, cons) + ) + ) => Right( mod.unsafeAsModule().getScope.getConstructors.get(cons.name) ) case Some( - BindingsMap.Resolution( - BindingsMap.ResolvedPolyglotSymbol(_, _) - ) - ) => + BindingsMap.Resolution( + BindingsMap.ResolvedPolyglotSymbol(_, _) + ) + ) => throw new CompilerError( "Impossible polyglot symbol here, should be caught by Patterns resolution pass." ) case Some( - BindingsMap.Resolution( - BindingsMap.ResolvedMethod(_, _) - ) - ) => + BindingsMap.Resolution( + BindingsMap.ResolvedMethod(_, _) + ) + ) => throw new CompilerError( "Impossible method here, should be caught by Patterns resolution pass." ) } } - val fieldNames = cons.unsafeFieldsAsNamed + val fieldNames = cons.unsafeFieldsAsNamed val fieldsAsArgs = fieldNames.map(genArgFromMatchField) val branchCodeNode = childProcessor.processFunctionBody( @@ -795,12 +760,12 @@ class IrToTruffle( ) runtimeConsOpt.map { atomCons => - val any = context.getBuiltins.any - val array = context.getBuiltins.mutable.array - val bool = context.getBuiltins.bool - val number = context.getBuiltins.number + val any = context.getBuiltins.any + val array = context.getBuiltins.mutable.array + val bool = context.getBuiltins.bool + val number = context.getBuiltins.number val polyglot = context.getBuiltins.polyglot.getPolyglot - val text = context.getBuiltins.text + val text = context.getBuiltins.text val branchNode: BranchNode = if (atomCons == bool.getTrue) { BooleanBranchNode.build(true, branchCodeNode.getCallTarget) @@ -842,11 +807,11 @@ class IrToTruffle( "Branch documentation should be desugared at an earlier stage." ) case IR.Error.Pattern( - _, - IR.Error.Pattern.WrongArity(name, expected, actual), - _, - _ - ) => + _, + IR.Error.Pattern.WrongArity(name, expected, actual), + _, + _ + ) => Left(BadPatternMatch.WrongArgCount(name, expected, actual)) } @@ -862,10 +827,10 @@ class IrToTruffle( */ /** Generates an argument from a field of a pattern match. - * - * @param name the pattern field to generate from - * @return `name` as a function definition argument. - */ + * + * @param name the pattern field to generate from + * @return `name` as a function definition argument. + */ def genArgFromMatchField(name: Pattern.Name): IR.DefinitionArgument = { IR.DefinitionArgument.Specified( name.name, @@ -873,16 +838,16 @@ class IrToTruffle( None, suspended = false, name.location, - passData = name.name.passData, + passData = name.name.passData, diagnostics = name.name.diagnostics ) } /** Generates code for an Enso binding expression. - * - * @param binding the binding to generate code for - * @return the truffle nodes corresponding to `binding` - */ + * + * @param binding the binding to generate code for + * @return the truffle nodes corresponding to `binding` + */ def processBinding(binding: IR.Expression.Binding): RuntimeExpression = { val occInfo = binding .unsafeGetMetadata( @@ -902,10 +867,10 @@ class IrToTruffle( } /** Generates code for an Enso function. - * - * @param function the function to generate code for - * @return the truffle nodes corresponding to `function` - */ + * + * @param function the function to generate code for + * @return the truffle nodes corresponding to `function` + */ def processFunction(function: IR.Function): RuntimeExpression = { val scopeInfo = function .unsafeGetMetadata(AliasAnalysis, "No scope info on a function.") @@ -914,7 +879,7 @@ class IrToTruffle( if (function.body.isInstanceOf[IR.Function]) { throw new CompilerError( "Lambda found directly as function body. It looks like Lambda " + - "Consolidation hasn't run." + "Consolidation hasn't run." ) } @@ -936,10 +901,10 @@ class IrToTruffle( } /** Generates code for an Enso name. - * - * @param name the name to generate code for - * @return the truffle nodes corresponding to `name` - */ + * + * @param name the name to generate code for + * @return the truffle nodes corresponding to `name` + */ def processName(name: IR.Name): RuntimeExpression = { val nameExpr = name match { case IR.Name.Literal(nameStr, _, _, _, _, _) => @@ -950,7 +915,7 @@ class IrToTruffle( ) .unsafeAs[AliasAnalysis.Info.Occurrence] - val slot = scope.getFramePointer(useInfo.id) + val slot = scope.getFramePointer(useInfo.id) val global = name.getMetadata(UppercaseNames) if (slot.isDefined) { ReadLocalVariableNode.build(slot.get) @@ -982,6 +947,8 @@ class IrToTruffle( "Impossible here, should be desugared by UppercaseNames resolver" ) } + } else if (nameStr == "from") { + ConstantObjectNode.build(UnresolvedConversion.build(moduleScope)) } else { DynamicSymbolNode.build( UnresolvedSymbol.build(nameStr, moduleScope) @@ -994,16 +961,16 @@ class IrToTruffle( IR.Name.Literal( Constants.Names.THIS_ARGUMENT, isReferent = false, - isMethod = false, + isMethod = false, location, passData ) ) case IR.Name.Special(name, _, _, _) => val fun = name match { - case Special.NewRef => context.getBuiltins.special().getNewRef - case Special.ReadRef => context.getBuiltins.special().getReadRef - case Special.WriteRef => context.getBuiltins.special().getWriteRef + case Special.NewRef => context.getBuiltins.special().getNewRef + case Special.ReadRef => context.getBuiltins.special().getReadRef + case Special.WriteRef => context.getBuiltins.special().getWriteRef case Special.RunThread => context.getBuiltins.special().getRunThread case Special.JoinThread => context.getBuiltins.special().getJoinThread @@ -1033,18 +1000,20 @@ class IrToTruffle( } /** Generates code for an Enso literal. - * - * @param literal the literal to generate code for - * @return the truffle nodes corresponding to `literal` - */ + * + * @param literal the literal to generate code for + * @return the truffle nodes corresponding to `literal` + */ def processLiteral(literal: IR.Literal): RuntimeExpression = literal match { - case lit @ IR.Literal.Number(base, value, location, _, _) => + case lit@IR.Literal.Number(base, value, location, _, _) => val node = if (lit.isFractional) { DecimalLiteralNode.build(value.toDouble) } else if (base.isDefined) { val baseNum = - try { Integer.parseInt(base.get) } + try { + Integer.parseInt(base.get) + } catch { case _: NumberFormatException => throw new CompilerError( @@ -1077,10 +1046,10 @@ class IrToTruffle( } /** Generates a runtime implementation for compile error nodes. - * - * @param error the IR representing a compile error. - * @return a runtime node representing the error. - */ + * + * @param error the IR representing a compile error. + * @return a runtime node representing the error. + */ def processError(error: IR.Error): RuntimeExpression = { val payload: AnyRef = error match { case Error.InvalidIR(_, _, _) => @@ -1148,22 +1117,22 @@ class IrToTruffle( } /** Processes function arguments, generates arguments reads and creates - * a node to represent the whole method body. - * - * @param arguments the argument definitions - * @param body the body definition - * @return a node for the final shape of function body and pre-processed - * argument definitions. - */ + * a node to represent the whole method body. + * + * @param arguments the argument definitions + * @param body the body definition + * @return a node for the final shape of function body and pre-processed + * argument definitions. + */ def buildFunctionBody( - arguments: List[IR.DefinitionArgument], - body: IR.Expression - ): (BlockNode, Array[ArgumentDefinition]) = { + arguments: List[IR.DefinitionArgument], + body: IR.Expression + ): (BlockNode, Array[ArgumentDefinition]) = { val argFactory = new DefinitionArgumentProcessor(scopeName, scope) val argDefinitions = new Array[ArgumentDefinition](arguments.size) val argExpressions = new ArrayBuffer[RuntimeExpression] - val seenArgNames = mutable.Set[String]() + val seenArgNames = mutable.Set[String]() // Note [Rewriting Arguments] val argSlots = @@ -1211,11 +1180,11 @@ class IrToTruffle( } private def buildForeignBody( - language: EpbParser.ForeignLanguage, - code: String, - argumentNames: List[String], - argumentSlots: List[FrameSlot] - ): RuntimeExpression = { + language: EpbParser.ForeignLanguage, + code: String, + argumentNames: List[String], + argumentSlots: List[FrameSlot] + ): RuntimeExpression = { val src = EpbParser.buildSource(language, code, scopeName) val foreignCt = context.getEnvironment .parseInternal(src, argumentNames: _*) @@ -1226,17 +1195,17 @@ class IrToTruffle( } /** Generates code for an Enso function body. - * - * @param arguments the arguments to the function - * @param body the body of the function - * @param location the location at which the function exists in the source - * @return a truffle node representing the described function - */ + * + * @param arguments the arguments to the function + * @param body the body of the function + * @param location the location at which the function exists in the source + * @return a truffle node representing the described function + */ def processFunctionBody( - arguments: List[IR.DefinitionArgument], - body: IR.Expression, - location: Option[IdentifiedLocation] - ): CreateFunctionNode = { + arguments: List[IR.DefinitionArgument], + body: IR.Expression, + location: Option[IdentifiedLocation] + ): CreateFunctionNode = { val (fnBodyNode, argDefinitions) = buildFunctionBody(arguments, body) val fnRootNode = ClosureRootNode.build( language, @@ -1278,17 +1247,17 @@ class IrToTruffle( */ /** Generates code for an Enso function application. - * - * @param application the function application to generate code for - * @return the truffle nodes corresponding to `application` - */ + * + * @param application the function application to generate code for + * @return the truffle nodes corresponding to `application` + */ def processApplication(application: IR.Application): RuntimeExpression = application match { case IR.Application.Prefix(fn, args, hasDefaultsSuspended, loc, _, _) => val callArgFactory = new CallArgumentProcessor(scope, scopeName) val arguments = args - val callArgs = new ArrayBuffer[CallArgument]() + val callArgs = new ArrayBuffer[CallArgument]() for ((unprocessedArg, position) <- arguments.view.zipWithIndex) { val arg = callArgFactory.run(unprocessedArg, position) @@ -1303,8 +1272,8 @@ class IrToTruffle( val appNode = application.getMetadata(ApplicationSaturation) match { case Some( - ApplicationSaturation.CallSaturation.Exact(createOptimised) - ) => + ApplicationSaturation.CallSaturation.Exact(createOptimised) + ) => createOptimised(moduleScope)(scope)(callArgs.toList) case _ => ApplicationNode.build( @@ -1341,7 +1310,7 @@ class IrToTruffle( case sec: IR.Application.Operator.Section => throw new CompilerError( s"Explicit operator sections not supported during codegen but " + - s"$sec found" + s"$sec found" ) } } @@ -1351,34 +1320,34 @@ class IrToTruffle( // ========================================================================== /** Performs codegen for call-site arguments in Enso. - * - * @param scope the scope in which the function call exists - * @param scopeName the name of `scope` - */ + * + * @param scope the scope in which the function call exists + * @param scopeName the name of `scope` + */ sealed private class CallArgumentProcessor( - val scope: LocalScope, - val scopeName: String - ) { + val scope: LocalScope, + val scopeName: String + ) { // === Runner ============================================================= /** Executes codegen on the call-site argument. - * - * @param arg the argument definition - * @param position the position of the argument at the call site - * @return a truffle construct corresponding to the argument definition - * `arg` - */ + * + * @param arg the argument definition + * @param position the position of the argument at the call site + * @return a truffle construct corresponding to the argument definition + * `arg` + */ def run(arg: IR.CallArgument, position: Int): CallArgument = arg match { case IR.CallArgument.Specified( - name, - value, - _, - shouldBeSuspended, - _, - _ - ) => + name, + value, + _, + shouldBeSuspended, + _, + _ + ) => val scopeInfo = arg .unsafeGetMetadata( AliasAnalysis, @@ -1387,8 +1356,8 @@ class IrToTruffle( .unsafeAs[AliasAnalysis.Info.Scope.Child] val shouldSuspend = value match { - case _: IR.Name => false - case _: IR.Literal.Text => false + case _: IR.Name => false + case _: IR.Literal.Text => false case _: IR.Literal.Number => false case _ => shouldBeSuspended.getOrElse( @@ -1456,14 +1425,14 @@ class IrToTruffle( // ========================================================================== /** Performs codegen for definition-site arguments in Enso. - * - * @param scope the scope in which the function is defined - * @param scopeName the name of `scope` - */ + * + * @param scope the scope in which the function is defined + * @param scopeName the name of `scope` + */ sealed private class DefinitionArgumentProcessor( - val scopeName: String = "", - val scope: LocalScope - ) { + val scopeName: String = "", + val scope: LocalScope + ) { // === Runner ============================================================= @@ -1478,16 +1447,16 @@ class IrToTruffle( */ /** Executes the code generator on the provided definition-site argument. - * - * @param inputArg the argument to generate code for - * @param position the position of `arg` at the function definition site - * @return a truffle entity corresponding to the definition of `arg` for a - * given function - */ + * + * @param inputArg the argument to generate code for + * @param position the position of `arg` at the function definition site + * @return a truffle entity corresponding to the definition of `arg` for a + * given function + */ def run( - inputArg: IR.DefinitionArgument, - position: Int - ): ArgumentDefinition = + inputArg: IR.DefinitionArgument, + position: Int + ): ArgumentDefinition = inputArg match { case arg: IR.DefinitionArgument.Specified => val defaultExpression = arg.defaultValue diff --git a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala index c43396901d7..8f6f49d8c94 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala @@ -4244,6 +4244,8 @@ object IR { keepDiagnostics: Boolean = true, keepIdentifiers: Boolean = false ): DefinitionArgument + + def withName(ir: IR.Name): DefinitionArgument } object DefinitionArgument { @@ -4310,6 +4312,8 @@ object IR { res } + override def withName(ir: Name): DefinitionArgument = copy(name=ir) + /** @inheritdoc */ override def duplicate( keepLocations: Boolean = true, @@ -6564,9 +6568,15 @@ object IR { case class SuspendedSourceArgument(argName: String) extends Reason { override def explain: String = - s"The source type argument in a conversion (here $argName) cannot " + + s"The `that` type argument in a conversion (here $argName) cannot " + s"be suspended." } + + case class InvalidSourceArgumentName(argName: String) extends Reason { + override def explain: String = + s"The source type argument must be ignored or named `that`, but" + + s" ${argName} was found." + } } /** A representation of an error resulting from name resolution. diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala index 4e0e543dfc9..d80b4f31fc4 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala @@ -135,37 +135,39 @@ case object AliasAnalysis extends IRPass { val zippedBindings = sourceBindings.lazyZip(copyBindings) zippedBindings.foreach { case (sourceBinding, copyBinding) => - val sourceRootScopeGraph = sourceBinding - .unsafeGetMetadata( - this, - "Alias analysis must have run." - ) - .asInstanceOf[Info.Scope.Root] - .graph - val scopeMapping = mutable.Map[Scope, Scope]() - val copyRootScopeGraph = sourceRootScopeGraph.deepCopy(scopeMapping) + val sourceRootScopeGraphOpt = sourceBinding + .getMetadata(this) - val sourceNodes = sourceBinding.preorder - val copyNodes = copyBinding.preorder + sourceRootScopeGraphOpt.map { sourceRootScopeGraphScope => + val sourceRootScopeGraph = + sourceRootScopeGraphScope.asInstanceOf[Info.Scope.Root].graph - val matchedNodes = sourceNodes.lazyZip(copyNodes) + val scopeMapping = mutable.Map[Scope, Scope]() + val copyRootScopeGraph = + sourceRootScopeGraph.deepCopy(scopeMapping) - matchedNodes.foreach { case (sourceNode, copyNode) => - sourceNode.getMetadata(this) match { - case Some(meta) => - val newMeta = meta match { - case root: Info.Scope.Root => - root.copy(graph = copyRootScopeGraph) - case child: Info.Scope.Child => - child.copy( - graph = copyRootScopeGraph, - scope = child.scope.deepCopy(scopeMapping) - ) - case occ: Info.Occurrence => - occ.copy(graph = copyRootScopeGraph) - } - copyNode.updateMetadata(this -->> newMeta) - case None => + val sourceNodes = sourceBinding.preorder + val copyNodes = copyBinding.preorder + + val matchedNodes = sourceNodes.lazyZip(copyNodes) + + matchedNodes.foreach { case (sourceNode, copyNode) => + sourceNode.getMetadata(this) match { + case Some(meta) => + val newMeta = meta match { + case root: Info.Scope.Root => + root.copy(graph = copyRootScopeGraph) + case child: Info.Scope.Child => + child.copy( + graph = copyRootScopeGraph, + scope = child.scope.deepCopy(scopeMapping) + ) + case occ: Info.Occurrence => + occ.copy(graph = copyRootScopeGraph) + } + copyNode.updateMetadata(this -->> newMeta) + case None => + } } } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/FunctionBinding.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/FunctionBinding.scala index cf9ec545dc1..7ce5636c24a 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/FunctionBinding.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/FunctionBinding.scala @@ -4,6 +4,7 @@ import org.enso.compiler.context.{InlineContext, ModuleContext} import org.enso.compiler.core.IR import org.enso.compiler.core.IR.Module.Scope.Definition import org.enso.compiler.core.IR.Module.Scope.Definition.Method +import org.enso.compiler.core.ir.MetadataStorage.ToPair import org.enso.compiler.exception.CompilerError import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.analyse.{ @@ -14,6 +15,7 @@ import org.enso.compiler.pass.analyse.{ } import org.enso.compiler.pass.optimise.LambdaConsolidate import org.enso.compiler.pass.resolve.IgnoredBindings +import org.enso.interpreter.Constants import scala.annotation.unused @@ -172,10 +174,35 @@ case object FunctionBinding extends IRPass { ) } else { val firstArgumentType = args.head.ascribedType.get - val nonDefaultedArg = args.drop(1).find(_.defaultValue.isEmpty) + val firstArgumentName = args.head.name + val newFirstArgument = + if (firstArgumentName.isInstanceOf[IR.Name.Blank]) { + val newName = IR.Name + .Literal( + Constants.Names.THAT_ARGUMENT, + firstArgumentName.isReferent, + firstArgumentName.isMethod, + firstArgumentName.location + ) - if (nonDefaultedArg.isEmpty) { - val newBody = args + args.head + .withName(newName) + .updateMetadata( + IgnoredBindings -->> IgnoredBindings.State.Ignored + ) + } else { + args.head + } + val nonDefaultedArg = args.drop(1).find(_.defaultValue.isEmpty) + if (newFirstArgument.name.name != Constants.Names.THAT_ARGUMENT) { + IR.Error.Conversion( + newFirstArgument, + IR.Error.Conversion.InvalidSourceArgumentName( + newFirstArgument.name.name + ) + ) + } else if (nonDefaultedArg.isEmpty) { + val newBody = (newFirstArgument :: args.tail) .map(_.mapExpressions(desugarExpression)) .foldRight(desugarExpression(body))((arg, body) => IR.Function.Lambda(List(arg), body, None) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala index 0941f086851..20025dc11eb 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala @@ -114,8 +114,12 @@ case object OverloadsResolution extends IRPass { } } + val diagnostics = ir.bindings.collect { + case diag: IR.Diagnostic => diag + } + ir.copy( - bindings = newAtoms ::: newMethods ::: conversions + bindings = newAtoms ::: newMethods ::: conversions ::: diagnostics ) } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala index 1332d36a389..8ec03075736 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala @@ -839,8 +839,8 @@ class AliasAnalysisTest extends CompilerTest { implicit val ctx: ModuleContext = mkModuleContext val conversionMethod = - """Bar.from (value : Foo) = - | Bar value.get_thing here + """Bar.from (that : Foo) = + | Bar that.get_thing here |""".stripMargin.preprocessModule.analyse.bindings.head .asInstanceOf[Method.Conversion] diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala index 8af54261fc5..f8c3aede462 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala @@ -60,7 +60,7 @@ class BindingAnalysisTest extends CompilerTest { |Bar.baz = Baz 1 2 . foo | |from (_ : Bar) = Foo 0 0 0 - |from (value : Baz) = Foo value.x value.x value.y + |from (that : Baz) = Foo that.x that.x that.y | |Foo.from (_ : Bar) = undefined | diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala index 6ea695a2258..4ef8507bd9a 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala @@ -1594,8 +1594,8 @@ class DataflowAnalysisTest extends CompilerTest { val ir = """ - |Foo.from (value : Bar) = - | Foo value 1 + |Foo.from (that : Bar) = + | Foo that 1 |""".stripMargin.preprocessModule.analyse val depInfo = ir.getMetadata(DataflowAnalysis).get diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/TailCallTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/TailCallTest.scala index 137c15c5bcb..0f3a6d1bab2 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/TailCallTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/TailCallTest.scala @@ -96,7 +96,7 @@ class TailCallTest extends CompilerTest { | |type MyAtom a b c | - |Foo.from (value : Bar) = undefined + |Foo.from (that : Bar) = undefined |""".stripMargin.preprocessModule.analyse "mark methods as tail" in { diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala index e149f2b80e8..44577bd2c9a 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/FunctionBindingTest.scala @@ -147,7 +147,7 @@ class FunctionBindingTest extends CompilerTest { "be turned into Method.Conversion IR entities" in { val ir = - s"""My_Type.$from (value : Other) ~config=Nothing = My_Type value.a + s"""My_Type.$from (that : Other) ~config=Nothing = My_Type value.a |""".stripMargin.preprocessModule.desugar ir.bindings.head shouldBe an[IR.Module.Scope.Definition.Method.Conversion] @@ -156,7 +156,7 @@ class FunctionBindingTest extends CompilerTest { conversion.sourceTypeName.asInstanceOf[IR.Name].name shouldEqual "Other" val arguments = conversion.body.asInstanceOf[IR.Function.Lambda].arguments arguments.length shouldEqual 1 - arguments.head.name.name shouldEqual "value" + arguments.head.name.name shouldEqual "that" arguments.head.ascribedType shouldBe defined arguments.head.defaultValue should not be defined arguments.head.suspended shouldBe false @@ -210,7 +210,7 @@ class FunctionBindingTest extends CompilerTest { "return an error if the conversion does not have a source type" in { val ir = - s"""My_Type.$from value = value + value + s"""My_Type.$from that = that + that |""".stripMargin.preprocessModule.desugar ir.bindings.head shouldBe an[IR.Error.Conversion] @@ -220,7 +220,7 @@ class FunctionBindingTest extends CompilerTest { "return an error if the additional arguments don't have defaults" in { val ir = - s"""My_Type.$from (value : Other) config = value + value + s"""My_Type.$from (that : Other) config = that + that |""".stripMargin.preprocessModule.desugar ir.bindings.head shouldBe an[IR.Error.Conversion] diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/GenerateMethodBodiesTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/GenerateMethodBodiesTest.scala index 4e50cdb2dbc..74d8c5bb73b 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/GenerateMethodBodiesTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/GenerateMethodBodiesTest.scala @@ -128,7 +128,7 @@ class GenerateMethodBodiesTest extends CompilerTest { "have the `this` argument added" in { val ir = - s"""My_Type.$from (value : Other) = value.a + s"""My_Type.$from (that : Other) = that.a |""".stripMargin.preprocessModule.desugar val conversion = @@ -141,7 +141,7 @@ class GenerateMethodBodiesTest extends CompilerTest { "have their bodies replaced by an error if they redefine `this`" in { val ir = - s"""My_Type.$from (value : Other) this=1 = value.a + s"""My_Type.$from (that : Other) this=1 = that.a |""".stripMargin.preprocessModule.desugar val conversion = diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala index df362d5ca3e..d562017479f 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala @@ -62,11 +62,11 @@ class MethodDefinitionsTest extends CompilerTest { | |Does_Not_Exist.method = 32 | - |Foo.from (value : Bar) = undefined + |Foo.from (that : Bar) = undefined | - |Bar.from (value : Does_Not_Exist) = undefined + |Bar.from (that : Does_Not_Exist) = undefined | - |Does_Not_Exist.from (value : Foo) = undefined + |Does_Not_Exist.from (that : Foo) = undefined |""".stripMargin.preprocessModule.analyse "attach resolved atoms to the method definitions" in { diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleThisToHereTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleThisToHereTest.scala index 77167609110..36aa4bd7ad1 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleThisToHereTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleThisToHereTest.scala @@ -69,13 +69,13 @@ class ModuleThisToHereTest extends CompilerTest { | A -> this * here | z = y -> this + y | - |from (other : Foo) = + |from (that : Foo) = | x = this * this + this | y = case this of | A -> this * here | z = y -> this + y | - |Foo.from (other : Foo) = + |Foo.from (that : Foo) = | x = this * this + this | y = case this of | A -> this * here diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/OverloadsResolutionTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/OverloadsResolutionTest.scala index fb82262652b..efbc15405ac 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/OverloadsResolutionTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/OverloadsResolutionTest.scala @@ -85,8 +85,8 @@ class OverloadsResolutionTest extends CompilerTest { "allow overloads on the source type" in { val ir = - """Unit.from (value : Integer) = undefined - |Unit.from (value : Boolean) = undefined + """Unit.from (that : Integer) = undefined + |Unit.from (that : Boolean) = undefined |""".stripMargin.preprocessModule.resolve ir.bindings.length shouldEqual 2 @@ -96,9 +96,9 @@ class OverloadsResolutionTest extends CompilerTest { "raise an error if there are multiple definitions with the same source type" in { val ir = - """Unit.from (value : Integer) = undefined - |Unit.from (value : Boolean) = undefined - |Unit.from (value : Boolean) = undefined + """Unit.from (that : Integer) = undefined + |Unit.from (that : Boolean) = undefined + |Unit.from (that : Boolean) = undefined |""".stripMargin.preprocessModule.resolve ir.bindings.length shouldEqual 3 diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SuspendedArgumentsTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SuspendedArgumentsTest.scala index b1669a74e7f..8b40880df20 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SuspendedArgumentsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/SuspendedArgumentsTest.scala @@ -156,7 +156,7 @@ class SuspendedArgumentsTest extends CompilerTest { val ir = """File.from : Text -> Suspended -> Any - |File.from (value : Text) config=Nothing = undefined + |File.from (that : Text) config=Nothing = undefined |""".stripMargin.preprocessModule.resolve.bindings.head .asInstanceOf[Method.Conversion] @@ -172,7 +172,7 @@ class SuspendedArgumentsTest extends CompilerTest { implicit val ctx: ModuleContext = mkModuleContext val ir = - """File.from (~value : Text) = undefined + """File.from (~that : Text) = undefined |""".stripMargin.preprocessModule.resolve.bindings.head ir shouldBe an[IR.Error.Conversion] diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ConversionMethodsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ConversionMethodsTest.scala new file mode 100644 index 00000000000..8d0505fad0a --- /dev/null +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ConversionMethodsTest.scala @@ -0,0 +1,26 @@ +package org.enso.interpreter.test.semantic + +import org.enso.interpreter.test.{InterpreterContext, InterpreterTest} + +class ConversionMethodsTest extends InterpreterTest { + override def subject: String = "Methods" + + override def specify(implicit + interpreterContext: InterpreterContext + ): Unit = { + "be defined in the global scope and dispatched to" in { + val code = + """ + |type Foo foo + |type Bar bar + |type Baz baz + | + |Foo.from (that:Bar) = Foo that.bar + |Foo.from (that:Baz) = Foo that.baz + | + |main = (Foo.from (Baz 10)).foo + (Foo.from (Bar 20)).foo + |""".stripMargin + eval(code) shouldEqual 30 + } + } +} diff --git a/test/Tests/src/Data/Text/Default_Regex_Engine_Spec.enso b/test/Tests/src/Data/Text/Default_Regex_Engine_Spec.enso index b5c5e4d8605..5dab66ee92d 100644 --- a/test/Tests/src/Data/Text/Default_Regex_Engine_Spec.enso +++ b/test/Tests/src/Data/Text/Default_Regex_Engine_Spec.enso @@ -398,7 +398,8 @@ spec = Test.specify "should return Nothing if the group did not match" <| match.group 3 . should_equal Nothing -Test.specify "should fail with No_Such_Group_Error if the group did not exist" <| + + Test.specify "should fail with No_Such_Group_Error if the group did not exist" <| match.group "fail" . should_fail_with Regex.No_Such_Group_Error match.group 5 . should_fail_with Regex.No_Such_Group_Error diff --git a/test/Tests/src/Main.enso b/test/Tests/src/Main.enso index 05d229676df..ac0009eed46 100644 --- a/test/Tests/src/Main.enso +++ b/test/Tests/src/Main.enso @@ -4,6 +4,7 @@ import Standard.Test import project.Semantic.Any_Spec import project.Semantic.Case_Spec +import project.Semantic.Conversion_Spec import project.Semantic.Deep_Export.Spec as Deep_Export_Spec import project.Semantic.Error_Spec import project.Semantic.Import_Loop.Spec as Import_Loop_Spec @@ -49,6 +50,7 @@ main = Test.Suite.run_main <| Any_Spec.spec Array_Spec.spec Case_Spec.spec + Conversion_Spec.spec Deep_Export_Spec.spec Error_Spec.spec Examples_Spec.spec diff --git a/test/Tests/src/Semantic/Conversion/Methods.enso b/test/Tests/src/Semantic/Conversion/Methods.enso new file mode 100644 index 00000000000..1d7ea4a0ee6 --- /dev/null +++ b/test/Tests/src/Semantic/Conversion/Methods.enso @@ -0,0 +1,7 @@ +from Standard.Base import all +import project.Semantic.Conversion.Types + +get_foo = Types.Foo "foo" +get_bar = Types.Bar "bar" + +Text.from (that:Types.Bar) = that.a.to_text diff --git a/test/Tests/src/Semantic/Conversion/Types.enso b/test/Tests/src/Semantic/Conversion/Types.enso new file mode 100644 index 00000000000..6fce83e0339 --- /dev/null +++ b/test/Tests/src/Semantic/Conversion/Types.enso @@ -0,0 +1,6 @@ +from Standard.Base import all + +type Foo a +type Bar a + +Vector.from (that:Foo) = [that.a] diff --git a/test/Tests/src/Semantic/Conversion_Spec.enso b/test/Tests/src/Semantic/Conversion_Spec.enso new file mode 100644 index 00000000000..afa390b0fbf --- /dev/null +++ b/test/Tests/src/Semantic/Conversion_Spec.enso @@ -0,0 +1,100 @@ +from Standard.Base import all +import project.Semantic.Conversion.Methods + +import Standard.Test + +type Foo foo +type Bar bar +type Baz baz +type Quux quux +type Quaffle +type MyError err + +type NotFoo notfoo + +Foo.from (that:Bar) = Foo that.bar +Foo.from (that:Baz) = Foo that.baz +Foo.from (that:Text) = Foo that.length +Foo.from (that:Number) first_param=0 second_param=0 third_param=0 = Foo [that, first_param, second_param, third_param] +Foo.from (that:Function) = Foo (that 5) +Foo.from (that:Boolean) = Foo that +Foo.from (that:Array) = Foo that.length + +NotFoo.from (that:True) = NotFoo that +NotFoo.from (_:False) = NotFoo True +NotFoo.from (_:Any) = NotFoo "ANY!!!" + +Foo.from (_:Quaffle) = Foo "quaffle" +Foo.from (_:Error) = Foo "oops" + +foreign js make_str x = """ + return "js string" + +foreign js call_function fn arg_1 = """ + return fn(arg_1, "a string"); + +Number.foo = "foo called" + +spec = + Test.group "Conversion" <| + Test.specify "should be able to convert atoms" <| + ((Foo.from (Baz 10)).foo + (Foo.from (Bar 20)).foo) . should_equal 30 + Foo.from Quaffle . foo . should_equal "quaffle" + Test.specify "should be able to convert text" <| + Foo.from "123" . foo . should_equal 3 + Test.specify "should be able to convert foreign text" <| + Foo.from (here.make_str 4) . foo . should_equal 9 + Test.specify "should be able to convert numbers" <| + Foo.from 4 . should_equal (Foo [4, 0, 0, 0]) + Foo.from (10^100) . should_equal (Foo [10^100, 0, 0, 0]) + Foo.from 4.5 . should_equal (Foo [4.5, 0, 0, 0]) + Test.specify "should be able to convert dataflow errors" <| + Foo.from (Error.throw <| MyError "i was bad") . should_equal (Foo "oops") + Test.specify "should be able to convert functions" <| + Foo.from (e -> e) . foo . should_equal 5 + Test.specify "should be able to convert booleans" <| + Foo.from True . foo . should_be_true + Foo.from False . foo . should_be_false + NotFoo.from True . notfoo . should_be_true + NotFoo.from False . notfoo . should_be_true + Test.specify "should be able to convert arrays" <| + Foo.from [1,2,3].to_array . foo . should_equal 3 + Test.specify "should be able to convert Any" <| + NotFoo.from that=Quaffle . notfoo . should_equal "ANY!!!" + NotFoo.from 4 . notfoo . should_equal "ANY!!!" + NotFoo.from (e -> e) . notfoo . should_equal "ANY!!!" + NotFoo.from [1,2,3].to_array . notfoo . should_equal "ANY!!!" + Test.specify "should call intrinsic object conversions for unimported constructors" <| + Vector.from Methods.get_foo . should_equal ["foo"] + Test.specify "should call extension conversions" <| + Text.from Methods.get_bar . should_equal "'bar'" + + Test.specify "should fail graciously when there is no conversion" <| + Panic.recover (Foo.from (Quux 10)) . catch .to_display_text . should_equal "Could not find a conversion from `Quux` to `Foo`" + Test.specify "should fail graciously when the conversion target is invalid" <| + Panic.recover (123.from (Quux 10)) . catch .to_display_text . should_equal "123 is not a valid conversion target. Expected a type." + + Test.specify "should be callable with by-name arguments" <| + .from that=4 this=Foo . should_equal (Foo [4, 0, 0, 0]) + Test.specify "should support the use of multiple arguments" <| + Foo.from that=4 second_param=1 2 . should_equal (Foo [4, 2, 1, 0]) + + Test.specify "should play nicely with polyglot" <| + here.call_function .from Foo . should_equal (Foo 8) + + Test.specify "should support the meta functions" <| + meta_from = Meta.meta .from + is_symbol = case meta_from of + Meta.Unresolved_Symbol _ -> True + _ -> False + is_symbol.should_be_true + + .from . is_a Meta.Unresolved_Symbol . should_be_true + + meta_from.name.should_equal "from" + + Meta.meta .foo . rename "from" . should_equal .from + Meta.meta .foo . rename "from" Foo "hello" . should_equal (Foo 5) + + meta_from.rename "foo" 123 . should_equal "foo called" + meta_from.rename "foo" . should_equal .foo