[ty] Support goto-definition on vendored typeshed stubs (#21020)

This is an alternative to #21012 that more narrowly handles this logic
in the stub-mapping machinery rather than pervasively allowing us to
identify cached files as typeshed stubs. Much of the logic is the same
(pulling the logic out of ty_server so it can be reused).

I don't have a good sense for if one approach is "better" or "worse" in
terms of like, semantics and Weird Bugs that this can cause. This one is
just "less spooky in its broad consequences" and "less muddying of
separation of concerns" and puts the extra logic on a much colder path.
I won't be surprised if one day the previous implementation needs to be
revisited for its more sweeping effects but for now this is good.

Fixes https://github.com/astral-sh/ty/issues/1054
This commit is contained in:
Aria Desires 2025-10-21 13:38:40 -04:00 committed by GitHub
parent 9d1ffd605c
commit 2e13b13012
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 83 additions and 22 deletions

View file

@ -1138,8 +1138,10 @@ mod resolve_definition {
}
use indexmap::IndexSet;
use ruff_db::files::{File, FileRange};
use ruff_db::files::{File, FileRange, vendored_path_to_file};
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_db::system::SystemPath;
use ruff_db::vendored::VendoredPathBuf;
use ruff_python_ast as ast;
use rustc_hash::FxHashSet;
use tracing::trace;
@ -1397,17 +1399,42 @@ mod resolve_definition {
pub fn map_stub_definition<'db>(
db: &'db dyn Db,
def: &ResolvedDefinition<'db>,
cached_vendored_typeshed: Option<&SystemPath>,
) -> Option<Vec<ResolvedDefinition<'db>>> {
trace!("Stub mapping definition...");
// If the file isn't a stub, this is presumably the real definition
let stub_file = def.file(db);
trace!("Stub mapping definition in: {}", stub_file.path(db));
if !stub_file.is_stub(db) {
trace!("File isn't a stub, no stub mapping to do");
return None;
}
// We write vendored typeshed stubs to disk in the cache, and consequently "forget"
// that they're typeshed when an IDE hands those paths back to us later. For most
// purposes this seemingly doesn't matter at all, and avoids issues with someone
// editing the cache by hand in their IDE and us getting confused about the contents
// of the file (hello and welcome to anyone who has found Bigger Issues this causes).
//
// The major exception is in exactly stub-mapping, where we need to "remember" that
// we're in typeshed to successfully stub-map to the Real Stdlib. So here we attempt
// to do just that. The resulting file must not be used for anything other than
// this module lookup, as the `ResolvedDefinition` we're handling isn't for that file.
let mut stub_file_for_module_lookup = stub_file;
if let Some(vendored_typeshed) = cached_vendored_typeshed
&& let Some(stub_path) = stub_file.path(db).as_system_path()
&& let Ok(rel_path) = stub_path.strip_prefix(vendored_typeshed)
&& let Ok(typeshed_file) =
vendored_path_to_file(db, VendoredPathBuf::from(rel_path.as_str()))
{
trace!(
"Stub is cached vendored typeshed: {}",
typeshed_file.path(db)
);
stub_file_for_module_lookup = typeshed_file;
}
// It's definitely a stub, so now rerun module resolution but with stubs disabled.
let stub_module = file_to_module(db, stub_file)?;
let stub_module = file_to_module(db, stub_file_for_module_lookup)?;
trace!("Found stub module: {}", stub_module.name(db));
let real_module = resolve_real_module(db, stub_module.name(db))?;
trace!("Found real module: {}", real_module.name(db));