Skip to content

Commit

Permalink
Promote broken values instead of ignoring them (#11777)
Browse files Browse the repository at this point in the history
Partially fixes #5430 by propagating `DataflowError`s found during statement execution out of the method.

# Important Notes
This change [may affect behavior](https://github.com/enso-org/enso/pull/11673/files#r1871128327) of existing methods that ignore `DataflowError` as [discussed here](https://github.com/enso-org/enso/pull/11673/files#r1871128327).
  • Loading branch information
JaroslavTulach authored Dec 19, 2024
1 parent c08a53d commit 014b562
Show file tree
Hide file tree
Showing 28 changed files with 540 additions and 157 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@

#### Enso Language & Runtime

- [Promote broken values instead of ignoring them][11777].
- [Intersection types & type checks][11600]
- A constructor or type definition with a single inline argument definition was
previously allowed to use spaces in the argument definition without
parentheses. [This is now a syntax error.][11856]

[11777]: https://github.com/enso-org/enso/pull/11777
[11600]: https://github.com/enso-org/enso/pull/11600
[11856]: https://github.com/enso-org/enso/pull/11856

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,8 @@ take_helper length at single_slice slice_ranges range:(Index_Sub_Range | Range |
Index_Sub_Range.First count -> single_slice 0 (length.min count)
Index_Sub_Range.Last count -> single_slice length-count length
Index_Sub_Range.While predicate ->
end = 0.up_to length . find i-> (predicate (at i)).not
true_end = if end.is_nothing then length else end
single_slice 0 true_end
end = 0.up_to length . find (i-> (predicate (at i)).not) if_missing=length
single_slice 0 end
Index_Sub_Range.By_Index one_or_many_descriptors -> Panic.recover [Index_Out_Of_Bounds, Illegal_Argument] <|
indices = case one_or_many_descriptors of
_ : Vector -> one_or_many_descriptors
Expand Down Expand Up @@ -255,9 +254,8 @@ drop_helper length at single_slice slice_ranges range:(Index_Sub_Range | Range |
Index_Sub_Range.First count -> single_slice count length
Index_Sub_Range.Last count -> single_slice 0 length-count
Index_Sub_Range.While predicate ->
end = 0.up_to length . find i-> (predicate (at i)).not
true_end = if end.is_nothing then length else end
single_slice true_end length
end = 0.up_to length . find (i-> (predicate (at i)).not) if_missing=length
single_slice end length
Index_Sub_Range.By_Index one_or_many_descriptors -> Panic.recover [Index_Out_Of_Bounds, Illegal_Argument] <|
indices = case one_or_many_descriptors of
_ : Vector -> one_or_many_descriptors
Expand Down
5 changes: 3 additions & 2 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range.enso
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import project.Data.Text.Text
import project.Data.Vector.Vector
import project.Error.Error
import project.Errors.Common.Index_Out_Of_Bounds
import project.Errors.Common.Not_Found
import project.Errors.Empty_Error.Empty_Error
import project.Errors.Illegal_Argument.Illegal_Argument
import project.Errors.Illegal_State.Illegal_State
Expand Down Expand Up @@ -397,7 +398,7 @@ type Range
@condition range_default_filter_condition_widget
any : (Filter_Condition | (Integer -> Boolean)) -> Boolean
any self (condition : Filter_Condition | (Integer -> Boolean)) =
self.find condition . is_nothing . not
self.find condition if_missing=Nothing . is_nothing . not

## GROUP Selections
ICON find
Expand All @@ -422,7 +423,7 @@ type Range
1.up_to 100 . find (..Greater than=10)
@condition range_default_filter_condition_widget
find : (Filter_Condition | (Integer -> Boolean)) -> Integer -> Any -> Any
find self (condition : Filter_Condition | (Integer -> Boolean)) (start : Integer = 0) ~if_missing=Nothing =
find self (condition : Filter_Condition | (Integer -> Boolean)) (start : Integer = 0) ~if_missing=(Error.throw Not_Found) =
predicate = unify_condition_or_predicate condition
check_start_valid start self used_start->
result = find_internal self used_start predicate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import project.Data.Time.Period.Period
import project.Data.Vector.Vector
import project.Error.Error
import project.Errors.Common.Index_Out_Of_Bounds
import project.Errors.Common.Not_Found
import project.Errors.Empty_Error.Empty_Error
import project.Errors.Illegal_Argument.Illegal_Argument
import project.Function.Function
Expand Down Expand Up @@ -418,7 +419,7 @@ type Date_Range
@condition date_range_default_filter_condition_widget
any : (Filter_Condition | (Date -> Boolean)) -> Boolean
any self (condition : Filter_Condition | (Date -> Boolean)) =
self.find condition . is_nothing . not
self.find condition if_missing=Nothing . is_nothing . not

## GROUP Selections
ICON find
Expand All @@ -438,7 +439,7 @@ type Date_Range
(Date.new 2020 10 01).up_to (Date.new 2020 10 31) . find (d-> d.day_of_week == Day_Of_Week.Monday)
@condition date_range_default_filter_condition_widget
find : (Filter_Condition | (Date -> Boolean)) -> Integer -> Any -> Any
find self (condition : Filter_Condition | (Date -> Boolean)) (start : Integer = 0) ~if_missing=Nothing =
find self (condition : Filter_Condition | (Date -> Boolean)) (start : Integer = 0) ~if_missing=(Error.throw Not_Found) =
predicate = unify_condition_or_predicate condition
index = self.index_of predicate start
case index of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ find vector condition start ~if_missing =
predicate = unify_condition_or_predicate condition
self_len = vector.length
check_start_valid start self_len used_start->
found = used_start.up_to self_len . find (idx -> (predicate (vector.at idx)))
found = used_start.up_to self_len . find (idx -> (predicate (vector.at idx))) if_missing=Nothing
if found.is_nothing then if_missing else vector.at found

transpose vec_of_vecs =
Expand Down
105 changes: 62 additions & 43 deletions distribution/lib/Standard/Test/0.0.0-dev/src/Extensions.enso
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from Standard.Base import all
import Standard.Base.Errors.Common.Incomparable_Values
import Standard.Base.Errors.Common.No_Such_Method
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument

import project.Spec_Result.Spec_Result
import project.Test.Test
from project.Extensions_Helpers import rhs_error_check

## Expect a function to fail with the provided dataflow error.

Expand Down Expand Up @@ -70,23 +72,25 @@ Error.should_fail_with self matcher frames_to_skip=0 unwrap_errors=True =

example_should_equal = Examples.add_1_to 1 . should_equal 2
Any.should_equal : Any -> Integer -> Spec_Result
Any.should_equal self that frames_to_skip=0 = case self == that of
True -> Spec_Result.Success
False ->
loc = Meta.get_source_location 2+frames_to_skip
additional_comment = case self of
_ : Vector -> case that of
_ : Vector ->
case self.length == that.length of
True ->
diff = self.zip that . index_of p->
p.first != p.second
"; first difference at index " + diff.to_text + " "
False -> "; lengths differ (" + self.length.to_text + " != " + that.length.to_text + ") "
Any.should_equal self that frames_to_skip=0 =
rhs_error_check that
loc = Meta.get_source_location 1+frames_to_skip
case self == that of
True -> Spec_Result.Success
False ->
additional_comment = case self of
_ : Vector -> case that of
_ : Vector ->
case self.length == that.length of
True ->
diff = self.zip that . index_of p->
p.first != p.second
"; first difference at index " + diff.to_text + " "
False -> "; lengths differ (" + self.length.to_text + " != " + that.length.to_text + ") "
_ -> ""
_ -> ""
_ -> ""
msg = self.pretty + " did not equal " + that.pretty + additional_comment + " (at " + loc + ")."
Test.fail msg
msg = self.pretty + " did not equal " + that.pretty + additional_comment + " (at " + loc + ")."
Test.fail msg

## Asserts that `self` value is equal to the expected type value.

Expand Down Expand Up @@ -130,12 +134,13 @@ Error.should_equal_type self that frames_to_skip=0 =

example_should_not_equal = Examples.add_1_to 1 . should_not_equal 2
Any.should_not_equal : Any -> Integer -> Spec_Result
Any.should_not_equal self that frames_to_skip=0 = case self != that of
True -> Spec_Result.Success
False ->
loc = Meta.get_source_location 2+frames_to_skip
msg = self.to_text + " did equal " + that.to_text + " (at " + loc + ")."
Test.fail msg
Any.should_not_equal self that frames_to_skip=0 = if that.is_error then (Panic.throw (Illegal_Argument.Error "Expected value provided as `that` for `should_not_equal` cannot be an error, but got: "+that.to_display_text)) else
loc = Meta.get_source_location 2+frames_to_skip
case self != that of
True -> Spec_Result.Success
False ->
msg = self.to_text + " did equal " + that.to_text + " (at " + loc + ")."
Test.fail msg

## Added so that dataflow errors are not silently lost.
Error.should_not_equal self that frames_to_skip=0 =
Expand Down Expand Up @@ -183,15 +188,16 @@ Error.should_not_equal_type self that frames_to_skip=0 =

example_should_start_with = "Hello World!" . should_start_with "Hello"
Any.should_start_with : Text -> Integer -> Spec_Result
Any.should_start_with self that frames_to_skip=0 = case self of
_ : Text -> if self.starts_with that then Spec_Result.Success else
loc = Meta.get_source_location 3+frames_to_skip
msg = self.to_text + " does not start with " + that.to_text + " (at " + loc + ")."
Test.fail msg
_ ->
loc = Meta.get_source_location 2+frames_to_skip
msg = self.to_text + " is not a `Text` value (at " + loc + ")."
Test.fail msg
Any.should_start_with self that frames_to_skip=0 =
rhs_error_check that
loc = Meta.get_source_location 1+frames_to_skip
case self of
_ : Text -> if self.starts_with that then Spec_Result.Success else
msg = self.to_text + " does not start with " + that.to_text + " (at " + loc + ")."
Test.fail msg
_ ->
msg = self.to_text + " is not a `Text` value (at " + loc + ")."
Test.fail msg

## Asserts that `self` value is a Text value and ends with `that`.

Expand All @@ -207,15 +213,16 @@ Any.should_start_with self that frames_to_skip=0 = case self of

example_should_end_with = "Hello World!" . should_end_with "ld!"
Any.should_end_with : Text -> Integer -> Spec_Result
Any.should_end_with self that frames_to_skip=0 = case self of
_ : Text -> if self.ends_with that then Spec_Result.Success else
loc = Meta.get_source_location 3+frames_to_skip
msg = self.to_text + " does not end with " + that.to_text + " (at " + loc + ")."
Test.fail msg
_ ->
loc = Meta.get_source_location 2+frames_to_skip
msg = self.to_text + " is not a `Text` value (at " + loc + ")."
Test.fail msg
Any.should_end_with self that frames_to_skip=0 =
rhs_error_check that
loc = Meta.get_source_location 1+frames_to_skip
case self of
_ : Text -> if self.ends_with that then Spec_Result.Success else
msg = self.to_text + " does not end with " + that.to_text + " (at " + loc + ")."
Test.fail msg
_ ->
msg = self.to_text + " is not a `Text` value (at " + loc + ")."
Test.fail msg

## Asserts that `self` value is a Text value and starts with `that`.

Expand Down Expand Up @@ -267,7 +274,7 @@ Error.should_end_with self that frames_to_skip=0 =
example_should_equal = Examples.add_1_to 1 . should_equal 2
Error.should_equal : Any -> Integer -> Spec_Result
Error.should_equal self that frames_to_skip=0 =
_ = [that]
rhs_error_check that
Test.fail_match_on_unexpected_error self 1+frames_to_skip

## Asserts that `self` is within `epsilon` from `that`.
Expand All @@ -294,13 +301,18 @@ Error.should_equal self that frames_to_skip=0 =
1.00000001 . should_equal 1.00000002 epsilon=0.0001
Number.should_equal : Float -> Float -> Integer -> Spec_Result
Number.should_equal self that epsilon=0 frames_to_skip=0 =
rhs_error_check that
loc = Meta.get_source_location 1+frames_to_skip
matches = case that of
n : Number -> self.equals n epsilon
n : Number -> self.equals n epsilon . catch Incomparable_Values _->
## Incomparable_Values is thrown if one of the values is NaN.
We fallback to is_same_object_as,
because in tests we actually NaN.should_equal NaN to succeed.
self.is_same_object_as n
_ -> self==that
case matches of
True -> Spec_Result.Success
False ->
loc = Meta.get_source_location 2+frames_to_skip
msg = self.to_text + " did not equal " + that.to_text + " (at " + loc + ")."
Test.fail msg

Expand All @@ -313,6 +325,7 @@ Number.should_equal self that epsilon=0 frames_to_skip=0 =
displayed as the source of this error.
Decimal.should_equal : Number -> Float-> Float -> Integer -> Spec_Result
Decimal.should_equal self that epsilon=0 frames_to_skip=0 =
rhs_error_check that
self.to_float . should_equal that.to_float epsilon frames_to_skip+1

## Asserts that `self` value is not an error.
Expand Down Expand Up @@ -423,6 +436,7 @@ Error.should_be_false self = Test.fail_match_on_unexpected_error self 1
example_should_be_a = 1.should_be_a Boolean
Any.should_be_a : Any -> Spec_Result
Any.should_be_a self typ =
rhs_error_check typ
loc = Meta.get_source_location 1
fail_on_wrong_arg_type =
Panic.throw <|
Expand Down Expand Up @@ -490,6 +504,8 @@ Any.should_be_a self typ =
Any.should_equal_ignoring_order : Any -> Integer -> Spec_Result
Any.should_equal_ignoring_order self that frames_to_skip=0 =
loc = Meta.get_source_location 1+frames_to_skip
if that.is_a Vector . not then
Panic.throw (Illegal_Argument.Error "Expected a Vector, but got a "+that.to_display_text+" (at "+loc+").")
that.each element->
if self.contains element . not then
msg = "The collection (" + self.to_text + ") did not contain " + element.to_text + " (at " + loc + ")."
Expand Down Expand Up @@ -556,6 +572,7 @@ Error.should_equal_ignoring_order self that frames_to_skip=0 =
example_should_equal = [1, 2] . should_only_contain_elements_in [1, 2, 3, 4]
Any.should_only_contain_elements_in : Any -> Integer -> Spec_Result
Any.should_only_contain_elements_in self that frames_to_skip=0 =
rhs_error_check that
loc = Meta.get_source_location 1+frames_to_skip
self.each element->
if that.contains element . not then
Expand Down Expand Up @@ -609,6 +626,7 @@ Error.should_only_contain_elements_in self that frames_to_skip=0 =
example_should_equal = "foobar".should_contain "foo"
Any.should_contain : Any -> Integer -> Spec_Result
Any.should_contain self element frames_to_skip=0 =
rhs_error_check element
loc = Meta.get_source_location 1+frames_to_skip
contains_result = Panic.catch No_Such_Method (self.contains element) caught_panic->
if caught_panic.payload.method_name != "contains" then Panic.throw caught_panic else
Expand Down Expand Up @@ -652,6 +670,7 @@ Error.should_contain self element frames_to_skip=0 =
implementing a method `contains : a -> Boolean`.
Any.should_not_contain : Any -> Integer -> Spec_Result
Any.should_not_contain self element frames_to_skip=0 =
rhs_error_check element
loc = Meta.get_source_location 1+frames_to_skip
contains_result = Panic.catch No_Such_Method (self.contains element) caught_panic->
if caught_panic.payload.method_name != "contains" then Panic.throw caught_panic else
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
private

from Standard.Base import all
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument

## PRIVATE
A helper that ensures that the expected value provided in some of the Test
operations is not an error.
The left-hand side may be an error and that will cause a test failure.
But the right-hand side being an error is bad test design and should be fixed.
rhs_error_check that =
if that.is_error then
msg = "Dataflow error ("+that.to_display_text+") provided as expected value. Use `should_fail_with` or change the test."+ ' Error stack trace was:\n'+that.get_stack_trace_text
Panic.throw (Illegal_Argument.Error msg)
Loading

0 comments on commit 014b562

Please sign in to comment.