[ty] Check assignments to implicit global symbols are assignable to the types declared on types.ModuleType (#18077)

This commit is contained in:
Alex Waygood 2025-05-13 16:37:20 -04:00 committed by GitHub
parent 301d9985d8
commit 65e48cb439
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 137 additions and 19 deletions

View file

@ -40,6 +40,39 @@ reveal_type(__dict__)
reveal_type(__init__)
```
## `ModuleType` globals combined with explicit assignments and declarations
A `ModuleType` attribute can be overridden in the global scope with a different type, but it must be
a type assignable to the declaration on `ModuleType` unless it is accompanied by an explicit
redeclaration:
`module.py`:
```py
__file__ = None
__path__: list[str] = []
__doc__: int # error: [invalid-declaration] "Cannot declare type `int` for inferred type `str | None`"
# error: [invalid-declaration] "Cannot shadow implicit global attribute `__package__` with declaration of type `int`"
__package__: int = 42
__spec__ = 42 # error: [invalid-assignment] "Object of type `Literal[42]` is not assignable to `ModuleSpec | None`"
```
`main.py`:
```py
import module
reveal_type(module.__file__) # revealed: Unknown | None
reveal_type(module.__path__) # revealed: list[str]
reveal_type(module.__doc__) # revealed: Unknown
reveal_type(module.__spec__) # revealed: Unknown | ModuleSpec | None
def nested_scope():
global __loader__
reveal_type(__loader__) # revealed: LoaderProtocol | None
__loader__ = 56 # error: [invalid-assignment] "Object of type `Literal[56]` is not assignable to `LoaderProtocol | None`"
```
## Accessed as attributes
`ModuleType` attributes can also be accessed as attributes on module-literal types. The special
@ -105,16 +138,16 @@ defined as a global, however, a name lookup should union the `ModuleType` type w
conditionally defined type:
```py
__file__ = 42
__file__ = "foo"
def returns_bool() -> bool:
return True
if returns_bool():
__name__ = 1
__name__ = 1 # error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`"
reveal_type(__file__) # revealed: Literal[42]
reveal_type(__name__) # revealed: Literal[1] | str
reveal_type(__file__) # revealed: Literal["foo"]
reveal_type(__name__) # revealed: str
```
## Conditionally global or `ModuleType` attribute, with annotation
@ -122,12 +155,14 @@ reveal_type(__name__) # revealed: Literal[1] | str
The same is true if the name is annotated:
```py
# error: [invalid-declaration] "Cannot shadow implicit global attribute `__file__` with declaration of type `int`"
__file__: int = 42
def returns_bool() -> bool:
return True
if returns_bool():
# error: [invalid-declaration] "Cannot shadow implicit global attribute `__name__` with declaration of type `int`"
__name__: int = 1
reveal_type(__file__) # revealed: Literal[42]