mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[red-knot] Extend instance/class attribute tests (#15959)
## Summary In preparation for creating some (sub) issues for https://github.com/astral-sh/ruff/issues/14164, I'm trying to document the current behavior (and a bug) a bit better.
This commit is contained in:
parent
7ca778f492
commit
eb08345fd5
1 changed files with 79 additions and 4 deletions
|
@ -210,6 +210,8 @@ def get_str() -> str:
|
||||||
return "a"
|
return "a"
|
||||||
|
|
||||||
class C:
|
class C:
|
||||||
|
z: int
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.x = get_int()
|
self.x = get_int()
|
||||||
self.y: int = 1
|
self.y: int = 1
|
||||||
|
@ -220,12 +222,14 @@ class C:
|
||||||
# TODO: this redeclaration should be an error
|
# TODO: this redeclaration should be an error
|
||||||
self.y: str = "a"
|
self.y: str = "a"
|
||||||
|
|
||||||
|
# TODO: this redeclaration should be an error
|
||||||
|
self.z: str = "a"
|
||||||
|
|
||||||
c_instance = C()
|
c_instance = C()
|
||||||
|
|
||||||
reveal_type(c_instance.x) # revealed: Unknown | int | str
|
reveal_type(c_instance.x) # revealed: Unknown | int | str
|
||||||
|
|
||||||
# TODO: We should probably infer `int | str` here.
|
|
||||||
reveal_type(c_instance.y) # revealed: int
|
reveal_type(c_instance.y) # revealed: int
|
||||||
|
reveal_type(c_instance.z) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Attributes defined in tuple unpackings
|
#### Attributes defined in tuple unpackings
|
||||||
|
@ -354,6 +358,77 @@ class C:
|
||||||
reveal_type(C().declared_and_bound) # revealed: Unknown
|
reveal_type(C().declared_and_bound) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Static methods do not influence implicitly defined attributes
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Other:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
class C:
|
||||||
|
@staticmethod
|
||||||
|
def f(other: Other) -> None:
|
||||||
|
other.x = 1
|
||||||
|
|
||||||
|
# error: [unresolved-attribute]
|
||||||
|
reveal_type(C.x) # revealed: Unknown
|
||||||
|
|
||||||
|
# TODO: this should raise `unresolved-attribute` as well, and the type should be `Unknown`
|
||||||
|
reveal_type(C().x) # revealed: Unknown | Literal[1]
|
||||||
|
|
||||||
|
# This also works if `staticmethod` is aliased:
|
||||||
|
|
||||||
|
my_staticmethod = staticmethod
|
||||||
|
|
||||||
|
class D:
|
||||||
|
@my_staticmethod
|
||||||
|
def f(other: Other) -> None:
|
||||||
|
other.x = 1
|
||||||
|
|
||||||
|
# error: [unresolved-attribute]
|
||||||
|
reveal_type(D.x) # revealed: Unknown
|
||||||
|
|
||||||
|
# TODO: this should raise `unresolved-attribute` as well, and the type should be `Unknown`
|
||||||
|
reveal_type(D().x) # revealed: Unknown | Literal[1]
|
||||||
|
```
|
||||||
|
|
||||||
|
If `staticmethod` is something else, that should not influence the behavior:
|
||||||
|
|
||||||
|
`other.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def staticmethod(f):
|
||||||
|
return f
|
||||||
|
|
||||||
|
class C:
|
||||||
|
@staticmethod
|
||||||
|
def f(self) -> None:
|
||||||
|
self.x = 1
|
||||||
|
|
||||||
|
reveal_type(C().x) # revealed: Unknown | Literal[1]
|
||||||
|
```
|
||||||
|
|
||||||
|
And if `staticmethod` is fully qualified, that should also be recognized:
|
||||||
|
|
||||||
|
`fully_qualified.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
class Other:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
class C:
|
||||||
|
@builtins.staticmethod
|
||||||
|
def f(other: Other) -> None:
|
||||||
|
other.x = 1
|
||||||
|
|
||||||
|
# error: [unresolved-attribute]
|
||||||
|
reveal_type(C.x) # revealed: Unknown
|
||||||
|
|
||||||
|
# TODO: this should raise `unresolved-attribute` as well, and the type should be `Unknown`
|
||||||
|
reveal_type(C().x) # revealed: Unknown | Literal[1]
|
||||||
|
```
|
||||||
|
|
||||||
#### Attributes defined in statically-known-to-be-false branches
|
#### Attributes defined in statically-known-to-be-false branches
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
@ -440,12 +515,12 @@ reveal_type(C.pure_class_variable) # revealed: Unknown
|
||||||
|
|
||||||
C.pure_class_variable = "overwritten on class"
|
C.pure_class_variable = "overwritten on class"
|
||||||
|
|
||||||
# TODO: should be `Literal["overwritten on class"]`
|
# TODO: should be `Unknown | Literal["value set in class method"]` or
|
||||||
|
# Literal["overwritten on class"]`, once/if we support local narrowing.
|
||||||
# error: [unresolved-attribute]
|
# error: [unresolved-attribute]
|
||||||
reveal_type(C.pure_class_variable) # revealed: Unknown
|
reveal_type(C.pure_class_variable) # revealed: Unknown
|
||||||
|
|
||||||
c_instance = C()
|
c_instance = C()
|
||||||
# TODO: should be `Literal["overwritten on class"]`
|
|
||||||
reveal_type(c_instance.pure_class_variable) # revealed: Unknown | Literal["value set in class method"]
|
reveal_type(c_instance.pure_class_variable) # revealed: Unknown | Literal["value set in class method"]
|
||||||
|
|
||||||
# TODO: should raise an error.
|
# TODO: should raise an error.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue