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 })
-                        },
-                    ));
+                        }),
+                );
             }
         }
     }