diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index 53a0fbde34..74e66d66b4 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -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): @@ -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])) @@ -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: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) reveal_mro(Url) static_assert(is_subtype_of(Url, tuple[str, int])) @@ -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: -reveal_mro(NT) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(NT) reveal_type(NT(1, "a").x) # revealed: Any ``` @@ -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: -reveal_mro(NT) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(NT) reveal_type(NT(1, 2).x) # revealed: Any ``` @@ -254,7 +258,7 @@ class Url(NamedTuple("Url", [("host", str), ("path", str)])): pass reveal_type(Url) # revealed: -# revealed: (, , , ) +# revealed: (, , , , , , , , typing.Protocol, typing.Generic, ) reveal_mro(Url) reveal_type(Url.__new__) # revealed: [Self](cls: type[Self], host: str, path: str) -> Self @@ -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: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) reveal_mro(GroundTruth) # No index-out-of-bounds error since the tuple length is unknown. @@ -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: -reveal_mro(Point1) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(Point1) # String field names with multiple spaces Point1a = collections.namedtuple("Point", "x y") reveal_type(Point1a) # revealed: -reveal_mro(Point1a) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(Point1a) # String field names (comma-separated also works at runtime) Point2 = collections.namedtuple("Point", "x, y") reveal_type(Point2) # revealed: -reveal_mro(Point2) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(Point2) # List of strings Point3 = collections.namedtuple("Point", ["x", "y"]) reveal_type(Point3) # revealed: -reveal_mro(Point3) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(Point3) # Tuple of strings Point4 = collections.namedtuple("Point", ("x", "y")) reveal_type(Point4) # revealed: -reveal_mro(Point4) # revealed: (, , ) - +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(Point4) # Invalid: integer is not a valid typename # error: [invalid-argument-type] Invalid = collections.namedtuple(123, ["x", "y"]) reveal_type(Invalid) # revealed: '> -reveal_mro(Invalid) # revealed: ('>, , ) +# revealed: ('>, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(Invalid) # Invalid: too many positional arguments # error: [too-many-positional-arguments] "Too many positional arguments to function `namedtuple`: expected 2, got 4" @@ -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: -reveal_mro(NT1) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(NT1) nt1 = NT1(1, 2) reveal_type(nt1.x) # revealed: Any @@ -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: -reveal_mro(NT2) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(NT2) nt2 = NT2(1, 2, 3) reveal_type(nt2.a) # revealed: Any @@ -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: reveal_type(Point.__new__) # revealed: [Self](cls: type[Self], x: Any, _1: Any, _2: Any, z: Any, _4: Any) -> Self -reveal_mro(Point) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(Point) p = Point(1, 2, 3, 4, 5) reveal_type(p.x) # revealed: Any reveal_type(p._1) # revealed: Any @@ -534,7 +546,8 @@ Person = collections.namedtuple("Person", ["name", "age", "city"], defaults=["Un reveal_type(Person) # revealed: reveal_type(Person.__new__) # revealed: [Self](cls: type[Self], name: Any, age: Any, city: Any = "Unknown") -> Self -reveal_mro(Person) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(Person) # Can create with all fields person1 = Person("Alice", 30, "NYC") # Can omit the field with default @@ -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: -reveal_mro(Bad1) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +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: -reveal_mro(Bad2) # revealed: (, , ) +# revealed: (, , , , , , , typing.Protocol, typing.Generic, ) +reveal_mro(Bad2) # Invalid type for `defaults` (not Iterable[Any] | None) # error: [invalid-argument-type] "Invalid argument to parameter `defaults` of `namedtuple()`" diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 5deca032ed..578604e110 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -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. `` + /// 2. `` + /// 3. `` + /// 4. `` + /// 5. `` + /// 6. `` + /// 7. `` + /// 8. `typing.Protocol` + /// 9. `typing.Generic` + /// 10. `` #[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.