mirror of
https://github.com/astral-sh/ruff
synced 2026-01-21 13:30:49 -05:00
[ty] Synthesize a _fields attribute for NamedTuples (#22163)
## Summary Closes #2176.
This commit is contained in:
@@ -266,7 +266,7 @@ class Person(NamedTuple):
|
||||
age: int | None = None
|
||||
|
||||
reveal_type(Person._field_defaults) # revealed: dict[str, Any]
|
||||
reveal_type(Person._fields) # revealed: tuple[str, ...]
|
||||
reveal_type(Person._fields) # revealed: tuple[Literal["name"], Literal["age"]]
|
||||
reveal_type(Person._make) # revealed: bound method <class 'Person'>._make(iterable: Iterable[Any]) -> Person
|
||||
reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any]
|
||||
reveal_type(Person._replace) # revealed: (self: Self, *, name: str = ..., age: int | None = ...) -> Self
|
||||
|
||||
@@ -2583,6 +2583,14 @@ impl<'db> ClassLiteral<'db> {
|
||||
.with_annotated_type(self_ty);
|
||||
signature_from_fields(vec![self_parameter], Some(self_ty))
|
||||
}
|
||||
(CodeGeneratorKind::NamedTuple, "_fields") => {
|
||||
// Synthesize a precise tuple type for _fields using literal string types.
|
||||
// For example, a NamedTuple with `name` and `age` fields gets
|
||||
// `tuple[Literal["name"], Literal["age"]]`.
|
||||
let fields = self.fields(db, specialization, field_policy);
|
||||
let field_types = fields.keys().map(|name| Type::string_literal(db, name));
|
||||
Some(Type::heterogeneous_tuple(db, field_types))
|
||||
}
|
||||
(CodeGeneratorKind::DataclassLike(_), "__lt__" | "__le__" | "__gt__" | "__ge__") => {
|
||||
if !has_dataclass_param(DataclassFlags::ORDER) {
|
||||
return None;
|
||||
|
||||
@@ -184,9 +184,13 @@ def reveal_mro(cls: type | types.GenericAlias) -> None:
|
||||
# A protocol describing an interface that should be satisfied by all named tuples
|
||||
# created using `typing.NamedTuple` or `collections.namedtuple`.
|
||||
class NamedTupleLike(Protocol):
|
||||
# from typing.NamedTuple stub
|
||||
# _fields is defined as `tuple[Any, ...]` rather than `tuple[str, ...]` so
|
||||
# that instances of actual `NamedTuple` classes with more precise `_fields`
|
||||
# types are considered assignable to this protocol (protocol attribute members
|
||||
# are invariant, and `tuple[str, str]` is not invariantly assignable to
|
||||
# `tuple[str, ...]`).
|
||||
_fields: ClassVar[tuple[Any, ...]]
|
||||
_field_defaults: ClassVar[dict[str, Any]]
|
||||
_fields: ClassVar[tuple[str, ...]]
|
||||
@classmethod
|
||||
def _make(cls: type[Self], iterable: Iterable[Any]) -> Self: ...
|
||||
def _asdict(self, /) -> dict[str, Any]: ...
|
||||
|
||||
Reference in New Issue
Block a user