-
Notifications
You must be signed in to change notification settings - Fork 323
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
base: develop
Are you sure you want to change the base?
Changes from all commits
1c2d007
49c628e
91f7497
1ed8505
7373304
00516f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package org.enso.interpreter.test.semantic; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
import org.enso.common.MethodNames; | ||
import org.enso.test.utils.ContextUtils; | ||
import org.graalvm.polyglot.Context; | ||
import org.graalvm.polyglot.PolyglotException; | ||
import org.graalvm.polyglot.Value; | ||
import org.junit.AfterClass; | ||
import org.junit.BeforeClass; | ||
import org.junit.Test; | ||
|
||
public class DataflowErrorPropagationTest { | ||
private static Context ctx; | ||
private static Value suppressError; | ||
private static Value suppressErrorWithAssign; | ||
|
||
@BeforeClass | ||
public static void prepareCtx() { | ||
ctx = ContextUtils.createDefaultContext(); | ||
var code = | ||
""" | ||
from Standard.Base import all | ||
|
||
private yield_error yes:Boolean -> Text = | ||
if yes then Error.throw "Yielding an error" else | ||
"OK" | ||
|
||
suppress_error yes:Boolean value = | ||
yield_error yes | ||
value | ||
|
||
suppress_error_with_assign yes:Boolean value = | ||
_ = yield_error yes | ||
value | ||
"""; | ||
suppressError = | ||
ctx.eval("enso", code).invokeMember(MethodNames.Module.EVAL_EXPRESSION, "suppress_error"); | ||
suppressErrorWithAssign = | ||
ctx.eval("enso", code) | ||
.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "suppress_error_with_assign"); | ||
} | ||
|
||
@AfterClass | ||
public static void disposeCtx() { | ||
ctx.close(); | ||
ctx = null; | ||
} | ||
|
||
@Test | ||
public void noErrorReturnValue() { | ||
var value = suppressError.execute(false, 42); | ||
assertTrue("It is a number", value.isNumber()); | ||
assertEquals(42, value.asInt()); | ||
} | ||
|
||
@Test | ||
public void propagateErrorImmediatelly() { | ||
var value = suppressError.execute(true, 42); | ||
assertFalse("It is not a number", value.isNumber()); | ||
assertTrue("It is an error", value.isException()); | ||
try { | ||
throw value.throwException(); | ||
} catch (PolyglotException ex) { | ||
assertEquals("Yielding an error", ex.getMessage()); | ||
} | ||
} | ||
|
||
@Test | ||
public void noErrorReturnValueWithAssign() { | ||
var value = suppressErrorWithAssign.execute(false, 42); | ||
assertTrue("It is a number", value.isNumber()); | ||
assertEquals(42, value.asInt()); | ||
} | ||
|
||
@Test | ||
public void errorIsAssignedAndThatIsEnoughReturnValue() { | ||
var value = suppressErrorWithAssign.execute(true, 42); | ||
assertTrue("It is a number", value.isNumber()); | ||
assertFalse("Not an error", value.isException()); | ||
assertEquals(42, value.asInt()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,8 @@ | |
import com.oracle.truffle.api.source.SourceSection; | ||
import java.util.Set; | ||
import org.enso.interpreter.node.ExpressionNode; | ||
import org.enso.interpreter.runtime.EnsoContext; | ||
import org.enso.interpreter.runtime.error.DataflowError; | ||
|
||
/** | ||
* This node defines the body of a function for execution, as well as the protocol for executing the | ||
|
@@ -55,8 +57,15 @@ public static BlockNode buildSilent(ExpressionNode[] expressions, ExpressionNode | |
@Override | ||
@ExplodeLoop | ||
public Object executeGeneric(VirtualFrame frame) { | ||
var ctx = EnsoContext.get(this); | ||
var nothing = ctx.getBuiltins().nothing(); | ||
for (ExpressionNode statement : statements) { | ||
statement.executeGeneric(frame); | ||
var result = statement.executeGeneric(frame); | ||
if (result != nothing) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not get rid of this if statement and use simply just There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
if (result instanceof DataflowError err) { | ||
return err; | ||
} | ||
} | ||
} | ||
return returnExpr.executeGeneric(frame); | ||
} | ||
|
There was a problem hiding this comment.
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:
Error
returned fromyield_error
was ignoredvalue
was returnedError
is detected and execution of this method ends immediately with theError
To suppress such behavior one may simply prefix the call with a dummy
_ = yield_error yes
assignment. As assignments always returnNothing
the block statement will be considered OK as theerrorIsAssignedAndThatIsEnoughReturnValue
test demonstrates.There was a problem hiding this comment.
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.There was a problem hiding this comment.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can such an error be missed without a
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:
the second line wouldn't even be invoked.
There was a problem hiding this comment.
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:
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.