mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:56 +00:00
Move undefined deletions into post-model-building pass (#5904)
## Summary Similar to #5902, but for undefined names in deletions (e.g., `del x` where `x` is unbound).
This commit is contained in:
parent
266e684192
commit
fe7505b738
3 changed files with 41 additions and 38 deletions
|
@ -2407,32 +2407,7 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
ExprContext::Del => {
|
||||
if let Some(binding_id) = self.semantic.scope().get(id) {
|
||||
// If the name is unbound, then it's an error.
|
||||
if self.enabled(Rule::UndefinedName) {
|
||||
let binding = self.semantic.binding(binding_id);
|
||||
if binding.is_unbound() {
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedName {
|
||||
name: id.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the name isn't bound at all, then it's an error.
|
||||
if self.enabled(Rule::UndefinedName) {
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedName {
|
||||
name: id.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprContext::Del => {}
|
||||
}
|
||||
if self.enabled(Rule::SixPY3) {
|
||||
flake8_2020::rules::name_or_attribute(self, expr);
|
||||
|
@ -4441,7 +4416,7 @@ impl<'a> Checker<'a> {
|
|||
let Expr::Name(ast::ExprName { id, .. }) = expr else {
|
||||
return;
|
||||
};
|
||||
self.semantic.resolve_read(id, expr.range());
|
||||
self.semantic.resolve_load(id, expr.range());
|
||||
}
|
||||
|
||||
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
||||
|
@ -4557,11 +4532,7 @@ impl<'a> Checker<'a> {
|
|||
return;
|
||||
};
|
||||
|
||||
// Treat the deletion of a name as a reference to that name.
|
||||
if let Some(binding_id) = self.semantic.scope().get(id) {
|
||||
self.semantic
|
||||
.add_local_reference(binding_id, expr.range(), ExecutionContext::Runtime);
|
||||
}
|
||||
self.semantic.resolve_del(id, expr.range());
|
||||
|
||||
if helpers::on_conditional_branch(&mut self.semantic.parents()) {
|
||||
return;
|
||||
|
@ -4743,7 +4714,7 @@ impl<'a> Checker<'a> {
|
|||
}
|
||||
|
||||
for reference in self.semantic.unresolved_references() {
|
||||
if reference.wildcard_import() {
|
||||
if reference.is_wildcard_import() {
|
||||
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithImportStarUsage {
|
||||
|
|
|
@ -251,8 +251,28 @@ impl<'a> SemanticModel<'a> {
|
|||
.map_or(true, |binding| binding.kind.is_builtin())
|
||||
}
|
||||
|
||||
/// Resolve a read reference to `symbol` at `range`.
|
||||
pub fn resolve_read(&mut self, symbol: &str, range: TextRange) -> ReadResult {
|
||||
/// Resolve a `del` reference to `symbol` at `range`.
|
||||
pub fn resolve_del(&mut self, symbol: &str, range: TextRange) {
|
||||
let is_unbound = self.scopes[self.scope_id]
|
||||
.get(symbol)
|
||||
.map_or(true, |binding_id| {
|
||||
// Treat the deletion of a name as a reference to that name.
|
||||
self.add_local_reference(binding_id, range, ExecutionContext::Runtime);
|
||||
self.bindings[binding_id].is_unbound()
|
||||
});
|
||||
|
||||
// If the binding is unbound, we need to add an unresolved reference.
|
||||
if is_unbound {
|
||||
self.unresolved_references.push(
|
||||
range,
|
||||
self.exceptions(),
|
||||
UnresolvedReferenceFlags::empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a `load` reference to `symbol` at `range`.
|
||||
pub fn resolve_load(&mut self, symbol: &str, range: TextRange) -> ReadResult {
|
||||
// PEP 563 indicates that if a forward reference can be resolved in the module scope, we
|
||||
// should prefer it over local resolutions.
|
||||
if self.in_forward_reference() {
|
||||
|
@ -436,7 +456,7 @@ impl<'a> SemanticModel<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Lookup a symbol in the current scope. This is a carbon copy of [`Self::resolve_read`], but
|
||||
/// Lookup a symbol in the current scope. This is a carbon copy of [`Self::resolve_load`], but
|
||||
/// doesn't add any read references to the resolved symbol.
|
||||
pub fn lookup_symbol(&self, symbol: &str) -> Option<BindingId> {
|
||||
if self.in_forward_reference() {
|
||||
|
|
|
@ -78,19 +78,23 @@ pub struct UnresolvedReference {
|
|||
}
|
||||
|
||||
impl UnresolvedReference {
|
||||
/// The range of the reference in the source code.
|
||||
pub const fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
|
||||
/// The set of exceptions that were handled when resolution was attempted.
|
||||
pub const fn exceptions(&self) -> Exceptions {
|
||||
self.exceptions
|
||||
}
|
||||
|
||||
pub const fn wildcard_import(&self) -> bool {
|
||||
/// Returns `true` if the unresolved reference may be resolved by a wildcard import.
|
||||
pub const fn is_wildcard_import(&self) -> bool {
|
||||
self.flags
|
||||
.contains(UnresolvedReferenceFlags::WILDCARD_IMPORT)
|
||||
}
|
||||
|
||||
/// Returns the name of the reference.
|
||||
pub fn name<'a>(&self, locator: &Locator<'a>) -> &'a str {
|
||||
locator.slice(self.range)
|
||||
}
|
||||
|
@ -99,7 +103,15 @@ impl UnresolvedReference {
|
|||
bitflags! {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct UnresolvedReferenceFlags: u8 {
|
||||
/// The unresolved reference appeared in a context that includes a wildcard import.
|
||||
/// The unresolved reference may be resolved by a wildcard import.
|
||||
///
|
||||
/// For example, the reference `x` in the following code may be resolved by the wildcard
|
||||
/// import of `module`:
|
||||
/// ```python
|
||||
/// from module import *
|
||||
///
|
||||
/// print(x)
|
||||
/// ```
|
||||
const WILDCARD_IMPORT = 1 << 0;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue