Cache module submodule attributes
diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 743934c..bbfb3d9 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs
@@ -1,5 +1,4 @@ use compact_str::ToCompactString; -use itertools::Itertools; use ruff_diagnostics::{Edit, Fix}; use rustc_hash::FxHashMap; @@ -7846,12 +7845,8 @@ /// /// We instead prefer handling most other import effects as definitions in the scope of /// the current file (i.e. `ty_python_core::definition::ImportFromDefinitionNodeRef`). - fn available_submodule_attributes(&self, db: &'db dyn Db) -> impl Iterator<Item = Name> { - self.importing_file(db) - .into_iter() - .flat_map(|file| semantic_index(db, file).imported_modules()) - .filter_map(|submodule_name| submodule_name.relative_to(self.module(db).name(db))) - .filter_map(|relative_submodule| relative_submodule.components().next().map(Name::from)) + fn available_submodule_attributes(self, db: &'db dyn Db) -> &'db [Name] { + module_literal_available_submodule_attributes(db, self) } fn resolve_submodule(self, db: &'db dyn Db, name: &str) -> Option<Type<'db>> { @@ -7907,7 +7902,10 @@ // the parent module's `__init__.py` file being evaluated. That said, we have // chosen to always have the submodule take priority. (This matches pyright's // current behavior, but is the opposite of mypy's current behavior.) - if self.available_submodule_attributes(db).contains(name) + if self + .available_submodule_attributes(db) + .iter() + .any(|submodule_name| submodule_name.as_str() == name) && let Some(submodule) = self.resolve_submodule(db, name) { return Place::bound(submodule).into(); @@ -7944,6 +7942,20 @@ } } +#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)] +fn module_literal_available_submodule_attributes<'db>( + db: &'db dyn Db, + module_literal: ModuleLiteralType<'db>, +) -> Box<[Name]> { + module_literal + .importing_file(db) + .into_iter() + .flat_map(|file| semantic_index(db, file).imported_modules()) + .filter_map(|submodule_name| submodule_name.relative_to(module_literal.module(db).name(db))) + .filter_map(|relative_submodule| relative_submodule.components().next().map(Name::from)) + .collect() +} + /// Either the explicit `metaclass=` keyword of the class, or the inferred metaclass of one of its base classes. #[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)] pub(super) struct MetaclassCandidate<'db> {
diff --git a/crates/ty_python_semantic/src/types/list_members.rs b/crates/ty_python_semantic/src/types/list_members.rs index 1cefe3f..3c1e41c 100644 --- a/crates/ty_python_semantic/src/types/list_members.rs +++ b/crates/ty_python_semantic/src/types/list_members.rs
@@ -432,14 +432,16 @@ }); } - self.members - .extend(literal.available_submodule_attributes(db).filter_map( - |submodule_name| { - let ty = literal.resolve_submodule(db, &submodule_name)?; + self.members.extend( + literal + .available_submodule_attributes(db) + .iter() + .filter_map(|submodule_name| { + let ty = literal.resolve_submodule(db, submodule_name.as_str())?; let name = submodule_name.clone(); Some(Member { name, ty }) - }, - )); + }), + ); } } }