[ty] Reformulation of public symbol inference test suite (#20667)

## Summary

Reformulation of the public symbol type inference test suite to use
class scopes instead of module scopes. This is in preparation for an
upcoming change to module-global scopes (#20664).

## Test Plan

Updated tests
This commit is contained in:
David Peter 2025-10-01 14:26:17 +02:00 committed by GitHub
parent 20eb5b5b35
commit 963bc8c228
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 85 additions and 129 deletions

View File

@ -36,34 +36,26 @@ In particular, we should raise errors in the "possibly-undeclared-and-unbound" a
If a symbol has a declared type (`int`), we use that even if there is a more precise inferred type If a symbol has a declared type (`int`), we use that even if there is a more precise inferred type
(`Literal[1]`), or a conflicting inferred type (`str` vs. `Literal[2]` below): (`Literal[1]`), or a conflicting inferred type (`str` vs. `Literal[2]` below):
`mod.py`:
```py ```py
from typing import Any from typing import Any
def any() -> Any: ... def any() -> Any: ...
a: int = 1 class Public:
b: str = 2 # error: [invalid-assignment] a: int = 1
c: Any = 3 b: str = 2 # error: [invalid-assignment]
d: int = any() c: Any = 3
``` d: int = any()
```py reveal_type(Public.a) # revealed: int
from mod import a, b, c, d reveal_type(Public.b) # revealed: str
reveal_type(Public.c) # revealed: Any
reveal_type(a) # revealed: int reveal_type(Public.d) # revealed: int
reveal_type(b) # revealed: str
reveal_type(c) # revealed: Any
reveal_type(d) # revealed: int
``` ```
### Declared and possibly unbound ### Declared and possibly unbound
If a symbol is declared and *possibly* unbound, we trust that other module and use the declared type If a symbol is declared and *possibly* unbound, we trust the declared type without raising an error.
without raising an error.
`mod.py`:
```py ```py
from typing import Any from typing import Any
@ -72,25 +64,22 @@ def any() -> Any: ...
def flag() -> bool: def flag() -> bool:
return True return True
a: int class Public:
b: str a: int
c: Any b: str
d: int c: Any
d: int
if flag: if flag:
a = 1 a = 1
b = 2 # error: [invalid-assignment] b = 2 # error: [invalid-assignment]
c = 3 c = 3
d = any() d = any()
```
```py reveal_type(Public.a) # revealed: int
from mod import a, b, c, d reveal_type(Public.b) # revealed: str
reveal_type(Public.c) # revealed: Any
reveal_type(a) # revealed: int reveal_type(Public.d) # revealed: int
reveal_type(b) # revealed: str
reveal_type(c) # revealed: Any
reveal_type(d) # revealed: int
``` ```
### Declared and unbound ### Declared and unbound
@ -98,20 +87,15 @@ reveal_type(d) # revealed: int
Similarly, if a symbol is declared but unbound, we do not raise an error. We trust that this symbol Similarly, if a symbol is declared but unbound, we do not raise an error. We trust that this symbol
is available somehow and simply use the declared type. is available somehow and simply use the declared type.
`mod.py`:
```py ```py
from typing import Any from typing import Any
a: int class Public:
b: Any a: int
``` b: Any
```py reveal_type(Public.a) # revealed: int
from mod import a, b reveal_type(Public.b) # revealed: Any
reveal_type(a) # revealed: int
reveal_type(b) # revealed: Any
``` ```
## Possibly undeclared ## Possibly undeclared
@ -121,8 +105,6 @@ reveal_type(b) # revealed: Any
If a symbol is possibly undeclared but definitely bound, we use the union of the declared and If a symbol is possibly undeclared but definitely bound, we use the union of the declared and
inferred types: inferred types:
`mod.py`:
```py ```py
from typing import Any from typing import Any
@ -130,28 +112,25 @@ def any() -> Any: ...
def flag() -> bool: def flag() -> bool:
return True return True
a = 1 class Public:
b = 2 a = 1
c = 3 b = 2
d = any() c = 3
if flag(): d = any()
if flag():
a: int a: int
b: Any b: Any
c: str # error: [invalid-declaration] c: str # error: [invalid-declaration]
d: int d: int
```
```py reveal_type(Public.a) # revealed: int
from mod import a, b, c, d reveal_type(Public.b) # revealed: Literal[2] | Any
reveal_type(Public.c) # revealed: Literal[3] | Unknown
reveal_type(a) # revealed: int reveal_type(Public.d) # revealed: Any | int
reveal_type(b) # revealed: Literal[2] | Any
reveal_type(c) # revealed: Literal[3] | Unknown
reveal_type(d) # revealed: Any | int
# External modifications of `a` that violate the declared type are not allowed: # External modifications of `a` that violate the declared type are not allowed:
# error: [invalid-assignment] # error: [invalid-assignment]
a = None Public.a = None
``` ```
### Possibly undeclared and possibly unbound ### Possibly undeclared and possibly unbound
@ -161,32 +140,28 @@ inferred types. This case is interesting because the "possibly declared" definit
same as the "possibly bound" definition (symbol `b`). Note that we raise a `possibly-missing-import` same as the "possibly bound" definition (symbol `b`). Note that we raise a `possibly-missing-import`
error for both `a` and `b`: error for both `a` and `b`:
`mod.py`:
```py ```py
from typing import Any from typing import Any
def flag() -> bool: def flag() -> bool:
return True return True
if flag(): class Public:
if flag():
a: Any = 1 a: Any = 1
b = 2 b = 2
else: else:
b: str b: str
```
```py # error: [possibly-missing-attribute]
# error: [possibly-missing-import] "Member `a` of module `mod` may be missing" reveal_type(Public.a) # revealed: Literal[1] | Any
# error: [possibly-missing-import] "Member `b` of module `mod` may be missing" # error: [possibly-missing-attribute]
from mod import a, b reveal_type(Public.b) # revealed: Literal[2] | str
reveal_type(a) # revealed: Literal[1] | Any
reveal_type(b) # revealed: Literal[2] | str
# External modifications of `b` that violate the declared type are not allowed: # External modifications of `b` that violate the declared type are not allowed:
# error: [possibly-missing-attribute]
# error: [invalid-assignment] # error: [invalid-assignment]
b = None Public.b = None
``` ```
### Possibly undeclared and unbound ### Possibly undeclared and unbound
@ -194,26 +169,21 @@ b = None
If a symbol is possibly undeclared and definitely unbound, we currently do not raise an error. This If a symbol is possibly undeclared and definitely unbound, we currently do not raise an error. This
seems inconsistent when compared to the case just above. seems inconsistent when compared to the case just above.
`mod.py`:
```py ```py
def flag() -> bool: def flag() -> bool:
return True return True
if flag(): class Public:
if flag():
a: int a: int
```
```py
# TODO: this should raise an error. Once we fix this, update the section description and the table # TODO: this should raise an error. Once we fix this, update the section description and the table
# on top of this document. # on top of this document.
from mod import a reveal_type(Public.a) # revealed: int
reveal_type(a) # revealed: int
# External modifications to `a` that violate the declared type are not allowed: # External modifications to `a` that violate the declared type are not allowed:
# error: [invalid-assignment] # error: [invalid-assignment]
a = None Public.a = None
``` ```
## Undeclared ## Undeclared
@ -224,24 +194,19 @@ If a symbol is *undeclared*, we use the union of `Unknown` with the inferred typ
treat this case differently from the case where a symbol is implicitly declared with `Unknown`, treat this case differently from the case where a symbol is implicitly declared with `Unknown`,
possibly due to the usage of an unknown name in the annotation: possibly due to the usage of an unknown name in the annotation:
`mod.py`:
```py ```py
# Undeclared: class Public:
a = 1 # Undeclared:
a = 1
# Implicitly declared with `Unknown`, due to the usage of an unknown name in the annotation: # Implicitly declared with `Unknown`, due to the usage of an unknown name in the annotation:
b: SomeUnknownName = 1 # error: [unresolved-reference] b: SomeUnknownName = 1 # error: [unresolved-reference]
```
```py reveal_type(Public.a) # revealed: Unknown | Literal[1]
from mod import a, b reveal_type(Public.b) # revealed: Unknown
reveal_type(a) # revealed: Unknown | Literal[1]
reveal_type(b) # revealed: Unknown
# All external modifications of `a` are allowed: # All external modifications of `a` are allowed:
a = None Public.a = None
``` ```
### Undeclared and possibly unbound ### Undeclared and possibly unbound
@ -249,48 +214,39 @@ a = None
If a symbol is undeclared and *possibly* unbound, we currently do not raise an error. This seems If a symbol is undeclared and *possibly* unbound, we currently do not raise an error. This seems
inconsistent when compared to the "possibly-undeclared-and-possibly-unbound" case. inconsistent when compared to the "possibly-undeclared-and-possibly-unbound" case.
`mod.py`:
```py ```py
def flag() -> bool: def flag() -> bool:
return True return True
if flag: class Public:
if flag:
a = 1 a = 1
b: SomeUnknownName = 1 # error: [unresolved-reference] b: SomeUnknownName = 1 # error: [unresolved-reference]
```
```py # TODO: these should raise an error. Once we fix this, update the section description and the table
# TODO: this should raise an error. Once we fix this, update the section description and the table
# on top of this document. # on top of this document.
from mod import a, b reveal_type(Public.a) # revealed: Unknown | Literal[1]
reveal_type(Public.b) # revealed: Unknown
reveal_type(a) # revealed: Unknown | Literal[1]
reveal_type(b) # revealed: Unknown
# All external modifications of `a` are allowed: # All external modifications of `a` are allowed:
a = None Public.a = None
``` ```
### Undeclared and unbound ### Undeclared and unbound
If a symbol is undeclared *and* unbound, we infer `Unknown` and raise an error. If a symbol is undeclared *and* unbound, we infer `Unknown` and raise an error.
`mod.py`:
```py ```py
if False: class Public:
if False:
a: int = 1 a: int = 1
```
```py # error: [unresolved-attribute]
# error: [unresolved-import] reveal_type(Public.a) # revealed: Unknown
from mod import a
reveal_type(a) # revealed: Unknown # Modification attempts yield an error:
# error: [unresolved-attribute]
# Modifications allowed in this case: Public.a = None
a = None
``` ```
## In stub files ## In stub files