From 8a70debb597cd3736e4d376a60f7977c7747ac22 Mon Sep 17 00:00:00 2001 From: Edward Kmett Date: Sun, 6 Feb 2022 04:02:09 -0500 Subject: [PATCH] Implement conversions (#180312665) (#3227) * Implement conversions start wip branch for conversion methods for collaborating with marcin add conversions to MethodDispatchLibrary (wip) start MethodDispatchLibrary implementations conversions for atoms and functions Implement a bunch of missing conversion lookups final bug fixes for merged methoddispatchlibrary implementations UnresolvedConversion.resolveFor progress on invokeConversion start extracting constructors (still not working) fix a bug add some initial conversion tests fix a bug in qualified name resolution, test conversions accross modules implement error reporting, discover a ton of ignored errors... start fixing errors that we exposed in the standard library fix remaining standard lib type errors not caused by the inability to parse type signatures for operators TODO: fix type signatures for operators. all of them are broken fix type signature parsing for operators test cases for meta & polyglot play nice with polyglot start pretending unresolved conversions are unresolved symbols treat UnresolvedConversons as UnresolvedSymbols in enso user land * update RELEASES.md * disable test error about from conversions being tail calls. (pivotal issue #181113110) * add changelog entry * fix OverloadsResolutionTest * fix MethodDefinitionsTest * fix DataflowAnalysisTest * the field name for a from conversion must be 'that'. Fix remaining tests that aren't ExpressionUpdates vs. ExecutionUpdate behavioral changes * fix ModuleThisToHereTest * feat: suppress compilation errors from Builtins * Revert "feat: suppress compilation errors from Builtins" This reverts commit 63d069bd4f059baad76b7b421c4a1330ea5ffea5. * fix tests * fix: formatting Co-authored-by: Dmitry Bushev Co-authored-by: Marcin Kostrzewa --- RELEASES.md | 6 + app/gui/CHANGELOG.md | 6 + .../src/Data/Any/Extensions.enso | 2 + .../src/Data/Map/Internal.enso | 2 +- .../0.2.32-SNAPSHOT/src/System/Platform.enso | 2 +- .../Examples/0.2.32-SNAPSHOT/src/Main.enso | 2 +- .../Table/0.2.32-SNAPSHOT/src/Data/Table.enso | 3 +- .../Test/0.2.32-SNAPSHOT/src/Main.enso | 2 +- .../java/org/enso/interpreter/Constants.java | 2 + .../node/callable/ApplicationNode.java | 1 - .../IndirectInvokeConversionNode.java | 218 ++++++ .../callable/InteropConversionCallNode.java | 102 +++ .../node/callable/InvokeCallableNode.java | 77 ++- .../node/callable/InvokeConversionNode.java | 207 ++++++ .../node/callable/InvokeMethodNode.java | 1 - ...onversionTargetErrorToDisplayTextNode.java | 42 ++ ...oSuchConversionErrorToDisplayTextNode.java | 35 + .../meta/CreateUnresolvedSymbolNode.java | 11 +- .../meta/GetUnresolvedSymbolNameNode.java | 35 +- .../meta/GetUnresolvedSymbolScopeNode.java | 29 +- .../builtin/meta/IsUnresolvedSymbolNode.java | 2 +- .../interpreter/runtime/builtin/Error.java | 38 + .../callable/UnresolvedConversion.java | 98 +++ .../runtime/callable/atom/Atom.java | 57 ++ .../callable/atom/AtomConstructor.java | 61 +- .../runtime/callable/function/Function.java | 51 ++ .../enso/interpreter/runtime/data/Array.java | 52 ++ .../interpreter/runtime/data/text/Text.java | 58 ++ .../runtime/error/DataflowError.java | 59 ++ .../runtime/error/PanicSentinel.java | 5 + .../error/RedefinedConversionException.java | 17 + .../dispatch/DefaultBooleanExports.java | 122 +++- .../dispatch/DefaultDoubleExports.java | 59 ++ .../library/dispatch/DefaultLongExports.java | 61 ++ .../dispatch/MethodDispatchLibrary.java | 23 + .../runtime/number/EnsoBigInteger.java | 58 ++ .../runtime/scope/ModuleScope.java | 84 ++- .../enso/interpreter/runtime/type/Types.java | 4 +- .../runtime/src/main/resources/Builtins.enso | 6 +- .../scala/org/enso/compiler/Compiler.scala | 8 +- .../org/enso/compiler/codegen/AstToIr.scala | 57 +- .../enso/compiler/codegen/IrToTruffle.scala | 653 +++++++++--------- .../scala/org/enso/compiler/core/IR.scala | 12 +- .../compiler/pass/analyse/AliasAnalysis.scala | 58 +- .../pass/desugar/FunctionBinding.scala | 33 +- .../pass/resolve/OverloadsResolution.scala | 6 +- .../test/pass/analyse/AliasAnalysisTest.scala | 4 +- .../pass/analyse/BindingAnalysisTest.scala | 2 +- .../pass/analyse/DataflowAnalysisTest.scala | 4 +- .../test/pass/analyse/TailCallTest.scala | 2 +- .../pass/desugar/FunctionBindingTest.scala | 8 +- .../desugar/GenerateMethodBodiesTest.scala | 4 +- .../pass/resolve/MethodDefinitionsTest.scala | 6 +- .../pass/resolve/ModuleThisToHereTest.scala | 4 +- .../resolve/OverloadsResolutionTest.scala | 10 +- .../pass/resolve/SuspendedArgumentsTest.scala | 4 +- .../test/semantic/ConversionMethodsTest.scala | 26 + .../Data/Text/Default_Regex_Engine_Spec.enso | 3 +- test/Tests/src/Main.enso | 2 + .../src/Semantic/Conversion/Methods.enso | 7 + test/Tests/src/Semantic/Conversion/Types.enso | 6 + test/Tests/src/Semantic/Conversion_Spec.enso | 100 +++ 62 files changed, 2259 insertions(+), 460 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeConversionNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropConversionCallNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeConversionNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/InvalidConversionTargetErrorToDisplayTextNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/displaytext/NoSuchConversionErrorToDisplayTextNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedConversion.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/error/RedefinedConversionException.java create mode 100644 engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ConversionMethodsTest.scala create mode 100644 test/Tests/src/Semantic/Conversion/Methods.enso create mode 100644 test/Tests/src/Semantic/Conversion/Types.enso create mode 100644 test/Tests/src/Semantic/Conversion_Spec.enso 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