Skip to content

Commit

Permalink
[red-knot] Extend instance/class attribute tests (#15959)
Browse files Browse the repository at this point in the history
## Summary

In preparation for creating some (sub) issues for
#14164, I'm trying to document
the current behavior (and a bug) a bit better.
  • Loading branch information
sharkdp authored Feb 5, 2025
1 parent 7ca778f commit eb08345
Showing 1 changed file with 79 additions and 4 deletions.
83 changes: 79 additions & 4 deletions crates/red_knot_python_semantic/resources/mdtest/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ def get_str() -> str:
return "a"

class C:
z: int

def __init__(self) -> None:
self.x = get_int()
self.y: int = 1
Expand All @@ -220,12 +222,14 @@ class C:
# TODO: this redeclaration should be an error
self.y: str = "a"

# TODO: this redeclaration should be an error
self.z: str = "a"

c_instance = C()

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.z) # revealed: int
```

#### Attributes defined in tuple unpackings
Expand Down Expand Up @@ -354,6 +358,77 @@ class C:
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

```py
Expand Down Expand Up @@ -440,12 +515,12 @@ reveal_type(C.pure_class_variable) # revealed: Unknown

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]
reveal_type(C.pure_class_variable) # revealed: Unknown

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"]

# TODO: should raise an error.
Expand Down

0 comments on commit eb08345

Please sign in to comment.