Restore existing bindings when unbinding caught exceptions (#5256)

## Summary

In the latest release, we made some improvements to the semantic model,
but our modifications to exception-unbinding are causing some
false-positives. For example:

```py
try:
    v = 3
except ImportError as v:
    print(v)
else:
    print(v)
```

In the latest release, we started unbinding `v` after the `except`
handler. (We used to restore the existing binding, the `v = 3`, but this
was quite complicated.) Because we don't have full branch analysis, we
can't then know that `v` is still bound in the `else` branch.

The solution here modifies `resolve_read` to skip-lookup when hitting
unbound exceptions. So when store the "unbind" for `except ImportError
as v`, we save the binding that it shadowed `v = 3`, and skip to that.

Closes #5249.

Closes #5250.
This commit is contained in:
Charlie Marsh 2023-06-21 12:53:58 -04:00 committed by GitHub
parent d99b3bf661
commit ecf61d49fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 429 additions and 25 deletions

View file

@ -75,7 +75,7 @@ impl<'a> Binding<'a> {
pub const fn is_unbound(&self) -> bool {
matches!(
self.kind,
BindingKind::Annotation | BindingKind::Deletion | BindingKind::UnboundException
BindingKind::Annotation | BindingKind::Deletion | BindingKind::UnboundException(_)
)
}
@ -427,7 +427,11 @@ pub enum BindingKind<'a> {
///
/// After the `except` block, `x` is unbound, despite the lack
/// of an explicit `del` statement.
UnboundException,
///
///
/// Stores the ID of the binding that was shadowed in the enclosing
/// scope, if any.
UnboundException(Option<BindingId>),
}
bitflags! {