Set Durability to 'HIGH' for most inputs and third-party libraries (#12566)

This commit is contained in:
Micha Reiser 2024-07-30 11:03:59 +02:00 committed by GitHub
parent fb9f566f56
commit a2286c8e47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 212 additions and 126 deletions

6
Cargo.lock generated
View file

@ -2724,7 +2724,7 @@ checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]] [[package]]
name = "salsa" name = "salsa"
version = "0.18.0" version = "0.18.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=cd339fc1c9a6ea0ffb1d09bd3bffb5633f776ef3#cd339fc1c9a6ea0ffb1d09bd3bffb5633f776ef3" source = "git+https://github.com/MichaReiser/salsa.git?rev=0cae5c52a3240172ef0be5c9d19e63448c53397c#0cae5c52a3240172ef0be5c9d19e63448c53397c"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"boomphf", "boomphf",
@ -2744,12 +2744,12 @@ dependencies = [
[[package]] [[package]]
name = "salsa-macro-rules" name = "salsa-macro-rules"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=cd339fc1c9a6ea0ffb1d09bd3bffb5633f776ef3#cd339fc1c9a6ea0ffb1d09bd3bffb5633f776ef3" source = "git+https://github.com/MichaReiser/salsa.git?rev=0cae5c52a3240172ef0be5c9d19e63448c53397c#0cae5c52a3240172ef0be5c9d19e63448c53397c"
[[package]] [[package]]
name = "salsa-macros" name = "salsa-macros"
version = "0.18.0" version = "0.18.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=cd339fc1c9a6ea0ffb1d09bd3bffb5633f776ef3#cd339fc1c9a6ea0ffb1d09bd3bffb5633f776ef3" source = "git+https://github.com/MichaReiser/salsa.git?rev=0cae5c52a3240172ef0be5c9d19e63448c53397c#0cae5c52a3240172ef0be5c9d19e63448c53397c"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",

View file

@ -107,7 +107,7 @@ rand = { version = "0.8.5" }
rayon = { version = "1.10.0" } rayon = { version = "1.10.0" }
regex = { version = "1.10.2" } regex = { version = "1.10.2" }
rustc-hash = { version = "2.0.0" } rustc-hash = { version = "2.0.0" }
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "cd339fc1c9a6ea0ffb1d09bd3bffb5633f776ef3" } salsa = { git = "https://github.com/MichaReiser/salsa.git", rev = "0cae5c52a3240172ef0be5c9d19e63448c53397c" }
schemars = { version = "0.8.16" } schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" } seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }

View file

@ -173,7 +173,7 @@ impl RootDatabase {
let package = workspace.package(self, &path); let package = workspace.package(self, &path);
let file = system_path_to_file(self, &path); let file = system_path_to_file(self, &path);
if let (Some(package), Some(file)) = (package, file) { if let (Some(package), Ok(file)) = (package, file) {
package.add_file(self, file); package.add_file(self, file);
} }
} }

View file

@ -1,4 +1,4 @@
use salsa::Setter as _; use salsa::{Durability, Setter as _};
use std::{collections::BTreeMap, sync::Arc}; use std::{collections::BTreeMap, sync::Arc};
use rustc_hash::{FxBuildHasher, FxHashSet}; use rustc_hash::{FxBuildHasher, FxHashSet};
@ -105,7 +105,9 @@ impl Workspace {
packages.insert(package.root.clone(), Package::from_metadata(db, package)); packages.insert(package.root.clone(), Package::from_metadata(db, package));
} }
Workspace::new(db, metadata.root, None, packages) Workspace::builder(metadata.root, None, packages)
.durability(Durability::MEDIUM)
.new(db)
} }
pub fn root(self, db: &dyn Db) -> &SystemPath { pub fn root(self, db: &dyn Db) -> &SystemPath {
@ -136,7 +138,9 @@ impl Workspace {
new_packages.insert(path, package); new_packages.insert(path, package);
} }
self.set_package_tree(db).to(new_packages); self.set_package_tree(db)
.with_durability(Durability::MEDIUM)
.to(new_packages);
} }
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
@ -305,20 +309,28 @@ impl Package {
} }
fn from_metadata(db: &dyn Db, metadata: PackageMetadata) -> Self { fn from_metadata(db: &dyn Db, metadata: PackageMetadata) -> Self {
Self::new(db, metadata.name, metadata.root, PackageFiles::default()) Self::builder(metadata.name, metadata.root, PackageFiles::default())
.durability(Durability::MEDIUM)
.new(db)
} }
fn update(self, db: &mut dyn Db, metadata: PackageMetadata) { fn update(self, db: &mut dyn Db, metadata: PackageMetadata) {
let root = self.root(db); let root = self.root(db);
assert_eq!(root, metadata.root()); assert_eq!(root, metadata.root());
self.set_name(db).to(metadata.name); if self.name(db) != metadata.name() {
self.set_name(db)
.with_durability(Durability::MEDIUM)
.to(metadata.name);
}
} }
#[tracing::instrument(level = "debug", skip(db))] #[tracing::instrument(level = "debug", skip(db))]
pub fn reload_files(self, db: &mut dyn Db) { pub fn reload_files(self, db: &mut dyn Db) {
// Force a re-index of the files in the next revision. if !self.file_set(db).is_lazy() {
self.set_file_set(db).to(PackageFiles::lazy()); // Force a re-index of the files in the next revision.
self.set_file_set(db).to(PackageFiles::lazy());
}
} }
} }
@ -364,7 +376,7 @@ fn discover_package_files(db: &dyn Db, path: &SystemPath) -> FxHashSet<File> {
for path in paths { for path in paths {
// If this returns `None`, then the file was deleted between the `walk_directory` call and now. // If this returns `None`, then the file was deleted between the `walk_directory` call and now.
// We can ignore this. // We can ignore this.
if let Some(file) = system_path_to_file(db.upcast(), &path) { if let Ok(file) = system_path_to_file(db.upcast(), &path) {
files.insert(file); files.insert(file);
} }
} }

View file

@ -47,6 +47,10 @@ impl PackageFiles {
} }
} }
pub fn is_lazy(&self) -> bool {
matches!(*self.state.lock().unwrap(), State::Lazy)
}
/// Returns a mutable view on the index that allows cheap in-place mutations. /// Returns a mutable view on the index that allows cheap in-place mutations.
/// ///
/// The changes are automatically written back to the database once the view is dropped. /// The changes are automatically written back to the database once the view is dropped.

View file

@ -10,7 +10,7 @@ use red_knot::watch;
use red_knot::watch::{directory_watcher, WorkspaceWatcher}; use red_knot::watch::{directory_watcher, WorkspaceWatcher};
use red_knot::workspace::WorkspaceMetadata; use red_knot::workspace::WorkspaceMetadata;
use red_knot_module_resolver::{resolve_module, ModuleName}; use red_knot_module_resolver::{resolve_module, ModuleName};
use ruff_db::files::{system_path_to_file, File}; use ruff_db::files::{system_path_to_file, File, FileError};
use ruff_db::program::{Program, ProgramSettings, SearchPathSettings, TargetVersion}; use ruff_db::program::{Program, ProgramSettings, SearchPathSettings, TargetVersion};
use ruff_db::source::source_text; use ruff_db::source::source_text;
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf}; use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
@ -82,7 +82,7 @@ impl TestCase {
collected collected
} }
fn system_file(&self, path: impl AsRef<SystemPath>) -> Option<File> { fn system_file(&self, path: impl AsRef<SystemPath>) -> Result<File, FileError> {
system_path_to_file(self.db(), path.as_ref()) system_path_to_file(self.db(), path.as_ref())
} }
} }
@ -190,7 +190,7 @@ fn new_file() -> anyhow::Result<()> {
let bar_file = case.system_file(&bar_path).unwrap(); let bar_file = case.system_file(&bar_path).unwrap();
let foo_path = case.workspace_path("foo.py"); let foo_path = case.workspace_path("foo.py");
assert_eq!(case.system_file(&foo_path), None); assert_eq!(case.system_file(&foo_path), Err(FileError::NotFound));
assert_eq!(&case.collect_package_files(&bar_path), &[bar_file]); assert_eq!(&case.collect_package_files(&bar_path), &[bar_file]);
std::fs::write(foo_path.as_std_path(), "print('Hello')")?; std::fs::write(foo_path.as_std_path(), "print('Hello')")?;
@ -213,7 +213,7 @@ fn new_ignored_file() -> anyhow::Result<()> {
let bar_file = case.system_file(&bar_path).unwrap(); let bar_file = case.system_file(&bar_path).unwrap();
let foo_path = case.workspace_path("foo.py"); let foo_path = case.workspace_path("foo.py");
assert_eq!(case.system_file(&foo_path), None); assert_eq!(case.system_file(&foo_path), Err(FileError::NotFound));
assert_eq!(&case.collect_package_files(&bar_path), &[bar_file]); assert_eq!(&case.collect_package_files(&bar_path), &[bar_file]);
std::fs::write(foo_path.as_std_path(), "print('Hello')")?; std::fs::write(foo_path.as_std_path(), "print('Hello')")?;
@ -222,7 +222,7 @@ fn new_ignored_file() -> anyhow::Result<()> {
case.db_mut().apply_changes(changes); case.db_mut().apply_changes(changes);
assert!(case.system_file(&foo_path).is_some()); assert!(case.system_file(&foo_path).is_ok());
assert_eq!(&case.collect_package_files(&bar_path), &[bar_file]); assert_eq!(&case.collect_package_files(&bar_path), &[bar_file]);
Ok(()) Ok(())
@ -234,9 +234,7 @@ fn changed_file() -> anyhow::Result<()> {
let mut case = setup([("foo.py", foo_source)])?; let mut case = setup([("foo.py", foo_source)])?;
let foo_path = case.workspace_path("foo.py"); let foo_path = case.workspace_path("foo.py");
let foo = case let foo = case.system_file(&foo_path)?;
.system_file(&foo_path)
.ok_or_else(|| anyhow!("Foo not found"))?;
assert_eq!(source_text(case.db(), foo).as_str(), foo_source); assert_eq!(source_text(case.db(), foo).as_str(), foo_source);
assert_eq!(&case.collect_package_files(&foo_path), &[foo]); assert_eq!(&case.collect_package_files(&foo_path), &[foo]);
@ -260,9 +258,7 @@ fn changed_metadata() -> anyhow::Result<()> {
let mut case = setup([("foo.py", "")])?; let mut case = setup([("foo.py", "")])?;
let foo_path = case.workspace_path("foo.py"); let foo_path = case.workspace_path("foo.py");
let foo = case let foo = case.system_file(&foo_path)?;
.system_file(&foo_path)
.ok_or_else(|| anyhow!("Foo not found"))?;
assert_eq!( assert_eq!(
foo.permissions(case.db()), foo.permissions(case.db()),
Some( Some(
@ -302,9 +298,7 @@ fn deleted_file() -> anyhow::Result<()> {
let mut case = setup([("foo.py", foo_source)])?; let mut case = setup([("foo.py", foo_source)])?;
let foo_path = case.workspace_path("foo.py"); let foo_path = case.workspace_path("foo.py");
let foo = case let foo = case.system_file(&foo_path)?;
.system_file(&foo_path)
.ok_or_else(|| anyhow!("Foo not found"))?;
assert!(foo.exists(case.db())); assert!(foo.exists(case.db()));
assert_eq!(&case.collect_package_files(&foo_path), &[foo]); assert_eq!(&case.collect_package_files(&foo_path), &[foo]);
@ -333,9 +327,7 @@ fn move_file_to_trash() -> anyhow::Result<()> {
let trash_path = case.root_path().join(".trash"); let trash_path = case.root_path().join(".trash");
std::fs::create_dir_all(trash_path.as_std_path())?; std::fs::create_dir_all(trash_path.as_std_path())?;
let foo = case let foo = case.system_file(&foo_path)?;
.system_file(&foo_path)
.ok_or_else(|| anyhow!("Foo not found"))?;
assert!(foo.exists(case.db())); assert!(foo.exists(case.db()));
assert_eq!(&case.collect_package_files(&foo_path), &[foo]); assert_eq!(&case.collect_package_files(&foo_path), &[foo]);
@ -367,7 +359,7 @@ fn move_file_to_workspace() -> anyhow::Result<()> {
let foo_in_workspace_path = case.workspace_path("foo.py"); let foo_in_workspace_path = case.workspace_path("foo.py");
assert!(case.system_file(&foo_path).is_some()); assert!(case.system_file(&foo_path).is_ok());
assert_eq!(&case.collect_package_files(&bar_path), &[bar]); assert_eq!(&case.collect_package_files(&bar_path), &[bar]);
assert!(case assert!(case
.db() .db()
@ -381,9 +373,7 @@ fn move_file_to_workspace() -> anyhow::Result<()> {
case.db_mut().apply_changes(changes); case.db_mut().apply_changes(changes);
let foo_in_workspace = case let foo_in_workspace = case.system_file(&foo_in_workspace_path)?;
.system_file(&foo_in_workspace_path)
.ok_or_else(|| anyhow!("Foo not found"))?;
assert!(foo_in_workspace.exists(case.db())); assert!(foo_in_workspace.exists(case.db()));
assert_eq!( assert_eq!(
@ -401,9 +391,7 @@ fn rename_file() -> anyhow::Result<()> {
let foo_path = case.workspace_path("foo.py"); let foo_path = case.workspace_path("foo.py");
let bar_path = case.workspace_path("bar.py"); let bar_path = case.workspace_path("bar.py");
let foo = case let foo = case.system_file(&foo_path)?;
.system_file(&foo_path)
.ok_or_else(|| anyhow!("Foo not found"))?;
assert_eq!(case.collect_package_files(&foo_path), [foo]); assert_eq!(case.collect_package_files(&foo_path), [foo]);
@ -415,9 +403,7 @@ fn rename_file() -> anyhow::Result<()> {
assert!(!foo.exists(case.db())); assert!(!foo.exists(case.db()));
let bar = case let bar = case.system_file(&bar_path)?;
.system_file(&bar_path)
.ok_or_else(|| anyhow!("Bar not found"))?;
assert!(bar.exists(case.db())); assert!(bar.exists(case.db()));
assert_eq!(case.collect_package_files(&foo_path), [bar]); assert_eq!(case.collect_package_files(&foo_path), [bar]);

View file

@ -5,7 +5,7 @@ use std::sync::Arc;
use camino::{Utf8Path, Utf8PathBuf}; use camino::{Utf8Path, Utf8PathBuf};
use ruff_db::files::{system_path_to_file, vendored_path_to_file, File}; use ruff_db::files::{system_path_to_file, vendored_path_to_file, File, FileError};
use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_db::system::{System, SystemPath, SystemPathBuf};
use ruff_db::vendored::{VendoredPath, VendoredPathBuf}; use ruff_db::vendored::{VendoredPath, VendoredPathBuf};
@ -68,16 +68,18 @@ impl ModulePath {
SearchPathInner::Extra(search_path) SearchPathInner::Extra(search_path)
| SearchPathInner::FirstParty(search_path) | SearchPathInner::FirstParty(search_path)
| SearchPathInner::SitePackages(search_path) | SearchPathInner::SitePackages(search_path)
| SearchPathInner::Editable(search_path) => resolver | SearchPathInner::Editable(search_path) => {
.system() system_path_to_file(resolver.db.upcast(), search_path.join(relative_path))
.is_directory(&search_path.join(relative_path)), == Err(FileError::IsADirectory)
}
SearchPathInner::StandardLibraryCustom(stdlib_root) => { SearchPathInner::StandardLibraryCustom(stdlib_root) => {
match query_stdlib_version(Some(stdlib_root), relative_path, resolver) { match query_stdlib_version(Some(stdlib_root), relative_path, resolver) {
TypeshedVersionsQueryResult::DoesNotExist => false, TypeshedVersionsQueryResult::DoesNotExist => false,
TypeshedVersionsQueryResult::Exists TypeshedVersionsQueryResult::Exists
| TypeshedVersionsQueryResult::MaybeExists => resolver | TypeshedVersionsQueryResult::MaybeExists => {
.system() system_path_to_file(resolver.db.upcast(), stdlib_root.join(relative_path))
.is_directory(&stdlib_root.join(relative_path)), == Err(FileError::IsADirectory)
}
} }
} }
SearchPathInner::StandardLibraryVendored(stdlib_root) => { SearchPathInner::StandardLibraryVendored(stdlib_root) => {
@ -105,10 +107,9 @@ impl ModulePath {
| SearchPathInner::SitePackages(search_path) | SearchPathInner::SitePackages(search_path)
| SearchPathInner::Editable(search_path) => { | SearchPathInner::Editable(search_path) => {
let absolute_path = search_path.join(relative_path); let absolute_path = search_path.join(relative_path);
system_path_to_file(resolver.db.upcast(), absolute_path.join("__init__.py")) system_path_to_file(resolver.db.upcast(), absolute_path.join("__init__.py")).is_ok()
.is_some()
|| system_path_to_file(resolver.db.upcast(), absolute_path.join("__init__.py")) || system_path_to_file(resolver.db.upcast(), absolute_path.join("__init__.py"))
.is_some() .is_ok()
} }
SearchPathInner::StandardLibraryCustom(search_path) => { SearchPathInner::StandardLibraryCustom(search_path) => {
match query_stdlib_version(Some(search_path), relative_path, resolver) { match query_stdlib_version(Some(search_path), relative_path, resolver) {
@ -118,7 +119,7 @@ impl ModulePath {
resolver.db.upcast(), resolver.db.upcast(),
search_path.join(relative_path).join("__init__.pyi"), search_path.join(relative_path).join("__init__.pyi"),
) )
.is_some(), .is_ok(),
} }
} }
SearchPathInner::StandardLibraryVendored(search_path) => { SearchPathInner::StandardLibraryVendored(search_path) => {
@ -145,14 +146,14 @@ impl ModulePath {
| SearchPathInner::FirstParty(search_path) | SearchPathInner::FirstParty(search_path)
| SearchPathInner::SitePackages(search_path) | SearchPathInner::SitePackages(search_path)
| SearchPathInner::Editable(search_path) => { | SearchPathInner::Editable(search_path) => {
system_path_to_file(db, search_path.join(relative_path)) system_path_to_file(db, search_path.join(relative_path)).ok()
} }
SearchPathInner::StandardLibraryCustom(stdlib_root) => { SearchPathInner::StandardLibraryCustom(stdlib_root) => {
match query_stdlib_version(Some(stdlib_root), relative_path, resolver) { match query_stdlib_version(Some(stdlib_root), relative_path, resolver) {
TypeshedVersionsQueryResult::DoesNotExist => None, TypeshedVersionsQueryResult::DoesNotExist => None,
TypeshedVersionsQueryResult::Exists TypeshedVersionsQueryResult::Exists
| TypeshedVersionsQueryResult::MaybeExists => { | TypeshedVersionsQueryResult::MaybeExists => {
system_path_to_file(db, stdlib_root.join(relative_path)) system_path_to_file(db, stdlib_root.join(relative_path)).ok()
} }
} }
} }
@ -161,7 +162,7 @@ impl ModulePath {
TypeshedVersionsQueryResult::DoesNotExist => None, TypeshedVersionsQueryResult::DoesNotExist => None,
TypeshedVersionsQueryResult::Exists TypeshedVersionsQueryResult::Exists
| TypeshedVersionsQueryResult::MaybeExists => { | TypeshedVersionsQueryResult::MaybeExists => {
vendored_path_to_file(db, stdlib_root.join(relative_path)) vendored_path_to_file(db, stdlib_root.join(relative_path)).ok()
} }
} }
} }
@ -301,11 +302,15 @@ pub(crate) enum SearchPathValidationError {
/// (This is only relevant for stdlib search paths.) /// (This is only relevant for stdlib search paths.)
NoStdlibSubdirectory(SystemPathBuf), NoStdlibSubdirectory(SystemPathBuf),
/// The path provided by the user is a directory, /// The typeshed path provided by the user is a directory,
/// but no `stdlib/VERSIONS` file exists. /// but no `stdlib/VERSIONS` file exists.
/// (This is only relevant for stdlib search paths.) /// (This is only relevant for stdlib search paths.)
NoVersionsFile(SystemPathBuf), NoVersionsFile(SystemPathBuf),
/// `stdlib/VERSIONS` is a directory.
/// (This is only relevant for stdlib search paths.)
VersionsIsADirectory(SystemPathBuf),
/// The path provided by the user is a directory, /// The path provided by the user is a directory,
/// and a `stdlib/VERSIONS` file exists, but it fails to parse. /// and a `stdlib/VERSIONS` file exists, but it fails to parse.
/// (This is only relevant for stdlib search paths.) /// (This is only relevant for stdlib search paths.)
@ -320,6 +325,7 @@ impl fmt::Display for SearchPathValidationError {
write!(f, "The directory at {path} has no `stdlib/` subdirectory") write!(f, "The directory at {path} has no `stdlib/` subdirectory")
} }
Self::NoVersionsFile(path) => write!(f, "Expected a file at {path}/stldib/VERSIONS"), Self::NoVersionsFile(path) => write!(f, "Expected a file at {path}/stldib/VERSIONS"),
Self::VersionsIsADirectory(path) => write!(f, "{path}/stldib/VERSIONS is a directory."),
Self::VersionsParseError(underlying_error) => underlying_error.fmt(f), Self::VersionsParseError(underlying_error) => underlying_error.fmt(f),
} }
} }
@ -408,10 +414,13 @@ impl SearchPath {
typeshed.to_path_buf(), typeshed.to_path_buf(),
)); ));
} }
let Some(typeshed_versions) = system_path_to_file(db.upcast(), stdlib.join("VERSIONS")) let typeshed_versions =
else { system_path_to_file(db.upcast(), stdlib.join("VERSIONS")).map_err(|err| match err {
return Err(SearchPathValidationError::NoVersionsFile(typeshed)); FileError::NotFound => SearchPathValidationError::NoVersionsFile(typeshed),
}; FileError::IsADirectory => {
SearchPathValidationError::VersionsIsADirectory(typeshed)
}
})?;
crate::typeshed::parse_typeshed_versions(db, typeshed_versions) crate::typeshed::parse_typeshed_versions(db, typeshed_versions)
.as_ref() .as_ref()
.map_err(|validation_error| { .map_err(|validation_error| {

View file

@ -489,6 +489,7 @@ fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, File, Mod
if is_builtin_module && !search_path.is_standard_library() { if is_builtin_module && !search_path.is_standard_library() {
continue; continue;
} }
let mut components = name.components(); let mut components = name.components();
let module_name = components.next_back()?; let module_name = components.next_back()?;
@ -1282,6 +1283,7 @@ mod tests {
db.memory_file_system() db.memory_file_system()
.remove_directory(foo_init_path.parent().unwrap())?; .remove_directory(foo_init_path.parent().unwrap())?;
File::sync_path(&mut db, &foo_init_path); File::sync_path(&mut db, &foo_init_path);
File::sync_path(&mut db, foo_init_path.parent().unwrap());
let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve"); let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve");
assert_eq!(&src.join("foo.py"), foo_module.file().path(&db)); assert_eq!(&src.join("foo.py"), foo_module.file().path(&db));
@ -1312,7 +1314,7 @@ mod tests {
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
assert_eq!(functools_module.search_path(), &stdlib); assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!( assert_eq!(
Some(functools_module.file()), Ok(functools_module.file()),
system_path_to_file(&db, &stdlib_functools_path) system_path_to_file(&db, &stdlib_functools_path)
); );
@ -1332,7 +1334,7 @@ mod tests {
); );
assert_eq!(functools_module.search_path(), &stdlib); assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!( assert_eq!(
Some(functools_module.file()), Ok(functools_module.file()),
system_path_to_file(&db, &stdlib_functools_path) system_path_to_file(&db, &stdlib_functools_path)
); );
} }
@ -1358,7 +1360,7 @@ mod tests {
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
assert_eq!(functools_module.search_path(), &stdlib); assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!( assert_eq!(
Some(functools_module.file()), Ok(functools_module.file()),
system_path_to_file(&db, stdlib.join("functools.pyi")) system_path_to_file(&db, stdlib.join("functools.pyi"))
); );
@ -1369,7 +1371,7 @@ mod tests {
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
assert_eq!(functools_module.search_path(), &src); assert_eq!(functools_module.search_path(), &src);
assert_eq!( assert_eq!(
Some(functools_module.file()), Ok(functools_module.file()),
system_path_to_file(&db, &src_functools_path) system_path_to_file(&db, &src_functools_path)
); );
} }
@ -1400,7 +1402,7 @@ mod tests {
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
assert_eq!(functools_module.search_path(), &src); assert_eq!(functools_module.search_path(), &src);
assert_eq!( assert_eq!(
Some(functools_module.file()), Ok(functools_module.file()),
system_path_to_file(&db, &src_functools_path) system_path_to_file(&db, &src_functools_path)
); );
@ -1413,7 +1415,7 @@ mod tests {
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap(); let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
assert_eq!(functools_module.search_path(), &stdlib); assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!( assert_eq!(
Some(functools_module.file()), Ok(functools_module.file()),
system_path_to_file(&db, stdlib.join("functools.pyi")) system_path_to_file(&db, stdlib.join("functools.pyi"))
); );
} }

View file

@ -1,5 +1,4 @@
use ruff_db::program::TargetVersion; use ruff_db::program::TargetVersion;
use ruff_db::system::System;
use ruff_db::vendored::VendoredFileSystem; use ruff_db::vendored::VendoredFileSystem;
use crate::db::Db; use crate::db::Db;
@ -20,10 +19,6 @@ impl<'db> ResolverState<'db> {
} }
} }
pub(crate) fn system(&self) -> &dyn System {
self.db.system()
}
pub(crate) fn vendored(&self) -> &VendoredFileSystem { pub(crate) fn vendored(&self) -> &VendoredFileSystem {
self.db.vendored() self.db.vendored()
} }

View file

@ -52,7 +52,7 @@ impl<'db> LazyTypeshedVersions<'db> {
} else { } else {
return &VENDORED_VERSIONS; return &VENDORED_VERSIONS;
}; };
let Some(versions_file) = system_path_to_file(db.upcast(), &versions_path) else { let Ok(versions_file) = system_path_to_file(db.upcast(), &versions_path) else {
todo!( todo!(
"Still need to figure out how to handle VERSIONS files being deleted \ "Still need to figure out how to handle VERSIONS files being deleted \
from custom typeshed directories! Expected a file to exist at {versions_path}" from custom typeshed directories! Expected a file to exist at {versions_path}"

View file

@ -1,8 +1,9 @@
use std::fmt::Formatter;
use std::sync::Arc; use std::sync::Arc;
use countme::Count; use countme::Count;
use dashmap::mapref::entry::Entry; use dashmap::mapref::entry::Entry;
use salsa::Setter; use salsa::{Durability, Setter};
pub use file_root::{FileRoot, FileRootKind}; pub use file_root::{FileRoot, FileRootKind};
pub use path::FilePath; pub use path::FilePath;
@ -13,28 +14,35 @@ use crate::files::file_root::FileRoots;
use crate::files::private::FileStatus; use crate::files::private::FileStatus;
use crate::system::{Metadata, SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf}; use crate::system::{Metadata, SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
use crate::vendored::{VendoredPath, VendoredPathBuf}; use crate::vendored::{VendoredPath, VendoredPathBuf};
use crate::{Db, FxDashMap}; use crate::{vendored, Db, FxDashMap};
mod file_root; mod file_root;
mod path; mod path;
/// Interns a file system path and returns a salsa `File` ingredient. /// Interns a file system path and returns a salsa `File` ingredient.
/// ///
/// Returns `None` if the path doesn't exist, isn't accessible, or if the path points to a directory. /// Returns `Err` if the path doesn't exist, isn't accessible, or if the path points to a directory.
#[inline] #[inline]
pub fn system_path_to_file(db: &dyn Db, path: impl AsRef<SystemPath>) -> Option<File> { pub fn system_path_to_file(db: &dyn Db, path: impl AsRef<SystemPath>) -> Result<File, FileError> {
let file = db.files().system(db, path.as_ref()); let file = db.files().system(db, path.as_ref());
// It's important that `vfs.file_system` creates a `VfsFile` even for files that don't exist or don't // It's important that `vfs.file_system` creates a `VfsFile` even for files that don't exist or don't
// exist anymore so that Salsa can track that the caller of this function depends on the existence of // exist anymore so that Salsa can track that the caller of this function depends on the existence of
// that file. This function filters out files that don't exist, but Salsa will know that it must // that file. This function filters out files that don't exist, but Salsa will know that it must
// re-run the calling query whenever the `file`'s status changes (because of the `.status` call here). // re-run the calling query whenever the `file`'s status changes (because of the `.status` call here).
file.exists(db).then_some(file) match file.status(db) {
FileStatus::Exists => Ok(file),
FileStatus::IsADirectory => Err(FileError::IsADirectory),
FileStatus::NotFound => Err(FileError::NotFound),
}
} }
/// Interns a vendored file path. Returns `Some` if the vendored file for `path` exists and `None` otherwise. /// Interns a vendored file path. Returns `Some` if the vendored file for `path` exists and `None` otherwise.
#[inline] #[inline]
pub fn vendored_path_to_file(db: &dyn Db, path: impl AsRef<VendoredPath>) -> Option<File> { pub fn vendored_path_to_file(
db: &dyn Db,
path: impl AsRef<VendoredPath>,
) -> Result<File, FileError> {
db.files().vendored(db, path.as_ref()) db.files().vendored(db, path.as_ref())
} }
@ -68,7 +76,7 @@ impl Files {
/// For a non-existing file, creates a new salsa [`File`] ingredient and stores it for future lookups. /// For a non-existing file, creates a new salsa [`File`] ingredient and stores it for future lookups.
/// ///
/// The operation always succeeds even if the path doesn't exist on disk, isn't accessible or if the path points to a directory. /// The operation always succeeds even if the path doesn't exist on disk, isn't accessible or if the path points to a directory.
/// In these cases, a file with status [`FileStatus::Deleted`] is returned. /// In these cases, a file with status [`FileStatus::NotFound`] is returned.
#[tracing::instrument(level = "trace", skip(self, db))] #[tracing::instrument(level = "trace", skip(self, db))]
fn system(&self, db: &dyn Db, path: &SystemPath) -> File { fn system(&self, db: &dyn Db, path: &SystemPath) -> File {
let absolute = SystemPath::absolute(path, db.system().current_directory()); let absolute = SystemPath::absolute(path, db.system().current_directory());
@ -78,27 +86,32 @@ impl Files {
.system_by_path .system_by_path
.entry(absolute.clone()) .entry(absolute.clone())
.or_insert_with(|| { .or_insert_with(|| {
// TODO: Set correct durability according to source root.
let metadata = db.system().path_metadata(path); let metadata = db.system().path_metadata(path);
let durability = self
.root(db, path)
.map_or(Durability::default(), |root| root.durability(db));
match metadata { let (permissions, revision, status) = match metadata {
Ok(metadata) if metadata.file_type().is_file() => File::new( Ok(metadata) if metadata.file_type().is_file() => (
db,
FilePath::System(absolute),
metadata.permissions(), metadata.permissions(),
metadata.revision(), metadata.revision(),
FileStatus::Exists, FileStatus::Exists,
Count::default(),
), ),
_ => File::new( Ok(metadata) if metadata.file_type().is_directory() => {
db, (None, FileRevision::zero(), FileStatus::IsADirectory)
FilePath::System(absolute), }
None, _ => (None, FileRevision::zero(), FileStatus::NotFound),
FileRevision::zero(), };
FileStatus::Deleted,
Count::default(), File::builder(
), FilePath::System(absolute),
} permissions,
revision,
status,
Count::default(),
)
.durability(durability)
.new(db)
}) })
} }
@ -114,20 +127,27 @@ impl Files {
/// Looks up a vendored file by its path. Returns `Some` if a vendored file for the given path /// Looks up a vendored file by its path. Returns `Some` if a vendored file for the given path
/// exists and `None` otherwise. /// exists and `None` otherwise.
#[tracing::instrument(level = "trace", skip(self, db))] #[tracing::instrument(level = "trace", skip(self, db))]
fn vendored(&self, db: &dyn Db, path: &VendoredPath) -> Option<File> { fn vendored(&self, db: &dyn Db, path: &VendoredPath) -> Result<File, FileError> {
let file = match self.inner.vendored_by_path.entry(path.to_path_buf()) { let file = match self.inner.vendored_by_path.entry(path.to_path_buf()) {
Entry::Occupied(entry) => *entry.get(), Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
let metadata = db.vendored().metadata(path).ok()?; let metadata = match db.vendored().metadata(path) {
Ok(metadata) => match metadata.kind() {
vendored::FileType::File => metadata,
vendored::FileType::Directory => return Err(FileError::IsADirectory),
},
Err(_) => return Err(FileError::NotFound),
};
let file = File::new( let file = File::builder(
db,
FilePath::Vendored(path.to_path_buf()), FilePath::Vendored(path.to_path_buf()),
Some(0o444), Some(0o444),
metadata.revision(), metadata.revision(),
FileStatus::Exists, FileStatus::Exists,
Count::default(), Count::default(),
); )
.durability(Durability::HIGH)
.new(db);
entry.insert(file); entry.insert(file);
@ -135,7 +155,7 @@ impl Files {
} }
}; };
Some(file) Ok(file)
} }
/// Looks up a virtual file by its `path`. /// Looks up a virtual file by its `path`.
@ -210,8 +230,7 @@ impl Files {
let inner = Arc::clone(&db.files().inner); let inner = Arc::clone(&db.files().inner);
for entry in inner.system_by_path.iter_mut() { for entry in inner.system_by_path.iter_mut() {
if entry.key().starts_with(&path) { if entry.key().starts_with(&path) {
let file = entry.value(); File::sync_system_path(db, entry.key(), Some(*entry.value()));
file.sync(db);
} }
} }
@ -219,7 +238,9 @@ impl Files {
for root in roots.all() { for root in roots.all() {
if root.path(db).starts_with(&path) { if root.path(db).starts_with(&path) {
root.set_revision(db).to(FileRevision::now()); root.set_revision(db)
.with_durability(Durability::HIGH)
.to(FileRevision::now());
} }
} }
} }
@ -236,14 +257,15 @@ impl Files {
pub fn sync_all(db: &mut dyn Db) { pub fn sync_all(db: &mut dyn Db) {
let inner = Arc::clone(&db.files().inner); let inner = Arc::clone(&db.files().inner);
for entry in inner.system_by_path.iter_mut() { for entry in inner.system_by_path.iter_mut() {
let file = entry.value(); File::sync_system_path(db, entry.key(), Some(*entry.value()));
file.sync(db);
} }
let roots = inner.roots.read().unwrap(); let roots = inner.roots.read().unwrap();
for root in roots.all() { for root in roots.all() {
root.set_revision(db).to(FileRevision::now()); root.set_revision(db)
.with_durability(Durability::HIGH)
.to(FileRevision::now());
} }
} }
} }
@ -335,6 +357,7 @@ impl File {
#[tracing::instrument(level = "debug", skip(db))] #[tracing::instrument(level = "debug", skip(db))]
pub fn sync_path(db: &mut dyn Db, path: &SystemPath) { pub fn sync_path(db: &mut dyn Db, path: &SystemPath) {
let absolute = SystemPath::absolute(path, db.system().current_directory()); let absolute = SystemPath::absolute(path, db.system().current_directory());
Files::touch_root(db, &absolute);
Self::sync_system_path(db, &absolute, None); Self::sync_system_path(db, &absolute, None);
} }
@ -345,6 +368,7 @@ impl File {
match path { match path {
FilePath::System(system) => { FilePath::System(system) => {
Files::touch_root(db, &system);
Self::sync_system_path(db, &system, Some(self)); Self::sync_system_path(db, &system, Some(self));
} }
FilePath::Vendored(_) => { FilePath::Vendored(_) => {
@ -357,34 +381,56 @@ impl File {
} }
fn sync_system_path(db: &mut dyn Db, path: &SystemPath, file: Option<File>) { fn sync_system_path(db: &mut dyn Db, path: &SystemPath, file: Option<File>) {
Files::touch_root(db, path);
let Some(file) = file.or_else(|| db.files().try_system(db, path)) else { let Some(file) = file.or_else(|| db.files().try_system(db, path)) else {
return; return;
}; };
let metadata = db.system().path_metadata(path); let metadata = db.system().path_metadata(path);
Self::sync_impl(db, metadata, file); let durability = db.files().root(db, path).map(|root| root.durability(db));
Self::sync_impl(db, metadata, file, durability);
} }
fn sync_system_virtual_path(db: &mut dyn Db, path: &SystemVirtualPath, file: File) { fn sync_system_virtual_path(db: &mut dyn Db, path: &SystemVirtualPath, file: File) {
let metadata = db.system().virtual_path_metadata(path); let metadata = db.system().virtual_path_metadata(path);
Self::sync_impl(db, metadata, file); Self::sync_impl(db, metadata, file, None);
} }
/// Private method providing the implementation for [`Self::sync_system_path`] and /// Private method providing the implementation for [`Self::sync_system_path`] and
/// [`Self::sync_system_virtual_path`]. /// [`Self::sync_system_virtual_path`].
fn sync_impl(db: &mut dyn Db, metadata: crate::system::Result<Metadata>, file: File) { fn sync_impl(
db: &mut dyn Db,
metadata: crate::system::Result<Metadata>,
file: File,
durability: Option<Durability>,
) {
let (status, revision, permission) = match metadata { let (status, revision, permission) = match metadata {
Ok(metadata) if metadata.file_type().is_file() => ( Ok(metadata) if metadata.file_type().is_file() => (
FileStatus::Exists, FileStatus::Exists,
metadata.revision(), metadata.revision(),
metadata.permissions(), metadata.permissions(),
), ),
_ => (FileStatus::Deleted, FileRevision::zero(), None), Ok(metadata) if metadata.file_type().is_directory() => {
(FileStatus::IsADirectory, FileRevision::zero(), None)
}
_ => (FileStatus::NotFound, FileRevision::zero(), None),
}; };
file.set_status(db).to(status); let durability = durability.unwrap_or_default();
file.set_revision(db).to(revision);
file.set_permissions(db).to(permission); if file.status(db) != status {
file.set_status(db).with_durability(durability).to(status);
}
if file.revision(db) != revision {
file.set_revision(db)
.with_durability(durability)
.to(revision);
}
if file.permissions(db) != permission {
file.set_permissions(db)
.with_durability(durability)
.to(permission);
}
} }
/// Returns `true` if the file exists. /// Returns `true` if the file exists.
@ -401,15 +447,35 @@ mod private {
/// The file exists. /// The file exists.
Exists, Exists,
/// The file was deleted, didn't exist to begin with or the path isn't a file. /// The path isn't a file and instead points to a directory.
Deleted, IsADirectory,
/// The path doesn't exist, isn't accessible, or no longer exists.
NotFound,
} }
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FileError {
IsADirectory,
NotFound,
}
impl std::fmt::Display for FileError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FileError::IsADirectory => f.write_str("Is a directory"),
FileError::NotFound => f.write_str("Not found"),
}
}
}
impl std::error::Error for FileError {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::file_revision::FileRevision; use crate::file_revision::FileRevision;
use crate::files::{system_path_to_file, vendored_path_to_file}; use crate::files::{system_path_to_file, vendored_path_to_file, FileError};
use crate::system::DbWithTestSystem; use crate::system::DbWithTestSystem;
use crate::tests::TestDb; use crate::tests::TestDb;
use crate::vendored::tests::VendoredFileSystemBuilder; use crate::vendored::tests::VendoredFileSystemBuilder;
@ -435,7 +501,7 @@ mod tests {
let test = system_path_to_file(&db, "test.py"); let test = system_path_to_file(&db, "test.py");
assert_eq!(test, None); assert_eq!(test, Err(FileError::NotFound));
} }
#[test] #[test]
@ -477,6 +543,9 @@ mod tests {
fn stubbed_vendored_file_non_existing() { fn stubbed_vendored_file_non_existing() {
let db = TestDb::new(); let db = TestDb::new();
assert_eq!(vendored_path_to_file(&db, "test.py"), None); assert_eq!(
vendored_path_to_file(&db, "test.py"),
Err(FileError::NotFound)
);
} }
} }

View file

@ -1,6 +1,7 @@
use std::fmt::Formatter; use std::fmt::Formatter;
use path_slash::PathExt; use path_slash::PathExt;
use salsa::Durability;
use crate::file_revision::FileRevision; use crate::file_revision::FileRevision;
use crate::system::{SystemPath, SystemPathBuf}; use crate::system::{SystemPath, SystemPathBuf};
@ -83,7 +84,9 @@ impl FileRoots {
let mut route = normalized_path.replace('{', "{{").replace('}', "}}"); let mut route = normalized_path.replace('{', "{{").replace('}', "}}");
// Insert a new source root // Insert a new source root
let root = FileRoot::new(db, path, kind, FileRevision::now()); let root = FileRoot::builder(path, kind, FileRevision::now())
.durability(Durability::HIGH)
.new(db);
// Insert a path that matches the root itself // Insert a path that matches the root itself
self.by_path.insert(route.clone(), root).unwrap(); self.by_path.insert(route.clone(), root).unwrap();

View file

@ -95,8 +95,8 @@ impl FilePath {
#[inline] #[inline]
pub fn to_file(&self, db: &dyn Db) -> Option<File> { pub fn to_file(&self, db: &dyn Db) -> Option<File> {
match self { match self {
FilePath::System(path) => system_path_to_file(db, path), FilePath::System(path) => system_path_to_file(db, path).ok(),
FilePath::Vendored(path) => vendored_path_to_file(db, path), FilePath::Vendored(path) => vendored_path_to_file(db, path).ok(),
FilePath::SystemVirtual(_) => None, FilePath::SystemVirtual(_) => None,
} }
} }

View file

@ -1,4 +1,5 @@
use crate::{system::SystemPathBuf, Db}; use crate::{system::SystemPathBuf, Db};
use salsa::Durability;
#[salsa::input(singleton)] #[salsa::input(singleton)]
pub struct Program { pub struct Program {
@ -10,7 +11,9 @@ pub struct Program {
impl Program { impl Program {
pub fn from_settings(db: &dyn Db, settings: ProgramSettings) -> Self { pub fn from_settings(db: &dyn Db, settings: ProgramSettings) -> Self {
Program::new(db, settings.target_version, settings.search_paths) Program::builder(settings.target_version, settings.search_paths)
.durability(Durability::HIGH)
.new(db)
} }
} }

View file

@ -207,7 +207,9 @@ impl MemoryFileSystem {
let normalized = self.normalize_path(path.as_ref()); let normalized = self.normalize_path(path.as_ref());
get_or_create_file(&mut by_path, &normalized)?.content = content.to_string(); let file = get_or_create_file(&mut by_path, &normalized)?;
file.content = content.to_string();
file.last_modified = FileTime::now();
Ok(()) Ok(())
} }

View file

@ -1,3 +1,7 @@
use std::any::Any;
use std::panic::RefUnwindSafe;
use std::sync::Arc;
use ruff_notebook::{Notebook, NotebookError}; use ruff_notebook::{Notebook, NotebookError};
use ruff_python_trivia::textwrap; use ruff_python_trivia::textwrap;
@ -6,9 +10,6 @@ use crate::system::{
DirectoryEntry, MemoryFileSystem, Metadata, Result, System, SystemPath, SystemVirtualPath, DirectoryEntry, MemoryFileSystem, Metadata, Result, System, SystemPath, SystemVirtualPath,
}; };
use crate::Db; use crate::Db;
use std::any::Any;
use std::panic::RefUnwindSafe;
use std::sync::Arc;
use super::walk_directory::WalkDirectoryBuilder; use super::walk_directory::WalkDirectoryBuilder;