Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 34 additions & 19 deletions crates/ty_python_semantic/resources/mdtest/named_tuple.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ name, and not just by its numeric position within the tuple:
### Basics

```py
from typing import NamedTuple
from typing import NamedTuple, Sequence
from ty_extensions import static_assert, is_subtype_of, is_assignable_to, reveal_mro

class Person(NamedTuple):
Expand All @@ -30,6 +30,8 @@ reveal_mro(Person)

static_assert(is_subtype_of(Person, tuple[int, str, int | None]))
static_assert(is_subtype_of(Person, tuple[object, ...]))
static_assert(is_subtype_of(Person, Sequence[int | str | None]))
static_assert(is_subtype_of(Person, Sequence[object]))
static_assert(not is_assignable_to(Person, tuple[int, str, int]))
static_assert(not is_assignable_to(Person, tuple[int, str]))

Expand Down Expand Up @@ -166,7 +168,7 @@ reveal_type(container.items) # revealed: list[int]
reveal_type(container.mapping) # revealed: dict[str, bool]

# MRO includes the properly specialized tuple type.
# revealed: (<class 'Url'>, <class 'tuple[str, int]'>, <class 'object'>)
# revealed: (<class 'Url'>, <class 'tuple[str, int]'>, <class 'Sequence[str | int]'>, <class 'Reversible[str | int]'>, <class 'Collection[str | int]'>, <class 'Iterable[str | int]'>, <class 'Container[str | int]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Url)

static_assert(is_subtype_of(Url, tuple[str, int]))
Expand Down Expand Up @@ -219,7 +221,8 @@ NT = NamedTuple("NT", fields)

# Fields are unknown, so attribute access returns Any and MRO has Unknown tuple.
reveal_type(NT) # revealed: <class 'NT'>
reveal_mro(NT) # revealed: (<class 'NT'>, <class 'tuple[Unknown, ...]'>, <class 'object'>)
# revealed: (<class 'NT'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(NT)
reveal_type(NT(1, "a").x) # revealed: Any
```

Expand All @@ -237,7 +240,8 @@ NT = collections.namedtuple("NT", field_names)

# Fields are unknown, so attribute access returns Any and MRO has Unknown tuple.
reveal_type(NT) # revealed: <class 'NT'>
reveal_mro(NT) # revealed: (<class 'NT'>, <class 'tuple[Unknown, ...]'>, <class 'object'>)
# revealed: (<class 'NT'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(NT)
reveal_type(NT(1, 2).x) # revealed: Any
```

Expand All @@ -254,7 +258,7 @@ class Url(NamedTuple("Url", [("host", str), ("path", str)])):
pass

reveal_type(Url) # revealed: <class 'Url'>
# revealed: (<class 'mdtest_snippet.Url @ src/mdtest_snippet.py:4:7'>, <class 'mdtest_snippet.Url @ src/mdtest_snippet.py:4:11'>, <class 'tuple[str, str]'>, <class 'object'>)
# revealed: (<class 'mdtest_snippet.Url @ src/mdtest_snippet.py:4:7'>, <class 'mdtest_snippet.Url @ src/mdtest_snippet.py:4:11'>, <class 'tuple[str, str]'>, <class 'Sequence[str]'>, <class 'Reversible[str]'>, <class 'Collection[str]'>, <class 'Iterable[str]'>, <class 'Container[str]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Url)
reveal_type(Url.__new__) # revealed: [Self](cls: type[Self], host: str, path: str) -> Self

Expand Down Expand Up @@ -364,7 +368,7 @@ reveal_type(config) # revealed: GroundTruth
reveal_type(config.duration) # revealed: Any

# Namedtuples with unknown fields inherit from tuple[Unknown, ...] to avoid false positives.
# revealed: (<class 'GroundTruth'>, <class 'tuple[Unknown, ...]'>, <class 'object'>)
# revealed: (<class 'GroundTruth'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(GroundTruth)

# No index-out-of-bounds error since the tuple length is unknown.
Expand All @@ -383,33 +387,38 @@ from ty_extensions import reveal_mro
# String field names (space-separated)
Point1 = collections.namedtuple("Point", "x y")
reveal_type(Point1) # revealed: <class 'Point'>
reveal_mro(Point1) # revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'object'>)
# revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Point1)

# String field names with multiple spaces
Point1a = collections.namedtuple("Point", "x y")
reveal_type(Point1a) # revealed: <class 'Point'>
reveal_mro(Point1a) # revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'object'>)
# revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Point1a)

# String field names (comma-separated also works at runtime)
Point2 = collections.namedtuple("Point", "x, y")
reveal_type(Point2) # revealed: <class 'Point'>
reveal_mro(Point2) # revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'object'>)
# revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Point2)

# List of strings
Point3 = collections.namedtuple("Point", ["x", "y"])
reveal_type(Point3) # revealed: <class 'Point'>
reveal_mro(Point3) # revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'object'>)
# revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Point3)

# Tuple of strings
Point4 = collections.namedtuple("Point", ("x", "y"))
reveal_type(Point4) # revealed: <class 'Point'>
reveal_mro(Point4) # revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'object'>)

# revealed: (<class 'Point'>, <class 'tuple[Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Point4)
# Invalid: integer is not a valid typename
# error: [invalid-argument-type]
Invalid = collections.namedtuple(123, ["x", "y"])
reveal_type(Invalid) # revealed: <class '<unknown>'>
reveal_mro(Invalid) # revealed: (<class '<unknown>'>, <class 'tuple[Any, Any]'>, <class 'object'>)
# revealed: (<class '<unknown>'>, <class 'tuple[Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Invalid)

# Invalid: too many positional arguments
# error: [too-many-positional-arguments] "Too many positional arguments to function `namedtuple`: expected 2, got 4"
Expand Down Expand Up @@ -452,7 +461,8 @@ from ty_extensions import reveal_mro
# Both `typename` and `field_names` can be passed as keyword arguments
NT1 = collections.namedtuple(typename="NT1", field_names="x y")
reveal_type(NT1) # revealed: <class 'NT1'>
reveal_mro(NT1) # revealed: (<class 'NT1'>, <class 'tuple[Any, Any]'>, <class 'object'>)
# revealed: (<class 'NT1'>, <class 'tuple[Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(NT1)

nt1 = NT1(1, 2)
reveal_type(nt1.x) # revealed: Any
Expand All @@ -461,7 +471,8 @@ reveal_type(nt1.y) # revealed: Any
# Only `field_names` as keyword argument
NT2 = collections.namedtuple("NT2", field_names=["a", "b", "c"])
reveal_type(NT2) # revealed: <class 'NT2'>
reveal_mro(NT2) # revealed: (<class 'NT2'>, <class 'tuple[Any, Any, Any]'>, <class 'object'>)
# revealed: (<class 'NT2'>, <class 'tuple[Any, Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(NT2)

nt2 = NT2(1, 2, 3)
reveal_type(nt2.a) # revealed: Any
Expand Down Expand Up @@ -494,7 +505,8 @@ from ty_extensions import reveal_mro
Point = collections.namedtuple("Point", ["x", "class", "_y", "z", "z"], rename=True)
reveal_type(Point) # revealed: <class 'Point'>
reveal_type(Point.__new__) # revealed: [Self](cls: type[Self], x: Any, _1: Any, _2: Any, z: Any, _4: Any) -> Self
reveal_mro(Point) # revealed: (<class 'Point'>, <class 'tuple[Any, Any, Any, Any, Any]'>, <class 'object'>)
# revealed: (<class 'Point'>, <class 'tuple[Any, Any, Any, Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Point)
p = Point(1, 2, 3, 4, 5)
reveal_type(p.x) # revealed: Any
reveal_type(p._1) # revealed: Any
Expand Down Expand Up @@ -534,7 +546,8 @@ Person = collections.namedtuple("Person", ["name", "age", "city"], defaults=["Un
reveal_type(Person) # revealed: <class 'Person'>
reveal_type(Person.__new__) # revealed: [Self](cls: type[Self], name: Any, age: Any, city: Any = "Unknown") -> Self

reveal_mro(Person) # revealed: (<class 'Person'>, <class 'tuple[Any, Any, Any]'>, <class 'object'>)
# revealed: (<class 'Person'>, <class 'tuple[Any, Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Person)
# Can create with all fields
person1 = Person("Alice", 30, "NYC")
# Can omit the field with default
Expand All @@ -556,14 +569,16 @@ reveal_type(TooManyDefaults.__new__) # revealed: [Self](cls: type[Self], x: Any
# error: [unknown-argument]
Bad1 = collections.namedtuple("Bad1", ["x", "y"], foobarbaz=42)
reveal_type(Bad1) # revealed: <class 'Bad1'>
reveal_mro(Bad1) # revealed: (<class 'Bad1'>, <class 'tuple[Any, Any]'>, <class 'object'>)
# revealed: (<class 'Bad1'>, <class 'tuple[Any, Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Bad1)

# Multiple unknown keyword arguments
# error: [unknown-argument]
# error: [unknown-argument]
Bad2 = collections.namedtuple("Bad2", ["x"], invalid1=True, invalid2=False)
reveal_type(Bad2) # revealed: <class 'Bad2'>
reveal_mro(Bad2) # revealed: (<class 'Bad2'>, <class 'tuple[Any]'>, <class 'object'>)
# revealed: (<class 'Bad2'>, <class 'tuple[Any]'>, <class 'Sequence[Any]'>, <class 'Reversible[Any]'>, <class 'Collection[Any]'>, <class 'Iterable[Any]'>, <class 'Container[Any]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(Bad2)

# Invalid type for `defaults` (not Iterable[Any] | None)
# error: [invalid-argument-type] "Invalid argument to parameter `defaults` of `namedtuple()`"
Expand Down
29 changes: 16 additions & 13 deletions crates/ty_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5497,23 +5497,26 @@ impl<'db> DynamicNamedTupleLiteral<'db> {

/// Compute the MRO for this namedtuple.
///
/// The MRO is `[self, tuple[field_types...], object]`.
/// For example, `namedtuple("Point", [("x", int), ("y", int)])` has MRO
/// `[Point, tuple[int, int], object]`.
/// The MRO is the MRO of the class's tuple base class, prepended by `self`.
/// For example, `namedtuple("Point", [("x", int), ("y", int)])` has the following MRO:
///
/// 1. `<class 'Point'>`
/// 2. `<class 'tuple[int, int]'>`
/// 3. `<class 'Sequence[int]'>`
/// 4. `<class 'Reversible[int]'>`
/// 5. `<class 'Collection[int]'>`
/// 6. `<class 'Iterable[int]'>`
/// 7. `<class 'Container[int]'>`
/// 8. `typing.Protocol`
/// 9. `typing.Generic`
/// 10. `<class 'object'>`
#[salsa::tracked(returns(ref), heap_size = ruff_memory_usage::heap_size)]
pub(crate) fn mro(self, db: &'db dyn Db) -> Mro<'db> {
let self_base = ClassBase::Class(ClassType::NonGeneric(self.into()));
let tuple_class = self.tuple_base_class(db);
let object_class = KnownClass::Object
.to_class_literal(db)
.as_class_literal()
.expect("object should be a class literal")
.default_specialization(db);
Mro::from([
self_base,
ClassBase::Class(tuple_class),
ClassBase::Class(object_class),
])
std::iter::once(self_base)
.chain(tuple_class.iter_mro(db))
.collect()
}

/// Get the metaclass of this dynamic namedtuple.
Expand Down
Loading