From 859ff2f01670c43ffff1ea597c8a2e375ada0fbe Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 10 Sep 2026 11:20:46 +0200 Subject: [PATCH] [ty] Track symlinked directory status in listings (#28482) --- crates/ruff_db/src/files/directory.rs | 45 ++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/crates/ruff_db/src/files/directory.rs b/crates/ruff_db/src/files/directory.rs index ba2f445a6d..450170b8fb 100644 --- a/crates/ruff_db/src/files/directory.rs +++ b/crates/ruff_db/src/files/directory.rs @@ -44,7 +44,7 @@ impl DirectoryListing { match self.file_type(name) { Some(FileType::Directory) => true, Some(FileType::File) | None => false, - Some(FileType::Symlink) => db.system().is_directory(&directory.join(name)), + Some(FileType::Symlink) => system_path_to_directory(db, directory.join(name)).is_ok(), } } @@ -140,6 +140,49 @@ mod tests { use crate::system::{DbWithWritableSystem as _, SystemPath}; use crate::tests::TestDb; + #[test] + #[cfg(all(unix, feature = "os"))] + fn symlink_target_changes_without_parent_listing_change() -> std::io::Result<()> { + use super::system_path_to_directory; + use crate::Db; + use crate::files::{File, FilePath}; + use crate::system::{DbWithTestSystem as _, OsSystem}; + + #[salsa::tracked] + fn linked_directory_exists(db: &dyn Db, directory: File) -> bool { + let FilePath::System(path) = directory.path(db) else { + return false; + }; + directory_listing(db, path) + .is_ok_and(|listing| listing.entry_is_directory(db, path, "link")) + } + + let temp_dir = tempfile::tempdir()?; + let root = SystemPath::from_std_path(temp_dir.path()) + .ok_or_else(|| std::io::Error::other("temporary directory is not UTF-8"))?; + let directory = root.join("src"); + let target = root.join("target"); + let link = directory.join("link"); + + let mut db = TestDb::new(); + db.use_system(OsSystem::new(root)); + std::fs::create_dir(directory.as_std_path())?; + std::os::unix::fs::symlink(target.as_std_path(), link.as_std_path())?; + + let directory_file = + system_path_to_directory(&db, &directory).map_err(std::io::Error::other)?; + + assert!(!linked_directory_exists(&db, directory_file)); + + std::fs::create_dir(target.as_std_path())?; + // The link's target changed, but the directory containing the link did not. + File::sync_path(&mut db, &link); + + assert!(linked_directory_exists(&db, directory_file)); + + Ok(()) + } + #[test] fn listing_is_sorted() -> std::io::Result<()> { let mut db = TestDb::new();