mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
Track top-level module imports in the semantic model (#9775)
## Summary This is a simple idea to avoid unnecessary work in the linter, especially for rules that run on all name and/or all attribute nodes. Imagine a rule like the NumPy deprecation check. If the user never imported `numpy`, we should be able to skip that rule entirely -- whereas today, we do a `resolve_call_path` check on _every_ name in the file. It turns out that there's basically a finite set of modules that we care about, so we now track imports on those modules as explicit flags on the semantic model. In rules that can _only_ ever trigger if those modules were imported, we add a dedicated and extremely cheap check to the top of the rule. We could consider generalizing this to all modules, but I would expect that not to be much faster than `resolve_call_path`, which is just a hash map lookup on `TextSize` anyway. It would also be nice to make this declarative, such that rules could declare the modules they care about, the analyzers could call the rules as appropriate. But, I don't think such a design should block merging this.
This commit is contained in:
parent
c3ca34543f
commit
e50603caf6
55 changed files with 395 additions and 100 deletions
|
@ -13,7 +13,7 @@ use ruff_text_size::Ranged;
|
|||
|
||||
use crate::analyze::type_inference::{PythonType, ResolvedPythonType};
|
||||
use crate::model::SemanticModel;
|
||||
use crate::{Binding, BindingKind};
|
||||
use crate::{Binding, BindingKind, Modules};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Callable {
|
||||
|
@ -101,18 +101,22 @@ impl std::fmt::Display for ModuleMember {
|
|||
/// Returns the PEP 585 standard library generic variant for a `typing` module reference, if such
|
||||
/// a variant exists.
|
||||
pub fn to_pep585_generic(expr: &Expr, semantic: &SemanticModel) -> Option<ModuleMember> {
|
||||
semantic.resolve_call_path(expr).and_then(|call_path| {
|
||||
let [module, member] = call_path.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
as_pep_585_generic(module, member).map(|(module, member)| {
|
||||
if module.is_empty() {
|
||||
ModuleMember::BuiltIn(member)
|
||||
} else {
|
||||
ModuleMember::Member(module, member)
|
||||
}
|
||||
semantic
|
||||
.seen_module(Modules::TYPING | Modules::TYPING_EXTENSIONS)
|
||||
.then(|| semantic.resolve_call_path(expr))
|
||||
.flatten()
|
||||
.and_then(|call_path| {
|
||||
let [module, member] = call_path.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
as_pep_585_generic(module, member).map(|(module, member)| {
|
||||
if module.is_empty() {
|
||||
ModuleMember::BuiltIn(member)
|
||||
} else {
|
||||
ModuleMember::Member(module, member)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Return whether a given expression uses a PEP 585 standard library generic.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue