mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
[red-knot] Add diagnostic for class-object access to pure instance variables (#16036)
## Summary Add a diagnostic if a pure instance variable is accessed on a class object. For example ```py class C: instance_only: str def __init__(self): self.instance_only = "a" # error: Attribute `instance_only` can only be accessed on instances, not on the class object `Literal[C]` itself. C.instance_only ``` --------- Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
parent
e7a6c19e3a
commit
68991d09a8
8 changed files with 246 additions and 136 deletions
|
@ -54,13 +54,12 @@ c_instance.declared_and_bound = False
|
|||
# error: [invalid-assignment] "Object of type `Literal["incompatible"]` is not assignable to attribute `declared_and_bound` of type `bool`"
|
||||
c_instance.declared_and_bound = "incompatible"
|
||||
|
||||
# TODO: we already show an error here but the message might be improved?
|
||||
# mypy shows no error here, but pyright raises "reportAttributeAccessIssue"
|
||||
# error: [unresolved-attribute] "Type `Literal[C]` has no attribute `inferred_from_value`"
|
||||
# error: [unresolved-attribute] "Attribute `inferred_from_value` can only be accessed on instances, not on the class object `Literal[C]` itself."
|
||||
reveal_type(C.inferred_from_value) # revealed: Unknown
|
||||
|
||||
# TODO: this should be an error (pure instance variables cannot be accessed on the class)
|
||||
# mypy shows no error here, but pyright raises "reportAttributeAccessIssue"
|
||||
# error: [invalid-attribute-access] "Cannot assign to instance attribute `inferred_from_value` from the class object `Literal[C]`"
|
||||
C.inferred_from_value = "overwritten on class"
|
||||
|
||||
# This assignment is fine:
|
||||
|
@ -90,13 +89,13 @@ c_instance = C()
|
|||
|
||||
reveal_type(c_instance.declared_and_bound) # revealed: str | None
|
||||
|
||||
# TODO: we currently plan to emit a diagnostic here. Note that both mypy
|
||||
# and pyright show no error in this case! So we may reconsider this in
|
||||
# the future, if it turns out to produce too many false positives.
|
||||
reveal_type(C.declared_and_bound) # revealed: str | None
|
||||
# Note that both mypy and pyright show no error in this case! So we may reconsider this in
|
||||
# the future, if it turns out to produce too many false positives. We currently emit:
|
||||
# error: [unresolved-attribute] "Attribute `declared_and_bound` can only be accessed on instances, not on the class object `Literal[C]` itself."
|
||||
reveal_type(C.declared_and_bound) # revealed: Unknown
|
||||
|
||||
# TODO: same as above. We plan to emit a diagnostic here, even if both mypy
|
||||
# and pyright allow this.
|
||||
# Same as above. Mypy and pyright do not show an error here.
|
||||
# error: [invalid-attribute-access] "Cannot assign to instance attribute `declared_and_bound` from the class object `Literal[C]`"
|
||||
C.declared_and_bound = "overwritten on class"
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `declared_and_bound` of type `str | None`"
|
||||
|
@ -116,11 +115,11 @@ c_instance = C()
|
|||
|
||||
reveal_type(c_instance.only_declared) # revealed: str
|
||||
|
||||
# TODO: mypy and pyright do not show an error here, but we plan to emit a diagnostic.
|
||||
# The type could be changed to 'Unknown' if we decide to emit an error?
|
||||
reveal_type(C.only_declared) # revealed: str
|
||||
# Mypy and pyright do not show an error here. We treat this as a pure instance variable.
|
||||
# error: [unresolved-attribute] "Attribute `only_declared` can only be accessed on instances, not on the class object `Literal[C]` itself."
|
||||
reveal_type(C.only_declared) # revealed: Unknown
|
||||
|
||||
# TODO: mypy and pyright do not show an error here, but we plan to emit one.
|
||||
# error: [invalid-attribute-access] "Cannot assign to instance attribute `only_declared` from the class object `Literal[C]`"
|
||||
C.only_declared = "overwritten on class"
|
||||
```
|
||||
|
||||
|
@ -191,11 +190,10 @@ reveal_type(c_instance.declared_only) # revealed: bytes
|
|||
|
||||
reveal_type(c_instance.declared_and_bound) # revealed: bool
|
||||
|
||||
# TODO: We already show an error here, but the message might be improved?
|
||||
# error: [unresolved-attribute]
|
||||
# error: [unresolved-attribute] "Attribute `inferred_from_value` can only be accessed on instances, not on the class object `Literal[C]` itself."
|
||||
reveal_type(C.inferred_from_value) # revealed: Unknown
|
||||
|
||||
# TODO: this should be an error
|
||||
# error: [invalid-attribute-access] "Cannot assign to instance attribute `inferred_from_value` from the class object `Literal[C]`"
|
||||
C.inferred_from_value = "overwritten on class"
|
||||
```
|
||||
|
||||
|
@ -598,6 +596,9 @@ C.class_method()
|
|||
# error: [unresolved-attribute]
|
||||
reveal_type(C.pure_class_variable) # revealed: Unknown
|
||||
|
||||
# TODO: should be no error when descriptor protocol is supported
|
||||
# and the assignment is properly attributed to the class method.
|
||||
# error: [invalid-attribute-access] "Cannot assign to instance attribute `pure_class_variable` from the class object `Literal[C]`"
|
||||
C.pure_class_variable = "overwritten on class"
|
||||
|
||||
# TODO: should be `Unknown | Literal["value set in class method"]` or
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue