mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:45:24 +00:00
Eagerly validate search paths (#12783)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
fabf19fdc9
commit
a99a45868c
15 changed files with 355 additions and 277 deletions
|
@ -184,7 +184,7 @@ fn run() -> anyhow::Result<ExitStatus> {
|
||||||
|
|
||||||
// TODO: Use the `program_settings` to compute the key for the database's persistent
|
// TODO: Use the `program_settings` to compute the key for the database's persistent
|
||||||
// cache and load the cache if it exists.
|
// cache and load the cache if it exists.
|
||||||
let mut db = RootDatabase::new(workspace_metadata, program_settings, system);
|
let mut db = RootDatabase::new(workspace_metadata, program_settings, system)?;
|
||||||
|
|
||||||
let (main_loop, main_loop_cancellation_token) = MainLoop::new();
|
let (main_loop, main_loop_cancellation_token) = MainLoop::new();
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ use std::io::Write;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use salsa::Setter;
|
|
||||||
|
|
||||||
use red_knot_python_semantic::{
|
use red_knot_python_semantic::{
|
||||||
resolve_module, ModuleName, Program, ProgramSettings, PythonVersion, SearchPathSettings,
|
resolve_module, ModuleName, Program, ProgramSettings, PythonVersion, SearchPathSettings,
|
||||||
|
@ -26,6 +25,7 @@ struct TestCase {
|
||||||
/// We need to hold on to it in the test case or the temp files get deleted.
|
/// We need to hold on to it in the test case or the temp files get deleted.
|
||||||
_temp_dir: tempfile::TempDir,
|
_temp_dir: tempfile::TempDir,
|
||||||
root_dir: SystemPathBuf,
|
root_dir: SystemPathBuf,
|
||||||
|
search_path_settings: SearchPathSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestCase {
|
impl TestCase {
|
||||||
|
@ -108,18 +108,20 @@ impl TestCase {
|
||||||
fn update_search_path_settings(
|
fn update_search_path_settings(
|
||||||
&mut self,
|
&mut self,
|
||||||
f: impl FnOnce(&SearchPathSettings) -> SearchPathSettings,
|
f: impl FnOnce(&SearchPathSettings) -> SearchPathSettings,
|
||||||
) {
|
) -> anyhow::Result<()> {
|
||||||
let program = Program::get(self.db());
|
let program = Program::get(self.db());
|
||||||
let search_path_settings = program.search_paths(self.db());
|
|
||||||
|
|
||||||
let new_settings = f(search_path_settings);
|
let new_settings = f(&self.search_path_settings);
|
||||||
|
|
||||||
program.set_search_paths(&mut self.db).to(new_settings);
|
program.update_search_paths(&mut self.db, new_settings.clone())?;
|
||||||
|
self.search_path_settings = new_settings;
|
||||||
|
|
||||||
if let Some(watcher) = &mut self.watcher {
|
if let Some(watcher) = &mut self.watcher {
|
||||||
watcher.update(&self.db);
|
watcher.update(&self.db);
|
||||||
assert!(!watcher.has_errored_paths());
|
assert!(!watcher.has_errored_paths());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_package_files(&self, path: &SystemPath) -> Vec<File> {
|
fn collect_package_files(&self, path: &SystemPath) -> Vec<File> {
|
||||||
|
@ -221,13 +223,13 @@ where
|
||||||
let system = OsSystem::new(&workspace_path);
|
let system = OsSystem::new(&workspace_path);
|
||||||
|
|
||||||
let workspace = WorkspaceMetadata::from_path(&workspace_path, &system)?;
|
let workspace = WorkspaceMetadata::from_path(&workspace_path, &system)?;
|
||||||
let search_paths = create_search_paths(&root_path, workspace.root());
|
let search_path_settings = create_search_paths(&root_path, workspace.root());
|
||||||
|
|
||||||
for path in search_paths
|
for path in search_path_settings
|
||||||
.extra_paths
|
.extra_paths
|
||||||
.iter()
|
.iter()
|
||||||
.chain(search_paths.site_packages.iter())
|
.chain(search_path_settings.site_packages.iter())
|
||||||
.chain(search_paths.custom_typeshed.iter())
|
.chain(search_path_settings.custom_typeshed.iter())
|
||||||
{
|
{
|
||||||
std::fs::create_dir_all(path.as_std_path())
|
std::fs::create_dir_all(path.as_std_path())
|
||||||
.with_context(|| format!("Failed to create search path '{path}'"))?;
|
.with_context(|| format!("Failed to create search path '{path}'"))?;
|
||||||
|
@ -235,10 +237,10 @@ where
|
||||||
|
|
||||||
let settings = ProgramSettings {
|
let settings = ProgramSettings {
|
||||||
target_version: PythonVersion::default(),
|
target_version: PythonVersion::default(),
|
||||||
search_paths,
|
search_paths: search_path_settings.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = RootDatabase::new(workspace, settings, system);
|
let db = RootDatabase::new(workspace, settings, system)?;
|
||||||
|
|
||||||
let (sender, receiver) = crossbeam::channel::unbounded();
|
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||||
let watcher = directory_watcher(move |events| sender.send(events).unwrap())
|
let watcher = directory_watcher(move |events| sender.send(events).unwrap())
|
||||||
|
@ -253,6 +255,7 @@ where
|
||||||
watcher: Some(watcher),
|
watcher: Some(watcher),
|
||||||
_temp_dir: temp_dir,
|
_temp_dir: temp_dir,
|
||||||
root_dir: root_path,
|
root_dir: root_path,
|
||||||
|
search_path_settings,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sometimes the file watcher reports changes for events that happened before the watcher was started.
|
// Sometimes the file watcher reports changes for events that happened before the watcher was started.
|
||||||
|
@ -737,7 +740,8 @@ fn add_search_path() -> anyhow::Result<()> {
|
||||||
case.update_search_path_settings(|settings| SearchPathSettings {
|
case.update_search_path_settings(|settings| SearchPathSettings {
|
||||||
site_packages: vec![site_packages.clone()],
|
site_packages: vec![site_packages.clone()],
|
||||||
..settings.clone()
|
..settings.clone()
|
||||||
});
|
})
|
||||||
|
.expect("Search path settings to be valid");
|
||||||
|
|
||||||
std::fs::write(site_packages.join("a.py").as_std_path(), "class A: ...")?;
|
std::fs::write(site_packages.join("a.py").as_std_path(), "class A: ...")?;
|
||||||
|
|
||||||
|
@ -767,7 +771,8 @@ fn remove_search_path() -> anyhow::Result<()> {
|
||||||
case.update_search_path_settings(|settings| SearchPathSettings {
|
case.update_search_path_settings(|settings| SearchPathSettings {
|
||||||
site_packages: vec![],
|
site_packages: vec![],
|
||||||
..settings.clone()
|
..settings.clone()
|
||||||
});
|
})
|
||||||
|
.expect("Search path settings to be valid");
|
||||||
|
|
||||||
std::fs::write(site_packages.join("a.py").as_std_path(), "class A: ...")?;
|
std::fs::write(site_packages.join("a.py").as_std_path(), "class A: ...")?;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ ruff_python_ast = { workspace = true }
|
||||||
ruff_python_stdlib = { workspace = true }
|
ruff_python_stdlib = { workspace = true }
|
||||||
ruff_text_size = { workspace = true }
|
ruff_text_size = { workspace = true }
|
||||||
|
|
||||||
|
anyhow = { workspace = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
camino = { workspace = true }
|
camino = { workspace = true }
|
||||||
compact_str = { workspace = true }
|
compact_str = { workspace = true }
|
||||||
|
@ -34,7 +35,7 @@ walkdir = { workspace = true }
|
||||||
zip = { workspace = true, features = ["zstd", "deflate"] }
|
zip = { workspace = true, features = ["zstd", "deflate"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ruff_db = { workspace = true, features = ["os", "testing"]}
|
ruff_db = { workspace = true, features = ["os", "testing"] }
|
||||||
ruff_python_parser = { workspace = true }
|
ruff_python_parser = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
|
@ -2,11 +2,13 @@ use std::iter::FusedIterator;
|
||||||
|
|
||||||
pub(crate) use module::Module;
|
pub(crate) use module::Module;
|
||||||
pub use resolver::resolve_module;
|
pub use resolver::resolve_module;
|
||||||
|
pub(crate) use resolver::SearchPaths;
|
||||||
use ruff_db::system::SystemPath;
|
use ruff_db::system::SystemPath;
|
||||||
pub use typeshed::vendored_typeshed_stubs;
|
pub use typeshed::vendored_typeshed_stubs;
|
||||||
|
|
||||||
|
use crate::module_resolver::resolver::search_paths;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use resolver::{module_resolution_settings, SearchPathIterator};
|
use resolver::SearchPathIterator;
|
||||||
|
|
||||||
mod module;
|
mod module;
|
||||||
mod path;
|
mod path;
|
||||||
|
@ -20,7 +22,7 @@ mod testing;
|
||||||
/// Returns an iterator over all search paths pointing to a system path
|
/// Returns an iterator over all search paths pointing to a system path
|
||||||
pub fn system_module_search_paths(db: &dyn Db) -> SystemModuleSearchPathsIter {
|
pub fn system_module_search_paths(db: &dyn Db) -> SystemModuleSearchPathsIter {
|
||||||
SystemModuleSearchPathsIter {
|
SystemModuleSearchPathsIter {
|
||||||
inner: module_resolution_settings(db).search_paths(db),
|
inner: search_paths(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,13 @@ use ruff_db::files::{File, FilePath, FileRootKind};
|
||||||
use ruff_db::system::{DirectoryEntry, SystemPath, SystemPathBuf};
|
use ruff_db::system::{DirectoryEntry, SystemPath, SystemPathBuf};
|
||||||
use ruff_db::vendored::VendoredPath;
|
use ruff_db::vendored::VendoredPath;
|
||||||
|
|
||||||
|
use crate::db::Db;
|
||||||
|
use crate::module_name::ModuleName;
|
||||||
|
use crate::{Program, SearchPathSettings};
|
||||||
|
|
||||||
use super::module::{Module, ModuleKind};
|
use super::module::{Module, ModuleKind};
|
||||||
use super::path::{ModulePath, SearchPath, SearchPathValidationError};
|
use super::path::{ModulePath, SearchPath, SearchPathValidationError};
|
||||||
use super::state::ResolverState;
|
use super::state::ResolverState;
|
||||||
use crate::db::Db;
|
|
||||||
use crate::module_name::ModuleName;
|
|
||||||
use crate::{Program, PythonVersion, SearchPathSettings};
|
|
||||||
|
|
||||||
/// 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: &dyn Db, module_name: ModuleName) -> Option<Module> {
|
||||||
|
@ -84,9 +85,7 @@ pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
|
||||||
FilePath::SystemVirtual(_) => return None,
|
FilePath::SystemVirtual(_) => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = module_resolution_settings(db);
|
let mut search_paths = search_paths(db);
|
||||||
|
|
||||||
let mut search_paths = settings.search_paths(db);
|
|
||||||
|
|
||||||
let module_name = loop {
|
let module_name = loop {
|
||||||
let candidate = search_paths.next()?;
|
let candidate = search_paths.next()?;
|
||||||
|
@ -119,106 +118,122 @@ pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate and normalize the raw settings given by the user
|
pub(crate) fn search_paths(db: &dyn Db) -> SearchPathIterator {
|
||||||
/// into settings we can use for module resolution
|
Program::get(db).search_paths(db).iter(db)
|
||||||
///
|
|
||||||
/// This method also implements the typing spec's [module resolution order].
|
|
||||||
///
|
|
||||||
/// [module resolution order]: https://typing.readthedocs.io/en/latest/spec/distributing.html#import-resolution-ordering
|
|
||||||
fn try_resolve_module_resolution_settings(
|
|
||||||
db: &dyn Db,
|
|
||||||
) -> Result<ModuleResolutionSettings, SearchPathValidationError> {
|
|
||||||
let program = Program::get(db.upcast());
|
|
||||||
|
|
||||||
let SearchPathSettings {
|
|
||||||
extra_paths,
|
|
||||||
src_root,
|
|
||||||
custom_typeshed,
|
|
||||||
site_packages,
|
|
||||||
} = program.search_paths(db.upcast());
|
|
||||||
|
|
||||||
if !extra_paths.is_empty() {
|
|
||||||
tracing::info!("Extra search paths: {extra_paths:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(custom_typeshed) = custom_typeshed {
|
|
||||||
tracing::info!("Custom typeshed directory: {custom_typeshed}");
|
|
||||||
}
|
|
||||||
|
|
||||||
let system = db.system();
|
|
||||||
let files = db.files();
|
|
||||||
|
|
||||||
let mut static_search_paths = vec![];
|
|
||||||
|
|
||||||
for path in extra_paths {
|
|
||||||
let search_path = SearchPath::extra(system, path.clone())?;
|
|
||||||
files.try_add_root(
|
|
||||||
db.upcast(),
|
|
||||||
search_path.as_system_path().unwrap(),
|
|
||||||
FileRootKind::LibrarySearchPath,
|
|
||||||
);
|
|
||||||
static_search_paths.push(search_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
static_search_paths.push(SearchPath::first_party(system, src_root.clone())?);
|
|
||||||
|
|
||||||
static_search_paths.push(if let Some(custom_typeshed) = custom_typeshed.as_ref() {
|
|
||||||
let search_path = SearchPath::custom_stdlib(db, custom_typeshed.clone())?;
|
|
||||||
files.try_add_root(
|
|
||||||
db.upcast(),
|
|
||||||
search_path.as_system_path().unwrap(),
|
|
||||||
FileRootKind::LibrarySearchPath,
|
|
||||||
);
|
|
||||||
search_path
|
|
||||||
} else {
|
|
||||||
SearchPath::vendored_stdlib()
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut site_packages_paths: Vec<_> = Vec::with_capacity(site_packages.len());
|
|
||||||
|
|
||||||
for path in site_packages {
|
|
||||||
let search_path = SearchPath::site_packages(system, path.to_path_buf())?;
|
|
||||||
files.try_add_root(
|
|
||||||
db.upcast(),
|
|
||||||
search_path.as_system_path().unwrap(),
|
|
||||||
FileRootKind::LibrarySearchPath,
|
|
||||||
);
|
|
||||||
site_packages_paths.push(search_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO vendor typeshed's third-party stubs as well as the stdlib and fallback to them as a final step
|
|
||||||
|
|
||||||
let target_version = program.target_version(db.upcast());
|
|
||||||
tracing::info!("Target version: {target_version}");
|
|
||||||
|
|
||||||
// Filter out module resolution paths that point to the same directory on disk (the same invariant maintained by [`sys.path` at runtime]).
|
|
||||||
// (Paths may, however, *overlap* -- e.g. you could have both `src/` and `src/foo`
|
|
||||||
// as module resolution paths simultaneously.)
|
|
||||||
//
|
|
||||||
// [`sys.path` at runtime]: https://docs.python.org/3/library/site.html#module-site
|
|
||||||
// This code doesn't use an `IndexSet` because the key is the system path and not the search root.
|
|
||||||
let mut seen_paths =
|
|
||||||
FxHashSet::with_capacity_and_hasher(static_search_paths.len(), FxBuildHasher);
|
|
||||||
|
|
||||||
static_search_paths.retain(|path| {
|
|
||||||
if let Some(path) = path.as_system_path() {
|
|
||||||
seen_paths.insert(path.to_path_buf())
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(ModuleResolutionSettings {
|
|
||||||
target_version,
|
|
||||||
static_search_paths,
|
|
||||||
site_packages_paths,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[salsa::tracked(return_ref)]
|
#[derive(Debug, PartialEq, Eq, Default)]
|
||||||
pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSettings {
|
pub(crate) struct SearchPaths {
|
||||||
// TODO proper error handling if this returns an error:
|
/// Search paths that have been statically determined purely from reading Ruff's configuration settings.
|
||||||
try_resolve_module_resolution_settings(db).unwrap()
|
/// These shouldn't ever change unless the config settings themselves change.
|
||||||
|
static_paths: Vec<SearchPath>,
|
||||||
|
|
||||||
|
/// site-packages paths are not included in the above field:
|
||||||
|
/// if there are multiple site-packages paths, editable installations can appear
|
||||||
|
/// *between* the site-packages paths on `sys.path` at runtime.
|
||||||
|
/// That means we can't know where a second or third `site-packages` path should sit
|
||||||
|
/// in terms of module-resolution priority until we've discovered the editable installs
|
||||||
|
/// for the first `site-packages` path
|
||||||
|
site_packages: Vec<SearchPath>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearchPaths {
|
||||||
|
/// Validate and normalize the raw settings given by the user
|
||||||
|
/// into settings we can use for module resolution
|
||||||
|
///
|
||||||
|
/// This method also implements the typing spec's [module resolution order].
|
||||||
|
///
|
||||||
|
/// [module resolution order]: https://typing.readthedocs.io/en/latest/spec/distributing.html#import-resolution-ordering
|
||||||
|
pub(crate) fn from_settings(
|
||||||
|
db: &dyn Db,
|
||||||
|
settings: SearchPathSettings,
|
||||||
|
) -> Result<Self, SearchPathValidationError> {
|
||||||
|
let SearchPathSettings {
|
||||||
|
extra_paths,
|
||||||
|
src_root,
|
||||||
|
custom_typeshed,
|
||||||
|
site_packages: site_packages_paths,
|
||||||
|
} = settings;
|
||||||
|
|
||||||
|
let system = db.system();
|
||||||
|
let files = db.files();
|
||||||
|
|
||||||
|
let mut static_paths = vec![];
|
||||||
|
|
||||||
|
for path in extra_paths {
|
||||||
|
tracing::debug!("Adding static extra search-path '{path}'");
|
||||||
|
|
||||||
|
let search_path = SearchPath::extra(system, path)?;
|
||||||
|
files.try_add_root(
|
||||||
|
db.upcast(),
|
||||||
|
search_path.as_system_path().unwrap(),
|
||||||
|
FileRootKind::LibrarySearchPath,
|
||||||
|
);
|
||||||
|
static_paths.push(search_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!("Adding static search path '{src_root}'");
|
||||||
|
static_paths.push(SearchPath::first_party(system, src_root)?);
|
||||||
|
|
||||||
|
static_paths.push(if let Some(custom_typeshed) = custom_typeshed {
|
||||||
|
tracing::debug!("Adding static custom-sdtlib search-path '{custom_typeshed}'");
|
||||||
|
|
||||||
|
let search_path = SearchPath::custom_stdlib(db, custom_typeshed)?;
|
||||||
|
files.try_add_root(
|
||||||
|
db.upcast(),
|
||||||
|
search_path.as_system_path().unwrap(),
|
||||||
|
FileRootKind::LibrarySearchPath,
|
||||||
|
);
|
||||||
|
search_path
|
||||||
|
} else {
|
||||||
|
SearchPath::vendored_stdlib()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut site_packages: Vec<_> = Vec::with_capacity(site_packages_paths.len());
|
||||||
|
|
||||||
|
for path in site_packages_paths {
|
||||||
|
tracing::debug!("Adding site-package path '{path}'");
|
||||||
|
let search_path = SearchPath::site_packages(system, path)?;
|
||||||
|
files.try_add_root(
|
||||||
|
db.upcast(),
|
||||||
|
search_path.as_system_path().unwrap(),
|
||||||
|
FileRootKind::LibrarySearchPath,
|
||||||
|
);
|
||||||
|
site_packages.push(search_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO vendor typeshed's third-party stubs as well as the stdlib and fallback to them as a final step
|
||||||
|
|
||||||
|
// Filter out module resolution paths that point to the same directory on disk (the same invariant maintained by [`sys.path` at runtime]).
|
||||||
|
// (Paths may, however, *overlap* -- e.g. you could have both `src/` and `src/foo`
|
||||||
|
// as module resolution paths simultaneously.)
|
||||||
|
//
|
||||||
|
// This code doesn't use an `IndexSet` because the key is the system path and not the search root.
|
||||||
|
//
|
||||||
|
// [`sys.path` at runtime]: https://docs.python.org/3/library/site.html#module-site
|
||||||
|
let mut seen_paths = FxHashSet::with_capacity_and_hasher(static_paths.len(), FxBuildHasher);
|
||||||
|
|
||||||
|
static_paths.retain(|path| {
|
||||||
|
if let Some(path) = path.as_system_path() {
|
||||||
|
seen_paths.insert(path.to_path_buf())
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(SearchPaths {
|
||||||
|
static_paths,
|
||||||
|
site_packages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn iter<'a>(&'a self, db: &'a dyn Db) -> SearchPathIterator<'a> {
|
||||||
|
SearchPathIterator {
|
||||||
|
db,
|
||||||
|
static_paths: self.static_paths.iter(),
|
||||||
|
dynamic_paths: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect all dynamic search paths. For each `site-packages` path:
|
/// Collect all dynamic search paths. For each `site-packages` path:
|
||||||
|
@ -231,19 +246,20 @@ pub(crate) fn module_resolution_settings(db: &dyn Db) -> ModuleResolutionSetting
|
||||||
/// module-resolution priority.
|
/// module-resolution priority.
|
||||||
#[salsa::tracked(return_ref)]
|
#[salsa::tracked(return_ref)]
|
||||||
pub(crate) fn dynamic_resolution_paths(db: &dyn Db) -> Vec<SearchPath> {
|
pub(crate) fn dynamic_resolution_paths(db: &dyn Db) -> Vec<SearchPath> {
|
||||||
let ModuleResolutionSettings {
|
tracing::debug!("Resolving dynamic module resolution paths");
|
||||||
target_version: _,
|
|
||||||
static_search_paths,
|
let SearchPaths {
|
||||||
site_packages_paths,
|
static_paths,
|
||||||
} = module_resolution_settings(db);
|
site_packages,
|
||||||
|
} = Program::get(db).search_paths(db);
|
||||||
|
|
||||||
let mut dynamic_paths = Vec::new();
|
let mut dynamic_paths = Vec::new();
|
||||||
|
|
||||||
if site_packages_paths.is_empty() {
|
if site_packages.is_empty() {
|
||||||
return dynamic_paths;
|
return dynamic_paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut existing_paths: FxHashSet<_> = static_search_paths
|
let mut existing_paths: FxHashSet<_> = static_paths
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|path| path.as_system_path())
|
.filter_map(|path| path.as_system_path())
|
||||||
.map(Cow::Borrowed)
|
.map(Cow::Borrowed)
|
||||||
|
@ -252,7 +268,7 @@ pub(crate) fn dynamic_resolution_paths(db: &dyn Db) -> Vec<SearchPath> {
|
||||||
let files = db.files();
|
let files = db.files();
|
||||||
let system = db.system();
|
let system = db.system();
|
||||||
|
|
||||||
for site_packages_search_path in site_packages_paths {
|
for site_packages_search_path in site_packages {
|
||||||
let site_packages_dir = site_packages_search_path
|
let site_packages_dir = site_packages_search_path
|
||||||
.as_system_path()
|
.as_system_path()
|
||||||
.expect("Expected site package path to be a system path");
|
.expect("Expected site package path to be a system path");
|
||||||
|
@ -302,6 +318,10 @@ pub(crate) fn dynamic_resolution_paths(db: &dyn Db) -> Vec<SearchPath> {
|
||||||
if existing_paths.insert(Cow::Owned(installation.clone())) {
|
if existing_paths.insert(Cow::Owned(installation.clone())) {
|
||||||
match SearchPath::editable(system, installation) {
|
match SearchPath::editable(system, installation) {
|
||||||
Ok(search_path) => {
|
Ok(search_path) => {
|
||||||
|
tracing::debug!(
|
||||||
|
"Adding editable installation to module resolution path {path}",
|
||||||
|
path = search_path.as_system_path().unwrap()
|
||||||
|
);
|
||||||
dynamic_paths.push(search_path);
|
dynamic_paths.push(search_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,38 +468,6 @@ impl<'db> Iterator for PthFileIterator<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validated and normalized module-resolution settings.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub(crate) struct ModuleResolutionSettings {
|
|
||||||
target_version: PythonVersion,
|
|
||||||
|
|
||||||
/// Search paths that have been statically determined purely from reading Ruff's configuration settings.
|
|
||||||
/// These shouldn't ever change unless the config settings themselves change.
|
|
||||||
static_search_paths: Vec<SearchPath>,
|
|
||||||
|
|
||||||
/// site-packages paths are not included in the above field:
|
|
||||||
/// if there are multiple site-packages paths, editable installations can appear
|
|
||||||
/// *between* the site-packages paths on `sys.path` at runtime.
|
|
||||||
/// That means we can't know where a second or third `site-packages` path should sit
|
|
||||||
/// in terms of module-resolution priority until we've discovered the editable installs
|
|
||||||
/// for the first `site-packages` path
|
|
||||||
site_packages_paths: Vec<SearchPath>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleResolutionSettings {
|
|
||||||
fn target_version(&self) -> PythonVersion {
|
|
||||||
self.target_version
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn search_paths<'db>(&'db self, db: &'db dyn Db) -> SearchPathIterator<'db> {
|
|
||||||
SearchPathIterator {
|
|
||||||
db,
|
|
||||||
static_paths: self.static_search_paths.iter(),
|
|
||||||
dynamic_paths: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A thin wrapper around `ModuleName` to make it a Salsa ingredient.
|
/// A thin wrapper around `ModuleName` to make it a Salsa ingredient.
|
||||||
///
|
///
|
||||||
/// This is needed because Salsa requires that all query arguments are salsa ingredients.
|
/// This is needed because Salsa requires that all query arguments are salsa ingredients.
|
||||||
|
@ -492,13 +480,13 @@ struct ModuleNameIngredient<'db> {
|
||||||
/// Given a module name and a list of search paths in which to lookup modules,
|
/// Given a module name and a list of search paths in which to lookup modules,
|
||||||
/// attempt to resolve the module name
|
/// attempt to resolve the module name
|
||||||
fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, File, ModuleKind)> {
|
fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, File, ModuleKind)> {
|
||||||
let resolver_settings = module_resolution_settings(db);
|
let program = Program::get(db);
|
||||||
let target_version = resolver_settings.target_version();
|
let target_version = program.target_version(db);
|
||||||
let resolver_state = ResolverState::new(db, target_version);
|
let resolver_state = ResolverState::new(db, target_version);
|
||||||
let is_builtin_module =
|
let is_builtin_module =
|
||||||
ruff_python_stdlib::sys::is_builtin_module(target_version.minor, name.as_str());
|
ruff_python_stdlib::sys::is_builtin_module(target_version.minor, name.as_str());
|
||||||
|
|
||||||
for search_path in resolver_settings.search_paths(db) {
|
for search_path in search_paths(db) {
|
||||||
// When a builtin module is imported, standard module resolution is bypassed:
|
// When a builtin module is imported, standard module resolution is bypassed:
|
||||||
// the module name always resolves to the stdlib module,
|
// the module name always resolves to the stdlib module,
|
||||||
// even if there's a module of the same name in the first-party root
|
// even if there's a module of the same name in the first-party root
|
||||||
|
@ -652,6 +640,8 @@ mod tests {
|
||||||
use crate::module_name::ModuleName;
|
use crate::module_name::ModuleName;
|
||||||
use crate::module_resolver::module::ModuleKind;
|
use crate::module_resolver::module::ModuleKind;
|
||||||
use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||||
|
use crate::ProgramSettings;
|
||||||
|
use crate::PythonVersion;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -1202,14 +1192,19 @@ mod tests {
|
||||||
std::fs::write(foo.as_std_path(), "")?;
|
std::fs::write(foo.as_std_path(), "")?;
|
||||||
std::os::unix::fs::symlink(foo.as_std_path(), bar.as_std_path())?;
|
std::os::unix::fs::symlink(foo.as_std_path(), bar.as_std_path())?;
|
||||||
|
|
||||||
let search_paths = SearchPathSettings {
|
Program::from_settings(
|
||||||
extra_paths: vec![],
|
&db,
|
||||||
src_root: src.clone(),
|
ProgramSettings {
|
||||||
custom_typeshed: Some(custom_typeshed.clone()),
|
target_version: PythonVersion::PY38,
|
||||||
site_packages: vec![site_packages],
|
search_paths: SearchPathSettings {
|
||||||
};
|
extra_paths: vec![],
|
||||||
|
src_root: src.clone(),
|
||||||
Program::new(&db, PythonVersion::PY38, search_paths);
|
custom_typeshed: Some(custom_typeshed.clone()),
|
||||||
|
site_packages: vec![site_packages],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.context("Invalid program settings")?;
|
||||||
|
|
||||||
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 bar_module = resolve_module(&db, ModuleName::new_static("bar").unwrap()).unwrap();
|
let bar_module = resolve_module(&db, ModuleName::new_static("bar").unwrap()).unwrap();
|
||||||
|
@ -1673,8 +1668,7 @@ not_a_directory
|
||||||
.with_site_packages_files(&[("_foo.pth", "/src")])
|
.with_site_packages_files(&[("_foo.pth", "/src")])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let search_paths: Vec<&SearchPath> =
|
let search_paths: Vec<&SearchPath> = search_paths(&db).collect();
|
||||||
module_resolution_settings(&db).search_paths(&db).collect();
|
|
||||||
|
|
||||||
assert!(search_paths.contains(
|
assert!(search_paths.contains(
|
||||||
&&SearchPath::first_party(db.system(), SystemPathBuf::from("/src")).unwrap()
|
&&SearchPath::first_party(db.system(), SystemPathBuf::from("/src")).unwrap()
|
||||||
|
@ -1703,16 +1697,19 @@ not_a_directory
|
||||||
])
|
])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Program::new(
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
PythonVersion::default(),
|
ProgramSettings {
|
||||||
SearchPathSettings {
|
target_version: PythonVersion::default(),
|
||||||
extra_paths: vec![],
|
search_paths: SearchPathSettings {
|
||||||
src_root: SystemPathBuf::from("/src"),
|
extra_paths: vec![],
|
||||||
custom_typeshed: None,
|
src_root: SystemPathBuf::from("/src"),
|
||||||
site_packages: vec![venv_site_packages, system_site_packages],
|
custom_typeshed: None,
|
||||||
|
site_packages: vec![venv_site_packages, system_site_packages],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.expect("Valid program settings");
|
||||||
|
|
||||||
// The editable installs discovered from the `.pth` file in the first `site-packages` directory
|
// The editable installs discovered from the `.pth` file in the first `site-packages` directory
|
||||||
// take precedence over the second `site-packages` directory...
|
// take precedence over the second `site-packages` directory...
|
||||||
|
|
|
@ -4,6 +4,7 @@ use ruff_db::vendored::VendoredPathBuf;
|
||||||
use crate::db::tests::TestDb;
|
use crate::db::tests::TestDb;
|
||||||
use crate::program::{Program, SearchPathSettings};
|
use crate::program::{Program, SearchPathSettings};
|
||||||
use crate::python_version::PythonVersion;
|
use crate::python_version::PythonVersion;
|
||||||
|
use crate::ProgramSettings;
|
||||||
|
|
||||||
/// A test case for the module resolver.
|
/// A test case for the module resolver.
|
||||||
///
|
///
|
||||||
|
@ -220,16 +221,19 @@ impl TestCaseBuilder<MockedTypeshed> {
|
||||||
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
||||||
let typeshed = Self::build_typeshed_mock(&mut db, &typeshed_option);
|
let typeshed = Self::build_typeshed_mock(&mut db, &typeshed_option);
|
||||||
|
|
||||||
Program::new(
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
target_version,
|
ProgramSettings {
|
||||||
SearchPathSettings {
|
target_version,
|
||||||
extra_paths: vec![],
|
search_paths: SearchPathSettings {
|
||||||
src_root: src.clone(),
|
extra_paths: vec![],
|
||||||
custom_typeshed: Some(typeshed.clone()),
|
src_root: src.clone(),
|
||||||
site_packages: vec![site_packages.clone()],
|
custom_typeshed: Some(typeshed.clone()),
|
||||||
|
site_packages: vec![site_packages.clone()],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.expect("Valid program settings");
|
||||||
|
|
||||||
TestCase {
|
TestCase {
|
||||||
db,
|
db,
|
||||||
|
@ -273,16 +277,19 @@ impl TestCaseBuilder<VendoredTypeshed> {
|
||||||
Self::write_mock_directory(&mut db, "/site-packages", site_packages_files);
|
Self::write_mock_directory(&mut db, "/site-packages", site_packages_files);
|
||||||
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
||||||
|
|
||||||
Program::new(
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
target_version,
|
ProgramSettings {
|
||||||
SearchPathSettings {
|
target_version,
|
||||||
extra_paths: vec![],
|
search_paths: SearchPathSettings {
|
||||||
src_root: src.clone(),
|
extra_paths: vec![],
|
||||||
custom_typeshed: None,
|
src_root: src.clone(),
|
||||||
site_packages: vec![site_packages.clone()],
|
custom_typeshed: None,
|
||||||
|
site_packages: vec![site_packages.clone()],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.expect("Valid search path settings");
|
||||||
|
|
||||||
TestCase {
|
TestCase {
|
||||||
db,
|
db,
|
||||||
|
|
|
@ -1,21 +1,53 @@
|
||||||
use crate::python_version::PythonVersion;
|
use crate::python_version::PythonVersion;
|
||||||
use crate::Db;
|
use anyhow::Context;
|
||||||
use ruff_db::system::SystemPathBuf;
|
|
||||||
use salsa::Durability;
|
use salsa::Durability;
|
||||||
|
use salsa::Setter;
|
||||||
|
|
||||||
|
use ruff_db::system::SystemPathBuf;
|
||||||
|
|
||||||
|
use crate::module_resolver::SearchPaths;
|
||||||
|
use crate::Db;
|
||||||
|
|
||||||
#[salsa::input(singleton)]
|
#[salsa::input(singleton)]
|
||||||
pub struct Program {
|
pub struct Program {
|
||||||
pub target_version: PythonVersion,
|
pub target_version: PythonVersion,
|
||||||
|
|
||||||
|
#[default]
|
||||||
#[return_ref]
|
#[return_ref]
|
||||||
pub search_paths: SearchPathSettings,
|
pub(crate) search_paths: SearchPaths,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Program {
|
impl Program {
|
||||||
pub fn from_settings(db: &dyn Db, settings: ProgramSettings) -> Self {
|
pub fn from_settings(db: &dyn Db, settings: ProgramSettings) -> anyhow::Result<Self> {
|
||||||
Program::builder(settings.target_version, settings.search_paths)
|
let ProgramSettings {
|
||||||
|
target_version,
|
||||||
|
search_paths,
|
||||||
|
} = settings;
|
||||||
|
|
||||||
|
tracing::info!("Target version: {target_version}");
|
||||||
|
|
||||||
|
let search_paths = SearchPaths::from_settings(db, search_paths)
|
||||||
|
.with_context(|| "Invalid search path settings")?;
|
||||||
|
|
||||||
|
Ok(Program::builder(settings.target_version)
|
||||||
.durability(Durability::HIGH)
|
.durability(Durability::HIGH)
|
||||||
.new(db)
|
.search_paths(search_paths)
|
||||||
|
.new(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_search_paths(
|
||||||
|
&self,
|
||||||
|
db: &mut dyn Db,
|
||||||
|
search_path_settings: SearchPathSettings,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let search_paths = SearchPaths::from_settings(db, search_path_settings)?;
|
||||||
|
|
||||||
|
if self.search_paths(db) != &search_paths {
|
||||||
|
tracing::debug!("Update search paths");
|
||||||
|
self.set_search_paths(db).to(search_paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,29 +171,32 @@ mod tests {
|
||||||
use crate::program::{Program, SearchPathSettings};
|
use crate::program::{Program, SearchPathSettings};
|
||||||
use crate::python_version::PythonVersion;
|
use crate::python_version::PythonVersion;
|
||||||
use crate::types::Type;
|
use crate::types::Type;
|
||||||
use crate::{HasTy, SemanticModel};
|
use crate::{HasTy, ProgramSettings, SemanticModel};
|
||||||
|
|
||||||
fn setup_db() -> TestDb {
|
fn setup_db<'a>(files: impl IntoIterator<Item = (&'a str, &'a str)>) -> anyhow::Result<TestDb> {
|
||||||
let db = TestDb::new();
|
let mut db = TestDb::new();
|
||||||
Program::new(
|
db.write_files(files)?;
|
||||||
|
|
||||||
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
PythonVersion::default(),
|
ProgramSettings {
|
||||||
SearchPathSettings {
|
target_version: PythonVersion::default(),
|
||||||
extra_paths: vec![],
|
search_paths: SearchPathSettings {
|
||||||
src_root: SystemPathBuf::from("/src"),
|
extra_paths: vec![],
|
||||||
site_packages: vec![],
|
src_root: SystemPathBuf::from("/src"),
|
||||||
custom_typeshed: None,
|
site_packages: vec![],
|
||||||
|
custom_typeshed: None,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
)?;
|
||||||
|
|
||||||
db
|
Ok(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn function_ty() -> anyhow::Result<()> {
|
fn function_ty() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db();
|
let db = setup_db([("/src/foo.py", "def test(): pass")])?;
|
||||||
|
|
||||||
db.write_file("/src/foo.py", "def test(): pass")?;
|
|
||||||
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||||
|
|
||||||
let ast = parsed_module(&db, foo);
|
let ast = parsed_module(&db, foo);
|
||||||
|
@ -209,9 +212,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn class_ty() -> anyhow::Result<()> {
|
fn class_ty() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db();
|
let db = setup_db([("/src/foo.py", "class Test: pass")])?;
|
||||||
|
|
||||||
db.write_file("/src/foo.py", "class Test: pass")?;
|
|
||||||
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
|
||||||
|
|
||||||
let ast = parsed_module(&db, foo);
|
let ast = parsed_module(&db, foo);
|
||||||
|
@ -227,12 +229,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alias_ty() -> anyhow::Result<()> {
|
fn alias_ty() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db();
|
let db = setup_db([
|
||||||
|
|
||||||
db.write_files([
|
|
||||||
("/src/foo.py", "class Test: pass"),
|
("/src/foo.py", "class Test: pass"),
|
||||||
("/src/bar.py", "from foo import Test"),
|
("/src/bar.py", "from foo import Test"),
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
let bar = system_path_to_file(&db, "/src/bar.py").unwrap();
|
let bar = system_path_to_file(&db, "/src/bar.py").unwrap();
|
||||||
|
|
||||||
let ast = parsed_module(&db, bar);
|
let ast = parsed_module(&db, bar);
|
||||||
|
|
|
@ -1494,6 +1494,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use anyhow::Context;
|
||||||
use ruff_db::files::{system_path_to_file, File};
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||||
|
@ -1508,40 +1509,58 @@ mod tests {
|
||||||
use crate::semantic_index::symbol::FileScopeId;
|
use crate::semantic_index::symbol::FileScopeId;
|
||||||
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
|
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
|
||||||
use crate::types::{global_symbol_ty_by_name, infer_definition_types, symbol_ty_by_name, Type};
|
use crate::types::{global_symbol_ty_by_name, infer_definition_types, symbol_ty_by_name, Type};
|
||||||
use crate::{HasTy, SemanticModel};
|
use crate::{HasTy, ProgramSettings, SemanticModel};
|
||||||
|
|
||||||
fn setup_db() -> TestDb {
|
fn setup_db() -> TestDb {
|
||||||
let db = TestDb::new();
|
let db = TestDb::new();
|
||||||
|
|
||||||
Program::new(
|
let src_root = SystemPathBuf::from("/src");
|
||||||
|
db.memory_file_system()
|
||||||
|
.create_directory_all(&src_root)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
PythonVersion::default(),
|
ProgramSettings {
|
||||||
SearchPathSettings {
|
target_version: PythonVersion::default(),
|
||||||
extra_paths: Vec::new(),
|
search_paths: SearchPathSettings {
|
||||||
src_root: SystemPathBuf::from("/src"),
|
extra_paths: Vec::new(),
|
||||||
site_packages: vec![],
|
src_root,
|
||||||
custom_typeshed: None,
|
site_packages: vec![],
|
||||||
|
custom_typeshed: None,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.expect("Valid search path settings");
|
||||||
|
|
||||||
db
|
db
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_db_with_custom_typeshed(typeshed: &str) -> TestDb {
|
fn setup_db_with_custom_typeshed<'a>(
|
||||||
let db = TestDb::new();
|
typeshed: &str,
|
||||||
|
files: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||||
|
) -> anyhow::Result<TestDb> {
|
||||||
|
let mut db = TestDb::new();
|
||||||
|
let src_root = SystemPathBuf::from("/src");
|
||||||
|
|
||||||
Program::new(
|
db.write_files(files)
|
||||||
|
.context("Failed to write test files")?;
|
||||||
|
|
||||||
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
PythonVersion::default(),
|
ProgramSettings {
|
||||||
SearchPathSettings {
|
target_version: PythonVersion::default(),
|
||||||
extra_paths: Vec::new(),
|
search_paths: SearchPathSettings {
|
||||||
src_root: SystemPathBuf::from("/src"),
|
extra_paths: Vec::new(),
|
||||||
site_packages: vec![],
|
src_root,
|
||||||
custom_typeshed: Some(SystemPathBuf::from(typeshed)),
|
site_packages: vec![],
|
||||||
|
custom_typeshed: Some(SystemPathBuf::from(typeshed)),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.context("Failed to create Program")?;
|
||||||
|
|
||||||
db
|
Ok(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_public_ty(db: &TestDb, file_name: &str, symbol_name: &str, expected: &str) {
|
fn assert_public_ty(db: &TestDb, file_name: &str, symbol_name: &str, expected: &str) {
|
||||||
|
@ -2131,16 +2150,17 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn builtin_symbol_custom_stdlib() -> anyhow::Result<()> {
|
fn builtin_symbol_custom_stdlib() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db_with_custom_typeshed("/typeshed");
|
let db = setup_db_with_custom_typeshed(
|
||||||
|
"/typeshed",
|
||||||
db.write_files([
|
[
|
||||||
("/src/a.py", "c = copyright"),
|
("/src/a.py", "c = copyright"),
|
||||||
(
|
(
|
||||||
"/typeshed/stdlib/builtins.pyi",
|
"/typeshed/stdlib/builtins.pyi",
|
||||||
"def copyright() -> None: ...",
|
"def copyright() -> None: ...",
|
||||||
),
|
),
|
||||||
("/typeshed/stdlib/VERSIONS", "builtins: 3.8-"),
|
("/typeshed/stdlib/VERSIONS", "builtins: 3.8-"),
|
||||||
])?;
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
assert_public_ty(&db, "/src/a.py", "c", "Literal[copyright]");
|
assert_public_ty(&db, "/src/a.py", "c", "Literal[copyright]");
|
||||||
|
|
||||||
|
@ -2160,13 +2180,14 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_builtin_later_defined() -> anyhow::Result<()> {
|
fn unknown_builtin_later_defined() -> anyhow::Result<()> {
|
||||||
let mut db = setup_db_with_custom_typeshed("/typeshed");
|
let db = setup_db_with_custom_typeshed(
|
||||||
|
"/typeshed",
|
||||||
db.write_files([
|
[
|
||||||
("/src/a.py", "x = foo"),
|
("/src/a.py", "x = foo"),
|
||||||
("/typeshed/stdlib/builtins.pyi", "foo = bar; bar = 1"),
|
("/typeshed/stdlib/builtins.pyi", "foo = bar; bar = 1"),
|
||||||
("/typeshed/stdlib/VERSIONS", "builtins: 3.8-"),
|
("/typeshed/stdlib/VERSIONS", "builtins: 3.8-"),
|
||||||
])?;
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
assert_public_ty(&db, "/src/a.py", "x", "Unbound");
|
assert_public_ty(&db, "/src/a.py", "x", "Unbound");
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,8 @@ impl Session {
|
||||||
custom_typeshed: None,
|
custom_typeshed: None,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
workspaces.insert(path, RootDatabase::new(metadata, program_settings, system));
|
// TODO(micha): Handle the case where the program settings are incorrect more gracefully.
|
||||||
|
workspaces.insert(path, RootDatabase::new(metadata, program_settings, system)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
@ -49,7 +49,8 @@ impl Workspace {
|
||||||
search_paths: SearchPathSettings::default(),
|
search_paths: SearchPathSettings::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = RootDatabase::new(workspace, program_settings, system.clone());
|
let db =
|
||||||
|
RootDatabase::new(workspace, program_settings, system.clone()).map_err(into_error)?;
|
||||||
|
|
||||||
Ok(Self { db, system })
|
Ok(Self { db, system })
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,11 @@ pub struct RootDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RootDatabase {
|
impl RootDatabase {
|
||||||
pub fn new<S>(workspace: WorkspaceMetadata, settings: ProgramSettings, system: S) -> Self
|
pub fn new<S>(
|
||||||
|
workspace: WorkspaceMetadata,
|
||||||
|
settings: ProgramSettings,
|
||||||
|
system: S,
|
||||||
|
) -> anyhow::Result<Self>
|
||||||
where
|
where
|
||||||
S: System + 'static + Send + Sync + RefUnwindSafe,
|
S: System + 'static + Send + Sync + RefUnwindSafe,
|
||||||
{
|
{
|
||||||
|
@ -41,10 +45,10 @@ impl RootDatabase {
|
||||||
|
|
||||||
let workspace = Workspace::from_metadata(&db, workspace);
|
let workspace = Workspace::from_metadata(&db, workspace);
|
||||||
// Initialize the `Program` singleton
|
// Initialize the `Program` singleton
|
||||||
Program::from_settings(&db, settings);
|
Program::from_settings(&db, settings)?;
|
||||||
|
|
||||||
db.workspace = Some(workspace);
|
db.workspace = Some(workspace);
|
||||||
db
|
Ok(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn workspace(&self) -> Workspace {
|
pub fn workspace(&self) -> Workspace {
|
||||||
|
|
|
@ -305,7 +305,7 @@ enum AnyImportRef<'a> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use red_knot_python_semantic::{Program, PythonVersion, SearchPathSettings};
|
use red_knot_python_semantic::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
|
||||||
use ruff_db::files::system_path_to_file;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||||
|
|
||||||
|
@ -320,16 +320,23 @@ mod tests {
|
||||||
fn setup_db_with_root(src_root: SystemPathBuf) -> TestDb {
|
fn setup_db_with_root(src_root: SystemPathBuf) -> TestDb {
|
||||||
let db = TestDb::new();
|
let db = TestDb::new();
|
||||||
|
|
||||||
Program::new(
|
db.memory_file_system()
|
||||||
|
.create_directory_all(&src_root)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Program::from_settings(
|
||||||
&db,
|
&db,
|
||||||
PythonVersion::default(),
|
ProgramSettings {
|
||||||
SearchPathSettings {
|
target_version: PythonVersion::default(),
|
||||||
extra_paths: Vec::new(),
|
search_paths: SearchPathSettings {
|
||||||
src_root,
|
extra_paths: Vec::new(),
|
||||||
site_packages: vec![],
|
src_root,
|
||||||
custom_typeshed: None,
|
site_packages: vec![],
|
||||||
|
custom_typeshed: None,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.expect("Valid program settings");
|
||||||
|
|
||||||
db
|
db
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,7 @@ fn setup_db(workspace_root: SystemPathBuf) -> anyhow::Result<RootDatabase> {
|
||||||
target_version: PythonVersion::default(),
|
target_version: PythonVersion::default(),
|
||||||
search_paths,
|
search_paths,
|
||||||
};
|
};
|
||||||
let db = RootDatabase::new(workspace, settings, system);
|
RootDatabase::new(workspace, settings, system)
|
||||||
Ok(db)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that all snippets in testcorpus can be checked without panic
|
/// Test that all snippets in testcorpus can be checked without panic
|
||||||
|
|
|
@ -52,7 +52,7 @@ fn setup_case() -> Case {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut db = RootDatabase::new(metadata, settings, system);
|
let mut db = RootDatabase::new(metadata, settings, system).unwrap();
|
||||||
let parser = system_path_to_file(&db, parser_path).unwrap();
|
let parser = system_path_to_file(&db, parser_path).unwrap();
|
||||||
|
|
||||||
db.workspace().open_file(&mut db, parser);
|
db.workspace().open_file(&mut db, parser);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue