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:
Charlie Marsh 2024-02-02 11:37:20 -08:00 committed by GitHub
parent c3ca34543f
commit e50603caf6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 395 additions and 100 deletions

View file

@ -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.