From 4f22ab8b70c638750a8f4987ccfd251c97b37965 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Thu, 18 Dec 2025 12:48:38 -0500 Subject: [PATCH] Make the default database truly statically infallible --- Cargo.lock | 1 + crates/ruff_benchmark/Cargo.toml | 4 +- crates/ruff_benchmark/benches/ty.rs | 7 +- crates/ruff_benchmark/benches/ty_walltime.rs | 3 +- crates/ruff_graph/src/db.rs | 7 +- crates/ty/src/lib.rs | 3 +- crates/ty/tests/file_watching.rs | 6 +- crates/ty_completion_eval/src/main.rs | 4 +- crates/ty_project/src/db.rs | 29 ++- crates/ty_project/src/db/changes.rs | 19 +- crates/ty_project/src/glob.rs | 36 +++ crates/ty_project/src/lib.rs | 20 +- crates/ty_project/src/metadata.rs | 40 ++-- crates/ty_project/src/metadata/options.rs | 69 +++--- crates/ty_project/src/metadata/settings.rs | 8 + crates/ty_python_semantic/src/db.rs | 6 +- crates/ty_python_semantic/src/lib.rs | 4 +- .../src/module_resolver/list.rs | 16 +- .../src/module_resolver/resolver.rs | 102 ++++----- .../src/module_resolver/testing.rs | 8 +- crates/ty_python_semantic/src/program.rs | 207 ++++++++++++++++-- crates/ty_python_semantic/tests/corpus.rs | 6 +- crates/ty_server/src/session.rs | 26 +-- crates/ty_test/src/assertion.rs | 5 +- crates/ty_test/src/lib.rs | 5 +- crates/ty_test/src/matcher.rs | 5 +- crates/ty_wasm/src/lib.rs | 19 +- 27 files changed, 439 insertions(+), 226 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cda8a8261d..2b80647fad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3004,6 +3004,7 @@ dependencies = [ "tikv-jemallocator", "tracing", "ty_project", + "ty_python_semantic", ] [[package]] diff --git a/crates/ruff_benchmark/Cargo.toml b/crates/ruff_benchmark/Cargo.toml index d10c45f452..a435825084 100644 --- a/crates/ruff_benchmark/Cargo.toml +++ b/crates/ruff_benchmark/Cargo.toml @@ -54,6 +54,7 @@ ruff_python_formatter = { workspace = true, optional = true } ruff_python_parser = { workspace = true, optional = true } ruff_python_trivia = { workspace = true, optional = true } ty_project = { workspace = true, optional = true } +ty_python_semantic = { workspace = true, optional = true } divan = { workspace = true, optional = true } anyhow = { workspace = true } @@ -76,10 +77,11 @@ instrumented = [ "ruff_python_parser", "ruff_python_trivia", "ty_project", + "ty_python_semantic", ] codspeed = ["codspeed-criterion-compat"] # Enables benchmark that should only run with codspeed's walltime runner. -walltime = ["ruff_db/os", "ty_project", "divan"] +walltime = ["ruff_db/os", "ty_project", "ty_python_semantic", "divan"] [target.'cfg(target_os = "windows")'.dev-dependencies] mimalloc = { workspace = true } diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index 9ae6e9c40b..96ea8a670b 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -1,6 +1,7 @@ #![allow(clippy::disallowed_names)] use ruff_benchmark::criterion; use ruff_benchmark::real_world_projects::{InstalledProject, RealWorldProject}; +use ty_python_semantic::FailStrategy; use std::fmt::Write; use std::ops::Range; @@ -88,7 +89,7 @@ fn setup_tomllib_case() -> Case { ..Options::default() }); - let mut db = ProjectDatabase::new(metadata, system).unwrap(); + let mut db = ProjectDatabase::new(metadata, system, &FailStrategy).unwrap(); let mut tomllib_files = FxHashSet::default(); let mut re: Option = None; @@ -235,7 +236,7 @@ fn setup_micro_case(code: &str) -> Case { ..Options::default() }); - let mut db = ProjectDatabase::new(metadata, system).unwrap(); + let mut db = ProjectDatabase::new(metadata, system, &FailStrategy).unwrap(); let file = system_path_to_file(&db, SystemPathBuf::from(file_path)).unwrap(); db.set_check_mode(CheckMode::OpenFiles); @@ -592,7 +593,7 @@ impl<'a> ProjectBenchmark<'a> { ..Options::default() }); - let mut db = ProjectDatabase::new(metadata, system).unwrap(); + let mut db = ProjectDatabase::new(metadata, system, &FailStrategy).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 5826da6073..c893edc3a0 100644 --- a/crates/ruff_benchmark/benches/ty_walltime.rs +++ b/crates/ruff_benchmark/benches/ty_walltime.rs @@ -1,5 +1,6 @@ use divan::{Bencher, bench}; use std::fmt::{Display, Formatter}; +use ty_python_semantic::FailStrategy; use rayon::ThreadPoolBuilder; use ruff_benchmark::real_world_projects::{InstalledProject, RealWorldProject}; @@ -51,7 +52,7 @@ impl<'a> Benchmark<'a> { ..Options::default() }); - let mut db = ProjectDatabase::new(metadata, system).unwrap(); + let mut db = ProjectDatabase::new(metadata, system, &FailStrategy).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 6c5a6e8121..b0e792d182 100644 --- a/crates/ruff_graph/src/db.rs +++ b/crates/ruff_graph/src/db.rs @@ -9,8 +9,9 @@ use ruff_db::vendored::{VendoredFileSystem, VendoredFileSystemBuilder}; use ruff_python_ast::PythonVersion; use ty_python_semantic::lint::{LintRegistry, RuleSelection}; use ty_python_semantic::{ - Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource, - PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, default_lint_registry, + Db, FailStrategy, Program, ProgramSettings, PythonEnvironment, PythonPlatform, + PythonVersionSource, PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, + default_lint_registry, }; static EMPTY_VENDORED: std::sync::LazyLock = std::sync::LazyLock::new(|| { @@ -47,7 +48,7 @@ impl ModuleDb { .into_vec(); } let search_paths = search_paths - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .context("Invalid search path settings")?; Program::from_settings( diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index 96e85fea6b..8cd6187d85 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -6,6 +6,7 @@ mod version; pub use args::Cli; use ty_project::metadata::settings::TerminalSettings; +use ty_python_semantic::FailStrategy; use ty_static::EnvVars; use std::fmt::Write; @@ -129,7 +130,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::new(project_metadata, system, &FailStrategy)?; db.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 5bb80ca857..c0aeabbee6 100644 --- a/crates/ty/tests/file_watching.rs +++ b/crates/ty/tests/file_watching.rs @@ -15,7 +15,9 @@ use ty_project::metadata::pyproject::{PyProject, Tool}; use ty_project::metadata::value::{RangedValue, RelativePathBuf}; use ty_project::watch::{ChangeEvent, ProjectWatcher, directory_watcher}; use ty_project::{Db, ProjectDatabase, ProjectMetadata}; -use ty_python_semantic::{Module, ModuleName, PythonPlatform, resolve_module_confident}; +use ty_python_semantic::{ + FailStrategy, Module, ModuleName, PythonPlatform, resolve_module_confident, +}; struct TestCase { db: ProjectDatabase, @@ -434,7 +436,7 @@ where } } - let mut db = ProjectDatabase::new(project, system)?; + let mut db = ProjectDatabase::new(project, system, &FailStrategy)?; if let Some(included_paths) = included_paths { db.project().set_included_paths(&mut db, included_paths); diff --git a/crates/ty_completion_eval/src/main.rs b/crates/ty_completion_eval/src/main.rs index 5d3b44ad18..3a9d0d9e1b 100644 --- a/crates/ty_completion_eval/src/main.rs +++ b/crates/ty_completion_eval/src/main.rs @@ -19,7 +19,7 @@ use ty_project::metadata::Options; use ty_project::metadata::options::EnvironmentOptions; use ty_project::metadata::value::RelativePathBuf; use ty_project::{ProjectDatabase, ProjectMetadata}; -use ty_python_semantic::ModuleName; +use ty_python_semantic::{FailStrategy, ModuleName}; #[derive(Debug, clap::Parser)] #[command( @@ -290,7 +290,7 @@ impl Task { ..Options::default() }); project_metadata.apply_configuration_files(&system)?; - let db = ProjectDatabase::new(project_metadata, system)?; + let db = ProjectDatabase::new(project_metadata, system, &FailStrategy)?; Ok(Task { db, dir: project_path.to_path_buf(), diff --git a/crates/ty_project/src/db.rs b/crates/ty_project/src/db.rs index 9c8eab8204..987002429a 100644 --- a/crates/ty_project/src/db.rs +++ b/crates/ty_project/src/db.rs @@ -15,7 +15,7 @@ use ruff_db::system::System; use ruff_db::vendored::VendoredFileSystem; use salsa::{Database, Event, Setter}; use ty_python_semantic::lint::{LintRegistry, RuleSelection}; -use ty_python_semantic::{Db as SemanticDb, Program}; +use ty_python_semantic::{Db as SemanticDb, MisconfigurationStrategy, Program}; mod changes; @@ -44,7 +44,11 @@ pub struct ProjectDatabase { } impl ProjectDatabase { - pub fn new(project_metadata: ProjectMetadata, system: S) -> anyhow::Result + pub fn new( + project_metadata: ProjectMetadata, + system: S, + strategy: &Strategy, + ) -> Result> where S: System + 'static + Send + Sync + RefUnwindSafe, { @@ -72,13 +76,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) } @@ -525,7 +533,8 @@ pub(crate) mod tests { use ruff_db::vendored::VendoredFileSystem; use ty_python_semantic::lint::{LintRegistry, RuleSelection}; use ty_python_semantic::{ - Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, SearchPathSettings, + FailStrategy, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, + SearchPathSettings, }; use crate::db::Db; @@ -562,7 +571,7 @@ pub(crate) mod tests { project: None, }; - let project = Project::from_metadata(&db, project).unwrap(); + let project = Project::from_metadata(&db, project, &FailStrategy).unwrap(); db.project = Some(project); db } @@ -571,7 +580,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(), &FailStrategy) .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 b4f8a0f19a..2a67976b21 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::{FailStrategy, Program}; /// Represents the result of applying changes to the project database. pub struct ChangeResult { @@ -260,7 +260,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(), + &FailStrategy, + ) { Ok(program_settings) => { let program = Program::get(self); program.update_from_settings(self, program_settings); @@ -276,7 +280,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, &FailStrategy) { Ok(new_project) => { tracing::debug!("Replace project after structural change"); project = new_project; @@ -304,10 +308,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(), + &FailStrategy, + ) { 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 81842f4948..ca068949ac 100644 --- a/crates/ty_project/src/glob.rs +++ b/crates/ty_project/src/glob.rs @@ -6,6 +6,8 @@ pub(crate) use portable::{ AbsolutePortableGlobPattern, PortableGlobError, PortableGlobKind, PortableGlobPattern, }; +use crate::metadata::options::DEFAULT_SRC_EXCLUDES; + mod exclude; mod include; mod portable; @@ -60,6 +62,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 2bcb287b40..744c33783e 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -27,9 +27,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::{ + FailStrategy, MisconfigurationStrategy, add_inferred_python_version_hint_to_diagnostic, +}; mod db; mod files; @@ -166,8 +168,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 @@ -226,7 +235,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(), &FailStrategy) + { 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 cb6c7b48f7..582f77f000 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::{FailStrategy, 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, } } @@ -74,7 +70,6 @@ impl ProjectMetadata { root: system.current_directory().to_path_buf(), options, extra_configuration_paths: vec![path], - misconfiguration_mode: MisconfigurationMode::Fail, }) } @@ -87,17 +82,17 @@ impl ProjectMetadata { pyproject.tool.and_then(|tool| tool.ty).unwrap_or_default(), root, pyproject.project.as_ref(), - MisconfigurationMode::Fail, + &FailStrategy, ) } /// 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)) @@ -111,7 +106,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); @@ -124,7 +125,6 @@ impl ProjectMetadata { root, options, extra_configuration_paths: Vec::new(), - misconfiguration_mode, }) } @@ -202,7 +202,7 @@ impl ProjectMetadata { pyproject .as_ref() .and_then(|pyproject| pyproject.project.as_ref()), - MisconfigurationMode::Fail, + &FailStrategy, ) .map_err(|err| { ProjectMetadataError::InvalidRequiresPythonConstraint { @@ -277,18 +277,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 3220e9c925..4eab8d9d27 100644 --- a/crates/ty_project/src/metadata/options.rs +++ b/crates/ty_project/src/metadata/options.rs @@ -30,7 +30,7 @@ use thiserror::Error; use ty_combine::Combine; use ty_python_semantic::lint::{Level, LintSource, RuleSelection}; use ty_python_semantic::{ - MisconfigurationMode, ProgramSettings, PythonEnvironment, PythonPlatform, + MisconfigurationStrategy, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource, SearchPathSettings, SearchPathValidationError, SearchPaths, SitePackagesPaths, SysPrefixPathOrigin, }; @@ -111,14 +111,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 = @@ -164,17 +164,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_site_packages = self_environment_search_paths( python_environment @@ -189,19 +183,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() + })?; self_site_packages.concatenate(site_packages_paths) } else { tracing::debug!("No virtual environment found"); @@ -226,15 +211,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}", @@ -249,7 +234,7 @@ impl Options { } #[expect(clippy::too_many_arguments)] - fn to_search_paths( + fn to_search_paths( &self, project_root: &SystemPath, project_name: &str, @@ -257,8 +242,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(); @@ -372,17 +357,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); @@ -432,7 +417,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 overrides = self .to_overrides_settings(db, project_root, &mut diagnostics) @@ -440,7 +426,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), @@ -918,7 +905,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 347daa57e2..0cfa9dcf07 100644 --- a/crates/ty_project/src/metadata/settings.rs +++ b/crates/ty_project/src/metadata/settings.rs @@ -67,6 +67,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 d03b001199..c3d49c595f 100644 --- a/crates/ty_python_semantic/src/db.rs +++ b/crates/ty_python_semantic/src/db.rs @@ -23,8 +23,8 @@ pub(crate) mod tests { use crate::program::{Program, SearchPathSettings}; use crate::{ - ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource, - default_lint_registry, + FailStrategy, ProgramSettings, PythonPlatform, PythonVersionSource, + PythonVersionWithSource, default_lint_registry, }; use super::Db; @@ -188,7 +188,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(), &FailStrategy) .context("Invalid search path settings")?, }, ); diff --git a/crates/ty_python_semantic/src/lib.rs b/crates/ty_python_semantic/src/lib.rs index 648157e334..c63493e037 100644 --- a/crates/ty_python_semantic/src/lib.rs +++ b/crates/ty_python_semantic/src/lib.rs @@ -17,8 +17,8 @@ pub use module_resolver::{ resolve_real_module_confident, resolve_real_shadowable_module, system_module_search_paths, }; pub use program::{ - MisconfigurationMode, Program, ProgramSettings, PythonVersionFileSource, PythonVersionSource, - PythonVersionWithSource, SearchPathSettings, + FailStrategy, MisconfigurationStrategy, Program, ProgramSettings, PythonVersionFileSource, + PythonVersionSource, PythonVersionWithSource, SearchPathSettings, UseDefaultStrategy, }; pub use python_platform::PythonPlatform; use rustc_hash::FxHasher; diff --git a/crates/ty_python_semantic/src/module_resolver/list.rs b/crates/ty_python_semantic/src/module_resolver/list.rs index 1f87ae5a34..f6824f55f5 100644 --- a/crates/ty_python_semantic/src/module_resolver/list.rs +++ b/crates/ty_python_semantic/src/module_resolver/list.rs @@ -395,7 +395,7 @@ mod tests { }; use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder}; use crate::program::{Program, ProgramSettings, SearchPathSettings}; - use crate::{PythonPlatform, PythonVersionSource, PythonVersionWithSource}; + use crate::{FailStrategy, PythonPlatform, PythonVersionSource, PythonVersionWithSource}; use super::list_modules; @@ -940,6 +940,8 @@ mod tests { fn symlink() -> anyhow::Result<()> { use anyhow::Context; + use crate::FailStrategy; + let mut db = TestDb::new(); let temp_dir = tempfile::TempDir::with_prefix("PREFIX-SENTINEL")?; @@ -980,7 +982,7 @@ mod tests { site_packages_paths: vec![site_packages], ..SearchPathSettings::new(vec![src]) } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .expect("Valid search path settings"), }, ); @@ -1488,7 +1490,7 @@ not_a_directory site_packages_paths: vec![venv_site_packages], ..SearchPathSettings::new(vec![src.to_path_buf()]) } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .expect("Valid search path settings"), }, ); @@ -1542,7 +1544,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(), &FailStrategy) .expect("Valid search path settings"), }, ); @@ -1627,7 +1629,7 @@ not_a_directory python_version: PythonVersionWithSource::default(), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(vec![src]) - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .expect("valid search path settings"), }, ); @@ -1671,7 +1673,7 @@ not_a_directory site_packages_paths: vec![site_packages], ..SearchPathSettings::new(vec![project_directory]) } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .unwrap(), }, ); @@ -1826,7 +1828,7 @@ not_a_directory python_version: PythonVersionWithSource::default(), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(vec![project_directory]) - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .unwrap(), }, ); diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index dd9e534375..2678956704 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -50,7 +50,7 @@ use ruff_python_ast::{ use crate::db::Db; use crate::module_name::ModuleName; use crate::module_resolver::typeshed::{TypeshedVersions, vendored_typeshed_versions}; -use crate::program::MisconfigurationMode; +use crate::program::MisconfigurationStrategy; use crate::{Program, SearchPathSettings}; use super::module::{Module, ModuleKind}; @@ -554,11 +554,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(crate) fn from_settings( + pub(crate) 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) @@ -571,7 +572,6 @@ impl SearchPaths { custom_typeshed: typeshed, site_packages_paths, real_stdlib_path, - misconfiguration_mode, } = settings; let mut static_paths = vec![]; @@ -580,30 +580,21 @@ 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); - } - } - } + let path = strategy.fallback_opt(SearchPath::extra(system, path), |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); - } - } - } + let path = strategy.fallback_opt( + SearchPath::first_party(system, src_root.to_path_buf()), + |err| { + tracing::debug!("Skipping invalid first-party search-path: {err}"); + }, + )?; + static_paths.extend(path); } let (typeshed_versions, stdlib_path) = if let Some(typeshed) = typeshed { @@ -623,20 +614,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"); ( @@ -646,17 +630,9 @@ 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); - } - } - } + strategy.fallback_opt(SearchPath::real_stdlib(system, path.clone()), |err| { + tracing::debug!("Skipping invalid real-stdlib search-path: {err}"); + })? } else { None }; @@ -665,16 +641,11 @@ 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 real-stdlib search-path: {err}"); - } else { - return Err(err); - } - } - } + let path = + strategy.fallback_opt(SearchPath::site_packages(system, path.clone()), |err| { + tracing::debug!("Skipping invalid real-stdlib search-path: {err}"); + })?; + site_packages.extend(path); } // TODO vendor typeshed's third-party stubs as well as the stdlib and @@ -1743,7 +1714,7 @@ mod tests { use crate::module_name::ModuleName; use crate::module_resolver::module::ModuleKind; use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder}; - use crate::{ProgramSettings, PythonPlatform, PythonVersionWithSource}; + use crate::{FailStrategy, ProgramSettings, PythonPlatform, PythonVersionWithSource}; use super::*; @@ -2269,7 +2240,8 @@ mod tests { use anyhow::Context; use crate::{ - PythonPlatform, PythonVersionSource, PythonVersionWithSource, program::Program, + FailStrategy, PythonPlatform, PythonVersionSource, PythonVersionWithSource, + program::Program, }; use ruff_db::system::{OsSystem, SystemPath}; @@ -2313,7 +2285,7 @@ mod tests { site_packages_paths: vec![site_packages], ..SearchPathSettings::new(vec![src.clone()]) } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .expect("Valid search path settings"), }, ); @@ -2857,7 +2829,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(), &FailStrategy) .expect("Valid search path settings"), }, ); @@ -2930,7 +2902,7 @@ not_a_directory python_version: PythonVersionWithSource::default(), python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(vec![src]) - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .expect("valid search path settings"), }, ); @@ -2973,7 +2945,7 @@ not_a_directory site_packages_paths: vec![site_packages.clone()], ..SearchPathSettings::new(vec![project_directory]) } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .unwrap(), }, ); diff --git a/crates/ty_python_semantic/src/module_resolver/testing.rs b/crates/ty_python_semantic/src/module_resolver/testing.rs index c2ff313901..63d0b81614 100644 --- a/crates/ty_python_semantic/src/module_resolver/testing.rs +++ b/crates/ty_python_semantic/src/module_resolver/testing.rs @@ -8,7 +8,9 @@ use ruff_python_ast::PythonVersion; use crate::db::tests::TestDb; use crate::program::{Program, SearchPathSettings}; -use crate::{ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource}; +use crate::{ + FailStrategy, ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource, +}; /// A test case for the module resolver. /// @@ -280,7 +282,7 @@ impl TestCaseBuilder { site_packages_paths: vec![site_packages.clone()], ..SearchPathSettings::new(vec![src.clone()]) } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .expect("valid search path settings"), }, ); @@ -351,7 +353,7 @@ impl TestCaseBuilder { site_packages_paths: vec![site_packages.clone()], ..SearchPathSettings::new(vec![src.clone()]) } - .to_search_paths(db.system(), db.vendored()) + .to_search_paths(db.system(), db.vendored(), &FailStrategy) .expect("valid search path settings"), }, ); diff --git a/crates/ty_python_semantic/src/program.rs b/crates/ty_python_semantic/src/program.rs index e27b6c64c1..c8fcd1f530 100644 --- a/crates/ty_python_semantic/src/program.rs +++ b/crates/ty_python_semantic/src/program.rs @@ -163,15 +163,195 @@ impl Default for PythonVersionWithSource { } } -#[derive(PartialEq, Eq, Debug, Copy, Clone, get_size2::GetSize)] -pub enum MisconfigurationMode { - /// Settings Failure Is Not An Error. +/// A type that never exists. +/// +/// In Rust if you have Result the compiler knows `Err` is impossible +/// and you can just write `let Ok(val) = result;` +pub enum Never {} + +/// Generic handling of two possible approaches to an Error: +/// +/// * [`FailStrategy`]: The code should simply fail +/// * [`UseDefaultStrategy`]: The chode 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: +/// +/// * [`FailStrategy`]: `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 { + /// * [`FailStrategy`]: `E` + /// * [`UseDefaultStrategy`]: `Never` + type Error; + + /// Try to get the value out of a Result that we need to proceed. /// - /// 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. - Fail, + /// If [`UseDefaultStrategy`], on `Err` this will call `fallback_fn` to compute + /// a default value and always return `Ok`. + /// + /// If [`FailStrategy`] 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 [`FailStrategy`] 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 = Never; + 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 FailStrategy; + +impl MisconfigurationStrategy for FailStrategy { + 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) + } } /// Configures the search paths for module resolution. @@ -198,9 +378,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 { @@ -218,15 +395,15 @@ 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_python_semantic/tests/corpus.rs b/crates/ty_python_semantic/tests/corpus.rs index 505be61383..a982e58c30 100644 --- a/crates/ty_python_semantic/tests/corpus.rs +++ b/crates/ty_python_semantic/tests/corpus.rs @@ -8,8 +8,8 @@ use ruff_python_ast::PythonVersion; use ty_python_semantic::lint::{LintRegistry, RuleSelection}; use ty_python_semantic::pull_types::pull_types; use ty_python_semantic::{ - Program, ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource, - SearchPathSettings, default_lint_registry, + FailStrategy, Program, ProgramSettings, PythonPlatform, PythonVersionSource, + PythonVersionWithSource, SearchPathSettings, default_lint_registry, }; use test_case::test_case; @@ -200,7 +200,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(), &FailStrategy) .unwrap(), }, ); diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index 29f301b799..f362500e0e 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -28,7 +28,7 @@ use ty_project::{ChangeResult, CheckMode, Db as _, ProjectDatabase, ProjectMetad use index::DocumentError; use options::GlobalOptions; -use ty_python_semantic::MisconfigurationMode; +use ty_python_semantic::{FailStrategy, UseDefaultStrategy}; pub(crate) use self::options::InitializationOptions; pub use self::options::{ClientOptions, DiagnosticMode}; @@ -497,7 +497,7 @@ impl Session { metadata.apply_overrides(overrides); } - ProjectDatabase::new(metadata, system.clone()) + ProjectDatabase::new(metadata, system.clone(), &FailStrategy) }); let (root, db) = match project { @@ -513,15 +513,15 @@ impl Session { Please refer to the logs for more details.", )); - 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, + ); + // FIXME(Gankra): make this infallible + let Ok(db_with_default_settings) = + ProjectDatabase::new(metadata, system, &UseDefaultStrategy); let default_root = db_with_default_settings .project() .root(&db_with_default_settings) @@ -1231,16 +1231,16 @@ impl DefaultProject { let index = index.unwrap(); let system = LSPSystem::new(index.clone(), fallback_system.clone()); - let metadata = ProjectMetadata::from_options( + let Ok(metadata) = ProjectMetadata::from_options( Options::default(), system.current_directory().to_path_buf(), None, - MisconfigurationMode::UseDefault, - ) - .unwrap(); + &UseDefaultStrategy, + ); + let Ok(db) = ProjectDatabase::new(metadata, system, &UseDefaultStrategy); ProjectState { - db: ProjectDatabase::new(metadata, system).unwrap(), + db, untracked_files_with_pushed_diagnostics: Vec::new(), } }) diff --git a/crates/ty_test/src/assertion.rs b/crates/ty_test/src/assertion.rs index fba6f7c899..8c8f23cdd0 100644 --- a/crates/ty_test/src/assertion.rs +++ b/crates/ty_test/src/assertion.rs @@ -503,7 +503,8 @@ mod tests { use ruff_python_trivia::textwrap::dedent; use ruff_source_file::OneIndexed; use ty_python_semantic::{ - Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, SearchPathSettings, + FailStrategy, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, + SearchPathSettings, }; fn get_assertions(source: &str) -> InlineFileAssertions { @@ -513,7 +514,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(), &FailStrategy) .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 312a00fc32..b45b27130e 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -19,7 +19,7 @@ use std::fmt::{Display, Write}; use ty_python_semantic::pull_types::pull_types; use ty_python_semantic::types::{UNDEFINED_REVEAL, check_types}; use ty_python_semantic::{ - MisconfigurationMode, Module, Program, ProgramSettings, PythonEnvironment, PythonPlatform, + FailStrategy, Module, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource, PythonVersionWithSource, SearchPath, SearchPathSettings, SysPrefixPathOrigin, list_modules, resolve_module_confident, }; @@ -441,9 +441,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(), &FailStrategy) .expect("Failed to resolve search path settings"), }; diff --git a/crates/ty_test/src/matcher.rs b/crates/ty_test/src/matcher.rs index 5fe219278c..701542137f 100644 --- a/crates/ty_test/src/matcher.rs +++ b/crates/ty_test/src/matcher.rs @@ -404,7 +404,8 @@ mod tests { use ruff_source_file::OneIndexed; use ruff_text_size::TextRange; use ty_python_semantic::{ - Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, SearchPathSettings, + FailStrategy, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, + SearchPathSettings, }; struct ExpectedDiagnostic { @@ -453,7 +454,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(), &FailStrategy) .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 addc3897de..5cc4c4ae8e 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::{FailStrategy, Program}; use wasm_bindgen::prelude::*; #[wasm_bindgen] @@ -99,15 +99,12 @@ impl Workspace { let system = WasmSystem::new(SystemPath::new(root)); - let project = ProjectMetadata::from_options( - options, - SystemPathBuf::from(root), - None, - MisconfigurationMode::Fail, - ) - .map_err(into_error)?; + let project = + ProjectMetadata::from_options(options, SystemPathBuf::from(root), None, &FailStrategy) + .map_err(into_error)?; - let mut db = ProjectDatabase::new(project, system.clone()).map_err(into_error)?; + let mut db = + ProjectDatabase::new(project, system.clone(), &FailStrategy).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. @@ -132,12 +129,12 @@ impl Workspace { options, self.db.project().root(&self.db).to_path_buf(), None, - MisconfigurationMode::Fail, + &FailStrategy, ) .map_err(into_error)?; let program_settings = project - .to_program_settings(&self.system, self.db.vendored()) + .to_program_settings(&self.system, self.db.vendored(), &FailStrategy) .map_err(into_error)?; Program::get(&self.db).update_from_settings(&mut self.db, program_settings);