Skip to content

Add collections._tuplegetter support #19479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
5 changes: 4 additions & 1 deletion mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,10 @@ def add_field(
var._fullname = f"{info.fullname}.{var.name}"
info.names[var.name] = SymbolTableNode(MDEF, var)

fields = [Var(item, typ) for item, typ in zip(items, types)]
fields = [
Var(item, self.api.named_type("collections._tuplegetter", [typ]))
for item, typ in zip(items, types)
]
for var in fields:
add_field(var, is_property=True)
# We can't share Vars between fields and method arguments, since they
Expand Down
4 changes: 2 additions & 2 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2097,7 +2097,7 @@ class Y(TypedDict):
@collect_cases
def test_named_tuple(self) -> Iterator[Case]:
yield Case(
stub="from typing import NamedTuple",
stub="from typing import NamedTuple; import collections",
Copy link
Member

@brianschubert brianschubert Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, let's add import collections to stubtest_typing_stub

runtime="from typing import NamedTuple",
error=None,
)
Expand Down Expand Up @@ -2133,7 +2133,7 @@ class X2(NamedTuple):
@collect_cases
def test_named_tuple_typing_and_collections(self) -> Iterator[Case]:
yield Case(
stub="from typing import NamedTuple",
stub="from typing import NamedTuple; import collections",
runtime="from collections import namedtuple",
error=None,
)
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ def typeddict() -> Sequence[D]:

a = (a.A(), A())
a.x # E: "tuple[a.A, b.A]" has no attribute "x"
[builtins fixtures/dict.pyi]
[builtins fixtures/tuple.pyi]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of replacing these fixtures and expanding fixtures/tuple.pyi, I'd suggest leaving them as-is and adding import collections to the fixtures that are used in named tuple tests. That should help reduce the diff

[typing fixtures/typing-full.pyi]

[case testReturnAnyFromFunctionDeclaredToReturnObject]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-callable.test
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ def g(o: Thing) -> None:
i + s # E: Unsupported operand types for + ("str" and "int")
o(1,2,3)

[builtins fixtures/callable.pyi]
[builtins fixtures/tuple.pyi]

[case testCallableNoArgs]

Expand Down
80 changes: 65 additions & 15 deletions test-data/unit/check-class-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ i, i = l[0] # E: Need more than 1 value to unpack (2 expected)
l = [A(1)]
a = (1,) # E: Incompatible types in assignment (expression has type "tuple[int]", \
variable has type "A")
[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleMissingClassAttribute]
from typing import NamedTuple
Expand Down Expand Up @@ -259,7 +259,7 @@ class C(B): pass
B(1).b
C(2).b

[builtins fixtures/property.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleAsDict]
from typing import NamedTuple, Any
Expand All @@ -271,7 +271,7 @@ class X(NamedTuple):
x: X
reveal_type(x._asdict()) # N: Revealed type is "builtins.dict[builtins.str, Any]"

[builtins fixtures/dict.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleReplaceTyped]
from typing import NamedTuple
Expand Down Expand Up @@ -301,7 +301,7 @@ reveal_type(X._field_defaults) # N: Revealed type is "builtins.dict[builtins.st
# but it's inferred as `Mapping[str, object]` here due to the fixture we're using
reveal_type(X.__annotations__) # N: Revealed type is "typing.Mapping[builtins.str, builtins.object]"

[builtins fixtures/dict-full.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleUnit]
from typing import NamedTuple
Expand All @@ -326,7 +326,7 @@ class Y(NamedTuple):

reveal_type([X(3, 'b'), Y(1, 'a')]) # N: Revealed type is "builtins.list[tuple[builtins.int, builtins.str]]"

[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleJoinTuple]
from typing import NamedTuple
Expand All @@ -338,7 +338,7 @@ class X(NamedTuple):
reveal_type([(3, 'b'), X(1, 'a')]) # N: Revealed type is "builtins.list[tuple[builtins.int, builtins.str]]"
reveal_type([X(1, 'a'), (3, 'b')]) # N: Revealed type is "builtins.list[tuple[builtins.int, builtins.str]]"

[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleWithTooManyArguments]
from typing import NamedTuple
Expand All @@ -358,7 +358,7 @@ class X(typing.NamedTuple):
x.x: int # E: Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]"
z: str = 'z'
aa: int # E: Non-default NamedTuple fields cannot follow default fields
[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleWithoutTypesSpecified]
from typing import NamedTuple
Expand All @@ -377,7 +377,7 @@ class N(NamedTuple):

def f(a: Type[N]):
a() # E: Missing positional arguments "x", "y" in call to "N"
[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleWithDefaults]
from typing import List, NamedTuple, Optional
Expand Down Expand Up @@ -416,7 +416,7 @@ reveal_type(UserDefined()) # N: Revealed type is "tuple[__main__.Default, fallb
reveal_type(UserDefined(Default())) # N: Revealed type is "tuple[__main__.Default, fallback=__main__.UserDefined]"
UserDefined(1) # E: Argument 1 to "UserDefined" has incompatible type "int"; expected "Default"

[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleWithDefaultsStrictOptional]
from typing import List, NamedTuple, Optional
Expand All @@ -434,7 +434,7 @@ class CannotBeNone(NamedTuple):
x: int
y: int = None # E: Incompatible types in assignment (expression has type "None", variable has type "int")

[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleWrongType]
from typing import NamedTuple
Expand All @@ -448,7 +448,8 @@ class X(NamedTuple):
from typing import NamedTuple

class X(NamedTuple):
x: int = 1 + '1' # E: Unsupported left operand type for + ("int")
x: int = 1 + '1' # E: Incompatible types in assignment (expression has type "str", variable has type "int") \
# E: Unsupported operand types for + ("int" and "str")
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleInheritance]
Expand Down Expand Up @@ -607,7 +608,7 @@ class ReuseCallableNamed(NamedTuple):
def z(self) -> int: # E: Name "z" already defined on line 31
return 0

[builtins fixtures/dict.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleDocString]
from typing import NamedTuple
Expand Down Expand Up @@ -639,7 +640,7 @@ class HasClassMethod(NamedTuple):
reveal_type(HasClassMethod) # N: Revealed type is "def (x: builtins.str) -> tuple[builtins.str, fallback=__main__.HasClassMethod]"
return cls(x=f)

[builtins fixtures/classmethod.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleStaticMethod]
from typing import NamedTuple
Expand All @@ -651,7 +652,7 @@ class HasStaticMethod(NamedTuple):
def new(f: str) -> 'HasStaticMethod':
return HasStaticMethod(x=f)

[builtins fixtures/classmethod.pyi]
[builtins fixtures/tuple.pyi]

[case testNewNamedTupleProperty]
from typing import NamedTuple
Expand All @@ -664,7 +665,7 @@ class HasStaticMethod(NamedTuple):
reveal_type(self) # N: Revealed type is "tuple[builtins.str, fallback=__main__.HasStaticMethod]"
return 4

[builtins fixtures/property.pyi]
[builtins fixtures/tuple.pyi]

[case testTypingExtensionsNamedTuple]
from typing_extensions import NamedTuple
Expand All @@ -683,3 +684,52 @@ reveal_type(y) # N: Revealed type is "builtins.int"
point.y = 6 # E: Property "y" defined in "Point" is read-only

[builtins fixtures/tuple.pyi]

[case testNamedTupleClassAttributeAccess]
from typing import NamedTuple

class A(NamedTuple):
x: str

reveal_type(A.x) # N: Revealed type is "collections._tuplegetter[builtins.str]"
A.x + 3 # E: Unsupported left operand type for + ("_tuplegetter[str]")
A.y # E: "type[A]" has no attribute "y"

[builtins fixtures/tuple.pyi]

[case testNamedTupleTupleGetterIsCompatibleWithObject]
from typing import NamedTuple

class A(NamedTuple):
x: str = "x"

a: object = A.x
b: int = A.x # E: Incompatible types in assignment (expression has type "_tuplegetter[str]", variable has type "int")
[builtins fixtures/tuple.pyi]

[case testNamedTupleTupleGetterNested]
from typing import NamedTuple

class Inner(NamedTuple):
x: int

class Outer(NamedTuple):
y: Inner

reveal_type(Inner.x) # N: Revealed type is "collections._tuplegetter[builtins.int]"
reveal_type(Outer.y) # N: Revealed type is "collections._tuplegetter[tuple[builtins.int, fallback=__main__.Inner]]"
[builtins fixtures/tuple.pyi]

[case testNamedTupleTupleGetterGeneric]
from typing import Generic
from typing import NamedTuple
from typing import TypeVar

T = TypeVar("T")

class GenericTuple(NamedTuple, Generic[T]):
x: T

reveal_type(GenericTuple.x) # E: Access to generic instance variables via class is ambiguous \
# N: Revealed type is "collections._tuplegetter[Any]"
[builtins fixtures/tuple.pyi]
20 changes: 10 additions & 10 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -3868,7 +3868,7 @@ from typing import Type, NamedTuple
N = NamedTuple('N', [('x', int), ('y', int)])
def f(a: Type[N]):
a()
[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]
[out]
main:4: error: Missing positional arguments "x", "y" in call to "N"

Expand Down Expand Up @@ -5346,7 +5346,7 @@ class M(NamedTuple):
n: N
m: M
lst = [n, m]
[builtins fixtures/isinstancelist.pyi]
[builtins fixtures/tuple.pyi]

[case testCorrectJoinOfSelfRecursiveTypedDicts]
from typing import TypedDict
Expand Down Expand Up @@ -5377,7 +5377,7 @@ def parse_ast(name_dict: NameDict) -> None:
if isinstance(name_dict[''], int):
pass
reveal_type(name_dict['test']) # N: Revealed type is "tuple[builtins.bool, fallback=__main__.NameInfo]"
[builtins fixtures/isinstancelist.pyi]
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-medium.pyi]

[case testCrashInForwardRefToTypedDictWithIsinstance]
Expand Down Expand Up @@ -5600,7 +5600,7 @@ class B(object):
return self.a.x

reveal_type(x.x) # N: Revealed type is "builtins.int"
[builtins fixtures/property.pyi]
[builtins fixtures/tuple.pyi]
[out]

[case testCorrectIsinstanceWithForwardUnion]
Expand All @@ -5613,7 +5613,7 @@ def f(x: ForwardUnion) -> None:
reveal_type(x) # N: Revealed type is "Union[tuple[builtins.int, fallback=__main__.TP], builtins.int]"
if isinstance(x, TP):
reveal_type(x) # N: Revealed type is "tuple[builtins.int, fallback=__main__.TP]"
[builtins fixtures/isinstance.pyi]
[builtins fixtures/tuple.pyi]
[out]

[case testCrashInvalidArgsSyntheticClassSyntax]
Expand All @@ -5626,7 +5626,7 @@ class NM(NamedTuple):
# These two should never crash, reveals are in the next test
TD({'x': []})
NM(x=[])
[builtins fixtures/dict.pyi]
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-typeddict.pyi]
[out]

Expand All @@ -5645,7 +5645,7 @@ reveal_type(x) # N: Revealed type is "TypedDict('__main__.TD', {'x': builtins.li
reveal_type(x1) # N: Revealed type is "TypedDict('__main__.TD', {'x': builtins.list[Any]})"
reveal_type(y) # N: Revealed type is "tuple[builtins.list[Any], fallback=__main__.NM]"
reveal_type(y1) # N: Revealed type is "tuple[builtins.list[Any], fallback=__main__.NM]"
[builtins fixtures/dict.pyi]
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-typeddict.pyi]
[out]

Expand All @@ -5659,7 +5659,7 @@ NT = NewType('NT', List[int, str]) # E: "list" expects 1 type argument, but 2 gi
TD({'x': []})
NM(x=[])
NT([])
[builtins fixtures/dict.pyi]
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-typeddict.pyi]
[out]

Expand All @@ -5677,7 +5677,7 @@ x: A1
y: A2
reveal_type(x.b) # N: Revealed type is "__main__.B"
reveal_type(y['b']) # N: Revealed type is "__main__.B"
[builtins fixtures/dict.pyi]
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-typeddict.pyi]
[out]

Expand All @@ -5691,7 +5691,7 @@ x: A1
y: A2
reveal_type(x.b) # N: Revealed type is "__main__.B"
reveal_type(y['b']) # N: Revealed type is "__main__.B"
[builtins fixtures/dict.pyi]
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-typeddict.pyi]
[out]

Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-custom-plugin.test
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ reveal_type(FullyQualifiedTestNamedTuple('')._asdict()) # N: Revealed type is "b
[file mypy.ini]
\[mypy]
plugins=<ROOT>/test-data/unit/plugins/fully_qualified_test_hook.py
[builtins fixtures/classmethod.pyi]
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-typeddict.pyi]

[case testDynamicClassPlugin]
Expand Down
1 change: 0 additions & 1 deletion test-data/unit/check-enum.test
Original file line number Diff line number Diff line change
Expand Up @@ -2452,7 +2452,6 @@ class Foo(Enum):
# E: Incompatible types in assignment (expression has type "<typing special form>", variable has type "Foo")
Baz: Any = Callable[[Dict[str, "Missing"]], None] # E: Enum members must be left unannotated \
# N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members \
# E: Type application targets a non-generic function or class \
# E: Name "Missing" is not defined

reveal_type(Foo.Bar) # N: Revealed type is "Literal[__main__.Foo.Bar]?"
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -2159,7 +2159,7 @@ class Custom(Base):
Base(int()) == int() # E: Non-overlapping equality check (left operand type: "Base", right operand type: "int")
Base(int()) == tuple()
Custom(int()) == int()
[builtins fixtures/bool.pyi]
[builtins fixtures/tuple.pyi]

[case testCustomEqCheckStrictEqualityMeta]
# flags: --strict-equality
Expand Down Expand Up @@ -2459,7 +2459,7 @@ def f() -> int: # E: Missing return statement
from typing import TypeVar
T = TypeVar("T")
x: int
x + T # E: Unsupported left operand type for + ("int")
x + T # E: Unsupported operand types for + ("int" and "TypeVar")
T() # E: "TypeVar" not callable
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-full.pyi]
6 changes: 3 additions & 3 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,7 @@ from missing import Unchecked

Point = NamedTuple('Point', [('x', List[Unchecked]),
('y', Unchecked)])
[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]
[out]
main:5: error: NamedTuple type becomes "tuple[list[Any], Any]" due to an unfollowed import

Expand Down Expand Up @@ -1315,7 +1315,7 @@ Point = NamedTuple('Point', [('x', int), ('y', int)]) # no error

def origin() -> Point:
return Point(x=0, y=0)
[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]

[case testDisallowAnyExprNewType]
# flags: --disallow-any-expr
Expand Down Expand Up @@ -1908,7 +1908,7 @@ z = cast(List[Any], x) # E: Explicit "Any" is not allowed [explicit-any]
from typing import Any, List, NamedTuple

Point = NamedTuple('Point', [('x', List[Any]), ('y', Any)]) # E: Explicit "Any" is not allowed [explicit-any]
[builtins fixtures/list.pyi]
[builtins fixtures/tuple.pyi]

[case testDisallowAnyExplicitTypeVarConstraint]
# flags: --disallow-any-explicit --show-error-codes
Expand Down
Loading
Loading