Add a dedicated read result for unbound locals (#5083)

## Summary

Small follow-up to #4888 to add a dedicated `ResolvedRead` case for
unbound locals, mostly for clarity and documentation purposes (no
behavior changes).

## Test Plan

`cargo test`
This commit is contained in:
Charlie Marsh 2023-06-14 09:58:48 -04:00 committed by GitHub
parent aa41ffcfde
commit 1e497162d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 12 deletions

View file

@ -4355,10 +4355,10 @@ impl<'a> Checker<'a> {
return;
};
match self.semantic_model.resolve_read(id, expr.range()) {
ResolvedRead::Resolved(..) | ResolvedRead::ImplicitGlobal => {
ResolvedRead::Resolved(_) | ResolvedRead::ImplicitGlobal => {
// Nothing to do.
}
ResolvedRead::StarImport => {
ResolvedRead::WildcardImport => {
// F405
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
let sources: Vec<String> = self
@ -4381,7 +4381,7 @@ impl<'a> Checker<'a> {
));
}
}
ResolvedRead::NotFound => {
ResolvedRead::NotFound | ResolvedRead::UnboundLocal(_) => {
// F821
if self.enabled(Rule::UndefinedName) {
// Allow __path__.

View file

@ -59,7 +59,7 @@ pub struct SemanticModel<'a> {
/// Map from binding ID to binding ID that it shadows (in another scope).
///
/// For example:
/// For example, given:
/// ```python
/// import x
///
@ -266,6 +266,7 @@ impl<'a> SemanticModel<'a> {
// The `name` in `print(name)` should be treated as unresolved, but the `name` in
// `name: str` should be treated as used.
BindingKind::Annotation => continue,
// If it's a deletion, don't treat it as resolved, since the name is now
// unbound. For example, given:
//
@ -276,11 +277,15 @@ impl<'a> SemanticModel<'a> {
// ```
//
// The `x` in `print(x)` should be treated as unresolved.
BindingKind::Deletion | BindingKind::UnboundException => break,
_ => {}
}
BindingKind::Deletion | BindingKind::UnboundException => {
return ResolvedRead::UnboundLocal(binding_id)
}
return ResolvedRead::Resolved(binding_id);
// Otherwise, treat it as resolved.
_ => {
return ResolvedRead::Resolved(binding_id);
}
}
}
// Allow usages of `__module__` and `__qualname__` within class scopes, e.g.:
@ -309,7 +314,7 @@ impl<'a> SemanticModel<'a> {
}
if import_starred {
ResolvedRead::StarImport
ResolvedRead::WildcardImport
} else {
ResolvedRead::NotFound
}
@ -1065,14 +1070,62 @@ pub struct Snapshot {
#[derive(Debug)]
pub enum ResolvedRead {
/// The read reference is resolved to a specific binding.
///
/// For example, given:
/// ```python
/// x = 1
/// print(x)
/// ```
///
/// The `x` in `print(x)` is resolved to the binding of `x` in `x = 1`.
Resolved(BindingId),
/// The read reference is resolved to a context-specific, implicit global (e.g., `__class__`
/// within a class scope).
///
/// For example, given:
/// ```python
/// class C:
/// print(__class__)
/// ```
///
/// The `__class__` in `print(__class__)` is resolved to the implicit global `__class__`.
ImplicitGlobal,
/// The read reference is unresolved, but at least one of the containing scopes contains a star
/// import.
StarImport,
/// The read reference is unresolved, but at least one of the containing scopes contains a
/// wildcard import.
///
/// For example, given:
/// ```python
/// from x import *
///
/// print(y)
/// ```
///
/// The `y` in `print(y)` is unresolved, but the containing scope contains a wildcard import,
/// so `y` _may_ be resolved to a symbol imported by the wildcard import.
WildcardImport,
/// The read reference is resolved, but to an unbound local variable.
///
/// For example, given:
/// ```python
/// x = 1
/// del x
/// print(x)
/// ```
///
/// The `x` in `print(x)` is an unbound local.
UnboundLocal(BindingId),
/// The read reference is definitively unresolved.
///
/// For example, given:
/// ```python
/// print(x)
/// ```
///
/// The `x` in `print(x)` is definitively unresolved.
NotFound,
}