Treat exception binding as explicit deletion (#5057)

## Summary

This PR corrects a misunderstanding I had related to Python's handling
of bound exceptions.

Previously, I thought this code ran without error:

```py
def f():
    x = 1

    try:
        1 / 0
    except Exception as x:
        pass

    print(x)
```

My understanding was that `except Exception as x` bound `x` within the
`except` block, but then restored the `x = 1` binding after exiting the
block.

In practice, however, this throws a `UnboundLocalError` error, because
`x` becomes "unbound" after exiting the exception handler. It's similar
to a `del` statement in this way.

This PR removes our behavior to "restore" the previous binding. This
could lead to faulty analysis in conditional blocks due to our lack of
control flow analysis, but those same problems already exist for `del`
statements.
This commit is contained in:
Charlie Marsh 2023-06-13 13:45:51 -04:00 committed by GitHub
parent a431dd0368
commit b0984a2868
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 6 deletions

View File

@ -3944,7 +3944,6 @@ where
);
}
let definition = self.semantic_model.scope().get(name);
self.handle_node_store(
name,
&Expr::Name(ast::ExprName {
@ -3979,11 +3978,6 @@ where
}
}
}
if let Some(binding_id) = definition {
let scope = self.semantic_model.scope_mut();
scope.add(name, binding_id);
}
}
None => walk_excepthandler(self, excepthandler),
}

View File

@ -311,6 +311,20 @@ mod tests {
"#,
"del_shadowed_local_import_in_local_scope"
)]
#[test_case(
r#"
def f():
x = 1
try:
1 / 0
except Exception as x:
pass
print(x)
"#,
"print_after_shadowing_except"
)]
fn contents(contents: &str, snapshot: &str) {
let diagnostics = test_snippet(contents, &Settings::for_rules(&Linter::Pyflakes));
assert_messages!(snapshot, diagnostics);

View File

@ -0,0 +1,32 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
---
<filename>:7:25: F841 [*] Local variable `x` is assigned to but never used
|
5 | try:
6 | 1 / 0
7 | except Exception as x:
| ^ F841
8 | pass
|
= help: Remove assignment to unused variable `x`
Suggested fix
4 4 |
5 5 | try:
6 6 | 1 / 0
7 |- except Exception as x:
7 |+ except Exception:
8 8 | pass
9 9 |
10 10 | print(x)
<filename>:10:11: F821 Undefined name `x`
|
8 | pass
9 |
10 | print(x)
| ^ F821
|