mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-08 04:38:04 +00:00
[ty] Improve and document equivalence for module-literal types (#19243)
This commit is contained in:
parent
59aa869724
commit
934aaa23f3
3 changed files with 89 additions and 18 deletions
|
@ -502,5 +502,55 @@ def f6(a, /): ...
|
|||
static_assert(not is_equivalent_to(CallableTypeOf[f1], CallableTypeOf[f6]))
|
||||
```
|
||||
|
||||
## Module-literal types
|
||||
|
||||
Two "copies" of a single-file module are considered equivalent types, even if the different copies
|
||||
were originally imported in different first-party modules:
|
||||
|
||||
`module.py`:
|
||||
|
||||
```py
|
||||
import typing
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import typing
|
||||
from module import typing as other_typing
|
||||
from ty_extensions import TypeOf, static_assert, is_equivalent_to
|
||||
|
||||
static_assert(is_equivalent_to(TypeOf[typing], TypeOf[other_typing]))
|
||||
static_assert(is_equivalent_to(TypeOf[typing] | int | str, str | int | TypeOf[other_typing]))
|
||||
```
|
||||
|
||||
We currently do not consider module-literal types to be equivalent if the underlying module is a
|
||||
package and the different "copies" of the module were originally imported in different modules. This
|
||||
is because we might consider submodules to be available as attributes on one copy but not on the
|
||||
other, depending on whether those submodules were explicitly imported in the original importing
|
||||
module:
|
||||
|
||||
`module2.py`:
|
||||
|
||||
```py
|
||||
import importlib
|
||||
import importlib.abc
|
||||
```
|
||||
|
||||
`main2.py`:
|
||||
|
||||
```py
|
||||
import importlib
|
||||
from module2 import importlib as other_importlib
|
||||
from ty_extensions import TypeOf, static_assert, is_equivalent_to
|
||||
|
||||
# error: [unresolved-attribute] "Type `<module 'importlib'>` has no attribute `abc`"
|
||||
reveal_type(importlib.abc) # revealed: Unknown
|
||||
|
||||
reveal_type(other_importlib.abc) # revealed: <module 'importlib.abc'>
|
||||
|
||||
static_assert(not is_equivalent_to(TypeOf[importlib], TypeOf[other_importlib]))
|
||||
```
|
||||
|
||||
[materializations]: https://typing.python.org/en/latest/spec/glossary.html#term-materialize
|
||||
[the equivalence relation]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent
|
||||
|
|
|
@ -818,7 +818,11 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
pub fn module_literal(db: &'db dyn Db, importing_file: File, submodule: &Module) -> Self {
|
||||
Self::ModuleLiteral(ModuleLiteralType::new(db, importing_file, submodule))
|
||||
Self::ModuleLiteral(ModuleLiteralType::new(
|
||||
db,
|
||||
submodule,
|
||||
submodule.kind().is_package().then_some(importing_file),
|
||||
))
|
||||
}
|
||||
|
||||
pub const fn into_module_literal(self) -> Option<ModuleLiteralType<'db>> {
|
||||
|
@ -7503,20 +7507,34 @@ pub enum WrapperDescriptorKind {
|
|||
#[salsa::interned(debug)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct ModuleLiteralType<'db> {
|
||||
/// The file in which this module was imported.
|
||||
///
|
||||
/// We need this in order to know which submodules should be attached to it as attributes
|
||||
/// (because the submodules were also imported in this file).
|
||||
pub importing_file: File,
|
||||
|
||||
/// The imported module.
|
||||
pub module: Module,
|
||||
|
||||
/// The file in which this module was imported.
|
||||
///
|
||||
/// If the module is a module that could have submodules (a package),
|
||||
/// we need this in order to know which submodules should be attached to it as attributes
|
||||
/// (because the submodules were also imported in this file). For a package, this should
|
||||
/// therefore always be `Some()`. If the module is not a package, however, this should
|
||||
/// always be `None`: this helps reduce memory usage (the information is redundant for
|
||||
/// single-file modules), and ensures that two module-literal types that both refer to
|
||||
/// the same underlying single-file module are understood by ty as being equivalent types
|
||||
/// in all situations.
|
||||
_importing_file: Option<File>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for ModuleLiteralType<'_> {}
|
||||
|
||||
impl<'db> ModuleLiteralType<'db> {
|
||||
fn importing_file(self, db: &'db dyn Db) -> Option<File> {
|
||||
debug_assert_eq!(
|
||||
self._importing_file(db).is_some(),
|
||||
self.module(db).kind().is_package()
|
||||
);
|
||||
self._importing_file(db)
|
||||
}
|
||||
|
||||
fn static_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
// `__dict__` is a very special member that is never overridden by module globals;
|
||||
// we should always look it up directly as an attribute on `types.ModuleType`,
|
||||
|
@ -7536,8 +7554,8 @@ impl<'db> ModuleLiteralType<'db> {
|
|||
// the parent module's `__init__.py` file being evaluated. That said, we have
|
||||
// chosen to always have the submodule take priority. (This matches pyright's
|
||||
// current behavior, but is the opposite of mypy's current behavior.)
|
||||
if let Some(importing_file) = self.importing_file(db) {
|
||||
if let Some(submodule_name) = ModuleName::new(name) {
|
||||
let importing_file = self.importing_file(db);
|
||||
let imported_submodules = imported_modules(db, importing_file);
|
||||
let mut full_submodule_name = self.module(db).name().clone();
|
||||
full_submodule_name.extend(&submodule_name);
|
||||
|
@ -7548,6 +7566,7 @@ impl<'db> ModuleLiteralType<'db> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.module(db)
|
||||
.file()
|
||||
|
|
|
@ -199,8 +199,10 @@ impl<'db> AllMembers<'db> {
|
|||
|
||||
let module_name = module.name();
|
||||
self.members.extend(
|
||||
imported_modules(db, literal.importing_file(db))
|
||||
.iter()
|
||||
literal
|
||||
.importing_file(db)
|
||||
.into_iter()
|
||||
.flat_map(|file| imported_modules(db, file))
|
||||
.filter_map(|submodule_name| {
|
||||
let module = resolve_module(db, submodule_name)?;
|
||||
let ty = Type::module_literal(db, file, &module);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue