mirror of
https://github.com/enso-org/enso.git
synced 2024-12-21 19:41:29 +03:00
f2cb1f097e
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.
142 lines
4.4 KiB
Markdown
142 lines
4.4 KiB
Markdown
---
|
|
layout: developer-doc
|
|
title: Wrapped Errors
|
|
category: semantics
|
|
tags: [semantics, errors, runtime]
|
|
order: 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](#obtaining-wrapped-errors).)
|
|
|
|
For example:
|
|
|
|
```ruby
|
|
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:
|
|
|
|
```ruby
|
|
[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`.
|
|
|
|
```ruby
|
|
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:
|
|
|
|
```ruby
|
|
(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:
|
|
|
|
```ruby
|
|
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:
|
|
|
|
```ruby
|
|
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:
|
|
|
|
```ruby
|
|
(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.
|