Reverts astral-sh/ruff#20156. As @sharkdp noted in his post-merge review, there were several issues with that PR that I didn't spot before merging — but I'm out for four days now, and would rather not leave things in an inconsistent state for that long. I'll revisit this on Wednesday.
3.3 KiB
Attribute assignment
This test suite demonstrates various kinds of diagnostics that can be emitted in a
obj.attr = value assignment.
Instance attributes with class-level defaults
These can be set on instances and on class objects.
class C:
attr: int = 0
instance = C()
instance.attr = 1 # fine
instance.attr = "wrong" # error: [invalid-assignment]
C.attr = 1 # fine
C.attr = "wrong" # error: [invalid-assignment]
Pure instance attributes
These can only be set on instances. When trying to set them on class objects, we generate a useful diagnostic that mentions that the attribute is only available on instances.
class C:
def __init__(self):
self.attr: int = 0
instance = C()
instance.attr = 1 # fine
instance.attr = "wrong" # error: [invalid-assignment]
C.attr = 1 # error: [invalid-attribute-access]
ClassVars
These can only be set on class objects. When trying to set them on instances, we generate a useful diagnostic that mentions that the attribute is only available on class objects.
from typing import ClassVar
class C:
attr: ClassVar[int] = 0
C.attr = 1 # fine
C.attr = "wrong" # error: [invalid-assignment]
instance = C()
instance.attr = 1 # error: [invalid-attribute-access]
Unknown attributes
When trying to set an attribute that is not defined, we also emit errors:
class C: ...
C.non_existent = 1 # error: [unresolved-attribute]
instance = C()
instance.non_existent = 1 # error: [unresolved-attribute]
Possibly-unbound attributes
When trying to set an attribute that is not defined in all branches, we emit errors:
def _(flag: bool) -> None:
class C:
if flag:
attr: int = 0
C.attr = 1 # error: [possibly-unbound-attribute]
instance = C()
instance.attr = 1 # error: [possibly-unbound-attribute]
Data descriptors
When assigning to a data descriptor attribute, we implicitly call the descriptor's __set__ method.
This can lead to various kinds of diagnostics.
Invalid argument type
class Descriptor:
def __set__(self, instance: object, value: int) -> None:
pass
class C:
attr: Descriptor = Descriptor()
instance = C()
instance.attr = 1 # fine
# TODO: ideally, we would mention why this is an invalid assignment (wrong argument type for `value` parameter)
instance.attr = "wrong" # error: [invalid-assignment]
Invalid __set__ method signature
class WrongDescriptor:
def __set__(self, instance: object, value: int, extra: int) -> None:
pass
class C:
attr: WrongDescriptor = WrongDescriptor()
instance = C()
# TODO: ideally, we would mention why this is an invalid assignment (wrong number of arguments for `__set__`)
instance.attr = 1 # error: [invalid-assignment]
Setting attributes on union types
def _(flag: bool) -> None:
if flag:
class C1:
attr: int = 0
else:
class C1:
attr: str = ""
# TODO: The error message here could be improved to explain why the assignment fails.
C1.attr = 1 # error: [invalid-assignment]
class C2:
if flag:
attr: int = 0
else:
attr: str = ""
# TODO: This should be an error
C2.attr = 1