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 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
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
1 change: 1 addition & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def use_tmp_dir(mod_name: str) -> Iterator[str]:


stubtest_typing_stub = """
import collections
Any = object()

class _SpecialForm:
Expand Down
16 changes: 16 additions & 0 deletions mypy/typeshed/stdlib/collections/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ _VT = TypeVar("_VT")
_KT_co = TypeVar("_KT_co", covariant=True)
_VT_co = TypeVar("_VT_co", covariant=True)

# at runtime this class is implemented in _collections, but it considers itself to live in collections since Python 3.12
@final
class _tuplegetter(Generic[_T]): # undocumented
def __init__(self, index: SupportsIndex, doc: str, /) -> None: ...
def __reduce__(self) -> tuple[type[Self], tuple[int, str]]: ...
if sys.version_info >= (3, 10):
@overload
def __get__(self, instance: None, owner: type[Any] | None = None, /) -> Self: ...
@overload
def __get__(self, instance: object, owner: type[Any] | None = None, /) -> _T: ...
else:
@overload
def __get__(self, instance: None, owner: type[Any] | None, /) -> Self: ...
@overload
def __get__(self, instance: object, owner: type[Any] | None, /) -> _T: ...

# namedtuple is special-cased in the type checker; the initializer is ignored.
def namedtuple(
typename: str,
Expand Down
1 change: 1 addition & 0 deletions mypyc/test-data/annotate-basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ def good1() -> int:
[file nonnative.py]
class C:
def foo(self) -> None: pass
[typing fixtures/typing-full.pyi]

[case testAnnotateGetAttrAndSetAttrBuiltins]
def f1(x, s: str):
Expand Down
3 changes: 3 additions & 0 deletions mypyc/test-data/fixtures/typing-full.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Many of the definitions have special handling in the type checker, so they
# can just be initialized to anything.

import collections
from abc import abstractmethod, ABCMeta

class GenericMeta(type): pass
Expand Down Expand Up @@ -175,3 +176,5 @@ class _TypedDict(Mapping[str, object]):

class TypeAliasType:
pass

def final(f: T) -> T: pass
1 change: 1 addition & 0 deletions mypyc/test-data/irbuild-basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -2315,6 +2315,7 @@ L2:
r73 = CPyDict_SetItem(r71, r72, r66)
r74 = r73 >= 0 :: signed
return 1
[typing fixtures/typing-full.pyi]

[case testChainedConditional]
def g(x: int) -> int:
Expand Down
1 change: 1 addition & 0 deletions mypyc/test-data/irbuild-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ L2:
r2 = CPySequenceTuple_GetItem(nt, 2)
r3 = unbox(int, r2)
return r3
[typing fixtures/typing-full.pyi]


[case testTupleOperatorIn]
Expand Down
1 change: 1 addition & 0 deletions mypyc/test-data/run-misc.test
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ def test_named_tuple() -> None:
assert type(t) is NT
assert isinstance(t, tuple)
assert not isinstance(tuple([1]), NT)
[typing fixtures/typing-full.pyi]

[case testUnion]
from typing import Union
Expand Down
2 changes: 2 additions & 0 deletions mypyc/test-data/run-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ assert f(NT(3, 2)) == 3
class Sub(NT):
pass
assert f(Sub(3, 2)) == 3
[typing fixtures/typing-full.pyi]

-- Ref: https://github.com/mypyc/mypyc/issues/924
[case testNamedTupleClassSyntax]
Expand Down Expand Up @@ -152,6 +153,7 @@ assert Record.__annotations__ == {
'ordering': type,
'extra_int_constants': list,
}, Record.__annotations__
[typing fixtures/typing-full.pyi]

[case testTupleOps]
from typing import Tuple, Final, List, Any, Optional, cast
Expand Down
49 changes: 49 additions & 0 deletions test-data/unit/check-class-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -683,3 +683,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]
2 changes: 1 addition & 1 deletion 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
16 changes: 8 additions & 8 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -3715,8 +3715,8 @@ cache_fine_grained = False
[file mypy.ini.2]
\[mypy]
cache_fine_grained = True
[rechecked _typeshed, a, builtins, typing]
[stale _typeshed, a, builtins, typing]
[rechecked _typeshed, a, builtins, collections, typing]
[stale _typeshed, a, builtins, collections, typing]
[builtins fixtures/tuple.pyi]

[case testIncrementalPackageNameOverload]
Expand Down Expand Up @@ -3767,8 +3767,8 @@ Signature: 8a477f597d28d172789f06886806bc55
[file b.py.2]
# uh
-- Every file should get reloaded, since the cache was invalidated
[stale _typeshed, a, b, builtins, typing]
[rechecked _typeshed, a, b, builtins, typing]
[stale _typeshed, a, b, builtins, collections, typing]
[rechecked _typeshed, a, b, builtins, collections, typing]
[builtins fixtures/tuple.pyi]

[case testIncrementalBustedFineGrainedCache2]
Expand All @@ -3780,8 +3780,8 @@ import b
[file b.py.2]
# uh
-- Every file should get reloaded, since the settings changed
[stale _typeshed, a, b, builtins, typing]
[rechecked _typeshed, a, b, builtins, typing]
[stale _typeshed, a, b, builtins, collections, typing]
[rechecked _typeshed, a, b, builtins, collections, typing]
[builtins fixtures/tuple.pyi]

[case testIncrementalBustedFineGrainedCache3]
Expand All @@ -3796,8 +3796,8 @@ import b
[file b.py.2]
# uh
-- Every file should get reloaded, since the cache was invalidated
[stale _typeshed, a, b, builtins, typing]
[rechecked _typeshed, a, b, builtins, typing]
[stale _typeshed, a, b, builtins, collections, typing]
[rechecked _typeshed, a, b, builtins, collections, typing]
[builtins fixtures/tuple.pyi]

[case testIncrementalWorkingFineGrainedCache]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-narrowing.test
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ if x5["key"] == "A":
reveal_type(x5) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key': Literal['A'], 'foo': builtins.int})"
else:
reveal_type(x5) # N: Revealed type is "TypedDict('__main__.TypedDict2', {'key': Literal['B'], 'foo': builtins.str})"
[builtins fixtures/primitives.pyi]
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-typeddict.pyi]

[case testNarrowingParentWithEnumsBasic]
Expand Down
3 changes: 3 additions & 0 deletions test-data/unit/deps-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class A: pass
<m.N> -> m.f
<a.A> -> <m.N.a>, <m.N>, m
<a> -> m
<collections._tuplegetter> -> <m.N.a>, <m.N>, m

[case testNamedTuple2]
from typing import NamedTuple, Any, Tuple
Expand All @@ -45,6 +46,7 @@ class B: pass
<a.A> -> <m.N.a>, <m.N>, m
<a.B> -> <m.N.a>, <m.N>, m
<a> -> m
<collections._tuplegetter> -> <m.N.a>, <m.N>, m

[case testNamedTuple3]
from typing import NamedTuple
Expand All @@ -62,6 +64,7 @@ y = M(x)
<m.N> -> <m.M.z>, <m.M>, <m.x>, <m.y>, m
<m.x> -> m
<m.y> -> m
<collections._tuplegetter> -> <m.M.z>, <m.M>, <m.N.x>, <m.N>, m

[case testNamedTuple4]
from typing import NamedTuple, Any
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/deps-types.test
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,7 @@ class I: pass
[out]
<a.A> -> m
<a> -> m
<collections._tuplegetter> -> <m.P.x>, <m.P>, m
<mod.I.__init__> -> a
<mod.I.__new__> -> a
<mod.I> -> <m.P.x>, <m.P>, m, a, mod.I
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -8685,7 +8685,7 @@ m.py:4: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#varia
==
m.py:4: error: Variable "a.A" is not valid as a type
m.py:4: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
m.py:5: note: Revealed type is "Any"
m.py:5: note: Revealed type is "collections._tuplegetter[Any]"
m.py:7: note: Revealed type is "Any"

[case testAliasForwardFunctionDirect]
Expand Down Expand Up @@ -9345,7 +9345,7 @@ def bar(self):
def f() -> int: pass
[file b.py.2]
def f() -> str: pass
[builtins fixtures/classmethod.pyi]
[builtins fixtures/tuple.pyi]
[out]
==

Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/callable.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import collections
from typing import Generic, Tuple, TypeVar, Union

T = TypeVar('T')
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/classmethod.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import collections
import typing

_T = typing.TypeVar('_T')
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/dict-full.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Builtins stub used in dictionary-related test cases (more complete).

import collections
from _typeshed import SupportsKeysAndGetItem
import _typeshed
from typing import (
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/dict.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# NOTE: Use dict-full.pyi if you need more builtins instead of adding here,
# if feasible.

import collections
from _typeshed import SupportsKeysAndGetItem
import _typeshed
from typing import (
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/for.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# builtins stub used in for statement test cases

import collections
from typing import TypeVar, Generic, Iterable, Iterator, Generator
from abc import abstractmethod, ABCMeta

Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/isinstance.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import collections
from typing import Tuple, TypeVar, Generic, Union, cast, Any, Type

T = TypeVar('T')
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/isinstancelist.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import collections
from typing import (
Iterable, Iterator, TypeVar, List, Mapping, overload, Tuple, Set, Union, Generic, Sequence
)
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/len.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import collections
from typing import Tuple, TypeVar, Generic, Union, Type, Sequence, Mapping
from typing_extensions import Protocol

Expand Down
16 changes: 16 additions & 0 deletions test-data/unit/fixtures/list-simple.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Builtins stub used in list-related test cases.
#
# This is a simpler version of list.pyi

from typing import Sequence, TypeVar

T = TypeVar('T')

class object:
def __init__(self) -> None: pass

class list(Sequence[T]): pass
class type: pass
class int: pass
class str: pass
class dict: pass
1 change: 1 addition & 0 deletions test-data/unit/fixtures/list.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Builtins stub used in list-related test cases.

import collections
from typing import TypeVar, Generic, Iterable, Iterator, Sequence, overload

T = TypeVar('T')
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/property.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import collections
import typing

_T = typing.TypeVar('_T')
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/slice.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Builtins stub used in slicing test cases.
import collections
from typing import Generic, TypeVar
T = TypeVar('T')

Expand Down
6 changes: 4 additions & 2 deletions test-data/unit/fixtures/tuple.pyi
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
# Builtins stub used in tuple-related test cases.

import collections
import _typeshed
from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Optional, overload, Tuple, Type, Self
from typing import Iterable, Iterator, TypeVar, Generic, Sequence, Optional, overload, Tuple, Self

_T = TypeVar("_T")
_Tco = TypeVar('_Tco', covariant=True)

class object:
def __init__(self) -> None: pass
def __new__(cls) -> Self: ...
def __eq__(self, other: object) -> bool: pass

class type:
def __init__(self, *a: object) -> None: pass
def __call__(self, *a: object) -> object: pass
class tuple(Sequence[_Tco], Generic[_Tco]):
def __new__(cls: Type[_T], iterable: Iterable[_Tco] = ...) -> _T: ...
def __new__(cls: type[_T], iterable: Iterable[_Tco] = ...) -> _T: ...
def __iter__(self) -> Iterator[_Tco]: pass
def __contains__(self, item: object) -> bool: pass
@overload
Expand Down
8 changes: 7 additions & 1 deletion test-data/unit/lib-stub/collections.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Iterable, Union, Dict, TypeVar, Optional, Callable, Generic, Sequence, MutableMapping
from typing import Any, Iterable, Union, Dict, TypeVar, Optional, Callable, Generic, Sequence, MutableMapping, overload

def namedtuple(
typename: str,
Expand All @@ -23,3 +23,9 @@ class Counter(Dict[KT, int], Generic[KT]): ...
class deque(Sequence[KT], Generic[KT]): ...

class ChainMap(MutableMapping[KT, VT], Generic[KT, VT]): ...

class _tuplegetter(Generic[KT]):
@overload
def __get__(self, instance: None, owner: type[Any]) -> _tuplegetter[KT]: ...
@overload
def __get__(self, instance: object, owner: type[Any]) -> KT: ...
Loading