From 1e497162d1f3dd0c22cd3dba2a3c96efaff93f45 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 14 Jun 2023 09:58:48 -0400 Subject: [PATCH] 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` --- crates/ruff/src/checkers/ast/mod.rs | 6 +- crates/ruff_python_semantic/src/model.rs | 71 +++++++++++++++++++++--- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 11e6f4de3a..6a496c39de 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -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 = self @@ -4381,7 +4381,7 @@ impl<'a> Checker<'a> { )); } } - ResolvedRead::NotFound => { + ResolvedRead::NotFound | ResolvedRead::UnboundLocal(_) => { // F821 if self.enabled(Rule::UndefinedName) { // Allow __path__. diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index c26b4dfb62..afb0e389e9 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -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, }