diff --git a/Cargo.lock b/Cargo.lock index a8e017fab7a9dd..c78d3597fdb6fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1966,7 +1966,6 @@ dependencies = [ "ruff_text_size", "rustc-hash 2.0.0", "salsa", - "thiserror", "tracing", ] diff --git a/crates/red_knot/tests/file_watching.rs b/crates/red_knot/tests/file_watching.rs index 0a555e813b49ca..f08f5c3e52dfe2 100644 --- a/crates/red_knot/tests/file_watching.rs +++ b/crates/red_knot/tests/file_watching.rs @@ -110,10 +110,10 @@ impl TestCase { ) -> anyhow::Result<()> { let program = Program::get(self.db()); - let new_settings = configuration.to_settings(self.db.workspace().root(&self.db)); - self.configuration.search_paths = configuration; + self.configuration.search_paths = configuration.clone(); + let new_settings = configuration.into_settings(self.db.workspace().root(&self.db)); - program.update_search_paths(&mut self.db, new_settings)?; + program.update_search_paths(&mut self.db, &new_settings)?; if let Some(watcher) = &mut self.watcher { watcher.update(&self.db); diff --git a/crates/red_knot_python_semantic/src/module_resolver/path.rs b/crates/red_knot_python_semantic/src/module_resolver/path.rs index fd8497771e2b5b..ae3e57962cf859 100644 --- a/crates/red_knot_python_semantic/src/module_resolver/path.rs +++ b/crates/red_knot_python_semantic/src/module_resolver/path.rs @@ -324,7 +324,10 @@ impl fmt::Display for SearchPathValidationError { write!(f, "The directory at {path} has no `stdlib/` subdirectory") } Self::FailedToReadVersionsFile { path, error } => { - write!(f, "Failed to read the '{path}' file: {error}") + write!( + f, + "Failed to read the custom typeshed versions file '{path}': {error}" + ) } Self::VersionsParseError(underlying_error) => underlying_error.fmt(f), SearchPathValidationError::SitePackagesDiscovery(error) => { diff --git a/crates/red_knot_python_semantic/src/module_resolver/resolver.rs b/crates/red_knot_python_semantic/src/module_resolver/resolver.rs index 15f4d560d662de..368b0010ca8f4d 100644 --- a/crates/red_knot_python_semantic/src/module_resolver/resolver.rs +++ b/crates/red_knot_python_semantic/src/module_resolver/resolver.rs @@ -1,7 +1,7 @@ +use rustc_hash::{FxBuildHasher, FxHashSet}; use std::borrow::Cow; use std::iter::FusedIterator; - -use rustc_hash::{FxBuildHasher, FxHashSet}; +use std::ops::Deref; use ruff_db::files::{File, FilePath, FileRootKind}; use ruff_db::system::{DirectoryEntry, System, SystemPath, SystemPathBuf}; @@ -136,7 +136,7 @@ pub(crate) struct SearchPaths { /// for the first `site-packages` path site_packages: Vec, - typeshed_versions: Cow<'static, TypeshedVersions>, + typeshed_versions: Typeshed, } impl SearchPaths { @@ -148,10 +148,12 @@ impl SearchPaths { /// [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, + settings: &SearchPathSettings, ) -> Result { - fn canonicalize(path: SystemPathBuf, system: &dyn System) -> SystemPathBuf { - system.canonicalize_path(&path).unwrap_or(path) + fn canonicalize(path: &SystemPath, system: &dyn System) -> SystemPathBuf { + system + .canonicalize_path(path) + .unwrap_or_else(|_| path.to_path_buf()) } let SearchPathSettings { @@ -175,7 +177,7 @@ impl SearchPaths { } tracing::debug!("Adding search path '{src_root}'"); - static_paths.push(SearchPath::first_party(system, src_root)?); + static_paths.push(SearchPath::first_party(system, src_root.to_path_buf())?); let (typeshed_versions, stdlib_path) = if let Some(custom_typeshed) = custom_typeshed { let custom_typeshed = canonicalize(custom_typeshed, system); @@ -200,11 +202,11 @@ impl SearchPaths { let search_path = SearchPath::custom_stdlib(db, &custom_typeshed)?; - (Cow::Owned(parsed), search_path) + (Typeshed::Custom(parsed), search_path) } else { tracing::debug!("Using vendored stdlib"); ( - Cow::Borrowed(vendored_typeshed_versions()), + Typeshed::Vendored(vendored_typeshed_versions()), SearchPath::vendored_stdlib(), ) }; @@ -214,14 +216,15 @@ impl SearchPaths { let site_packages_paths = match site_packages_paths { SitePackages::Derived { venv_path } => VirtualEnvironment::new(venv_path, system) .and_then(|venv| venv.site_packages_directories(system))?, - SitePackages::Known(paths) => paths, + SitePackages::Known(paths) => paths + .iter() + .map(|path| canonicalize(path, system)) + .collect(), }; let mut site_packages: Vec<_> = Vec::with_capacity(site_packages_paths.len()); for path in site_packages_paths { - let path = canonicalize(path, system); - tracing::debug!("Adding site-package path '{path}'"); files.try_add_root(db.upcast(), &path, FileRootKind::LibrarySearchPath); site_packages.push(SearchPath::site_packages(system, path)?); @@ -276,6 +279,23 @@ impl SearchPaths { } } +#[derive(Debug, PartialEq, Eq)] +enum Typeshed { + Vendored(&'static TypeshedVersions), + Custom(TypeshedVersions), +} + +impl Deref for Typeshed { + type Target = TypeshedVersions; + + fn deref(&self) -> &Self::Target { + match self { + Typeshed::Vendored(versions) => versions, + Typeshed::Custom(versions) => versions, + } + } +} + /// Collect all dynamic search paths. For each `site-packages` path: /// - Collect that `site-packages` path /// - Collect any search paths listed in `.pth` files in that `site-packages` directory @@ -1254,7 +1274,7 @@ mod tests { Program::from_settings( &db, - ProgramSettings { + &ProgramSettings { target_version: PythonVersion::PY38, search_paths: SearchPathSettings { extra_paths: vec![], @@ -1759,7 +1779,7 @@ not_a_directory Program::from_settings( &db, - ProgramSettings { + &ProgramSettings { target_version: PythonVersion::default(), search_paths: SearchPathSettings { extra_paths: vec![], diff --git a/crates/red_knot_python_semantic/src/module_resolver/testing.rs b/crates/red_knot_python_semantic/src/module_resolver/testing.rs index bce01b51eebaf5..0cf486ab6adbe4 100644 --- a/crates/red_knot_python_semantic/src/module_resolver/testing.rs +++ b/crates/red_knot_python_semantic/src/module_resolver/testing.rs @@ -225,7 +225,7 @@ impl TestCaseBuilder { Program::from_settings( &db, - ProgramSettings { + &ProgramSettings { target_version, search_paths: SearchPathSettings { extra_paths: vec![], @@ -281,10 +281,9 @@ impl TestCaseBuilder { Program::from_settings( &db, - ProgramSettings { + &ProgramSettings { target_version, search_paths: SearchPathSettings { - src_root: src.clone(), site_packages: SitePackages::Known(vec![site_packages.clone()]), ..SearchPathSettings::new(src.clone()) }, diff --git a/crates/red_knot_python_semantic/src/module_resolver/typeshed/versions.rs b/crates/red_knot_python_semantic/src/module_resolver/typeshed/versions.rs index 5ca40bc63ca0c7..61d007457fe3a8 100644 --- a/crates/red_knot_python_semantic/src/module_resolver/typeshed/versions.rs +++ b/crates/red_knot_python_semantic/src/module_resolver/typeshed/versions.rs @@ -105,7 +105,7 @@ impl fmt::Display for TypeshedVersionsParseErrorKind { } } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq)] pub(crate) struct TypeshedVersions(FxHashMap); impl TypeshedVersions { diff --git a/crates/red_knot_python_semantic/src/program.rs b/crates/red_knot_python_semantic/src/program.rs index cb78e1786578e9..94b1df4a89d24e 100644 --- a/crates/red_knot_python_semantic/src/program.rs +++ b/crates/red_knot_python_semantic/src/program.rs @@ -17,7 +17,7 @@ pub struct Program { } impl Program { - pub fn from_settings(db: &dyn Db, settings: ProgramSettings) -> anyhow::Result { + pub fn from_settings(db: &dyn Db, settings: &ProgramSettings) -> anyhow::Result { let ProgramSettings { target_version, search_paths, @@ -36,7 +36,7 @@ impl Program { pub fn update_search_paths( self, db: &mut dyn Db, - search_path_settings: SearchPathSettings, + search_path_settings: &SearchPathSettings, ) -> anyhow::Result<()> { let search_paths = SearchPaths::from_settings(db, search_path_settings)?; @@ -53,7 +53,7 @@ impl Program { } } -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct ProgramSettings { pub target_version: PythonVersion, pub search_paths: SearchPathSettings, diff --git a/crates/red_knot_python_semantic/src/semantic_model.rs b/crates/red_knot_python_semantic/src/semantic_model.rs index 73528c1deb2978..26b80509d4f0f5 100644 --- a/crates/red_knot_python_semantic/src/semantic_model.rs +++ b/crates/red_knot_python_semantic/src/semantic_model.rs @@ -189,7 +189,7 @@ mod tests { Program::from_settings( &db, - ProgramSettings { + &ProgramSettings { target_version: PythonVersion::default(), search_paths: SearchPathSettings::new(SystemPathBuf::from("/src")), }, diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 9a11ead1cf729f..494e7fb53e6ae6 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1599,7 +1599,7 @@ mod tests { Program::from_settings( &db, - ProgramSettings { + &ProgramSettings { target_version: PythonVersion::default(), search_paths: SearchPathSettings::new(src_root), }, @@ -1621,7 +1621,7 @@ mod tests { Program::from_settings( &db, - ProgramSettings { + &ProgramSettings { target_version: PythonVersion::default(), search_paths: SearchPathSettings { custom_typeshed: Some(SystemPathBuf::from(typeshed)), diff --git a/crates/red_knot_workspace/Cargo.toml b/crates/red_knot_workspace/Cargo.toml index ba7c8bfaa3ca3d..804b3e9bad3a2a 100644 --- a/crates/red_knot_workspace/Cargo.toml +++ b/crates/red_knot_workspace/Cargo.toml @@ -24,7 +24,6 @@ crossbeam = { workspace = true } notify = { workspace = true } rustc-hash = { workspace = true } salsa = { workspace = true } -thiserror = { workspace = true } tracing = { workspace = true } [dev-dependencies] diff --git a/crates/red_knot_workspace/src/db.rs b/crates/red_knot_workspace/src/db.rs index 2183cab9a4527e..764927af7ecdb2 100644 --- a/crates/red_knot_workspace/src/db.rs +++ b/crates/red_knot_workspace/src/db.rs @@ -31,8 +31,6 @@ impl RootDatabase { where S: System + 'static + Send + Sync + RefUnwindSafe, { - let program_settings = workspace.to_program_settings(workspace.root()); - let mut db = Self { workspace: None, storage: salsa::Storage::default(), @@ -40,11 +38,11 @@ impl RootDatabase { system: Arc::new(system), }; - let workspace = Workspace::from_metadata(&db, workspace); // Initialize the `Program` singleton - Program::from_settings(&db, program_settings)?; + Program::from_settings(&db, workspace.settings().program())?; + + db.workspace = Some(Workspace::from_metadata(&db, workspace)); - db.workspace = Some(workspace); Ok(db) } diff --git a/crates/red_knot_workspace/src/db/changes.rs b/crates/red_knot_workspace/src/db/changes.rs index f98dd7c077a941..998fa090ee728d 100644 --- a/crates/red_knot_workspace/src/db/changes.rs +++ b/crates/red_knot_workspace/src/db/changes.rs @@ -156,11 +156,9 @@ impl RootDatabase { return; } else if custom_stdlib_change { - let search_path_configuration = workspace.search_path_configuration(self); - - let search_path_settings = search_path_configuration.to_settings(&workspace_path); - if let Err(error) = program.update_search_paths(self, search_path_settings) { - tracing::error!("Failed to apply to set the new search paths: {error}"); + let search_paths = workspace.search_path_settings(self).clone(); + if let Err(error) = program.update_search_paths(self, &search_paths) { + tracing::error!("Failed to set the new search paths: {error}"); } } diff --git a/crates/red_knot_workspace/src/lint.rs b/crates/red_knot_workspace/src/lint.rs index 2c9e3d2659679d..5814997fc1df5b 100644 --- a/crates/red_knot_workspace/src/lint.rs +++ b/crates/red_knot_workspace/src/lint.rs @@ -374,7 +374,7 @@ mod tests { Program::from_settings( &db, - ProgramSettings { + &ProgramSettings { target_version: PythonVersion::default(), search_paths: SearchPathSettings::new(src_root), }, diff --git a/crates/red_knot_workspace/src/workspace.rs b/crates/red_knot_workspace/src/workspace.rs index ceee86642786d1..5a68cb284ed149 100644 --- a/crates/red_knot_workspace/src/workspace.rs +++ b/crates/red_knot_workspace/src/workspace.rs @@ -4,6 +4,7 @@ use rustc_hash::{FxBuildHasher, FxHashSet}; use salsa::{Durability, Setter as _}; pub use metadata::{PackageMetadata, WorkspaceMetadata}; +use red_knot_python_semantic::SearchPathSettings; use ruff_db::source::{source_text, SourceDiagnostic}; use ruff_db::{ files::{system_path_to_file, File}, @@ -12,7 +13,6 @@ use ruff_db::{ use ruff_python_ast::{name::Name, PySourceType}; use crate::workspace::files::{Index, IndexedFiles, PackageFiles}; -use crate::workspace::settings::SearchPathConfiguration; use crate::{ db::Db, lint::{lint_semantic, lint_syntax, Diagnostics}, @@ -84,7 +84,7 @@ pub struct Workspace { /// The unresolved search path configuration. #[return_ref] - pub search_path_configuration: SearchPathConfiguration, + pub search_path_settings: SearchPathSettings, } /// A first-party package in a workspace. @@ -113,10 +113,14 @@ impl Workspace { packages.insert(package.root.clone(), Package::from_metadata(db, package)); } - Workspace::builder(metadata.root, packages, metadata.configuration.search_paths) - .durability(Durability::MEDIUM) - .open_fileset_durability(Durability::LOW) - .new(db) + Workspace::builder( + metadata.root, + packages, + metadata.settings.program.search_paths, + ) + .durability(Durability::MEDIUM) + .open_fileset_durability(Durability::LOW) + .new(db) } pub fn root(self, db: &dyn Db) -> &SystemPath { @@ -147,9 +151,9 @@ impl Workspace { new_packages.insert(path, package); } - if &metadata.configuration.search_paths != self.search_path_configuration(db) { - self.set_search_path_configuration(db) - .to(metadata.configuration.search_paths); + if &metadata.settings.program.search_paths != self.search_path_settings(db) { + self.set_search_path_settings(db) + .to(metadata.settings.program.search_paths); } self.set_package_tree(db) diff --git a/crates/red_knot_workspace/src/workspace/metadata.rs b/crates/red_knot_workspace/src/workspace/metadata.rs index f47cdf6387a39f..a56f93e19b745c 100644 --- a/crates/red_knot_workspace/src/workspace/metadata.rs +++ b/crates/red_knot_workspace/src/workspace/metadata.rs @@ -1,5 +1,4 @@ -use crate::workspace::settings::Configuration; -use red_knot_python_semantic::ProgramSettings; +use crate::workspace::settings::{Configuration, WorkspaceSettings}; use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_python_ast::name::Name; @@ -10,7 +9,7 @@ pub struct WorkspaceMetadata { /// The (first-party) packages in this workspace. pub(super) packages: Vec, - pub(super) configuration: Configuration, + pub(super) settings: WorkspaceSettings, } /// A first-party package in a workspace. @@ -46,18 +45,20 @@ impl WorkspaceMetadata { root: root.clone(), }; + // TODO: Load the configuration from disk. let mut configuration = Configuration::default(); if let Some(base_configuration) = base_configuration { configuration.extend(base_configuration); } - // TODO store settings instead of configuration? + // TODO: Respect the package configurations when resolving settings (e.g. for the target version). + let settings = configuration.into_workspace_settings(&root); let workspace = WorkspaceMetadata { root, packages: vec![package], - configuration, + settings, }; Ok(workspace) @@ -71,18 +72,8 @@ impl WorkspaceMetadata { &self.packages } - pub fn configuration(&self) -> &Configuration { - &self.configuration - } - - pub fn to_program_settings(&self, workspace_root: &SystemPath) -> ProgramSettings { - let search_path_settings = self.configuration.search_paths.to_settings(workspace_root); - - ProgramSettings { - // TODO: Resolve the target version across all packages. - target_version: self.configuration.target_version.unwrap_or_default(), - search_paths: search_path_settings, - } + pub fn settings(&self) -> &WorkspaceSettings { + &self.settings } } diff --git a/crates/red_knot_workspace/src/workspace/settings.rs b/crates/red_knot_workspace/src/workspace/settings.rs index c39bc741d8d2d1..38a633b07dcfe2 100644 --- a/crates/red_knot_workspace/src/workspace/settings.rs +++ b/crates/red_knot_workspace/src/workspace/settings.rs @@ -1,6 +1,21 @@ -use red_knot_python_semantic::{PythonVersion, SearchPathSettings, SitePackages}; +use red_knot_python_semantic::{ProgramSettings, PythonVersion, SearchPathSettings, SitePackages}; use ruff_db::system::{SystemPath, SystemPathBuf}; +/// The resolved configurations. +/// +/// The main difference to [`Configuration`] is that default values are filled in. +#[derive(Debug, Clone)] +pub struct WorkspaceSettings { + pub(super) program: ProgramSettings, +} + +impl WorkspaceSettings { + pub fn program(&self) -> &ProgramSettings { + &self.program + } +} + +/// The configuration for the workspace or a package. #[derive(Debug, Default, Clone)] pub struct Configuration { pub target_version: Option, @@ -13,6 +28,15 @@ impl Configuration { self.target_version = self.target_version.or(with.target_version); self.search_paths.extend(with.search_paths); } + + pub fn into_workspace_settings(self, workspace_root: &SystemPath) -> WorkspaceSettings { + WorkspaceSettings { + program: ProgramSettings { + target_version: self.target_version.unwrap_or_default(), + search_paths: self.search_paths.into_settings(workspace_root), + }, + } + } } #[derive(Debug, Default, Clone, Eq, PartialEq)] @@ -35,19 +59,15 @@ pub struct SearchPathConfiguration { } impl SearchPathConfiguration { - pub fn to_settings(&self, workspace_root: &SystemPath) -> SearchPathSettings { - let site_packages = self - .site_packages - .clone() - .unwrap_or(SitePackages::Known(vec![])); + pub fn into_settings(self, workspace_root: &SystemPath) -> SearchPathSettings { + let site_packages = self.site_packages.unwrap_or(SitePackages::Known(vec![])); SearchPathSettings { - extra_paths: self.extra_paths.clone().unwrap_or_default(), + extra_paths: self.extra_paths.unwrap_or_default(), src_root: self .src_root - .clone() .unwrap_or_else(|| workspace_root.to_path_buf()), - custom_typeshed: self.custom_typeshed.clone(), + custom_typeshed: self.custom_typeshed, site_packages, } }