Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propagate DataflowError in middle of block instead of ignoring it #11777

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from

Conversation

JaroslavTulach
Copy link
Member

@JaroslavTulach JaroslavTulach commented Dec 5, 2024

Pull Request Description

Partially fixes #5430 by propagating DataflowErrors found during statement execution out of the method.

Important Notes

This change may affect behavior of existing methods that ignore DataflowError as discussed here.

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • All code follows the
    Scala,
    Java,
  • Unit tests have been written where possible.
  • Engine benchmarks run is fine
  • StdLib benchmarks run seems fine

@JaroslavTulach JaroslavTulach changed the title Currently a DataflowError may get lost in a middle of block statements Propagate DataflowError in middle of block instead of ignoring it Dec 5, 2024
"OK"

suppress_error yes:Boolean value =
yield_error yes
Copy link
Member Author

@JaroslavTulach JaroslavTulach Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This 49c628e commit changes the behavior on this line:

  • previously the Error returned from yield_error was ignored
  • previously the execution continued to next line and value was returned
  • now the Error is detected and execution of this method ends immediately with the Error

To suppress such behavior one may simply prefix the call with a dummy _ = yield_error yes assignment. As assignments always return Nothing the block statement will be considered OK as the errorIsAssignedAndThatIsEnoughReturnValue test demonstrates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we want assignment to also propagate errors in this way? This is another class of common missed errors, I would think. Only catch should allow you to stop propagation, I would think.

Copy link
Member

@radeusgd radeusgd Dec 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate and show some example where you think the behaviour is not what we want?

From my perspective, with the changes from this PR all is good - the dataflow errors flow with the dataflow, determined by dependencies between method calls. The problem was when the value was unassigned because the dataflow error would be lost unhandled. But if it's assigned to something, it should flow further and be propagated. Unless the name is unused - but we have 'unused warning' for that already.

I think that propagating 'more aggressively' upon assignment would make the dataflow error not much (if at all in practice?) different from a Panic. Also it may be problematic for the IDE. The whole point of dataflow errors is that they do not stop execution of other nodes if they are assigned to something, so that even if one node in the GUI errors, all the others can still be executed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assignment to also propagate errors in this way?
This is another class of common missed errors, I would think.

Can such an error be missed without a

we have 'unused warning' for that already.

warning being printed by the compiler? If not, then it is less critical scenario. Users get compilation warning and that might warn them enough.

Btw. I think there is a contra-example:

x = Error.throw "OK"
x . catch Any h->h

the second line wouldn't even be invoked.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@radeusgd I definitely see your point -- assignments like this form the top-level spine of the entire program, so this would prevent execution after a dataflow error. For that case, I agree. For an assignment with a regular method call, one that does not represent a top-level node (from the user's point of view), it's still something that could be lost.

@JaroslavTulach I see your point here as well. However, this case:

x = Error.throw "OK"

Will still mask errors, and hide them from the end user and the library developer. I don't have a suggestion for how to handle this, but it still should be handled. If something is called "error" then it should never be hidden.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mask errors, and hide them from ... the library developer

No, it won't hide them. There will be a warning about unused variable x. E.g. library developer has been notified the code is smelly.

for (ExpressionNode statement : statements) {
statement.executeGeneric(frame);
var result = statement.executeGeneric(frame);
if (result != nothing) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not get rid of this if statement and use simply just if (result instanceof DataflowError err). If it is an instance of DataflowError, it cannot be nothing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed this seems redundant, can we remove it, or is there some reason that we don't see here?

Copy link
Member Author

@JaroslavTulach JaroslavTulach Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is the other part of #5430:

We don't need to solve it in this PR. Just my 2 Kč explanation of the != Nothing check. In any case, that's for another PR.

@JaroslavTulach
Copy link
Member Author

JaroslavTulach commented Dec 5, 2024

There are two failed tests in standard libraries:

This is the kind of errors we can expect if we enable this DataflowError propagation. Can you fix the tests easily, @GregoryTravis, @radeusgd?

Btw. this change makes the Decimal_Spec error go away:

diff --git test/Base_Tests/src/Data/Decimal_Spec.enso test/Base_Tests/src/Data/Decimal_Spec.enso
index 3465ce9cd8..6e3a463b82 100644
--- test/Base_Tests/src/Data/Decimal_Spec.enso
+++ test/Base_Tests/src/Data/Decimal_Spec.enso
@@ -487,7 +487,7 @@ add_specs suite_builder =
             (Decimal.from_integer 7297927982888383 . multiply  (Decimal.from_integer 828737) (Math_Context.new 6)) . should_equal (Decimal.from_integer 6048060000000000000000 )
             (Decimal.new "893872388.3535345" . multiply  (Decimal.new "72374727737.23434535") (Math_Context.new 14)) . should_equal (Decimal.new "64693770738918000000")
 
-            (Decimal.new "909678645268840" . divide (Decimal.new "28029830") (Math_Context.new 6)) . should_equal (Decimal.new "32453900 ")
+            _ = (Decimal.new "909678645268840" . divide (Decimal.new "28029830") (Math_Context.new 6)) . should_equal (Decimal.new "32453900 ")
             (Decimal.new "384456406.7860325392609633764" . divide (Decimal.new "24556.125563546") (Math_Context.new 7)) . should_equal (Decimal.new "15656.23")
 
             (Decimal.from_integer 3948539458034580838458034803485 . add (Decimal.from_integer 237957498573948579387495837459837) (Math_Context.new 20)) . should_equal (Decimal.from_integer 241906038031983160230000000000000)

e.g. exactly as expected it is enough to add a dummy assignment and the problem goes away. I assume you want to do a better fix @GregoryTravis - please integrate it into my branch, so it gets green.

diff --git test/Base_Tests/src/Data/Time/Date_Range_Spec.enso test/Base_Tests/src/Data/Time/Date_Range_Spec.enso
index b2b469ae1c..22843aa6cb 100644
--- test/Base_Tests/src/Data/Time/Date_Range_Spec.enso
+++ test/Base_Tests/src/Data/Time/Date_Range_Spec.enso
@@ -157,7 +157,7 @@ add_specs suite_builder =
                 r.partition p . should_equal (r.to_vector.partition p)
                 r.all p . should_equal (r.to_vector.all p)
                 r.any p . should_equal (r.to_vector.any p)
-                r.find p . should_equal (r.to_vector.find p)
+                _ = r.find p . should_equal (r.to_vector.find p)
                 r.index_of p . should_equal (r.to_vector.index_of p)
                 r.last_index_of p . should_equal (r.to_vector.last_index_of p)
                 count_mondays acc date =
@@ -170,7 +170,7 @@ add_specs suite_builder =
                 r.partition fc . should_equal (r.to_vector.partition fc)
                 r.all fc . should_equal (r.to_vector.all fc)
                 r.any fc . should_equal (r.to_vector.any fc)
-                r.find fc . should_equal (r.to_vector.find fc)
+                _ = r.find fc . should_equal (r.to_vector.find fc)
                 r.index_of fc . should_equal (r.to_vector.index_of fc)
                 r.last_index_of fc . should_equal (r.to_vector.last_index_of fc)

same for Date_Range_Spec @radeusgd.

Copy link
Member

@radeusgd radeusgd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Santa came early this year! 🎄
I'm so happy to finally see this change - deep down I knew we may be losing something due to the unhandled errors disappearing into the void. And you confirmed my suspicions. Thankfully that isn't that many errors and they look relatively harmless. But I'm happy that from now on I can keep writing code with the peace of mind that errors will go on unnoticed.

Also we can now remove most instances of if_not_error - yay! 🎉 It was basically a workaround for ensuring error propagation. I guess we want to keep the function because it is still useful when we want to control the propagation with 'more precision'. But in numerous cases it should be no longer needed and we can remove a lot of unnecessary indentation from the code.

I submitted commits that fix should_equal silently breaking if expected value provided is dataflow error (in that case should_fail_with is expected). I will add a commit fixing the Date_Range_Spec tests in a moment.

@JaroslavTulach
Copy link
Member Author

we want to keep the function because ...

... we already published first customer releases and we are dedicated to backward compatibility, right?

@JaroslavTulach
Copy link
Member Author

Engine benchmarks run is fine - looks like nobody writes benchmarks for errors ;-) I also glanced StdLib benchmarks run and found nothing suspicious.

@JaroslavTulach
Copy link
Member Author

@radeusgd, @GregoryTravis there are still failures in stdlib tests:

Can you fix them?

Copy link
Member

@jdunkerley jdunkerley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving the libs bits.

@GregoryTravis
Copy link
Contributor

Can you fix them?

I will look at these, after the release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Avoid discarded (error) values in runtime
5 participants