Compare commits
3 Commits
dcreager/c
...
gankra/sfi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df090b40af | ||
|
|
6d7aa3684c | ||
|
|
4f22ab8b70 |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3004,6 +3004,7 @@ dependencies = [
|
||||
"tikv-jemallocator",
|
||||
"tracing",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<File> = 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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<VendoredFileSystem> = 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(
|
||||
|
||||
@@ -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<ExitStatus> {
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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<S>(project_metadata: ProjectMetadata, system: S) -> anyhow::Result<Self>
|
||||
pub fn new<S, Strategy: MisconfigurationStrategy>(
|
||||
project_metadata: ProjectMetadata,
|
||||
system: S,
|
||||
strategy: &Strategy,
|
||||
) -> Result<Self, Strategy::Error<anyhow::Error>>
|
||||
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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<Self, ToSettingsError> {
|
||||
let (settings, diagnostics) = metadata.options().to_settings(db, metadata.root())?;
|
||||
pub fn from_metadata<Strategy: MisconfigurationStrategy>(
|
||||
db: &dyn Db,
|
||||
metadata: ProjectMetadata,
|
||||
strategy: &Strategy,
|
||||
) -> Result<Self, Strategy::Error<ToSettingsError>> {
|
||||
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));
|
||||
|
||||
@@ -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<SystemPathBuf>,
|
||||
|
||||
#[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<Strategy: MisconfigurationStrategy>(
|
||||
mut options: Options,
|
||||
root: SystemPathBuf,
|
||||
project: Option<&Project>,
|
||||
misconfiguration_mode: MisconfigurationMode,
|
||||
) -> Result<Self, ResolveRequiresPythonError> {
|
||||
strategy: &Strategy,
|
||||
) -> Result<Self, Strategy::Error<ResolveRequiresPythonError>> {
|
||||
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<Strategy: MisconfigurationStrategy>(
|
||||
&self,
|
||||
system: &dyn System,
|
||||
vendored: &VendoredFileSystem,
|
||||
) -> anyhow::Result<ProgramSettings> {
|
||||
self.options.to_program_settings(
|
||||
self.root(),
|
||||
self.name(),
|
||||
system,
|
||||
vendored,
|
||||
self.misconfiguration_mode,
|
||||
)
|
||||
strategy: &Strategy,
|
||||
) -> Result<ProgramSettings, Strategy::Error<anyhow::Error>> {
|
||||
self.options
|
||||
.to_program_settings(self.root(), self.name(), system, vendored, strategy)
|
||||
}
|
||||
|
||||
pub fn apply_overrides(&mut self, overrides: &ProjectOptionsOverrides) {
|
||||
|
||||
@@ -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<Strategy: MisconfigurationStrategy>(
|
||||
&self,
|
||||
project_root: &SystemPath,
|
||||
project_name: &str,
|
||||
system: &dyn System,
|
||||
vendored: &VendoredFileSystem,
|
||||
misconfiguration_mode: MisconfigurationMode,
|
||||
) -> anyhow::Result<ProgramSettings> {
|
||||
strategy: &Strategy,
|
||||
) -> Result<ProgramSettings, Strategy::Error<anyhow::Error>> {
|
||||
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<Strategy: MisconfigurationStrategy>(
|
||||
&self,
|
||||
project_root: &SystemPath,
|
||||
project_name: &str,
|
||||
@@ -257,8 +242,8 @@ impl Options {
|
||||
real_stdlib_path: Option<SystemPathBuf>,
|
||||
system: &dyn System,
|
||||
vendored: &VendoredFileSystem,
|
||||
misconfiguration_mode: MisconfigurationMode,
|
||||
) -> Result<SearchPaths, SearchPathValidationError> {
|
||||
strategy: &Strategy,
|
||||
) -> Result<SearchPaths, Strategy::Error<SearchPathValidationError>> {
|
||||
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<Strategy: MisconfigurationStrategy>(
|
||||
&self,
|
||||
db: &dyn Db,
|
||||
project_root: &SystemPath,
|
||||
) -> Result<(Settings, Vec<OptionDiagnostic>), ToSettingsError> {
|
||||
strategy: &Strategy,
|
||||
) -> Result<(Settings, Vec<OptionDiagnostic>), Strategy::Error<ToSettingsError>> {
|
||||
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/",
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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")?,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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<Strategy: MisconfigurationStrategy>(
|
||||
settings: &SearchPathSettings,
|
||||
system: &dyn System,
|
||||
vendored: &VendoredFileSystem,
|
||||
) -> Result<Self, SearchPathValidationError> {
|
||||
strategy: &Strategy,
|
||||
) -> Result<Self, Strategy::Error<SearchPathValidationError>> {
|
||||
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(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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<MockedTypeshed> {
|
||||
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<VendoredTypeshed> {
|
||||
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"),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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<T, Never> 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<T, E>
|
||||
/// {
|
||||
/// let x = something_fallible()?;
|
||||
/// Ok(x)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// New:
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn do_thing<Strategy: MisconfigurationStrategy>(strategy: &Strategy)
|
||||
/// -> Result<T, Strategy::Error<E>>
|
||||
/// {
|
||||
/// 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<T, E>` your function should
|
||||
/// return `Result<T, Strategy::Error<E>>`. Which simplifies to:
|
||||
///
|
||||
/// * [`FailStrategy`]: `Result<T, E>`
|
||||
/// * [`UseDefaultStrategy`]: `Result<T, Never>` ~= `T`
|
||||
///
|
||||
/// Notably, if your function returns `Result<T, Strategy::Error<E>>` 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<E>;
|
||||
|
||||
/// 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<T, E>(
|
||||
&self,
|
||||
result: Result<T, E>,
|
||||
fallback_fn: impl FnOnce(E) -> T,
|
||||
) -> Result<T, Self::Error<E>>;
|
||||
|
||||
/// 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<T, E>(
|
||||
&self,
|
||||
result: Result<T, E>,
|
||||
fallback_fn: impl FnOnce(E),
|
||||
) -> Result<Option<T>, Self::Error<E>>;
|
||||
|
||||
/// Convenience to convert the inner `Error` to `anyhow::Error`
|
||||
fn to_anyhow<T, E>(
|
||||
&self,
|
||||
result: Result<T, Self::Error<E>>,
|
||||
) -> Result<T, Self::Error<anyhow::Error>>
|
||||
where
|
||||
anyhow::Error: From<E>;
|
||||
|
||||
/// Convenience to map the inner `Error`
|
||||
fn map_err<T, E1, E2>(
|
||||
&self,
|
||||
result: Result<T, Self::Error<E1>>,
|
||||
map_err: impl FnOnce(E1) -> E2,
|
||||
) -> Result<T, Self::Error<E2>>;
|
||||
}
|
||||
|
||||
/// 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<E> = Never;
|
||||
fn fallback<T, E>(
|
||||
&self,
|
||||
result: Result<T, E>,
|
||||
fallback_fn: impl FnOnce(E) -> T,
|
||||
) -> Result<T, Self::Error<E>> {
|
||||
Ok(result.unwrap_or_else(fallback_fn))
|
||||
}
|
||||
|
||||
fn fallback_opt<T, E>(
|
||||
&self,
|
||||
result: Result<T, E>,
|
||||
fallback_fn: impl FnOnce(E),
|
||||
) -> Result<Option<T>, Self::Error<E>> {
|
||||
match result {
|
||||
Ok(val) => Ok(Some(val)),
|
||||
Err(e) => {
|
||||
fallback_fn(e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_anyhow<T, E>(
|
||||
&self,
|
||||
result: Result<T, Self::Error<E>>,
|
||||
) -> Result<T, Self::Error<anyhow::Error>>
|
||||
where
|
||||
anyhow::Error: From<E>,
|
||||
{
|
||||
let Ok(val) = result;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn map_err<T, E1, E2>(
|
||||
&self,
|
||||
result: Result<T, Self::Error<E1>>,
|
||||
_map_err: impl FnOnce(E1) -> E2,
|
||||
) -> Result<T, Self::Error<E2>> {
|
||||
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> = E;
|
||||
|
||||
fn fallback<T, E>(
|
||||
&self,
|
||||
result: Result<T, E>,
|
||||
_fallback_fn: impl FnOnce(E) -> T,
|
||||
) -> Result<T, Self::Error<E>> {
|
||||
result
|
||||
}
|
||||
|
||||
fn fallback_opt<T, E>(
|
||||
&self,
|
||||
result: Result<T, E>,
|
||||
_fallback_fn: impl FnOnce(E),
|
||||
) -> Result<Option<T>, Self::Error<E>> {
|
||||
result.map(Some)
|
||||
}
|
||||
|
||||
fn to_anyhow<T, E>(
|
||||
&self,
|
||||
result: Result<T, Self::Error<E>>,
|
||||
) -> Result<T, Self::Error<anyhow::Error>>
|
||||
where
|
||||
anyhow::Error: From<E>,
|
||||
{
|
||||
Ok(result?)
|
||||
}
|
||||
|
||||
fn map_err<T, E1, E2>(
|
||||
&self,
|
||||
result: Result<T, Self::Error<E1>>,
|
||||
map_err: impl FnOnce(E1) -> E2,
|
||||
) -> Result<T, Self::Error<E2>> {
|
||||
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<SystemPathBuf>,
|
||||
|
||||
/// 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<Strategy: MisconfigurationStrategy>(
|
||||
&self,
|
||||
system: &dyn System,
|
||||
vendored: &VendoredFileSystem,
|
||||
) -> Result<SearchPaths, SearchPathValidationError> {
|
||||
SearchPaths::from_settings(self, system, vendored)
|
||||
strategy: &Strategy,
|
||||
) -> Result<SearchPaths, Strategy::Error<SearchPathValidationError>> {
|
||||
SearchPaths::from_settings(self, system, vendored, strategy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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,14 @@ 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,
|
||||
);
|
||||
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 +1230,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(),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"),
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user