mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-17 09:00:26 +00:00
Enable attribute lookups via semantic model (#5536)
## Summary This PR enables us to resolve attribute accesses within files, at least for static and class methods. For example, we can now detect that this is a function access (and avoid a false-positive): ```python class Class: @staticmethod def error(): return ValueError("Something") # OK raise Class.error() ``` Closes #5487. Closes #5416.
This commit is contained in:
parent
9478454b96
commit
9e1039f823
7 changed files with 89 additions and 31 deletions
|
@ -126,11 +126,11 @@ impl<'a> Binding<'a> {
|
|||
}
|
||||
matches!(
|
||||
existing.kind,
|
||||
BindingKind::ClassDefinition
|
||||
| BindingKind::FunctionDefinition
|
||||
| BindingKind::Import(..)
|
||||
| BindingKind::FromImport(..)
|
||||
| BindingKind::SubmoduleImport(..)
|
||||
BindingKind::ClassDefinition(_)
|
||||
| BindingKind::FunctionDefinition(_)
|
||||
| BindingKind::Import(_)
|
||||
| BindingKind::FromImport(_)
|
||||
| BindingKind::SubmoduleImport(_)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -372,14 +372,14 @@ pub enum BindingKind<'a> {
|
|||
/// class Foo:
|
||||
/// ...
|
||||
/// ```
|
||||
ClassDefinition,
|
||||
ClassDefinition(ScopeId),
|
||||
|
||||
/// A binding for a function, like `foo` in:
|
||||
/// ```python
|
||||
/// def foo():
|
||||
/// ...
|
||||
/// ```
|
||||
FunctionDefinition,
|
||||
FunctionDefinition(ScopeId),
|
||||
|
||||
/// A binding for an `__all__` export, like `__all__` in:
|
||||
/// ```python
|
||||
|
|
|
@ -414,7 +414,7 @@ impl<'a> SemanticModel<'a> {
|
|||
|
||||
/// Lookup a symbol in the current scope. This is a carbon copy of [`Self::resolve_read`], but
|
||||
/// doesn't add any read references to the resolved symbol.
|
||||
pub fn lookup(&mut self, symbol: &str) -> Option<BindingId> {
|
||||
pub fn lookup_symbol(&self, symbol: &str) -> Option<BindingId> {
|
||||
if self.in_forward_reference() {
|
||||
if let Some(binding_id) = self.scopes.global().get(symbol) {
|
||||
if !self.bindings[binding_id].is_unbound() {
|
||||
|
@ -456,6 +456,32 @@ impl<'a> SemanticModel<'a> {
|
|||
None
|
||||
}
|
||||
|
||||
/// Lookup a qualified attribute in the current scope.
|
||||
///
|
||||
/// For example, given `["Class", "method"`], resolve the `BindingKind::ClassDefinition`
|
||||
/// associated with `Class`, then the `BindingKind::FunctionDefinition` associated with
|
||||
/// `Class#method`.
|
||||
pub fn lookup_attribute(&'a self, value: &'a Expr) -> Option<BindingId> {
|
||||
let call_path = collect_call_path(value)?;
|
||||
|
||||
// Find the symbol in the current scope.
|
||||
let (symbol, attribute) = call_path.split_first()?;
|
||||
let mut binding_id = self.lookup_symbol(symbol)?;
|
||||
|
||||
// Recursively resolve class attributes, e.g., `foo.bar.baz` in.
|
||||
let mut tail = attribute;
|
||||
while let Some((symbol, rest)) = tail.split_first() {
|
||||
// Find the next symbol in the class scope.
|
||||
let BindingKind::ClassDefinition(scope_id) = self.binding(binding_id).kind else {
|
||||
return None;
|
||||
};
|
||||
binding_id = self.scopes[scope_id].get(symbol)?;
|
||||
tail = rest;
|
||||
}
|
||||
|
||||
Some(binding_id)
|
||||
}
|
||||
|
||||
/// Given a `BindingId`, return the `BindingId` of the submodule import that it aliases.
|
||||
fn resolve_submodule(
|
||||
&self,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue