Fix: Recover boolean test flag after visiting subexpressions#13909
Conversation
|
|
Thanks. Would you mind adding a test demonstrating that restoring the flag is necessary? |
@MichaReiser Could you direct me to where this test would fit? |
|
We unfortunately don't have any semantic model tests. So the best you can do is to extend the tests of a rule that uses the flag. One such rule is RUF019.py)
|
MichaReiser
left a comment
There was a problem hiding this comment.
Could you explain me why restoring the flags here is necessary.
To me, it doesn't seem necessary because restoring the semantic.flags here means that the BOOLEAN_TEST flag won't be set for the analyze::expression phase.
We do reset the `semantic.flags on line 1480 so that it is correctly restored for its ancestor and sibling expressions
|
Take for example this simple code: if not f(a):
passCurrently, when visiting the expression f(a), it loses its BOOLEAN_TEST flag. This happens because it doesn’t match the conditions for
But the Example with Current vs. Proposed BehaviorTo see the difference, here’s a comparison using various test cases: if not a(b):
pass
assert c(d)
x = e(f) or g(h)
while i == j:
pass
Testing the ChangeTesting this change with current rules is difficult, as they cover only Expr::BoolOp. However, implementing rule |
Can you expand on why do you think it's an issue for this specific rule? I think that rule would need to be implemented at the |
|
I intended to illustrate with the rule example that the I agree with your interpretation of how the
|
|
|
||
| // Restore boolean test flag after examining subexpressions | ||
| if flags_snapshot.intersects(SemanticModelFlags::BOOLEAN_TEST) { | ||
| self.semantic.flags |= SemanticModelFlags::BOOLEAN_TEST; | ||
| } | ||
|
|
||
| // Step 4: Analysis | ||
| analyze::expression(expr, self); | ||
| match expr { |
There was a problem hiding this comment.
Thanks for the explanation. The change now makes sense to me. However, I do think that this problem isn't specific to BOOLEAN_TEST but applies to all flags.
The flags should be restored when visiting the expression itself. They're only relevant when visiting the sub-expressions.
That's why I suggest that we change the code to:
// Step 4: Analysis
match expr {
Expr::StringLiteral(string_literal) => {
analyze::string_like(string_literal.into(), self);
}
Expr::BytesLiteral(bytes_literal) => analyze::string_like(bytes_literal.into(), self),
Expr::FString(f_string) => analyze::string_like(f_string.into(), self),
_ => {}
}
self.semantic.flags = flags_snapshot;
analyze::expression(expr, self);There was a problem hiding this comment.
I'm not sure about this. Could there be cases where a subexpression changes the semantics of its parent expression?
If so, this approach might not be correct.
There was a problem hiding this comment.
I skimmed through the SemanticFlags and I didn't spot any expression level flag that would change the flags for the parent or a sibling.
|
Thanks and nice find! |
Summary
This PR corrects the logic for handling the boolean test flag during expression analysis.
Since the flag can be modified during subexpression traversal, it must be restored before analyzing the full expression.
Additionally, this change allows the
in_bool_test()method incrates/ruff_python_semantic/src/model.rsto be used outside of boolean operations, such as in conditional tests withinStmt::If,Stmt::While,Stmt::Assert, and others.