diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ac88e5782..41db2cec155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,8 +105,10 @@ - [Added overloaded `from` conversions.][3227] - [Upgraded to Graal VM 21.3.0][3258] +- [Added the ability to decorate values with warnings.][3248] [3227]: https://github.com/enso-org/enso/pull/3227 +[3248]: https://github.com/enso-org/enso/pull/3248 [3258]: https://github.com/enso-org/enso/pull/3258 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index 77b2f36e3f7..20300da37da 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -22,8 +22,10 @@ import project.Meta import project.Meta.Enso_Project import project.Polyglot.Java import project.Runtime.Extensions +import project.System.Environment import project.System.File import project.Data.Text.Regex.Mode as Regex_Mode +import project.Warning from Standard.Builtins import Nothing, Number, Integer, Any, True, False, Cons, Boolean, Arithmetic_Error @@ -37,8 +39,10 @@ export project.Data.Ordering.Sort_Order export project.Data.Vector export project.Math export project.Meta +export project.System.Environment export project.System.File export project.Data.Text.Regex.Mode as Regex_Mode +export project.Warning from project.Data.Any.Extensions export all from project.Data.Array.Extensions export all diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Extensions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Extensions.enso index ba4a27ef751..d5c4df978a5 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Extensions.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Extensions.enso @@ -52,7 +52,7 @@ type Source_Location end = this.end_column.to_text row + ":" + start + "-" + end False -> - start_line + '-' + end_line + start_line.to_text + '-' + end_line.to_text cwd = File.current_directory file = this.file.absolute formatted_file = case file.is_child_of cwd of diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso new file mode 100644 index 00000000000..707ff4f0851 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso @@ -0,0 +1,69 @@ +from Standard.Base import all + +## A representation of a dataflow warning attached to a value. +type Warning + ## PRIVATE + + The constructor to wrap primitive warnings. + type Warning prim_warning + + ## UNSTABLE + + Returns the warning value – usually its explanation or other contents. + value : Any + value = Prim_Warning.get_value this.prim_warning + + ## UNSTABLE + ADVANCED + + A stack trace for the original warning creation. + origin : Vector.Vector Stack_Trace_Element + origin = Prim_Warning.get_origin this.prim_warning + + ## UNSTABLE + ADVANCED + + A list of locations where the warning was reassigned in the order of + latest-first. + + Warnings are reassigned whenever they interract with specific language + elements: + - When pattern matching, the warnings of the scrutinee will be reassigned + to the `case` expression result. + - When calling a method, warnings assigned to `this` will be reassigned to + the method return value. + - When calling a polyglot function or method, warnings assigned to any + arguments will be accumulated in the return value. + - The standard library methods reassign warnings such that their dataflow + nature is preserved. + reassignments : Vector.Vector Stack_Trace_Element + reassignments = + Vector.Vector (Prim_Warning.get_reassignments this.prim_warning) . map r-> + loc = case Polyglot.has_source_location r of + False -> Nothing + True -> Source_Location (Polyglot.get_source_location r) + Stack_Trace_Element (Polyglot.get_executable_name r) loc + +## UNSTABLE + + Attaches a new warning to the value. +attach : Any -> Any -> Any +attach warning value = + origin = Runtime.get_stack_trace + Prim_Warning.attach value warning (origin.drop_start 1) + +## UNSTABLE + + Gets all the warnings attached to the given value. Warnings are returned in the + reverse-chronological order with respect to their attachment time. +get_all : Any -> Vector.Vector Warning +get_all value = + Vector.Vector (Prim_Warning.get_all value) . map Warning + +## UNSTABLE + ADVANCED + + Sets a new list of warnings for the given value. Any warnings already present + in `value` will be lost. +set warnings value = + Prim_Warning.set value (warnings.map .prim_warning).to_array diff --git a/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso index d4db97449ce..1c6565d4002 100644 --- a/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Test/0.0.0-dev/src/Main.enso @@ -21,10 +21,14 @@ import Standard.Builtins 2*3 . should_equal 6 Suite.run_main : Any -> Nothing Suite.run_main ~specs = - r = this.run specs + r = this.run specs here.config_from_env code = if r.is_fail then 1 else 0 System.exit code +config_from_env = + only_group_regexp = Environment.get "TEST_ONLY_GROUP" + Suite_Config only_group_regexp + ## Creates a new test group, desribing properties of the object described by `this`. @@ -42,9 +46,9 @@ Suite.run_main ~specs = 2+3 . should_equal 5 Test.specify "should define multiplication" <| 2*3 . should_equal 6 -Suite.run : Any -> Any -Suite.run ~specs = - r = State.run Suite (Suite Nil) <| +Suite.run : Any -> Suite_Config -> Any +Suite.run ~specs config = + r = State.run Suite (Suite config Nil) <| specs State.get Suite r @@ -67,18 +71,20 @@ Suite.run ~specs = Test.group "Number" <| Nothing group : Text -> Any -> (Text | Nothing) -> Nothing group name ~behaviors pending=Nothing = - case pending of - Nothing -> - r = State.run Spec (Spec name Nil) <| - behaviors - State.get Spec - r.print_report - suite = State.get Suite - new_suite = Suite (Cons r suite.specs) - State.put Suite new_suite - reason -> - IO.print_err ("[PENDING] " + name) - IO.print_err (" Reason: " + reason) + config = State.get Suite . config + if config.should_run_group name then + case pending of + Nothing -> + r = State.run Spec (Spec name Nil) <| + behaviors + State.get Spec + r.print_report + suite = State.get Suite + new_suite = Suite suite.config (Cons r suite.specs) + State.put Suite new_suite + reason -> + IO.print_err ("[PENDING] " + name) + IO.print_err (" Reason: " + reason) ## Specifies a single behavior, described by `this`. @@ -435,13 +441,24 @@ Spec.print_report = IO.print_err (" - [PENDING] " + behavior.name) IO.print_err (" Reason: " + reason) +## PRVATE +type Suite_Config + type Suite_Config only_group_regexp + + should_run_group name = + regexp = this.only_group_regexp + case regexp of + Text -> name.matches regexp . catch (_->True) + _ -> True + + ## PRIVATE The top-level entry point for a test suite. Arguments: - specs: The specs contained within the test suite. -type Suite specs +type Suite config specs ## PRIVATE diff --git a/engine/runtime/src/main/java/org/enso/interpreter/Language.java b/engine/runtime/src/main/java/org/enso/interpreter/Language.java index 2fc4cd8b4a3..1437c69d2c5 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/Language.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/Language.java @@ -8,6 +8,7 @@ import com.oracle.truffle.api.TruffleLogger; import com.oracle.truffle.api.debug.DebuggerTags; import com.oracle.truffle.api.instrumentation.ProvidedTags; import com.oracle.truffle.api.instrumentation.StandardTags; +import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; import org.enso.distribution.DistributionManager; import org.enso.distribution.Environment; @@ -58,6 +59,12 @@ import org.graalvm.options.OptionDescriptors; }) public final class Language extends TruffleLanguage { private IdExecutionInstrument idExecutionInstrument; + private static final LanguageReference REFERENCE = + LanguageReference.create(Language.class); + + public static Language get(Node node) { + return REFERENCE.get(node); + } /** * Creates a new Enso context. diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java index 928c03961e6..2ea50f1f2a6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeCallableNode.java @@ -156,7 +156,8 @@ public abstract class IndirectInvokeCallableNode extends Node { schema, defaultsExecutionMode, argumentsExecutionMode, - isTail); + isTail, + thisArgumentPosition); } else { throw new RuntimeException("Currying without `this` argument is not yet supported."); } 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 index 3014370748f..d6dfe930719 100644 --- 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 @@ -23,10 +23,9 @@ 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.ArrayRope; 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.error.*; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; import org.enso.interpreter.runtime.state.Stateful; @@ -50,7 +49,8 @@ public abstract class IndirectInvokeConversionNode extends Node { CallArgumentInfo[] schema, InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, - BaseNode.TailStatus isTail); + BaseNode.TailStatus isTail, + int thatArgumentPosition); @Specialization(guards = "dispatch.canConvertFrom(that)") Stateful doConvertFrom( @@ -64,8 +64,8 @@ public abstract class IndirectInvokeConversionNode extends Node { InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, BaseNode.TailStatus isTail, + int thatArgumentPosition, @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch, - @CachedContext(Language.class) TruffleLanguage.ContextReference ctx, @Cached ConditionProfile atomProfile, @Cached ConditionProfile atomConstructorProfile, @Cached IndirectInvokeFunctionNode indirectInvokeFunctionNode) { @@ -74,7 +74,7 @@ public abstract class IndirectInvokeConversionNode extends Node { dispatch.getConversionFunction( that, InvokeConversionNode.extractConstructor( - this, _this, ctx, atomConstructorProfile, atomProfile), + this, _this, atomConstructorProfile, atomProfile), conversion); return indirectInvokeFunctionNode.execute( function, @@ -87,7 +87,11 @@ public abstract class IndirectInvokeConversionNode extends Node { isTail); } catch (MethodDispatchLibrary.NoSuchConversionException e) { throw new PanicException( - ctx.get().getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), this); + Context.get(this) + .getBuiltins() + .error() + .makeNoSuchConversionError(_this, that, conversion), + this); } } @@ -103,8 +107,8 @@ public abstract class IndirectInvokeConversionNode extends Node { InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, BaseNode.TailStatus isTail, + int thatArgumentPosition, @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch, - @CachedContext(Language.class) TruffleLanguage.ContextReference ctx, @Cached BranchProfile profile, @Cached ConditionProfile atomProfile, @Cached ConditionProfile atomConstructorProfile, @@ -114,7 +118,7 @@ public abstract class IndirectInvokeConversionNode extends Node { dispatch.getConversionFunction( that, InvokeConversionNode.extractConstructor( - this, _this, ctx, atomConstructorProfile, atomProfile), + this, _this, atomConstructorProfile, atomProfile), conversion); return indirectInvokeFunctionNode.execute( function, @@ -142,10 +146,43 @@ public abstract class IndirectInvokeConversionNode extends Node { CallArgumentInfo[] schema, InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, - BaseNode.TailStatus isTail) { + BaseNode.TailStatus isTail, + int thatArgumentPosition) { throw that; } + @Specialization + Stateful doWarning( + MaterializedFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + WithWarnings that, + Object[] arguments, + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + BaseNode.TailStatus isTail, + int thatArgumentPosition, + @Cached IndirectInvokeConversionNode childDispatch) { + arguments[thatArgumentPosition] = that.getValue(); + ArrayRope warnings = that.getReassignedWarnings(this); + Stateful result = + childDispatch.execute( + frame, + state, + conversion, + _this, + that.getValue(), + arguments, + schema, + defaultsExecutionMode, + argumentsExecutionMode, + isTail, + thatArgumentPosition); + return new Stateful(result.getState(), WithWarnings.prependTo(result.getValue(), warnings)); + } + @Specialization(guards = "interop.isString(that)") Stateful doConvertText( MaterializedFrame frame, @@ -158,12 +195,12 @@ public abstract class IndirectInvokeConversionNode extends Node { InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, BaseNode.TailStatus isTail, + int thatArgumentPosition, @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); @@ -172,7 +209,7 @@ public abstract class IndirectInvokeConversionNode extends Node { textDispatch.getConversionFunction( txt, InvokeConversionNode.extractConstructor( - this, _this, ctx, atomConstructorProfile, atomProfile), + this, _this, atomConstructorProfile, atomProfile), conversion); arguments[0] = txt; return indirectInvokeFunctionNode.execute( @@ -188,31 +225,36 @@ public abstract class IndirectInvokeConversionNode extends Node { 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); + Context.get(this) + .getBuiltins() + .error() + .makeNoSuchConversionError(_this, that, conversion), + this); } } @Specialization( - guards = { - "!methods.canConvertFrom(that)", - "!interop.isString(that)", - "!methods.hasSpecialConversion(that)" - }) + 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) { + MaterializedFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + Object that, + Object[] arguments, + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + BaseNode.TailStatus isTail, + int thatArgumentPosition, + @CachedLibrary(limit = "10") MethodDispatchLibrary methods, + @CachedLibrary(limit = "10") InteropLibrary interop) { throw new PanicException( - ctx.getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), this); + Context.get(this).getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), + this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java index d399413aad1..ea5681f1db3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/IndirectInvokeMethodNode.java @@ -27,10 +27,9 @@ 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.Array; +import org.enso.interpreter.runtime.data.ArrayRope; import org.enso.interpreter.runtime.data.text.Text; -import org.enso.interpreter.runtime.error.DataflowError; -import org.enso.interpreter.runtime.error.PanicSentinel; -import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.error.*; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; import org.enso.interpreter.runtime.number.EnsoBigInteger; import org.enso.interpreter.runtime.state.Stateful; @@ -54,7 +53,8 @@ public abstract class IndirectInvokeMethodNode extends Node { CallArgumentInfo[] schema, InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, - BaseNode.TailStatus isTail); + BaseNode.TailStatus isTail, + int thisArgumentPosition); @Specialization(guards = "dispatch.hasFunctionalDispatch(_this)") Stateful doFunctionalDispatch( @@ -67,9 +67,9 @@ public abstract class IndirectInvokeMethodNode extends Node { InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, BaseNode.TailStatus isTail, + int thisArgumentPosition, @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch, - @Cached IndirectInvokeFunctionNode invokeFunctionNode, - @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + @Cached IndirectInvokeFunctionNode invokeFunctionNode) { try { Function function = dispatch.getFunctionalDispatch(_this, symbol); return invokeFunctionNode.execute( @@ -83,7 +83,7 @@ public abstract class IndirectInvokeMethodNode extends Node { isTail); } catch (MethodDispatchLibrary.NoSuchMethodException e) { throw new PanicException( - ctx.get().getBuiltins().error().makeNoSuchMethodError(_this, symbol), this); + Context.get(this).getBuiltins().error().makeNoSuchMethodError(_this, symbol), this); } } @@ -98,6 +98,7 @@ public abstract class IndirectInvokeMethodNode extends Node { InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, BaseNode.TailStatus isTail, + int thisArgumentPosition, @Cached DataflowErrorResolverNode dataflowErrorResolverNode, @Cached IndirectInvokeFunctionNode invokeFunctionNode, @Cached ConditionProfile profile) { @@ -117,6 +118,36 @@ public abstract class IndirectInvokeMethodNode extends Node { } } + @Specialization + Stateful doWarning( + MaterializedFrame frame, + Object state, + UnresolvedSymbol symbol, + WithWarnings _this, + Object[] arguments, + CallArgumentInfo[] schema, + InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + BaseNode.TailStatus isTail, + int thisArgumentPosition, + @Cached IndirectInvokeMethodNode childDispatch) { + arguments[thisArgumentPosition] = _this.getValue(); + ArrayRope warnings = _this.getReassignedWarnings(this); + Stateful result = + childDispatch.execute( + frame, + state, + symbol, + _this.getValue(), + arguments, + schema, + defaultsExecutionMode, + argumentsExecutionMode, + isTail, + thisArgumentPosition); + return new Stateful(result.getState(), WithWarnings.prependTo(result.getValue(), warnings)); + } + @Specialization Stateful doPanicSentinel( MaterializedFrame frame, @@ -127,7 +158,8 @@ public abstract class IndirectInvokeMethodNode extends Node { CallArgumentInfo[] schema, InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, - BaseNode.TailStatus isTail) { + BaseNode.TailStatus isTail, + int thisArgumentPosition) { throw _this; } @@ -148,6 +180,7 @@ public abstract class IndirectInvokeMethodNode extends Node { InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, BaseNode.TailStatus isTail, + int thisArgumentPosition, @CachedLibrary(limit = "10") MethodDispatchLibrary methods, @CachedLibrary(limit = "10") InteropLibrary interop, @Bind("getPolyglotCallType(_this, symbol.getName(), interop)") @@ -184,10 +217,10 @@ public abstract class IndirectInvokeMethodNode extends Node { InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, BaseNode.TailStatus isTail, + int thisArgumentPosition, @CachedLibrary(limit = "10") MethodDispatchLibrary methods, @CachedLibrary(limit = "1") MethodDispatchLibrary textDispatch, @CachedLibrary(limit = "10") InteropLibrary interop, - @CachedContext(Language.class) TruffleLanguage.ContextReference ctx, @Cached IndirectInvokeFunctionNode invokeFunctionNode) { try { String str = interop.asString(_this); @@ -207,7 +240,7 @@ public abstract class IndirectInvokeMethodNode extends Node { throw new IllegalStateException("Impossible, _this is guaranteed to be a string."); } catch (MethodDispatchLibrary.NoSuchMethodException e) { throw new PanicException( - ctx.get().getBuiltins().error().makeNoSuchMethodError(_this, symbol), this); + Context.get(this).getBuiltins().error().makeNoSuchMethodError(_this, symbol), this); } } @@ -228,6 +261,7 @@ public abstract class IndirectInvokeMethodNode extends Node { InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, BaseNode.TailStatus isTail, + int thisArgumentPosition, @CachedLibrary(limit = "10") MethodDispatchLibrary methods, @CachedLibrary(limit = "10") InteropLibrary interop, @Bind("getPolyglotCallType(_this, symbol.getName(), interop)") 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 index e4ce0958362..d0ee7866325 100644 --- 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 @@ -46,20 +46,21 @@ public abstract class InteropConversionCallNode extends Node { InvokeConversionNode buildInvoker(int length) { CallArgumentInfo[] args = buildSchema(length); return InvokeConversionNode.build( - args, - DefaultsExecutionMode.EXECUTE, - ArgumentsExecutionMode.PRE_EXECUTED); + args, DefaultsExecutionMode.EXECUTE, ArgumentsExecutionMode.PRE_EXECUTED, 1); + } + + Context getContext() { + return Context.get(this); } @Specialization( - guards = {"!context.isInlineCachingDisabled()", "arguments.length == cachedArgsLength"}, + guards = {"!getContext().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) @@ -68,7 +69,7 @@ public abstract class InteropConversionCallNode extends Node { for (int i = 0; i < cachedArgsLength; i++) { args[i] = hostValueToEnsoNode.execute(arguments[i]); } - if (cachedArgsLength < 2) throw ArityException.create(2, cachedArgsLength); + if (cachedArgsLength < 2) throw ArityException.create(2, -1, cachedArgsLength); return invokerNode.execute(null, state, conversion, args[0], args[1], args).getValue(); } @@ -84,7 +85,7 @@ public abstract class InteropConversionCallNode extends Node { for (int i = 0; i < arguments.length; i++) { args[i] = hostValueToEnsoNode.execute(arguments[i]); } - if (arguments.length < 2) throw ArityException.create(2, arguments.length); + if (arguments.length < 2) throw ArityException.create(2, -1, arguments.length); return indirectInvokeConversionNode .execute( null, @@ -96,7 +97,8 @@ public abstract class InteropConversionCallNode extends Node { buildSchema(arguments.length), DefaultsExecutionMode.EXECUTE, ArgumentsExecutionMode.PRE_EXECUTED, - TailStatus.NOT_TAIL) + TailStatus.NOT_TAIL, + 1) .getValue(); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropMethodCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropMethodCallNode.java index e8eb724044d..6a05d3fb374 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropMethodCallNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropMethodCallNode.java @@ -61,7 +61,8 @@ public abstract class InteropMethodCallNode extends Node { return InvokeMethodNode.build( args, InvokeCallableNode.DefaultsExecutionMode.EXECUTE, - InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED); + InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED, + 0); } @Specialization( @@ -107,7 +108,8 @@ public abstract class InteropMethodCallNode extends Node { buildSchema(arguments.length), DefaultsExecutionMode.EXECUTE, ArgumentsExecutionMode.PRE_EXECUTED, - TailStatus.NOT_TAIL) + TailStatus.NOT_TAIL, + 0) .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 9f5591ea874..dda48abb7d9 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 @@ -112,9 +112,11 @@ public abstract class InvokeCallableNode extends BaseNode { this.invokeFunctionNode = InvokeFunctionNode.build(schema, defaultsExecutionMode, argumentsExecutionMode); this.invokeMethodNode = - InvokeMethodNode.build(schema, defaultsExecutionMode, argumentsExecutionMode); + InvokeMethodNode.build( + schema, defaultsExecutionMode, argumentsExecutionMode, thisArgumentPosition); this.invokeConversionNode = - InvokeConversionNode.build(schema, defaultsExecutionMode, argumentsExecutionMode); + InvokeConversionNode.build( + schema, defaultsExecutionMode, argumentsExecutionMode, thatArgumentPosition); } public static Integer thisArgumentPosition(CallArgumentInfo[] schema) { @@ -143,9 +145,7 @@ public abstract class InvokeCallableNode extends BaseNode { return null; } - /** - * * Creates a new instance of this node. * * @param schema a description of the arguments being applied to the callable @@ -215,20 +215,22 @@ public abstract class InvokeCallableNode extends BaseNode { } } Stateful selfResult = thisExecutor.executeThunk(selfArgument, state, TailStatus.NOT_TAIL); - Stateful thatResult = thatExecutor.executeThunk(thatArgument, selfResult.getState(), 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); + return invokeConversionNode.execute( + callerFrame, state, conversion, selfArgument, thatArgument, arguments); } else { - throw new RuntimeException("Conversion currying without `this` or `that` argument is not supported."); + 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) { @@ -261,8 +263,7 @@ public abstract class InvokeCallableNode extends BaseNode { @Fallback public Stateful invokeGeneric( Object callable, VirtualFrame callerFrame, Object state, Object[] arguments) { - Context ctx = lookupContextReference(Language.class).get(); - Atom error = ctx.getBuiltins().error().makeNotInvokableError(callable); + Atom error = Context.get(this).getBuiltins().error().makeNotInvokableError(callable); throw new PanicException(error, this); } 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 index 22ab57c5f98..5cdc8bf0d8d 100644 --- 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 @@ -1,5 +1,6 @@ package org.enso.interpreter.node.callable; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.dsl.*; import com.oracle.truffle.api.frame.VirtualFrame; @@ -20,19 +21,21 @@ 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.ArrayRope; 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.error.*; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; import org.enso.interpreter.runtime.state.Stateful; import java.util.UUID; +import java.util.concurrent.locks.Lock; public abstract class InvokeConversionNode extends BaseNode { private @Child InvokeFunctionNode invokeFunctionNode; + private @Child InvokeConversionNode childDispatch; private final ConditionProfile atomProfile = ConditionProfile.createCountingProfile(); private final ConditionProfile atomConstructorProfile = ConditionProfile.createCountingProfile(); + private final int thatArgumentPosition; /** * Creates a new node for method invocation. @@ -45,16 +48,20 @@ public abstract class InvokeConversionNode extends BaseNode { public static InvokeConversionNode build( CallArgumentInfo[] schema, InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, - InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode) { - return InvokeConversionNodeGen.create(schema, defaultsExecutionMode, argumentsExecutionMode); + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + int thatArgumentPosition) { + return InvokeConversionNodeGen.create( + schema, defaultsExecutionMode, argumentsExecutionMode, thatArgumentPosition); } InvokeConversionNode( CallArgumentInfo[] schema, InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, - InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode) { + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + int thatArgumentPosition) { this.invokeFunctionNode = InvokeFunctionNode.build(schema, defaultsExecutionMode, argumentsExecutionMode); + this.thatArgumentPosition = thatArgumentPosition; } @Override @@ -74,7 +81,6 @@ public abstract class InvokeConversionNode extends BaseNode { static AtomConstructor extractConstructor( Node thisNode, Object _this, - TruffleLanguage.ContextReference ctx, ConditionProfile atomConstructorProfile, ConditionProfile atomProfile) { if (atomConstructorProfile.profile(_this instanceof AtomConstructor)) { @@ -83,12 +89,13 @@ public abstract class InvokeConversionNode extends BaseNode { return ((Atom) _this).getConstructor(); } else { throw new PanicException( - ctx.get().getBuiltins().error().makeInvalidConversionTargetError(_this), thisNode); + Context.get(thisNode).getBuiltins().error().makeInvalidConversionTargetError(_this), + thisNode); } } - AtomConstructor extractConstructor(Object _this, TruffleLanguage.ContextReference ctx) { - return extractConstructor(this, _this, ctx, atomConstructorProfile, atomProfile); + AtomConstructor extractConstructor(Object _this) { + return extractConstructor(this, _this, atomConstructorProfile, atomProfile); } @Specialization(guards = "dispatch.canConvertFrom(that)") @@ -99,15 +106,18 @@ public abstract class InvokeConversionNode extends BaseNode { Object _this, Object that, Object[] arguments, - @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch, - @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch) { try { Function function = - dispatch.getConversionFunction(that, extractConstructor(_this, ctx), conversion); + dispatch.getConversionFunction(that, extractConstructor(_this), conversion); return invokeFunctionNode.execute(function, frame, state, arguments); } catch (MethodDispatchLibrary.NoSuchConversionException e) { throw new PanicException( - ctx.get().getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), this); + Context.get(this) + .getBuiltins() + .error() + .makeNoSuchConversionError(_this, that, conversion), + this); } } @@ -120,11 +130,10 @@ public abstract class InvokeConversionNode extends BaseNode { DataflowError that, Object[] arguments, @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch, - @Cached BranchProfile profile, - @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + @Cached BranchProfile profile) { try { Function function = - dispatch.getConversionFunction(that, extractConstructor(_this, ctx), conversion); + dispatch.getConversionFunction(that, extractConstructor(_this), conversion); return invokeFunctionNode.execute(function, frame, state, arguments); } catch (MethodDispatchLibrary.NoSuchConversionException e) { profile.enter(); @@ -134,15 +143,51 @@ public abstract class InvokeConversionNode extends BaseNode { @Specialization Stateful doPanicSentinel( - VirtualFrame frame, - Object state, - UnresolvedConversion conversion, - Object _this, - PanicSentinel that, - Object[] arguments) { + VirtualFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + PanicSentinel that, + Object[] arguments) { throw that; } + @Specialization + Stateful doWarning( + VirtualFrame frame, + Object state, + UnresolvedConversion conversion, + Object _this, + WithWarnings that, + Object[] arguments) { + // Cannot use @Cached for childDispatch, because we need to call notifyInserted. + if (childDispatch == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + Lock lock = getLock(); + lock.lock(); + try { + if (childDispatch == null) { + childDispatch = + insert( + build( + invokeFunctionNode.getSchema(), + invokeFunctionNode.getDefaultsExecutionMode(), + invokeFunctionNode.getArgumentsExecutionMode(), + thatArgumentPosition)); + childDispatch.setTailStatus(getTailStatus()); + notifyInserted(childDispatch); + } + } finally { + lock.unlock(); + } + } + arguments[thatArgumentPosition] = that.getValue(); + ArrayRope warnings = that.getReassignedWarnings(this); + Stateful result = + childDispatch.execute(frame, state, conversion, _this, that.getValue(), arguments); + return new Stateful(result.getState(), WithWarnings.prependTo(result.getValue(), warnings)); + } + @Specialization(guards = "interop.isString(that)") Stateful doConvertText( VirtualFrame frame, @@ -153,20 +198,23 @@ public abstract class InvokeConversionNode extends BaseNode { Object[] arguments, @CachedLibrary(limit = "10") MethodDispatchLibrary methods, @CachedLibrary(limit = "1") MethodDispatchLibrary textDispatch, - @CachedLibrary(limit = "10") InteropLibrary interop, - @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + @CachedLibrary(limit = "10") InteropLibrary interop) { try { String str = interop.asString(that); Text txt = Text.create(str); Function function = - textDispatch.getConversionFunction(txt, extractConstructor(_this, ctx), conversion); + textDispatch.getConversionFunction(txt, extractConstructor(_this), 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); + Context.get(this) + .getBuiltins() + .error() + .makeNoSuchConversionError(_this, that, conversion), + this); } } @@ -184,10 +232,10 @@ public abstract class InvokeConversionNode extends BaseNode { Object that, Object[] arguments, @CachedLibrary(limit = "10") MethodDispatchLibrary methods, - @CachedLibrary(limit = "10") InteropLibrary interop, - @CachedContext(Language.class) Context ctx) { + @CachedLibrary(limit = "10") InteropLibrary interop) { throw new PanicException( - ctx.getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), this); + Context.get(this).getBuiltins().error().makeNoSuchConversionError(_this, that, conversion), + this); } @Override 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 690927386f9..dfca6bd78e0 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 @@ -1,5 +1,6 @@ package org.enso.interpreter.node.callable; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.dsl.*; import com.oracle.truffle.api.frame.VirtualFrame; @@ -12,6 +13,8 @@ import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.ConditionProfile; import com.oracle.truffle.api.source.SourceSection; import java.util.UUID; +import java.util.concurrent.locks.Lock; + import org.enso.interpreter.Language; import org.enso.interpreter.node.BaseNode; import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode; @@ -21,10 +24,9 @@ import org.enso.interpreter.runtime.Context; 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.ArrayRope; import org.enso.interpreter.runtime.data.text.Text; -import org.enso.interpreter.runtime.error.DataflowError; -import org.enso.interpreter.runtime.error.PanicSentinel; -import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.error.*; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; import org.enso.interpreter.runtime.state.Stateful; @@ -32,7 +34,10 @@ 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 @Child InvokeMethodNode childDispatch; private final int argumentCount; + private final int thisArgumentPosition; /** * Creates a new node for method invocation. @@ -45,23 +50,30 @@ public abstract class InvokeMethodNode extends BaseNode { public static InvokeMethodNode build( CallArgumentInfo[] schema, InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, - InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode) { - return InvokeMethodNodeGen.create(schema, defaultsExecutionMode, argumentsExecutionMode); + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + int thisArgumentPosition) { + return InvokeMethodNodeGen.create( + schema, defaultsExecutionMode, argumentsExecutionMode, thisArgumentPosition); } InvokeMethodNode( CallArgumentInfo[] schema, InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode, - InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode) { + InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode, + int thisArgumentPosition) { this.invokeFunctionNode = InvokeFunctionNode.build(schema, defaultsExecutionMode, argumentsExecutionMode); this.argumentCount = schema.length; + this.thisArgumentPosition = thisArgumentPosition; } @Override public void setTailStatus(TailStatus tailStatus) { super.setTailStatus(tailStatus); this.invokeFunctionNode.setTailStatus(tailStatus); + if (childDispatch != null) { + childDispatch.setTailStatus(tailStatus); + } } public abstract Stateful execute( @@ -74,14 +86,13 @@ public abstract class InvokeMethodNode extends BaseNode { UnresolvedSymbol symbol, Object _this, Object[] arguments, - @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch, - @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + @CachedLibrary(limit = "10") MethodDispatchLibrary dispatch) { try { Function function = dispatch.getFunctionalDispatch(_this, symbol); return invokeFunctionNode.execute(function, frame, state, arguments); } catch (MethodDispatchLibrary.NoSuchMethodException e) { throw new PanicException( - ctx.get().getBuiltins().error().makeNoSuchMethodError(_this, symbol), this); + Context.get(this).getBuiltins().error().makeNoSuchMethodError(_this, symbol), this); } } @@ -111,6 +122,41 @@ public abstract class InvokeMethodNode extends BaseNode { throw _this; } + @Specialization + Stateful doWarning( + VirtualFrame frame, + Object state, + UnresolvedSymbol symbol, + WithWarnings _this, + Object[] arguments) { + // Cannot use @Cached for childDispatch, because we need to call notifyInserted. + if (childDispatch == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + Lock lock = getLock(); + lock.lock(); + try { + if (childDispatch == null) { + childDispatch = + insert( + build( + invokeFunctionNode.getSchema(), + invokeFunctionNode.getDefaultsExecutionMode(), + invokeFunctionNode.getArgumentsExecutionMode(), + thisArgumentPosition)); + childDispatch.setTailStatus(getTailStatus()); + notifyInserted(childDispatch); + } + } finally { + lock.unlock(); + } + } + + arguments[thisArgumentPosition] = _this.getValue(); + ArrayRope warnings = _this.getReassignedWarnings(this); + Stateful result = childDispatch.execute(frame, state, symbol, _this.getValue(), arguments); + return new Stateful(result.getState(), WithWarnings.prependTo(result.getValue(), warnings)); + } + @ExplodeLoop @Specialization( guards = { @@ -131,19 +177,33 @@ public abstract class InvokeMethodNode extends BaseNode { HostMethodCallNode.PolyglotCallType polyglotCallType, @Cached(value = "buildExecutors()") ThunkExecutorNode[] argExecutors, @Cached(value = "buildProfiles()", dimensions = 1) BranchProfile[] profiles, + @Cached(value = "buildProfiles()", dimensions = 1) BranchProfile[] warningProfiles, + @Cached BranchProfile anyWarningsProfile, @Cached HostMethodCallNode hostMethodCallNode) { Object[] args = new Object[argExecutors.length]; + boolean anyWarnings = false; + ArrayRope accumulatedWarnings = new ArrayRope<>(); for (int i = 0; i < argExecutors.length; i++) { Stateful r = argExecutors[i].executeThunk(arguments[i + 1], state, TailStatus.NOT_TAIL); + state = r.getState(); + args[i] = r.getValue(); if (r.getValue() instanceof DataflowError) { profiles[i].enter(); return r; + } else if (r.getValue() instanceof WithWarnings) { + warningProfiles[i].enter(); + anyWarnings = true; + accumulatedWarnings = + accumulatedWarnings.append(((WithWarnings) r.getValue()).getReassignedWarnings(this)); + args[i] = ((WithWarnings) r.getValue()).getValue(); } - state = r.getState(); - args[i] = r.getValue(); } - return new Stateful( - state, hostMethodCallNode.execute(polyglotCallType, symbol.getName(), _this, args)); + Object res = hostMethodCallNode.execute(polyglotCallType, symbol.getName(), _this, args); + if (anyWarnings) { + anyWarningsProfile.enter(); + res = WithWarnings.prependTo(res, accumulatedWarnings); + } + return new Stateful(state, res); } @Specialization( @@ -160,8 +220,7 @@ public abstract class InvokeMethodNode extends BaseNode { Object[] arguments, @CachedLibrary(limit = "10") MethodDispatchLibrary methods, @CachedLibrary(limit = "1") MethodDispatchLibrary textDispatch, - @CachedLibrary(limit = "10") InteropLibrary interop, - @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + @CachedLibrary(limit = "10") InteropLibrary interop) { try { String str = interop.asString(_this); Text txt = Text.create(str); @@ -172,7 +231,7 @@ public abstract class InvokeMethodNode extends BaseNode { throw new IllegalStateException("Impossible, _this is guaranteed to be a string."); } catch (MethodDispatchLibrary.NoSuchMethodException e) { throw new PanicException( - ctx.get().getBuiltins().error().makeNoSuchMethodError(_this, symbol), this); + Context.get(this).getBuiltins().error().makeNoSuchMethodError(_this, symbol), this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/GatherWarningsNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/GatherWarningsNode.java new file mode 100644 index 00000000000..bb1edc1ce5b --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/argument/GatherWarningsNode.java @@ -0,0 +1,45 @@ +package org.enso.interpreter.node.callable.argument; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.nodes.ExplodeLoop; +import com.oracle.truffle.api.profiles.ConditionProfile; +import org.enso.interpreter.node.BaseNode; +import org.enso.interpreter.runtime.data.ArrayRope; +import org.enso.interpreter.runtime.error.Warning; +import org.enso.interpreter.runtime.error.WithWarnings; + +import java.util.stream.Stream; + +public class GatherWarningsNode extends BaseNode { + private final int argsCount; + private final @CompilerDirectives.CompilationFinal(dimensions = 1) ConditionProfile[] profiles; + + private GatherWarningsNode(int argsCount) { + this.argsCount = argsCount; + this.profiles = + Stream.generate(ConditionProfile::createCountingProfile) + .limit(argsCount) + .toArray(ConditionProfile[]::new); + } + + public static GatherWarningsNode create(int argsCount) { + return new GatherWarningsNode(argsCount); + } + + @ExplodeLoop + public ArrayRope execute(Object... arguments) { + ArrayRope result = new ArrayRope<>(); + boolean anyFound = false; + for (int i = 0; i < argsCount; i++) { + if (profiles[i].profile(arguments[i] instanceof WithWarnings)) { + anyFound = true; + result = result.append(((WithWarnings) arguments[i]).getWarnings()); + } + } + if (anyFound) { + return result; + } else { + return null; + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/InvokeFunctionNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/InvokeFunctionNode.java index b4f3f14221f..9631756b34f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/InvokeFunctionNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/InvokeFunctionNode.java @@ -160,15 +160,15 @@ public abstract class InvokeFunctionNode extends BaseNode { public abstract Stateful execute( Function callable, VirtualFrame callerFrame, Object state, Object[] arguments); - CallArgumentInfo[] getSchema() { + public CallArgumentInfo[] getSchema() { return schema; } - InvokeCallableNode.DefaultsExecutionMode getDefaultsExecutionMode() { + public InvokeCallableNode.DefaultsExecutionMode getDefaultsExecutionMode() { return this.defaultsExecutionMode; } - InvokeCallableNode.ArgumentsExecutionMode getArgumentsExecutionMode() { + public InvokeCallableNode.ArgumentsExecutionMode getArgumentsExecutionMode() { return argumentsExecutionMode; } @@ -187,4 +187,5 @@ public abstract class InvokeFunctionNode extends BaseNode { public void setId(UUID id) { functionCallInstrumentationNode.setId(id); } + } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java index 6719d5b6978..3c87ab8f2c4 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java @@ -12,9 +12,8 @@ import com.oracle.truffle.api.nodes.NodeInfo; import org.enso.interpreter.Language; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.runtime.Context; -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.data.ArrayRope; +import org.enso.interpreter.runtime.error.*; import org.enso.interpreter.runtime.type.TypesGen; /** @@ -72,6 +71,13 @@ public abstract class CaseNode extends ExpressionNode { throw sentinel; } + @Specialization + Object doWarning(VirtualFrame frame, WithWarnings object) { + ArrayRope warnings = object.getReassignedWarnings(this); + Object result = doMatch(frame, object.getValue()); + return WithWarnings.appendTo(result, warnings); + } + /** * Executes the case expression. * @@ -80,12 +86,10 @@ public abstract class CaseNode extends ExpressionNode { * @param ctx the language context reference * @return the result of executing the case expression on {@code object} */ - @Specialization(guards = {"!isDataflowError(object)", "!isPanicSentinel(object)"}) + @Specialization( + guards = {"!isDataflowError(object)", "!isPanicSentinel(object)", "!isWarning(object)"}) @ExplodeLoop - public Object doMatch( - VirtualFrame frame, - Object object, - @CachedContext(Language.class) TruffleLanguage.ContextReference ctx) { + public Object doMatch(VirtualFrame frame, Object object) { Object state = FrameUtil.getObjectSafe(frame, getStateFrameSlot()); try { for (BranchNode branchNode : cases) { @@ -93,7 +97,11 @@ public abstract class CaseNode extends ExpressionNode { } CompilerDirectives.transferToInterpreter(); throw new PanicException( - ctx.get().getBuiltins().error().inexhaustivePatternMatchError().newInstance(object), + Context.get(this) + .getBuiltins() + .error() + .inexhaustivePatternMatchError() + .newInstance(object), this); } catch (BranchSelectedException e) { // Note [Branch Selection Control Flow] @@ -110,6 +118,10 @@ public abstract class CaseNode extends ExpressionNode { return TypesGen.isPanicSentinel(sentinel); } + boolean isWarning(Object warning) { + return warning instanceof WithWarnings; + } + /* Note [Branch Selection Control Flow] * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Truffle provides no easy way to return control flow from multiple paths. This is entirely due diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java index 534b3091c2a..e830d8b5320 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/InstantiateNode.java @@ -7,7 +7,11 @@ import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.ConditionProfile; import org.enso.interpreter.node.ExpressionNode; +import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.data.ArrayRope; +import org.enso.interpreter.runtime.error.Warning; +import org.enso.interpreter.runtime.error.WithWarnings; import org.enso.interpreter.runtime.type.TypesGen; /** @@ -19,16 +23,20 @@ public class InstantiateNode extends ExpressionNode { private final AtomConstructor constructor; private @Children ExpressionNode[] arguments; private @CompilationFinal(dimensions = 1) ConditionProfile[] profiles; + private @CompilationFinal(dimensions = 1) ConditionProfile[] warningProfiles; private @CompilationFinal(dimensions = 1) BranchProfile[] sentinelProfiles; + private final ConditionProfile anyWarningsProfile = ConditionProfile.createCountingProfile(); InstantiateNode(AtomConstructor constructor, ExpressionNode[] arguments) { this.constructor = constructor; this.arguments = arguments; this.profiles = new ConditionProfile[arguments.length]; this.sentinelProfiles = new BranchProfile[arguments.length]; + this.warningProfiles = new ConditionProfile[arguments.length]; for (int i = 0; i < arguments.length; ++i) { this.profiles[i] = ConditionProfile.createCountingProfile(); this.sentinelProfiles[i] = BranchProfile.create(); + this.warningProfiles[i] = ConditionProfile.createCountingProfile(); } } @@ -54,12 +62,21 @@ public class InstantiateNode extends ExpressionNode { @ExplodeLoop public Object executeGeneric(VirtualFrame frame) { Object[] argumentValues = new Object[arguments.length]; + boolean anyWarnings = false; + ArrayRope accumulatedWarnings = new ArrayRope<>(); for (int i = 0; i < arguments.length; i++) { ConditionProfile profile = profiles[i]; + ConditionProfile warningProfile = warningProfiles[i]; BranchProfile sentinelProfile = sentinelProfiles[i]; Object argument = arguments[i].executeGeneric(frame); if (profile.profile(TypesGen.isDataflowError(argument))) { return argument; + } else if (warningProfile.profile(argument instanceof WithWarnings)) { + anyWarnings = true; + WithWarnings originalArg = (WithWarnings) argument; + accumulatedWarnings = + accumulatedWarnings.append(originalArg.getReassignedWarnings(this)); + argumentValues[i] = originalArg.getValue(); } else if (TypesGen.isPanicSentinel(argument)) { sentinelProfile.enter(); throw TypesGen.asPanicSentinel(argument); @@ -67,6 +84,10 @@ public class InstantiateNode extends ExpressionNode { argumentValues[i] = argument; } } - return constructor.newInstance(argumentValues); + if (anyWarningsProfile.profile(anyWarnings)) { + return WithWarnings.appendTo(constructor.newInstance(argumentValues), accumulatedWarnings); + } else { + return constructor.newInstance(argumentValues); + } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java index 8a8fa58c73f..58e632878db 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/InstantiateAtomNode.java @@ -40,7 +40,7 @@ public class InstantiateAtomNode extends RootNode { */ @Override public String getName() { - return "constructor::" + name; + return name; } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/PutStateNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/PutStateNode.java index 1b18396bda0..ed63e0206e5 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/PutStateNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/state/PutStateNode.java @@ -8,6 +8,7 @@ import com.oracle.truffle.api.dsl.ReportPolymorphism; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.AcceptsWarning; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.MonadicState; import org.enso.interpreter.runtime.Context; @@ -24,7 +25,8 @@ public abstract class PutStateNode extends Node { return PutStateNodeGen.create(); } - abstract Stateful execute(@MonadicState Object state, Object _this, Object key, Object new_state); + abstract Stateful execute( + @MonadicState Object state, Object _this, Object key, Object new_state); @Specialization(guards = "state.getKey() == key") Stateful doExistingSingleton(SingletonMap state, Object _this, Object key, Object new_state) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/AttachWarningNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/AttachWarningNode.java new file mode 100644 index 00000000000..9074e361a64 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/AttachWarningNode.java @@ -0,0 +1,34 @@ +package org.enso.interpreter.node.expression.builtin.warning; + +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.dsl.AcceptsWarning; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.error.DataflowError; +import org.enso.interpreter.runtime.error.Warning; +import org.enso.interpreter.runtime.error.WithWarnings; + +@BuiltinMethod( + type = "Prim_Warning", + name = "attach", + description = "Attaches the given warning to the value.") +public abstract class AttachWarningNode extends Node { + abstract WithWarnings execute( + Object _this, @AcceptsWarning Object value, Object warning, Object origin); + + static AttachWarningNode build() { + return AttachWarningNodeGen.create(); + } + + @Specialization + WithWarnings doWarning(Object _this, WithWarnings value, Object warning, Object origin) { + return value.prepend(new Warning(warning, origin, Context.get(this).clockTick())); + } + + @Fallback + WithWarnings doOther(Object _this, Object value, Object warning, Object origin) { + return new WithWarnings(value, new Warning(warning, origin, Context.get(this).clockTick())); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetOriginNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetOriginNode.java new file mode 100644 index 00000000000..edfa0e49607 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetOriginNode.java @@ -0,0 +1,15 @@ +package org.enso.interpreter.node.expression.builtin.warning; + +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.error.Warning; + +@BuiltinMethod( + type = "Prim_Warning", + name = "get_origin", + description = "Gets the payload of the warning.") +public class GetOriginNode extends Node { + Object execute(Object _this, Warning warning) { + return warning.getOrigin(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetReassignmentsNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetReassignmentsNode.java new file mode 100644 index 00000000000..cd3ff731a86 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetReassignmentsNode.java @@ -0,0 +1,23 @@ +package org.enso.interpreter.node.expression.builtin.warning; + +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.error.Warning; + +import java.util.Arrays; +import java.util.Comparator; + +@BuiltinMethod( + type = "Prim_Warning", + name = "get_reassignments", + description = "Gets the list of locations where the warnings was reassigned.") +public class GetReassignmentsNode extends Node { + Array execute(Object _this, Warning warning) { + Warning.Reassignment[] reassignments = + warning.getReassignments().toArray(Warning.Reassignment[]::new); + return new Array(Arrays.copyOf(reassignments, reassignments.length, Object[].class)); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetValueNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetValueNode.java new file mode 100644 index 00000000000..e80320a6406 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetValueNode.java @@ -0,0 +1,15 @@ +package org.enso.interpreter.node.expression.builtin.warning; + +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.error.Warning; + +@BuiltinMethod( + type = "Prim_Warning", + name = "get_value", + description = "Gets the payload of the warning.") +public class GetValueNode extends Node { + Object execute(Object _this, Warning warning) { + return warning.getValue(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetWarningsNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetWarningsNode.java new file mode 100644 index 00000000000..192c6c08c87 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/GetWarningsNode.java @@ -0,0 +1,39 @@ +package org.enso.interpreter.node.expression.builtin.warning; + +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.dsl.AcceptsWarning; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.error.Warning; +import org.enso.interpreter.runtime.error.WithWarnings; + +import java.util.Arrays; +import java.util.Comparator; + +@BuiltinMethod( + type = "Prim_Warning", + name = "get_all", + description = "Gets all the warnings associated with the value.") +public abstract class GetWarningsNode extends Node { + abstract Array execute(Object _this, @AcceptsWarning Object value); + + static GetWarningsNode build() { + return GetWarningsNodeGen.create(); + } + + @Specialization + Array doWarning(Object _this, WithWarnings value) { + Warning[] warnings = value.getWarningsArray(); + Arrays.sort(warnings, Comparator.comparing(Warning::getCreationTime).reversed()); + Object[] result = new Object[warnings.length]; + System.arraycopy(warnings, 0, result, 0, warnings.length); + return new Array(result); + } + + @Fallback + Array doOther(Object _this, Object value) { + return new Array(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/SetNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/SetNode.java new file mode 100644 index 00000000000..54af61ba764 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/warning/SetNode.java @@ -0,0 +1,43 @@ +package org.enso.interpreter.node.expression.builtin.warning; + +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.dsl.AcceptsWarning; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.error.Warning; +import org.enso.interpreter.runtime.error.WithWarnings; + +@BuiltinMethod( + type = "Prim_Warning", + name = "set", + description = "Attaches the given warning to the value.") +public abstract class SetNode extends Node { + abstract Object execute(Object _this, @AcceptsWarning Object value, Array warnings); + + static SetNode build() { + return SetNodeGen.create(); + } + + @Specialization + Object doWarning(Object _this, WithWarnings value, Array warnings) { + if (warnings.length() == 0) { + return value.getValue(); + } + Warning[] warningsCast = new Warning[warnings.length()]; + System.arraycopy(warnings.getItems(), 0, warningsCast, 0, warningsCast.length); + return new WithWarnings(value.getValue(), warningsCast); + } + + @Fallback + Object doOther(Object _this, Object value, Array warnings) { + if (warnings.length() == 0) { + return value; + } + Warning[] warningsCast = new Warning[warnings.length()]; + System.arraycopy(warnings.getItems(), 0, warningsCast, 0, warningsCast.length); + return new WithWarnings(value, warningsCast); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java index 026453fac8c..0ff7c256a7c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java @@ -31,6 +31,7 @@ import scala.jdk.javaapi.OptionConverters; import java.io.*; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; /** * The language context is the internal state of the language that is associated with each thread in @@ -62,6 +63,7 @@ public class Context { private final TruffleLogger logger = TruffleLogger.getLogger(LanguageInfo.ID, Context.class); private final DistributionManager distributionManager; private final LockManager lockManager; + private final AtomicLong clock = new AtomicLong(); /** * Creates a new Enso context. @@ -151,6 +153,10 @@ public class Context { return REFERENCE.get(node); } + public static TruffleLanguage.ContextReference getReference() { + return REFERENCE; + } + /** Performs eventual cleanup before the context is disposed of. */ public void shutdown() { threadManager.shutdown(); @@ -449,4 +455,8 @@ public class Context { public TruffleLogger getLogger(Class klass) { return TruffleLogger.getLogger(LanguageInfo.ID, klass); } + + public long clockTick() { + return clock.getAndIncrement(); + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java index 169aebe6d09..234735c2bf6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java @@ -93,6 +93,7 @@ public class Builtins { bool = new Bool(language, scope); debug = new AtomConstructor("Debug", scope).initializeFields(); dataflowError = new DataflowError(language, scope); + Warning.initWarningMethods(language, scope); projectDescription = new AtomConstructor("Project_Description", scope) .initializeFields( diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Warning.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Warning.java new file mode 100644 index 00000000000..1900b506dd0 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Warning.java @@ -0,0 +1,21 @@ +package org.enso.interpreter.runtime.builtin; + +import org.enso.interpreter.Language; +import org.enso.interpreter.node.expression.builtin.warning.*; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.scope.ModuleScope; + +public class Warning { + public static void initWarningMethods(Language language, ModuleScope scope) { + var warning = new AtomConstructor("Prim_Warning", scope).initializeFields(); + + scope.registerConstructor(warning); + scope.registerMethod(warning, "get_all", GetWarningsMethodGen.makeFunction(language)); + scope.registerMethod(warning, "attach", AttachWarningMethodGen.makeFunction(language)); + scope.registerMethod(warning, "get_value", GetValueMethodGen.makeFunction(language)); + scope.registerMethod(warning, "get_origin", GetOriginMethodGen.makeFunction(language)); + scope.registerMethod( + warning, "get_reassignments", GetReassignmentsMethodGen.makeFunction(language)); + scope.registerMethod(warning, "set", SetMethodGen.makeFunction(language)); + } +} 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 0abc7f645b6..090cbc6cb1e 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 @@ -82,7 +82,9 @@ public final class AtomConstructor implements TruffleObject { argumentReaders[i] = ReadArgumentNode.build(i, args[i].getDefaultValue().orElse(null)); } ExpressionNode instantiateNode = InstantiateNode.build(this, argumentReaders); - RootNode rootNode = InstantiateAtomNode.build(null, name, instantiateNode); + RootNode rootNode = + InstantiateAtomNode.build( + null, definitionScope.getModule().getName().item() + "." + name, instantiateNode); RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode); return new Function(callTarget, null, new FunctionSchema(args)); } @@ -303,7 +305,8 @@ public final class AtomConstructor implements TruffleObject { @Cached("conversion") UnresolvedConversion cachedConversion, @Cached("target") AtomConstructor cachedTarget, @Cached("_this") AtomConstructor cachedConstructor, - @Cached("doResolve(context, cachedConstructor, cachedTarget, cachedConversion)") Function function) { + @Cached("doResolve(context, cachedConstructor, cachedTarget, cachedConversion)") + Function function) { return function; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ArrayRope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ArrayRope.java new file mode 100644 index 00000000000..9ee3a1c5c81 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ArrayRope.java @@ -0,0 +1,121 @@ +package org.enso.interpreter.runtime.data; + +import java.util.Arrays; +import java.util.function.Function; + +public final class ArrayRope { + private final ArrayRopeSegment segment; + + private ArrayRope(ArrayRopeSegment segment) { + this.segment = segment; + } + + @SafeVarargs + public ArrayRope(T... elements) { + segment = new ArraySegment<>(elements); + } + + public ArrayRope append(ArrayRope that) { + return new ArrayRope<>(new ConcatSegment<>(this.segment, that.segment)); + } + + @SafeVarargs + public final ArrayRope append(T... items) { + return new ArrayRope<>(new ConcatSegment<>(this.segment, new ArraySegment<>(items))); + } + + public ArrayRope prepend(ArrayRope that) { + return new ArrayRope<>(new ConcatSegment<>(that.segment, this.segment)); + } + + @SafeVarargs + public final ArrayRope prepend(T... items) { + return new ArrayRope<>(new ConcatSegment<>(new ArraySegment<>(items), this.segment)); + } + + public T[] toArray(Function genArray) { + T[] res = genArray.apply(size()); + writeArray(res); + return res; + } + + public void writeArray(T[] arr) { + segment.appendTo(arr, 0); + } + + public int size() { + return segment.size(); + } + + @Override + public String toString() { + return "ArrayRope{" + "segment=" + segment + '}'; + } + + private interface ArrayRopeSegment { + void appendTo(T[] builder, int start); + + int size(); + } + + private static final class ArraySegment implements ArrayRopeSegment { + private final T[] elements; + + public ArraySegment(T[] elements) { + this.elements = elements; + } + + @Override + public void appendTo(T[] builder, int start) { + System.arraycopy(elements, 0, builder, start, elements.length); + } + + @Override + public int size() { + return elements.length; + } + + @Override + public String toString() { + return "ArraySegment{" + "elements=" + Arrays.toString(elements) + '}'; + } + } + + private static final class ConcatSegment implements ArrayRopeSegment { + private final ArrayRopeSegment left; + private final ArrayRopeSegment right; + private int cachedSize = UNKNOWN; + private static final int UNKNOWN = -1; + + public ConcatSegment(ArrayRopeSegment left, ArrayRopeSegment right) { + this.left = left; + this.right = right; + } + + @Override + public void appendTo(T[] builder, int start) { + left.appendTo(builder, start); + right.appendTo(builder, start + left.size()); + } + + @Override + public int size() { + if (cachedSize == UNKNOWN) { + cachedSize = left.size() + right.size(); + } + return cachedSize; + } + + @Override + public String toString() { + return "ConcatSegment{" + + "left=" + + left + + ", right=" + + right + + ", cachedSize=" + + cachedSize + + '}'; + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/Warning.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/Warning.java new file mode 100644 index 00000000000..7593c42c299 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/Warning.java @@ -0,0 +1,84 @@ +package org.enso.interpreter.runtime.error; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.api.source.SourceSection; +import org.enso.interpreter.runtime.data.ArrayRope; +import org.enso.interpreter.runtime.data.EnsoSourceSection; + +public class Warning implements TruffleObject { + private final Object value; + private final Object origin; + private final ArrayRope reassignments; + private final long creationTime; + + public Warning(Object value, Object origin, long creationTime) { + this(value, origin, creationTime, new ArrayRope<>()); + } + + public Warning( + Object value, Object origin, long creationTime, ArrayRope reassignments) { + this.value = value; + this.origin = origin; + this.reassignments = reassignments; + this.creationTime = creationTime; + } + + @ExportLibrary(InteropLibrary.class) + public static class Reassignment implements TruffleObject { + private final String methodName; + private final SourceSection location; + + public Reassignment(String methodName, SourceSection location) { + this.methodName = methodName; + this.location = location; + } + + @ExportMessage + boolean hasExecutableName() { + return true; + } + + @ExportMessage + String getExecutableName() { + return methodName; + } + + @ExportMessage + boolean hasSourceLocation() { + return location != null; + } + + @ExportMessage + SourceSection getSourceLocation() { + return location; + } + } + + public Object getValue() { + return value; + } + + public Object getOrigin() { + return origin; + } + + public long getCreationTime() { + return creationTime; + } + + public ArrayRope getReassignments() { + return reassignments; + } + + public Warning reassign(Node location) { + RootNode root = location.getRootNode(); + SourceSection section = location.getEncapsulatingSourceSection(); + Reassignment reassignment = new Reassignment(root.getName(), section); + return new Warning(value, origin, creationTime, reassignments.prepend(reassignment)); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java new file mode 100644 index 00000000000..bb6822fda34 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java @@ -0,0 +1,83 @@ +package org.enso.interpreter.runtime.error; + +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.data.ArrayRope; +import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; + +@ExportLibrary(MethodDispatchLibrary.class) +public class WithWarnings implements TruffleObject { + private final ArrayRope warnings; + private final Object value; + + public WithWarnings(Object value, Warning... warnings) { + this.warnings = new ArrayRope<>(warnings); + this.value = value; + } + + private WithWarnings(Object value, ArrayRope warnings) { + this.warnings = warnings; + this.value = value; + } + + public Object getValue() { + return value; + } + + public WithWarnings append(Warning... newWarnings) { + return new WithWarnings(value, warnings.append(newWarnings)); + } + + public WithWarnings append(ArrayRope newWarnings) { + return new WithWarnings(value, warnings.append(newWarnings)); + } + + public WithWarnings prepend(Warning... newWarnings) { + return new WithWarnings(value, warnings.prepend(newWarnings)); + } + + public WithWarnings prepend(ArrayRope newWarnings) { + return new WithWarnings(value, warnings.prepend(newWarnings)); + } + + public Warning[] getWarningsArray() { + return warnings.toArray(Warning[]::new); + } + + public ArrayRope getWarnings() { + return warnings; + } + + public ArrayRope getReassignedWarnings(Node location) { + Warning[] warnings = getWarningsArray(); + for (int i = 0; i < warnings.length; i++) { + warnings[i] = warnings[i].reassign(location); + } + return new ArrayRope<>(warnings); + } + + public static WithWarnings appendTo(Object target, ArrayRope warnings) { + if (target instanceof WithWarnings) { + return ((WithWarnings) target).append(warnings); + } else { + return new WithWarnings(target, warnings); + } + } + + public static WithWarnings prependTo(Object target, ArrayRope warnings) { + if (target instanceof WithWarnings) { + return ((WithWarnings) target).prepend(warnings); + } else { + return new WithWarnings(target, warnings); + } + } + + @ExportMessage + boolean hasSpecialDispatch() { + return true; + } +} 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 1cdbe2999dc..94137e7e7ab 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 @@ -17,6 +17,7 @@ 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.error.Warning; import org.enso.interpreter.runtime.number.EnsoBigInteger; import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.polyglot.data.TypeGraph; @@ -49,7 +50,8 @@ import org.enso.polyglot.data.TypeGraph; ModuleScope.class, Ref.class, PanicException.class, - PanicSentinel.class + PanicSentinel.class, + Warning.class }) public class Types { diff --git a/engine/runtime/src/main/resources/Builtins.enso b/engine/runtime/src/main/resources/Builtins.enso index 8f2bfcfa591..5d6f3dfc5a9 100644 --- a/engine/runtime/src/main/resources/Builtins.enso +++ b/engine/runtime/src/main/resources/Builtins.enso @@ -243,6 +243,30 @@ type Error to_text : Text to_text = @Builtin_Method "Error.to_text" +@Builtin_Type +type Prim_Warning + type Prim_Warning + + ## PRIVATE + attach : Any -> Any -> Any + attach value warning = @Builtin_Method "Prim_Warning.attach" + + ## PRIVATE + get_all : Any -> Array Prim_Warning + get_all value = @Builtin_Method "Prim_Warning.get_all" + + ## PRIVATE + get_origin : Prim_Warning -> Any + get_origin warn = @Builtin_Method "Prim_Warning.get_origin" + + ## PRIVATE + get_value : Prim_Warning -> Any + get_value warn = @Builtin_Method "Prim_Warning.get_value" + + ## PRIVATE + get_reassignments : Prim_Warning -> Any + get_reassignments warn = @Builtin_Method "Prim_Warning.get_reassignments" + ## The runtime representation of a syntax error. Arguments: diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/AcceptsWarning.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/AcceptsWarning.java new file mode 100644 index 00000000000..3728d3ceb86 --- /dev/null +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/AcceptsWarning.java @@ -0,0 +1,11 @@ +package org.enso.interpreter.dsl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** An interface marking an argument as allowing it to accept a WithWarnings. */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.SOURCE) +public @interface AcceptsWarning {} diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java index 75a5247fd4c..5395d8b64de 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java @@ -14,6 +14,7 @@ import javax.tools.JavaFileObject; import java.io.IOException; import java.io.PrintWriter; import java.util.*; +import java.util.stream.Collectors; /** The processor used to generate code from the {@link BuiltinMethod} annotation. */ @SupportedAnnotationTypes("org.enso.interpreter.dsl.BuiltinMethod") @@ -82,7 +83,11 @@ public class MethodProcessor extends AbstractProcessor { "org.enso.interpreter.runtime.callable.argument.ArgumentDefinition", "org.enso.interpreter.runtime.callable.function.Function", "org.enso.interpreter.runtime.callable.function.FunctionSchema", + "org.enso.interpreter.runtime.Context", + "org.enso.interpreter.runtime.data.ArrayRope", "org.enso.interpreter.runtime.error.PanicException", + "org.enso.interpreter.runtime.error.Warning", + "org.enso.interpreter.runtime.error.WithWarnings", "org.enso.interpreter.runtime.state.Stateful", "org.enso.interpreter.runtime.type.TypesGen"); @@ -111,15 +116,21 @@ public class MethodProcessor extends AbstractProcessor { for (MethodDefinition.ArgumentDefinition arg : methodDefinition.getArguments()) { if (!arg.isState() && !arg.isFrame() && !arg.isCallerInfo()) { - String condName = "arg" + arg.getPosition() + "ConditionProfile"; - String branchName = "arg" + arg.getPosition() + "BranchProfile"; + String condName = mkArgumentInternalVarName(arg) + "ConditionProfile"; + String branchName = mkArgumentInternalVarName(arg) + "BranchProfile"; out.println( " private final ConditionProfile " + condName + " = ConditionProfile.createCountingProfile();"); out.println(" private final BranchProfile " + branchName + " = BranchProfile.create();"); + if (!arg.isThis() && !arg.acceptsWarning()) { + String warningName = mkArgumentInternalVarName(arg) + "WarningProfile"; + out.println( + " private final BranchProfile " + warningName + " = BranchProfile.create();"); + } } } + out.println(" private final BranchProfile anyWarningsProfile = BranchProfile.create();"); out.println(" private " + methodDefinition.getClassName() + "(Language language) {"); out.println(" super(language);"); @@ -169,6 +180,8 @@ public class MethodProcessor extends AbstractProcessor { out.println( " Object[] arguments = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments());"); List callArgNames = new ArrayList<>(); + boolean warningsPossible = + generateWarningsCheck(out, methodDefinition.getArguments(), "arguments"); for (MethodDefinition.ArgumentDefinition argumentDefinition : methodDefinition.getArguments()) { if (argumentDefinition.isState()) { @@ -178,15 +191,37 @@ public class MethodProcessor extends AbstractProcessor { } else if (argumentDefinition.isCallerInfo()) { callArgNames.add("callerInfo"); } else { - callArgNames.add("arg" + argumentDefinition.getPosition()); + callArgNames.add(mkArgumentInternalVarName(argumentDefinition)); generateArgumentRead(out, argumentDefinition, "arguments"); } } String executeCall = "bodyNode.execute(" + String.join(", ", callArgNames) + ")"; - if (methodDefinition.modifiesState()) { - out.println(" return " + executeCall + ";"); + if (warningsPossible) { + out.println(" if (anyWarnings) {"); + out.println(" anyWarningsProfile.enter();"); + if (methodDefinition.modifiesState()) { + out.println(" Stateful result = " + executeCall + ";"); + out.println( + " Object newValue = WithWarnings.appendTo(result.getValue(), gatheredWarnings);"); + out.println(" return new Stateful(result.getState(), newValue);"); + } else { + out.println(" Object result = " + executeCall + ";"); + out.println( + " return new Stateful(state, WithWarnings.appendTo(result, gatheredWarnings));"); + } + out.println(" } else {"); + if (methodDefinition.modifiesState()) { + out.println(" return " + executeCall + ";"); + } else { + out.println(" return new Stateful(state, " + executeCall + ");"); + } + out.println(" }"); } else { - out.println(" return new Stateful(state, " + executeCall + ");"); + if (methodDefinition.modifiesState()) { + out.println(" return " + executeCall + ";"); + } else { + out.println(" return new Stateful(state, " + executeCall + ");"); + } } out.println(" }"); @@ -215,10 +250,7 @@ public class MethodProcessor extends AbstractProcessor { out.println(" @Override"); out.println(" protected RootNode cloneUninitialized() {"); - out.println( - " return new " - + methodDefinition.getClassName() - + "(lookupLanguageReference(Language.class).get());"); + out.println(" return new " + methodDefinition.getClassName() + "(Language.get(this));"); out.println(" }"); out.println(); @@ -239,8 +271,8 @@ public class MethodProcessor extends AbstractProcessor { if (!arg.acceptsError()) { - String varName = "arg" + arg.getPosition(); - String condProfile = "arg" + arg.getPosition() + "ConditionProfile"; + String varName = mkArgumentInternalVarName(arg); + String condProfile = mkArgumentInternalVarName(arg) + "ConditionProfile"; out.println( " if (" + condProfile @@ -252,7 +284,7 @@ public class MethodProcessor extends AbstractProcessor { + ");\n" + " }"); if (!(arg.getName().equals("this") && arg.getPosition() == 0)) { - String branchProfile = "arg" + arg.getPosition() + "BranchProfile"; + String branchProfile = mkArgumentInternalVarName(arg) + "BranchProfile"; out.println( " else if (TypesGen.isPanicSentinel(" + varName @@ -270,7 +302,7 @@ public class MethodProcessor extends AbstractProcessor { private void generateUncastedArgumentRead( PrintWriter out, MethodDefinition.ArgumentDefinition arg, String argsArray) { - String varName = "arg" + arg.getPosition(); + String varName = mkArgumentInternalVarName(arg); out.println( " " + arg.getTypeName() @@ -286,7 +318,7 @@ public class MethodProcessor extends AbstractProcessor { private void generateUncheckedArgumentRead( PrintWriter out, MethodDefinition.ArgumentDefinition arg, String argsArray) { String castName = "TypesGen.as" + capitalize(arg.getTypeName()); - String varName = "arg" + arg.getPosition(); + String varName = mkArgumentInternalVarName(arg); out.println( " " + arg.getTypeName() @@ -304,13 +336,13 @@ public class MethodProcessor extends AbstractProcessor { private void generateCheckedArgumentRead( PrintWriter out, MethodDefinition.ArgumentDefinition arg, String argsArray) { String castName = "TypesGen.expect" + capitalize(arg.getTypeName()); - String varName = "arg" + arg.getPosition(); + String varName = mkArgumentInternalVarName(arg); out.println(" " + arg.getTypeName() + " " + varName + ";"); out.println(" try {"); out.println( " " + varName + " = " + castName + "(" + argsArray + "[" + arg.getPosition() + "]);"); out.println(" } catch (UnexpectedResultException e) {"); - out.println(" var builtins = lookupContextReference(Language.class).get().getBuiltins();"); + out.println(" var builtins = Context.get(this).getBuiltins();"); out.println( " var expected = builtins.fromTypeSystem(TypesGen.getName(arguments[" + arg.getPosition() @@ -325,6 +357,52 @@ public class MethodProcessor extends AbstractProcessor { out.println(" }"); } + private boolean generateWarningsCheck( + PrintWriter out, List arguments, String argumentsArray) { + List argsToCheck = + arguments.stream() + .filter(arg -> !arg.acceptsWarning() && !arg.isThis()) + .collect(Collectors.toList()); + if (argsToCheck.isEmpty()) { + return false; + } else { + out.println(" boolean anyWarnings = false;"); + out.println(" ArrayRope gatheredWarnings = new ArrayRope<>();"); + for (var arg : argsToCheck) { + out.println( + " if (" + + arrayRead(argumentsArray, arg.getPosition()) + + " instanceof WithWarnings) {"); + out.println(" " + mkArgumentInternalVarName(arg) + "WarningProfile.enter();"); + out.println(" anyWarnings = true;"); + out.println( + " WithWarnings withWarnings = (WithWarnings) " + + arrayRead(argumentsArray, arg.getPosition()) + + ";"); + out.println( + " " + + arrayRead(argumentsArray, arg.getPosition()) + + " = withWarnings.getValue();"); + out.println( + " gatheredWarnings = gatheredWarnings.prepend(withWarnings.getReassignedWarnings(this));"); + out.println(" }"); + } + return true; + } + } + + private String warningCheck(MethodDefinition.ArgumentDefinition arg) { + return "(" + mkArgumentInternalVarName(arg) + " instanceof WithWarnings)"; + } + + private String mkArgumentInternalVarName(MethodDefinition.ArgumentDefinition arg) { + return "arg" + arg.getPosition(); + } + + private String arrayRead(String array, int index) { + return array + "[" + index + "]"; + } + private String capitalize(String name) { return name.substring(0, 1).toUpperCase() + name.substring(1); } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java index 7883a36de36..b1cb9b5122b 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java @@ -1,9 +1,6 @@ package org.enso.interpreter.dsl.model; -import org.enso.interpreter.dsl.AcceptsError; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.dsl.MonadicState; -import org.enso.interpreter.dsl.Suspend; +import org.enso.interpreter.dsl.*; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.*; @@ -185,6 +182,7 @@ public class MethodDefinition { private final boolean isCallerInfo; private final boolean isSuspended; private final boolean acceptsError; + private final boolean acceptsWarning; private final int position; private final VariableElement element; @@ -206,6 +204,7 @@ public class MethodDefinition { acceptsError = (element.getAnnotation(AcceptsError.class) != null) || type.toString().equals(DATAFLOW_ERROR); + acceptsWarning = element.getAnnotation(AcceptsWarning.class) != null; isFrame = type.toString().equals(VIRTUAL_FRAME); isCallerInfo = type.toString().equals(CALLER_INFO); this.position = position; @@ -288,5 +287,13 @@ public class MethodDefinition { public boolean acceptsError() { return acceptsError; } + + public boolean acceptsWarning() { + return acceptsWarning; + } + + public boolean isThis() { + return name.equals("this") || position == 0; + } } } diff --git a/test/Tests/src/Main.enso b/test/Tests/src/Main.enso index ffba60894c2..b8558f158ea 100644 --- a/test/Tests/src/Main.enso +++ b/test/Tests/src/Main.enso @@ -11,6 +11,7 @@ import project.Semantic.Import_Loop.Spec as Import_Loop_Spec import project.Semantic.Meta_Spec import project.Semantic.Names_Spec import project.Semantic.Runtime_Spec +import project.Semantic.Warnings_Spec import project.Semantic.Java_Interop_Spec import project.Semantic.Js_Interop_Spec @@ -90,3 +91,4 @@ main = Test.Suite.run_main <| Time_Spec.spec Uri_Spec.spec Vector_Spec.spec + Warnings_Spec.spec diff --git a/test/Tests/src/Semantic/Warnings_Spec.enso b/test/Tests/src/Semantic/Warnings_Spec.enso new file mode 100644 index 00000000000..c0b14a8061d --- /dev/null +++ b/test/Tests/src/Semantic/Warnings_Spec.enso @@ -0,0 +1,105 @@ +from Standard.Base import all + +polyglot java import java.lang.Long + +import Standard.Test + +type My_Warning reason + +type My_Type a b c +My_Type.my_method = this.a + this.b + this.c + +type Wrap foo + +rewrap w = case w of + Wrap a -> Wrap a+1 + +poly_sum x y = + Long.sum x y + +get_foo x = x.foo + +unwrap x = Integer.from x + +reassign_test x = + consed = Wrap x + reconsed = here.rewrap consed + i = here.unwrap reconsed + rereconsed = Wrap i + x1 = here.get_foo rereconsed + prim_sum = 1 + x1 + r = here.poly_sum prim_sum 1 + r + +baz value = Warning.attach value "I have warned you" +bar value = here.baz value +foo value = here.bar value + +Integer.from (that:Wrap) = that.foo + +spec = Test.group "Dataflow Warnings" <| + Test.specify "should allow to attach multiple warnings and read them back" <| + x = 1233 + y = Warning.attach "don't do this" x + z = Warning.attach "I'm serious" y + Warning.get_all z . map .value . should_equal ["I'm serious", "don't do this"] + + Test.specify "should thread warnings through constructor calls" <| + z = Warning.attach (My_Warning "warn!!!") 3 + y = Warning.attach (My_Warning "warn!!") 2 + x = Warning.attach (My_Warning "warn!") 1 + mtp = My_Type x y z + mtp.should_equal (My_Type 1 2 3) + Warning.get_all mtp . map .value . should_equal [My_Warning "warn!", My_Warning "warn!!", My_Warning "warn!!!"] + + Test.specify "should thread warnings through method calls" + mtp = My_Type 1 2 3 + warned = Warning.attach "omgggg" mtp + r = warned.my_method + r.should_equal 6 + Warning.get_all r . map .value . should_equal ["omgggg"] + + Test.specify "should thread warnings through polyglot calls" <| + y = Warning.attach "warn!!" 2 + x = Warning.attach "warn!" 1 + r = Long.sum x y + r.should_equal 3 + Warning.get_all r . map .value . should_equal ['warn!', 'warn!!'] + + Test.specify "should thread warnings through case expressions" <| + z = Warning.attach (My_Warning "warn!!!") 3 + y = Warning.attach (My_Warning "warn!!") 2 + x = Warning.attach (My_Warning "warn!") 1 + mtp = My_Type x y z + r = case mtp of + My_Type a b c -> a + b + c + r.should_equal 6 + Warning.get_all r . map .value . should_equal [My_Warning "warn!", My_Warning "warn!!", My_Warning "warn!!!"] + + Test.specify "should thread warnings through conversions" <| + z = Wrap (Warning.attach 'warn!' 1) + i = Integer.from z + Warning.get_all i . map .value . should_equal ['warn!'] + + Test.specify "should attach correct stacktraces" <| + current = Runtime.get_stack_trace + warned = here.foo "value" + warning_stack = Warning.get_all warned . head . origin + relevant = warning_stack . drop_end current.length + relevant.map .name . should_equal (['baz', 'bar', 'foo'].map ('Warnings_Spec.'+)) + + Test.specify "should attach reassignment info in the last-reassigned-first order" <| + x = Warning.attach "warn!" 1 + r = here.reassign_test x + warn = Warning.get_all r . head + reassignments = warn.reassignments.map .name + reassignments.should_equal ['Warnings_Spec.poly_sum', 'Small_Integer.+', 'Warnings_Spec.get_foo', 'Warnings_Spec.Wrap', 'Warnings_Spec.unwrap', 'Warnings_Spec.rewrap', 'Warnings_Spec.Wrap'] + + Test.specify "should allow to set all warnings" <| + warned = Warning.attach 1 <| Warning.attach 2 <| Warning.attach 3 <| Warning.attach 4 "foo" + warnings = Warning.get_all warned + filtered = warnings.filter x-> x.value % 2 == 0 + rewarned = Warning.set filtered warned + rewarned.should_equal 'foo' + Warning.get_all rewarned . map .value . should_equal [2,4] +