Implements `Warnings.get_all wrap_errors=True` which wraps warnings attached to values inside vectors with `Map_Error`, which includes the position of the value within the vector. See [the documentation](https://github.com/enso-org/enso/blob/develop/docs/semantics/wrapped-errors.md) for more details. `get_all wrap_errors=True` does not change the warnings that are attached to values -- it wraps them before returning them to the caller, but does not change the original warnings attached to the values. Wrapped warnings only appear attached to the vector itself. The values inside the vector do not have their warnings wrapped. Warning propagation is not changed at all; `Warnings.get_all` (with default `wrap_errors=False`) behaves as before. `get_all wrap_errors=True` is meant to be used primarily by the IDE, although it can be used anywhere this wrapping is desired.
4.4 KiB
layout | title | category | tags | order | |||
---|---|---|---|---|---|---|---|
developer-doc | Wrapped Errors | semantics |
|
12 |
Wrapped Errors
A wrapped error is an error wrapped in an additional 'error wrapper' value providing additional information about the error.
For example, an error attached to a value within a Vector
can be wrapped in a
Map_Error
wrapper which indicates the position of the value within the
Vector
.
(Such errors are only wrapped when they are obtained through
Warning.get_all wrap_errors=True
; see
Obtaining Wrapped Errors.)
For example:
err = My_Error.Error "my error"
vec = [10, Warning.attach err 20, 30]
IO.println (Warning.get_all vec)
IO.println (Warning.get_all wrap_errors=True vec)
Output:
[My_Error.Error my error]
[Map_Error.Error 1 (My_Error.Error my error)] # The error is at index 1
Catching Wrapped Errors
Wrapped errors are "transparent" to Error.catch
. That is, if you attempt to
catch a certain error, but the error that is actually thrown is wrapped, the
catch will still succeed. In the example below, a My_Error
error is thrown in
a call to map
, and so the resulting error is wrapped. You can catch the error
as a My_Error
, or as a Map_Error
.
fun x = if x == 20 then Error.throw (My_Error.Error "my error") else x
[10, 20, 30].map fun . catch My_Error e->
IO.println e
[10, 20, 30].map fun . catch Map_Error e->
IO.println e
Output:
(My_Error.Error 'my error')
(Map_Error.Error 1 (My_Error.Error 'my error'))
Note that if you catch the error as the inner error (My_Error
), the
Map_Error
wrapper is stripped off.
Implementing Error Wrappers
An error wrapper is a regular Enso value that has a conversion to
Wrapped_Error
. For example:
type Map_Error
Error (index:Integer) inner_error
Wrapped_Error.from (that : Map_Error) = Wrapped_Error.Value that that.inner_error
The from
implementation allows Error.catch
to detect that it is an error
wrapper, and possibly perform automatic unwrapping on it.
Obtaining Wrapped Errors
Wrapped errors are obtained in two ways:
- An error thrown during a call to
Vector.map
- An error attached to a value within a
Vector
In the case of an error thrown during Vector.map
, the error is caught, wrapped
in Map_Error
, and re-thrown.
In the case of an error attached to a value within a Vector
, the wrapper is
added by Warning.get_all wrap_errors=True
when it is called on the Vector
.
In this case, the wrapping is not attached to the value itself, and is therefore
not propagated to downstream values.
Map_Error
Map_Error
is the motivating example for wrapped errors, and is currently the
only implemented error wrapper. It exists so that an error thrown during a map
call over a large Vector
will contain the index at which the error occurred,
so it can be displayed to the user in the IDE.
Note that the error does not have to occur during a call to map
. A Map_Error
wrapping is added by Warning.get_all wrap_errors=True
to any error attached to
a value within a Vector
(or any array-like container). If the value is
extracted from the Vector
(for example, using .at
), its attached Warning
is not wrapped.
If a value is nested within multiple Vector
s, its attached errors are wrapped
with Map_Error
multiple times. The outermost Map_Error
index indicates the
index into the outermost Vector
, the second Map_Error
index the index into
the sub-Vector
within the outermost Vector
, and so on.
For example:
fun a = if a == 30 then Error.throw (My_Error.Error a) else a+1
nested_vector = [[10, 20, 30, 40], [30, 10, 20, 30]]
result = nested_vector.map (_.map fun on_problems=Problem_Behavior.Report_Warning) on_problems=Problem_Behavior.Report_Warning
warnings = Warning.get_all wrap_errors=True result . map .value
warnings.map w->
IO.println w
Output:
(Map_Error.Error 1 (Map_Error.Error 3 (My_Error.Error 30))) # [1, 3]
(Map_Error.Error 1 (Map_Error.Error 0 (My_Error.Error 30))) # [1, 0]
(Map_Error.Error 0 (Map_Error.Error 2 (My_Error.Error 30))) # [0, 2]
Testing Wrapped Errors
The following test utilities take an unwrap_errors
parameter:
Error.should_fail_with
Problem.test_problem_handling
Problem.expect_warning
Problem.expect_only_warning
By default, these methods will automatically unwrap errors. Passing
unwrap_errors=False
will disable this behavior.