mirror of https://github.com/astral-sh/ruff
[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:
parent
20eb5b5b35
commit
963bc8c228
|
|
@ -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: ...
|
||||||
|
|
||||||
|
class Public:
|
||||||
a: int = 1
|
a: int = 1
|
||||||
b: str = 2 # error: [invalid-assignment]
|
b: str = 2 # error: [invalid-assignment]
|
||||||
c: Any = 3
|
c: Any = 3
|
||||||
d: int = any()
|
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,6 +64,7 @@ def any() -> Any: ...
|
||||||
def flag() -> bool:
|
def flag() -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
class Public:
|
||||||
a: int
|
a: int
|
||||||
b: str
|
b: str
|
||||||
c: Any
|
c: Any
|
||||||
|
|
@ -82,15 +75,11 @@ if flag:
|
||||||
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
|
||||||
|
|
||||||
|
class Public:
|
||||||
a: int
|
a: int
|
||||||
b: Any
|
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,6 +112,7 @@ def any() -> Any: ...
|
||||||
def flag() -> bool:
|
def flag() -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
class Public:
|
||||||
a = 1
|
a = 1
|
||||||
b = 2
|
b = 2
|
||||||
c = 3
|
c = 3
|
||||||
|
|
@ -139,19 +122,15 @@ if flag():
|
||||||
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
|
||||||
|
|
||||||
|
class Public:
|
||||||
if flag():
|
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
|
||||||
|
|
||||||
|
class Public:
|
||||||
if flag():
|
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
|
||||||
|
class Public:
|
||||||
# Undeclared:
|
# Undeclared:
|
||||||
a = 1
|
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
|
||||||
|
|
||||||
|
class Public:
|
||||||
if flag:
|
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
|
||||||
|
class Public:
|
||||||
if False:
|
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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue