diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index cd3a005f21fb91..50effa99bb9dcf 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -92,7 +92,7 @@ fn setup_tomllib_case() -> Case { ..Options::default() }); - let mut db = ProjectDatabase::new(metadata, system).unwrap(); + let mut db = ProjectDatabase::fallible(metadata, system).unwrap(); let mut tomllib_files = FxHashSet::default(); let mut re: Option = None; @@ -239,7 +239,7 @@ fn setup_micro_case(code: &str) -> Case { ..Options::default() }); - let mut db = ProjectDatabase::new(metadata, system).unwrap(); + let mut db = ProjectDatabase::fallible(metadata, system).unwrap(); let file = system_path_to_file(&db, SystemPathBuf::from(file_path)).unwrap(); db.set_check_mode(CheckMode::OpenFiles); @@ -812,7 +812,7 @@ impl<'a> ProjectBenchmark<'a> { ..Options::default() }); - let mut db = ProjectDatabase::new(metadata, system).unwrap(); + let mut db = ProjectDatabase::fallible(metadata, system).unwrap(); db.project().set_included_paths( &mut db, diff --git a/crates/ruff_benchmark/benches/ty_walltime.rs b/crates/ruff_benchmark/benches/ty_walltime.rs index d367a3020e9fe7..290bcfafa725c1 100644 --- a/crates/ruff_benchmark/benches/ty_walltime.rs +++ b/crates/ruff_benchmark/benches/ty_walltime.rs @@ -51,7 +51,7 @@ impl<'a> Benchmark<'a> { ..Options::default() }); - let mut db = ProjectDatabase::new(metadata, system).unwrap(); + let mut db = ProjectDatabase::fallible(metadata, system).unwrap(); db.project().set_included_paths( &mut db, diff --git a/crates/ruff_graph/src/db.rs b/crates/ruff_graph/src/db.rs index 97cb28a121eac6..cfd7ed0f994c9f 100644 --- a/crates/ruff_graph/src/db.rs +++ b/crates/ruff_graph/src/db.rs @@ -7,7 +7,7 @@ use ruff_db::files::Files; use ruff_db::system::{OsSystem, System, SystemPathBuf}; use ruff_db::vendored::{VendoredFileSystem, VendoredFileSystemBuilder}; use ruff_python_ast::PythonVersion; -use ty_module_resolver::{SearchPathSettings, SearchPaths}; +use ty_module_resolver::{FallibleStrategy, SearchPathSettings, SearchPaths}; use ty_site_packages::{PythonEnvironment, SysPrefixPathOrigin}; static EMPTY_VENDORED: std::sync::LazyLock = std::sync::LazyLock::new(|| { @@ -45,7 +45,7 @@ impl ModuleDb { .into_vec(); } let search_paths = search_path_settings - .to_search_paths(&system, &EMPTY_VENDORED) + .to_search_paths(&system, &EMPTY_VENDORED, &FallibleStrategy) .context("Invalid search path settings")?; let db = Self { diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index 03ca295dd0fab5..9245fe383cc12c 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -146,7 +146,7 @@ fn run_check(args: CheckCommand) -> anyhow::Result { let project_options_overrides = ProjectOptionsOverrides::new(config_file, args.into_options()); project_metadata.apply_overrides(&project_options_overrides); - let mut db = ProjectDatabase::new(project_metadata, system)?; + let mut db = ProjectDatabase::fallible(project_metadata, system)?; let project = db.project(); project.set_verbose(&mut db, verbosity >= VerbosityLevel::Verbose); diff --git a/crates/ty/tests/file_watching.rs b/crates/ty/tests/file_watching.rs index 098a397acb7678..f3ad976e2aca83 100644 --- a/crates/ty/tests/file_watching.rs +++ b/crates/ty/tests/file_watching.rs @@ -435,7 +435,7 @@ where } } - let mut db = ProjectDatabase::new(project, system)?; + let mut db = ProjectDatabase::fallible(project, system)?; if let Some(included_paths) = included_paths { db.project().set_included_paths(&mut db, included_paths); diff --git a/crates/ty_completion_bench/src/main.rs b/crates/ty_completion_bench/src/main.rs index 7e3901420b1159..cd4839ea617951 100644 --- a/crates/ty_completion_bench/src/main.rs +++ b/crates/ty_completion_bench/src/main.rs @@ -99,7 +99,7 @@ fn main() -> anyhow::Result { ..Options::default() }); project_metadata.apply_configuration_files(&system)?; - let db = ProjectDatabase::new(project_metadata, system)?; + let db = ProjectDatabase::fallible(project_metadata, system)?; let start = std::time::Instant::now(); let mut completions = get_completions(&db, &args.file, offset)?; diff --git a/crates/ty_completion_eval/src/main.rs b/crates/ty_completion_eval/src/main.rs index 73dbaa989ca7ec..ff0155e8f539bf 100644 --- a/crates/ty_completion_eval/src/main.rs +++ b/crates/ty_completion_eval/src/main.rs @@ -290,7 +290,7 @@ impl Task { ..Options::default() }); project_metadata.apply_configuration_files(&system)?; - let db = ProjectDatabase::new(project_metadata, system)?; + let db = ProjectDatabase::fallible(project_metadata, system)?; Ok(Task { db, dir: project_path.to_path_buf(), diff --git a/crates/ty_ide/src/lib.rs b/crates/ty_ide/src/lib.rs index 7bca400d2f9271..6250a020f88f06 100644 --- a/crates/ty_ide/src/lib.rs +++ b/crates/ty_ide/src/lib.rs @@ -562,7 +562,7 @@ mod tests { impl SitePackagesCursorTestBuilder { pub(super) fn build(&self) -> CursorTest { use ty_module_resolver::SearchPathSettings; - use ty_python_semantic::{Program, ProgramSettings}; + use ty_python_semantic::{FallibleStrategy, Program, ProgramSettings}; let project_root = SystemPathBuf::from("/src"); let site_packages_path = SystemPathBuf::from("/site-packages"); @@ -593,7 +593,7 @@ mod tests { site_packages_paths: vec![site_packages_path.clone()], ..SearchPathSettings::empty() } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("valid search paths"); Program::from_settings( diff --git a/crates/ty_module_resolver/Cargo.toml b/crates/ty_module_resolver/Cargo.toml index dd9443c8aa2e8f..a97e9e7b8ec956 100644 --- a/crates/ty_module_resolver/Cargo.toml +++ b/crates/ty_module_resolver/Cargo.toml @@ -16,6 +16,7 @@ ruff_memory_usage = { workspace = true } ruff_python_ast = { workspace = true, features = ["salsa"] } ruff_python_stdlib = { workspace = true } +anyhow = { workspace = true } camino = { workspace = true } compact_str = { workspace = true } get-size2 = { workspace = true } diff --git a/crates/ty_module_resolver/src/lib.rs b/crates/ty_module_resolver/src/lib.rs index 42b6e50bbdce43..3006abe1f2208c 100644 --- a/crates/ty_module_resolver/src/lib.rs +++ b/crates/ty_module_resolver/src/lib.rs @@ -11,7 +11,8 @@ pub use resolve::{ SearchPaths, file_to_module, resolve_module, resolve_module_confident, resolve_real_module, resolve_real_module_confident, resolve_real_shadowable_module, }; -pub use settings::{MisconfigurationMode, SearchPathSettings, SearchPathSettingsError}; +pub use settings::{SearchPathSettings, SearchPathSettingsError}; +pub use strategy::{FallibleStrategy, MisconfigurationStrategy, UseDefaultStrategy}; pub use typeshed::{ PyVersionRange, TypeshedVersions, TypeshedVersionsParseError, vendored_typeshed_versions, }; @@ -28,6 +29,7 @@ mod module_name; mod path; mod resolve; mod settings; +mod strategy; mod typeshed; #[cfg(test)] diff --git a/crates/ty_module_resolver/src/list.rs b/crates/ty_module_resolver/src/list.rs index 1770d2a5367cf4..df6f56a5baa1c2 100644 --- a/crates/ty_module_resolver/src/list.rs +++ b/crates/ty_module_resolver/src/list.rs @@ -387,6 +387,7 @@ mod tests { ModuleResolveMode, ModuleResolveModeIngredient, dynamic_resolution_paths, }; use crate::settings::SearchPathSettings; + use crate::strategy::FallibleStrategy; use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder}; use super::list_modules; @@ -966,7 +967,7 @@ mod tests { db.set_search_paths( settings - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"), ); @@ -1473,7 +1474,7 @@ not_a_directory db.set_search_paths( settings - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"), ); @@ -1524,7 +1525,7 @@ not_a_directory db.set_search_paths( settings - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"), ); @@ -1604,7 +1605,7 @@ not_a_directory let settings = SearchPathSettings::new(vec![src]); let search_paths = settings - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("valid search path settings"); db.set_search_paths(search_paths); @@ -1644,7 +1645,7 @@ not_a_directory }; db.set_search_paths( settings - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .unwrap(), ); @@ -1792,7 +1793,7 @@ not_a_directory let settings = SearchPathSettings::new(vec![project_directory]); let search_paths = settings - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"); db.set_search_paths(search_paths); diff --git a/crates/ty_module_resolver/src/resolve.rs b/crates/ty_module_resolver/src/resolve.rs index 0532222577d471..db7c29bd8ca34a 100644 --- a/crates/ty_module_resolver/src/resolve.rs +++ b/crates/ty_module_resolver/src/resolve.rs @@ -51,8 +51,9 @@ use crate::db::Db; use crate::module::{Module, ModuleKind}; use crate::module_name::ModuleName; use crate::path::{ModulePath, SearchPath, SystemOrVendoredPathRef}; +use crate::strategy::MisconfigurationStrategy; use crate::typeshed::{TypeshedVersions, vendored_typeshed_versions}; -use crate::{MisconfigurationMode, SearchPathSettings, SearchPathSettingsError}; +use crate::{SearchPathSettings, SearchPathSettingsError}; /// Resolves a module name to a module. pub fn resolve_module<'db>( @@ -552,11 +553,12 @@ impl SearchPaths { /// This method also implements the typing spec's [module resolution order]. /// /// [module resolution order]: https://typing.python.org/en/latest/spec/distributing.html#import-resolution-ordering - pub fn from_settings( + pub fn from_settings( settings: &SearchPathSettings, system: &dyn System, vendored: &VendoredFileSystem, - ) -> Result { + strategy: &Strategy, + ) -> Result> { fn canonicalize(path: &SystemPath, system: &dyn System) -> SystemPathBuf { system .canonicalize_path(path) @@ -569,7 +571,6 @@ impl SearchPaths { custom_typeshed: typeshed, site_packages_paths, real_stdlib_path, - misconfiguration_mode, } = settings; let mut static_paths = vec![]; @@ -578,30 +579,25 @@ impl SearchPaths { let path = canonicalize(path, system); tracing::debug!("Adding extra search-path `{path}`"); - match SearchPath::extra(system, path) { - Ok(path) => static_paths.push(path), - Err(err) => { - if *misconfiguration_mode == MisconfigurationMode::UseDefault { - tracing::debug!("Skipping invalid extra search-path: {err}"); - } else { - return Err(err.into()); - } - } - } + let path = strategy.fallback_opt( + SearchPath::extra(system, path).map_err(SearchPathSettingsError::from), + |err| { + tracing::debug!("Skipping invalid extra search-path: {err}"); + }, + )?; + static_paths.extend(path); } for src_root in src_roots { tracing::debug!("Adding first-party search path `{src_root}`"); - match SearchPath::first_party(system, src_root.to_path_buf()) { - Ok(path) => static_paths.push(path), - Err(err) => { - if *misconfiguration_mode == MisconfigurationMode::UseDefault { - tracing::debug!("Skipping invalid first-party search-path: {err}"); - } else { - return Err(err.into()); - } - } - } + let path = strategy.fallback_opt( + SearchPath::first_party(system, src_root.to_path_buf()) + .map_err(SearchPathSettingsError::from), + |err| { + tracing::debug!("Skipping invalid first-party search-path: {err}"); + }, + )?; + static_paths.extend(path); } let (typeshed_versions, stdlib_path) = if let Some(typeshed) = typeshed { @@ -619,20 +615,13 @@ impl SearchPaths { .and_then(|versions_content| Ok(versions_content.parse()?)) .and_then(|parsed| Ok((parsed, SearchPath::custom_stdlib(system, &typeshed)?))); - match results { - Ok(results) => results, - Err(err) => { - if settings.misconfiguration_mode == MisconfigurationMode::UseDefault { - tracing::debug!("Skipping custom-stdlib search-path: {err}"); - ( - vendored_typeshed_versions(vendored), - SearchPath::vendored_stdlib(), - ) - } else { - return Err(err); - } - } - } + strategy.fallback(results, |err| { + tracing::debug!("Skipping custom-stdlib search-path: {err}"); + ( + vendored_typeshed_versions(vendored), + SearchPath::vendored_stdlib(), + ) + })? } else { tracing::debug!("Using vendored stdlib"); ( @@ -642,17 +631,13 @@ impl SearchPaths { }; let real_stdlib_path = if let Some(path) = real_stdlib_path { - match SearchPath::real_stdlib(system, path.clone()) { - Ok(path) => Some(path), - Err(err) => { - if *misconfiguration_mode == MisconfigurationMode::UseDefault { - tracing::debug!("Skipping invalid real-stdlib search-path: {err}"); - None - } else { - return Err(err.into()); - } - } - } + strategy.fallback_opt( + SearchPath::real_stdlib(system, path.clone()) + .map_err(SearchPathSettingsError::from), + |err| { + tracing::debug!("Skipping invalid real-stdlib search-path: {err}"); + }, + )? } else { None }; @@ -661,16 +646,14 @@ impl SearchPaths { for path in site_packages_paths { tracing::debug!("Adding site-packages search path `{path}`"); - match SearchPath::site_packages(system, path.clone()) { - Ok(path) => site_packages.push(path), - Err(err) => { - if settings.misconfiguration_mode == MisconfigurationMode::UseDefault { - tracing::debug!("Skipping invalid site-packages search-path: {err}"); - } else { - return Err(err.into()); - } - } - } + let path = strategy.fallback_opt( + SearchPath::site_packages(system, path.clone()) + .map_err(SearchPathSettingsError::from), + |err| { + tracing::debug!("Skipping invalid site-packages search-path: {err}"); + }, + )?; + site_packages.extend(path); } // TODO vendor typeshed's third-party stubs as well as the stdlib and @@ -1827,6 +1810,7 @@ mod tests { use crate::db::tests::TestDb; use crate::module::ModuleKind; use crate::module_name::ModuleName; + use crate::strategy::FallibleStrategy; use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder}; use super::*; @@ -2387,7 +2371,7 @@ mod tests { site_packages_paths: vec![site_packages], ..SearchPathSettings::empty() } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"), ); @@ -2926,7 +2910,7 @@ not_a_directory site_packages_paths: vec![venv_site_packages, system_site_packages], ..SearchPathSettings::new(vec![SystemPathBuf::from("/src")]) } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"), ); @@ -2994,7 +2978,7 @@ not_a_directory db.set_search_paths( SearchPathSettings::new(vec![src]) - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"), ); @@ -3032,7 +3016,7 @@ not_a_directory site_packages_paths: vec![site_packages.clone()], ..SearchPathSettings::empty() } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"); db.set_search_paths(search_paths); diff --git a/crates/ty_module_resolver/src/settings.rs b/crates/ty_module_resolver/src/settings.rs index f4e9236c1259d0..2cab5e60d76f53 100644 --- a/crates/ty_module_resolver/src/settings.rs +++ b/crates/ty_module_resolver/src/settings.rs @@ -5,21 +5,9 @@ use ruff_db::vendored::VendoredFileSystem; use crate::path::SearchPathError; use crate::resolve::SearchPaths; +use crate::strategy::MisconfigurationStrategy; use crate::typeshed::TypeshedVersionsParseError; -/// How to handle apparent misconfiguration -#[derive(PartialEq, Eq, Debug, Copy, Clone, Default, get_size2::GetSize)] -pub enum MisconfigurationMode { - /// Settings Failure Is Not An Error. - /// - /// This is used by the default database, which we are incentivized to make infallible, - /// while still trying to "do our best" to set things up properly where we can. - UseDefault, - /// Settings Failure Is An Error. - #[default] - Fail, -} - /// Configures the search paths for module resolution. #[derive(Eq, PartialEq, Debug, Clone)] pub struct SearchPathSettings { @@ -44,9 +32,6 @@ pub struct SearchPathSettings { /// We should ideally only ever use this for things like goto-definition, /// where typeshed isn't the right answer. pub real_stdlib_path: Option, - - /// How to handle apparent misconfiguration - pub misconfiguration_mode: MisconfigurationMode, } impl SearchPathSettings { @@ -64,16 +49,16 @@ impl SearchPathSettings { custom_typeshed: None, site_packages_paths: vec![], real_stdlib_path: None, - misconfiguration_mode: MisconfigurationMode::Fail, } } - pub fn to_search_paths( + pub fn to_search_paths( &self, system: &dyn System, vendored: &VendoredFileSystem, - ) -> Result { - SearchPaths::from_settings(self, system, vendored) + strategy: &Strategy, + ) -> Result> { + SearchPaths::from_settings(self, system, vendored, strategy) } } diff --git a/crates/ty_module_resolver/src/strategy.rs b/crates/ty_module_resolver/src/strategy.rs new file mode 100644 index 00000000000000..8a471e0f309c87 --- /dev/null +++ b/crates/ty_module_resolver/src/strategy.rs @@ -0,0 +1,186 @@ +use std::convert::Infallible; + +/// Generic handling of two possible approaches to an Error: +/// +/// * [`FallibleStrategy`]: The code should simply fail +/// * [`UseDefaultStrategy`]: The code should apply default values and never fail +/// +/// Any function that wants to be made generic over these approaches should be changed thusly. +/// +/// Old: +/// +/// ```ignore +/// fn do_thing() +/// -> Result +/// { +/// let x = something_fallible()?; +/// Ok(x) +/// } +/// ``` +/// +/// New: +/// +/// ```ignore +/// fn do_thing(strategy: &Strategy) +/// -> Result> +/// { +/// let x = strategy.fallback(something_fallible(), |err| { +/// tracing::debug!("Failed to get value: {err}"); +/// MyType::default() +/// })?; +/// Ok(x) +/// } +/// ``` +/// +/// The key trick is instead of returning `Result` your function should +/// return `Result>`. Which simplifies to: +/// +/// * [`FallibleStrategy`]: `Result` +/// * [`UseDefaultStrategy`]: `Result` ~= `T` +/// +/// Notably, if your function returns `Result>` you will +/// be *statically prevented* from returning an `Err` without going through +/// [`MisconfigurationStrategy::fallback`][] or [`MisconfigurationStrategy::fallback_opt`][] +/// which ensure you're handling both approaches (or you wrote an `unwrap` but +/// those standout far more than adding a new `?` to a function that must be able to Not Fail). +/// +/// Also, for any caller that passes in [`UseDefaultStrategy`], they will be able +/// to write `let Ok(val) = do_thing(&UseDefaultStrategy);` instead of having to +/// write an `unwrap()`. +pub trait MisconfigurationStrategy { + /// * [`FallibleStrategy`][]: `E` + /// * [`UseDefaultStrategy`][]: [`Infallible`] + type Error; + + /// Try to get the value out of a Result that we need to proceed. + /// + /// If [`UseDefaultStrategy`], on `Err` this will call `fallback_fn` to compute + /// a default value and always return `Ok`. + /// + /// If [`FallibleStrategy`] this is a no-op and will return the Result. + fn fallback( + &self, + result: Result, + fallback_fn: impl FnOnce(E) -> T, + ) -> Result>; + + /// Try to get the value out of a Result that we can do without. + /// + /// If [`UseDefaultStrategy`], this will call `fallback_fn` to report an issue + /// (i.e. you can invoke `tracing::debug!` or something) and then return `None`. + /// + /// If [`FallibleStrategy`] this is a no-op and will return the Result (but `Ok` => `Ok(Some)`). + fn fallback_opt( + &self, + result: Result, + fallback_fn: impl FnOnce(E), + ) -> Result, Self::Error>; + + /// Convenience to convert the inner `Error` to `anyhow::Error`. + fn to_anyhow( + &self, + result: Result>, + ) -> Result> + where + anyhow::Error: From; + + /// Convenience to map the inner `Error`. + fn map_err( + &self, + result: Result>, + map_err: impl FnOnce(E1) -> E2, + ) -> Result>; +} + +/// A [`MisconfigurationStrategy`] that refuses to *ever* return an `Err` +/// and instead substitutes default values or skips functionality. +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +pub struct UseDefaultStrategy; + +impl MisconfigurationStrategy for UseDefaultStrategy { + type Error = Infallible; + fn fallback( + &self, + result: Result, + fallback_fn: impl FnOnce(E) -> T, + ) -> Result> { + Ok(result.unwrap_or_else(fallback_fn)) + } + + fn fallback_opt( + &self, + result: Result, + fallback_fn: impl FnOnce(E), + ) -> Result, Self::Error> { + match result { + Ok(val) => Ok(Some(val)), + Err(e) => { + fallback_fn(e); + Ok(None) + } + } + } + + fn to_anyhow( + &self, + result: Result>, + ) -> Result> + where + anyhow::Error: From, + { + let Ok(val) = result; + Ok(val) + } + + fn map_err( + &self, + result: Result>, + _map_err: impl FnOnce(E1) -> E2, + ) -> Result> { + let Ok(val) = result; + Ok(val) + } +} + +/// A [`MisconfigurationStrategy`] that happily fails whenever +/// an important `Err` is encountered. +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +pub struct FallibleStrategy; + +impl MisconfigurationStrategy for FallibleStrategy { + type Error = E; + + fn fallback( + &self, + result: Result, + _fallback_fn: impl FnOnce(E) -> T, + ) -> Result> { + result + } + + fn fallback_opt( + &self, + result: Result, + _fallback_fn: impl FnOnce(E), + ) -> Result, Self::Error> { + result.map(Some) + } + + fn to_anyhow( + &self, + result: Result>, + ) -> Result> + where + anyhow::Error: From, + { + Ok(result?) + } + + fn map_err( + &self, + result: Result>, + map_err: impl FnOnce(E1) -> E2, + ) -> Result> { + result.map_err(map_err) + } +} diff --git a/crates/ty_module_resolver/src/testing.rs b/crates/ty_module_resolver/src/testing.rs index 08bd6e97659aa5..a468783aa28ea5 100644 --- a/crates/ty_module_resolver/src/testing.rs +++ b/crates/ty_module_resolver/src/testing.rs @@ -8,6 +8,7 @@ use ruff_python_ast::PythonVersion; use crate::db::tests::TestDb; use crate::settings::SearchPathSettings; +use crate::strategy::FallibleStrategy; /// A test case for the module resolver. /// @@ -253,7 +254,7 @@ impl TestCaseBuilder { site_packages_paths: vec![site_packages.clone()], ..SearchPathSettings::empty() } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"); db = db.with_search_paths(search_paths); @@ -327,7 +328,7 @@ impl TestCaseBuilder { site_packages_paths: vec![site_packages.clone()], ..SearchPathSettings::empty() } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"); db = db.with_search_paths(search_paths); diff --git a/crates/ty_project/src/db.rs b/crates/ty_project/src/db.rs index ba6299836ef811..129e4e0539a333 100644 --- a/crates/ty_project/src/db.rs +++ b/crates/ty_project/src/db.rs @@ -16,7 +16,10 @@ use ruff_db::vendored::VendoredFileSystem; use salsa::{Database, Event, Setter}; use ty_module_resolver::SearchPaths; use ty_python_semantic::lint::{LintRegistry, RuleSelection}; -use ty_python_semantic::{AnalysisSettings, Db as SemanticDb, Program}; +use ty_python_semantic::{ + AnalysisSettings, Db as SemanticDb, FallibleStrategy, MisconfigurationStrategy, Program, + UseDefaultStrategy, +}; mod changes; @@ -45,7 +48,28 @@ pub struct ProjectDatabase { } impl ProjectDatabase { - pub fn new(project_metadata: ProjectMetadata, system: S) -> anyhow::Result + /// Creates a new database, returning an error if the project metadata is misconfigured. + pub fn fallible(project_metadata: ProjectMetadata, system: S) -> anyhow::Result + where + S: System + 'static + Send + Sync + RefUnwindSafe, + { + Self::new(project_metadata, system, &FallibleStrategy) + } + + /// Creates a new database, substituting default values for any misconfigured settings. + pub fn use_defaults(project_metadata: ProjectMetadata, system: S) -> Self + where + S: System + 'static + Send + Sync + RefUnwindSafe, + { + let Ok(db) = Self::new(project_metadata, system, &UseDefaultStrategy); + db + } + + fn new( + project_metadata: ProjectMetadata, + system: S, + strategy: &Strategy, + ) -> Result> where S: System + 'static + Send + Sync + RefUnwindSafe, { @@ -73,13 +97,17 @@ impl ProjectDatabase { // we may want to have a dedicated method for this? // Initialize the `Program` singleton - let program_settings = project_metadata.to_program_settings(db.system(), db.vendored())?; + let program_settings = strategy.to_anyhow(project_metadata.to_program_settings( + db.system(), + db.vendored(), + strategy, + ))?; Program::from_settings(&db, program_settings); - db.project = Some( - Project::from_metadata(&db, project_metadata) - .map_err(|error| anyhow::anyhow!("{}", error.pretty(&db)))?, - ); + db.project = Some(strategy.map_err( + Project::from_metadata(&db, project_metadata, strategy), + |error| anyhow::anyhow!("{}", error.pretty(&db)), + )?); Ok(db) } @@ -546,7 +574,8 @@ pub(crate) mod tests { use ty_module_resolver::SearchPathSettings; use ty_python_semantic::lint::{LintRegistry, RuleSelection}; use ty_python_semantic::{ - AnalysisSettings, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, + AnalysisSettings, FallibleStrategy, Program, ProgramSettings, PythonPlatform, + PythonVersionWithSource, }; use crate::db::Db; @@ -583,7 +612,7 @@ pub(crate) mod tests { project: None, }; - let project = Project::from_metadata(&db, project).unwrap(); + let project = Project::from_metadata(&db, project, &FallibleStrategy).unwrap(); db.project = Some(project); db } @@ -599,7 +628,7 @@ pub(crate) mod tests { let root = self.project().root(self); let search_paths = SearchPathSettings::new(vec![root.to_path_buf()]) - .to_search_paths(self.system(), self.vendored()) + .to_search_paths(self.system(), self.vendored(), &FallibleStrategy) .expect("Valid search path settings"); Program::from_settings( diff --git a/crates/ty_project/src/db/changes.rs b/crates/ty_project/src/db/changes.rs index dcbed3581e6c36..3b1a3907255477 100644 --- a/crates/ty_project/src/db/changes.rs +++ b/crates/ty_project/src/db/changes.rs @@ -11,7 +11,7 @@ use ruff_db::files::{File, FileRootKind, Files}; use ruff_db::system::SystemPath; use rustc_hash::FxHashSet; use salsa::Setter; -use ty_python_semantic::Program; +use ty_python_semantic::{FallibleStrategy, Program}; /// Represents the result of applying changes to the project database. pub struct ChangeResult { @@ -263,7 +263,11 @@ impl ProjectDatabase { metadata.apply_overrides(overrides); } - match metadata.to_program_settings(self.system(), self.vendored()) { + match metadata.to_program_settings( + self.system(), + self.vendored(), + &FallibleStrategy, + ) { Ok(program_settings) => { let program = Program::get(self); program.update_from_settings(self, program_settings); @@ -279,7 +283,7 @@ impl ProjectDatabase { tracing::debug!("Reloading project after structural change"); project.reload(self, metadata); } else { - match Project::from_metadata(self, metadata) { + match Project::from_metadata(self, metadata, &FallibleStrategy) { Ok(new_project) => { tracing::debug!("Replace project after structural change"); project = new_project; @@ -307,10 +311,11 @@ impl ProjectDatabase { return result; } else if result.custom_stdlib_changed { - match project - .metadata(self) - .to_program_settings(self.system(), self.vendored()) - { + match project.metadata(self).to_program_settings( + self.system(), + self.vendored(), + &FallibleStrategy, + ) { Ok(program_settings) => { program.update_from_settings(self, program_settings); } diff --git a/crates/ty_project/src/glob.rs b/crates/ty_project/src/glob.rs index 080a96db64cd1d..9c023103c0a92b 100644 --- a/crates/ty_project/src/glob.rs +++ b/crates/ty_project/src/glob.rs @@ -7,6 +7,8 @@ pub(crate) use portable::{ AbsolutePortableGlobPattern, PortableGlobError, PortableGlobKind, PortableGlobPattern, }; +use crate::metadata::options::DEFAULT_SRC_EXCLUDES; + mod exclude; mod include; mod portable; @@ -69,6 +71,40 @@ impl IncludeExcludeFilter { } } +impl Default for IncludeExcludeFilter { + fn default() -> Self { + let mut includes = IncludeFilterBuilder::new(); + includes + .add( + &PortableGlobPattern::parse("**", PortableGlobKind::Include) + .unwrap() + .into_absolute(""), + ) + .expect("default include filter to be infallible"); + + let mut excludes = ExcludeFilterBuilder::new(); + + for pattern in DEFAULT_SRC_EXCLUDES { + PortableGlobPattern::parse(pattern, PortableGlobKind::Exclude) + .and_then(|exclude| Ok(excludes.add(&exclude.into_absolute(""))?)) + .unwrap_or_else(|err| { + panic!( + "Expected default exclude to be valid glob but adding it failed with: {err}" + ) + }); + } + + Self { + include: includes + .build() + .expect("default include filter to be infallible"), + exclude: excludes + .build() + .expect("default exclude filter to be infallible"), + } + } +} + impl std::fmt::Display for IncludeExcludeFilter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "include={}, exclude={}", &self.include, &self.exclude) diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index dd6b7a7ed00719..25bfaf8f44bd8d 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -28,9 +28,11 @@ use std::iter::FusedIterator; use std::panic::{AssertUnwindSafe, UnwindSafe}; use std::sync::Arc; use thiserror::Error; -use ty_python_semantic::add_inferred_python_version_hint_to_diagnostic; use ty_python_semantic::lint::RuleSelection; use ty_python_semantic::types::check_types; +use ty_python_semantic::{ + FallibleStrategy, MisconfigurationStrategy, add_inferred_python_version_hint_to_diagnostic, +}; mod db; mod files; @@ -172,8 +174,15 @@ impl ProgressReporter for CollectReporter { #[salsa::tracked] impl Project { - pub fn from_metadata(db: &dyn Db, metadata: ProjectMetadata) -> Result { - let (settings, diagnostics) = metadata.options().to_settings(db, metadata.root())?; + pub fn from_metadata( + db: &dyn Db, + metadata: ProjectMetadata, + strategy: &Strategy, + ) -> Result> { + let (settings, diagnostics) = + metadata + .options() + .to_settings(db, metadata.root(), strategy)?; // This adds a file root for the project itself. This enables // tracking of when changes are made to the files in a project @@ -236,7 +245,10 @@ impl Project { assert_eq!(self.root(db), metadata.root()); if &metadata != self.metadata(db) { - match metadata.options().to_settings(db, metadata.root()) { + match metadata + .options() + .to_settings(db, metadata.root(), &FallibleStrategy) + { Ok((settings, settings_diagnostics)) => { if self.settings(db) != &settings { self.set_settings(db).to(Box::new(settings)); diff --git a/crates/ty_project/src/metadata.rs b/crates/ty_project/src/metadata.rs index 8d98b31bc23d76..b4b5339ef879fe 100644 --- a/crates/ty_project/src/metadata.rs +++ b/crates/ty_project/src/metadata.rs @@ -5,7 +5,7 @@ use ruff_python_ast::name::Name; use std::sync::Arc; use thiserror::Error; use ty_combine::Combine; -use ty_python_semantic::{MisconfigurationMode, ProgramSettings}; +use ty_python_semantic::{FallibleStrategy, MisconfigurationStrategy, ProgramSettings}; use crate::metadata::options::ProjectOptionsOverrides; use crate::metadata::pyproject::{Project, PyProject, PyProjectError, ResolveRequiresPythonError}; @@ -37,9 +37,6 @@ pub struct ProjectMetadata { /// The path ordering doesn't imply precedence. #[cfg_attr(test, serde(skip_serializing_if = "Vec::is_empty"))] pub(super) extra_configuration_paths: Vec, - - #[cfg_attr(test, serde(skip))] - pub(super) misconfiguration_mode: MisconfigurationMode, } impl ProjectMetadata { @@ -50,7 +47,6 @@ impl ProjectMetadata { root, extra_configuration_paths: Vec::default(), options: Options::default(), - misconfiguration_mode: MisconfigurationMode::Fail, } } @@ -75,7 +71,6 @@ impl ProjectMetadata { root: root.to_path_buf(), options, extra_configuration_paths: vec![path], - misconfiguration_mode: MisconfigurationMode::Fail, }) } @@ -88,17 +83,17 @@ impl ProjectMetadata { pyproject.tool.and_then(|tool| tool.ty).unwrap_or_default(), root, pyproject.project.as_ref(), - MisconfigurationMode::Fail, + &FallibleStrategy, ) } /// Loads a project from a set of options with an optional pyproject-project table. - pub fn from_options( + pub fn from_options( mut options: Options, root: SystemPathBuf, project: Option<&Project>, - misconfiguration_mode: MisconfigurationMode, - ) -> Result { + strategy: &Strategy, + ) -> Result> { let name = project .and_then(|project| project.name.as_deref()) .map(|name| Name::new(&**name)) @@ -112,7 +107,13 @@ impl ProjectMetadata { .as_ref() .is_none_or(|env| env.python_version.is_none()) { - if let Some(requires_python) = project.resolve_requires_python_lower_bound()? { + let requires_python = strategy.fallback_opt( + project.resolve_requires_python_lower_bound(), + |err| { + tracing::debug!("skipping invalid requires_python lower bound: {err}"); + }, + )?; + if let Some(requires_python) = requires_python.flatten() { let mut environment = options.environment.unwrap_or_default(); environment.python_version = Some(requires_python); options.environment = Some(environment); @@ -125,7 +126,6 @@ impl ProjectMetadata { root, options, extra_configuration_paths: Vec::new(), - misconfiguration_mode, }) } @@ -203,7 +203,7 @@ impl ProjectMetadata { pyproject .as_ref() .and_then(|pyproject| pyproject.project.as_ref()), - MisconfigurationMode::Fail, + &FallibleStrategy, ) .map_err(|err| { ProjectMetadataError::InvalidRequiresPythonConstraint { @@ -278,18 +278,14 @@ impl ProjectMetadata { &self.extra_configuration_paths } - pub fn to_program_settings( + pub fn to_program_settings( &self, system: &dyn System, vendored: &VendoredFileSystem, - ) -> anyhow::Result { - self.options.to_program_settings( - self.root(), - self.name(), - system, - vendored, - self.misconfiguration_mode, - ) + strategy: &Strategy, + ) -> Result> { + self.options + .to_program_settings(self.root(), self.name(), system, vendored, strategy) } pub fn apply_overrides(&mut self, overrides: &ProjectOptionsOverrides) { diff --git a/crates/ty_project/src/metadata/options.rs b/crates/ty_project/src/metadata/options.rs index 10a7d38a4d88ef..932c5282562ed6 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -33,7 +33,7 @@ use ty_module_resolver::{ }; use ty_python_semantic::lint::{Level, LintSource, RuleSelection}; use ty_python_semantic::{ - AnalysisSettings, MisconfigurationMode, ProgramSettings, PythonEnvironment, PythonPlatform, + AnalysisSettings, MisconfigurationStrategy, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource, SitePackagesPaths, SysPrefixPathOrigin, }; @@ -119,14 +119,14 @@ impl Options { Self::deserialize(deserializer) } - pub(crate) fn to_program_settings( + pub(crate) fn to_program_settings( &self, project_root: &SystemPath, project_name: &str, system: &dyn System, vendored: &VendoredFileSystem, - misconfiguration_mode: MisconfigurationMode, - ) -> anyhow::Result { + strategy: &Strategy, + ) -> Result> { let environment = self.environment.or_default(); let options_python_version = @@ -172,17 +172,11 @@ impl Options { }; // If in safe-mode, fallback to None if this fails instead of erroring. - let python_environment = match python_environment { - Ok(python_environment) => python_environment, - Err(err) => { - if misconfiguration_mode == MisconfigurationMode::UseDefault { - tracing::debug!("Default settings failed to discover local Python environment"); - None - } else { - return Err(err); - } - } - }; + let python_environment = strategy + .fallback_opt(python_environment, |_| { + tracing::debug!("Default settings failed to discover local Python environment"); + })? + .flatten(); let self_environment = self_environment_search_paths( python_environment @@ -196,19 +190,10 @@ impl Options { let site_packages_paths = python_environment .site_packages_paths(system) .context("Failed to discover the site-packages directory"); - let site_packages_paths = match site_packages_paths { - Ok(paths) => paths, - Err(err) => { - if misconfiguration_mode == MisconfigurationMode::UseDefault { - tracing::debug!( - "Default settings failed to discover site-packages directory" - ); - SitePackagesPaths::default() - } else { - return Err(err); - } - } - }; + let site_packages_paths = strategy.fallback(site_packages_paths, |_| { + tracing::debug!("Default settings failed to discover site-packages directory"); + SitePackagesPaths::default() + })?; match self_environment { // When ty is installed in a virtual environment (e.g., `uvx --with ...`), // the self-environment takes priority over the discovered environment. @@ -242,15 +227,15 @@ impl Options { .unwrap_or_default(); // Safe mode is handled inside this function, so we just assume this can't fail - let search_paths = self.to_search_paths( + let search_paths = strategy.to_anyhow(self.to_search_paths( project_root, project_name, site_packages_paths, real_stdlib_path, system, vendored, - misconfiguration_mode, - )?; + strategy, + ))?; tracing::info!( "Python version: Python {python_version}, platform: {python_platform}", @@ -265,7 +250,7 @@ impl Options { } #[expect(clippy::too_many_arguments)] - fn to_search_paths( + fn to_search_paths( &self, project_root: &SystemPath, project_name: &str, @@ -273,8 +258,8 @@ impl Options { real_stdlib_path: Option, system: &dyn System, vendored: &VendoredFileSystem, - misconfiguration_mode: MisconfigurationMode, - ) -> Result { + strategy: &Strategy, + ) -> Result> { let environment = self.environment.or_default(); let src = self.src.or_default(); @@ -386,17 +371,17 @@ impl Options { .map(|path| path.absolute(project_root, system)), site_packages_paths: site_packages_paths.into_vec(), real_stdlib_path, - misconfiguration_mode, }; - settings.to_search_paths(system, vendored) + settings.to_search_paths(system, vendored, strategy) } - pub(crate) fn to_settings( + pub(crate) fn to_settings( &self, db: &dyn Db, project_root: &SystemPath, - ) -> Result<(Settings, Vec), ToSettingsError> { + strategy: &Strategy, + ) -> Result<(Settings, Vec), Strategy::Error> { let mut diagnostics = Vec::new(); let rules = self.to_rule_selection(db, &mut diagnostics); @@ -446,7 +431,8 @@ impl Options { diagnostic: err, output_format: terminal.output_format, color: colored::control::SHOULD_COLORIZE.should_colorize(), - })?; + }); + let src = strategy.fallback(src, |_| SrcSettings::default())?; let mut analysis_diagnostics = Vec::new(); let analysis = self @@ -454,13 +440,17 @@ impl Options { .or_default() .to_settings(db, &mut analysis_diagnostics); - if let Some(diagnostic) = analysis_diagnostics.into_iter().next() { - return Err(ToSettingsError { - diagnostic: Box::new(diagnostic), - output_format: terminal.output_format, - color: colored::control::SHOULD_COLORIZE.should_colorize(), - }); - } + let analysis_result: Result<_, ToSettingsError> = + if let Some(diagnostic) = analysis_diagnostics.into_iter().next() { + Err(ToSettingsError { + diagnostic: Box::new(diagnostic), + output_format: terminal.output_format, + color: colored::control::SHOULD_COLORIZE.should_colorize(), + }) + } else { + Ok(analysis) + }; + let analysis = strategy.fallback(analysis_result, |_| AnalysisSettings::default())?; let overrides = self .to_overrides_settings(db, project_root, &mut diagnostics) @@ -468,7 +458,8 @@ impl Options { diagnostic: err, output_format: terminal.output_format, color: colored::control::SHOULD_COLORIZE.should_colorize(), - })?; + }); + let overrides = strategy.fallback(overrides, |_| Vec::new())?; let settings = Settings { rules: Arc::new(rules), @@ -970,7 +961,7 @@ impl Rules { } /// Default exclude patterns for src options. -const DEFAULT_SRC_EXCLUDES: &[&str] = &[ +pub(crate) const DEFAULT_SRC_EXCLUDES: &[&str] = &[ "**/.bzr/", "**/.direnv/", "**/.eggs/", diff --git a/crates/ty_project/src/metadata/settings.rs b/crates/ty_project/src/metadata/settings.rs index c3c5db5d9431d9..df81c1391f3f72 100644 --- a/crates/ty_project/src/metadata/settings.rs +++ b/crates/ty_project/src/metadata/settings.rs @@ -73,6 +73,14 @@ pub struct SrcSettings { pub respect_ignore_files: bool, pub files: IncludeExcludeFilter, } +impl SrcSettings { + pub(crate) fn default() -> Self { + Self { + respect_ignore_files: true, + files: IncludeExcludeFilter::default(), + } + } +} /// A single configuration override that applies to files matching specific patterns. #[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)] diff --git a/crates/ty_python_semantic/src/db.rs b/crates/ty_python_semantic/src/db.rs index f6a1a2f17c2b28..c892860fb8d978 100644 --- a/crates/ty_python_semantic/src/db.rs +++ b/crates/ty_python_semantic/src/db.rs @@ -26,7 +26,7 @@ pub(crate) mod tests { use crate::program::Program; use crate::{ - AnalysisSettings, ProgramSettings, PythonPlatform, PythonVersionSource, + AnalysisSettings, FallibleStrategy, ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource, default_lint_registry, }; use ty_module_resolver::SearchPathSettings; @@ -207,7 +207,7 @@ pub(crate) mod tests { }, python_platform: self.python_platform, search_paths: SearchPathSettings::new(vec![src_root]) - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .context("Invalid search path settings")?, }, ); diff --git a/crates/ty_python_semantic/src/lib.rs b/crates/ty_python_semantic/src/lib.rs index afb5f55f6480f6..5c84457b0815e2 100644 --- a/crates/ty_python_semantic/src/lib.rs +++ b/crates/ty_python_semantic/src/lib.rs @@ -10,7 +10,9 @@ use crate::suppression::{ }; pub use db::Db; pub use diagnostic::add_inferred_python_version_hint_to_diagnostic; -pub use program::{Program, ProgramSettings}; +pub use program::{ + FallibleStrategy, MisconfigurationStrategy, Program, ProgramSettings, UseDefaultStrategy, +}; pub use python_platform::PythonPlatform; use rustc_hash::FxHasher; pub use semantic_model::{ @@ -19,7 +21,6 @@ pub use semantic_model::{ pub use suppression::{ UNUSED_IGNORE_COMMENT, is_unused_ignore_comment_lint, suppress_all, suppress_single, }; -pub use ty_module_resolver::MisconfigurationMode; use ty_module_resolver::ModuleGlobSet; pub use ty_site_packages::{ PythonEnvironment, PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource, diff --git a/crates/ty_python_semantic/src/program.rs b/crates/ty_python_semantic/src/program.rs index 702c6382b7a507..55a7068abac592 100644 --- a/crates/ty_python_semantic/src/program.rs +++ b/crates/ty_python_semantic/src/program.rs @@ -8,6 +8,9 @@ use salsa::Setter; use ty_module_resolver::SearchPaths; use ty_site_packages::PythonVersionWithSource; +// Re-export the misconfiguration strategy types from ty_module_resolver. +pub use ty_module_resolver::{FallibleStrategy, MisconfigurationStrategy, UseDefaultStrategy}; + #[salsa::input(singleton, heap_size=ruff_memory_usage::heap_size)] pub struct Program { #[returns(ref)] diff --git a/crates/ty_python_semantic/tests/corpus.rs b/crates/ty_python_semantic/tests/corpus.rs index 8b522b086ffc4e..798ba11b1159c9 100644 --- a/crates/ty_python_semantic/tests/corpus.rs +++ b/crates/ty_python_semantic/tests/corpus.rs @@ -11,8 +11,8 @@ use ty_module_resolver::SearchPathSettings; use ty_python_semantic::lint::{LintRegistry, RuleSelection}; use ty_python_semantic::pull_types::pull_types; use ty_python_semantic::{ - AnalysisSettings, Program, ProgramSettings, PythonPlatform, PythonVersionSource, - PythonVersionWithSource, default_lint_registry, + AnalysisSettings, FallibleStrategy, Program, ProgramSettings, PythonPlatform, + PythonVersionSource, PythonVersionWithSource, default_lint_registry, }; use test_case::test_case; @@ -205,7 +205,7 @@ impl CorpusDb { }, python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(vec![]) - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .unwrap(), }, ); diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index 5b2bb03fd4e684..41eb3e1430a80e 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -27,7 +27,7 @@ use ty_project::watch::{ChangeEvent, CreatedKind}; use ty_project::{ChangeResult, Db as _, ProjectDatabase, ProjectMetadata}; use index::DocumentError; -use ty_python_semantic::MisconfigurationMode; +use ty_python_semantic::UseDefaultStrategy; pub(crate) use self::options::InitializationOptions; pub use self::options::{ClientOptions, DiagnosticMode, GlobalOptions, WorkspaceOptions}; @@ -611,7 +611,7 @@ impl Session { metadata.apply_overrides(overrides); } - ProjectDatabase::new(metadata, system.clone()) + ProjectDatabase::fallible(metadata, system.clone()) }); let (root, db) = match project { @@ -627,15 +627,13 @@ impl Session { self.client_name.log_guidance(), )); - let db_with_default_settings = ProjectMetadata::from_options( + let Ok(metadata) = ProjectMetadata::from_options( Options::default(), root, None, - MisconfigurationMode::UseDefault, - ) - .context("Failed to convert default options to metadata") - .and_then(|metadata| ProjectDatabase::new(metadata, system)) - .expect("Default configuration to be valid"); + &UseDefaultStrategy, + ); + let db_with_default_settings = ProjectDatabase::use_defaults(metadata, system); let default_root = db_with_default_settings .project() .root(&db_with_default_settings) diff --git a/crates/ty_test/src/assertion.rs b/crates/ty_test/src/assertion.rs index 6f1e80f9c95fec..47d9e18fa363e5 100644 --- a/crates/ty_test/src/assertion.rs +++ b/crates/ty_test/src/assertion.rs @@ -509,7 +509,9 @@ mod tests { use ruff_python_trivia::textwrap::dedent; use ruff_source_file::OneIndexed; use ty_module_resolver::SearchPathSettings; - use ty_python_semantic::{Program, ProgramSettings, PythonPlatform, PythonVersionWithSource}; + use ty_python_semantic::{ + FallibleStrategy, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, + }; fn get_assertions(source: &str) -> InlineFileAssertions { let mut db = Db::setup(); @@ -518,7 +520,7 @@ mod tests { python_version: PythonVersionWithSource::default(), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(Vec::new()) - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .unwrap(), }; Program::init_or_update(&mut db, settings); diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index dcc63b6508f9f2..3ee2e993417ff6 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -23,7 +23,7 @@ use ty_module_resolver::{ use ty_python_semantic::pull_types::pull_types; use ty_python_semantic::types::{UNDEFINED_REVEAL, check_types}; use ty_python_semantic::{ - MisconfigurationMode, Program, ProgramSettings, PythonEnvironment, PythonPlatform, + FallibleStrategy, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource, PythonVersionWithSource, SysPrefixPathOrigin, }; @@ -462,9 +462,8 @@ fn run_test( custom_typeshed: custom_typeshed_path.map(SystemPath::to_path_buf), site_packages_paths, real_stdlib_path: None, - misconfiguration_mode: MisconfigurationMode::Fail, } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Failed to resolve search path settings"), }; diff --git a/crates/ty_test/src/matcher.rs b/crates/ty_test/src/matcher.rs index 415ac2bf7419dc..8a26a3e376deb4 100644 --- a/crates/ty_test/src/matcher.rs +++ b/crates/ty_test/src/matcher.rs @@ -425,7 +425,9 @@ mod tests { use ruff_source_file::OneIndexed; use ruff_text_size::TextRange; use ty_module_resolver::SearchPathSettings; - use ty_python_semantic::{Program, ProgramSettings, PythonPlatform, PythonVersionWithSource}; + use ty_python_semantic::{ + FallibleStrategy, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, + }; struct ExpectedDiagnostic { id: DiagnosticId, @@ -473,7 +475,7 @@ mod tests { python_version: PythonVersionWithSource::default(), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(Vec::new()) - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search paths settings"), }; Program::init_or_update(&mut db, settings); diff --git a/crates/ty_wasm/src/lib.rs b/crates/ty_wasm/src/lib.rs index c6e6d736b4ef9a..a6cae1a7699b7c 100644 --- a/crates/ty_wasm/src/lib.rs +++ b/crates/ty_wasm/src/lib.rs @@ -26,7 +26,7 @@ use ty_project::metadata::value::ValueSource; use ty_project::watch::{ChangeEvent, ChangedKind, CreatedKind, DeletedKind}; use ty_project::{CheckMode, ProjectMetadata}; use ty_project::{Db, ProjectDatabase}; -use ty_python_semantic::{MisconfigurationMode, Program}; +use ty_python_semantic::{FallibleStrategy, Program}; use wasm_bindgen::prelude::*; #[wasm_bindgen] @@ -131,11 +131,11 @@ impl Workspace { options, SystemPathBuf::from(root), None, - MisconfigurationMode::Fail, + &FallibleStrategy, ) .map_err(into_error)?; - let mut db = ProjectDatabase::new(project, system.clone()).map_err(into_error)?; + let mut db = ProjectDatabase::fallible(project, system.clone()).map_err(into_error)?; // By default, it will check all files in the project but we only want to check the open // files in the playground. @@ -160,12 +160,12 @@ impl Workspace { options, self.db.project().root(&self.db).to_path_buf(), None, - MisconfigurationMode::Fail, + &FallibleStrategy, ) .map_err(into_error)?; let program_settings = project - .to_program_settings(&self.system, self.db.vendored()) + .to_program_settings(&self.system, self.db.vendored(), &FallibleStrategy) .map_err(into_error)?; Program::get(&self.db).update_from_settings(&mut self.db, program_settings); diff --git a/fuzz/fuzz_targets/ty_check_invalid_syntax.rs b/fuzz/fuzz_targets/ty_check_invalid_syntax.rs index 6c0e7f5ec8af7a..a764e89bdee7b8 100644 --- a/fuzz/fuzz_targets/ty_check_invalid_syntax.rs +++ b/fuzz/fuzz_targets/ty_check_invalid_syntax.rs @@ -19,7 +19,7 @@ use ty_module_resolver::{Db as ModuleResolverDb, SearchPathSettings}; use ty_python_semantic::lint::LintRegistry; use ty_python_semantic::types::check_types; use ty_python_semantic::{ - AnalysisSettings, Db as SemanticDb, Program, ProgramSettings, PythonPlatform, + AnalysisSettings, Db as SemanticDb, FallibleStrategy, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, default_lint_registry, lint::RuleSelection, }; @@ -130,7 +130,7 @@ fn setup_db() -> TestDb { python_version: PythonVersionWithSource::default(), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(vec![src_root]) - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FallibleStrategy) .expect("Valid search path settings"), }, );