mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-21 15:52:34 +00:00
[ty] Synthesize read-only properties for all declared members on NamedTuple
classes (#19899)
This commit is contained in:
parent
82350a398e
commit
f6093452ed
2 changed files with 74 additions and 5 deletions
|
@ -74,8 +74,16 @@ Person(3, "Eve", 99, "extra")
|
|||
# error: [invalid-argument-type]
|
||||
Person(id="3", name="Eve")
|
||||
|
||||
# TODO: over-writing NamedTuple fields should be an error
|
||||
reveal_type(Person.id) # revealed: property
|
||||
reveal_type(Person.name) # revealed: property
|
||||
reveal_type(Person.age) # revealed: property
|
||||
|
||||
# TODO... the error is correct, but this is not the friendliest error message
|
||||
# for assigning to a read-only property :-)
|
||||
#
|
||||
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `id` on type `Person` with custom `__set__` method"
|
||||
alice.id = 42
|
||||
# error: [invalid-assignment]
|
||||
bob.age = None
|
||||
```
|
||||
|
||||
|
@ -151,9 +159,42 @@ from typing import NamedTuple
|
|||
class User(NamedTuple):
|
||||
id: int
|
||||
name: str
|
||||
age: int | None
|
||||
nickname: str
|
||||
|
||||
class SuperUser(User):
|
||||
id: int # this should be an error
|
||||
# TODO: this should be an error because it implies that the `id` attribute on
|
||||
# `SuperUser` is mutable, but the read-only `id` property from the superclass
|
||||
# has not been overridden in the class body
|
||||
id: int
|
||||
|
||||
# this is fine; overriding a read-only attribute with a mutable one
|
||||
# does not conflict with the Liskov Substitution Principle
|
||||
name: str = "foo"
|
||||
|
||||
# this is also fine
|
||||
@property
|
||||
def age(self) -> int:
|
||||
return super().age or 42
|
||||
|
||||
def now_called_robert(self):
|
||||
self.name = "Robert" # fine because overridden with a mutable attribute
|
||||
|
||||
# TODO: this should cause us to emit an error as we're assigning to a read-only property
|
||||
# inherited from the `NamedTuple` superclass (requires https://github.com/astral-sh/ty/issues/159)
|
||||
self.nickname = "Bob"
|
||||
|
||||
james = SuperUser(0, "James", 42, "Jimmy")
|
||||
|
||||
# fine because the property on the superclass was overridden with a mutable attribute
|
||||
# on the subclass
|
||||
james.name = "Robert"
|
||||
|
||||
# TODO: the error is correct (can't assign to the read-only property inherited from the superclass)
|
||||
# but the error message could be friendlier :-)
|
||||
#
|
||||
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `nickname` on type `SuperUser` with custom `__set__` method"
|
||||
james.nickname = "Bob"
|
||||
```
|
||||
|
||||
### Generic named tuples
|
||||
|
@ -164,13 +205,29 @@ python-version = "3.12"
|
|||
```
|
||||
|
||||
```py
|
||||
from typing import NamedTuple
|
||||
from typing import NamedTuple, Generic, TypeVar
|
||||
|
||||
class Property[T](NamedTuple):
|
||||
name: str
|
||||
value: T
|
||||
|
||||
reveal_type(Property("height", 3.4)) # revealed: Property[float]
|
||||
reveal_type(Property.value) # revealed: property
|
||||
reveal_type(Property.value.fget) # revealed: (self, /) -> Unknown
|
||||
reveal_type(Property[str].value.fget) # revealed: (self, /) -> str
|
||||
reveal_type(Property("height", 3.4).value) # revealed: float
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class LegacyProperty(NamedTuple, Generic[T]):
|
||||
name: str
|
||||
value: T
|
||||
|
||||
reveal_type(LegacyProperty("height", 42)) # revealed: LegacyProperty[int]
|
||||
reveal_type(LegacyProperty.value) # revealed: property
|
||||
reveal_type(LegacyProperty.value.fget) # revealed: (self, /) -> Unknown
|
||||
reveal_type(LegacyProperty[str].value.fget) # revealed: (self, /) -> str
|
||||
reveal_type(LegacyProperty("height", 3.4).value) # revealed: float
|
||||
```
|
||||
|
||||
## Attributes on `NamedTuple`
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue