[ty] Implement non-stdlib stub mapping for classes and functions (#19471)

This implements mapping of definitions in stubs to definitions in the
"real" implementation using the approach described in
https://github.com/astral-sh/ty/issues/788#issuecomment-3097000287

I've tested this with goto-definition in vscode with code that uses
`colorama` and `types-colorama`.

Notably this implementation does not add support for stub-mapping stdlib
modules, which can be done as an essentially orthogonal followup in the
implementation of `resolve_real_module`.

Part of https://github.com/astral-sh/ty/issues/788
This commit is contained in:
Aria Desires 2025-07-22 08:42:55 -04:00 committed by GitHub
parent 6d4687c9af
commit c82fa94e0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 881 additions and 49 deletions

View file

@ -314,7 +314,11 @@ fn query_stdlib_version(
let Some(module_name) = stdlib_path_to_module_name(relative_path) else {
return TypeshedVersionsQueryResult::DoesNotExist;
};
let ResolverContext { db, python_version } = context;
let ResolverContext {
db,
python_version,
mode: _,
} = context;
typeshed_versions(*db).query_module(&module_name, *python_version)
}
@ -701,6 +705,7 @@ mod tests {
use ruff_python_ast::PythonVersion;
use crate::db::tests::TestDb;
use crate::module_resolver::resolver::ModuleResolveMode;
use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
use super::*;
@ -965,7 +970,8 @@ mod tests {
};
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
let resolver = ResolverContext::new(&db, PythonVersion::PY38);
let resolver =
ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::StubsAllowed);
let asyncio_regular_package = stdlib_path.join("asyncio");
assert!(asyncio_regular_package.is_directory(&resolver));
@ -995,7 +1001,8 @@ mod tests {
};
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
let resolver = ResolverContext::new(&db, PythonVersion::PY38);
let resolver =
ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::StubsAllowed);
let xml_namespace_package = stdlib_path.join("xml");
assert!(xml_namespace_package.is_directory(&resolver));
@ -1017,7 +1024,8 @@ mod tests {
};
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
let resolver = ResolverContext::new(&db, PythonVersion::PY38);
let resolver =
ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::StubsAllowed);
let functools_module = stdlib_path.join("functools.pyi");
assert!(functools_module.to_file(&resolver).is_some());
@ -1033,7 +1041,8 @@ mod tests {
};
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
let resolver = ResolverContext::new(&db, PythonVersion::PY38);
let resolver =
ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::StubsAllowed);
let collections_regular_package = stdlib_path.join("collections");
assert_eq!(collections_regular_package.to_file(&resolver), None);
@ -1049,7 +1058,8 @@ mod tests {
};
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
let resolver = ResolverContext::new(&db, PythonVersion::PY38);
let resolver =
ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::StubsAllowed);
let importlib_namespace_package = stdlib_path.join("importlib");
assert_eq!(importlib_namespace_package.to_file(&resolver), None);
@ -1070,7 +1080,8 @@ mod tests {
};
let (db, stdlib_path) = py38_typeshed_test_case(TYPESHED);
let resolver = ResolverContext::new(&db, PythonVersion::PY38);
let resolver =
ResolverContext::new(&db, PythonVersion::PY38, ModuleResolveMode::StubsAllowed);
let non_existent = stdlib_path.join("doesnt_even_exist");
assert_eq!(non_existent.to_file(&resolver), None);
@ -1098,7 +1109,8 @@ mod tests {
};
let (db, stdlib_path) = py39_typeshed_test_case(TYPESHED);
let resolver = ResolverContext::new(&db, PythonVersion::PY39);
let resolver =
ResolverContext::new(&db, PythonVersion::PY39, ModuleResolveMode::StubsAllowed);
// Since we've set the target version to Py39,
// `collections` should now exist as a directory, according to VERSIONS...
@ -1129,7 +1141,8 @@ mod tests {
};
let (db, stdlib_path) = py39_typeshed_test_case(TYPESHED);
let resolver = ResolverContext::new(&db, PythonVersion::PY39);
let resolver =
ResolverContext::new(&db, PythonVersion::PY39, ModuleResolveMode::StubsAllowed);
// The `importlib` directory now also exists
let importlib_namespace_package = stdlib_path.join("importlib");
@ -1153,7 +1166,8 @@ mod tests {
};
let (db, stdlib_path) = py39_typeshed_test_case(TYPESHED);
let resolver = ResolverContext::new(&db, PythonVersion::PY39);
let resolver =
ResolverContext::new(&db, PythonVersion::PY39, ModuleResolveMode::StubsAllowed);
// The `xml` package no longer exists on py39:
let xml_namespace_package = stdlib_path.join("xml");