From 1695e5382b721fce2194923d7033b4dcad5883d7 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 5 Feb 2025 11:30:24 +0100 Subject: [PATCH 1/4] [red-knot] Extend instance/class attribute tests --- .../resources/mdtest/attributes.md | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/attributes.md b/crates/red_knot_python_semantic/resources/mdtest/attributes.md index 17da6541ff90e..1191d05728bb1 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/attributes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/attributes.md @@ -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 @@ -220,12 +222,18 @@ 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 + +# TODO: We should probably infer `int | str` here. +reveal_type(c_instance.z) # revealed: int ``` #### Attributes defined in tuple unpackings @@ -354,6 +362,39 @@ 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] +``` + #### Attributes defined in statically-known-to-be-false branches ```py @@ -440,12 +481,14 @@ 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"]` +# TODO: should be `Literal["overwritten on class"]` once/if we support +# local narrowing. reveal_type(c_instance.pure_class_variable) # revealed: Unknown | Literal["value set in class method"] # TODO: should raise an error. From cb5e3bbe4dc8d14dfdb7f9373a63a2af075ae03c Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 5 Feb 2025 12:02:49 +0100 Subject: [PATCH 2/4] Remove TODO comment --- crates/red_knot_python_semantic/resources/mdtest/attributes.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/attributes.md b/crates/red_knot_python_semantic/resources/mdtest/attributes.md index 1191d05728bb1..ee135fae1f377 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/attributes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/attributes.md @@ -487,8 +487,6 @@ C.pure_class_variable = "overwritten on class" reveal_type(C.pure_class_variable) # revealed: Unknown c_instance = C() -# TODO: should be `Literal["overwritten on class"]` once/if we support -# local narrowing. reveal_type(c_instance.pure_class_variable) # revealed: Unknown | Literal["value set in class method"] # TODO: should raise an error. From 43213b712e48d471acea9de429dff4cfadb2d1f1 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 5 Feb 2025 12:04:11 +0100 Subject: [PATCH 3/4] Remove TODO comments for union types --- .../red_knot_python_semantic/resources/mdtest/attributes.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/attributes.md b/crates/red_knot_python_semantic/resources/mdtest/attributes.md index ee135fae1f377..712563490f271 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/attributes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/attributes.md @@ -228,11 +228,7 @@ class C: 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 - -# TODO: We should probably infer `int | str` here. reveal_type(c_instance.z) # revealed: int ``` From 1c4078483e396ce6caf655bb9aeee99e7ac496dd Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 5 Feb 2025 12:37:49 +0100 Subject: [PATCH 4/4] Add more tests for staticmethod --- .../resources/mdtest/attributes.md | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/crates/red_knot_python_semantic/resources/mdtest/attributes.md b/crates/red_knot_python_semantic/resources/mdtest/attributes.md index 712563490f271..9d0bf3bf7a662 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/attributes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/attributes.md @@ -391,6 +391,44 @@ reveal_type(D.x) # revealed: 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