diff --git a/CHANGELOG.md b/CHANGELOG.md index e29c074839b5..8a3973d00e13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -377,6 +377,8 @@ - [Added support for Date/Time columns in the Postgres backend and added `year`/`month`/`day` operations to Table columns.][6153] - [`Text.split` can now take a vector of delimiters.][6156] +- [Add `has_warnings`, `remove_warnings` and `throw_on_warning` extension + methods.][6176] - [Implemented `Table.union` for the Database backend.][6204] - [Array & Vector have the same methods & behavior][6218] @@ -572,6 +574,7 @@ [6150]: https://github.com/enso-org/enso/pull/6150 [6153]: https://github.com/enso-org/enso/pull/6153 [6156]: https://github.com/enso-org/enso/pull/6156 +[6176]: https://github.com/enso-org/enso/pull/6176 [6204]: https://github.com/enso-org/enso/pull/6204 [6077]: https://github.com/enso-org/enso/pull/6077 [6218]: https://github.com/enso-org/enso/pull/6218 diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso index 67455926fc36..6a2984689c05 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso @@ -1,13 +1,15 @@ import project.Data.Pair.Pair import project.Data.Range.Extensions import project.Data.Text.Text +import project.Data.Vector.Vector import project.Error.Error import project.Errors.Common.Incomparable_Values import project.Errors.Common.No_Such_Conversion import project.Errors.Common.Type_Error -import project.Nothing.Nothing import project.Meta +import project.Nothing.Nothing import project.Panic.Panic +import project.Warning.Warning from project.Data.Boolean import Boolean, True, False from project.Data.Ordering import all @@ -450,6 +452,32 @@ type Any >> : (Any -> Any) -> (Any -> Any) >> self ~that = x -> that (self x) + ## Checks if any warnings (either all or of a specified type) are attached to the value. + + Arguments: + - warning_type: The type to check if attached to the value. Defaults to any warning. + has_warnings : Any -> Boolean + has_warnings self warning_type=Any = + _ = warning_type + False + + ## Remove the warnings (either all or of a specified type) attached to the value. + + Arguments: + - warning_type: The type to remove if attached to the value. Defaults to all warnings. + remove_warnings : Any -> Any + remove_warnings self warning_type=Any = + _ = warning_type + self + + ## Throws the first matching warning (either all or of a specified type) as a data flow error. + + Arguments: + - warning_type: The type to throw if attached to the value. Defaults to all warnings. + throw_on_warning : Any -> Any + throw_on_warning self warning_type=Any = + _ = warning_type + self ## PRIVATE Checks if the comparators for the given objects are both of the same type. If so, 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 index 70c8289cc962..08aade3b3a4c 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso @@ -6,6 +6,7 @@ import project.Data.Numbers.Integer import project.Data.Pair.Pair import project.Data.Vector.Vector import project.Error.Error +import project.Meta import project.Nothing.Nothing import project.Polyglot.Polyglot import project.Runtime @@ -28,7 +29,26 @@ type Warning ## ADVANCED Are any warnings attached to the value? has_warnings : Any -> Boolean - has_warnings value = has_warnings_builtin value + has_warnings value warning_type=Any = + Warning.get_all value . any (w-> w.value.is_a warning_type) + + ## Remove the warnings (either all or of a specified type) attached to the value. + + Arguments: + - warning_type: The type to remove if attached to the value. Defaults to all warnings. + remove_warnings : Any -> Any + remove_warnings value warning_type=Any = + Warning.detach_selected_warnings value (w-> w.is_a warning_type) . first + + ## Throws the first matching warning (either all or of a specified type) as a data flow error. + + Arguments: + - warning_type: The type to throw if attached to the value. Defaults to all warnings. + throw_on_warning : Any -> Any + throw_on_warning self warning_type=Any = + warnings = Warning.get_all self + first = warnings.find (w-> w.value.is_a warning_type) if_missing=Nothing + if first.is_nothing then self else Error.throw first.value ## ADVANCED Gets all the warnings attached to the given value. Warnings are returned in the @@ -251,12 +271,6 @@ create payload origin = @Builtin_Method "Warning.create" attach_with_stacktrace : Any -> Any -> Vector Stack_Trace_Element -> Any attach_with_stacktrace value warning origin = @Builtin_Method "Warning.attach_with_stacktrace" -## PRIVATE - - Builtin function that sees if any warnings attached. -has_warnings_builtin : Any -> Array Warning -has_warnings_builtin value = @Builtin_Method "Warning.has_warnings_builtin" - ## PRIVATE Builtin function that gets all the warnings attached to the given value. 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 79fddcd65b0e..0ccd30812095 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 @@ -20,15 +20,18 @@ import org.enso.interpreter.node.callable.resolver.MethodResolverNode; import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.builtin.Builtins; 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.callable.function.FunctionSchema; import org.enso.interpreter.runtime.data.ArrayRope; import org.enso.interpreter.runtime.data.EnsoDate; import org.enso.interpreter.runtime.data.EnsoDateTime; import org.enso.interpreter.runtime.data.EnsoDuration; import org.enso.interpreter.runtime.data.EnsoTimeOfDay; import org.enso.interpreter.runtime.data.EnsoTimeZone; +import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.error.PanicException; @@ -51,6 +54,7 @@ public abstract class InvokeMethodNode extends BaseNode { private @Child InvokeFunctionNode invokeFunctionNode; private final ConditionProfile errorReceiverProfile = ConditionProfile.createCountingProfile(); private @Child InvokeMethodNode childDispatch; + private final int argumentCount; private final int thisArgumentPosition; @@ -82,6 +86,10 @@ public static InvokeMethodNode build( this.thisArgumentPosition = thisArgumentPosition; } + InvokeFunctionNode getInvokeFunctionNode() { + return invokeFunctionNode; + } + @Override public void setTailStatus(TailStatus tailStatus) { super.setTailStatus(tailStatus); @@ -134,13 +142,113 @@ Object doPanicSentinel( throw self; } - @Specialization + /** + * Resolves symbol to a Warning method, if possible. + * + *

A regular method dispatch logic will extract/append warnings of `self` before invoking the + * actual method dispatch logic. This allows for ignoring complexity related to the presence of + * warnings but prevents us from manipulating warnings directly in the Enso code (they have just + * been removed). `resolveWarningFunction` will attempt to resolve the symbol in the Warning type + * scope, if possible. Additionally, we check if under the non-warning `self`, the symbol would + * resolve to `Any`. If not, it means that we should employ a regular method dispatch logic, due + * to a method name clash. E.g. if some collection type Foo defines a `has_warnings` method, we + * should dispatch the call to `Foo`'s `has_warning` rather than to a `Warning` or `Any`'s `one. + * + * @param self `self` argument that has some warnings + * @param symbol symbol to be resolved + * @param types TypesLibrary instance + * @param warnings WarningsLibrary instance + * @return resolved Warning method to be called + */ + Function resolveWarningFunction( + Object self, UnresolvedSymbol symbol, TypesLibrary types, WarningsLibrary warnings) { + Object selfWithoutWarnings; + try { + selfWithoutWarnings = warnings.removeWarnings(self); + } catch (UnsupportedMessageException e) { + throw CompilerDirectives.shouldNotReachHere( + "`self` object should have some warnings when calling `" + symbol.getName() + "` method", + e); + } + + Type typeOfSymbol = symbol.resolveDeclaringType(types.getType(selfWithoutWarnings)); + Builtins builtins = EnsoContext.get(this).getBuiltins(); + if (typeOfSymbol == builtins.any()) { + return symbol + .getScope() + .lookupMethodDefinition(builtins.warning().getEigentype(), symbol.getName()); + } + + return null; + } + + Object[] argumentsWithExplicitSelf(FunctionSchema cachedSchema, Object[] arguments) { + Object[] arguments1; + if (!cachedSchema.isFullyApplied()) { + arguments1 = new Object[cachedSchema.getArgumentsCount()]; + System.arraycopy(arguments, 0, arguments1, 1, arguments.length); + arguments1[0] = arguments[0]; + } else { + arguments1 = arguments; + } + return arguments1; + } + + public InvokeFunctionNode buildInvokeFunctionWithSelf() { + int length = invokeFunctionNode.getSchema().length; + CallArgumentInfo[] schema = new CallArgumentInfo[length + 1]; + System.arraycopy(invokeFunctionNode.getSchema(), 0, schema, 1, length); + schema[0] = new CallArgumentInfo(); + return InvokeFunctionNode.build( + schema, + invokeFunctionNode.getDefaultsExecutionMode(), + invokeFunctionNode.getArgumentsExecutionMode()); + } + + @Specialization( + guards = { + "warnings.hasWarnings(self)", + "resolvedFunction != null", + "resolvedFunction.getSchema() == cachedSchema" + }) + Object doWarningsCustom( + VirtualFrame frame, + State state, + UnresolvedSymbol symbol, + Object self, + Object[] arguments, + @CachedLibrary(limit = "10") TypesLibrary types, + @CachedLibrary(limit = "10") WarningsLibrary warnings, + @Cached("resolveWarningFunction(self, symbol, types, warnings)") Function resolvedFunction, + @Cached("resolvedFunction.getSchema()") FunctionSchema cachedSchema, + @Cached("buildInvokeFunctionWithSelf()") InvokeFunctionNode warningFunctionNode) { + // Warning's builtin type methods are static meaning that they have a synthetic `self` + // parameter. However, the constructed `InvokeFunctionNode` is missing that + // information and the function, if called with `arguments`, will not be fully applied. + // Hence, the synthetic construction of a new `InvokeFunctionNode` with the updated schema + // and call including an additional, dummy, argument. + Object[] arguments1 = argumentsWithExplicitSelf(cachedSchema, arguments); + return warningFunctionNode.execute(resolvedFunction, frame, state, arguments1); + } + + @Specialization(guards = "warnings.hasWarnings(self)") Object doWarning( VirtualFrame frame, State state, UnresolvedSymbol symbol, - WithWarnings self, - Object[] arguments) { + Object self, + Object[] arguments, + @CachedLibrary(limit = "10") WarningsLibrary warnings) { + Object selfWithoutWarnings; + Warning[] arrOfWarnings; + try { + selfWithoutWarnings = warnings.removeWarnings(self); + arrOfWarnings = warnings.getWarnings(self, this); + } catch (UnsupportedMessageException e) { + // Can't throw `CompilerDirectives.shouldNotReachHere` as it crashes native-image build + throw new IllegalStateException(e); + } + // Cannot use @Cached for childDispatch, because we need to call notifyInserted. if (childDispatch == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); @@ -164,15 +272,16 @@ Object doWarning( } } - arguments[thisArgumentPosition] = self.getValue(); - ArrayRope warnings = self.getReassignedWarnings(this); - Object result = childDispatch.execute(frame, state, symbol, self.getValue(), arguments); - return WithWarnings.prependTo(result, warnings); + arguments[thisArgumentPosition] = selfWithoutWarnings; + + Object result = childDispatch.execute(frame, state, symbol, selfWithoutWarnings, arguments); + return WithWarnings.prependTo(result, arrOfWarnings); } @ExplodeLoop @Specialization( guards = { + "!warnings.hasWarnings(self)", "!methods.hasType(self)", "!methods.hasSpecialDispatch(self)", "polyglotCallType.isInteropLibrary()", @@ -225,6 +334,7 @@ Object doPolyglot( @Specialization( guards = { + "!warnings.hasWarnings(self)", "!types.hasType(self)", "!types.hasSpecialDispatch(self)", "getPolyglotCallType(self, symbol, interop) == CONVERT_TO_TEXT" @@ -237,6 +347,7 @@ Object doConvertText( Object[] arguments, @CachedLibrary(limit = "10") InteropLibrary interop, @CachedLibrary(limit = "10") TypesLibrary types, + @CachedLibrary(limit = "10") WarningsLibrary warnings, @Cached MethodResolverNode methodResolverNode) { try { var str = interop.asString(self); @@ -253,6 +364,7 @@ Object doConvertText( @Specialization( guards = { + "!warnings.hasWarnings(self)", "!types.hasType(self)", "!types.hasSpecialDispatch(self)", "getPolyglotCallType(self, symbol, interop, methodResolverNode) == CONVERT_TO_ARRAY", @@ -265,6 +377,7 @@ Object doConvertArray( Object[] arguments, @CachedLibrary(limit = "10") InteropLibrary interop, @CachedLibrary(limit = "10") TypesLibrary types, + @CachedLibrary(limit = "10") WarningsLibrary warnings, @Cached MethodResolverNode methodResolverNode) { var ctx = EnsoContext.get(this); var arrayType = ctx.getBuiltins().array(); @@ -275,6 +388,7 @@ Object doConvertArray( @Specialization( guards = { + "!warnings.hasWarnings(self)", "!types.hasType(self)", "!types.hasSpecialDispatch(self)", "getPolyglotCallType(self, symbol, interop, methodResolverNode) == CONVERT_TO_HASH_MAP", @@ -287,6 +401,7 @@ Object doConvertHashMap( Object[] arguments, @CachedLibrary(limit = "10") InteropLibrary interop, @CachedLibrary(limit = "10") TypesLibrary types, + @CachedLibrary(limit = "10") WarningsLibrary warnings, @Cached MethodResolverNode methodResolverNode) { var ctx = EnsoContext.get(this); var hashMapType = ctx.getBuiltins().map(); @@ -297,6 +412,7 @@ Object doConvertHashMap( @Specialization( guards = { + "!warnings.hasWarnings(self)", "!types.hasType(self)", "!types.hasSpecialDispatch(self)", "getPolyglotCallType(self, symbol, interop) == CONVERT_TO_DATE" @@ -309,6 +425,7 @@ Object doConvertDate( Object[] arguments, @CachedLibrary(limit = "10") TypesLibrary types, @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedLibrary(limit = "10") WarningsLibrary warnings, @Cached MethodResolverNode methodResolverNode) { var ctx = EnsoContext.get(this); try { @@ -325,6 +442,7 @@ Object doConvertDate( @Specialization( guards = { + "!warnings.hasWarnings(self)", "!types.hasType(self)", "!types.hasSpecialDispatch(self)", "getPolyglotCallType(self, symbol, interop) == CONVERT_TO_DATE_TIME" @@ -337,6 +455,7 @@ Object doConvertDateTime( Object[] arguments, @CachedLibrary(limit = "10") TypesLibrary types, @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedLibrary(limit = "10") WarningsLibrary warnings, @Cached MethodResolverNode methodResolverNode) { var ctx = EnsoContext.get(this); try { @@ -355,6 +474,7 @@ Object doConvertDateTime( @Specialization( guards = { + "!warnings.hasWarnings(self)", "!types.hasType(self)", "!types.hasSpecialDispatch(self)", "getPolyglotCallType(self, symbol, interop) == CONVERT_TO_DURATION" @@ -367,6 +487,7 @@ Object doConvertDuration( Object[] arguments, @CachedLibrary(limit = "10") TypesLibrary types, @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedLibrary(limit = "10") WarningsLibrary warnings, @Cached MethodResolverNode methodResolverNode) { var ctx = EnsoContext.get(this); try { @@ -393,6 +514,7 @@ private ZonedDateTime dateTime(LocalDate date, LocalTime time, ZoneId zone) { @Specialization( guards = { + "!warnings.hasWarnings(self)", "!types.hasType(self)", "!types.hasSpecialDispatch(self)", "getPolyglotCallType(self, symbol, interop) == CONVERT_TO_ZONED_DATE_TIME" @@ -405,6 +527,7 @@ Object doConvertZonedDateTime( Object[] arguments, @CachedLibrary(limit = "10") TypesLibrary types, @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedLibrary(limit = "10") WarningsLibrary warnings, @Cached MethodResolverNode methodResolverNode) { var ctx = EnsoContext.get(this); try { @@ -423,6 +546,7 @@ Object doConvertZonedDateTime( @Specialization( guards = { + "!warnings.hasWarnings(self)", "!types.hasType(self)", "!types.hasSpecialDispatch(self)", "getPolyglotCallType(self, symbol, interop) == CONVERT_TO_TIME_ZONE" @@ -435,6 +559,7 @@ Object doConvertZone( Object[] arguments, @CachedLibrary(limit = "10") TypesLibrary types, @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedLibrary(limit = "10") WarningsLibrary warnings, @Cached MethodResolverNode methodResolverNode) { var ctx = EnsoContext.get(this); try { @@ -451,6 +576,7 @@ Object doConvertZone( @Specialization( guards = { + "!warnings.hasWarnings(self)", "!types.hasType(self)", "!types.hasSpecialDispatch(self)", "getPolyglotCallType(self, symbol, interop) == CONVERT_TO_TIME_OF_DAY" @@ -463,6 +589,7 @@ Object doConvertTimeOfDay( Object[] arguments, @CachedLibrary(limit = "10") TypesLibrary types, @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedLibrary(limit = "10") WarningsLibrary warnings, @Cached MethodResolverNode methodResolverNode) { var ctx = EnsoContext.get(this); try { @@ -479,6 +606,7 @@ Object doConvertTimeOfDay( @Specialization( guards = { + "!warnings.hasWarnings(self)", "!methods.hasType(self)", "!methods.hasSpecialDispatch(self)", "getPolyglotCallType(self, symbol, interop) == NOT_SUPPORTED" @@ -491,6 +619,7 @@ Object doFallback( Object[] arguments, @CachedLibrary(limit = "10") TypesLibrary methods, @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedLibrary(limit = "10") WarningsLibrary warnings, @Cached MethodResolverNode anyResolverNode) { var ctx = EnsoContext.get(this); Function function = anyResolverNode.expectNonNull(self, ctx.getBuiltins().any(), symbol); 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 f9f5fd5becb5..151c32cccbee 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 @@ -30,7 +30,7 @@ @ImportStatic({CallArgumentInfo.ArgumentMappingBuilder.class, Constants.CacheSizes.class}) public abstract class InvokeFunctionNode extends BaseNode { - private @CompilationFinal(dimensions = 1) CallArgumentInfo[] schema; + private final CallArgumentInfo[] schema; private final InvokeCallableNode.DefaultsExecutionMode defaultsExecutionMode; private final InvokeCallableNode.ArgumentsExecutionMode argumentsExecutionMode; private @Child CaptureCallerInfoNode captureCallerInfoNode = CaptureCallerInfoNode.build(); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java index 58b3ed74e5cf..9dfdecaec14f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java @@ -68,6 +68,24 @@ public Function resolveFor(Type type) { return null; } + /** + * Resolves the type where the symbol is declared. + * + * @param type the type for which this symbol should be resolved + * @return the resolved function definition, or null if not found + */ + public Type resolveDeclaringType(Type type) { + Type current = type; + while (current != null) { + Function candidate = scope.lookupMethodDefinition(current, name); + if (candidate != null) { + return current; + } + current = current.getSupertype(); + } + return null; + } + @Override public String toString() { return "UnresolvedSymbol<" + this.name + ">"; 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 index a010c22528e5..07247bda6da6 100644 --- 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 @@ -85,25 +85,6 @@ public static WithWarnings attach(EnsoContext ctx, Object value, Object warning, return new WithWarnings(value, new Warning(warning, origin, ctx.clockTick())); } - @Builtin.Method( - name = "has_warnings_builtin", - description = "Are any warnings attached to the value.", - autoRegister = false) - @Builtin.Specialize - @CompilerDirectives.TruffleBoundary - public static Boolean hasWarnings(WithWarnings value, WarningsLibrary warningsLib) { - return value.hasWarnings(); - } - - @Builtin.Method( - name = "has_warnings_builtin", - description = "Are any warnings attached to the value.", - autoRegister = false) - @Builtin.Specialize(fallback = true) - public static Boolean hasWarnings(Object value, WarningsLibrary warnings) { - return warnings.hasWarnings(value); - } - @Builtin.Method( name = "get_all_array", description = "Gets all the warnings associated with the value.", 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 index ea1536cd23ef..aa3e3bc96cce 100644 --- 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 @@ -100,6 +100,14 @@ public static WithWarnings prependTo(Object target, ArrayRope warnings) } } + public static WithWarnings prependTo(Object target, Warning[] warnings) { + if (target instanceof WithWarnings) { + return ((WithWarnings) target).prepend(warnings); + } else { + return new WithWarnings(target, warnings); + } + } + @ExportMessage Object send(Message message, Object[] args, @CachedLibrary(limit = "3") ReflectionLibrary lib) throws Exception { diff --git a/test/Tests/src/Semantic/Warnings_Spec.enso b/test/Tests/src/Semantic/Warnings_Spec.enso index 3d5cdb28b17a..d94fa98f64ae 100644 --- a/test/Tests/src/Semantic/Warnings_Spec.enso +++ b/test/Tests/src/Semantic/Warnings_Spec.enso @@ -1,4 +1,5 @@ from Standard.Base import all +import Standard.Base.Errors.Unimplemented.Unimplemented polyglot java import java.lang.Long polyglot java import java.util.function.Function as Java_Function @@ -17,6 +18,20 @@ My_Type.my_method self = self.a + self.b + self.c type Wrap Value foo +type My_Fancy_Collection + Value (x:Integer) + + get_all : Vector Integer + get_all self = [self.x] + + has_warnings : Boolean + has_warnings self = False + + remove_warnings : Any -> Integer + remove_warnings self warning_type=Any = + _ = warning_type + 42 + rewrap w = case w of Wrap.Value a -> Wrap.Value a+1 @@ -149,12 +164,24 @@ spec = Test.group "Dataflow Warnings" <| rewarned.should_equal 'foo' Warning.get_all rewarned . map .value . should_contain_the_same_elements_as [2,4] + Test.specify "should allow checking for any warnings" <| + Warning.has_warnings "foo" . should_be_false + "foo".has_warnings.should_be_false + + warned = Warning.attach 1 "foo" + warned.has_warnings.should_be_true + Warning.has_warnings warned . should_be_true + Test.specify "should allow to clear warnings" <| warned = Warning.attach 1 <| Warning.attach 2 <| Warning.attach 3 <| Warning.attach 4 "foo" cleared = Warning.clear warned cleared.should_equal 'foo' Warning.get_all cleared . map .value . should_equal [] + clear_2 = warned.remove_warnings + clear_2.should_equal 'foo' + Warning.get_all clear_2 . map .value . should_equal [] + Test.specify "should allow to run a function suspending warnings attached to an argument and reattach them to the result" <| x = Warning.attach 1 <| Warning.attach 2 <| Warning.attach 3 <| Warning.attach 4 "foo" y = Warning.with_suspended x x-> @@ -194,6 +221,40 @@ spec = Test.group "Dataflow Warnings" <| Warning.get_all (detached_pair . first) . map .value . should_equal [2,4] detached_pair.second . map .value . should_equal [1,3] + Test.specify "should allow to checking for warnings, by type" <| + warned = Warning.attach 1 <| Warning.attach "Alpha" <| Warning.attach Nothing <| Warning.attach (Unimplemented.Error "An Error Here") "foo" + + warned.has_warnings.should_be_true + warned.has_warnings warning_type=Integer . should_be_true + warned.has_warnings warning_type=Number . should_be_true + warned.has_warnings warning_type=Decimal . should_be_false + warned.has_warnings warning_type=Text . should_be_true + warned.has_warnings warning_type=Nothing . should_be_true + warned.has_warnings warning_type=Unimplemented . should_be_true + + Test.specify "should allow to remove warnings, by type" <| + warned = Warning.attach 1 <| Warning.attach "Alpha" <| Warning.attach Nothing <| Warning.attach (Unimplemented.Error "An Error Here") "foo" + + no_int = warned.remove_warnings warning_type=Integer . first + Warning.get_all no_int . map .value . should_equal ["Alpha", Nothing, (Unimplemented.Error "An Error Here")] + + no_text = warned.remove_warnings Text + Warning.get_all no_text . map .value . should_equal [1, Nothing, (Unimplemented.Error "An Error Here")] + + no_nothing = warned.remove_warnings Nothing + Warning.get_all no_nothing . map .value . should_equal [1, "Alpha", (Unimplemented.Error "An Error Here")] + + no_error = warned.remove_warnings Unimplemented + Warning.get_all no_error . map .value . should_equal [1, "Alpha", Nothing] + + Test.specify "should allow to throwing warnings, by type" <| + warned = Warning.attach 1 <| Warning.attach "Alpha" <| Warning.attach Nothing <| Warning.attach (Unimplemented.Error "An Error Here") "foo" + + warned.throw_on_warning . should_fail_with Integer + warned.throw_on_warning warning_type=Text . should_fail_with Text + warned.throw_on_warning warning_type=Nothing . should_fail_with Nothing + warned.throw_on_warning warning_type=Unimplemented . should_fail_with Unimplemented + Test.specify "should allow to map the warnings, selectively" <| x = Warning.attach "foo" 1 result = x.is_static_nothing x @@ -298,4 +359,16 @@ spec = Test.group "Dataflow Warnings" <| r4.should_equal Nothing Warning.get_all r4 . map .value . should_contain_the_same_elements_as ["i(1)", "x"] + Test.specify "should not affect method dispatch" <| + a = My_Fancy_Collection.Value 42 + b = Warning.attach "WARN" <| My_Fancy_Collection.Value 23 + + a.get_all . should_equal [42] + b.get_all . should_equal [23] + a.has_warnings . should_equal False + b.has_warnings . should_equal False + a.remove_warnings . should_equal 42 + b.remove_warnings . should_equal 42 + + main = Test_Suite.run_main spec