Skip to content

Commit 058fc37

Browse files
authored
[ty] Fix panic 'missing root' when handling completion request (#20917)
1 parent ec9faa3 commit 058fc37

File tree

5 files changed

+90
-24
lines changed

5 files changed

+90
-24
lines changed

crates/ruff_db/src/files.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,17 @@ impl Files {
193193
roots.at(&absolute)
194194
}
195195

196+
/// The same as [`Self::root`] but panics if no root is found.
197+
#[track_caller]
198+
pub fn expect_root(&self, db: &dyn Db, path: &SystemPath) -> FileRoot {
199+
if let Some(root) = self.root(db, path) {
200+
return root;
201+
}
202+
203+
let roots = self.inner.roots.read().unwrap();
204+
panic!("No root found for path '{path}'. Known roots: {roots:#?}");
205+
}
206+
196207
/// Adds a new root for `path` and returns the root.
197208
///
198209
/// The root isn't added nor is the file root's kind updated if a root for `path` already exists.

crates/ruff_db/src/files/file_root.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ impl FileRoots {
8181
}
8282
}
8383

84+
tracing::debug!("Adding new file root '{path}' of kind {kind:?}");
85+
8486
// normalize the path to use `/` separators and escape the '{' and '}' characters,
8587
// which matchit uses for routing parameters
8688
let mut route = normalized_path.replace('{', "{{").replace('}', "}}");

crates/ty_python_semantic/src/module_resolver/list.rs

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,15 @@ fn list_modules_in<'db>(
6565
db: &'db dyn Db,
6666
search_path: SearchPathIngredient<'db>,
6767
) -> Vec<Module<'db>> {
68+
tracing::debug!("Listing modules in search path '{}'", search_path.path(db));
6869
let mut lister = Lister::new(db, search_path.path(db));
6970
match search_path.path(db).as_path() {
7071
SystemOrVendoredPathRef::System(system_search_path) => {
7172
// Read the revision on the corresponding file root to
7273
// register an explicit dependency on this directory. When
7374
// the revision gets bumped, the cache that Salsa creates
7475
// for this routine will be invalidated.
75-
let root = db
76-
.files()
77-
.root(db, system_search_path)
78-
.expect("System search path should have a registered root");
76+
let root = db.files().expect_root(db, system_search_path);
7977
let _ = root.revision(db);
8078

8179
let Ok(it) = db.system().read_directory(system_search_path) else {
@@ -969,10 +967,6 @@ mod tests {
969967
std::os::unix::fs::symlink(foo.as_std_path(), bar.as_std_path())?;
970968

971969
db.files().try_add_root(&db, &src, FileRootKind::Project);
972-
db.files()
973-
.try_add_root(&db, &site_packages, FileRootKind::LibrarySearchPath);
974-
db.files()
975-
.try_add_root(&db, &custom_typeshed, FileRootKind::LibrarySearchPath);
976970

977971
Program::from_settings(
978972
&db,
@@ -1468,6 +1462,55 @@ not_a_directory
14681462
);
14691463
}
14701464

1465+
#[test]
1466+
fn editable_installs_into_first_party_search_path() {
1467+
let mut db = TestDb::new();
1468+
1469+
let src = SystemPath::new("/src");
1470+
let venv_site_packages = SystemPathBuf::from("/venv-site-packages");
1471+
let site_packages_pth = venv_site_packages.join("foo.pth");
1472+
let editable_install_location = src.join("x/y/a.py");
1473+
1474+
db.write_files([
1475+
(&site_packages_pth, "/src/x/y/"),
1476+
(&editable_install_location, ""),
1477+
])
1478+
.unwrap();
1479+
1480+
db.files()
1481+
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
1482+
1483+
Program::from_settings(
1484+
&db,
1485+
ProgramSettings {
1486+
python_version: PythonVersionWithSource::default(),
1487+
python_platform: PythonPlatform::default(),
1488+
search_paths: SearchPathSettings {
1489+
site_packages_paths: vec![venv_site_packages],
1490+
..SearchPathSettings::new(vec![src.to_path_buf()])
1491+
}
1492+
.to_search_paths(db.system(), db.vendored())
1493+
.expect("Valid search path settings"),
1494+
},
1495+
);
1496+
1497+
insta::assert_debug_snapshot!(
1498+
list_snapshot_filter(&db, |m| m.name(&db).as_str() == "a"),
1499+
@r#"
1500+
[
1501+
Module::File("a", "editable", "/src/x/y/a.py", Module, None),
1502+
]
1503+
"#,
1504+
);
1505+
1506+
let editable_root = db
1507+
.files()
1508+
.root(&db, &editable_install_location)
1509+
.expect("file root for editable install");
1510+
1511+
assert_eq!(editable_root.path(&db), src);
1512+
}
1513+
14711514
#[test]
14721515
fn multiple_site_packages_with_editables() {
14731516
let mut db = TestDb::new();
@@ -1490,12 +1533,6 @@ not_a_directory
14901533

14911534
db.files()
14921535
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
1493-
db.files()
1494-
.try_add_root(&db, &venv_site_packages, FileRootKind::LibrarySearchPath);
1495-
db.files()
1496-
.try_add_root(&db, &system_site_packages, FileRootKind::LibrarySearchPath);
1497-
db.files()
1498-
.try_add_root(&db, SystemPath::new("/x"), FileRootKind::LibrarySearchPath);
14991536

15001537
Program::from_settings(
15011538
&db,
@@ -1625,8 +1662,6 @@ not_a_directory
16251662

16261663
db.files()
16271664
.try_add_root(&db, &project_directory, FileRootKind::Project);
1628-
db.files()
1629-
.try_add_root(&db, &site_packages, FileRootKind::LibrarySearchPath);
16301665

16311666
Program::from_settings(
16321667
&db,

crates/ty_python_semantic/src/module_resolver/module.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,7 @@ fn all_submodule_names_for_package<'db>(
175175
// tree. When the revision gets bumped, the cache
176176
// that Salsa creates does for this routine will be
177177
// invalidated.
178-
let root = db
179-
.files()
180-
.root(db, parent_directory)
181-
.expect("System search path should have a registered root");
178+
let root = db.files().expect_root(db, parent_directory);
182179
let _ = root.revision(db);
183180

184181
db.system()

crates/ty_python_semantic/src/module_resolver/resolver.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,9 +348,15 @@ impl SearchPaths {
348348
})
349349
}
350350

351+
/// Registers the file roots for all non-dynamically discovered search paths that aren't first-party.
351352
pub(crate) fn try_register_static_roots(&self, db: &dyn Db) {
352353
let files = db.files();
353-
for path in self.static_paths.iter().chain(self.site_packages.iter()) {
354+
for path in self
355+
.static_paths
356+
.iter()
357+
.chain(self.site_packages.iter())
358+
.chain(&self.stdlib_path)
359+
{
354360
if let Some(system_path) = path.as_system_path() {
355361
if !path.is_first_party() {
356362
files.try_add_root(db, system_path, FileRootKind::LibrarySearchPath);
@@ -451,9 +457,7 @@ pub(crate) fn dynamic_resolution_paths<'db>(
451457
continue;
452458
}
453459

454-
let site_packages_root = files
455-
.root(db, &site_packages_dir)
456-
.expect("Site-package root to have been created");
460+
let site_packages_root = files.expect_root(db, &site_packages_dir);
457461

458462
// This query needs to be re-executed each time a `.pth` file
459463
// is added, modified or removed from the `site-packages` directory.
@@ -500,6 +504,23 @@ pub(crate) fn dynamic_resolution_paths<'db>(
500504
"Adding editable installation to module resolution path {path}",
501505
path = installation
502506
);
507+
508+
// Register a file root for editable installs that are outside any other root
509+
// (Most importantly, don't register a root for editable installations from the project
510+
// directory as that would change the durability of files within those folders).
511+
// Not having an exact file root for editable installs just means that
512+
// some queries (like `list_modules_in`) will run slightly more frequently
513+
// than they would otherwise.
514+
if let Some(dynamic_path) = search_path.as_system_path() {
515+
if files.root(db, dynamic_path).is_none() {
516+
files.try_add_root(
517+
db,
518+
dynamic_path,
519+
FileRootKind::LibrarySearchPath,
520+
);
521+
}
522+
}
523+
503524
dynamic_paths.push(search_path);
504525
}
505526

0 commit comments

Comments
 (0)