[ty] Make Module a Salsa ingredient

We want to write queries that depend on `Module` for caching. While it
seems it can be done without making `Module` an ingredient, it seems it
is best practice to do so.

[best practice to do so]: https://github.com/astral-sh/ruff/pull/19408#discussion_r2215867301
This commit is contained in:
Andrew Gallant 2025-07-18 14:15:42 -04:00 committed by Andrew Gallant
parent 905b9d7f51
commit 4573a0f6a0
24 changed files with 258 additions and 253 deletions

View file

@ -20,7 +20,7 @@ impl<'a> Resolver<'a> {
match import { match import {
CollectedImport::Import(import) => { CollectedImport::Import(import) => {
let module = resolve_module(self.db, &import)?; let module = resolve_module(self.db, &import)?;
Some(module.file()?.path(self.db)) Some(module.file(self.db)?.path(self.db))
} }
CollectedImport::ImportFrom(import) => { CollectedImport::ImportFrom(import) => {
// Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`). // Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`).
@ -32,7 +32,7 @@ impl<'a> Resolver<'a> {
resolve_module(self.db, &parent?) resolve_module(self.db, &parent?)
})?; })?;
Some(module.file()?.path(self.db)) Some(module.file(self.db)?.path(self.db))
} }
} }
} }

View file

@ -163,7 +163,7 @@ fn stem(path: &str) -> &str {
} }
/// Infer the [`Visibility`] of a module from its path. /// Infer the [`Visibility`] of a module from its path.
pub(crate) fn module_visibility(module: &Module) -> Visibility { pub(crate) fn module_visibility(module: Module) -> Visibility {
match &module.source { match &module.source {
ModuleSource::Path(path) => { ModuleSource::Path(path) => {
if path.iter().any(|m| is_private_module(m)) { if path.iter().any(|m| is_private_module(m)) {

View file

@ -223,7 +223,7 @@ impl<'a> Definitions<'a> {
// visibility. // visibility.
let visibility = { let visibility = {
match &definition { match &definition {
Definition::Module(module) => module_visibility(module), Definition::Module(module) => module_visibility(*module),
Definition::Member(member) => match member.kind { Definition::Member(member) => match member.kind {
MemberKind::Class(class) => { MemberKind::Class(class) => {
let parent = &definitions[member.parent]; let parent = &definitions[member.parent];

View file

@ -230,6 +230,21 @@ impl TestCase {
fn system_file(&self, path: impl AsRef<SystemPath>) -> Result<File, FileError> { fn system_file(&self, path: impl AsRef<SystemPath>) -> Result<File, FileError> {
system_path_to_file(self.db(), path.as_ref()) system_path_to_file(self.db(), path.as_ref())
} }
fn module<'c>(&'c self, name: &str) -> Module<'c> {
resolve_module(self.db(), &ModuleName::new(name).unwrap()).expect("module to be present")
}
fn sorted_submodule_names(&self, parent_module_name: &str) -> Vec<String> {
let mut names = self
.module(parent_module_name)
.all_submodules(self.db())
.iter()
.map(|name| name.as_str().to_string())
.collect::<Vec<String>>();
names.sort();
names
}
} }
trait MatchEvent { trait MatchEvent {
@ -1398,7 +1413,7 @@ mod unix {
let baz = resolve_module(case.db(), &ModuleName::new_static("bar.baz").unwrap()) let baz = resolve_module(case.db(), &ModuleName::new_static("bar.baz").unwrap())
.expect("Expected bar.baz to exist in site-packages."); .expect("Expected bar.baz to exist in site-packages.");
let baz_project = case.project_path("bar/baz.py"); let baz_project = case.project_path("bar/baz.py");
let baz_file = baz.file().unwrap(); let baz_file = baz.file(case.db()).unwrap();
assert_eq!(source_text(case.db(), baz_file).as_str(), "def baz(): ..."); assert_eq!(source_text(case.db(), baz_file).as_str(), "def baz(): ...");
assert_eq!( assert_eq!(
@ -1473,7 +1488,7 @@ mod unix {
let baz = resolve_module(case.db(), &ModuleName::new_static("bar.baz").unwrap()) let baz = resolve_module(case.db(), &ModuleName::new_static("bar.baz").unwrap())
.expect("Expected bar.baz to exist in site-packages."); .expect("Expected bar.baz to exist in site-packages.");
let baz_file = baz.file().unwrap(); let baz_file = baz.file(case.db()).unwrap();
let bar_baz = case.project_path("bar/baz.py"); let bar_baz = case.project_path("bar/baz.py");
let patched_bar_baz = case.project_path("patched/bar/baz.py"); let patched_bar_baz = case.project_path("patched/bar/baz.py");
@ -1594,7 +1609,10 @@ mod unix {
"def baz(): ..." "def baz(): ..."
); );
assert_eq!( assert_eq!(
baz.file().unwrap().path(case.db()).as_system_path(), baz.file(case.db())
.unwrap()
.path(case.db())
.as_system_path(),
Some(&*baz_original) Some(&*baz_original)
); );
@ -1891,19 +1909,9 @@ fn rename_files_casing_only() -> anyhow::Result<()> {
#[test] #[test]
fn submodule_cache_invalidation_created() -> anyhow::Result<()> { fn submodule_cache_invalidation_created() -> anyhow::Result<()> {
let mut case = setup([("lib.py", ""), ("bar/__init__.py", ""), ("bar/foo.py", "")])?; let mut case = setup([("lib.py", ""), ("bar/__init__.py", ""), ("bar/foo.py", "")])?;
let module = resolve_module(case.db(), &ModuleName::new("bar").unwrap()).expect("`bar` module");
let get_submodules = |db: &dyn Db, module: &Module| {
let mut names = module
.all_submodules(db)
.iter()
.map(|name| name.as_str().to_string())
.collect::<Vec<String>>();
names.sort();
names.join("\n")
};
insta::assert_snapshot!( insta::assert_snapshot!(
get_submodules(case.db(), &module), case.sorted_submodule_names("bar").join("\n"),
@"foo", @"foo",
); );
@ -1912,7 +1920,7 @@ fn submodule_cache_invalidation_created() -> anyhow::Result<()> {
case.apply_changes(changes, None); case.apply_changes(changes, None);
insta::assert_snapshot!( insta::assert_snapshot!(
get_submodules(case.db(), &module), case.sorted_submodule_names("bar").join("\n"),
@r" @r"
foo foo
wazoo wazoo
@ -1932,19 +1940,9 @@ fn submodule_cache_invalidation_deleted() -> anyhow::Result<()> {
("bar/foo.py", ""), ("bar/foo.py", ""),
("bar/wazoo.py", ""), ("bar/wazoo.py", ""),
])?; ])?;
let module = resolve_module(case.db(), &ModuleName::new("bar").unwrap()).expect("`bar` module");
let get_submodules = |db: &dyn Db, module: &Module| {
let mut names = module
.all_submodules(db)
.iter()
.map(|name| name.as_str().to_string())
.collect::<Vec<String>>();
names.sort();
names.join("\n")
};
insta::assert_snapshot!( insta::assert_snapshot!(
get_submodules(case.db(), &module), case.sorted_submodule_names("bar").join("\n"),
@r" @r"
foo foo
wazoo wazoo
@ -1956,7 +1954,7 @@ fn submodule_cache_invalidation_deleted() -> anyhow::Result<()> {
case.apply_changes(changes, None); case.apply_changes(changes, None);
insta::assert_snapshot!( insta::assert_snapshot!(
get_submodules(case.db(), &module), case.sorted_submodule_names("bar").join("\n"),
@"foo", @"foo",
); );
@ -1968,19 +1966,9 @@ fn submodule_cache_invalidation_deleted() -> anyhow::Result<()> {
#[test] #[test]
fn submodule_cache_invalidation_created_then_deleted() -> anyhow::Result<()> { fn submodule_cache_invalidation_created_then_deleted() -> anyhow::Result<()> {
let mut case = setup([("lib.py", ""), ("bar/__init__.py", ""), ("bar/foo.py", "")])?; let mut case = setup([("lib.py", ""), ("bar/__init__.py", ""), ("bar/foo.py", "")])?;
let module = resolve_module(case.db(), &ModuleName::new("bar").unwrap()).expect("`bar` module");
let get_submodules = |db: &dyn Db, module: &Module| {
let mut names = module
.all_submodules(db)
.iter()
.map(|name| name.as_str().to_string())
.collect::<Vec<String>>();
names.sort();
names.join("\n")
};
insta::assert_snapshot!( insta::assert_snapshot!(
get_submodules(case.db(), &module), case.sorted_submodule_names("bar").join("\n"),
@"foo", @"foo",
); );
@ -1993,7 +1981,7 @@ fn submodule_cache_invalidation_created_then_deleted() -> anyhow::Result<()> {
case.apply_changes(changes, None); case.apply_changes(changes, None);
insta::assert_snapshot!( insta::assert_snapshot!(
get_submodules(case.db(), &module), case.sorted_submodule_names("bar").join("\n"),
@"foo", @"foo",
); );
@ -2006,19 +1994,9 @@ fn submodule_cache_invalidation_created_then_deleted() -> anyhow::Result<()> {
#[test] #[test]
fn submodule_cache_invalidation_after_pyproject_created() -> anyhow::Result<()> { fn submodule_cache_invalidation_after_pyproject_created() -> anyhow::Result<()> {
let mut case = setup([("lib.py", ""), ("bar/__init__.py", ""), ("bar/foo.py", "")])?; let mut case = setup([("lib.py", ""), ("bar/__init__.py", ""), ("bar/foo.py", "")])?;
let module = resolve_module(case.db(), &ModuleName::new("bar").unwrap()).expect("`bar` module");
let get_submodules = |db: &dyn Db, module: &Module| {
let mut names = module
.all_submodules(db)
.iter()
.map(|name| name.as_str().to_string())
.collect::<Vec<String>>();
names.sort();
names.join("\n")
};
insta::assert_snapshot!( insta::assert_snapshot!(
get_submodules(case.db(), &module), case.sorted_submodule_names("bar").join("\n"),
@"foo", @"foo",
); );
@ -2029,7 +2007,7 @@ fn submodule_cache_invalidation_after_pyproject_created() -> anyhow::Result<()>
case.apply_changes(changes, None); case.apply_changes(changes, None);
insta::assert_snapshot!( insta::assert_snapshot!(
get_submodules(case.db(), &module), case.sorted_submodule_names("bar").join("\n"),
@r" @r"
foo foo
wazoo wazoo

View file

@ -525,7 +525,7 @@ fn resolve_module_to_navigation_target(
if let Some(module_name) = ModuleName::new(module_name_str) { if let Some(module_name) = ModuleName::new(module_name_str) {
if let Some(resolved_module) = resolve_module(db, &module_name) { if let Some(resolved_module) = resolve_module(db, &module_name) {
if let Some(module_file) = resolved_module.file() { if let Some(module_file) = resolved_module.file(db) {
return Some(crate::NavigationTargets::single(crate::NavigationTarget { return Some(crate::NavigationTargets::single(crate::NavigationTarget {
file: module_file, file: module_file,
focus_range: TextRange::default(), focus_range: TextRange::default(),

View file

@ -103,7 +103,7 @@ impl<'db> DunderAllNamesCollector<'db> {
}; };
let Some(module_dunder_all_names) = module_literal let Some(module_dunder_all_names) = module_literal
.module(self.db) .module(self.db)
.file() .file(self.db)
.and_then(|file| dunder_all_names(self.db, file)) .and_then(|file| dunder_all_names(self.db, file))
else { else {
// The module either does not have a `__all__` variable or it is invalid. // The module either does not have a `__all__` variable or it is invalid.
@ -173,7 +173,7 @@ impl<'db> DunderAllNamesCollector<'db> {
let module_name = let module_name =
ModuleName::from_import_statement(self.db, self.file, import_from).ok()?; ModuleName::from_import_statement(self.db, self.file, import_from).ok()?;
let module = resolve_module(self.db, &module_name)?; let module = resolve_module(self.db, &module_name)?;
dunder_all_names(self.db, module.file()?) dunder_all_names(self.db, module.file(self.db)?)
} }
/// Infer the type of a standalone expression. /// Infer the type of a standalone expression.

View file

@ -345,12 +345,12 @@ fn relative_module_name(
.ok_or(ModuleNameResolutionError::UnknownCurrentModule)?; .ok_or(ModuleNameResolutionError::UnknownCurrentModule)?;
let mut level = level.get(); let mut level = level.get();
if module.kind().is_package() { if module.kind(db).is_package() {
level = level.saturating_sub(1); level = level.saturating_sub(1);
} }
let mut module_name = module let mut module_name = module
.name() .name(db)
.ancestors() .ancestors()
.nth(level as usize) .nth(level as usize)
.ok_or(ModuleNameResolutionError::TooManyDots)?; .ok_or(ModuleNameResolutionError::TooManyDots)?;

View file

@ -1,10 +1,11 @@
use std::fmt::Formatter; use std::fmt::Formatter;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use ruff_db::files::File; use ruff_db::files::File;
use ruff_python_ast::name::Name; use ruff_python_ast::name::Name;
use ruff_python_stdlib::identifiers::is_identifier; use ruff_python_stdlib::identifiers::is_identifier;
use salsa::Database;
use salsa::plumbing::AsId;
use super::path::SearchPath; use super::path::SearchPath;
use crate::Db; use crate::Db;
@ -12,14 +13,19 @@ use crate::module_name::ModuleName;
use crate::module_resolver::path::SystemOrVendoredPathRef; use crate::module_resolver::path::SystemOrVendoredPathRef;
/// Representation of a Python module. /// Representation of a Python module.
#[derive(Clone, PartialEq, Eq, Hash, get_size2::GetSize)] #[derive(Clone, Copy, Eq, Hash, PartialEq, salsa::Supertype, salsa::Update)]
pub struct Module { pub enum Module<'db> {
inner: Arc<ModuleInner>, File(FileModule<'db>),
Namespace(NamespacePackage<'db>),
} }
// The Salsa heap is tracked separately.
impl get_size2::GetSize for Module<'_> {}
#[salsa::tracked] #[salsa::tracked]
impl Module { impl<'db> Module<'db> {
pub(crate) fn file_module( pub(crate) fn file_module(
db: &'db dyn Db,
name: ModuleName, name: ModuleName,
kind: ModuleKind, kind: ModuleKind,
search_path: SearchPath, search_path: SearchPath,
@ -27,67 +33,57 @@ impl Module {
) -> Self { ) -> Self {
let known = KnownModule::try_from_search_path_and_name(&search_path, &name); let known = KnownModule::try_from_search_path_and_name(&search_path, &name);
Self { Self::File(FileModule::new(db, name, kind, search_path, file, known))
inner: Arc::new(ModuleInner::FileModule {
name,
kind,
search_path,
file,
known,
}),
}
} }
pub(crate) fn namespace_package(name: ModuleName) -> Self { pub(crate) fn namespace_package(db: &'db dyn Db, name: ModuleName) -> Self {
Self { Self::Namespace(NamespacePackage::new(db, name))
inner: Arc::new(ModuleInner::NamespacePackage { name }),
}
} }
/// The absolute name of the module (e.g. `foo.bar`) /// The absolute name of the module (e.g. `foo.bar`)
pub fn name(&self) -> &ModuleName { pub fn name(self, db: &'db dyn Database) -> &'db ModuleName {
match &*self.inner { match self {
ModuleInner::FileModule { name, .. } => name, Module::File(module) => module.name(db),
ModuleInner::NamespacePackage { name, .. } => name, Module::Namespace(ref package) => package.name(db),
} }
} }
/// The file to the source code that defines this module /// The file to the source code that defines this module
/// ///
/// This is `None` for namespace packages. /// This is `None` for namespace packages.
pub fn file(&self) -> Option<File> { pub fn file(self, db: &'db dyn Database) -> Option<File> {
match &*self.inner { match self {
ModuleInner::FileModule { file, .. } => Some(*file), Module::File(module) => Some(module.file(db)),
ModuleInner::NamespacePackage { .. } => None, Module::Namespace(_) => None,
} }
} }
/// Is this a module that we special-case somehow? If so, which one? /// Is this a module that we special-case somehow? If so, which one?
pub fn known(&self) -> Option<KnownModule> { pub fn known(self, db: &'db dyn Database) -> Option<KnownModule> {
match &*self.inner { match self {
ModuleInner::FileModule { known, .. } => *known, Module::File(module) => module.known(db),
ModuleInner::NamespacePackage { .. } => None, Module::Namespace(_) => None,
} }
} }
/// Does this module represent the given known module? /// Does this module represent the given known module?
pub fn is_known(&self, known_module: KnownModule) -> bool { pub fn is_known(self, db: &'db dyn Database, known_module: KnownModule) -> bool {
self.known() == Some(known_module) self.known(db) == Some(known_module)
} }
/// The search path from which the module was resolved. /// The search path from which the module was resolved.
pub(crate) fn search_path(&self) -> Option<&SearchPath> { pub(crate) fn search_path(self, db: &'db dyn Database) -> Option<&'db SearchPath> {
match &*self.inner { match self {
ModuleInner::FileModule { search_path, .. } => Some(search_path), Module::File(module) => Some(module.search_path(db)),
ModuleInner::NamespacePackage { .. } => None, Module::Namespace(_) => None,
} }
} }
/// Determine whether this module is a single-file module or a package /// Determine whether this module is a single-file module or a package
pub fn kind(&self) -> ModuleKind { pub fn kind(self, db: &'db dyn Database) -> ModuleKind {
match &*self.inner { match self {
ModuleInner::FileModule { kind, .. } => *kind, Module::File(module) => module.kind(db),
ModuleInner::NamespacePackage { .. } => ModuleKind::Package, Module::Namespace(_) => ModuleKind::Package,
} }
} }
@ -98,16 +94,13 @@ impl Module {
/// ///
/// The names returned correspond to the "base" name of the module. /// The names returned correspond to the "base" name of the module.
/// That is, `{self.name}.{basename}` should give the full module name. /// That is, `{self.name}.{basename}` should give the full module name.
pub fn all_submodules<'db>(&self, db: &'db dyn Db) -> &'db [Name] { pub fn all_submodules(self, db: &'db dyn Db) -> &'db [Name] {
self.clone() self.all_submodules_inner(db).as_deref().unwrap_or_default()
.all_submodules_inner(db, ())
.as_deref()
.unwrap_or_default()
} }
#[allow(clippy::ref_option, clippy::used_underscore_binding)] #[allow(clippy::ref_option)]
#[salsa::tracked(returns(ref))] #[salsa::tracked(returns(ref))]
fn all_submodules_inner(self, db: &dyn Db, _dummy: ()) -> Option<Vec<Name>> { fn all_submodules_inner(self, db: &'db dyn Db) -> Option<Vec<Name>> {
fn is_submodule( fn is_submodule(
is_dir: bool, is_dir: bool,
is_file: bool, is_file: bool,
@ -125,16 +118,14 @@ impl Module {
// to a single file; it can span multiple directories across multiple // to a single file; it can span multiple directories across multiple
// search paths. For now, we only compute submodules for traditional // search paths. For now, we only compute submodules for traditional
// packages that exist in a single directory on a single search path. // packages that exist in a single directory on a single search path.
let ModuleInner::FileModule { let Module::File(module) = self else {
kind: ModuleKind::Package,
file,
..
} = &*self.inner
else {
return None; return None;
}; };
if !matches!(module.kind(db), ModuleKind::Package) {
return None;
}
let path = SystemOrVendoredPathRef::try_from_file(db, *file)?; let path = SystemOrVendoredPathRef::try_from_file(db, module.file(db))?;
debug_assert!( debug_assert!(
matches!(path.file_name(), Some("__init__.py" | "__init__.pyi")), matches!(path.file_name(), Some("__init__.py" | "__init__.pyi")),
"expected package file `{:?}` to be `__init__.py` or `__init__.pyi`", "expected package file `{:?}` to be `__init__.py` or `__init__.pyi`",
@ -201,33 +192,41 @@ impl Module {
} }
} }
impl std::fmt::Debug for Module { impl std::fmt::Debug for Module<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Module") salsa::with_attached_database(|db| {
.field("name", &self.name()) f.debug_struct("Module")
.field("kind", &self.kind()) .field("name", &self.name(db))
.field("file", &self.file()) .field("kind", &self.kind(db))
.field("search_path", &self.search_path()) .field("file", &self.file(db))
.field("known", &self.known()) .field("search_path", &self.search_path(db))
.finish() .field("known", &self.known(db))
.finish()
})
.unwrap_or_else(|| f.debug_tuple("Module").field(&self.as_id()).finish())
} }
} }
#[derive(PartialEq, Eq, Hash, get_size2::GetSize)] /// A module that resolves to a file (`lib.py` or `package/__init__.py`)
enum ModuleInner { #[salsa::tracked(debug)]
/// A module that resolves to a file (`lib.py` or `package/__init__.py`) pub struct FileModule<'db> {
FileModule { #[returns(ref)]
name: ModuleName, name: ModuleName,
kind: ModuleKind, kind: ModuleKind,
search_path: SearchPath, #[returns(ref)]
file: File, search_path: SearchPath,
known: Option<KnownModule>, file: File,
}, known: Option<KnownModule>,
}
/// A namespace package. Namespace packages are special because /// A namespace package.
/// there are multiple possible paths and they have no corresponding ///
/// code file. /// Namespace packages are special because there are
NamespacePackage { name: ModuleName }, /// multiple possible paths and they have no corresponding code file.
#[salsa::tracked(debug)]
pub struct NamespacePackage<'db> {
#[returns(ref)]
name: ModuleName,
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]

View file

@ -411,7 +411,7 @@ enum SearchPathInner {
/// and "Standard-library" categories, however, there will always be exactly /// and "Standard-library" categories, however, there will always be exactly
/// one search path from that category in any given list of search paths. /// one search path from that category in any given list of search paths.
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
pub(crate) struct SearchPath(Arc<SearchPathInner>); pub struct SearchPath(Arc<SearchPathInner>);
impl SearchPath { impl SearchPath {
fn directory_path(system: &dyn System, root: SystemPathBuf) -> SearchPathResult<SystemPathBuf> { fn directory_path(system: &dyn System, root: SystemPathBuf) -> SearchPathResult<SystemPathBuf> {

View file

@ -20,14 +20,14 @@ use super::module::{Module, ModuleKind};
use super::path::{ModulePath, SearchPath, SearchPathValidationError, SystemOrVendoredPathRef}; use super::path::{ModulePath, SearchPath, SearchPathValidationError, SystemOrVendoredPathRef};
/// Resolves a module name to a module. /// Resolves a module name to a module.
pub fn resolve_module(db: &dyn Db, module_name: &ModuleName) -> Option<Module> { pub fn resolve_module<'db>(db: &'db dyn Db, module_name: &ModuleName) -> Option<Module<'db>> {
let interned_name = ModuleNameIngredient::new(db, module_name, ModuleResolveMode::StubsAllowed); let interned_name = ModuleNameIngredient::new(db, module_name, ModuleResolveMode::StubsAllowed);
resolve_module_query(db, interned_name) resolve_module_query(db, interned_name)
} }
/// Resolves a module name to a module (stubs not allowed). /// Resolves a module name to a module (stubs not allowed).
pub fn resolve_real_module(db: &dyn Db, module_name: &ModuleName) -> Option<Module> { pub fn resolve_real_module<'db>(db: &'db dyn Db, module_name: &ModuleName) -> Option<Module<'db>> {
let interned_name = let interned_name =
ModuleNameIngredient::new(db, module_name, ModuleResolveMode::StubsNotAllowed); ModuleNameIngredient::new(db, module_name, ModuleResolveMode::StubsNotAllowed);
@ -55,7 +55,7 @@ impl ModuleResolveMode {
pub(crate) fn resolve_module_query<'db>( pub(crate) fn resolve_module_query<'db>(
db: &'db dyn Db, db: &'db dyn Db,
module_name: ModuleNameIngredient<'db>, module_name: ModuleNameIngredient<'db>,
) -> Option<Module> { ) -> Option<Module<'db>> {
let name = module_name.name(db); let name = module_name.name(db);
let mode = module_name.mode(db); let mode = module_name.mode(db);
let _span = tracing::trace_span!("resolve_module", %name).entered(); let _span = tracing::trace_span!("resolve_module", %name).entered();
@ -71,11 +71,17 @@ pub(crate) fn resolve_module_query<'db>(
"Resolved module `{name}` to `{path}`", "Resolved module `{name}` to `{path}`",
path = module.file.path(db) path = module.file.path(db)
); );
Module::file_module(name.clone(), module.kind, module.search_path, module.file) Module::file_module(
db,
name.clone(),
module.kind,
module.search_path,
module.file,
)
} }
ResolvedName::NamespacePackage => { ResolvedName::NamespacePackage => {
tracing::trace!("Module `{name}` is a namespace package"); tracing::trace!("Module `{name}` is a namespace package");
Module::namespace_package(name.clone()) Module::namespace_package(db, name.clone())
} }
}; };
@ -86,7 +92,7 @@ pub(crate) fn resolve_module_query<'db>(
/// ///
/// Returns `None` if the path is not a module locatable via any of the known search paths. /// Returns `None` if the path is not a module locatable via any of the known search paths.
#[allow(unused)] #[allow(unused)]
pub(crate) fn path_to_module(db: &dyn Db, path: &FilePath) -> Option<Module> { pub(crate) fn path_to_module<'db>(db: &'db dyn Db, path: &FilePath) -> Option<Module<'db>> {
// It's not entirely clear on first sight why this method calls `file_to_module` instead of // It's not entirely clear on first sight why this method calls `file_to_module` instead of
// it being the other way round, considering that the first thing that `file_to_module` does // it being the other way round, considering that the first thing that `file_to_module` does
// is to retrieve the file's path. // is to retrieve the file's path.
@ -103,7 +109,7 @@ pub(crate) fn path_to_module(db: &dyn Db, path: &FilePath) -> Option<Module> {
/// ///
/// Returns `None` if the file is not a module locatable via any of the known search paths. /// Returns `None` if the file is not a module locatable via any of the known search paths.
#[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)] #[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)]
pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> { pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module<'_>> {
let _span = tracing::trace_span!("file_to_module", ?file).entered(); let _span = tracing::trace_span!("file_to_module", ?file).entered();
let path = SystemOrVendoredPathRef::try_from_file(db, file)?; let path = SystemOrVendoredPathRef::try_from_file(db, file)?;
@ -121,7 +127,7 @@ pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
// root paths, but that the module corresponding to `path` is in a lower priority search path, // root paths, but that the module corresponding to `path` is in a lower priority search path,
// in which case we ignore it. // in which case we ignore it.
let module = resolve_module(db, &module_name)?; let module = resolve_module(db, &module_name)?;
let module_file = module.file()?; let module_file = module.file(db)?;
if file.path(db) == module_file.path(db) { if file.path(db) == module_file.path(db) {
Some(module) Some(module)
@ -939,12 +945,12 @@ mod tests {
resolve_module(&db, &foo_module_name).as_ref() resolve_module(&db, &foo_module_name).as_ref()
); );
assert_eq!("foo", foo_module.name()); assert_eq!("foo", foo_module.name(&db));
assert_eq!(&src, foo_module.search_path().unwrap()); assert_eq!(&src, foo_module.search_path(&db).unwrap());
assert_eq!(ModuleKind::Module, foo_module.kind()); assert_eq!(ModuleKind::Module, foo_module.kind(&db));
let expected_foo_path = src.join("foo.py"); let expected_foo_path = src.join("foo.py");
assert_eq!(&expected_foo_path, foo_module.file().unwrap().path(&db)); assert_eq!(&expected_foo_path, foo_module.file(&db).unwrap().path(&db));
assert_eq!( assert_eq!(
Some(foo_module), Some(foo_module),
path_to_module(&db, &FilePath::System(expected_foo_path)) path_to_module(&db, &FilePath::System(expected_foo_path))
@ -962,7 +968,7 @@ mod tests {
let builtins = resolve_module(&db, &builtins_module_name).expect("builtins to resolve"); let builtins = resolve_module(&db, &builtins_module_name).expect("builtins to resolve");
assert_eq!( assert_eq!(
builtins.file().unwrap().path(&db), builtins.file(&db).unwrap().path(&db),
&stdlib.join("builtins.pyi") &stdlib.join("builtins.pyi")
); );
} }
@ -986,7 +992,7 @@ mod tests {
let builtins = resolve_module(&db, &builtins_module_name).expect("builtins to resolve"); let builtins = resolve_module(&db, &builtins_module_name).expect("builtins to resolve");
assert_eq!( assert_eq!(
builtins.file().unwrap().path(&db), builtins.file(&db).unwrap().path(&db),
&stdlib.join("builtins.pyi") &stdlib.join("builtins.pyi")
); );
} }
@ -1011,13 +1017,13 @@ mod tests {
resolve_module(&db, &functools_module_name).as_ref() resolve_module(&db, &functools_module_name).as_ref()
); );
assert_eq!(&stdlib, functools_module.search_path().unwrap()); assert_eq!(&stdlib, functools_module.search_path(&db).unwrap());
assert_eq!(ModuleKind::Module, functools_module.kind()); assert_eq!(ModuleKind::Module, functools_module.kind(&db));
let expected_functools_path = stdlib.join("functools.pyi"); let expected_functools_path = stdlib.join("functools.pyi");
assert_eq!( assert_eq!(
&expected_functools_path, &expected_functools_path,
functools_module.file().unwrap().path(&db) functools_module.file(&db).unwrap().path(&db)
); );
assert_eq!( assert_eq!(
@ -1064,7 +1070,7 @@ mod tests {
let resolved_module = resolve_module(&db, &module_name).unwrap_or_else(|| { let resolved_module = resolve_module(&db, &module_name).unwrap_or_else(|| {
panic!("Expected module {module_name} to exist in the mock stdlib") panic!("Expected module {module_name} to exist in the mock stdlib")
}); });
let search_path = resolved_module.search_path().unwrap(); let search_path = resolved_module.search_path(&db).unwrap();
assert_eq!( assert_eq!(
&stdlib, search_path, &stdlib, search_path,
"Search path for {module_name} was unexpectedly {search_path:?}" "Search path for {module_name} was unexpectedly {search_path:?}"
@ -1160,7 +1166,7 @@ mod tests {
let resolved_module = resolve_module(&db, &module_name).unwrap_or_else(|| { let resolved_module = resolve_module(&db, &module_name).unwrap_or_else(|| {
panic!("Expected module {module_name} to exist in the mock stdlib") panic!("Expected module {module_name} to exist in the mock stdlib")
}); });
let search_path = resolved_module.search_path().unwrap(); let search_path = resolved_module.search_path(&db).unwrap();
assert_eq!( assert_eq!(
&stdlib, search_path, &stdlib, search_path,
"Search path for {module_name} was unexpectedly {search_path:?}" "Search path for {module_name} was unexpectedly {search_path:?}"
@ -1221,11 +1227,11 @@ mod tests {
Some(&functools_module), Some(&functools_module),
resolve_module(&db, &functools_module_name).as_ref() resolve_module(&db, &functools_module_name).as_ref()
); );
assert_eq!(&src, functools_module.search_path().unwrap()); assert_eq!(&src, functools_module.search_path(&db).unwrap());
assert_eq!(ModuleKind::Module, functools_module.kind()); assert_eq!(ModuleKind::Module, functools_module.kind(&db));
assert_eq!( assert_eq!(
&src.join("functools.py"), &src.join("functools.py"),
functools_module.file().unwrap().path(&db) functools_module.file(&db).unwrap().path(&db)
); );
assert_eq!( assert_eq!(
@ -1244,10 +1250,10 @@ mod tests {
let pydoc_data_topics_name = ModuleName::new_static("pydoc_data.topics").unwrap(); let pydoc_data_topics_name = ModuleName::new_static("pydoc_data.topics").unwrap();
let pydoc_data_topics = resolve_module(&db, &pydoc_data_topics_name).unwrap(); let pydoc_data_topics = resolve_module(&db, &pydoc_data_topics_name).unwrap();
assert_eq!("pydoc_data.topics", pydoc_data_topics.name()); assert_eq!("pydoc_data.topics", pydoc_data_topics.name(&db));
assert_eq!(pydoc_data_topics.search_path().unwrap(), &stdlib); assert_eq!(pydoc_data_topics.search_path(&db).unwrap(), &stdlib);
assert_eq!( assert_eq!(
pydoc_data_topics.file().unwrap().path(&db), pydoc_data_topics.file(&db).unwrap().path(&db),
&stdlib.join("pydoc_data/topics.pyi") &stdlib.join("pydoc_data/topics.pyi")
); );
} }
@ -1261,9 +1267,9 @@ mod tests {
let foo_path = src.join("foo/__init__.py"); let foo_path = src.join("foo/__init__.py");
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap(); let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
assert_eq!("foo", foo_module.name()); assert_eq!("foo", foo_module.name(&db));
assert_eq!(&src, foo_module.search_path().unwrap()); assert_eq!(&src, foo_module.search_path(&db).unwrap());
assert_eq!(&foo_path, foo_module.file().unwrap().path(&db)); assert_eq!(&foo_path, foo_module.file(&db).unwrap().path(&db));
assert_eq!( assert_eq!(
Some(&foo_module), Some(&foo_module),
@ -1289,9 +1295,9 @@ mod tests {
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap(); let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
let foo_init_path = src.join("foo/__init__.py"); let foo_init_path = src.join("foo/__init__.py");
assert_eq!(&src, foo_module.search_path().unwrap()); assert_eq!(&src, foo_module.search_path(&db).unwrap());
assert_eq!(&foo_init_path, foo_module.file().unwrap().path(&db)); assert_eq!(&foo_init_path, foo_module.file(&db).unwrap().path(&db));
assert_eq!(ModuleKind::Package, foo_module.kind()); assert_eq!(ModuleKind::Package, foo_module.kind(&db));
assert_eq!( assert_eq!(
Some(foo_module), Some(foo_module),
@ -1312,8 +1318,8 @@ mod tests {
let foo = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap(); let foo = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
let foo_stub = src.join("foo.pyi"); let foo_stub = src.join("foo.pyi");
assert_eq!(&src, foo.search_path().unwrap()); assert_eq!(&src, foo.search_path(&db).unwrap());
assert_eq!(&foo_stub, foo.file().unwrap().path(&db)); assert_eq!(&foo_stub, foo.file(&db).unwrap().path(&db));
assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub))); assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub)));
assert_eq!( assert_eq!(
@ -1336,8 +1342,8 @@ mod tests {
resolve_module(&db, &ModuleName::new_static("foo.bar.baz").unwrap()).unwrap(); resolve_module(&db, &ModuleName::new_static("foo.bar.baz").unwrap()).unwrap();
let baz_path = src.join("foo/bar/baz.py"); let baz_path = src.join("foo/bar/baz.py");
assert_eq!(&src, baz_module.search_path().unwrap()); assert_eq!(&src, baz_module.search_path(&db).unwrap());
assert_eq!(&baz_path, baz_module.file().unwrap().path(&db)); assert_eq!(&baz_path, baz_module.file(&db).unwrap().path(&db));
assert_eq!( assert_eq!(
Some(baz_module), Some(baz_module),
@ -1360,8 +1366,8 @@ mod tests {
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap(); let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
let foo_src_path = src.join("foo.py"); let foo_src_path = src.join("foo.py");
assert_eq!(&src, foo_module.search_path().unwrap()); assert_eq!(&src, foo_module.search_path(&db).unwrap());
assert_eq!(&foo_src_path, foo_module.file().unwrap().path(&db)); assert_eq!(&foo_src_path, foo_module.file(&db).unwrap().path(&db));
assert_eq!( assert_eq!(
Some(foo_module), Some(foo_module),
path_to_module(&db, &FilePath::System(foo_src_path)) path_to_module(&db, &FilePath::System(foo_src_path))
@ -1433,14 +1439,14 @@ mod tests {
assert_ne!(foo_module, bar_module); assert_ne!(foo_module, bar_module);
assert_eq!(&src, foo_module.search_path().unwrap()); assert_eq!(&src, foo_module.search_path(&db).unwrap());
assert_eq!(&foo, foo_module.file().unwrap().path(&db)); assert_eq!(&foo, foo_module.file(&db).unwrap().path(&db));
// `foo` and `bar` shouldn't resolve to the same file // `foo` and `bar` shouldn't resolve to the same file
assert_eq!(&src, bar_module.search_path().unwrap()); assert_eq!(&src, bar_module.search_path(&db).unwrap());
assert_eq!(&bar, bar_module.file().unwrap().path(&db)); assert_eq!(&bar, bar_module.file(&db).unwrap().path(&db));
assert_eq!(&foo, foo_module.file().unwrap().path(&db)); assert_eq!(&foo, foo_module.file(&db).unwrap().path(&db));
assert_ne!(&foo_module, &bar_module); assert_ne!(&foo_module, &bar_module);
@ -1465,6 +1471,13 @@ mod tests {
let foo_module_name = ModuleName::new_static("foo").unwrap(); let foo_module_name = ModuleName::new_static("foo").unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap(); let foo_module = resolve_module(&db, &foo_module_name).unwrap();
let foo_pieces = (
foo_module.name(&db).clone(),
foo_module.file(&db),
foo_module.known(&db),
foo_module.search_path(&db).cloned(),
foo_module.kind(&db),
);
let bar_path = src.join("bar.py"); let bar_path = src.join("bar.py");
let bar = system_path_to_file(&db, &bar_path).expect("bar.py to exist"); let bar = system_path_to_file(&db, &bar_path).expect("bar.py to exist");
@ -1479,6 +1492,15 @@ mod tests {
// for resolving `foo`. // for resolving `foo`.
let foo_module2 = resolve_module(&db, &foo_module_name); let foo_module2 = resolve_module(&db, &foo_module_name);
let foo_pieces2 = foo_module2.map(|foo_module2| {
(
foo_module2.name(&db).clone(),
foo_module2.file(&db),
foo_module2.known(&db),
foo_module2.search_path(&db).cloned(),
foo_module2.kind(&db),
)
});
assert!( assert!(
!db.take_salsa_events() !db.take_salsa_events()
@ -1486,7 +1508,7 @@ mod tests {
.any(|event| { matches!(event.kind, salsa::EventKind::WillExecute { .. }) }) .any(|event| { matches!(event.kind, salsa::EventKind::WillExecute { .. }) })
); );
assert_eq!(Some(foo_module), foo_module2); assert_eq!(Some(foo_pieces), foo_pieces2);
} }
#[test] #[test]
@ -1504,7 +1526,7 @@ mod tests {
let foo_file = system_path_to_file(&db, &foo_path).expect("foo.py to exist"); let foo_file = system_path_to_file(&db, &foo_path).expect("foo.py to exist");
let foo_module = resolve_module(&db, &foo_module_name).expect("Foo module to resolve"); let foo_module = resolve_module(&db, &foo_module_name).expect("Foo module to resolve");
assert_eq!(foo_file, foo_module.file().unwrap()); assert_eq!(foo_file, foo_module.file(&db).unwrap());
Ok(()) Ok(())
} }
@ -1520,7 +1542,7 @@ mod tests {
let foo_module = resolve_module(&db, &foo_module_name).expect("foo module to exist"); let foo_module = resolve_module(&db, &foo_module_name).expect("foo module to exist");
let foo_init_path = src.join("foo/__init__.py"); let foo_init_path = src.join("foo/__init__.py");
assert_eq!(&foo_init_path, foo_module.file().unwrap().path(&db)); assert_eq!(&foo_init_path, foo_module.file(&db).unwrap().path(&db));
// Delete `foo/__init__.py` and the `foo` folder. `foo` should now resolve to `foo.py` // Delete `foo/__init__.py` and the `foo` folder. `foo` should now resolve to `foo.py`
db.memory_file_system().remove_file(&foo_init_path)?; db.memory_file_system().remove_file(&foo_init_path)?;
@ -1530,7 +1552,7 @@ mod tests {
File::sync_path(&mut db, foo_init_path.parent().unwrap()); File::sync_path(&mut db, foo_init_path.parent().unwrap());
let foo_module = resolve_module(&db, &foo_module_name).expect("Foo module to resolve"); let foo_module = resolve_module(&db, &foo_module_name).expect("Foo module to resolve");
assert_eq!(&src.join("foo.py"), foo_module.file().unwrap().path(&db)); assert_eq!(&src.join("foo.py"), foo_module.file(&db).unwrap().path(&db));
Ok(()) Ok(())
} }
@ -1556,9 +1578,9 @@ mod tests {
let stdlib_functools_path = stdlib.join("functools.pyi"); let stdlib_functools_path = stdlib.join("functools.pyi");
let functools_module = resolve_module(&db, &functools_module_name).unwrap(); let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(functools_module.search_path().unwrap(), &stdlib); assert_eq!(functools_module.search_path(&db).unwrap(), &stdlib);
assert_eq!( assert_eq!(
Ok(functools_module.file().unwrap()), Ok(functools_module.file(&db).unwrap()),
system_path_to_file(&db, &stdlib_functools_path) system_path_to_file(&db, &stdlib_functools_path)
); );
@ -1569,6 +1591,8 @@ mod tests {
db.write_file(&site_packages_functools_path, "f: int") db.write_file(&site_packages_functools_path, "f: int")
.unwrap(); .unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap(); let functools_module = resolve_module(&db, &functools_module_name).unwrap();
let functools_file = functools_module.file(&db).unwrap();
let functools_search_path = functools_module.search_path(&db).unwrap().clone();
let events = db.take_salsa_events(); let events = db.take_salsa_events();
assert_function_query_was_not_run( assert_function_query_was_not_run(
&db, &db,
@ -1576,9 +1600,9 @@ mod tests {
ModuleNameIngredient::new(&db, functools_module_name, ModuleResolveMode::StubsAllowed), ModuleNameIngredient::new(&db, functools_module_name, ModuleResolveMode::StubsAllowed),
&events, &events,
); );
assert_eq!(functools_module.search_path().unwrap(), &stdlib); assert_eq!(&functools_search_path, &stdlib);
assert_eq!( assert_eq!(
Ok(functools_module.file().unwrap()), Ok(functools_file),
system_path_to_file(&db, &stdlib_functools_path) system_path_to_file(&db, &stdlib_functools_path)
); );
} }
@ -1602,9 +1626,9 @@ mod tests {
let functools_module_name = ModuleName::new_static("functools").unwrap(); let functools_module_name = ModuleName::new_static("functools").unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap(); let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(functools_module.search_path().unwrap(), &stdlib); assert_eq!(functools_module.search_path(&db).unwrap(), &stdlib);
assert_eq!( assert_eq!(
Ok(functools_module.file().unwrap()), Ok(functools_module.file(&db).unwrap()),
system_path_to_file(&db, stdlib.join("functools.pyi")) system_path_to_file(&db, stdlib.join("functools.pyi"))
); );
@ -1613,9 +1637,9 @@ mod tests {
let src_functools_path = src.join("functools.py"); let src_functools_path = src.join("functools.py");
db.write_file(&src_functools_path, "FOO: int").unwrap(); db.write_file(&src_functools_path, "FOO: int").unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap(); let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(functools_module.search_path().unwrap(), &src); assert_eq!(functools_module.search_path(&db).unwrap(), &src);
assert_eq!( assert_eq!(
Ok(functools_module.file().unwrap()), Ok(functools_module.file(&db).unwrap()),
system_path_to_file(&db, &src_functools_path) system_path_to_file(&db, &src_functools_path)
); );
} }
@ -1644,9 +1668,9 @@ mod tests {
let src_functools_path = src.join("functools.py"); let src_functools_path = src.join("functools.py");
let functools_module = resolve_module(&db, &functools_module_name).unwrap(); let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(functools_module.search_path().unwrap(), &src); assert_eq!(functools_module.search_path(&db).unwrap(), &src);
assert_eq!( assert_eq!(
Ok(functools_module.file().unwrap()), Ok(functools_module.file(&db).unwrap()),
system_path_to_file(&db, &src_functools_path) system_path_to_file(&db, &src_functools_path)
); );
@ -1657,9 +1681,9 @@ mod tests {
.unwrap(); .unwrap();
File::sync_path(&mut db, &src_functools_path); File::sync_path(&mut db, &src_functools_path);
let functools_module = resolve_module(&db, &functools_module_name).unwrap(); let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(functools_module.search_path().unwrap(), &stdlib); assert_eq!(functools_module.search_path(&db).unwrap(), &stdlib);
assert_eq!( assert_eq!(
Ok(functools_module.file().unwrap()), Ok(functools_module.file(&db).unwrap()),
system_path_to_file(&db, stdlib.join("functools.pyi")) system_path_to_file(&db, stdlib.join("functools.pyi"))
); );
} }
@ -1682,11 +1706,11 @@ mod tests {
let foo_bar_module = resolve_module(&db, &foo_bar_module_name).unwrap(); let foo_bar_module = resolve_module(&db, &foo_bar_module_name).unwrap();
assert_eq!( assert_eq!(
foo_module.file().unwrap().path(&db), foo_module.file(&db).unwrap().path(&db),
&FilePath::system("/x/src/foo/__init__.py") &FilePath::system("/x/src/foo/__init__.py")
); );
assert_eq!( assert_eq!(
foo_bar_module.file().unwrap().path(&db), foo_bar_module.file(&db).unwrap().path(&db),
&FilePath::system("/x/src/foo/bar.py") &FilePath::system("/x/src/foo/bar.py")
); );
} }
@ -1713,7 +1737,7 @@ mod tests {
let bar_module_name = ModuleName::new_static("bar").unwrap(); let bar_module_name = ModuleName::new_static("bar").unwrap();
let bar_module = resolve_module(&db, &bar_module_name).unwrap(); let bar_module = resolve_module(&db, &bar_module_name).unwrap();
assert_eq!( assert_eq!(
bar_module.file().unwrap().path(&db), bar_module.file(&db).unwrap().path(&db),
&FilePath::system("/y/src/bar.py") &FilePath::system("/y/src/bar.py")
); );
} }
@ -1733,7 +1757,7 @@ mod tests {
let foo_module = resolve_module(&db, &foo_module_name).unwrap(); let foo_module = resolve_module(&db, &foo_module_name).unwrap();
assert_eq!( assert_eq!(
foo_module.file().unwrap().path(&db), foo_module.file(&db).unwrap().path(&db),
&FilePath::system("/x/y/src/foo.pyi") &FilePath::system("/x/y/src/foo.pyi")
); );
} }
@ -1784,19 +1808,19 @@ not_a_directory
let spam_module = resolve_module(&db, &spam_module_name).unwrap(); let spam_module = resolve_module(&db, &spam_module_name).unwrap();
assert_eq!( assert_eq!(
foo_module.file().unwrap().path(&db), foo_module.file(&db).unwrap().path(&db),
&FilePath::system("/x/y/src/foo.pyi") &FilePath::system("/x/y/src/foo.pyi")
); );
assert_eq!( assert_eq!(
a_module.file().unwrap().path(&db), a_module.file(&db).unwrap().path(&db),
&FilePath::system("/a.py") &FilePath::system("/a.py")
); );
assert_eq!( assert_eq!(
b_module.file().unwrap().path(&db), b_module.file(&db).unwrap().path(&db),
&FilePath::system("/baz/b.py") &FilePath::system("/baz/b.py")
); );
assert_eq!( assert_eq!(
spam_module.file().unwrap().path(&db), spam_module.file(&db).unwrap().path(&db),
&FilePath::System(site_packages.join("spam/spam.py")) &FilePath::System(site_packages.join("spam/spam.py"))
); );
} }
@ -1817,14 +1841,14 @@ not_a_directory
let foo_module = resolve_module(&db, &foo_module_name).unwrap(); let foo_module = resolve_module(&db, &foo_module_name).unwrap();
assert_eq!( assert_eq!(
foo_module.file().unwrap().path(&db), foo_module.file(&db).unwrap().path(&db),
&FilePath::system("/x/src/foo.py") &FilePath::system("/x/src/foo.py")
); );
db.clear_salsa_events(); db.clear_salsa_events();
let bar_module = resolve_module(&db, &bar_module_name).unwrap(); let bar_module = resolve_module(&db, &bar_module_name).unwrap();
assert_eq!( assert_eq!(
bar_module.file().unwrap().path(&db), bar_module.file(&db).unwrap().path(&db),
&FilePath::system("/y/src/bar.py") &FilePath::system("/y/src/bar.py")
); );
let events = db.take_salsa_events(); let events = db.take_salsa_events();
@ -1849,7 +1873,7 @@ not_a_directory
let foo_module_name = ModuleName::new_static("foo").unwrap(); let foo_module_name = ModuleName::new_static("foo").unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap(); let foo_module = resolve_module(&db, &foo_module_name).unwrap();
assert_eq!( assert_eq!(
foo_module.file().unwrap().path(&db), foo_module.file(&db).unwrap().path(&db),
&FilePath::system("/x/src/foo.py") &FilePath::system("/x/src/foo.py")
); );
@ -1877,7 +1901,7 @@ not_a_directory
let foo_module = resolve_module(&db, &foo_module_name).unwrap(); let foo_module = resolve_module(&db, &foo_module_name).unwrap();
let src_path = SystemPathBuf::from("/x/src"); let src_path = SystemPathBuf::from("/x/src");
assert_eq!( assert_eq!(
foo_module.file().unwrap().path(&db), foo_module.file(&db).unwrap().path(&db),
&FilePath::System(src_path.join("foo.py")) &FilePath::System(src_path.join("foo.py"))
); );
@ -1948,7 +1972,7 @@ not_a_directory
let a_module_name = ModuleName::new_static("a").unwrap(); let a_module_name = ModuleName::new_static("a").unwrap();
let a_module = resolve_module(&db, &a_module_name).unwrap(); let a_module = resolve_module(&db, &a_module_name).unwrap();
assert_eq!( assert_eq!(
a_module.file().unwrap().path(&db), a_module.file(&db).unwrap().path(&db),
&editable_install_location &editable_install_location
); );
@ -1962,7 +1986,7 @@ not_a_directory
// second `site-packages` directory // second `site-packages` directory
let a_module = resolve_module(&db, &a_module_name).unwrap(); let a_module = resolve_module(&db, &a_module_name).unwrap();
assert_eq!( assert_eq!(
a_module.file().unwrap().path(&db), a_module.file(&db).unwrap().path(&db),
&system_site_packages_location &system_site_packages_location
); );
} }
@ -2024,7 +2048,7 @@ not_a_directory
let a_module = resolve_module(&db, &a_module_name).expect("a.py to resolve"); let a_module = resolve_module(&db, &a_module_name).expect("a.py to resolve");
assert!( assert!(
a_module a_module
.file() .file(&db)
.unwrap() .unwrap()
.path(&db) .path(&db)
.as_str() .as_str()
@ -2059,6 +2083,6 @@ not_a_directory
let foo_module_file = File::new(&db, FilePath::System(installed_foo_module)); let foo_module_file = File::new(&db, FilePath::System(installed_foo_module));
let module = file_to_module(&db, foo_module_file).unwrap(); let module = file_to_module(&db, foo_module_file).unwrap();
assert_eq!(module.search_path().unwrap(), &site_packages); assert_eq!(module.search_path(&db).unwrap(), &site_packages);
} }
} }

View file

@ -374,7 +374,7 @@ pub(crate) fn imported_symbol<'db>(
pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> PlaceAndQualifiers<'db> { pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> PlaceAndQualifiers<'db> {
resolve_module(db, &KnownModule::Builtins.name()) resolve_module(db, &KnownModule::Builtins.name())
.and_then(|module| { .and_then(|module| {
let file = module.file()?; let file = module.file(db)?;
Some( Some(
symbol_impl( symbol_impl(
db, db,
@ -404,7 +404,7 @@ pub(crate) fn known_module_symbol<'db>(
) -> PlaceAndQualifiers<'db> { ) -> PlaceAndQualifiers<'db> {
resolve_module(db, &known_module.name()) resolve_module(db, &known_module.name())
.and_then(|module| { .and_then(|module| {
let file = module.file()?; let file = module.file(db)?;
Some(imported_symbol(db, file, symbol, None)) Some(imported_symbol(db, file, symbol, None))
}) })
.unwrap_or_default() .unwrap_or_default()
@ -442,7 +442,7 @@ pub(crate) fn builtins_module_scope(db: &dyn Db) -> Option<ScopeId<'_>> {
/// Can return `None` if a custom typeshed is used that is missing the core module in question. /// Can return `None` if a custom typeshed is used that is missing the core module in question.
fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option<ScopeId<'_>> { fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option<ScopeId<'_>> {
let module = resolve_module(db, &core_module.name())?; let module = resolve_module(db, &core_module.name())?;
Some(global_scope(db, module.file()?)) Some(global_scope(db, module.file(db)?))
} }
/// Infer the combined type from an iterator of bindings, and return it /// Infer the combined type from an iterator of bindings, and return it
@ -812,7 +812,7 @@ fn symbol_impl<'db>(
if name == "platform" if name == "platform"
&& file_to_module(db, scope.file(db)) && file_to_module(db, scope.file(db))
.is_some_and(|module| module.is_known(KnownModule::Sys)) .is_some_and(|module| module.is_known(db, KnownModule::Sys))
{ {
match Program::get(db).python_platform(db) { match Program::get(db).python_platform(db) {
crate::PythonPlatform::Identifier(platform) => { crate::PythonPlatform::Identifier(platform) => {

View file

@ -1278,7 +1278,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
continue; continue;
}; };
let Some(referenced_module) = module.file() else { let Some(referenced_module) = module.file(self.db) else {
continue; continue;
}; };

View file

@ -257,7 +257,7 @@ impl<'db> Visitor<'db> for ExportFinder<'db> {
.iter() .iter()
.flat_map(|module| { .flat_map(|module| {
module module
.file() .file(self.db)
.map(|file| exported_names(self.db, file)) .map(|file| exported_names(self.db, file))
.unwrap_or_default() .unwrap_or_default()
}) })

View file

@ -67,8 +67,8 @@ impl<'db> SemanticModel<'db> {
tracing::debug!("Could not resolve module from `{module_name:?}`"); tracing::debug!("Could not resolve module from `{module_name:?}`");
return vec![]; return vec![];
}; };
let ty = Type::module_literal(self.db, self.file, &module); let ty = Type::module_literal(self.db, self.file, module);
let builtin = module.is_known(KnownModule::Builtins); let builtin = module.is_known(self.db, KnownModule::Builtins);
let mut completions = vec![]; let mut completions = vec![];
for crate::types::Member { name, ty } in crate::types::all_members(self.db, ty) { for crate::types::Member { name, ty } in crate::types::all_members(self.db, ty) {
@ -84,7 +84,7 @@ impl<'db> SemanticModel<'db> {
let Some(submodule) = resolve_module(self.db, &submodule_name) else { let Some(submodule) = resolve_module(self.db, &submodule_name) else {
continue; continue;
}; };
let ty = Type::module_literal(self.db, self.file, &submodule); let ty = Type::module_literal(self.db, self.file, submodule);
completions.push(Completion { completions.push(Completion {
name: submodule_basename.clone(), name: submodule_basename.clone(),
ty, ty,

View file

@ -841,11 +841,11 @@ impl<'db> Type<'db> {
matches!(self, Type::PropertyInstance(..)) matches!(self, Type::PropertyInstance(..))
} }
pub fn module_literal(db: &'db dyn Db, importing_file: File, submodule: &Module) -> Self { pub fn module_literal(db: &'db dyn Db, importing_file: File, submodule: Module<'db>) -> Self {
Self::ModuleLiteral(ModuleLiteralType::new( Self::ModuleLiteral(ModuleLiteralType::new(
db, db,
submodule, submodule,
submodule.kind().is_package().then_some(importing_file), submodule.kind(db).is_package().then_some(importing_file),
)) ))
} }
@ -6291,7 +6291,7 @@ impl<'db> InvalidTypeExpression<'db> {
return; return;
}; };
let module = module_type.module(db); let module = module_type.module(db);
let Some(module_name_final_part) = module.name().components().next_back() else { let Some(module_name_final_part) = module.name(db).components().next_back() else {
return; return;
}; };
let Some(module_member_with_same_name) = ty let Some(module_member_with_same_name) = ty
@ -7722,7 +7722,7 @@ pub enum WrapperDescriptorKind {
#[derive(PartialOrd, Ord)] #[derive(PartialOrd, Ord)]
pub struct ModuleLiteralType<'db> { pub struct ModuleLiteralType<'db> {
/// The imported module. /// The imported module.
pub module: Module, pub module: Module<'db>,
/// The file in which this module was imported. /// The file in which this module was imported.
/// ///
@ -7744,7 +7744,7 @@ impl<'db> ModuleLiteralType<'db> {
fn importing_file(self, db: &'db dyn Db) -> Option<File> { fn importing_file(self, db: &'db dyn Db) -> Option<File> {
debug_assert_eq!( debug_assert_eq!(
self._importing_file(db).is_some(), self._importing_file(db).is_some(),
self.module(db).kind().is_package() self.module(db).kind(db).is_package()
); );
self._importing_file(db) self._importing_file(db)
} }
@ -7753,17 +7753,17 @@ impl<'db> ModuleLiteralType<'db> {
self.importing_file(db) self.importing_file(db)
.into_iter() .into_iter()
.flat_map(|file| imported_modules(db, file)) .flat_map(|file| imported_modules(db, file))
.filter_map(|submodule_name| submodule_name.relative_to(self.module(db).name())) .filter_map(|submodule_name| submodule_name.relative_to(self.module(db).name(db)))
.filter_map(|relative_submodule| relative_submodule.components().next().map(Name::from)) .filter_map(|relative_submodule| relative_submodule.components().next().map(Name::from))
} }
fn resolve_submodule(self, db: &'db dyn Db, name: &str) -> Option<Type<'db>> { fn resolve_submodule(self, db: &'db dyn Db, name: &str) -> Option<Type<'db>> {
let importing_file = self.importing_file(db)?; let importing_file = self.importing_file(db)?;
let relative_submodule_name = ModuleName::new(name)?; let relative_submodule_name = ModuleName::new(name)?;
let mut absolute_submodule_name = self.module(db).name().clone(); let mut absolute_submodule_name = self.module(db).name(db).clone();
absolute_submodule_name.extend(&relative_submodule_name); absolute_submodule_name.extend(&relative_submodule_name);
let submodule = resolve_module(db, &absolute_submodule_name)?; let submodule = resolve_module(db, &absolute_submodule_name)?;
Some(Type::module_literal(db, importing_file, &submodule)) Some(Type::module_literal(db, importing_file, submodule))
} }
fn static_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { fn static_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
@ -7792,7 +7792,7 @@ impl<'db> ModuleLiteralType<'db> {
} }
self.module(db) self.module(db)
.file() .file(db)
.map(|file| imported_symbol(db, file, name, None)) .map(|file| imported_symbol(db, file, name, None))
.unwrap_or_default() .unwrap_or_default()
} }

View file

@ -648,7 +648,7 @@ impl<'db> Bindings<'db> {
Type::ModuleLiteral(module_literal) => { Type::ModuleLiteral(module_literal) => {
let all_names = module_literal let all_names = module_literal
.module(db) .module(db)
.file() .file(db)
.map(|file| dunder_all_names(db, file)) .map(|file| dunder_all_names(db, file))
.unwrap_or_default(); .unwrap_or_default();
match all_names { match all_names {

View file

@ -3498,7 +3498,7 @@ impl KnownClass {
}; };
candidate candidate
.check_module(db, file_to_module(db, file)?.known()?) .check_module(db, file_to_module(db, file)?.known(db)?)
.then_some(candidate) .then_some(candidate)
} }
@ -4067,7 +4067,11 @@ mod tests {
let class_module = resolve_module(&db, &class.canonical_module(&db).name()).unwrap(); let class_module = resolve_module(&db, &class.canonical_module(&db).name()).unwrap();
assert_eq!( assert_eq!(
KnownClass::try_from_file_and_name(&db, class_module.file().unwrap(), class_name), KnownClass::try_from_file_and_name(
&db,
class_module.file(&db).unwrap(),
class_name
),
Some(class), Some(class),
"`KnownClass::candidate_from_str` appears to be missing a case for `{class_name}`" "`KnownClass::candidate_from_str` appears to be missing a case for `{class_name}`"
); );

View file

@ -7,7 +7,7 @@ use ruff_text_size::{TextLen, TextRange};
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub enum TypeDefinition<'db> { pub enum TypeDefinition<'db> {
Module(Module), Module(Module<'db>),
Class(Definition<'db>), Class(Definition<'db>),
Function(Definition<'db>), Function(Definition<'db>),
TypeVar(Definition<'db>), TypeVar(Definition<'db>),
@ -31,7 +31,7 @@ impl TypeDefinition<'_> {
pub fn full_range(&self, db: &dyn Db) -> Option<FileRange> { pub fn full_range(&self, db: &dyn Db) -> Option<FileRange> {
match self { match self {
Self::Module(module) => { Self::Module(module) => {
let file = module.file()?; let file = module.file(db)?;
let source = source_text(db, file); let source = source_text(db, file);
Some(FileRange::new(file, TextRange::up_to(source.text_len()))) Some(FileRange::new(file, TextRange::up_to(source.text_len())))
} }

View file

@ -2522,9 +2522,9 @@ pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions(
db: &dyn Db, db: &dyn Db,
mut diagnostic: LintDiagnosticGuard, mut diagnostic: LintDiagnosticGuard,
full_submodule_name: &ModuleName, full_submodule_name: &ModuleName,
parent_module: &Module, parent_module: Module,
) { ) {
let Some(search_path) = parent_module.search_path() else { let Some(search_path) = parent_module.search_path(db) else {
return; return;
}; };
@ -2547,7 +2547,7 @@ pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions(
diagnostic.info(format_args!( diagnostic.info(format_args!(
"The stdlib module `{module_name}` only has a `{name}` \ "The stdlib module `{module_name}` only has a `{name}` \
submodule on Python {version_range}", submodule on Python {version_range}",
module_name = parent_module.name(), module_name = parent_module.name(db),
name = full_submodule_name name = full_submodule_name
.components() .components()
.next_back() .next_back()

View file

@ -102,7 +102,7 @@ impl Display for DisplayRepresentation<'_> {
}, },
Type::PropertyInstance(_) => f.write_str("property"), Type::PropertyInstance(_) => f.write_str("property"),
Type::ModuleLiteral(module) => { Type::ModuleLiteral(module) => {
write!(f, "<module '{}'>", module.module(self.db).name()) write!(f, "<module '{}'>", module.module(self.db).name(self.db))
} }
Type::ClassLiteral(class) => { Type::ClassLiteral(class) => {
write!(f, "<class '{}'>", class.name(self.db)) write!(f, "<class '{}'>", class.name(self.db))

View file

@ -1097,7 +1097,7 @@ impl KnownFunction {
) -> Option<Self> { ) -> Option<Self> {
let candidate = Self::from_str(name).ok()?; let candidate = Self::from_str(name).ok()?;
candidate candidate
.check_module(file_to_module(db, definition.file(db))?.known()?) .check_module(file_to_module(db, definition.file(db))?.known(db)?)
.then_some(candidate) .then_some(candidate)
} }
@ -1368,7 +1368,7 @@ impl KnownFunction {
return; return;
}; };
overload.set_return_type(Type::module_literal(db, file, &module)); overload.set_return_type(Type::module_literal(db, file, module));
} }
_ => {} _ => {}

View file

@ -156,7 +156,7 @@ impl<'db> AllMembers<'db> {
self.extend_with_type(db, KnownClass::ModuleType.to_instance(db)); self.extend_with_type(db, KnownClass::ModuleType.to_instance(db));
let module = literal.module(db); let module = literal.module(db);
let Some(file) = module.file() else { let Some(file) = module.file(db) else {
return; return;
}; };
@ -544,7 +544,7 @@ pub fn definitions_for_attribute<'db>(
for ty in expanded_tys { for ty in expanded_tys {
// Handle modules // Handle modules
if let Type::ModuleLiteral(module_literal) = ty { if let Type::ModuleLiteral(module_literal) = ty {
if let Some(module_file) = module_literal.module(db).file() { if let Some(module_file) = module_literal.module(db).file(db) {
let module_scope = global_scope(db, module_file); let module_scope = global_scope(db, module_file);
for def in find_symbol_in_scope(db, module_scope, name_str) { for def in find_symbol_in_scope(db, module_scope, name_str) {
resolved.extend(resolve_definition(db, def, Some(name_str))); resolved.extend(resolve_definition(db, def, Some(name_str)));
@ -878,7 +878,7 @@ mod resolve_definition {
return Vec::new(); // Module not found, return empty list return Vec::new(); // Module not found, return empty list
}; };
let Some(module_file) = resolved_module.file() else { let Some(module_file) = resolved_module.file(db) else {
return Vec::new(); // No file for module, return empty list return Vec::new(); // No file for module, return empty list
}; };
@ -939,7 +939,7 @@ mod resolve_definition {
let Some(resolved_module) = resolve_module(db, &module_name) else { let Some(resolved_module) = resolve_module(db, &module_name) else {
return Vec::new(); return Vec::new();
}; };
resolved_module.file() resolved_module.file(db)
}; };
let Some(module_file) = module_file else { let Some(module_file) = module_file else {
@ -1013,10 +1013,10 @@ mod resolve_definition {
// It's definitely a stub, so now rerun module resolution but with stubs disabled. // It's definitely a stub, so now rerun module resolution but with stubs disabled.
let stub_module = file_to_module(db, stub_file)?; let stub_module = file_to_module(db, stub_file)?;
trace!("Found stub module: {}", stub_module.name()); trace!("Found stub module: {}", stub_module.name(db));
let real_module = resolve_real_module(db, stub_module.name())?; let real_module = resolve_real_module(db, stub_module.name(db))?;
trace!("Found real module: {}", real_module.name()); trace!("Found real module: {}", real_module.name(db));
let real_file = real_module.file()?; let real_file = real_module.file(db)?;
trace!("Found real file: {}", real_file.path(db)); trace!("Found real file: {}", real_file.path(db));
// A definition has a "Definition Path" in a file made of nested definitions (~scopes): // A definition has a "Definition Path" in a file made of nested definitions (~scopes):

View file

@ -4866,7 +4866,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
return; return;
}; };
let module_ty = Type::module_literal(self.db(), self.file(), &module); let module_ty = Type::module_literal(self.db(), self.file(), module);
// The indirection of having `star_import_info` as a separate variable // The indirection of having `star_import_info` as a separate variable
// is required in order to make the borrow checker happy. // is required in order to make the borrow checker happy.
@ -4892,7 +4892,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// (e.g. `from parent import submodule` inside the `parent` module). // (e.g. `from parent import submodule` inside the `parent` module).
let import_is_self_referential = module_ty let import_is_self_referential = module_ty
.into_module_literal() .into_module_literal()
.is_some_and(|module| Some(self.file()) == module.module(self.db()).file()); .is_some_and(|module| Some(self.file()) == module.module(self.db()).file(self.db()));
// First try loading the requested attribute from the module. // First try loading the requested attribute from the module.
if !import_is_self_referential { if !import_is_self_referential {
@ -4990,7 +4990,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.db(), self.db(),
diagnostic, diagnostic,
&full_submodule_name, &full_submodule_name,
&module, module,
); );
} }
} }
@ -5144,7 +5144,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
fn module_type_from_name(&self, module_name: &ModuleName) -> Option<Type<'db>> { fn module_type_from_name(&self, module_name: &ModuleName) -> Option<Type<'db>> {
resolve_module(self.db(), module_name) resolve_module(self.db(), module_name)
.map(|module| Type::module_literal(self.db(), self.file(), &module)) .map(|module| Type::module_literal(self.db(), self.file(), module))
} }
fn infer_decorator(&mut self, decorator: &ast::Decorator) -> Type<'db> { fn infer_decorator(&mut self, decorator: &ast::Decorator) -> Type<'db> {

View file

@ -187,7 +187,7 @@ impl SpecialFormType {
) -> Option<Self> { ) -> Option<Self> {
let candidate = Self::from_str(symbol_name).ok()?; let candidate = Self::from_str(symbol_name).ok()?;
candidate candidate
.check_module(file_to_module(db, file)?.known()?) .check_module(file_to_module(db, file)?.known(db)?)
.then_some(candidate) .then_some(candidate)
} }