diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index b7cf17ea46..8c5a79d62f 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -139,6 +139,33 @@ class Property[T](NamedTuple): reveal_type(Property("height", 3.4)) # revealed: Property[Unknown] ``` +## Attributes on `NamedTuple` + +The following attributes are available on `NamedTuple` classes / instances: + +```py +from typing import NamedTuple + +class Person(NamedTuple): + name: str + age: int | None = None + +reveal_type(Person._field_defaults) # revealed: dict[str, Any] +reveal_type(Person._fields) # revealed: tuple[str, ...] +reveal_type(Person._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Self +reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any] +reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self + +# TODO: should be `Person` once we support `Self` +reveal_type(Person._make(("Alice", 42))) # revealed: Unknown + +person = Person("Alice", 42) + +reveal_type(person._asdict()) # revealed: dict[str, Any] +# TODO: should be `Person` once we support `Self` +reveal_type(person._replace(name="Bob")) # revealed: Unknown +``` + ## `collections.namedtuple` ```py diff --git a/crates/ty_python_semantic/src/module_resolver/module.rs b/crates/ty_python_semantic/src/module_resolver/module.rs index 2b02d4daca..c56d16dbdc 100644 --- a/crates/ty_python_semantic/src/module_resolver/module.rs +++ b/crates/ty_python_semantic/src/module_resolver/module.rs @@ -118,6 +118,8 @@ pub enum KnownModule { Dataclasses, Collections, Inspect, + #[strum(serialize = "_typeshed._type_checker_internals")] + TypeCheckerInternals, TyExtensions, } @@ -135,6 +137,7 @@ impl KnownModule { Self::Dataclasses => "dataclasses", Self::Collections => "collections", Self::Inspect => "inspect", + Self::TypeCheckerInternals => "_typeshed._type_checker_internals", Self::TyExtensions => "ty_extensions", } } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index d5763dfe62..5e2e780d8b 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1289,6 +1289,14 @@ impl<'db> ClassLiteral<'db> { Some(Type::Callable(CallableType::single(db, signature))) } + (CodeGeneratorKind::NamedTuple, name) if name != "__init__" => { + KnownClass::NamedTupleFallback + .to_class_literal(db) + .into_class_literal()? + .own_class_member(db, None, name) + .symbol + .ignore_possibly_unbound() + } _ => None, } } @@ -1985,6 +1993,8 @@ pub enum KnownClass { NotImplementedType, // dataclasses Field, + // _typeshed._type_checker_internals + NamedTupleFallback, } impl<'db> KnownClass { @@ -2067,7 +2077,8 @@ impl<'db> KnownClass { // (see https://docs.python.org/3/library/constants.html#NotImplemented) | Self::NotImplementedType | Self::Classmethod - | Self::Field => Truthiness::Ambiguous, + | Self::Field + | Self::NamedTupleFallback => Truthiness::Ambiguous, } } @@ -2141,7 +2152,8 @@ impl<'db> KnownClass { | Self::EllipsisType | Self::NotImplementedType | Self::UnionType - | Self::Field => false, + | Self::Field + | Self::NamedTupleFallback => false, } } @@ -2217,6 +2229,7 @@ impl<'db> KnownClass { } Self::NotImplementedType => "_NotImplementedType", Self::Field => "Field", + Self::NamedTupleFallback => "NamedTupleFallback", } } @@ -2446,6 +2459,7 @@ impl<'db> KnownClass { | Self::Deque | Self::OrderedDict => KnownModule::Collections, Self::Field => KnownModule::Dataclasses, + Self::NamedTupleFallback => KnownModule::TypeCheckerInternals, } } @@ -2508,7 +2522,8 @@ impl<'db> KnownClass { | Self::Super | Self::NamedTuple | Self::NewType - | Self::Field => false, + | Self::Field + | Self::NamedTupleFallback => false, } } @@ -2573,7 +2588,8 @@ impl<'db> KnownClass { | Self::UnionType | Self::NamedTuple | Self::NewType - | Self::Field => false, + | Self::Field + | Self::NamedTupleFallback => false, } } @@ -2646,6 +2662,7 @@ impl<'db> KnownClass { } "_NotImplementedType" => Self::NotImplementedType, "Field" => Self::Field, + "NamedTupleFallback" => Self::NamedTupleFallback, _ => return None, }; @@ -2700,7 +2717,8 @@ impl<'db> KnownClass { | Self::GeneratorType | Self::AsyncGeneratorType | Self::WrapperDescriptorType - | Self::Field => module == self.canonical_module(db), + | Self::Field + | Self::NamedTupleFallback => module == self.canonical_module(db), Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types), Self::SpecialForm | Self::TypeVar