From b364d780a830cbfbf6febb989d53b97bb7b7404e Mon Sep 17 00:00:00 2001 From: Nils K <24257556+septatrix@users.noreply.github.com> Date: Thu, 25 Feb 2021 16:32:03 +0100 Subject: [PATCH 001/539] Document availability of Annotated (#790) This was previously not included in the readme although implemented --- typing_extensions/README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 37b8a4c22..eb998a953 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -59,6 +59,7 @@ Python 3.4+ only: Python 3.5+ only: ----------------- +- ``Annotated`` (except on Python 3.5.0-3.5.2) - ``AsyncIterable`` - ``AsyncIterator`` - ``AsyncContextManager`` From c487c47523581defc77036ab0651baf704638eae Mon Sep 17 00:00:00 2001 From: Daniel Ciborowski Date: Mon, 1 Mar 2021 18:06:02 -0500 Subject: [PATCH 002/539] Only allow installing this package for Python 2.7 and 3.4 (#784) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 39bb41930..6fc7c4747 100644 --- a/setup.py +++ b/setup.py @@ -67,5 +67,5 @@ 'checker typehints typehinting typechecking backport', package_dir={'': package_dir}, py_modules=['typing'], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <3.5', classifiers=classifiers) From ea7f88e8d2dd28a5569b2385c21b17063c480c7f Mon Sep 17 00:00:00 2001 From: James Morris <6653392+J-M0@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:37:53 -0500 Subject: [PATCH 003/539] Add OrderedDict to typing_extensions (#791) (The solution for 3.7.0 and 3.7.1 is a little funky, but those releases are dead anyways.) --- typing_extensions/README.rst | 1 + .../src_py3/test_typing_extensions.py | 24 +++++++++++++++ .../src_py3/typing_extensions.py | 29 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index eb998a953..885c4d2f7 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -44,6 +44,7 @@ All Python versions: - ``NewType`` - ``NoReturn`` - ``overload`` (note that older versions of ``typing`` only let you use ``overload`` in stubs) +- ``OrderedDict`` - ``Protocol`` (except on Python 3.5.0) - ``runtime`` (except on Python 3.5.0) - ``Text`` diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 98d02a0b9..6d131b82b 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -610,6 +610,30 @@ class MyDefDict(typing_extensions.DefaultDict[str, int]): if TYPING_3_5_3: self.assertNotIsSubclass(collections.defaultdict, MyDefDict) + @skipUnless(CAN_INSTANTIATE_COLLECTIONS, "Behavior added in typing 3.6.1") + def test_ordereddict_instantiation(self): + self.assertIs( + type(typing_extensions.OrderedDict()), + collections.OrderedDict) + self.assertIs( + type(typing_extensions.OrderedDict[KT, VT]()), + collections.OrderedDict) + self.assertIs( + type(typing_extensions.OrderedDict[str, int]()), + collections.OrderedDict) + + def test_ordereddict_subclass(self): + + class MyOrdDict(typing_extensions.OrderedDict[str, int]): + pass + + od = MyOrdDict() + self.assertIsInstance(od, MyOrdDict) + + self.assertIsSubclass(MyOrdDict, collections.OrderedDict) + if TYPING_3_5_3: + self.assertNotIsSubclass(collections.OrderedDict, MyOrdDict) + def test_chainmap_instantiation(self): self.assertIs(type(typing_extensions.ChainMap()), collections.ChainMap) self.assertIs(type(typing_extensions.ChainMap[KT, VT]()), collections.ChainMap) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 97fa7dc3e..57b8d75e4 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -134,6 +134,7 @@ def _check_methods_in_mro(C, *methods): 'Counter', 'Deque', 'DefaultDict', + 'OrderedDict' 'TypedDict', # Structural checks, a.k.a. protocols. @@ -938,6 +939,34 @@ def __new__(cls, *args, **kwds): return _generic_new(collections.defaultdict, cls, *args, **kwds) +if hasattr(typing, 'OrderedDict'): + OrderedDict = typing.OrderedDict +elif (3, 7, 0) <= sys.version_info[:3] < (3, 7, 2): + OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) +elif _geqv_defined: + class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], + metaclass=_ExtensionsGenericMeta, + extra=collections.OrderedDict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if _geqv(cls, OrderedDict): + return collections.OrderedDict(*args, **kwds) + return _generic_new(collections.OrderedDict, cls, *args, **kwds) +else: + class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], + metaclass=_ExtensionsGenericMeta, + extra=collections.OrderedDict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is OrderedDict: + return collections.OrderedDict(*args, **kwds) + return _generic_new(collections.OrderedDict, cls, *args, **kwds) + + if hasattr(typing, 'Counter'): Counter = typing.Counter elif (3, 5, 0) <= sys.version_info[:3] <= (3, 5, 1): From dacb6b0dc6caa15146e47eba29f9d6772ead3580 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 10 Apr 2021 12:24:47 -0700 Subject: [PATCH 004/539] also run python 3.9 in CI (#796) --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 38809e319..3e6391950 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: python jobs: include: + - name: "3.9" + dist: xenial + python: 3.9.0 - name: "3.8" dist: xenial python: 3.8.0 From c23141fd08aba8284ce76b096bf08fd7c019b51c Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sun, 11 Apr 2021 03:26:42 +0800 Subject: [PATCH 005/539] Support PEP 612 in typing_extensions (Python 3) (#774) --- typing_extensions/README.rst | 7 + .../src_py3/test_typing_extensions.py | 119 ++++++++- .../src_py3/typing_extensions.py | 237 ++++++++++++++++++ 3 files changed, 362 insertions(+), 1 deletion(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 885c4d2f7..9223f1289 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -50,6 +50,7 @@ All Python versions: - ``Text`` - ``Type`` - ``TypedDict`` +- ``TypeAlias`` - ``TYPE_CHECKING`` Python 3.4+ only: @@ -82,6 +83,12 @@ subclass ``typing.Reversible`` as of Python 3.5.3. These changes are _not_ backported to prevent subtle compatibility issues when mixing the differing implementations of modified classes. +Certain types have incorrect runtime behavior due to limitations of older +versions of the typing module. For example, ``ParamSpec`` and ``Concatenate`` +will not work with ``get_args``, ``get_origin`` or user-defined ``Generic``\ s +because they need to be lists to work with older versions of ``Callable``. +These types are only guaranteed to work for static type checking. + Running tests ============= diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 6d131b82b..b12d1a86c 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -13,7 +13,8 @@ from typing import Generic from typing import no_type_check from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict -from typing_extensions import TypeAlias +from typing_extensions import TypeAlias, ParamSpec, Concatenate + try: from typing_extensions import Protocol, runtime, runtime_checkable except ImportError: @@ -1898,6 +1899,118 @@ def test_cannot_subscript(self): with self.assertRaises(TypeError): TypeAlias[int] +class ParamSpecTests(BaseTestCase): + + def test_basic_plain(self): + P = ParamSpec('P') + self.assertEqual(P, P) + self.assertIsInstance(P, ParamSpec) + # Should be hashable + hash(P) + + def test_repr(self): + P = ParamSpec('P') + P_co = ParamSpec('P_co', covariant=True) + P_contra = ParamSpec('P_contra', contravariant=True) + P_2 = ParamSpec('P_2') + self.assertEqual(repr(P), '~P') + self.assertEqual(repr(P_2), '~P_2') + + # Note: PEP 612 doesn't require these to be repr-ed correctly, but + # just follow CPython. + self.assertEqual(repr(P_co), '+P_co') + self.assertEqual(repr(P_contra), '-P_contra') + + def test_valid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') + C1 = typing.Callable[P, int] + C2 = typing.Callable[P, T] + + # Note: no tests for Callable.__args__ and Callable.__parameters__ here + # because pre-3.10 Callable sees ParamSpec as a plain list, not a + # TypeVar. + + # Test collections.abc.Callable too. + if sys.version_info[:2] >= (3, 9): + C3 = collections.abc.Callable[P, int] + C4 = collections.abc.Callable[P, T] + + # ParamSpec instances should also have args and kwargs attributes. + self.assertIn('args', dir(P)) + self.assertIn('kwargs', dir(P)) + P.args + P.kwargs + + # Note: ParamSpec doesn't work for pre-3.10 user-defined Generics due + # to type checks inside Generic. + + def test_pickle(self): + global P, P_co, P_contra + P = ParamSpec('P') + P_co = ParamSpec('P_co', covariant=True) + P_contra = ParamSpec('P_contra', contravariant=True) + for proto in range(pickle.HIGHEST_PROTOCOL): + with self.subTest('Pickle protocol {proto}'.format(proto=proto)): + for paramspec in (P, P_co, P_contra): + z = pickle.loads(pickle.dumps(paramspec, proto)) + self.assertEqual(z.__name__, paramspec.__name__) + self.assertEqual(z.__covariant__, paramspec.__covariant__) + self.assertEqual(z.__contravariant__, paramspec.__contravariant__) + self.assertEqual(z.__bound__, paramspec.__bound__) + + def test_eq(self): + P = ParamSpec('P') + self.assertEqual(P, P) + self.assertEqual(hash(P), hash(P)) + # ParamSpec should compare by id similar to TypeVar in CPython + self.assertNotEqual(ParamSpec('P'), P) + self.assertIsNot(ParamSpec('P'), P) + # Note: normally you don't test this as it breaks when there's + # a hash collision. However, ParamSpec *must* guarantee that + # as long as two objects don't have the same ID, their hashes + # won't be the same. + self.assertNotEqual(hash(ParamSpec('P')), hash(P)) + + +class ConcatenateTests(BaseTestCase): + def test_basics(self): + P = ParamSpec('P') + + class MyClass: ... + + c = Concatenate[MyClass, P] + self.assertNotEqual(c, Concatenate) + + def test_valid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') + C1 = typing.Callable[Concatenate[int, P], int] + C2 = typing.Callable[Concatenate[int, T, P], T] + + # Test collections.abc.Callable too. + if sys.version_info[:2] >= (3, 9): + C3 = collections.abc.Callable[Concatenate[int, P], int] + C4 = collections.abc.Callable[Concatenate[int, T, P], T] + + def test_basic_introspection(self): + P = ParamSpec('P') + C1 = Concatenate[int, P] + C2 = Concatenate[int, T, P] + self.assertEqual(C1.__origin__, Concatenate) + self.assertEqual(C1.__args__, (int, P)) + self.assertEqual(C2.__origin__, Concatenate) + self.assertEqual(C2.__args__, (int, T, P)) + + def test_eq(self): + P = ParamSpec('P') + C1 = Concatenate[int, P] + C2 = Concatenate[int, P] + C3 = Concatenate[int, T, P] + self.assertEqual(C1, C2) + self.assertEqual(hash(C1), hash(C2)) + self.assertNotEqual(C1, C3) + class AllTests(BaseTestCase): @@ -1914,6 +2027,10 @@ def test_typing_extensions_includes_standard(self): self.assertIn('overload', a) self.assertIn('Text', a) self.assertIn('TYPE_CHECKING', a) + self.assertIn('TypeAlias', a) + self.assertIn('ParamSpec', a) + self.assertIn("Concatenate", a) + if TYPING_3_5_3: self.assertIn('Annotated', a) if PEP_560: diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 57b8d75e4..b914111d7 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -115,7 +115,9 @@ def _check_methods_in_mro(C, *methods): __all__ = [ # Super-special typing primitives. 'ClassVar', + 'Concatenate', 'Final', + 'ParamSpec', 'Type', # ABCs (from collections.abc). @@ -147,6 +149,7 @@ def _check_methods_in_mro(C, *methods): 'NewType', 'overload', 'Text', + 'TypeAlias', 'TYPE_CHECKING', ] @@ -2195,3 +2198,237 @@ class TypeAlias(metaclass=_TypeAliasMeta, _root=True): It's invalid when used anywhere except as in the example above. """ __slots__ = () + + +# Python 3.10+ has PEP 612 +if hasattr(typing, 'ParamSpec'): + ParamSpec = typing.ParamSpec +else: + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class ParamSpec(list): + """Parameter specification variable. + + Usage:: + + P = ParamSpec('P') + + Parameter specification variables exist primarily for the benefit of static + type checkers. They are used to forward the parameter types of one + callable to another callable, a pattern commonly found in higher order + functions and decorators. They are only valid when used in ``Concatenate``, + or s the first argument to ``Callable``. In Python 3.10 and higher, + they are also supported in user-defined Generics at runtime. + See class Generic for more information on generic types. An + example for annotating a decorator:: + + T = TypeVar('T') + P = ParamSpec('P') + + def add_logging(f: Callable[P, T]) -> Callable[P, T]: + '''A type-safe decorator to add logging to a function.''' + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + logging.info(f'{f.__name__} was called') + return f(*args, **kwargs) + return inner + + @add_logging + def add_two(x: float, y: float) -> float: + '''Add two numbers together.''' + return x + y + + Parameter specification variables defined with covariant=True or + contravariant=True can be used to declare covariant or contravariant + generic types. These keyword arguments are valid, but their actual semantics + are yet to be decided. See PEP 612 for details. + + Parameter specification variables can be introspected. e.g.: + + P.__name__ == 'T' + P.__bound__ == None + P.__covariant__ == False + P.__contravariant__ == False + + Note that only parameter specification variables defined in global scope can + be pickled. + """ + args = object() + kwargs = object() + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False): + super().__init__([self]) + self.__name__ = name + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if bound: + self.__bound__ = typing._type_check(bound, 'Bound must be a type.') + else: + self.__bound__ = None + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + # Hack to get typing._type_check to pass. + def __call__(self, *args, **kwargs): + pass + + # Note: Can't fake ParamSpec as a TypeVar to get it to work + # with Generics. ParamSpec isn't an instance of TypeVar in 3.10. + # So encouraging code like isinstance(ParamSpec('P'), TypeVar)) + # will lead to breakage in 3.10. + # This also means no accurate __parameters__ for GenericAliases. + +# Inherits from list as a workaround for Callable checks in Python < 3.9.2. +class _ConcatenateGenericAlias(list): + def __init__(self, origin, args): + super().__init__(args) + self.__origin__ = origin + self.__args__ = args + + def __repr__(self): + _type_repr = typing._type_repr + return '{origin}[{args}]' \ + .format(origin=_type_repr(self.__origin__), + args=', '.join(_type_repr(arg) for arg in self.__args__)) + + def __hash__(self): + return hash((self.__origin__, self.__args__)) + +@_tp_cache +def _concatenate_getitem(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Concatenate of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if not isinstance(parameters[-1], ParamSpec): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable.") + msg = "Concatenate[arg, ...]: each arg must be a type." + parameters = tuple(typing._type_check(p, msg) for p in parameters) + return _ConcatenateGenericAlias(self, parameters) + + +if hasattr(typing, 'Concatenate'): + Concatenate = typing.Concatenate + _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa +elif sys.version_info[:2] >= (3, 9): + @_TypeAliasForm + def Concatenate(self, parameters): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + return _concatenate_getitem(self, parameters) + +elif sys.version_info[:2] >= (3, 7): + class _ConcatenateForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + Concatenate = _ConcatenateForm('Concatenate', + doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """) + +elif hasattr(typing, '_FinalTypingBase'): + class _ConcatenateAliasMeta(typing.TypingMeta): + """Metaclass for Concatenate.""" + + def __repr__(self): + return 'typing_extensions.Concatenate' + + class _ConcatenateAliasBase(typing._FinalTypingBase, + metaclass=_ConcatenateAliasMeta, + _root=True): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("Concatenate cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Concatenate cannot be used with issubclass().") + + def __repr__(self): + return 'typing_extensions.Concatenate' + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + Concatenate = _ConcatenateAliasBase(_root=True) +# For 3.5.0 - 3.5.2 +else: + class _ConcatenateAliasMeta(typing.TypingMeta): + """Metaclass for Concatenate.""" + + def __instancecheck__(self, obj): + raise TypeError("TypeAlias cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("TypeAlias cannot be used with issubclass().") + + def __call__(self, *args, **kwargs): + raise TypeError("Cannot instantiate TypeAlias") + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + class Concatenate(metaclass=_ConcatenateAliasMeta, _root=True): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + __slots__ = () From 6a2a490d60be18ed36894c1eaf23102e671a3df9 Mon Sep 17 00:00:00 2001 From: Dawid Kraczkowski Date: Sat, 10 Apr 2021 22:43:30 +0200 Subject: [PATCH 006/539] =?UTF-8?q?Bring=20in=20protocol=E2=80=99s=20=5F?= =?UTF-8?q?=5Finit=5F=5F=20behaviour=20same=20like=20in=20python=20>=203.8?= =?UTF-8?q?=20(#780)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src_py3/test_typing_extensions.py | 9 +++++++++ typing_extensions/src_py3/typing_extensions.py | 16 +++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index b12d1a86c..60a6a51d5 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -1448,6 +1448,15 @@ def close(self): self.assertIsSubclass(B, Custom) self.assertNotIsSubclass(A, Custom) + def test_no_init_same_for_different_protocol_implementations(self): + class CustomProtocolWithoutInitA(Protocol): + pass + + class CustomProtocolWithoutInitB(Protocol): + pass + + self.assertEqual(CustomProtocolWithoutInitA.__init__, CustomProtocolWithoutInitB.__init__) + class TypedDictTests(BaseTestCase): diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index b914111d7..befc47a85 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -1151,6 +1151,11 @@ def _is_callable_members_only(cls): if hasattr(typing, 'Protocol'): Protocol = typing.Protocol elif HAVE_PROTOCOLS and not PEP_560: + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + class _ProtocolMeta(GenericMeta): """Internal metaclass for Protocol. @@ -1241,9 +1246,6 @@ def __init__(cls, *args, **kwargs): raise TypeError('Protocols can only inherit from other' ' protocols, got %r' % base) - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') cls.__init__ = _no_init def _proto_hook(other): @@ -1398,6 +1400,10 @@ def __new__(cls, *args, **kwds): elif PEP_560: from typing import _type_check, _GenericAlias, _collect_type_vars # noqa + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + class _ProtocolMeta(abc.ABCMeta): # This metaclass is a bit unfortunate and exists only because of the lack # of __instancehook__. @@ -1574,10 +1580,6 @@ def _proto_hook(other): isinstance(base, _ProtocolMeta) and base._is_protocol): raise TypeError('Protocols can only inherit from other' ' protocols, got %r' % base) - - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') cls.__init__ = _no_init From 40932e3975a1be61d5904f6f790b4631cbaeb74b Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Sun, 11 Apr 2021 21:02:29 +0100 Subject: [PATCH 007/539] Fixed required/optional keys with old-style TypedDict (#778) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alex Grönholm --- typing_extensions/src_py3/test_typing_extensions.py | 6 +++++- typing_extensions/src_py3/typing_extensions.py | 13 ++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 60a6a51d5..da78255ea 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -1530,7 +1530,7 @@ def test_typeddict_create_errors(self): def test_typeddict_errors(self): Emp = TypedDict('Emp', {'name': str, 'id': int}) - if sys.version_info[:2] >= (3, 9): + if sys.version_info >= (3, 9, 2): self.assertEqual(TypedDict.__module__, 'typing') else: self.assertEqual(TypedDict.__module__, 'typing_extensions') @@ -1586,11 +1586,15 @@ def test_total(self): self.assertEqual(D(), {}) self.assertEqual(D(x=1), {'x': 1}) self.assertEqual(D.__total__, False) + self.assertEqual(D.__required_keys__, frozenset()) + self.assertEqual(D.__optional_keys__, {'x'}) if PY36: self.assertEqual(Options(), {}) self.assertEqual(Options(log_level=2), {'log_level': 2}) self.assertEqual(Options.__total__, False) + self.assertEqual(Options.__required_keys__, frozenset()) + self.assertEqual(Options.__optional_keys__, {'log_level', 'log_path'}) @skipUnless(PY36, 'Python 3.6 required') def test_optional_keys(self): diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index befc47a85..ac18d5653 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -1618,9 +1618,11 @@ def __index__(self) -> int: pass -if sys.version_info[:2] >= (3, 9): +if sys.version_info >= (3, 9, 2): # The standard library TypedDict in Python 3.8 does not store runtime information # about which (if any) keys are optional. See https://bugs.python.org/issue38834 + # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" + # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 TypedDict = typing.TypedDict else: def _check_fails(cls, other): @@ -1677,19 +1679,24 @@ def _typeddict_new(*args, total=True, **kwargs): raise TypeError("TypedDict takes either a dict or keyword arguments," " but not both") - ns = {'__annotations__': dict(fields), '__total__': total} + ns = {'__annotations__': dict(fields)} try: # Setting correct module is necessary to make typed dict classes pickleable. ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass - return _TypedDictMeta(typename, (), ns) + return _TypedDictMeta(typename, (), ns, total=total) _typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,' ' /, *, total=True, **kwargs)') class _TypedDictMeta(type): + def __init__(cls, name, bases, ns, total=True): + # In Python 3.4 and 3.5 the __init__ method also needs to support the keyword arguments. + # See https://www.python.org/dev/peps/pep-0487/#implementation-details + super(_TypedDictMeta, cls).__init__(name, bases, ns) + def __new__(cls, name, bases, ns, total=True): # Create new typed dict class object. # This method is called directly when TypedDict is subclassed, From 4ba98e8c348cd0b2e9b8cc67e6b99eb19a0ed433 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 13 Apr 2021 08:26:08 -0700 Subject: [PATCH 008/539] backport ParamSpecArgs/Kwargs (#798) From python/cpython#25298. I also added more tests for get_args/get_origin, which previously didn't exist in in typing_extensions. --- README.md | 4 +- typing_extensions/README.rst | 4 + .../src_py3/test_typing_extensions.py | 95 +++++++++++++++++-- .../src_py3/typing_extensions.py | 85 +++++++++++++++-- 4 files changed, 174 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3c2d45b58..90b345cfb 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ Workflow Workflow for PyPI releases -------------------------- -* Run tests under all supported versions. As of May 2019 this includes - 2.7, 3.4, 3.5, 3.6, 3.7. +* Run tests under all supported versions. As of April 2021 this includes + 2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9. * On macOS, you can use `pyenv `_ to manage multiple Python installations. Long story short: diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 9223f1289..8dba9ca5b 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -57,6 +57,10 @@ Python 3.4+ only: ----------------- - ``ChainMap`` +- ``ParamSpec`` +- ``Concatenate`` +- ``ParamSpecArgs`` +- ``ParamSpecKwargs`` Python 3.5+ only: ----------------- diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index da78255ea..275a05d55 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -7,13 +7,13 @@ import subprocess import types from unittest import TestCase, main, skipUnless, skipIf -from typing import TypeVar, Optional +from typing import TypeVar, Optional, Union from typing import T, KT, VT # Not in __all__. -from typing import Tuple, List, Dict, Iterator +from typing import Tuple, List, Dict, Iterator, Callable from typing import Generic from typing import no_type_check from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict -from typing_extensions import TypeAlias, ParamSpec, Concatenate +from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs try: from typing_extensions import Protocol, runtime, runtime_checkable @@ -519,6 +519,80 @@ def test_final_forward_ref(self): self.assertNotEqual(gth(Loop, globals())['attr'], Final) +@skipUnless(PEP_560, "Python 3.7+ required") +class GetUtilitiesTestCase(TestCase): + def test_get_origin(self): + from typing_extensions import get_origin + + T = TypeVar('T') + P = ParamSpec('P') + class C(Generic[T]): pass + self.assertIs(get_origin(C[int]), C) + self.assertIs(get_origin(C[T]), C) + self.assertIs(get_origin(int), None) + self.assertIs(get_origin(ClassVar[int]), ClassVar) + self.assertIs(get_origin(Union[int, str]), Union) + self.assertIs(get_origin(Literal[42, 43]), Literal) + self.assertIs(get_origin(Final[List[int]]), Final) + self.assertIs(get_origin(Generic), Generic) + self.assertIs(get_origin(Generic[T]), Generic) + self.assertIs(get_origin(List[Tuple[T, T]][int]), list) + self.assertIs(get_origin(Annotated[T, 'thing']), Annotated) + self.assertIs(get_origin(List), list) + self.assertIs(get_origin(Tuple), tuple) + self.assertIs(get_origin(Callable), collections.abc.Callable) + if sys.version_info >= (3, 9): + self.assertIs(get_origin(list[int]), list) + self.assertIs(get_origin(list), None) + self.assertIs(get_origin(P.args), P) + self.assertIs(get_origin(P.kwargs), P) + + def test_get_args(self): + from typing_extensions import get_args + + T = TypeVar('T') + class C(Generic[T]): pass + self.assertEqual(get_args(C[int]), (int,)) + self.assertEqual(get_args(C[T]), (T,)) + self.assertEqual(get_args(int), ()) + self.assertEqual(get_args(ClassVar[int]), (int,)) + self.assertEqual(get_args(Union[int, str]), (int, str)) + self.assertEqual(get_args(Literal[42, 43]), (42, 43)) + self.assertEqual(get_args(Final[List[int]]), (List[int],)) + self.assertEqual(get_args(Union[int, Tuple[T, int]][str]), + (int, Tuple[str, int])) + self.assertEqual(get_args(typing.Dict[int, Tuple[T, T]][Optional[int]]), + (int, Tuple[Optional[int], Optional[int]])) + self.assertEqual(get_args(Callable[[], T][int]), ([], int)) + self.assertEqual(get_args(Callable[..., int]), (..., int)) + self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]]), + (int, Callable[[Tuple[T, ...]], str])) + self.assertEqual(get_args(Tuple[int, ...]), (int, ...)) + self.assertEqual(get_args(Tuple[()]), ((),)) + self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three'])) + self.assertEqual(get_args(List), ()) + self.assertEqual(get_args(Tuple), ()) + self.assertEqual(get_args(Callable), ()) + if sys.version_info >= (3, 9): + self.assertEqual(get_args(list[int]), (int,)) + self.assertEqual(get_args(list), ()) + if sys.version_info >= (3, 9): + # Support Python versions with and without the fix for + # https://bugs.python.org/issue42195 + # The first variant is for 3.9.2+, the second for 3.9.0 and 1 + self.assertIn(get_args(collections.abc.Callable[[int], str]), + (([int], str), ([[int]], str))) + self.assertIn(get_args(collections.abc.Callable[[], str]), + (([], str), ([[]], str))) + self.assertEqual(get_args(collections.abc.Callable[..., str]), (..., str)) + P = ParamSpec('P') + # In 3.9 and lower we use typing_extensions's hacky implementation + # of ParamSpec, which gets incorrectly wrapped in a list + self.assertIn(get_args(Callable[P, int]), [(P, int), ([P], int)]) + self.assertEqual(get_args(Callable[Concatenate[int, P], int]), + (Concatenate[int, P], int)) + + class CollectionsAbcTests(BaseTestCase): def test_isinstance_collections(self): @@ -1952,8 +2026,17 @@ def test_valid_uses(self): # ParamSpec instances should also have args and kwargs attributes. self.assertIn('args', dir(P)) self.assertIn('kwargs', dir(P)) - P.args - P.kwargs + + def test_args_kwargs(self): + P = ParamSpec('P') + self.assertIn('args', dir(P)) + self.assertIn('kwargs', dir(P)) + self.assertIsInstance(P.args, ParamSpecArgs) + self.assertIsInstance(P.kwargs, ParamSpecKwargs) + self.assertIs(P.args.__origin__, P) + self.assertIs(P.kwargs.__origin__, P) + self.assertEqual(repr(P.args), "P.args") + self.assertEqual(repr(P.kwargs), "P.kwargs") # Note: ParamSpec doesn't work for pre-3.10 user-defined Generics due # to type checks inside Generic. @@ -2072,7 +2155,7 @@ def test_typing_extensions_defers_when_possible(self): 'Final', 'get_type_hints' } - if sys.version_info[:2] == (3, 8): + if sys.version_info < (3, 10): exclude |= {'get_args', 'get_origin'} for item in typing_extensions.__all__: if item not in exclude and hasattr(typing, item): diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index ac18d5653..ab4e6986f 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2065,11 +2065,23 @@ class Annotated(metaclass=AnnotatedMeta): # Python 3.8 has get_origin() and get_args() but those implementations aren't # Annotated-aware, so we can't use those, only Python 3.9 versions will do. -if sys.version_info[:2] >= (3, 9): +# Similarly, Python 3.9's implementation doesn't support ParamSpecArgs and +# ParamSpecKwargs. +if sys.version_info[:2] >= (3, 10): get_origin = typing.get_origin get_args = typing.get_args elif PEP_560: - from typing import _GenericAlias # noqa + from typing import _GenericAlias + try: + # 3.9+ + from typing import _BaseGenericAlias + except ImportError: + _BaseGenericAlias = _GenericAlias + try: + # 3.9+ + from typing import GenericAlias + except ImportError: + GenericAlias = _GenericAlias def get_origin(tp): """Get the unsubscripted version of a type. @@ -2084,10 +2096,12 @@ def get_origin(tp): get_origin(Generic[T]) is Generic get_origin(Union[T, int]) is Union get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P """ if isinstance(tp, _AnnotatedAlias): return Annotated - if isinstance(tp, _GenericAlias): + if isinstance(tp, (_GenericAlias, GenericAlias, _BaseGenericAlias, + ParamSpecArgs, ParamSpecKwargs)): return tp.__origin__ if tp is Generic: return Generic @@ -2106,7 +2120,9 @@ def get_args(tp): """ if isinstance(tp, _AnnotatedAlias): return (tp.__origin__,) + tp.__metadata__ - if isinstance(tp, _GenericAlias) and not tp._special: + if isinstance(tp, (_GenericAlias, GenericAlias)): + if getattr(tp, "_special", False): + return () res = tp.__args__ if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: res = (list(res[:-1]), res[-1]) @@ -2210,9 +2226,60 @@ class TypeAlias(metaclass=_TypeAliasMeta, _root=True): # Python 3.10+ has PEP 612 +if hasattr(typing, 'ParamSpecArgs'): + ParamSpecArgs = typing.ParamSpecArgs + ParamSpecKwargs = typing.ParamSpecKwargs +else: + class _Immutable: + """Mixin to indicate that object should not be copied.""" + __slots__ = () + + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + + class ParamSpecArgs(_Immutable): + """The args for a ParamSpec object. + + Given a ParamSpec object P, P.args is an instance of ParamSpecArgs. + + ParamSpecArgs objects have a reference back to their ParamSpec: + + P.args.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return "{}.args".format(self.__origin__.__name__) + + class ParamSpecKwargs(_Immutable): + """The kwargs for a ParamSpec object. + + Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs. + + ParamSpecKwargs objects have a reference back to their ParamSpec: + + P.kwargs.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return "{}.kwargs".format(self.__origin__.__name__) + if hasattr(typing, 'ParamSpec'): ParamSpec = typing.ParamSpec else: + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. class ParamSpec(list): """Parameter specification variable. @@ -2260,8 +2327,14 @@ def add_two(x: float, y: float) -> float: Note that only parameter specification variables defined in global scope can be pickled. """ - args = object() - kwargs = object() + + @property + def args(self): + return ParamSpecArgs(self) + + @property + def kwargs(self): + return ParamSpecKwargs(self) def __init__(self, name, *, bound=None, covariant=False, contravariant=False): super().__init__([self]) From c33fe16001e11ba01bd39edb1ba49b99233f5410 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 30 Apr 2021 17:39:51 -0700 Subject: [PATCH 009/539] Implement TypeGuard (PEP 649) (#803) This should be the last missing piece for a 3.10-compatible release. The implementation was mostly inspired by that of Final, with an additional special case for 3.9. --- typing_extensions/README.rst | 3 +- .../src_py3/test_typing_extensions.py | 47 ++- .../src_py3/typing_extensions.py | 289 ++++++++++++++++++ 3 files changed, 337 insertions(+), 2 deletions(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 8dba9ca5b..98f621358 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -46,7 +46,7 @@ All Python versions: - ``overload`` (note that older versions of ``typing`` only let you use ``overload`` in stubs) - ``OrderedDict`` - ``Protocol`` (except on Python 3.5.0) -- ``runtime`` (except on Python 3.5.0) +- ``runtime_checkable`` (except on Python 3.5.0) - ``Text`` - ``Type`` - ``TypedDict`` @@ -61,6 +61,7 @@ Python 3.4+ only: - ``Concatenate`` - ``ParamSpecArgs`` - ``ParamSpecKwargs`` +- ``TypeGuard`` Python 3.5+ only: ----------------- diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 275a05d55..e2889ce0e 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -13,7 +13,7 @@ from typing import Generic from typing import no_type_check from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict -from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs +from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard try: from typing_extensions import Protocol, runtime, runtime_checkable @@ -2108,6 +2108,51 @@ def test_eq(self): self.assertNotEqual(C1, C3) +class TypeGuardTests(BaseTestCase): + def test_basics(self): + TypeGuard[int] # OK + self.assertEqual(TypeGuard[int], TypeGuard[int]) + + def foo(arg) -> TypeGuard[int]: ... + self.assertEqual(gth(foo), {'return': TypeGuard[int]}) + + def test_repr(self): + if hasattr(typing, 'TypeGuard'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(TypeGuard), '{}.TypeGuard'.format(mod_name)) + cv = TypeGuard[int] + self.assertEqual(repr(cv), '{}.TypeGuard[int]'.format(mod_name)) + cv = TypeGuard[Employee] + self.assertEqual(repr(cv), '{}.TypeGuard[{}.Employee]'.format(mod_name, __name__)) + cv = TypeGuard[Tuple[int]] + self.assertEqual(repr(cv), '{}.TypeGuard[typing.Tuple[int]]'.format(mod_name)) + + @skipUnless(SUBCLASS_CHECK_FORBIDDEN, "Behavior added in typing 3.5.3") + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(TypeGuard)): + pass + with self.assertRaises(TypeError): + class C(type(TypeGuard[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + TypeGuard() + with self.assertRaises(TypeError): + type(TypeGuard)() + with self.assertRaises(TypeError): + type(TypeGuard[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, TypeGuard[int]) + with self.assertRaises(TypeError): + issubclass(int, TypeGuard) + + class AllTests(BaseTestCase): def test_typing_extensions_includes_standard(self): diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index ab4e6986f..82d1c2dc2 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -150,6 +150,7 @@ def _check_methods_in_mro(C, *methods): 'overload', 'Text', 'TypeAlias', + 'TypeGuard', 'TYPE_CHECKING', ] @@ -2514,3 +2515,291 @@ class Concatenate(metaclass=_ConcatenateAliasMeta, _root=True): See PEP 612 for detailed information. """ __slots__ = () + +if hasattr(typing, 'TypeGuard'): + TypeGuard = typing.TypeGuard +elif sys.version_info[:2] >= (3, 9): + class _TypeGuardForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_TypeGuardForm + def TypeGuard(self, parameters): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + item = typing._type_check(parameters, '{} accepts only single type.'.format(self)) + return _GenericAlias(self, (item,)) + +elif sys.version_info[:2] >= (3, 7): + class _TypeGuardForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + '{} accepts only a single type'.format(self._name)) + return _GenericAlias(self, (item,)) + + TypeGuard = _TypeGuardForm( + 'TypeGuard', + doc="""Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """) +elif hasattr(typing, '_FinalTypingBase'): + class _TypeGuard(typing._FinalTypingBase, _root=True): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + '{} accepts only a single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(typing._type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _TypeGuard): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + TypeGuard = _TypeGuard(_root=True) +else: + class _TypeGuardMeta(typing.TypingMeta): + """Metaclass for TypeGuard""" + + def __new__(cls, name, bases, namespace, tp=None, _root=False): + self = super().__new__(cls, name, bases, namespace, _root=_root) + if tp is not None: + self.__type__ = tp + return self + + def __instancecheck__(self, obj): + raise TypeError("TypeGuard cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("TypeGuard cannot be used with issubclass().") + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is not None: + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + param = typing._type_check( + item, + '{} accepts only single type.'.format(cls.__name__[1:])) + return cls(self.__name__, self.__bases__, + dict(self.__dict__), tp=param, _root=True) + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(self.__name__, self.__bases__, + dict(self.__dict__), tp=self.__type__, + _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(typing._type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not hasattr(other, "__type__"): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + class TypeGuard(typing.Final, metaclass=_TypeGuardMeta, _root=True): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + __type__ = None From edd78b2253abffc5cc82c32a43c22845fb7c465f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 1 May 2021 10:49:46 -0700 Subject: [PATCH 010/539] prepare release 3.10.0.0 (#805) --- setup.py | 2 +- tox.ini | 2 +- typing_extensions/setup.py | 24 +++++++++++++----------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/setup.py b/setup.py index 6fc7c4747..49578939c 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ 'to install the typing package.\n') exit(1) -version = '3.7.4.3' +version = '3.10.0.0' description = 'Type Hints for Python' long_description = '''\ Typing -- Type Hints for Python diff --git a/tox.ini b/tox.ini index 1cfaa550b..750c38ff7 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, py34, py35, py36 +envlist = py27, py34 [testenv] changedir = src diff --git a/typing_extensions/setup.py b/typing_extensions/setup.py index 023ab3983..7b1611cb8 100644 --- a/typing_extensions/setup.py +++ b/typing_extensions/setup.py @@ -9,23 +9,24 @@ 'to install the typing package.\n') exit(1) -version = '3.7.4.3' +version = '3.10.0.0' description = 'Backported and Experimental Type Hints for Python 3.5+' long_description = '''\ Typing Extensions -- Backported and Experimental Type Hints for Python -The ``typing`` module was added to the standard library in Python 3.5 on -a provisional basis and will no longer be provisional in Python 3.7. However, -this means users of Python 3.5 - 3.6 who are unable to upgrade will not be +The ``typing`` module was added to the standard library in Python 3.5, but +many new features have been added to the module since then. +This means users of Python 3.5 - 3.6 who are unable to upgrade will not be able to take advantage of new types added to the ``typing`` module, such as -``typing.Text`` or ``typing.Coroutine``. +``typing.Protocol`` or ``typing.TypedDict``. -The ``typing_extensions`` module contains both backports of these changes -as well as experimental types that will eventually be added to the ``typing`` -module, such as ``Protocol`` or ``TypedDict``. +The ``typing_extensions`` module contains backports of these changes. +Experimental types that will eventually be added to the ``typing`` +module are also included in ``typing_extensions``, such as +``typing.ParamSpec`` and ``typing.TypeGuard``. -Users of other Python versions should continue to install and use -the ``typing`` module from PyPi instead of using this one unless specifically +Users of Python versions before 3.5 should install and use +the ``typing`` module from PyPI instead of using this one, unless specifically writing code that must be compatible with multiple Python versions or requires experimental types. ''' @@ -43,6 +44,7 @@ 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Software Development', ] @@ -57,7 +59,7 @@ version=version, description=description, long_description=long_description, - author='Guido van Rossum, Jukka Lehtosalo, Lukasz Langa, Michael Lee', + author='Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee', author_email='levkivskyi@gmail.com', url='https://github.com/python/typing/blob/master/typing_extensions/README.rst', license='PSF', From fcc7df6f12d50bc5cd40835549944ebe357095ab Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 May 2021 01:00:26 -0700 Subject: [PATCH 011/539] add CONTRIBUTING.md (#806) --- CONTRIBUTING.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 40 ++---------------------- 2 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..14d7b2168 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,83 @@ +This repository contains backports of the CPython `typing` module to earlier versions of +Python. Therefore, code in this repo should follow CPython's style guidelines and +contributors need to sign the PSF Contributor Agreement. + +# typing + +The `typing` module provided by this repository is a backport for Python versions that +do not have `typing` in the standard library: Python 2.7 and 3.4. These versions are no +longer officially supported by CPython, so there is little remaining interest in keeping +the backport up to date. We will accept contributions backporting new features to +`typing`, but we are no longer actively requiring Python 2 support for all +contributions. + +# typing_extensions + +The `typing_extensions` module provides a way to access new features from the standard +library `typing` module in older versions of Python. For example, Python 3.10 adds +`typing.TypeGuard`, but users of older versions of Python can use `typing_extensions` to +use `TypeGuard` in their code even if they are unable to upgrade to Python 3.10. + +If you contribute the runtime implementation of a new `typing` feature to CPython, you +are encouraged to also implement the feature in `typing_extensions`. Because the runtime +implementation of much of the infrastructure in the `typing` module has changed over +time, this may require different code for some older Python versions. + +`typing_extensions` may also include experimental features that are not yet part of the +standard library, so that users can experiment with them before they are added to the +standard library. Such features should ideally already be specified in a PEP or draft +PEP. + +`typing_extensions` still supports all Python versions supported by `typing`, down to +Python 2.7 and 3.4. However, it is OK to omit support for Python versions that have +reached end of life if doing so is too difficult or otherwise does not make sense. For +example, `typing_extensions.AsyncGenerator` only exists on Python 3.6 and higher, +because async generators were added to the language in 3.6. + +# Versioning scheme + +`typing_extensions` and `typing` are usually released together using the same version +numbers. The version number indicates the version of the standard library `typing` +module that is reflected in the backport. For example, `typing_extensions` version +3.10.0.0 includes features from the Python 3.10.0 standard library's `typing` module. A +new release that doesn't include any new standard library features would be called +3.10.0.1. + +# Workflow for PyPI releases + +- Do this for both `typing` and `typing_extensions` + +- Run tests under all supported versions. As of April 2021 this includes 2.7, 3.4, 3.5, + 3.6, 3.7, 3.8, 3.9. + +- On macOS, you can use `pyenv `\_ to manage multiple + Python installations. Long story short: + + - `xcode-select --install` + - `brew install pyenv` + - `echo 'eval "$(pyenv init -)"' >> ~/.bash_profile` + - Open a new shell + - `pyenv install 3.5.3` + - `pyenv install 3.4.6` + - (assuming you already have 2.7.13 and 3.6.1 from Homebrew) + - `pyenv global system 3.5.3 3.4.6` + - (or some more recent versions) + +- You can use `tox` to automate running tests. + +- Update the version number in `setup.py`. + +- Build the source and wheel distributions: + + - `pip3 install -U setuptools wheel` + - `pip2 install -U setuptools wheel` + - `rm -rf dist/ build/` + - `python3 setup.py sdist bdist_wheel` + - `rm -rf build/` (Works around + `a Wheel bug `\_) + - `python2 setup.py bdist_wheel` + +- Install the built distributions locally and test (if you were using `tox`, you already + tested the source distribution). + +- Make sure twine is up to date, then run `twine upload dist/*`. diff --git a/README.md b/README.md index 90b345cfb..a1df5e212 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This GitHub repo is used for three separate things: [typing-sig](https://mail.python.org/mailman3/lists/typing-sig.python.org/) is more appropriate these days. -- A copy of the `typing` module for older Python versions (2.7 and +- A backport of the `typing` module for older Python versions (2.7 and 3.4) is maintained here. Note that the canonical source lives [upstream](https://github.com/python/cpython/blob/master/Lib/typing.py) in the CPython repo. @@ -20,42 +20,8 @@ This GitHub repo is used for three separate things: Workflow -------- +* See [CONTRIBUTING.md](/CONTRIBUTING.md) for more. + * The typing.py module and its unittests are edited in the `src` subdirectory of this repo. The `python2` subdirectory contains the Python 2 backport. - -Workflow for PyPI releases --------------------------- - -* Run tests under all supported versions. As of April 2021 this includes - 2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9. - -* On macOS, you can use `pyenv `_ to - manage multiple Python installations. Long story short: - - * ``xcode-select --install`` - * ``brew install pyenv`` - * ``echo 'eval "$(pyenv init -)"' >> ~/.bash_profile`` - * Open a new shell - * ``pyenv install 3.5.3`` - * ``pyenv install 3.4.6`` - * (assuming you already have 2.7.13 and 3.6.1 from Homebrew) - * ``pyenv global system 3.5.3 3.4.6`` - -* You can use ``tox`` to automate running tests. - -* Update the version number in ``setup.py``. - -* Build the source and wheel distributions: - - * ``pip3 install -U setuptools wheel`` - * ``pip2 install -U setuptools wheel`` - * ``rm -rf dist/ build/`` - * ``python3 setup.py sdist bdist_wheel`` - * ``rm -rf build/`` (Works around `a Wheel bug `_) - * ``python2 setup.py bdist_wheel`` - -* Install the built distributions locally and test (if you - were using ``tox``, you already tested the source distribution). - -* Make sure twine is up to date, then run ``twine upload dist/*``. From c4191ac15aa54c8269ccf2e6292b4a9cc928359e Mon Sep 17 00:00:00 2001 From: Bas van Beek <43369155+BvB93@users.noreply.github.com> Date: Tue, 4 May 2021 22:12:45 +0200 Subject: [PATCH 012/539] Add a missing comma to `__all__` (#808) * BUG: Added a missing comma in `__all__` * TST: Add a test to ensure that all objects in `__all__` are present in the module --- typing_extensions/src_py3/test_typing_extensions.py | 4 ++++ typing_extensions/src_py3/typing_extensions.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index e2889ce0e..3f3c2f9e5 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -2191,6 +2191,10 @@ def test_typing_extensions_includes_standard(self): self.assertIn('Protocol', a) self.assertIn('runtime', a) + # Check that all objects in `__all__` are present in the module + for name in a: + self.assertTrue(hasattr(typing_extensions, name)) + def test_typing_extensions_defers_when_possible(self): exclude = { 'overload', diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 82d1c2dc2..433b15feb 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -136,7 +136,7 @@ def _check_methods_in_mro(C, *methods): 'Counter', 'Deque', 'DefaultDict', - 'OrderedDict' + 'OrderedDict', 'TypedDict', # Structural checks, a.k.a. protocols. From 2de0a9324080da3ce9b1445b3729248a6f1cee53 Mon Sep 17 00:00:00 2001 From: Shannon Zhu Date: Thu, 3 Jun 2021 18:53:01 -0700 Subject: [PATCH 013/539] Initial sphinx docs setup (#814) Run Sphinx initialize to set up structure for Python typing documentation. See typing-sig discussion: https://mail.python.org/archives/list/typing-sig@python.org/thread/4E7V7MVO4FQVYPVELMCAYFEIYJRXDSY2/ Python docs community issue: python/docs-community#8 --- docs/Makefile | 20 +++++++++++++++++++ docs/conf.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 20 +++++++++++++++++++ docs/make.bat | 35 +++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..d4bb2cbb9 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..70960ded0 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,52 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'typing' +copyright = '2021, The Python Typing Team' +author = 'The Python Typing Team' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..f08f4cf90 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +.. typing documentation master file, created by + sphinx-quickstart on Mon May 24 16:43:52 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to the Python Type System documentation! +================================================ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000..2119f5109 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd From a1143792004b6e2e5482f76f419989d9c63a0aea Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 16 Jun 2021 20:50:57 +0800 Subject: [PATCH 014/539] Support most use cases for PEP 612 with Generic (#817) --- typing_extensions/README.rst | 4 +- .../src_py3/test_typing_extensions.py | 55 ++++++++++++++++--- .../src_py3/typing_extensions.py | 44 +++++++++++++-- 3 files changed, 87 insertions(+), 16 deletions(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 98f621358..4166510a7 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -90,8 +90,8 @@ issues when mixing the differing implementations of modified classes. Certain types have incorrect runtime behavior due to limitations of older versions of the typing module. For example, ``ParamSpec`` and ``Concatenate`` -will not work with ``get_args``, ``get_origin`` or user-defined ``Generic``\ s -because they need to be lists to work with older versions of ``Callable``. +will not work with ``get_args``, ``get_origin``. Certain PEP 612 special cases +in user-defined ``Generic``\ s are also not available. These types are only guaranteed to work for static type checking. Running tests diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 3f3c2f9e5..06d4cc40a 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -2012,25 +2012,38 @@ def test_valid_uses(self): P = ParamSpec('P') T = TypeVar('T') C1 = typing.Callable[P, int] + # Callable in Python 3.5.2 might be bugged when collecting __args__. + # https://github.com/python/cpython/blob/91185fe0284a04162e0b3425b53be49bdbfad67d/Lib/typing.py#L1026 + PY_3_5_2 = sys.version_info[:3] == (3, 5, 2) + if not PY_3_5_2: + self.assertEqual(C1.__args__, (P, int)) + self.assertEqual(C1.__parameters__, (P,)) C2 = typing.Callable[P, T] + if not PY_3_5_2: + self.assertEqual(C2.__args__, (P, T)) + self.assertEqual(C2.__parameters__, (P, T)) - # Note: no tests for Callable.__args__ and Callable.__parameters__ here - # because pre-3.10 Callable sees ParamSpec as a plain list, not a - # TypeVar. # Test collections.abc.Callable too. if sys.version_info[:2] >= (3, 9): + # Note: no tests for Callable.__parameters__ here + # because types.GenericAlias Callable is hardcoded to search + # for tp_name "TypeVar" in C. This was changed in 3.10. C3 = collections.abc.Callable[P, int] + self.assertEqual(C3.__args__, (P, int)) C4 = collections.abc.Callable[P, T] + self.assertEqual(C4.__args__, (P, T)) # ParamSpec instances should also have args and kwargs attributes. - self.assertIn('args', dir(P)) - self.assertIn('kwargs', dir(P)) + # Note: not in dir(P) because of __class__ hacks + self.assertTrue(hasattr(P, 'args')) + self.assertTrue(hasattr(P, 'kwargs')) def test_args_kwargs(self): P = ParamSpec('P') - self.assertIn('args', dir(P)) - self.assertIn('kwargs', dir(P)) + # Note: not in dir(P) because of __class__ hacks + self.assertTrue(hasattr(P, 'args')) + self.assertTrue(hasattr(P, 'kwargs')) self.assertIsInstance(P.args, ParamSpecArgs) self.assertIsInstance(P.kwargs, ParamSpecKwargs) self.assertIs(P.args.__origin__, P) @@ -2038,8 +2051,32 @@ def test_args_kwargs(self): self.assertEqual(repr(P.args), "P.args") self.assertEqual(repr(P.kwargs), "P.kwargs") - # Note: ParamSpec doesn't work for pre-3.10 user-defined Generics due - # to type checks inside Generic. + def test_user_generics(self): + T = TypeVar("T") + P = ParamSpec("P") + P_2 = ParamSpec("P_2") + + class X(Generic[T, P]): + pass + + G1 = X[int, P_2] + self.assertEqual(G1.__args__, (int, P_2)) + self.assertEqual(G1.__parameters__, (P_2,)) + + G2 = X[int, Concatenate[int, P_2]] + self.assertEqual(G2.__args__, (int, Concatenate[int, P_2])) + self.assertEqual(G2.__parameters__, (P_2,)) + + # The following are some valid uses cases in PEP 612 that don't work: + # These do not work in 3.9, _type_check blocks the list and ellipsis. + # G3 = X[int, [int, bool]] + # G4 = X[int, ...] + # G5 = Z[[int, str, bool]] + # Not working because this is special-cased in 3.10. + # G6 = Z[int, str, bool] + + class Z(Generic[P]): + pass def test_pickle(self): global P, P_co, P_contra diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 433b15feb..e4d164404 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2329,6 +2329,9 @@ def add_two(x: float, y: float) -> float: be pickled. """ + # Trick Generic __parameters__. + __class__ = TypeVar + @property def args(self): return ParamSpecArgs(self) @@ -2377,14 +2380,31 @@ def __reduce__(self): def __call__(self, *args, **kwargs): pass - # Note: Can't fake ParamSpec as a TypeVar to get it to work - # with Generics. ParamSpec isn't an instance of TypeVar in 3.10. - # So encouraging code like isinstance(ParamSpec('P'), TypeVar)) - # will lead to breakage in 3.10. - # This also means no accurate __parameters__ for GenericAliases. + if not PEP_560: + # Only needed in 3.6 and lower. + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) # Inherits from list as a workaround for Callable checks in Python < 3.9.2. class _ConcatenateGenericAlias(list): + + # Trick Generic into looking into this for __parameters__. + if PEP_560: + __class__ = _GenericAlias + elif sys.version_info[:3] == (3, 5, 2): + __class__ = typing.TypingMeta + else: + __class__ = typing._TypingBase + + # Flag in 3.8. + _special = False + # Attribute in 3.6 and earlier. + if sys.version_info[:3] == (3, 5, 2): + _gorg = typing.GenericMeta + else: + _gorg = typing.Generic + def __init__(self, origin, args): super().__init__(args) self.__origin__ = origin @@ -2399,6 +2419,20 @@ def __repr__(self): def __hash__(self): return hash((self.__origin__, self.__args__)) + # Hack to get typing._type_check to pass in Generic. + def __call__(self, *args, **kwargs): + pass + + @property + def __parameters__(self): + return tuple(tp for tp in self.__args__ if isinstance(tp, (TypeVar, ParamSpec))) + + if not PEP_560: + # Only required in 3.6 and lower. + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + typing._get_type_vars(self.__parameters__, tvars) + @_tp_cache def _concatenate_getitem(self, parameters): if parameters == (): From 0fc0eda1e6b415811d4f94f1af0c90543db48485 Mon Sep 17 00:00:00 2001 From: Shannon Zhu Date: Sun, 27 Jun 2021 10:57:12 -0700 Subject: [PATCH 015/539] Add docs build options and theme requirements (#818) --- docs/Makefile | 32 ++++++++++++++++++++++++++++--- docs/README.rst | 44 +++++++++++++++++++++++++++++++++++++++++++ docs/conf.py | 2 +- docs/requirements.txt | 10 ++++++++++ 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 docs/README.rst create mode 100644 docs/requirements.txt diff --git a/docs/Makefile b/docs/Makefile index d4bb2cbb9..4f7c95a88 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,16 +3,42 @@ # You can set these variables from the command line, and also # from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build SOURCEDIR = . +SOURCES = BUILDDIR = _build +PYTHON = python3 +VENVDIR = ./venv +SPHINXBUILD = PATH=$(VENVDIR)/bin:$$PATH sphinx-build + +ALLSPHINXOPTS = -b $(BUILDER) -d build/doctrees $(SPHINXOPTS) . build/$(BUILDER) $(SOURCES) + +.PHONY: help clean build html text venv Makefile # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -.PHONY: help Makefile +clean: + -rm -rf build/* $(VENVDIR)/* + +build: + -mkdir -p build + $(SPHINXBUILD) $(ALLSPHINXOPTS) + @echo + +html: BUILDER = html +html: build + @echo "Build finished. The HTML pages are in build/html." + +text: BUILDER = text +text: build + @echo "Build finished; the text files are in build/text." + +venv: + $(PYTHON) -m venv $(VENVDIR) + $(VENVDIR)/bin/python3 -m pip install -U pip setuptools + $(VENVDIR)/bin/python3 -m pip install -r requirements.txt + @echo "The venv has been created in the $(VENVDIR) directory" # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). diff --git a/docs/README.rst b/docs/README.rst new file mode 100644 index 000000000..359fc0314 --- /dev/null +++ b/docs/README.rst @@ -0,0 +1,44 @@ +Python Typing Documentation +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Building the docs +================= + +The documentation is built with tools which are not included in this +tree but are maintained separately and are available from +`PyPI `_. + +* `Sphinx `_ +* `python-docs-theme `_ + +The easiest way to install these tools is to create a virtual environment and +install the tools into there. + +Using make +---------- + +To get started on UNIX, you can create a virtual environment with the command :: + + make venv + +That will install all the tools necessary to build the documentation. Assuming +the virtual environment was created in the ``venv`` directory (the default; +configurable with the VENVDIR variable), you can run the following command to +build the HTML output files:: + + make html + +By default, if the virtual environment is not created, the Makefile will +look for instances of sphinxbuild and blurb installed on your process PATH +(configurable with the SPHINXBUILD and BLURB variables). + +Available make targets are: + +* "clean", which removes all build files. + +* "venv", which creates a virtual environment with all necessary tools + installed. + +* "html", which builds standalone HTML files for offline viewing. + +* "text", which builds a plain text file for each source file. diff --git a/docs/conf.py b/docs/conf.py index 70960ded0..881c068d4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,7 +44,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = 'python_docs_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..35236bd90 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,10 @@ +# Requirements to build the Python documentation + +# Sphinx version is pinned so that new versions that introduce new warnings +# won't suddenly cause build failures. Updating the version is fine as long +# as no warnings are raised by doing so. +sphinx==3.2.1 + +# The theme used by the documentation is stored separately, so we need +# to install that as well. +python-docs-theme From f36dc8dbf19eb931d759f58f939fbb7f6d0ed764 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 18 Aug 2021 15:25:57 +0300 Subject: [PATCH 016/539] Replace deprecated unittest aliases (#836) --- src/test_typing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index bddad7bdb..a987a8dc7 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -1515,8 +1515,8 @@ def foo(a: c1_gth, b: c2_gth): self.assertEqual(List[c1], List[c1_gth]) self.assertNotEqual(List[c1], List[C]) self.assertNotEqual(List[c1_gth], List[C]) - self.assertEquals(Union[c1, c1_gth], Union[c1]) - self.assertEquals(Union[c1, c1_gth, int], Union[c1, int]) + self.assertEqual(Union[c1, c1_gth], Union[c1]) + self.assertEqual(Union[c1, c1_gth, int], Union[c1, int]) def test_forward_equality_hash(self): c1 = typing._ForwardRef('int') From 421e0a4672b75db6a2e91e2f85bf207808b5f138 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 23 Aug 2021 19:23:06 +0200 Subject: [PATCH 017/539] Add docs/venv to .gitignore (#841) This directory is automatically created by "make -C docs venv". --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 98a79f50c..1997aade5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ MANIFEST build/ dist/ +docs/venv/ .tox/ .vscode/ .idea/ From d7f07b71538db86e849c3391c26940adeb912d77 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 23 Aug 2021 19:55:06 +0200 Subject: [PATCH 018/539] Add type stubs document (#844) This was originally intended to become a PEP and was written by: Sebastian Rittau Rebecca Chen Teddy Sudol Jelle Zijlstra Also, remove unnecessary modindex link from main index. --- docs/index.rst | 2 +- docs/stubs.rst | 1069 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1070 insertions(+), 1 deletion(-) create mode 100644 docs/stubs.rst diff --git a/docs/index.rst b/docs/index.rst index f08f4cf90..97ab6bdf6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,11 +10,11 @@ Welcome to the Python Type System documentation! :maxdepth: 2 :caption: Contents: + stubs Indices and tables ================== * :ref:`genindex` -* :ref:`modindex` * :ref:`search` diff --git a/docs/stubs.rst b/docs/stubs.rst new file mode 100644 index 000000000..5e4a3681c --- /dev/null +++ b/docs/stubs.rst @@ -0,0 +1,1069 @@ +.. _stubs: + +********** +Type Stubs +********** + +Abstract +======== + +Optional type hints were introduced to the Python language in PEP 484 +[#pep484]_, based on the function annotation syntax from PEP 3107 +[#pep3107]_. Static type checkers can use type hints to prevent bugs, +documentation tools can automatically add type information, +and IDEs can offer improved autocompletion and support safer refactorings. + +PEP 484 also introduced *type stubs*, also called *stub files*, +that provide type information for untyped Python packages and modules. Type +stubs serve multiple purposes: + +* They are the only way to add type information to extension modules. +* They can provide type information for packages that do not wish to + add them inline. +* They can be distributed separately from the implementation. + This allows stubs to be developed at a different pace or by different + authors, which is especially useful when adding type annotations to + existing packages. +* They can act as documentation, succinctly explaining the external + API of a package, without including the implementation or private + members. + +This PEP aims to give guidance to both authors of type stubs and developers +of type checkers and other tools. It describes the constructs that can be used safely in type stubs, +suggests a style guide for them, and lists constructs that type +checkers are expected to support. + +Type stubs that only use constructs described in this PEP should work with +all type checkers that also follow this PEP. +Type stub authors can elect to use additional constructs, but +must be prepared that some type checkers will not parse them as expected. + +A type checker that conforms to this PEP will parse a type stub that only uses +constructs described here without error and will not interpret any +construct in a contradictory manner. However, type checkers are not +required to implement checks for all these constructs, and +can elect to ignore unsupported ones. Additionally type checkers +can support constructs not described in this PEP and tool authors are +encouraged to experiment with additional features. + +This PEP is intended as a living document and will be updated as new +features are supported or best practices evolve. + +Syntax +====== + +Type stubs are syntactically valid Python 3.7 files with a ``.pyi`` suffix. +The Python syntax used for type stubs is independent from the Python +versions supported by the implementation, and from the Python version the type +checker runs under (if any). Therefore, type stub authors should use the +latest available syntax features in stubs (up to Python 3.7), even if the +implementation supports older, pre-3.7 Python versions. +Type checker authors are encouraged to support syntax features from +post-3.7 Python versions, although type stub authors should not use such +features if they wish to maintain compatibility with all type checkers. + +For example, Python 3.7 added the ``async`` keyword (see PEP 492 [#pep492]_). +Stub authors should use it to mark coroutines, even if the implementation +still uses the ``@coroutine`` decorator. On the other hand, type stubs should +not use the positional-only syntax from PEP 570 [#pep570]_, introduced in +Python 3.8, although type checker authors are encouraged to support it. + +Stubs are treated as if ``from __future__ import annotations`` is enabled. +In particular, built-in generics and forward references can be used. + +Starting with Python 3.8, the ast_ module from the standard library supports +all syntax features required by this PEP. Older Python versions can use the +typed_ast_ package, which also supports Python 3.7 syntax and ``# type`` +comments. + +Distribution +============ + +Type stubs can be distributed with or separately from the implementation; +see PEP 561 [#pep561]_ for more information. The typeshed_ project +includes stubs for Python's standard library and several third-party +packages. These are usually distributed with type checkers and do not +require separate installation. + +Supported Constructs +==================== + +This sections lists constructs that type checkers will accept in type stubs. +Type stub authors can safely use these constructs. If a +construct is marked as "unspecified", type checkers may handle it +as they best see fit or report an error. Linters should usually +flag those constructs. Type stub authors should avoid using them to +ensure compatibility across type checkers. + +Unless otherwise mentioned, type stubs support all features from the +``typing`` module of the latest released Python version. If a stub uses +typing features from a later Python version than what the implementation +supports, these features can be imported from ``typing_extensions`` instead +of ``typing``. + +For example, a stub could use ``Literal``, introduced in Python 3.8, +for a library supporting Python 3.7+:: + + from typing_extensions import Literal + + def foo(x: Literal[""]) -> int: ... + +Comments +-------- + +Standard Python comments are accepted everywhere Python syntax allows them. + +Two kinds of structured comments are accepted: + +* A ``# type: X`` comment at the end of a line that defines a variable, + declaring that the variable has type ``X``. However, PEP 526-style [#pep526]_ + variable annotations are preferred over type comments. +* A ``# type: ignore`` comment at the end of any line, which suppresses all type + errors in that line. + +Imports +------- + +Type stubs distinguish between imports that are re-exported and those +that are only used internally. Imports are re-exported if they use one of these +forms:[#pep484]_ + +* ``import X as X`` +* ``from Y import X as X`` +* ``from Y import *`` + +Here are some examples of imports that make names available for internal use in +a stub but do not re-export them:: + + import X + from Y import X + from Y import X as OtherX + +Type aliases can be used to re-export an import under a different name:: + + from foo import bar as _bar + new_bar = _bar # "bar" gets re-exported with the name "new_bar" + +Sub-modules are always exported when they are imported in a module. +For example, consider the following file structure:: + + foo/ + __init__.pyi + bar.pyi + +Then ``foo`` will export ``bar`` when one of the following constructs is used in +``__init__.pyi``:: + + from . import bar + from .bar import Bar + +Stubs support customizing star import semantics by defining a module-level +variable called ``__all__``. In stubs, this must be a string list literal. +Other types are not supported. Neither is the dynamic creation of this +variable (for example by concatenation). + +By default, ``from foo import *`` imports all names in ``foo`` that +do not begin with an underscore. When ``__all__`` is defined, only those names +specified in ``__all__`` are imported:: + + __all__ = ['public_attr', '_private_looking_public_attr'] + + public_attr: int + _private_looking_public_attr: int + private_attr: int + +Type checkers can handle cyclic imports in stub files. + +Module Level Attributes +----------------------- + +Module level variables and constants can be annotated using either +type comments or variable annotation syntax:: + + x: int # recommended + x: int = 0 + x = 0 # type: int + x = ... # type: int + +The type of a variable is unspecified when the variable is unannotated or +when the annotation +and the assigned value disagree. As an exception, the ellipsis literal can +stand in for any type:: + + x = 0 # type is unspecified + x = ... # type is unspecified + x: int = "" # type is unspecified + x: int = ... # type is int + +Classes +------- + +Class definition syntax follows general Python syntax, but type checkers +are only expected to understand the following constructs in class bodies: + +* The ellipsis literal ``...`` is ignored and used for empty + class bodies. Using ``pass`` in class bodies is undefined. +* Instance attributes follow the same rules as module level attributes + (see above). +* Method definitions (see below) and properties. +* Method aliases. +* Inner class definitions. + +More complex statements don't need to be supported:: + + class Simple: ... + + class Complex(Base): + read_write: int + @property + def read_only(self) -> int: ... + def do_stuff(self, y: str) -> None: ... + doStuff = do_stuff + +The type of generic classes can be narrowed by annotating the ``self`` +argument of the ``__init__`` method:: + + class Foo(Generic[_T]): + @overload + def __init__(self: Foo[str], type: Literal["s"]) -> None: ... + @overload + def __init__(self: Foo[int], type: Literal["i"]) -> None: ... + @overload + def __init__(self, type: str) -> None: ... + +The class must match the class in which it is declared. Using other classes, +including sub or super classes, will not work. In addition, the ``self`` +annotation cannot contain type variables. + +Functions and Methods +--------------------- + +Function and method definition syntax follows general Python syntax. +Unless an argument name is prefixed with two underscores (but not suffixed +with two underscores), it can be used as a keyword argument [#pep484]_:: + + # x is positional-only + # y can be used positionally or as keyword argument + # z is keyword-only + def foo(__x, y, *, z): ... + +PEP 570 [#pep570]_ style positional-only parameters are currently not +supported. + +If an argument or return type is unannotated, per PEP 484 [#pep484]_ its +type is assumed to be ``Any``. It is preferred to leave unknown +types unannotated rather than explicitly marking them as ``Any``, as some +type checkers can optionally warn about unannotated arguments. + +If an argument has a literal or constant default value, it must match the implementation +and the type of the argument (if specified) must match the default value. +Alternatively, ``...`` can be used in place of any default value:: + + # The following arguments all have type Any. + def unannotated(a, b=42, c=...): ... + # The following arguments all have type int. + def annotated(a: int, b: int = 42, c: int = ...): ... + # The following default values are invalid and the types are unspecified. + def invalid(a: int = "", b: Foo = Foo()): ... + +For a class ``C``, the type of the first argument to a classmethod is +assumed to be ``Type[C]``, if unannotated. For other non-static methods, +its type is assumed to be ``C``:: + + class Foo: + def do_things(self): ... # self has type Foo + @classmethod + def create_it(cls): ... # cls has type Type[Foo] + @staticmethod + def utility(x): ... # x has type Any + +But:: + + _T = TypeVar("_T") + + class Foo: + def do_things(self: _T): ... # self has type _T + @classmethod + def create_it(cls: _T): ... # cls has type _T + +Using a function or method body other than the ellipsis literal is currently +unspecified. Stub authors may experiment with other bodies, but it is up to +individual type checkers how to interpret them. + + def foo(): ... # compatible + def bar(): pass # behavior undefined + +All variants of overloaded functions and methods must have an ``@overload`` +decorator:: + + @overload + def foo(x: str) -> str: ... + @overload + def foo(x: float) -> int: ... + +The following (which would be used in the implementation) is wrong in +type stubs:: + + @overload + def foo(x: str) -> str: ... + @overload + def foo(x: float) -> int: ... + def foo(x: Union[str, float]) -> Any: ... + +Aliases and NewType +------------------- + +Type checkers should accept module-level and class-level aliases, e.g.:: + + _IntList = list[int] + + class C: + def f(self) -> int: ... + g = f + +An alias to a type may contain type variables. As per PEP 484 [#pep484]_, +all type variables must be substituted when the alias is used:: + + _K = TypeVar("_K") + _V = TypeVar("_V") + _MyMap = Dict[str, Dict[_K, _V]] + + # either concrete types or other type variables can be substituted + def f(x: _MyMap[str, _V]) -> _V: ... + # explicitly substitute in Any rather than using a bare alias + def g(x: _MyMap[Any, Any]) -> Any: ... + +Otherwise, type variables in aliases follow the same rules as type variables in +generic class definitions. + +``typing.NewType`` is also supported in stubs. + +Decorators +---------- + +Type stubs may only use decorators defined in the ``typing`` module, plus a +fixed set of additional ones: + +* ``classmethod`` +* ``staticmethod`` +* ``property`` (including ``.setter``) +* ``abc.abstractmethod`` +* ``dataclasses.dataclass`` +* ``asyncio.coroutine`` (although ``async`` should be used instead) + +The behavior of other decorators should instead be incorporated into the types. +For example, for the following function:: + + import contextlib + @contextlib.contextmanager + def f(): + yield 42 + +the stub definition should be:: + + from contextlib import AbstractContextManager + def f() -> AbstractContextManager[int]: ... + +Version and Platform Checks +--------------------------- + +Type stubs for libraries that support multiple Python versions can use version +checks to supply version-specific type hints. Type stubs for different Python +versions should still conform to the most recent supported Python version's +syntax, as explain in the Syntax_ section above. + +Version checks are if-statements that use ``sys.version_info`` to determine the +current Python version. Version checks should only check against the ``major`` and +``minor`` parts of ``sys.version_info``. Type checkers are only required to +support the tuple-based version check syntax:: + + if sys.version_info >= (3,): + # Python 3-specific type hints. This tuple-based syntax is recommended. + else: + # Python 2-specific type hints. + + if sys.version_info >= (3, 5): + # Specific minor version features can be easily checked with tuples. + + if sys.version_info < (3,): + # This is only necessary when a feature has no Python 3 equivalent. + +Type stubs should avoid checking against ``sys.version_info.major`` +directly and should not use comparison operators other than ``<`` and ``>=``. + +No:: + + if sys.version_info.major >= 3: + # Semantically the same as the first tuple check. + + if sys.version_info[0] >= 3: + # This is also the same. + + if sys.version_info <= (2, 7): + # This does not work because e.g. (2, 7, 1) > (2, 7). + +Some type stubs also may need to specify type hints for different platforms. +Platform checks must be equality comparisons between ``sys.platform`` and the name +of a platform as a string literal: + +Yes:: + + if sys.platform == 'win32': + # Windows-specific type hints. + else: + # Posix-specific type hints. + +No:: + + if sys.platform.startswith('linux'): + # Not necessary since Python 3.3. + + if sys.platform in ['linux', 'cygwin', 'darwin']: + # Only '==' or '!=' should be used in platform checks. + +Version and platform comparisons can be chained using the ``and`` and ``or`` +operators:: + + if sys.platform == 'linux' and (sys.version_info < (3,) or sys,version_info >= (3, 7)): ... + +Enums +----- + +Enum classes are supported in stubs, regardless of the Python version targeted by +the stubs. + +Enum members may be specified just like other forms of assignments, for example as +``x: int``, ``x = 0``, or ``x = ...``. The first syntax is preferred because it +allows type checkers to correctly type the ``.value`` attribute of enum members, +without providing unnecessary information like the runtime value of the enum member. + +Additional properties on enum members should be specified with ``@property``, so they +do not get interpreted by type checkers as enum members. + +Yes:: + + from enum import Enum + + class Color(Enum): + RED: int + BLUE: int + @property + def rgb_value(self) -> int: ... + + class Color(Enum): + # discouraged; type checkers will not understand that Color.RED.value is an int + RED = ... + BLUE = ... + @property + def rgb_value(self) -> int: ... + +No:: + + from enum import Enum + + class Color(Enum): + RED: int + BLUE: int + rgb_value: int # no way for type checkers to know that this is not an enum member + +Unsupported Features +-------------------- + +Currently, positional-only argument syntax (PEP 570 [#pep570]_), +unions using the pipe operator (``|``) (PEP 604 [#pep604]_), +``ParamSpec`` (PEP 612 [#pep612]_), and ``TypeAlias`` (PEP 613 [#pep613]_) +are not supported by all type +checkers and should not be used in stubs. + +Type Stub Content +================= + +This section documents best practices on what elements to include or +leave out of type stubs. + +Public Interface +---------------- + +Stubs should include the complete public interface (classes, functions, +constants, etc.) of the module they cover, but it is not always +clear exactly what is part of the interface. + +The following should always be included: + +* All objects listed in the module's documentation. +* All objects included in ``__all__`` (if present). + +Other objects may be included if they are not prefixed with an underscore +or if they are being used in practice. (See the next section.) + +Undocumented Objects +-------------------- + +Undocumented objects may be included as long as they are marked with a comment +of the form ``# undocumented``. + +Example:: + + def list2cmdline(seq: Sequence[str]) -> str: ... # undocumented + +Such undocumented objects are allowed because omitting objects can confuse +users. Users who see an error like "module X has no attribute Y" will +not know whether the error appeared because their code had a bug or +because the stub is wrong. Although it may also be helpful for a type +checker to point out usage of private objects, we usually prefer false +negatives (no errors for wrong code) over false positives (type errors +for correct code). In addition, even for private objects a type checker +can be helpful in pointing out that an incorrect type was used. + +``__all__`` +------------ + +A type stub should contain an ``__all__`` variable if and only if it also +present at runtime. In that case, the contents of ``__all__`` should be +identical in the stub and at runtime. If the runtime dynamically adds +or removes elements (for example if certain functions are only available on +some platforms), include all possible elements in the stubs. + +Stub-Only Objects +----------------- + +Definitions that do not exist at runtime may be included in stubs to aid in +expressing types. Sometimes, it is desirable to make a stub-only class available +to a stub's users - for example, to allow them to type the return value of a +public method for which a library does not provided a usable runtime type:: + + from typing import Protocol + + class Readable(Protocol): + def read(self) -> str: ... + + def get_reader() -> Readable: ... + +Structural Types +---------------- + +As seen in the example with ``Readable`` in the previous section, a common use +of stub-only objects is to model types that are best described by their +structure. These objects are called protocols [#pep544]_, and it is encouraged +to use them freely to describe simple structural types. + +Incomplete Stubs +---------------- + +Partial stubs can be useful, especially for larger packages, but they should +follow the following guidelines: + +* Included functions and methods should list all arguments, but the arguments + can be left unannotated. +* Do not use ``Any`` to mark unannotated arguments or return values. +* Partial classes should include a ``__getattr__()`` method marked with an + ``# incomplete`` comment (see example below). +* Partial modules (i.e. modules that are missing some or all classes, + functions, or attributes) should include a top-level ``__getattr__()`` + function marked with an ``# incomplete`` comment (see example below). +* Partial packages (i.e. packages that are missing one or more sub-modules) + should have a ``__init__.pyi`` stub that is marked as incomplete (see above). + A better alternative is to create empty stubs for all sub-modules and + mark them as incomplete individually. + +Example of a partial module with a partial class ``Foo`` and a partially +annotated function ``bar()``:: + + def __getattr__(name: str) -> Any: ... # incomplete + + class Foo: + def __getattr__(self, name: str) -> Any: # incomplete + x: int + y: str + + def bar(x: str, y, *, z=...): ... + +Attribute Access +---------------- + +Python has several methods for customizing attribute access: ``__getattr__``, +``__getattribute__``, ``__setattr__``, and ``__delattr__``. Of these, +``__getattr__`` and ``__setattr___`` should sometimes be included in stubs. + +In addition to marking incomplete definitions, ``__getattr__`` should be +included when a class or module allows any name to be accessed. For example, consider +the following class:: + + class Foo: + def __getattribute__(self, name): + return self.__dict__.setdefault(name) + +An appropriate stub definition is:: + + from typing import Any, Optional + class Foo: + def __getattr__(self, name: str) -> Optional[Any]: ... + +Note that only ``__getattr__``, not ``__getattribute__``, is guaranteed to be +supported in stubs. + +On the other hand, ``__getattr__`` should be omitted even if the source code +includes it, if only limited names are allowed. For example, consider this class:: + + class ComplexNumber: + def __init__(self, n): + self._n = n + def __getattr__(self, name): + if name in ("real", "imag"): + return getattr(self._n, name) + raise AttributeError(name) + +In this case, the stub should list the attributes individually:: + + class ComplexNumber: + @property + def real(self) -> float: ... + @property + def imag(self) -> float: ... + def __init__(self, n: complex) -> None: ... + +``__setattr___`` should be included when a class allows any name to be set and +restricts the type. For example:: + + class IntHolder: + def __setattr__(self, name, value): + if isinstance(value, int): + return super().__setattr__(name, value) + raise ValueError(value) + +A good stub definition would be:: + + class IntHolder: + def __setattr__(self, name: str, value: int) -> None: ... + +``__delattr__`` should not be included in stubs. + +Finally, even in the presence of ``__getattr__`` and ``__setattr__``, it is +still recommended to separately define known attributes. + +Constants +--------- + +When the value of a constant is important, annotate it using ``Literal`` +instead of its type. + +Yes:: + + TEL_LANDLINE: Literal["landline"] + TEL_MOBILE: Literal["mobile"] + DAY_FLAG: Literal[0x01] + NIGHT_FLAG: Literal[0x02] + +No:: + + TEL_LANDLINE: str + TEL_MOBILE: str + DAY_FLAG: int + NIGHT_FLAG: int + +Documentation or Implementation +------------------------------- + +Sometimes a library's documented types will differ from the actual types in the +code. In such cases, type stub authors should use their best judgment. Consider +these two examples:: + + def print_elements(x): + """Print every element of list x.""" + for y in x: + print(y) + + def maybe_raise(x): + """Raise an error if x (a boolean) is true.""" + if x: + raise ValueError() + +The implementation of ``print_elements`` takes any iterable, despite the +documented type of ``list``. In this case, annotate the argument as +``Iterable[Any]``, to follow this PEP's style recommendation of preferring +abstract types. + +For ``maybe_raise``, on the other hand, it is better to annotate the argument as +``bool`` even though the implementation accepts any object. This guards against +common mistakes like unintentionally passing in ``None``. + +If in doubt, consider asking the library maintainers about their intent. + +Style Guide +=========== + +The recommendations in this section are aimed at type stub authors +who wish to provide a consistent style for type stubs. Type checkers +should not reject stubs that do not follow these recommendations, but +linters can warn about them. + +Stub files should generally follow the Style Guide for Python Code (PEP 8) +[#pep8]_. There are a few exceptions, outlined below, that take the +different structure of stub files into account and are aimed to create +more concise files. + +Maximum Line Length +------------------- + +Type stubs should be limited to 130 characters per line. + +Blank Lines +----------- + +Do not use empty lines between functions, methods, and fields, except to +group them with one empty line. Use one empty line around classes, but do not +use empty lines between body-less classes, except for grouping. + +Yes:: + + def time_func() -> None: ... + def date_func() -> None: ... + + def ip_func() -> None: ... + + class Foo: + x: int + y: int + def __init__(self) -> None: ... + + class MyError(Exception): ... + class AnotherError(Exception): ... + +No:: + + def time_func() -> None: ... + + def date_func() -> None: ... # do no leave unnecessary empty lines + + def ip_func() -> None: ... + + + class Foo: # leave only one empty line above + x: int + class MyError(Exception): ... # leave an empty line between the classes + +Module Level Attributes +----------------------- + +Do not use an assignment for module-level attributes. + +Yes:: + + CONST: Literal["const"] + x: int + +No:: + + CONST = "const" + x: int = 0 + y: float = ... + z = 0 # type: int + a = ... # type: int + +Classes +------- + +Classes without bodies should use the ellipsis literal ``...`` in place +of the body on the same line as the class definition. + +Yes:: + + class MyError(Exception): ... + +No:: + + class MyError(Exception): + ... + class AnotherError(Exception): pass + +Instance attributes and class variables follow the same recommendations as +module level attributes: + +Yes:: + + class Foo: + c: ClassVar[str] + x: int + +No:: + + class Foo: + c: ClassVar[str] = "" + d: ClassVar[int] = ... + x = 4 + y: int = ... + +Functions and Methods +--------------------- + +Use the same argument names as in the implementation, because +otherwise using keyword arguments will fail. Of course, this +does not apply to positional-only arguments, which are marked with a double +underscore. + +Use the ellipsis literal ``...`` in place of actual default argument +values. Use an explicit ``Optional`` annotation instead of +a ``None`` default. + +Yes:: + + def foo(x: int = ...) -> None: ... + def bar(y: Optional[str] = ...) -> None: ... + +No:: + + def foo(x: int = 0) -> None: ... + def bar(y: str = None) -> None: ... + def baz(z: Optional[str] = None) -> None: ... + +Do not annotate ``self`` and ``cls`` in method definitions, except when +referencing a type variable. + +Yes:: + + _T = TypeVar("_T") + class Foo: + def bar(self) -> None: ... + @classmethod + def create(cls: type[_T]) -> _T: ... + +No:: + + class Foo: + def bar(self: Foo) -> None: ... + @classmethod + def baz(cls: type[Foo]) -> int: ... + +The bodies of functions and methods should consist of only the ellipsis +literal ``...`` on the same line as the closing parenthesis and colon. + +Yes:: + + def to_int1(x: str) -> int: ... + def to_int2( + x: str, + ) -> int: ... + +No:: + + def to_int1(x: str) -> int: + return int(x) + def to_int2(x: str) -> int: + ... + def to_int3(x: str) -> int: pass + +Private Definitions +------------------- + +Type variables, type aliases, and other definitions that should not +be used outside the stub should be marked as private by prefixing them +with an underscore. + +Yes:: + + _T = TypeVar("_T") + _DictList = dict[str, list[Optional[int]]] + +No:: + + T = TypeVar("T") + DictList = dict[str, list[Optional[int]]] + +Language Features +----------------- + +Use the latest language features available as outlined +in the Syntax_ section, even for stubs targeting older Python versions. +Do not use quotes around forward references and do not use ``__future__`` +imports. + +Yes:: + + class Py35Class: + x: int + forward_reference: OtherClass + class OtherClass: ... + +No:: + + class Py35Class: + x = 0 # type: int + forward_reference: 'OtherClass' + class OtherClass: ... + +Types +----- + +Generally, use ``Any`` when a type cannot be expressed appropriately +with the current type system or using the correct type is unergonomic. + +Use ``float`` instead of ``Union[int, float]``. +Use ``None`` instead of ``Literal[None]``. +For argument types, +use ``bytes`` instead of ``Union[bytes, memoryview, bytearray]``. + +Use ``Text`` in stubs that support Python 2 when something accepts both +``str`` and ``unicode``. Avoid using ``Text`` in stubs or branches for +Python 3 only. + +Yes:: + + if sys.version_info < (3,): + def foo(s: Text) -> None: ... + else: + def foo(s: str, *, i: int) -> None: ... + def bar(s: Text) -> None: ... + +No:: + + if sys.version_info < (3,): + def foo(s: unicode) -> None: ... + else: + def foo(s: Text, *, i: int) -> None: ... + +For arguments, prefer protocols and abstract types (``Mapping``, +``Sequence``, ``Iterable``, etc.). If an argument accepts literally any value, +use ``object`` instead of ``Any``. + +For return values, prefer concrete types (``list``, ``dict``, etc.) for +concrete implementations. The return values of protocols +and abstract base classes must be judged on a case-by-case basis. + +Yes:: + + def map_it(input: Iterable[str]) -> list[int]: ... + def create_map() -> dict[str, int]: ... + def to_string(o: object) -> str: ... # accepts any object + +No:: + + def map_it(input: list[str]) -> list[int]: ... + def create_map() -> MutableMapping[str, int]: ... + def to_string(o: Any) -> str: ... + +Maybe:: + + class MyProto(Protocol): + def foo(self) -> list[int]: ... + def bar(self) -> Mapping[str]: ... + +Avoid ``Union`` return types, since they require ``isinstance()`` checks. +Use ``Any`` if necessary. + +Use built-in generics instead of the aliases from ``typing``. + +Yes:: + + from collections.abc import Iterable + + def foo(x: type[MyClass]) -> list[str]: ... + def bar(x: Iterable[str]) -> None: ... + +No:: + + from typing import Iterable, List, Type + + def foo(x: Type[MyClass]) -> List[str]: ... + def bar(x: Iterable[str]) -> None: ... + +NamedTuple and TypedDict +------------------------ + +Use the class-based syntax for ``typing.NamedTuple`` and +``typing.TypedDict``, following the Classes section of this style guide. + +Yes:: + + from typing import NamedTuple, TypedDict + class Point(NamedTuple): + x: float + y: float + + class Thing(TypedDict): + stuff: str + index: int + +No:: + + from typing import NamedTuple, TypedDict + Point = NamedTuple("Point", [('x', float), ('y', float)]) + Thing = TypedDict("Thing", {'stuff': str, 'index': int}) + +Existing Tools +============== + +Type Checkers +------------- + +* mypy [#mypy]_, the reference implementation for type checkers. + Supports Python 2 and 3. +* pyre [#pyre]_, written in OCaml and optimized for performance. + Supports Python 3 only. +* pyright [#pyright]_, a type checker that emphasizes speed. Supports Python 3 + only. +* pytype [#pytype]_, checks and infers types for unannotated code. + Supports Python 2 and 3. + +Development Environments +------------------------ + +* PyCharm [#pycharm]_, an IDE that supports type stubs both for type + checking and code completion. +* Visual Studio Code [#vscode]_, a code editor that supports type + checking using mypy, pyright, or the Pylance [#pylance]_ extension. + +Linters and Formatters +---------------------- + +* black [#black]_, a code formatter with support for type stub files. +* flake8-pyi [#flake8-pyi]_, a plugin for the flake8 linter [#flake8]_ that adds support for + type stubs. + +References +========== + +PEPs +---- + +.. [#pep8] PEP 8 -- Style Guide for Python Code, van Rossum et al. (https://www.python.org/dev/peps/pep-0008/) +.. [#pep484] PEP 484 -- Type Hints, van Rossum et al. (https://www.python.org/dev/peps/pep-0484) +.. [#pep492] PEP 492 -- Coroutines with async and await syntax, Selivanov (https://www.python.org/dev/peps/pep-0492/) +.. [#pep526] PEP 526 -- Syntax for Variable Annotations, Gonzalez et al. (https://www.python.org/dev/peps/pep-0526) +.. [#pep544] PEP 544 -- Protocols: Structural Subtyping, Levkivskyi et al. (https://www.python.org/dev/peps/pep-0544) +.. [#pep561] PEP 561 -- Distributing and Packaging Type Information, Smith (https://www.python.org/dev/peps/pep-0561) +.. [#pep570] PEP 570 -- Python Positional-Only Parameters, Hastings et al. (https://www.python.org/dev/peps/pep-0570) +.. [#pep585] PEP 585 -- Type Hinting Generics In Standard Collections, Langa (https://www.python.org/dev/peps/pep-0585) +.. [#pep604] PEP 604 -- Allow writing union types as X | Y, Prados and Moss (https://www.python.org/dev/peps/pep-0604) +.. [#pep612] PEP 612 -- Parameter Specification Variables, Mendoza (https://www.python.org/dev/peps/pep-0612) +.. [#pep613] PEP 613 -- Explicit Type Aliases, Zhu (https://www.python.org/dev/peps/pep-0613) +.. [#pep3107] PEP 3107 -- Function Annotations, Winter and Lownds (https://www.python.org/dev/peps/pep-3107) + +Type Checkers +------------- + +.. [#mypy] mypy -- Optional Static Typing for Python (http://www.mypy-lang.org/) +.. [#pyre] Pyre -- A performant type-checker for Python 3 (https://pyre-check.org/) +.. [#pyright] pyright -- Static type checker for Python (https://github.com/microsoft/pyright) +.. [#pytype] pytype -- A static analyzer for Python code (https://github.com/google/pytype) + +IDEs with Typing Support +------------------------ + +.. [#pycharm] PyCharm -- The Python IDE for Professional Developers (https://www.jetbrains.com/pycharm/) +.. [#pylance] Pylance -- Fast, feature-rich language support for Python (https://github.com/microsoft/pylance-release) +.. [#vscode] Visual Studio Code -- Code Editing. Redefined (https://code.visualstudio.com/) + +Other Resources +--------------- + +.. [#black] black -- The uncompromising code formatter (https://black.readthedocs.io/) +.. [#flake8] Flake8 -- Your Tool For Style Guide Enforcement (http://flake8.pycqa.org/) +.. [#flake8-pyi] flake8-pyi -- A plugin for Flake8 that provides specializations for type hinting stub files (https://github.com/ambv/flake8-pyi) +.. [#typeshed] typeshed -- Collection of library stubs for Python, with static types (https://github.com/python/typeshed) +.. [#ast] ast -- Abstract Syntax Trees, Python standard library module (https://docs.python.org/3/library/ast.html) +.. [#typed_ast] typed_ast -- Fork of CPython's ast module (https://pypi.org/project/typed-ast/) + +Copyright +========= + +This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive. From 82b3940a59d173b210b4d525d0921b25f43669b6 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 23 Aug 2021 19:56:11 +0200 Subject: [PATCH 019/539] Ignore venv and README when building docs (#843) * Ignore venv and README when building docs This suppresses warnings during the docs build if the venv directory exists. * Remove html static path from Sphinx config Fixes another Sphinx warning --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 881c068d4..f870c7be4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,7 +36,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'venv', 'README.rst'] # -- Options for HTML output ------------------------------------------------- @@ -49,4 +49,4 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = [] From 9e1e4478010f02a57ebe8e2ad6ccf227679dd2ad Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 23 Aug 2021 23:20:19 +0200 Subject: [PATCH 020/539] Move the typing tools section to the index (#847) * Enable the intersphinx extension for linking to Python documentation. * Use inline links instead of footnotes for links to projects and modules. * Mention that typed_ast is available on PyPI. --- docs/conf.py | 3 +++ docs/index.rst | 34 +++++++++++++++++++++++++++ docs/stubs.rst | 63 ++++---------------------------------------------- 3 files changed, 42 insertions(+), 58 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index f870c7be4..f16401bae 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,3 +50,6 @@ # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] + +extensions = ['sphinx.ext.intersphinx'] +intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} diff --git a/docs/index.rst b/docs/index.rst index 97ab6bdf6..7a7649100 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,3 +18,37 @@ Indices and tables * :ref:`genindex` * :ref:`search` + +Typing-related Tools +==================== + +Type Checkers +------------- + +* `mypy `_, the reference implementation for type + checkers. Supports Python 2 and 3. +* `pyre `_, written in OCaml and optimized for + performance. Supports Python 3 only. +* `pyright `_, a type checker that + emphasizes speed. Supports Python 3 only. +* `pytype `_, checks and infers types for + unannotated code. Supports Python 2 and 3. + +Development Environments +------------------------ + +* `PyCharm `_, an IDE that supports + type stubs both for type checking and code completion. +* `Visual Studio Code `_, a code editor that + supports type checking using mypy, pyright, or the + `Pylance `_ + extension. + +Linters and Formatters +---------------------- + +* `black `_, a code formatter with support for + type stub files. +* `flake8-pyi `_, a plugin for the + `flake8 `_ linter that adds support for type + stubs. diff --git a/docs/stubs.rst b/docs/stubs.rst index 5e4a3681c..8b8880484 100644 --- a/docs/stubs.rst +++ b/docs/stubs.rst @@ -71,16 +71,18 @@ Python 3.8, although type checker authors are encouraged to support it. Stubs are treated as if ``from __future__ import annotations`` is enabled. In particular, built-in generics and forward references can be used. -Starting with Python 3.8, the ast_ module from the standard library supports +Starting with Python 3.8, the :py:mod:`ast` module from the standard library supports all syntax features required by this PEP. Older Python versions can use the -typed_ast_ package, which also supports Python 3.7 syntax and ``# type`` +`typed_ast `_ package from the +Python Package Index, which also supports Python 3.7 syntax and ``# type`` comments. Distribution ============ Type stubs can be distributed with or separately from the implementation; -see PEP 561 [#pep561]_ for more information. The typeshed_ project +see PEP 561 [#pep561]_ for more information. The +`typeshed `_ project includes stubs for Python's standard library and several third-party packages. These are usually distributed with type checkers and do not require separate installation. @@ -989,36 +991,6 @@ No:: Point = NamedTuple("Point", [('x', float), ('y', float)]) Thing = TypedDict("Thing", {'stuff': str, 'index': int}) -Existing Tools -============== - -Type Checkers -------------- - -* mypy [#mypy]_, the reference implementation for type checkers. - Supports Python 2 and 3. -* pyre [#pyre]_, written in OCaml and optimized for performance. - Supports Python 3 only. -* pyright [#pyright]_, a type checker that emphasizes speed. Supports Python 3 - only. -* pytype [#pytype]_, checks and infers types for unannotated code. - Supports Python 2 and 3. - -Development Environments ------------------------- - -* PyCharm [#pycharm]_, an IDE that supports type stubs both for type - checking and code completion. -* Visual Studio Code [#vscode]_, a code editor that supports type - checking using mypy, pyright, or the Pylance [#pylance]_ extension. - -Linters and Formatters ----------------------- - -* black [#black]_, a code formatter with support for type stub files. -* flake8-pyi [#flake8-pyi]_, a plugin for the flake8 linter [#flake8]_ that adds support for - type stubs. - References ========== @@ -1038,31 +1010,6 @@ PEPs .. [#pep613] PEP 613 -- Explicit Type Aliases, Zhu (https://www.python.org/dev/peps/pep-0613) .. [#pep3107] PEP 3107 -- Function Annotations, Winter and Lownds (https://www.python.org/dev/peps/pep-3107) -Type Checkers -------------- - -.. [#mypy] mypy -- Optional Static Typing for Python (http://www.mypy-lang.org/) -.. [#pyre] Pyre -- A performant type-checker for Python 3 (https://pyre-check.org/) -.. [#pyright] pyright -- Static type checker for Python (https://github.com/microsoft/pyright) -.. [#pytype] pytype -- A static analyzer for Python code (https://github.com/google/pytype) - -IDEs with Typing Support ------------------------- - -.. [#pycharm] PyCharm -- The Python IDE for Professional Developers (https://www.jetbrains.com/pycharm/) -.. [#pylance] Pylance -- Fast, feature-rich language support for Python (https://github.com/microsoft/pylance-release) -.. [#vscode] Visual Studio Code -- Code Editing. Redefined (https://code.visualstudio.com/) - -Other Resources ---------------- - -.. [#black] black -- The uncompromising code formatter (https://black.readthedocs.io/) -.. [#flake8] Flake8 -- Your Tool For Style Guide Enforcement (http://flake8.pycqa.org/) -.. [#flake8-pyi] flake8-pyi -- A plugin for Flake8 that provides specializations for type hinting stub files (https://github.com/ambv/flake8-pyi) -.. [#typeshed] typeshed -- Collection of library stubs for Python, with static types (https://github.com/python/typeshed) -.. [#ast] ast -- Abstract Syntax Trees, Python standard library module (https://docs.python.org/3/library/ast.html) -.. [#typed_ast] typed_ast -- Fork of CPython's ast module (https://pypi.org/project/typed-ast/) - Copyright ========= From b4ea885106d49b29d666975e0fa0957e5bcd9317 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 23 Aug 2021 23:29:44 +0200 Subject: [PATCH 021/539] Build the docs when a PR is created (#848) This only triggers on pr, not on pushes, since there will be a separate workflow for pushed that builds and uploads the documentation. --- .github/workflows/build-docs.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/build-docs.yml diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 000000000..806f8331d --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,26 @@ +name: Build the documentation + +on: + pull_request: + +permissions: + contents: read + +jobs: + build: + + name: Build documentation + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r docs/requirements.txt + - name: Build the documentation + run: make -C docs html From f5bc93aea3a6e13bcd860e2998d1c08299819a94 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 24 Aug 2021 05:47:32 +0200 Subject: [PATCH 022/539] Link to the typing module docs (#849) --- docs/index.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 7a7649100..670c78244 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,8 +1,3 @@ -.. typing documentation master file, created by - sphinx-quickstart on Mon May 24 16:43:52 2021. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - Welcome to the Python Type System documentation! ================================================ @@ -10,6 +5,7 @@ Welcome to the Python Type System documentation! :maxdepth: 2 :caption: Contents: + typing Module Documentation stubs @@ -19,6 +15,7 @@ Indices and tables * :ref:`genindex` * :ref:`search` + Typing-related Tools ==================== From ce2ea207b0e5df921a820594e231858654c41aa3 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 24 Aug 2021 08:08:17 +0200 Subject: [PATCH 023/539] Various improvements to the Type Stubs document (#846) * Rename "Abstract" to "Introduction" and remove fluff originally intended for the PEP. * "This PEP" -> "This document" * Remove note about the living document status. This is a given, not that it isn't a PEP anymore. * Third-party stubs are now distributed via PyPI. * Mention "# type: ignore[xxx]". * Fix indentation. * Reformulate sentence about cyclic imports. * Use type vars correctly in example. * Use new union syntax. * Remove "we" from sentence and slightly reformulate. * Clarify that "# incomplete" is mainly intended for stub authors. * Mention "X | Any" return type. Still needs to be explained. --- docs/stubs.rst | 65 ++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/docs/stubs.rst b/docs/stubs.rst index 8b8880484..f06efe963 100644 --- a/docs/stubs.rst +++ b/docs/stubs.rst @@ -4,18 +4,11 @@ Type Stubs ********** -Abstract -======== - -Optional type hints were introduced to the Python language in PEP 484 -[#pep484]_, based on the function annotation syntax from PEP 3107 -[#pep3107]_. Static type checkers can use type hints to prevent bugs, -documentation tools can automatically add type information, -and IDEs can offer improved autocompletion and support safer refactorings. +Introduction +============ -PEP 484 also introduced *type stubs*, also called *stub files*, -that provide type information for untyped Python packages and modules. Type -stubs serve multiple purposes: +*type stubs*, also called *stub files*, provide type information for untyped +Python packages and modules. Type stubs serve multiple purposes: * They are the only way to add type information to extension modules. * They can provide type information for packages that do not wish to @@ -28,27 +21,24 @@ stubs serve multiple purposes: API of a package, without including the implementation or private members. -This PEP aims to give guidance to both authors of type stubs and developers +This document aims to give guidance to both authors of type stubs and developers of type checkers and other tools. It describes the constructs that can be used safely in type stubs, suggests a style guide for them, and lists constructs that type checkers are expected to support. -Type stubs that only use constructs described in this PEP should work with -all type checkers that also follow this PEP. +Type stubs that only use constructs described in this document should work with +all type checkers that also follow this document. Type stub authors can elect to use additional constructs, but must be prepared that some type checkers will not parse them as expected. -A type checker that conforms to this PEP will parse a type stub that only uses +A type checker that conforms to this document will parse a type stub that only uses constructs described here without error and will not interpret any construct in a contradictory manner. However, type checkers are not required to implement checks for all these constructs, and can elect to ignore unsupported ones. Additionally type checkers -can support constructs not described in this PEP and tool authors are +can support constructs not described in this document and tool authors are encouraged to experiment with additional features. -This PEP is intended as a living document and will be updated as new -features are supported or best practices evolve. - Syntax ====== @@ -84,8 +74,10 @@ Type stubs can be distributed with or separately from the implementation; see PEP 561 [#pep561]_ for more information. The `typeshed `_ project includes stubs for Python's standard library and several third-party -packages. These are usually distributed with type checkers and do not -require separate installation. +packages. The stubs for the standard library are usually distributed with type checkers and do not +require separate installation. Stubs for third-party libraries are +available on the `Python Package Index `_. A stub package for +a library called ``widget`` will be called ``types-widget``. Supported Constructs ==================== @@ -121,7 +113,9 @@ Two kinds of structured comments are accepted: declaring that the variable has type ``X``. However, PEP 526-style [#pep526]_ variable annotations are preferred over type comments. * A ``# type: ignore`` comment at the end of any line, which suppresses all type - errors in that line. + errors in that line. The type checker mypy supports suppressing certain + type errors by using ``# type: ignore[error-type]``. This is not supported + by other type checkers and should not be used in stubs. Imports ------- @@ -168,13 +162,13 @@ By default, ``from foo import *`` imports all names in ``foo`` that do not begin with an underscore. When ``__all__`` is defined, only those names specified in ``__all__`` are imported:: - __all__ = ['public_attr', '_private_looking_public_attr'] + __all__ = ['public_attr', '_private_looking_public_attr'] public_attr: int _private_looking_public_attr: int private_attr: int -Type checkers can handle cyclic imports in stub files. +Type checkers support cyclic imports in stub files. Module Level Attributes ----------------------- @@ -269,7 +263,7 @@ Alternatively, ``...`` can be used in place of any default value:: def invalid(a: int = "", b: Foo = Foo()): ... For a class ``C``, the type of the first argument to a classmethod is -assumed to be ``Type[C]``, if unannotated. For other non-static methods, +assumed to be ``type[C]``, if unannotated. For other non-static methods, its type is assumed to be ``C``:: class Foo: @@ -284,9 +278,9 @@ But:: _T = TypeVar("_T") class Foo: - def do_things(self: _T): ... # self has type _T + def do_things(self: _T) -> _T: ... # self has type _T @classmethod - def create_it(cls: _T): ... # cls has type _T + def create_it(cls: _T) -> _T: ... # cls has type _T Using a function or method body other than the ellipsis literal is currently unspecified. Stub authors may experiment with other bodies, but it is up to @@ -310,7 +304,7 @@ type stubs:: def foo(x: str) -> str: ... @overload def foo(x: float) -> int: ... - def foo(x: Union[str, float]) -> Any: ... + def foo(x: str | float) -> Any: ... Aliases and NewType ------------------- @@ -512,8 +506,8 @@ Such undocumented objects are allowed because omitting objects can confuse users. Users who see an error like "module X has no attribute Y" will not know whether the error appeared because their code had a bug or because the stub is wrong. Although it may also be helpful for a type -checker to point out usage of private objects, we usually prefer false -negatives (no errors for wrong code) over false positives (type errors +checker to point out usage of private objects, false negatives (no errors for +wrong code) are preferable over false positives (type errors for correct code). In addition, even for private objects a type checker can be helpful in pointing out that an incorrect type was used. @@ -580,6 +574,9 @@ annotated function ``bar()``:: def bar(x: str, y, *, z=...): ... +The ``# incomplete`` comment is mainly intended as a reminder for stub +authors, but can be used by tools to flag such items. + Attribute Access ---------------- @@ -899,10 +896,10 @@ Types Generally, use ``Any`` when a type cannot be expressed appropriately with the current type system or using the correct type is unergonomic. -Use ``float`` instead of ``Union[int, float]``. +Use ``float`` instead of ``int | float``. Use ``None`` instead of ``Literal[None]``. For argument types, -use ``bytes`` instead of ``Union[bytes, memoryview, bytearray]``. +use ``bytes`` instead of ``bytes | memoryview | bytearray``. Use ``Text`` in stubs that support Python 2 when something accepts both ``str`` and ``unicode``. Avoid using ``Text`` in stubs or branches for @@ -949,8 +946,8 @@ Maybe:: def foo(self) -> list[int]: ... def bar(self) -> Mapping[str]: ... -Avoid ``Union`` return types, since they require ``isinstance()`` checks. -Use ``Any`` if necessary. +Avoid union return types, since they require ``isinstance()`` checks. +Use ``Any`` or ``X | Any`` if necessary. Use built-in generics instead of the aliases from ``typing``. From c7c30c894e671a4175d7fd3ed1054a013b5853a9 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 24 Aug 2021 08:40:48 +0200 Subject: [PATCH 024/539] Link to discussion forums (#852) --- docs/index.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 670c78244..0f03af18d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,6 +16,13 @@ Indices and tables * :ref:`search` +Discussions and Support +======================= + +* `User help forum `_ +* `User chat on Gitter `_ +* `Developer mailing list `_ + Typing-related Tools ==================== From 651cb585f1db980ac111a11fc40f1ebc4a8ff719 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 24 Aug 2021 23:44:46 +0200 Subject: [PATCH 025/539] Rename the documentation (#854) Use correct heading level --- docs/index.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 0f03af18d..dba85dd8d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,6 @@ -Welcome to the Python Type System documentation! -================================================ +************************* +Static Typing with Python +************************* .. toctree:: :maxdepth: 2 From 7d47c9fb37fdb4317831ae6e92d9324e7c92a8f4 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 25 Aug 2021 15:51:17 +0200 Subject: [PATCH 026/539] Rework README (#853) * Rename to "Static Typing for Python". * Add "Documentation and Support" section. * Update what the repository is used for. --- README.md | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a1df5e212..aa1531d1e 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,40 @@ [![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -PEP 484: Type Hints -=================== +Static Typing for Python +======================== -This GitHub repo is used for three separate things: +Documentation and Support +------------------------- -- The issue tracker is used to discuss PEP-level type system issues. - However, - [typing-sig](https://mail.python.org/mailman3/lists/typing-sig.python.org/) - is more appropriate these days. +The documentation for Python's static typing can be found at +[typing.readthedocs.io](https://typing.readthedocs.io/). You can get +help either in our [support forum](/python/typing/discussions) or +chat with us on (Gitter)[https://gitter.im/python/typing]. + +Improvements to the type system should be discussed on the +[typing-sig](https://mail.python.org/mailman3/lists/typing-sig.python.org/) +mailing list, although the [issues](/python/typing/issues) in this +repository contain some historic discussions. + +Repository Content +------------------ + +This GitHub repo is used for several things: - A backport of the `typing` module for older Python versions (2.7 and - 3.4) is maintained here. Note that the canonical source lives + 3.4) is maintained in the [src directory](./src). + Note that the canonical source lives [upstream](https://github.com/python/cpython/blob/master/Lib/typing.py) in the CPython repo. -- The `typing_extensions` module lives here. +- The `typing_extensions` module lives in the + [typing\_extensions](./typing_extensions) directory. + +- The documentation at [typing.readthedocs.io](https://typing.readthedocs.io/) + is maintained in the [docs directory](./docs). + +- A [discussion forum](/python/typing/discussions) for typing-related user + help is hosted here. Workflow -------- From 01b0368690ff9a367d6320cb161c4104fbb1af7a Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 25 Aug 2021 16:12:19 +0200 Subject: [PATCH 027/539] Linkfixes (#857) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aa1531d1e..860cc5678 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ Documentation and Support The documentation for Python's static typing can be found at [typing.readthedocs.io](https://typing.readthedocs.io/). You can get -help either in our [support forum](/python/typing/discussions) or -chat with us on (Gitter)[https://gitter.im/python/typing]. +help either in our [support forum](https://github.com/python/typing/discussions) or +chat with us on [Gitter](https://gitter.im/python/typing). Improvements to the type system should be discussed on the [typing-sig](https://mail.python.org/mailman3/lists/typing-sig.python.org/) -mailing list, although the [issues](/python/typing/issues) in this +mailing list, although the [issues](https://github.com/python/typing/issues) in this repository contain some historic discussions. Repository Content @@ -33,7 +33,7 @@ This GitHub repo is used for several things: - The documentation at [typing.readthedocs.io](https://typing.readthedocs.io/) is maintained in the [docs directory](./docs). -- A [discussion forum](/python/typing/discussions) for typing-related user +- A [discussion forum](https://github.com/python/typing/discussions) for typing-related user help is hosted here. Workflow From 7d2fae84d7592fdf04380fbbe2d3e667ecedbcf6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 29 Aug 2021 08:52:30 -0700 Subject: [PATCH 028/539] prepare release 3.10.0.1 (#863) --- typing_extensions/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing_extensions/setup.py b/typing_extensions/setup.py index 7b1611cb8..ca537456b 100644 --- a/typing_extensions/setup.py +++ b/typing_extensions/setup.py @@ -9,7 +9,7 @@ 'to install the typing package.\n') exit(1) -version = '3.10.0.0' +version = '3.10.0.1' description = 'Backported and Experimental Type Hints for Python 3.5+' long_description = '''\ Typing Extensions -- Backported and Experimental Type Hints for Python From 3ed2adbe3c1c595179b115d26ec209733ac03fb4 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Sun, 29 Aug 2021 17:58:26 +0200 Subject: [PATCH 029/539] Update supported features (#855) * Union shorthand is now mostly supported * Explicitly mention type guards as being supported * ParamSpec is partly supported * Describe cases where built-in generics are not supported * Reference typeshed issues for unsupported features Restructure unsupported features section --- docs/stubs.rst | 112 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 15 deletions(-) diff --git a/docs/stubs.rst b/docs/stubs.rst index f06efe963..d5ad68506 100644 --- a/docs/stubs.rst +++ b/docs/stubs.rst @@ -59,7 +59,8 @@ not use the positional-only syntax from PEP 570 [#pep570]_, introduced in Python 3.8, although type checker authors are encouraged to support it. Stubs are treated as if ``from __future__ import annotations`` is enabled. -In particular, built-in generics and forward references can be used. +In particular, built-in generics, pipe union syntax (``X | Y``), and forward +references can be used. Starting with Python 3.8, the :py:mod:`ast` module from the standard library supports all syntax features required by this PEP. Older Python versions can use the @@ -170,6 +171,35 @@ specified in ``__all__`` are imported:: Type checkers support cyclic imports in stub files. +Built-in Generics +----------------- + +PEP 585 [#pep585]_ built-in generics are generally supported, with +the following exceptions [#ts-4820]_: + +* Built-in generics don't work in type aliases. +* Built-in generics don't work in base classes. +* ``type`` is not supported. +* Variable length tuples (``tuple[X, ...]``) are not supported. + +In these cases, the appropriate types from ``typing`` must be used. + +Using imports from ``collections.abc`` instead of ``typing`` is +generally possible and recommended. + +Unions +------ + +Declaring unions with ``Union`` and ``Optional`` is supported by all +type checkers. With the exception of type aliases [#ts-4819]_, the shorthand syntax +is also supported:: + + def foo(x: int | str) -> int | None: ... # recommended + def foo(x: Union[int, str]) -> Optional[int]: ... # ok + + TYPE_ALIAS = Union[int, str] # ok + TYPE_ALIAS = int | str # does not work with all type checkers + Module Level Attributes ----------------------- @@ -231,6 +261,8 @@ The class must match the class in which it is declared. Using other classes, including sub or super classes, will not work. In addition, the ``self`` annotation cannot contain type variables. +.. _supported-functions: + Functions and Methods --------------------- @@ -282,9 +314,21 @@ But:: @classmethod def create_it(cls: _T) -> _T: ... # cls has type _T +PEP 612 [#pep612]_ parameter specification variables (``ParamSpec``) +are supported in argument and return types, although +they need to be marked with ``# type: ignore`` to work with all +type checkers [#ts-4827]_:: + + _P = ParamSpec("_P") + _T = TypeVar("_T") + + def foo(cb: Callable[_P, _T]) -> Callable[_P, _T]: ... # type: ignore + +PEP 647 [#pep647]_ type guards are supported. + Using a function or method body other than the ellipsis literal is currently unspecified. Stub authors may experiment with other bodies, but it is up to -individual type checkers how to interpret them. +individual type checkers how to interpret them:: def foo(): ... # compatible def bar(): pass # behavior undefined @@ -465,11 +509,14 @@ No:: Unsupported Features -------------------- -Currently, positional-only argument syntax (PEP 570 [#pep570]_), -unions using the pipe operator (``|``) (PEP 604 [#pep604]_), -``ParamSpec`` (PEP 612 [#pep612]_), and ``TypeAlias`` (PEP 613 [#pep613]_) -are not supported by all type -checkers and should not be used in stubs. +Currently, the following features are not supported by all type checkers +and should not be used in stubs: + +* Positional-only argument syntax (PEP 570 [#pep570]_). Instead, use + the syntax described in the section :ref:`supported-functions`. + [#ts-4972]_ +* ``TypeAlias`` (PEP 613 [#pep613]_). Instead, use a simple + assigment to define a type alias. [#ts-4913]_ Type Stub Content ================= @@ -594,9 +641,9 @@ the following class:: An appropriate stub definition is:: - from typing import Any, Optional + from typing import Any class Foo: - def __getattr__(self, name: str) -> Optional[Any]: ... + def __getattr__(self, name: str) -> Any | None: ... Note that only ``__getattr__``, not ``__getattribute__``, is guaranteed to be supported in stubs. @@ -741,6 +788,29 @@ No:: x: int class MyError(Exception): ... # leave an empty line between the classes +Shorthand Syntax +---------------- + +Where possible, use shorthand syntax for unions instead of +``Union`` or ``Optional``. ``None`` should be the last +element of an union. See the Unions_ section for cases where +using the shorthand syntax is not possible. + +Yes:: + + def foo(x: str | int) -> None: ... + def bar(x: str | None) -> int | None: ... + +No:: + + def foo(x: Union[str, int]) -> None: ... + def bar(x: Optional[str]) -> Optional[int]: ... + def baz(x: None | str) -> None: ... + +But the following is still necessary:: + + TYPE_ALIAS = Optional[Union[str, int]] + Module Level Attributes ----------------------- @@ -801,19 +871,19 @@ does not apply to positional-only arguments, which are marked with a double underscore. Use the ellipsis literal ``...`` in place of actual default argument -values. Use an explicit ``Optional`` annotation instead of +values. Use an explicit ``X | None`` annotation instead of a ``None`` default. Yes:: def foo(x: int = ...) -> None: ... - def bar(y: Optional[str] = ...) -> None: ... + def bar(y: str | None = ...) -> None: ... No:: def foo(x: int = 0) -> None: ... def bar(y: str = None) -> None: ... - def baz(z: Optional[str] = None) -> None: ... + def baz(z: str | None = None) -> None: ... Do not annotate ``self`` and ``cls`` in method definitions, except when referencing a type variable. @@ -861,12 +931,12 @@ with an underscore. Yes:: _T = TypeVar("_T") - _DictList = dict[str, list[Optional[int]]] + _DictList = Dict[str, List[Optional[int]] No:: T = TypeVar("T") - DictList = dict[str, list[Optional[int]]] + DictList = Dict[str, List[Optional[int]]] Language Features ----------------- @@ -949,7 +1019,9 @@ Maybe:: Avoid union return types, since they require ``isinstance()`` checks. Use ``Any`` or ``X | Any`` if necessary. -Use built-in generics instead of the aliases from ``typing``. +Use built-in generics instead of the aliases from ``typing``, +where possible. See the section `Built-in Generics`_ for cases, +where it's not possible to use them. Yes:: @@ -1005,8 +1077,18 @@ PEPs .. [#pep604] PEP 604 -- Allow writing union types as X | Y, Prados and Moss (https://www.python.org/dev/peps/pep-0604) .. [#pep612] PEP 612 -- Parameter Specification Variables, Mendoza (https://www.python.org/dev/peps/pep-0612) .. [#pep613] PEP 613 -- Explicit Type Aliases, Zhu (https://www.python.org/dev/peps/pep-0613) +.. [#pep647] PEP 647 -- User-Defined Type Guards, Traut (https://www.python.org/dev/peps/pep-0647) .. [#pep3107] PEP 3107 -- Function Annotations, Winter and Lownds (https://www.python.org/dev/peps/pep-3107) +Bugs +---- + +.. [#ts-4819] typeshed issue #4819 -- PEP 604 tracker (https://github.com/python/typeshed/issues/4819) +.. [#ts-4820] typeshed issue #4820 -- PEP 585 tracker (https://github.com/python/typeshed/issues/4820) +.. [#ts-4827] typeshed issue #4827 -- PEP 612 tracker (https://github.com/python/typeshed/issues/4827) +.. [#ts-4913] typeshed issue #4913 -- PEP 613 tracker (https://github.com/python/typeshed/issues/4913) +.. [#ts-4972] typeshed issue #4972 -- PEP 570 tracker (https://github.com/python/typeshed/issues/4972) + Copyright ========= From ec052c368f6ab7bb4f83c04824346015cdd9ae97 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 30 Aug 2021 16:19:33 +0200 Subject: [PATCH 030/539] Fix linter warnings (#868) --- typing_extensions/src_py3/typing_extensions.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index e4d164404..5cca4e0c7 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -1694,7 +1694,8 @@ def _typeddict_new(*args, total=True, **kwargs): class _TypedDictMeta(type): def __init__(cls, name, bases, ns, total=True): - # In Python 3.4 and 3.5 the __init__ method also needs to support the keyword arguments. + # In Python 3.4 and 3.5 the __init__ method also needs to support the + # keyword arguments. # See https://www.python.org/dev/peps/pep-0487/#implementation-details super(_TypedDictMeta, cls).__init__(name, bases, ns) @@ -2072,7 +2073,6 @@ class Annotated(metaclass=AnnotatedMeta): get_origin = typing.get_origin get_args = typing.get_args elif PEP_560: - from typing import _GenericAlias try: # 3.9+ from typing import _BaseGenericAlias @@ -2386,6 +2386,7 @@ def _get_type_vars(self, tvars): if self not in tvars: tvars.append(self) + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. class _ConcatenateGenericAlias(list): @@ -2433,6 +2434,7 @@ def _get_type_vars(self, tvars): if self.__origin__ and self.__parameters__: typing._get_type_vars(self.__parameters__, tvars) + @_tp_cache def _concatenate_getitem(self, parameters): if parameters == (): @@ -2473,7 +2475,8 @@ def __repr__(self): def __getitem__(self, parameters): return _concatenate_getitem(self, parameters) - Concatenate = _ConcatenateForm('Concatenate', + Concatenate = _ConcatenateForm( + 'Concatenate', doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a higher order function which adds, removes or transforms parameters of a callable. @@ -2616,8 +2619,8 @@ def __getitem__(self, parameters): return _GenericAlias(self, (item,)) TypeGuard = _TypeGuardForm( - 'TypeGuard', - doc="""Special typing form used to annotate the return type of a user-defined + 'TypeGuard', + doc="""Special typing form used to annotate the return type of a user-defined type guard function. ``TypeGuard`` only accepts a single type argument. At runtime, functions marked this way should return a boolean. From c1cbcace0107b8a4d6e246c403aaa009caf9cc5f Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Mon, 30 Aug 2021 18:56:36 +0300 Subject: [PATCH 031/539] Fixes crash on Python3.10-beta.2 and typing_extensions@3.10.0.1 (#869) --- typing_extensions/src_py3/typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 5cca4e0c7..4c02bc622 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2392,7 +2392,7 @@ class _ConcatenateGenericAlias(list): # Trick Generic into looking into this for __parameters__. if PEP_560: - __class__ = _GenericAlias + __class__ = typing._GenericAlias elif sys.version_info[:3] == (3, 5, 2): __class__ = typing.TypingMeta else: From af13555328b3a9d6cfa941eaa574f841d47187d2 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 30 Aug 2021 09:19:56 -0700 Subject: [PATCH 032/539] fix _GenericAlias import (#871) --- typing_extensions/src_py3/typing_extensions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 4c02bc622..95bb87357 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -18,6 +18,7 @@ if PEP_560: GenericMeta = TypingMeta = type + from typing import _GenericAlias else: from typing import GenericMeta, TypingMeta OLD_GENERICS = False @@ -1399,7 +1400,7 @@ def __new__(cls, *args, **kwds): elif PEP_560: - from typing import _type_check, _GenericAlias, _collect_type_vars # noqa + from typing import _type_check, _collect_type_vars # noqa def _no_init(self, *args, **kwargs): if type(self)._is_protocol: From 58de2e9c5bfb47cc6438a879eb304153bf153b95 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Tue, 31 Aug 2021 01:56:40 +0800 Subject: [PATCH 033/539] Fix tests for 3.10rc1 and up (#872) --- typing_extensions/src_py3/test_typing_extensions.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 06d4cc40a..75bdb8ebb 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -53,6 +53,7 @@ TYPING_3_5_3 = TYPING_LATEST or sys.version_info[:3] >= (3, 5, 3) TYPING_3_6_1 = TYPING_LATEST or sys.version_info[:3] >= (3, 6, 1) TYPING_3_10_0 = TYPING_LATEST or sys.version_info[:3] >= (3, 10, 0) +TYPING_3_11_0 = TYPING_LATEST or sys.version_info[:3] >= (3, 11, 0) # For typing versions where issubclass(...) and # isinstance(...) checks are forbidden. @@ -466,6 +467,10 @@ class GetTypeHintTests(BaseTestCase): @skipUnless(PY36, 'Python 3.6 required') def test_get_type_hints_modules(self): ann_module_type_hints = {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str} + if (TYPING_3_11_0 + or (TYPING_3_10_0 and sys.version_info.releaselevel in {'candidate', 'final'})): + # More tests were added in 3.10rc1. + ann_module_type_hints['u'] = int | float self.assertEqual(gth(ann_module), ann_module_type_hints) self.assertEqual(gth(ann_module2), {}) self.assertEqual(gth(ann_module3), {}) From 7552efe8b5f96f0e63f8e77711a4cf03cae92921 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 30 Aug 2021 11:42:55 -0700 Subject: [PATCH 034/539] prepare release 3.10.0.2 (#873) --- typing_extensions/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing_extensions/setup.py b/typing_extensions/setup.py index ca537456b..6075ee913 100644 --- a/typing_extensions/setup.py +++ b/typing_extensions/setup.py @@ -9,7 +9,7 @@ 'to install the typing package.\n') exit(1) -version = '3.10.0.1' +version = '3.10.0.2' description = 'Backported and Experimental Type Hints for Python 3.5+' long_description = '''\ Typing Extensions -- Backported and Experimental Type Hints for Python From f3d3d054c819d9350aa91dde5aafe3547a2b63fa Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Tue, 31 Aug 2021 02:49:43 +0800 Subject: [PATCH 035/539] don't use custom _ConcatenenateGenericAlias for 3.10 (#870) --- .../src_py3/typing_extensions.py | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 95bb87357..f36bba372 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2388,52 +2388,53 @@ def _get_type_vars(self, tvars): tvars.append(self) -# Inherits from list as a workaround for Callable checks in Python < 3.9.2. -class _ConcatenateGenericAlias(list): - - # Trick Generic into looking into this for __parameters__. - if PEP_560: - __class__ = typing._GenericAlias - elif sys.version_info[:3] == (3, 5, 2): - __class__ = typing.TypingMeta - else: - __class__ = typing._TypingBase +if not hasattr(typing, 'Concatenate'): + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class _ConcatenateGenericAlias(list): - # Flag in 3.8. - _special = False - # Attribute in 3.6 and earlier. - if sys.version_info[:3] == (3, 5, 2): - _gorg = typing.GenericMeta - else: - _gorg = typing.Generic + # Trick Generic into looking into this for __parameters__. + if PEP_560: + __class__ = typing._GenericAlias + elif sys.version_info[:3] == (3, 5, 2): + __class__ = typing.TypingMeta + else: + __class__ = typing._TypingBase - def __init__(self, origin, args): - super().__init__(args) - self.__origin__ = origin - self.__args__ = args + # Flag in 3.8. + _special = False + # Attribute in 3.6 and earlier. + if sys.version_info[:3] == (3, 5, 2): + _gorg = typing.GenericMeta + else: + _gorg = typing.Generic - def __repr__(self): - _type_repr = typing._type_repr - return '{origin}[{args}]' \ - .format(origin=_type_repr(self.__origin__), - args=', '.join(_type_repr(arg) for arg in self.__args__)) + def __init__(self, origin, args): + super().__init__(args) + self.__origin__ = origin + self.__args__ = args - def __hash__(self): - return hash((self.__origin__, self.__args__)) + def __repr__(self): + _type_repr = typing._type_repr + return '{origin}[{args}]' \ + .format(origin=_type_repr(self.__origin__), + args=', '.join(_type_repr(arg) for arg in self.__args__)) - # Hack to get typing._type_check to pass in Generic. - def __call__(self, *args, **kwargs): - pass + def __hash__(self): + return hash((self.__origin__, self.__args__)) - @property - def __parameters__(self): - return tuple(tp for tp in self.__args__ if isinstance(tp, (TypeVar, ParamSpec))) + # Hack to get typing._type_check to pass in Generic. + def __call__(self, *args, **kwargs): + pass - if not PEP_560: - # Only required in 3.6 and lower. - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - typing._get_type_vars(self.__parameters__, tvars) + @property + def __parameters__(self): + return tuple(tp for tp in self.__args__ if isinstance(tp, (TypeVar, ParamSpec))) + + if not PEP_560: + # Only required in 3.6 and lower. + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + typing._get_type_vars(self.__parameters__, tvars) @_tp_cache From 6a6f807a967fc9533cda3b25b42a4baa279e9adb Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 30 Aug 2021 22:51:45 +0200 Subject: [PATCH 036/539] Switch pipeline to Github Actions (#866) --- .github/workflows/ci.yml | 67 ++++++++++++++++++++++++++++++++++++++++ .travis.yml | 53 ------------------------------- test-requirements.txt | 2 +- 3 files changed, 68 insertions(+), 54 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..0e368d104 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,67 @@ +name: Test and lint + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + tests: + name: Run tests + + strategy: + fail-fast: false + matrix: + # Python 3.4 disabled, due to problems with GitHub Actions. + python-version: [3.10-dev, 3.9, 3.8, 3.7, 3.6, 3.5, 2.7] + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r test-requirements.txt + + - name: Run tests + env: + PYTHON_VERSION: ${{ matrix.python-version }} + run: | + export PYTHONPATH=`python -c "import sys; print('python2' if sys.version.startswith('2') else 'src')"` + if [[ $PYTHON_VERSION < '3.7' ]]; then pytest $PYTHONPATH; fi + + if [[ $PYTHON_VERSION < '3.5' ]]; then pip install -U .; fi + export PYTHONPATH=`python -c "import sys; print('typing_extensions/src_py2' if sys.version.startswith('2') else 'typing_extensions/src_py3')"` + pytest $PYTHONPATH + + linting: + name: Run linting + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r test-requirements.txt + + - name: Run linting + run: | + flake8 + flake8 --config=.flake8-tests src/test_typing.py python2/test_typing.py typing_extensions/src_py2/test_typing_extensions.py typing_extensions/src_py3/test_typing_extensions.py diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3e6391950..000000000 --- a/.travis.yml +++ /dev/null @@ -1,53 +0,0 @@ -language: python - -jobs: - include: - - name: "3.9" - dist: xenial - python: 3.9.0 - - name: "3.8" - dist: xenial - python: 3.8.0 - - name: "3.7.3" - dist: xenial - python: 3.7.3 - - name: "3.7.2" - dist: xenial - python: 3.7.2 - - name: "3.7.1" - dist: xenial - python: 3.7.1 - - name: "3.7.0" - dist: xenial - python: 3.7.0 - - name: "3.6.2" - python: 3.6.2 - - name: "3.6.1" - python: 3.6.1 - - name: "3.6" - python: 3.6 - - name: "3.5.3" - python: 3.5.3 - - name: "3.5.2" - python: 3.5.2 -## - name: "3.5.1" -## dist: trusty -## python: 3.5.1 - - name: "3.5" - python: 3.5 - - name: "3.4" - python: 3.4 - - name: "2.7" - python: 2.7 - -install: -- pip install -r test-requirements.txt - -script: - - export PYTHONPATH=`python -c "import sys; print('python2' if sys.version.startswith('2') else 'src')"`; - if [[ $TRAVIS_PYTHON_VERSION < '3.7' ]]; then py.test $PYTHONPATH; fi - - if [[ $TRAVIS_PYTHON_VERSION < '3.5' ]]; then pip install -U .; fi - - export PYTHONPATH=`python -c "import sys; print('typing_extensions/src_py2' if sys.version.startswith('2') else 'typing_extensions/src_py3')"`; - py.test $PYTHONPATH; - - if [[ $TRAVIS_PYTHON_VERSION == '3.8' ]]; then flake8; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.8' ]]; then flake8 --config=.flake8-tests src/test_typing.py python2/test_typing.py typing_extensions/src_py2/test_typing_extensions.py typing_extensions/src_py3/test_typing_extensions.py; fi diff --git a/test-requirements.txt b/test-requirements.txt index 1a033f49d..1e44ed02b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,6 @@ flake8; python_version >= '3.6' flake8-bugbear; python_version >= '3.6' flake8-pyi; python_version >= '3.6' -pytest>=4.4.1; python_version >= '3.4' +pytest==4.6.11 pytest-xdist>=1.18; python_version >= '3.4' pytest-cov>=2.4.0; python_version >= '3.4' From 68e7cb7c90a1932e04fde6808a3ca9d3818fd64c Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 31 Aug 2021 00:06:04 +0200 Subject: [PATCH 037/539] Fix linting (#875) * Split linting into two steps * Fix an overly long line * Run linter with Python 3.9 * Ignore more errors in test linting --- .flake8-tests | 12 ++++++++++-- .github/workflows/ci.yml | 13 +++++++------ typing_extensions/src_py3/typing_extensions.py | 4 +++- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.flake8-tests b/.flake8-tests index 06b437dbf..6af938e1f 100644 --- a/.flake8-tests +++ b/.flake8-tests @@ -11,17 +11,25 @@ builtins = basestring, unicode max-line-length = 100 ignore = # temporary ignores until we sort it out + B017, + E302, + E303, E306, + E501, E701, E704, + F722, F811, + F821, + F841, + W503, # irrelevant plugins B3, - DW12 + DW12, # consistency with mypy W504 exclude = # This config is NOT for the main module. - setup.py + setup.py, python2/typing.py, src/typing.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e368d104..e09f89d41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: pytest $PYTHONPATH linting: - name: Run linting + name: Lint runs-on: ubuntu-latest @@ -54,14 +54,15 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r test-requirements.txt - - name: Run linting - run: | - flake8 - flake8 --config=.flake8-tests src/test_typing.py python2/test_typing.py typing_extensions/src_py2/test_typing_extensions.py typing_extensions/src_py3/test_typing_extensions.py + - name: Lint implementation + run: flake8 + + - name: Lint tests + run: flake8 --config=.flake8-tests src/test_typing.py python2/test_typing.py typing_extensions/src_py2/test_typing_extensions.py typing_extensions/src_py3/test_typing_extensions.py diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index f36bba372..184da0ad1 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2428,7 +2428,9 @@ def __call__(self, *args, **kwargs): @property def __parameters__(self): - return tuple(tp for tp in self.__args__ if isinstance(tp, (TypeVar, ParamSpec))) + return tuple( + tp for tp in self.__args__ if isinstance(tp, (TypeVar, ParamSpec)) + ) if not PEP_560: # Only required in 3.6 and lower. From a4757c645af1de0592f324bb100237a32d41a928 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 31 Aug 2021 01:16:28 +0200 Subject: [PATCH 038/539] Remove the drafts directory (#878) This contained historic notes for PEP 484. --- drafts/GENERICS.rst | 155 ----------------------------------------- drafts/NOTES-AMBV.txt | 83 ---------------------- drafts/NOTES-GUIDO.txt | 51 -------------- drafts/NOTES-SIEK.txt | 136 ------------------------------------ drafts/VARIANCE.rst | 140 ------------------------------------- 5 files changed, 565 deletions(-) delete mode 100644 drafts/GENERICS.rst delete mode 100644 drafts/NOTES-AMBV.txt delete mode 100644 drafts/NOTES-GUIDO.txt delete mode 100644 drafts/NOTES-SIEK.txt delete mode 100644 drafts/VARIANCE.rst diff --git a/drafts/GENERICS.rst b/drafts/GENERICS.rst deleted file mode 100644 index 424471f08..000000000 --- a/drafts/GENERICS.rst +++ /dev/null @@ -1,155 +0,0 @@ -Generics --------- - -The subscript operation on container types is overloaded to support -specifying the item type for the container as part of a type hint. -Example:: - - from typing import List - - def space_join(a: List[str]) -> str: - """Join list items with spaces.""" - return ' '.join(a) - -Given this definition, the following code will be flagged as a type -error, because the argument has type ``List[int]`` (list of integers) -rather than ``List[str]`` (list of strings):: - - space_join([1, 2, 3]) # Error - -Depending on the definition of the container, multiple type arguments -may be given in this notation:: - - from collections import defaultdict - from typing import Iterable, Dict - - def word_count(words: Iterable[str]) -> Dict[str, int]: - """Count words in document.""" - res = defaultdict(int) - for word in words: - res[word] += 1 - return dict(res) - - print(word_count(['to', 'be', 'or', 'not', 'to', 'be'])) - # {'to': 2, 'be': 2, 'or': 1, 'not': 1} - - -Sometimes we want the type of several arguments and/or the return type -to vary collectively. We can do this using type variables. A type -variable must be defined using the ``TypeVar()`` factory, after which -it can be used in mutiple function or method signatures. This is -called a generic function. Example:: - - from collections import defaultdict - from typing import Iterable, Mapping, TypeVar - - T = TypeVar('T') - - def thing_count(things: Iterable[T]) -> Dict[T, int]: - """Count words in document.""" - res = defaultdict(int) - for thing in things: - res[thing] += 1 - return dict(res) - - print(thing_count([2, 3, 5, 7, 2, 3]) - # {2: 2, 3: 2, 5: 1, 7: 1} - -Note that the argument to ``TypeVar()`` must be a string, and it must -be assigned to a variable with exactly that name. Type variables -cannot be redefined in the same module. These constraints are -enforced by the type checker (but not by the runtime implementation of -``TypeVar()``). - -We can also define generic classes, as follows:: - - from typing import Generic, TypeVar - - T = TypeVar('T') - - class Node(Generic[T]): - - def __init__(self, label: T) -> None: - self.label = label - - def get_label(self) -> T: - return self.label - - def mknod(x: int) -> Node[int]: - return Node(x) - - print(mknod(40).get_label() + 2) - # 42 - -This same mechanism is used to define the container classes exported -by ``typing`` (although the implementation is hairier due to the -desire to also emulate collection ABCs). - -Type variables have a few more tricks up their sleeves: - -* Additional positional arguments must be type expressions that will - be used to constrain the types that are acceptable substitutions. - This feature is used for example by the predefined type variable - ``AnyStr``, which is defined as:: - - AnyStr = TypeVar('AnyStr', str, bytes) - - When such a constrained type variable is used in an argument type, - the actual type must be a subtype of one of the constraints. When - used in a return value type, the inferred return type will be - exactly the corresponding constraint (*not* the inferred argument - type, which may be a subtype thereof). For example:: - - from typing import AnyStr - - def add_strings(a: AnyStr, b: AnyStr) -> AnyStr: - return a+b - - add_string('x', 'y') # 'xy' - add_string(b'a', b'b') # b'ab' - add_string('x', b'z') # Error - - class MyStr(str): - pass - - add_string(MyStr('a'), MyStr('b')) # 'ab', not MyStr('ab') - -* Type variables may be declared as covariant or contravariant. The - default is invariant. Covariance is best explained using an - example:: - - from typing import TypeVar - - T = TypeVar('T') - Tco = TypeVar('Tco', covariant=True) - - class MyTuple(Generic[Tco]): - ... # Implements immutable sequence operations - - class MyList(MyTuple[T]): - ... # Adds mutable sequence operations - - class Employee: - ... - - class Manager(Employee): - ... - - issubclass(MyTuple[Manager], MyTuple[Employee]) # True - issubclass(MyList[Manager], MyList[Employee]) # False - - def print_employees(emps: MyTuple[Employee]) -> None: - for emp in emps: - print(emp) - - def add_employee(emps: MyList[Employee], emp: Employee) -> None: - emps.append(emp) - - mgrs = MyList[Manager](...) # Undecided if this is allowed - print_employees(mgrs) # OK - bob = Manager(...) - add_employee(mgrs, bob) # Error - - For a good if theoretical explanation of covariance and - contravariance see the Wikipedia article: - http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29 diff --git a/drafts/NOTES-AMBV.txt b/drafts/NOTES-AMBV.txt deleted file mode 100644 index f9ee1af95..000000000 --- a/drafts/NOTES-AMBV.txt +++ /dev/null @@ -1,83 +0,0 @@ -============================= -Re: notes from Jython meeting -============================= - -* In PEPs Abstract rarely went straight to the meat of the discussion. - I'll try to make the first few examples in the **Type Definition - Syntax** stronger. There's no reason I'm using ``.format()`` at this - point. - -* Yes, the lack of Callable signature definition is an ommision, it's - going to be hard to use it with the subscription syntax Callable[]. - We can't use the call syntax Callable() if we wish to operate directly - on ABCs, which I believe is what the users will expect. We can't - since the syntax wouldn't work on concrete subclasses of ABCs. - I added this as an open issue in README. - -* The remark about removing ``types`` from being mentioned actually - makes me think we could solve many issues before they arise by - introducing a short section **The place of the ``typing`` module in - the standard library** which would explain how the authors intend for - it to be used and what is its role compared to builtin types, - ``types``, ``collections``, and ``collections.abc``. The worries - that Guido has about the ``types`` module being ill-suited for type - hinted are spot on, we should mention that in the document. - -* I am thrilled by ``Set[Employee]`` returning a new class object that - is a Set-of-Employees. This leaves the door open for runtime - inspection. However, that immediately raises the questions: - - * ``Set is not Set[Employee] == True``, right? - - * ``Set is Set[Any] == True``, right? - - * ``Set[Employee] is Set[Union[Employee]] == True``, right? - - * ``issubclass(Set[Employee], Set)``, right? We must accept a set of - employees in every place that accepts "any set". Taking the latter - check into account, we'd have ``issubclass(Set[Employee], - Set[Any])``. The covariance/contravariance discussion needs to - happen at some point. - -* While I don't see the point of ``AnyStr`` specifically (reeks too much - of ``basestring``), I see that it's a useful example for ``Var``. - You're right it's not equivalent to ``Union[bytes, str]``, it must be - obviously parametric to keep the other occurences in scope consistent. - With ``Var`` the syntax I was thinking about a following expression:: - - AnyStr = Var('AnyStr', base=Union[str, bytes]) - - Now that I saw this, I asked myself if that should be equivalent to - ``typevar('AnyStr', values=(str, bytes))`` and the answer is "No". - Union is not parametrized, hence not kept in sync between occurences. - My mistake makes me wonder if we should make the parametrized types - syntax more explicit for the type definition reader (in Java world - seeing is pretty clear). I will ponder about this some - more. - -* Interoperability of ``typing.List`` with ``java.util.ArrayList`` - sounds good in principle. - -* Realistically I think retroactive conformance in Python is going to be - achieved by means of ABC.register() and/or Protocol.register(). - I have my hopes in using protocols as a more static analysis-friendly - version of __subclasshook__ - -Unadressed matters ------------------- - -Lack of time currently to write about the following, will adress as soon -as I get to it again. - -* how does filter() construct an instance of Y, write an implementation, - extend Callable to say Callable[X] - -* typevar values vs. Var base= - -* should we encourage using abstract types: in principle yes, in - practice there's issues (long type names, strings and bytes being - iterables and sequences) - -* consequences of covariance in generics as compared to invariance - -* support for structural types diff --git a/drafts/NOTES-GUIDO.txt b/drafts/NOTES-GUIDO.txt deleted file mode 100644 index d13953f8c..000000000 --- a/drafts/NOTES-GUIDO.txt +++ /dev/null @@ -1,51 +0,0 @@ -Notes from meeting ------------------- - -Meeting at Dropbox. Attendees: Guido, Jukka, Jim Baker, Jeremy Siek, -Michael Vitousek (student of Jeremy's). - - -- Could have a few examples in Abstract. - -- Discussion of function types, e.g. keyword parameters. Observation - that what the type checker can represent may be more than what the - syntax supports. Jukka says in practice callbacks rarely if ever - are called with callbacks. - -- Any reason you use format() in the first example? (It is worthy of - a very custom checker all by itself.) - -- Let's not mention the 'types' module much, nor use it in examples. - Its types are too concrete for most practical purposes. - E.g. types.FunctionType does not include built-in types nor bound - methods, but those rarely pose problems. - -- The early function/callable examples don't specify the signature. - It would be good to fill in the bodies of the example functions. - -- Discussion of Jython, mostly java.util.List. Jim wants to generate - mypy stubs from .class files and then run standard mypy; he wants - java.util.List to be compatible with typing.List. - -- We want Set[Employee] to actually create a new class object that - knows it is a Set of Employees (not just Set). However - Set[Employee]() should just return an empty concrete set, - i.e. set(). - -- Problems with the filter() example -- how does it construct an - instance of Y? - -- Discussion about typevar('T', values=(alt1, alt2)) vs. Var('T', - base=...). Jeremy doesn't like base= because it reeks of - subclassing too much. Note that AnyStr is very different from - Union[str, bytes]. - -- Jython wants to use type hints for code generation and doesn't want - to change its parser to retain #type: comments. But List[int]() is - ugly and slows down execution. No clear solution; maybe Jython's - type inferencing can infer the type of p in cases like this:: - - def primes(n: int) -> List[int]: - p = [] - ...fill p with n primes... - return p diff --git a/drafts/NOTES-SIEK.txt b/drafts/NOTES-SIEK.txt deleted file mode 100644 index b3fb143cc..000000000 --- a/drafts/NOTES-SIEK.txt +++ /dev/null @@ -1,136 +0,0 @@ -Notes from Meeting at Dropbox, Oct. 3, 2014 -with Guido, Jukka, Jim, Michael, and Jeremy - -Concrete Types vs. Abstract types ---------------------------------- - -Jim cares about interoperability with Java, and specifically wants -code annotated with type List to work with instances of -java.util.ArrayList. Guido stated that it would be reasonable for List -to be more flexible than to just admit the concrete Python list. - - -We discussed whether to minimize the use of concrete types in type -annotations in preference to abstract types. Perhaps the best approach -here is to encourage the use of some types over others in the -documentation and other writing about optional types. - - -Type Annotations in Comments ----------------------------- - -Jim, Jeremy and Michael would like to avoid putting type annotations -in comments because that requires a non-trivial addition to our -implementation. We discussed several alternatives: -* use x = List[int]() - This is a good solution for global, module-level variables - but Guido points out it is not so great for local variables - because there is a performance cost. -* Improve the type inference algorithm to avoid the need for - explicit type annotations for locals, perhaps making more - use of function return type annotations. -* Have type annotations in comments, but give then a lesser - status as a hint, so that implementatiosn that ignore them - are still conformant. (This may already be the case because - the PEP doesn't mandate any static or dynamic checking.) -* Use decorators. Guido points out that this isn't what - decorators where meant for and puts the annotations too - far away from the variable uses. -Here's an example we were discussing: -def f() -> List[int]: - x = List[int]() use this for stub syntax, i.e., global variables - versus - x = [] - versus - x = [] # type: List[int] - - ... - return x - -For Python 3.6, Guido suggests - x : List[int] = [] - - -Type Variables --------------- - -Regarding type variables and bounds, -Jeremy prefers -T = Var('T', implements = C) -to -T = Var('T', base = C) - -It's important that we make clear the distinction between -X = Var('X') -Y = Var('Y', base = Iterable[X]) -versus -X = Var('X') -Y = Iterable[X] - - -The filter example seemed to be missing some pieces. -Instead of -X = Var('X') -Y = Var('Y', base = Iterable[X]) -filter(rule: Callable, collection: Y) -> Y -we should have -filter(rule: Callable[X], collection: Y) -> Y -and there's also an issue with the return type and -how a collection of that type would be created. -Perhaps the return type should have been Iterable[X]. -In any event, writing down the function body and -running it through MyPy would keep us honest. - -We also discussed the nuances of AnyStr and the -values parameter of Var. - -Erasure: should MutableMapping[int] evaluate to -collections.abc.MutableMapping? No, the type parameter needs to be -remembered. - - -Covariance ----------- - -"As with function arguments, generics are covariant, which is in -spirit of duck typing." -Jeremy want's to be able to express strictly static checking as an -option, don't want to limit strength of type system. -Put another way, Jeremy's hope for gradual typing is to let programmers -choose anywhere in between fully dynamic and fully static checking, -but if the type system is weakened by allowing covariance, then -the fully static option goes away. - -Jim wants to generate MyPy-style stub files from Java class files. - - -Syntax for Generic Types ------------------------- -Jeremy voiced a preference to reserve []'s for generic types -and not other types. Jukka shared that he had tried that -and got feedback from users that going with a uniform -syntax, such as the current []'s for all types with sub-parts, -is easier for them. - - -Retroactive Conformance ------------------------ -Jeremy wants to allow concrete types to be usable with -abstract types for which they did not inherit from. -Structural types allow this, and there is research on how to -do this with nominal types. -Jukka voiced the concern that some of those approaches give -up modular type checking. Jeremy agreed that modular type checking -is important, but that perhaps there's a way to have our cake -and eat it too. - -This issue is related to the use of the register method on ABC's to -get the right behavior with isinstance. - - -Structural vs. Nominal Types ----------------------------- -We briefly discussed the possibility of having structual -object types in addition to the nominal types currently in MyPy. -Reticulated currently suppose structural object types and -not nominal types. diff --git a/drafts/VARIANCE.rst b/drafts/VARIANCE.rst deleted file mode 100644 index ffce8c823..000000000 --- a/drafts/VARIANCE.rst +++ /dev/null @@ -1,140 +0,0 @@ -Variance -======== - -Author's note -------------- - -In an earlier draft I had swapped the meanings of 'in' and 'out' -(having misremembered what I had read on Wikipedia). I believe I am -not unique in making this mistake, so we've switched to -covariant=True and contravariant=True. - -Discussion ----------- - -The big question is: If Manager is a subclass of Employee, should -List[Manager] be considered a subclass of List[Employee}? - -If we answer "yes", we may encounter a runtime type error in a program -that passes the type check. I'll show you how. - -Let's first consider an innocent example that does not have a problem:: - - # Example 1 - - def add_employee(emps: List[Employee], new_emp: Employee) -> None: - emps.append(new_emp) - - def add_manager(mgrs: List[Manager], new_mgr: Manager) -> None: - add_employee(mgrs, new_mgr) - -Suppose we define the type checker so that this passes. Now let's -make a change to add_employee():: - - # Example 2 - - def add_employee(emps: List[Employee], new_emp: Employee) -> None: - copy_of_new_emp = Employee() - copy_of_new_emp.name = new_emp.name - # Etc., copy all known Employee fields - emps.append(copy_of_new_emp) - - # add_manager() is unchanged - -Using the above definition of the type checker (where List[Manager] is -a subclass of List[Employee]), example 2 would also type-check, but it -has a bug that example 1 doesn't have: When add_manager() is called, -it now appends a new Employee to the argument list (mgrs), which -violates its type (List[Manager]) at runtime. - -Let's leave aside for now why one would make this change, or how to -fix the bug. In real software development the two functions may be in -different packages, maintained by different teams, and much more -complicated, and that's exactly where you'd like a type checker to -catch problems -- but the type checker has let us down. - -There are languages whose type system has this problem; but they -typically address it in the language by making the append() call in -add_employee() fail at runtime. IIUC Java does this for Arrays -- an -Array object "knows" the item type from its declaration and rejects -new elements that aren't a subclass of this type. - -BTW, the reason why languages introduce such unsound type checks is -that intuitively (i.e. if you haven't considered or encountered cases -like example 2 above) making List[Manager] a subclass of -List[Employee] feels right to many developers. A sound type system -will make example 1 fail the type check, which is hard to explain to -developers, especially since there is no bug when you run it. - -("Sound" is a technical term used for type systems, meaning "won't -pass programs that fail at runtime". There's also a dual property -whose name escapes me, meaning "won't fail programs that pass at -runtime". Both properties are equally elusive. :-) - -Now, PEP 484 doesn't in general make hard promises of soundness -- -there are lots of ways that Python's dynamic typing can do things that -will break a program even though it passes the type check, so why -should we promise soundness for this particular situation (roughly, -defining subclassing of mutable container types based on the item -classes)? - -The reason is that example 2 is a pretty gross violation of an -expectation: you have an object whose declared type is List[Manager] -but, without doing anything sneaky, you've managed to add an Employee -object that isn't a Manager to it, while flying under the radar of the -type checked. - -The proposed solution gives the developer who writes a container class -a way to tell the type checker whether the container should be -considered mutable or immutable. For immutable container -types, problems like example 2 cannot happen (proof left as an -exercise for the reader :-), so the type checker can consider -e.g. Tuple[Manager] a subclass of Tuple[Employee], while disallowing -List[Manager] as a subclass of List[Employee]. - -The proposal adds a way to declare that a type variable follows the -rules for covariance or contravariance, while by default type -variables are invariant:: - - X = TypeVar('X') # Invariant - Y = TypeVar('Y', covariant=True) # Covariant - Z = TypeVar('Z', contravariant=True) # Contravariant - - class A(Generic[X]): - ... - - class B(Generic[Y]): - ... - - class C(Generic[Z]): - ... - - issubclass(A[Manager], A[Employee]) # False - issubclass(B[Manager], B[Employee]) # True - issubclass(B[Employee], B[Manager]) # True (I think) - -Unfortunately this still doesn't let us write example 1. But the -consolation is that this means example 2 won't happen. So how can we -rewrite example 1 in a way that is type-safe under the new rule? - -The solution is perhaps unexpected: We have to write a mutable data -type that's covariant and implements a run-time type check like Java -Array. Here's a sketch of such a data type:: - - T = TypeVar('T', covariant=True) # Covariant - - class MyList(MutableSequence[T]): - def __init__(self): - self._data = [] # type: List[T] - self._item_type = self.__class__.__parameters__[0] - ... - def append(self, item: T): - if not isinstance(item, self._item_type): - raise TypeError("item type %r does not match %r" % - (item.__class__, self._item_type)) - ... - -Reference ---------- - -http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29 From f530b8cde47c862b566951ffc2ae562e247619aa Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 31 Aug 2021 01:16:50 +0200 Subject: [PATCH 039/539] Remove two obsolete scripts (#879) --- update-mypy.sh | 19 ------------------- update-stdlib.sh | 23 ----------------------- 2 files changed, 42 deletions(-) delete mode 100755 update-mypy.sh delete mode 100755 update-stdlib.sh diff --git a/update-mypy.sh b/update-mypy.sh deleted file mode 100755 index 704e4bd7f..000000000 --- a/update-mypy.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -xe - -case $# in - 0) echo "Please supply a commit message as argument(s)"; exit 2;; -esac - -HERE=$PWD - -cd ~/src/mypy -git co master -git pull - -cp $HERE/src/typing.py lib-typing/3.2/typing.py -cp $HERE/src/test_typing.py lib-typing/3.2/test_typing.py - -cp $HERE/python2/typing.py lib-typing/2.7/typing.py -cp $HERE/python2/test_typing.py lib-typing/2.7/test_typing.py - -git ci lib-typing -m "$@" diff --git a/update-stdlib.sh b/update-stdlib.sh deleted file mode 100755 index f27560b54..000000000 --- a/update-stdlib.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -xe - -case $# in - 0) echo "Please supply a commit message as argument(s)"; exit 2;; -esac - -HERE=$PWD - -cd ~/src/cpython35 -hg pull -u -cp $HERE/src/typing.py Lib/typing.py -cp $HERE/src/test_typing.py Lib/test/test_typing.py -hg ci -m "$@" - -cd ~/src/cpython36 -hg pull -u ../cpython35 -hg merge 3.5 -hg ci -m "$@ (3.5->3.6)" - -cd ~/src/cpython37 -hg pull -u ../cpython36 -hg merge 3.6 -hg ci -m "$@ (3.6->3.7)" From 829eb415207c1eee3856cb80ae33e6d2913697d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Lilleb=C3=B8=20Gundersen?= Date: Tue, 31 Aug 2021 12:08:48 +0300 Subject: [PATCH 040/539] Add caching to the GHA workflow (#880) --- .github/workflows/build-docs.yml | 2 +- .github/workflows/ci.yml | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 806f8331d..43711d0f7 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -20,7 +20,7 @@ jobs: python-version: 3.9 - name: Install dependencies run: | - python -m pip install --upgrade pip + pip install --upgrade pip pip install -r docs/requirements.txt - name: Build the documentation run: make -C docs html diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e09f89d41..d3a866cd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,9 +27,17 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Load pip cache + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/test-requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install dependencies run: | - python -m pip install --upgrade pip + pip install --upgrade pip pip install -r test-requirements.txt - name: Run tests @@ -56,9 +64,17 @@ jobs: with: python-version: 3.9 + - name: Load pip cache + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/test-requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install dependencies run: | - python -m pip install --upgrade pip + pip install --upgrade pip pip install -r test-requirements.txt - name: Lint implementation From 9f0f1b623eabc0abfc2c0fa425b66bb97f930faf Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 31 Aug 2021 16:24:38 +0200 Subject: [PATCH 041/539] Fix and clean up CI script (#876) * Split Python 2.7 tests from Python 3 tests as they use different paths and require installation of the typing module. * Split running typing and typing_extensions tests into two steps. * Don't test typing on Python 3 * Remove pytest-xdist and pytest-cov not necessary here --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++------------ test-requirements.txt | 5 ++--- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3a866cd5..6ed45a5b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,14 +8,38 @@ permissions: contents: read jobs: + tests-27: + name: Run tests (2.7) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 2.7 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r test-requirements.txt + + - name: Test typing + run: pytest python2 + + - name: Test typing_extensions + run: | + pip install typing + pytest typing_extensions/src_py2 + tests: name: Run tests strategy: fail-fast: false matrix: - # Python 3.4 disabled, due to problems with GitHub Actions. - python-version: [3.10-dev, 3.9, 3.8, 3.7, 3.6, 3.5, 2.7] + python-version: [3.10-dev, 3.9, 3.8, 3.7, 3.6] runs-on: ubuntu-latest @@ -40,16 +64,8 @@ jobs: pip install --upgrade pip pip install -r test-requirements.txt - - name: Run tests - env: - PYTHON_VERSION: ${{ matrix.python-version }} - run: | - export PYTHONPATH=`python -c "import sys; print('python2' if sys.version.startswith('2') else 'src')"` - if [[ $PYTHON_VERSION < '3.7' ]]; then pytest $PYTHONPATH; fi - - if [[ $PYTHON_VERSION < '3.5' ]]; then pip install -U .; fi - export PYTHONPATH=`python -c "import sys; print('typing_extensions/src_py2' if sys.version.startswith('2') else 'typing_extensions/src_py3')"` - pytest $PYTHONPATH + - name: Test typing_extensions + run: pytest typing_extensions/src_py3 linting: name: Lint diff --git a/test-requirements.txt b/test-requirements.txt index 1e44ed02b..13a0c968f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,5 @@ flake8; python_version >= '3.6' flake8-bugbear; python_version >= '3.6' flake8-pyi; python_version >= '3.6' -pytest==4.6.11 -pytest-xdist>=1.18; python_version >= '3.4' -pytest-cov>=2.4.0; python_version >= '3.4' +pytest==4.6.11; python_version < '3.0' +pytest; python_version >= '3.0' From 409715f17b57f7c7bb6c06b9b8322c0d86fd0a89 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 9 Sep 2021 21:20:11 +0200 Subject: [PATCH 042/539] Git ignore .venv* and venv* directories (#888) Also sort entries and remove the now redundant docs/venv/ entry. --- .gitignore | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 1997aade5..0ad58f48a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,17 @@ MANIFEST + +__pycache__/ build/ dist/ -docs/venv/ +tmp/ +venv*/ + +.cache/ +.idea/ .tox/ +.venv*/ .vscode/ -.idea/ -.cache/ -__pycache__/ -tmp/ + *.swp *.pyc *.egg-info/ From 1d8fb26709687cd76f159b069a4ba2d29f1fa48a Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 14 Sep 2021 04:41:05 +0200 Subject: [PATCH 043/539] Add "Typing Guidance for Python Libraries" document (#889) By Eric Traut, source: https://raw.githubusercontent.com/microsoft/pyright/main/docs/typed-libraries.md See https://github.com/srittau/type-stub-pep/issues/94 for background. This is a pristine copy of the original document. It needs to be converted into RestructuredText and linked from the index as a next step. --- docs/libraries.md | 395 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 docs/libraries.md diff --git a/docs/libraries.md b/docs/libraries.md new file mode 100644 index 000000000..283fb5196 --- /dev/null +++ b/docs/libraries.md @@ -0,0 +1,395 @@ +# Typing Guidance for Python Libraries + +Much of Python’s popularity can be attributed to the rich collection of Python libraries available to developers. Authors of these libraries play an important role in improving the experience for Python developers. This document provides some recommendations and guidance for Python library authors. + +These recommendations are intended to provide the following benefits: + +1. Consumers of libraries should have a great coding experience with fast and accurate completion suggestions, class and function documentation, signature help (including parameter default values), hover text, and auto-imports. This should happen by default without needing to download extra packages and without any special configuration. These features should be consistent across the Python ecosystem regardless of a developer’s choice of editor, IDE, notebook environment, etc. +2. Consumers of libraries should be able to rely on complete and accurate type information so static type checkers can detect and report type inconsistencies and other violations of the interface contract. +3. Library authors should be able to specify a well-defined interface contract that is enforced by tools. This allows a library implementation to evolve and improve without breaking consumers of the library. +4. Library authors should have the benefits of static type checking to produce high-quality, bug-free implementations. + + +## Inlined Type Annotations and Type Stubs +[PEP 561](https://www.python.org/dev/peps/pep-0561/) documents several ways type information can be delivered for a library: inlined type annotations, type stub files included in the package, a separate companion type stub package, and type stubs in the typeshed repository. Some of these options fall short on delivering the benefits above. We therefore provide the following more specific guidance to library authors. + +*All libraries should include inlined type annotations for the functions, classes, methods, and constants that comprise the public interface for the library.* + +Inlined type annotations should be included directly within the source code that ships with the package. Of the options listed in PEP 561, inlined type annotations offer the most benefits. They typically require the least effort to add and maintain, they are always consistent with the implementation, and docstrings and default parameter values are readily available, allowing language servers to enhance the development experience. + +There are cases where inlined type annotations are not possible — most notably when a library’s exposed functionality is implemented in a language other than Python. + +*Libraries that expose symbols implemented in languages other than Python should include stub (“.pyi”) files that describe the types for those symbols. These stubs should also contain docstrings and default parameter values.* + +In many existing type stubs (such as those found in typeshed), default parameter values are replaced with with “...” and all docstrings are removed. We recommend that default values and docstrings remain within the type stub file so language servers can display this information to developers. + + +## Library Interface +[PEP 561](https://www.python.org/dev/peps/pep-0561/) indicates that a “py.typed” marker file must be included in the package if the author wishes to support type checking of their code. + +If a “py.typed” module is present, a type checker will treat all modules within that package (i.e. all files that end in “.py” or “.pyi”) as importable unless the file name begins with an underscore. These modules comprise the supported interface for the library. + +Each module exposes a set of symbols. Some of these symbols are considered “private” — implementation details that are not part of the library’s interface. Type checkers like pyright use the following rules to determine which symbols are visible outside of the package. + +* Symbols whose names begin with an underscore (but are not dunder names) are considered private. +* Imported symbols are considered private by default. If they use the “import A as A” (a redundant module alias), “from X import A as A” (a redundant symbol alias), or “from . import A” forms, symbol “A” is not private unless the name begins with an underscore. If a file `__init__.py` uses form “from .A import X”, symbol “A” is treated likewise. If a wildcard import (of the form “from X import *”) is used, all symbols referenced by the wildcard are not private. +* A module can expose an `__all__` symbol at the module level that provides a list of names that are considered part of the interface. This overrides all other rules above, allowing imported symbols or symbols whose names begin with an underscore to be included in the interface. +* Local variables within a function (including nested functions) are always considered private. + +The following idioms are supported for defining the values contained within `__all__`. These restrictions allow type checkers to statically determine the value of `__all__`. + +* `__all__ = ('a', b')` +* `__all__ = ['a', b']` +* `__all__ += ['a', b']` +* `__all__ += submodule.__all__` +* `__all__.extend(['a', b'])` +* `__all__.extend(submodule.__all__)` +* `__all__.append('a')` +* `__all__.remove('a')` + + +## Type Completeness +A “py.typed” library is said to be “type complete” if all of the symbols that comprise its interface have type annotations that refer to types that are fully known. Private symbols are exempt. + +A “known type” is defined as follows: + +Classes: + +* All class variables, instance variables, and methods that are “visible” (not overridden) are annotated and refer to known types +* If a class is a subclass of a generic class, type arguments are provided for each generic type parameter, and these type arguments are known types + +Functions and Methods: + +* All input parameters have type annotations that refer to known types +* The return parameter is annotated and refers to a known type +* The result of applying one or more decorators results in a known type + +Type Aliases: + +* All of the types referenced by the type alias are known + +Variables: + +* All variables have type annotations that refer to known types + +Type annotations can be omitted in a few specific cases where the type is obvious from the context: + +* Constants that are assigned simple literal values (e.g. `RED = '#F00'` or `MAX_TIMEOUT = 50` or `room_temperature: Final = 20`). A constant is a symbol that is assigned only once and is either annotated with `Final` or is named in all-caps. A constant that is not assigned a simple literal value requires explicit annotations, preferably with a `Final` annotation (e.g. `WOODWINDS: Final[List[str]] = ['Oboe', 'Bassoon']`). +* Enum values within an Enum class do not require annotations because they take on the type of the Enum class. +* Type aliases do not require annotations. A type alias is a symbol that is defined at a module level with a single assignment where the assigned value is an instantiable type, as opposed to a class instance (e.g. `Foo = Callable[[Literal["a", "b"]], Union[int, str]]` or `Bar = Optional[MyGenericClass[int]]`). +* The “self” parameter in an instance method and the “cls” parameter in a class method do not require an explicit annotation. +* The return type for an `__init__` method does not need to be specified, since it is always `None`. +* The following module-level symbols do not require type annotations: `__all__`,`__author__`, `__copyright__`, `__email__`, `__license__`, `__title__`, `__uri__`, `__version__`. +* The following class-level symbols do not require type annotations: `__class__`, `__dict__`, `__doc__`, `__module__`, `__slots__`. + +### Examples of known and unknown types +```python + +# Variable with unknown type +a = [3, 4, 5] + +# Variable with known type +a: List[int] = [3, 4, 5] + +# Type alias with partially unknown type (because type +# arguments are missing for list and dict) +DictOrList = Union[list, dict] + +# Type alias with known type +DictOrList = Union[List[Any], Dict[str, Any]] + +# Generic type alias with known type +_T = TypeVar("_T") +DictOrList = Union[List[_T], Dict[str, _T]] + +# Function with known type +def func(a: Optional[int], b: Dict[str, float] = {}) -> None: + pass + +# Function with partially unknown type (because type annotations +# are missing for input parameters and return type) +def func(a, b): + pass + +# Function with partially unknown type (because of missing +# type args on Dict) +def func(a: int, b: Dict) -> None: + pass + +# Function with partially unknown type (because return type +# annotation is missing) +def func(a: int, b: Dict[str, float]): + pass + +# Decorator with partially unknown type (because type annotations +# are missing for input parameters and return type) +def my_decorator(func): + return func + +# Function with partially unknown type (because type is obscured +# by untyped decorator) +@my_decorator +def func(a: int) -> str: + pass + + +# Class with known type +class MyClass: + height: float = 2.0 + + def __init__(self, name: str, age: int): + self.age: int = age + + @property + def name(self) -> str: + ... + +# Class with partially unknown type +class MyClass: + # Missing type annotation for class variable + height = 2.0 + + # Missing input parameter annotations + def __init__(self, name, age): + # Missing type annotation for instance variable + self.age = age + + # Missing return type annotation + @property + def name(self): + ... + +# Class with partially unknown type +class BaseClass: + # Missing type annotation + height = 2.0 + + # Missing type annotation + def get_stuff(self): + ... + +# Class with known type (because it overrides all symbols +# exposed by BaseClass that have incomplete types) +class DerivedClass(BaseClass): + height: float + + def get_stuff(self) -> str: + ... + +# Class with partially unknown type because base class +# (dict) is generic, and type arguments are not specified. +class DictSubclass(dict): + pass + +``` + +## Verifying Type Completeness +Pyright provides a feature that allows library authors to verify type completeness for a “py.typed” package. To use this feature, create a clean Python environment and install your package along with all of the other dependent packages. Run the CLI version of pyright with the `--verifytypes` option. + +`pyright --verifytypes ` + +Pyright will analyze the library, identify all symbols that comprise the interface to the library and emit errors for any symbols whose types are unknown. It also produces a “type completeness score” which is the percentage of symbols with known types. + +To see additional details (including a full list of symbols in the library), append the `--verbose` option. + +The `--verifytypes` option can be combined with `--outputjson` to emit the results in a form that can be consumed by other tools. + +The `--verifytypes` feature can be integrated into a continuous integration (CI) system to verify that a library remains “type complete”. + +If the `--verifytypes` option is combined with `--ignoreexternal`, any incomplete types that are imported from other external packages are ignored. This allows library authors to focus on adding type annotations for the code that is directly under their control. + + +### Improving Type Completeness + +Here are some tips for increasing the type completeness score for your library: + +* If your package includes tests or sample code, consider removing them from the distribution. If there is good reason to include them, consider placing them in a directory that begins with an underscore so they are not considered part of your library’s interface. +* If your package includes submodules that are meant to be implementation details, rename those files to begin with an underscore. +* If a symbol is not intended to be part of the library’s interface and is considered an implementation detail, rename it such that it begins with an underscore. It will then be considered private and excluded from the type completeness check. +* If your package exposes types from other libraries, work with the maintainers of these other libraries to achieve type completeness. + + +## Best Practices for Inlined Types + +### Wide vs. Narrow Types +In type theory, when comparing two types that are related to each other, the “wider” type is the one that is more general, and the “narrower” type is more specific. For example, `Sequence[str]` is a wider type than `List[str]` because all `List` objects are also `Sequence` objects, but the converse is not true. A subclass is narrower than a class it derives from. A union of types is wider than the individual types that comprise the union. + +In general, a function input parameter should be annotated with the widest possible type supported by the implementation. For example, if the implementation requires the caller to provide an iterable collection of strings, the parameter should be annotated as `Iterable[str]`, not as `List[str]`. The latter type is narrower than necessary, so if a user attempts to pass a tuple of strings (which is supported by the implementation), a type checker will complain about a type incompatibility. + +As a specific application of the “use the widest type possible” rule, libraries should generally use immutable forms of container types instead of mutable forms (unless the function needs to modify the container). Use `Sequence` rather than `List`, `Mapping` rather than `Dict`, etc. Immutable containers allow for more flexibility because their type parameters are covariant rather than invariant. A parameter that is typed as `Sequence[Union[str, int]]` can accept a `List[int]`, `Sequence[str]`, and a `Sequence[int]`. But a parameter typed as `List[Union[str, int]]` is much more restrictive and accepts only a `List[Union[str, int]]`. + +### Overloads +If a function or method can return multiple different types and those types can be determined based on the presence or types of certain parameters, use the `@overload` mechanism defined in [PEP 484](https://www.python.org/dev/peps/pep-0484/#id45). When overloads are used within a “.py” file, they must appear prior to the function implementation, which should not have an `@overload` decorator. + +### Keyword-only Parameters +If a function or method is intended to take parameters that are specified only by name, use the keyword-only separator ("*"). + +```python +def create_user(age: int, *, dob: Optional[date] = None): + ... +``` + +### Annotating Decorators +Decorators modify the behavior of a class or a function. Providing annotations for decorators is straightforward if the decorator retains the original signature of the decorated function. + +```python +_F = TypeVar("_F", bound=Callable[..., Any]) + +def simple_decorator(_func: _F) -> _F: + """ + Simple decorators are invoked without parentheses like this: + @simple_decorator + def my_function(): ... + """ + ... + +def complex_decorator(*, mode: str) -> Callable[[_F], _F]: + """ + Complex decorators are invoked with arguments like this: + @complex_decorator(mode="easy") + def my_function(): ... + """ + ... +``` + +Decorators that mutate the signature of the decorated function present challenges for type annotations. The `ParamSpec` and `Concatenate` mechanisms described in [PEP 612](https://www.python.org/dev/peps/pep-0612/) provide some help here, but these are available only in Python 3.10 and newer. More complex signature mutations may require type annotations that erase the original signature, thus blinding type checkers and other tools that provide signature assistance. As such, library authors are discouraged from creating decorators that mutate function signatures in this manner. + +### Generic Classes and Functions +Classes and functions that can operate in a generic manner on various types should declare themselves as generic using the mechanisms described in [PEP 484](https://www.python.org/dev/peps/pep-0484/). This includes the use of `TypeVar` symbols. Typically, a `TypeVar` should be private to the file that declares it, and should therefore begin with an underscore. + +### Type Aliases +Type aliases are symbols that refer to other types. Generic type aliases (those that refer to unspecialized generic classes) are supported by most type checkers. Pyright also provides support for recursive type aliases. + +[PEP 613](https://www.python.org/dev/peps/pep-0613/) provides a way to explicitly designate a symbol as a type alias using the new TypeAlias annotation. + +```python +# Simple type alias +FamilyPet = Union[Cat, Dog, GoldFish] + +# Generic type alias +ListOrTuple = Union[List[_T], Tuple[_T, ...]] + +# Recursive type alias +TreeNode = Union[LeafNode, List["TreeNode"]] + +# Explicit type alias using PEP 613 syntax +StrOrInt: TypeAlias = Union[str, int] +``` + +### Abstract Classes and Methods +Classes that must be subclassed should derive from `ABC`, and methods or properties that must be overridden should be decorated with the `@abstractmethod` decorator. This allows type checkers to validate that the required methods have been overridden and provide developers with useful error messages when they are not. It is customary to implement an abstract method by raising a `NotImplementedError` exception. + +```python +from abc import ABC, abstractmethod + +class Hashable(ABC): + @property + @abstractmethod + def hash_value(self) -> int: + """Subclasses must override""" + raise NotImplementedError() + + @abstractmethod + def print(self) -> str: + """Subclasses must override""" + raise NotImplementedError() +``` + +### Final Classes and Methods +Classes that are not intended to be subclassed should be decorated as `@final` as described in [PEP 591](https://www.python.org/dev/peps/pep-0591/). The same decorator can also be used to specify methods that cannot be overridden by subclasses. + +### Literals +Type annotations should make use of the Literal type where appropriate, as described in [PEP 586](https://www.python.org/dev/peps/pep-0586/). Literals allow for more type specificity than their non-literal counterparts. + +### Constants +Constant values (those that are read-only) can be specified using the Final annotation as described in [PEP 591](https://www.python.org/dev/peps/pep-0591/). + +Type checkers will also typically treat variables that are named using all upper-case characters as constants. + +In both cases, it is OK to omit the declared type of a constant if it is assigned a literal str, int, float, bool or None value. In such cases, the type inference rules are clear and unambiguous, and adding a literal type annotation would be redundant. + +```python +# All-caps constant with inferred type +COLOR_FORMAT_RGB = "rgb" + +# All-caps constant with explicit type +COLOR_FORMAT_RGB: Literal["rgb"] = "rgb" +LATEST_VERSION: Tuple[int, int] = (4, 5) + +# Final variable with inferred type +ColorFormatRgb: Final = "rgb" + +# Final variable with explicit type +ColorFormatRgb: Final[Literal["rgb"]] = "rgb" +LATEST_VERSION: Final[Tuple[int, int]] = (4, 5) +``` + +### Typed Dictionaries, Data Classes, and Named Tuples +If your library runs only on newer versions of Python, you are encouraged to use some of the new type-friendly classes. + +NamedTuple (described in [PEP 484](https://www.python.org/dev/peps/pep-0484/)) is preferred over namedtuple. + +Data classes (described in [PEP 557](https://www.python.org/dev/peps/pep-0557/)) is preferred over untyped dictionaries. + +TypedDict (described in [PEP 589](https://www.python.org/dev/peps/pep-0589/)) is preferred over untyped dictionaries. + + +## Compatibility with Older Python Versions +Each new version of Python from 3.5 onward has introduced new typing constructs. This presents a challenge for library authors who want to maintain runtime compatibility with older versions of Python. This section documents several techniques that can be used to add types while maintaining backward compatibility. + +### Quoted Annotations +Type annotations for variables, parameters, and return types can be placed in quotes. The Python interpreter will then ignore them, whereas a type checker will interpret them as type annotations. + +```python +# Older versions of Python do not support subscripting +# for the OrderedDict type, so the annotation must be +# enclosed in quotes. +def get_config(self) -> "OrderedDict[str, str]": + return self._config +``` + +### Type Comment Annotations +Python 3.0 introduced syntax for parameter and return type annotations, as specified in [PEP 484](https://www.python.org/dev/peps/pep-0484/). Python 3.6 introduced support for variable type annotations, as specified in [PEP 526](https://www.python.org/dev/peps/pep-0526/). + +If you need to support older versions of Python, type annotations can still be provided as “type comments”. These comments take the form # type: . + +```python +class Foo: + # Variable type comments go at the end of the line + # where the variable is assigned. + timeout = None # type: Optional[int] + + # Function type comments can be specified on the + # line after the function signature. + def send_message(self, name, length): + # type: (str, int) -> None + ... + + # Function type comments can also specify the type + # of each parameter on its own line. + def receive_message( + self, + name, # type: str + length # type: int + ): + # type: () -> Message + ... +``` + +### typing_extensions +New type features that require runtime support are typically included in the stdlib `typing` module. Where possible, these new features are back-ported to a runtime library called `typing_extensions` that works with older Python runtimes. + +### TYPE_CHECKING +The `typing` module exposes a variable called `TYPE_CHECKING` which has a value of False within the Python runtime but a value of True when the type checker is performing its analysis. This allows type checking statements to be conditionalized. + +Care should be taken when using `TYPE_CHECKING` because behavioral changes between type checking and runtime could mask problems that the type checker would otherwise catch. + + +## Non-Standard Type Behaviors +Type annotations provide a way to annotate typical type behaviors, but some classes implement specialized, non-standard behaviors that cannot be described using standard type annotations. For now, such types need to be annotated as Any, which is unfortunate because the benefits of static typing are lost. + + +## Docstrings +Docstrings should be provided for all classes, functions, and methods in the interface. They should be formatted according to [PEP 257](https://www.python.org/dev/peps/pep-0257/). + +There is currently no single agreed-upon standard for function and method docstrings, but several common variants have emerged. We recommend using one of these variants. From 0a9ee8f924e9081f98c1dbe26cc2eabb5f581c14 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 15 Sep 2021 17:00:20 +0200 Subject: [PATCH 044/539] typing_extensions: Drop support for Python 3.4 & 3.5 (#881) Co-authored-by: Jelle Zijlstra --- typing_extensions/setup.cfg | 3 +++ typing_extensions/setup.py | 20 ++++++-------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/typing_extensions/setup.cfg b/typing_extensions/setup.cfg index 637292647..328dc21ca 100644 --- a/typing_extensions/setup.cfg +++ b/typing_extensions/setup.cfg @@ -1,2 +1,5 @@ [metadata] license-file = LICENSE + +[options] +python_version = >=3.6 diff --git a/typing_extensions/setup.py b/typing_extensions/setup.py index 6075ee913..806a8bf92 100644 --- a/typing_extensions/setup.py +++ b/typing_extensions/setup.py @@ -4,31 +4,25 @@ import sys from setuptools import setup -if sys.version_info < (2, 7, 0) or (3, 0, 0) <= sys.version_info < (3, 4, 0): - sys.stderr.write('ERROR: You need Python 2.7 or 3.4+ ' - 'to install the typing package.\n') +if sys.version_info < (2, 7, 0) or (3, 0, 0) <= sys.version_info < (3, 6, 0): + sys.stderr.write('ERROR: You need Python 2.7 or 3.6+ ' + 'to install typing_extensions.\n') exit(1) version = '3.10.0.2' -description = 'Backported and Experimental Type Hints for Python 3.5+' +description = 'Backported and Experimental Type Hints for Python 3.6+' long_description = '''\ Typing Extensions -- Backported and Experimental Type Hints for Python The ``typing`` module was added to the standard library in Python 3.5, but many new features have been added to the module since then. -This means users of Python 3.5 - 3.6 who are unable to upgrade will not be +This means users of older Python versions who are unable to upgrade will not be able to take advantage of new types added to the ``typing`` module, such as ``typing.Protocol`` or ``typing.TypedDict``. The ``typing_extensions`` module contains backports of these changes. Experimental types that will eventually be added to the ``typing`` -module are also included in ``typing_extensions``, such as -``typing.ParamSpec`` and ``typing.TypeGuard``. - -Users of Python versions before 3.5 should install and use -the ``typing`` module from PyPI instead of using this one, unless specifically -writing code that must be compatible with multiple Python versions or requires -experimental types. +module are also included in ``typing_extensions``. ''' classifiers = [ @@ -38,8 +32,6 @@ 'License :: OSI Approved :: Python Software Foundation License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', From 17fe6c432103e265a8e1724c62a49af1bbeda297 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 15 Sep 2021 17:40:20 +0200 Subject: [PATCH 045/539] Remove typing backport (#882) Closes: #877 --- tox.ini => .flake8 | 13 +- .flake8-tests | 7 +- .github/workflows/ci.yml | 5 +- CONTRIBUTING.md | 35 +- MANIFEST.in | 7 - README.md | 19 +- python2/mod_generics_cache.py | 14 - python2/test_typing.py | 2706 ------------------------------- python2/typing.py | 2550 ----------------------------- setup.cfg | 2 - setup.py | 71 - src/mod_generics_cache.py | 49 - src/test_typing.py | 2815 --------------------------------- src/typing.py | 2454 ---------------------------- 14 files changed, 13 insertions(+), 10734 deletions(-) rename tox.ini => .flake8 (71%) delete mode 100644 MANIFEST.in delete mode 100644 python2/mod_generics_cache.py delete mode 100644 python2/test_typing.py delete mode 100644 python2/typing.py delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 src/mod_generics_cache.py delete mode 100644 src/test_typing.py delete mode 100644 src/typing.py diff --git a/tox.ini b/.flake8 similarity index 71% rename from tox.ini rename to .flake8 index 750c38ff7..b61006a00 100644 --- a/tox.ini +++ b/.flake8 @@ -1,14 +1,5 @@ -[tox] -envlist = py27, py34 - -[testenv] -changedir = src -commands = python -m unittest discover - -[testenv:py27] -changedir = python2 - [flake8] + # fake builtins for python2/* builtins = basestring, unicode max-line-length = 90 @@ -23,7 +14,5 @@ ignore = exclude = # tests have more relaxed formatting rules # and its own specific config in .flake8-tests - python2/test_typing.py, - src/test_typing.py, typing_extensions/src_py2/test_typing_extensions.py, typing_extensions/src_py3/test_typing_extensions.py, diff --git a/.flake8-tests b/.flake8-tests index 6af938e1f..dab93cc62 100644 --- a/.flake8-tests +++ b/.flake8-tests @@ -1,7 +1,7 @@ # This configuration is specific to test_*.py; you need to invoke it # by specifically naming this config, like this: # -# $ flake8 --config=.flake8-tests src/test_typing.py python2/test_typing.py +# $ flake8 --config=.flake8-tests [SOURCES] # # This will be possibly merged in the future. @@ -28,8 +28,3 @@ ignore = DW12, # consistency with mypy W504 -exclude = - # This config is NOT for the main module. - setup.py, - python2/typing.py, - src/typing.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ed45a5b4..0e9d09ce6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,9 +25,6 @@ jobs: python -m pip install --upgrade pip pip install -r test-requirements.txt - - name: Test typing - run: pytest python2 - - name: Test typing_extensions run: | pip install typing @@ -97,4 +94,4 @@ jobs: run: flake8 - name: Lint tests - run: flake8 --config=.flake8-tests src/test_typing.py python2/test_typing.py typing_extensions/src_py2/test_typing_extensions.py typing_extensions/src_py3/test_typing_extensions.py + run: flake8 --config=.flake8-tests typing_extensions/src_py2/test_typing_extensions.py typing_extensions/src_py3/test_typing_extensions.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14d7b2168..a26372d55 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,6 @@ -This repository contains backports of the CPython `typing` module to earlier versions of -Python. Therefore, code in this repo should follow CPython's style guidelines and +Code in this repository should follow CPython's style guidelines and contributors need to sign the PSF Contributor Agreement. -# typing - -The `typing` module provided by this repository is a backport for Python versions that -do not have `typing` in the standard library: Python 2.7 and 3.4. These versions are no -longer officially supported by CPython, so there is little remaining interest in keeping -the backport up to date. We will accept contributions backporting new features to -`typing`, but we are no longer actively requiring Python 2 support for all -contributions. - # typing_extensions The `typing_extensions` module provides a way to access new features from the standard @@ -36,8 +26,7 @@ because async generators were added to the language in 3.6. # Versioning scheme -`typing_extensions` and `typing` are usually released together using the same version -numbers. The version number indicates the version of the standard library `typing` +The version number of `typing_extensions` indicates the version of the standard library `typing` module that is reflected in the backport. For example, `typing_extensions` version 3.10.0.0 includes features from the Python 3.10.0 standard library's `typing` module. A new release that doesn't include any new standard library features would be called @@ -45,25 +34,7 @@ new release that doesn't include any new standard library features would be call # Workflow for PyPI releases -- Do this for both `typing` and `typing_extensions` - -- Run tests under all supported versions. As of April 2021 this includes 2.7, 3.4, 3.5, - 3.6, 3.7, 3.8, 3.9. - -- On macOS, you can use `pyenv `\_ to manage multiple - Python installations. Long story short: - - - `xcode-select --install` - - `brew install pyenv` - - `echo 'eval "$(pyenv init -)"' >> ~/.bash_profile` - - Open a new shell - - `pyenv install 3.5.3` - - `pyenv install 3.4.6` - - (assuming you already have 2.7.13 and 3.6.1 from Homebrew) - - `pyenv global system 3.5.3 3.4.6` - - (or some more recent versions) - -- You can use `tox` to automate running tests. +- Ensure that GitHub Actions reports no errors. - Update the version number in `setup.py`. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 3dc64dfeb..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,7 +0,0 @@ -include LICENSE README.rst -include src/typing.py -include src/test_typing.py -include src/mod_generics_cache.py -include python2/typing.py -include python2/test_typing.py -include python2/mod_generics_cache.py diff --git a/README.md b/README.md index 860cc5678..64b91182b 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,7 @@ repository contain some historic discussions. Repository Content ------------------ -This GitHub repo is used for several things: - -- A backport of the `typing` module for older Python versions (2.7 and - 3.4) is maintained in the [src directory](./src). - Note that the canonical source lives - [upstream](https://github.com/python/cpython/blob/master/Lib/typing.py) - in the CPython repo. +This GitHub repository is used for several things: - The `typing_extensions` module lives in the [typing\_extensions](./typing_extensions) directory. @@ -36,11 +30,12 @@ This GitHub repo is used for several things: - A [discussion forum](https://github.com/python/typing/discussions) for typing-related user help is hosted here. +Historically, this repository hosted a backport of the +[`typing` module](https://docs.python.org/3/library/typing.html) for older +Python versions. The last released version, supporting Python 2.7 and 3.4, +is [available at PyPI](https://pypi.org/project/typing/). + Workflow -------- -* See [CONTRIBUTING.md](/CONTRIBUTING.md) for more. - -* The typing.py module and its unittests are edited in the `src` - subdirectory of this repo. The `python2` subdirectory contains the - Python 2 backport. +See [CONTRIBUTING.md](/CONTRIBUTING.md) for more. diff --git a/python2/mod_generics_cache.py b/python2/mod_generics_cache.py deleted file mode 100644 index d9a60b4b2..000000000 --- a/python2/mod_generics_cache.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Module for testing the behavior of generics across different modules.""" - -from typing import TypeVar, Generic - -T = TypeVar('T') - - -class A(Generic[T]): - pass - - -class B(Generic[T]): - class A(Generic[T]): - pass diff --git a/python2/test_typing.py b/python2/test_typing.py deleted file mode 100644 index 95677c9ea..000000000 --- a/python2/test_typing.py +++ /dev/null @@ -1,2706 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import collections -import contextlib -import os -import pickle -import re -import subprocess -import sys -import abc -import types -from unittest import TestCase, main, SkipTest -from copy import copy, deepcopy - -from typing import Any, NoReturn -from typing import TypeVar, AnyStr -from typing import T, KT, VT # Not in __all__. -from typing import Union, Optional -from typing import Tuple, List, MutableMapping -from typing import Callable -from typing import Generic, ClassVar, GenericMeta, Final, Literal -from typing import cast -from typing import Type, Protocol, runtime_checkable -from typing import NewType -from typing import NamedTuple, TypedDict -from typing import Pattern, Match -import typing -import weakref -import collections - - -class BaseTestCase(TestCase): - - def assertIsSubclass(self, cls, class_or_tuple, msg=None): - if not issubclass(cls, class_or_tuple): - message = '%r is not a subclass of %r' % (cls, class_or_tuple) - if msg is not None: - message += ' : %s' % msg - raise self.failureException(message) - - def assertNotIsSubclass(self, cls, class_or_tuple, msg=None): - if issubclass(cls, class_or_tuple): - message = '%r is a subclass of %r' % (cls, class_or_tuple) - if msg is not None: - message += ' : %s' % msg - raise self.failureException(message) - - def clear_caches(self): - for f in typing._cleanups: - f() - - -class Employee(object): - pass - - -class Manager(Employee): - pass - - -class Founder(Employee): - pass - - -class ManagingFounder(Manager, Founder): - pass - - -class AnyTests(BaseTestCase): - - def test_any_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(42, Any) - - def test_any_subclass_type_error(self): - with self.assertRaises(TypeError): - issubclass(Employee, Any) - with self.assertRaises(TypeError): - issubclass(Any, Employee) - - def test_repr(self): - self.assertEqual(repr(Any), 'typing.Any') - - def test_errors(self): - with self.assertRaises(TypeError): - issubclass(42, Any) - with self.assertRaises(TypeError): - Any[int] # Any is not a generic type. - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class A(Any): - pass - with self.assertRaises(TypeError): - class A(type(Any)): - pass - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - Any() - with self.assertRaises(TypeError): - type(Any)() - - def test_any_is_subclass(self): - # These expressions must simply not fail. - typing.Match[Any] - typing.Pattern[Any] - typing.IO[Any] - - -class NoReturnTests(BaseTestCase): - - def test_noreturn_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(42, NoReturn) - - def test_noreturn_subclass_type_error(self): - with self.assertRaises(TypeError): - issubclass(Employee, NoReturn) - with self.assertRaises(TypeError): - issubclass(NoReturn, Employee) - - def test_repr(self): - self.assertEqual(repr(NoReturn), 'typing.NoReturn') - - def test_not_generic(self): - with self.assertRaises(TypeError): - NoReturn[int] - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class A(NoReturn): - pass - with self.assertRaises(TypeError): - class A(type(NoReturn)): - pass - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - NoReturn() - with self.assertRaises(TypeError): - type(NoReturn)() - - -class TypeVarTests(BaseTestCase): - - def test_basic_plain(self): - T = TypeVar('T') - # T equals itself. - self.assertEqual(T, T) - # T is an instance of TypeVar - self.assertIsInstance(T, TypeVar) - - def test_typevar_instance_type_error(self): - T = TypeVar('T') - with self.assertRaises(TypeError): - isinstance(42, T) - - def test_typevar_subclass_type_error(self): - T = TypeVar('T') - with self.assertRaises(TypeError): - issubclass(int, T) - with self.assertRaises(TypeError): - issubclass(T, int) - - def test_constrained_error(self): - with self.assertRaises(TypeError): - X = TypeVar('X', int) - X - - def test_union_unique(self): - X = TypeVar('X') - Y = TypeVar('Y') - self.assertNotEqual(X, Y) - self.assertEqual(Union[X], X) - self.assertNotEqual(Union[X], Union[X, Y]) - self.assertEqual(Union[X, X], X) - self.assertNotEqual(Union[X, int], Union[X]) - self.assertNotEqual(Union[X, int], Union[int]) - self.assertEqual(Union[X, int].__args__, (X, int)) - self.assertEqual(Union[X, int].__parameters__, (X,)) - self.assertIs(Union[X, int].__origin__, Union) - - def test_union_constrained(self): - A = TypeVar('A', str, bytes) - self.assertNotEqual(Union[A, str], Union[A]) - - def test_repr(self): - self.assertEqual(repr(T), '~T') - self.assertEqual(repr(KT), '~KT') - self.assertEqual(repr(VT), '~VT') - self.assertEqual(repr(AnyStr), '~AnyStr') - T_co = TypeVar('T_co', covariant=True) - self.assertEqual(repr(T_co), '+T_co') - T_contra = TypeVar('T_contra', contravariant=True) - self.assertEqual(repr(T_contra), '-T_contra') - - def test_no_redefinition(self): - self.assertNotEqual(TypeVar('T'), TypeVar('T')) - self.assertNotEqual(TypeVar('T', int, str), TypeVar('T', int, str)) - - def test_cannot_subclass_vars(self): - with self.assertRaises(TypeError): - class V(TypeVar('T')): - pass - - def test_cannot_subclass_var_itself(self): - with self.assertRaises(TypeError): - class V(TypeVar): - pass - - def test_cannot_instantiate_vars(self): - with self.assertRaises(TypeError): - TypeVar('A')() - - def test_bound_errors(self): - with self.assertRaises(TypeError): - TypeVar('X', bound=42) - with self.assertRaises(TypeError): - TypeVar('X', str, float, bound=Employee) - - def test_no_bivariant(self): - with self.assertRaises(ValueError): - TypeVar('T', covariant=True, contravariant=True) - - -class UnionTests(BaseTestCase): - - def test_basics(self): - u = Union[int, float] - self.assertNotEqual(u, Union) - - def test_subclass_error(self): - with self.assertRaises(TypeError): - issubclass(int, Union) - with self.assertRaises(TypeError): - issubclass(Union, int) - with self.assertRaises(TypeError): - issubclass(int, Union[int, str]) - with self.assertRaises(TypeError): - issubclass(Union[int, str], int) - - def test_union_any(self): - u = Union[Any] - self.assertEqual(u, Any) - u1 = Union[int, Any] - u2 = Union[Any, int] - u3 = Union[Any, object] - self.assertEqual(u1, u2) - self.assertNotEqual(u1, Any) - self.assertNotEqual(u2, Any) - self.assertNotEqual(u3, Any) - - def test_union_object(self): - u = Union[object] - self.assertEqual(u, object) - u = Union[int, object] - self.assertEqual(u, object) - u = Union[object, int] - self.assertEqual(u, object) - - def test_unordered(self): - u1 = Union[int, float] - u2 = Union[float, int] - self.assertEqual(u1, u2) - - def test_single_class_disappears(self): - t = Union[Employee] - self.assertIs(t, Employee) - - def test_base_class_disappears(self): - u = Union[Employee, Manager, int] - self.assertEqual(u, Union[int, Employee]) - u = Union[Manager, int, Employee] - self.assertEqual(u, Union[int, Employee]) - u = Union[Employee, Manager] - self.assertIs(u, Employee) - - def test_union_union(self): - u = Union[int, float] - v = Union[u, Employee] - self.assertEqual(v, Union[int, float, Employee]) - - def test_repr(self): - self.assertEqual(repr(Union), 'typing.Union') - u = Union[Employee, int] - self.assertEqual(repr(u), 'typing.Union[%s.Employee, int]' % __name__) - u = Union[int, Employee] - self.assertEqual(repr(u), 'typing.Union[int, %s.Employee]' % __name__) - T = TypeVar('T') - u = Union[T, int][int] - self.assertEqual(repr(u), repr(int)) - u = Union[List[int], int] - self.assertEqual(repr(u), 'typing.Union[typing.List[int], int]') - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(Union): - pass - with self.assertRaises(TypeError): - class C(type(Union)): - pass - with self.assertRaises(TypeError): - class C(Union[int, str]): - pass - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - Union() - u = Union[int, float] - with self.assertRaises(TypeError): - u() - with self.assertRaises(TypeError): - type(u)() - - def test_union_generalization(self): - self.assertFalse(Union[str, typing.Iterable[int]] == str) - self.assertFalse(Union[str, typing.Iterable[int]] == typing.Iterable[int]) - self.assertTrue(Union[str, typing.Iterable] == typing.Iterable) - - def test_union_compare_other(self): - self.assertNotEqual(Union, object) - self.assertNotEqual(Union, Any) - self.assertNotEqual(ClassVar, Union) - self.assertNotEqual(Optional, Union) - self.assertNotEqual([None], Optional) - self.assertNotEqual(Optional, typing.Mapping) - self.assertNotEqual(Optional[typing.MutableMapping], Union) - - def test_optional(self): - o = Optional[int] - u = Union[int, None] - self.assertEqual(o, u) - - def test_empty(self): - with self.assertRaises(TypeError): - Union[()] - - def test_union_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(42, Union[int, str]) - - def test_no_eval_union(self): - u = Union[int, str] - self.assertIs(u._eval_type({}, {}), u) - - def test_function_repr_union(self): - def fun(): pass - self.assertEqual(repr(Union[fun, int]), 'typing.Union[fun, int]') - - def test_union_str_pattern(self): - # Shouldn't crash; see http://bugs.python.org/issue25390 - A = Union[str, Pattern] - A - - def test_etree(self): - # See https://github.com/python/typing/issues/229 - # (Only relevant for Python 2.) - try: - from xml.etree.cElementTree import Element - except ImportError: - raise SkipTest("cElementTree not found") - Union[Element, str] # Shouldn't crash - - def Elem(*args): - return Element(*args) - - Union[Elem, str] # Nor should this - - -class TupleTests(BaseTestCase): - - def test_basics(self): - with self.assertRaises(TypeError): - issubclass(Tuple, Tuple[int, str]) - with self.assertRaises(TypeError): - issubclass(tuple, Tuple[int, str]) - - class TP(tuple): pass - self.assertTrue(issubclass(tuple, Tuple)) - self.assertTrue(issubclass(TP, Tuple)) - - def test_equality(self): - self.assertEqual(Tuple[int], Tuple[int]) - self.assertEqual(Tuple[int, ...], Tuple[int, ...]) - self.assertNotEqual(Tuple[int], Tuple[int, int]) - self.assertNotEqual(Tuple[int], Tuple[int, ...]) - - def test_tuple_subclass(self): - class MyTuple(tuple): - pass - self.assertTrue(issubclass(MyTuple, Tuple)) - - def test_tuple_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance((0, 0), Tuple[int, int]) - isinstance((0, 0), Tuple) - - def test_repr(self): - self.assertEqual(repr(Tuple), 'typing.Tuple') - self.assertEqual(repr(Tuple[()]), 'typing.Tuple[()]') - self.assertEqual(repr(Tuple[int, float]), 'typing.Tuple[int, float]') - self.assertEqual(repr(Tuple[int, ...]), 'typing.Tuple[int, ...]') - - def test_errors(self): - with self.assertRaises(TypeError): - issubclass(42, Tuple) - with self.assertRaises(TypeError): - issubclass(42, Tuple[int]) - - -class CallableTests(BaseTestCase): - - def test_self_subclass(self): - with self.assertRaises(TypeError): - self.assertTrue(issubclass(type(lambda x: x), Callable[[int], int])) - self.assertTrue(issubclass(type(lambda x: x), Callable)) - - def test_eq_hash(self): - self.assertEqual(Callable[[int], int], Callable[[int], int]) - self.assertEqual(len({Callable[[int], int], Callable[[int], int]}), 1) - self.assertNotEqual(Callable[[int], int], Callable[[int], str]) - self.assertNotEqual(Callable[[int], int], Callable[[str], int]) - self.assertNotEqual(Callable[[int], int], Callable[[int, int], int]) - self.assertNotEqual(Callable[[int], int], Callable[[], int]) - self.assertNotEqual(Callable[[int], int], Callable) - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - Callable() - with self.assertRaises(TypeError): - type(Callable)() - c = Callable[[int], str] - with self.assertRaises(TypeError): - c() - with self.assertRaises(TypeError): - type(c)() - - def test_callable_wrong_forms(self): - with self.assertRaises(TypeError): - Callable[(), int] - with self.assertRaises(TypeError): - Callable[[()], int] - with self.assertRaises(TypeError): - Callable[[int, 1], 2] - with self.assertRaises(TypeError): - Callable[int] - - def test_callable_instance_works(self): - def f(): - pass - self.assertIsInstance(f, Callable) - self.assertNotIsInstance(None, Callable) - - def test_callable_instance_type_error(self): - def f(): - pass - with self.assertRaises(TypeError): - self.assertIsInstance(f, Callable[[], None]) - with self.assertRaises(TypeError): - self.assertIsInstance(f, Callable[[], Any]) - with self.assertRaises(TypeError): - self.assertNotIsInstance(None, Callable[[], None]) - with self.assertRaises(TypeError): - self.assertNotIsInstance(None, Callable[[], Any]) - - def test_repr(self): - ct0 = Callable[[], bool] - self.assertEqual(repr(ct0), 'typing.Callable[[], bool]') - ct2 = Callable[[str, float], int] - self.assertEqual(repr(ct2), 'typing.Callable[[str, float], int]') - ctv = Callable[..., str] - self.assertEqual(repr(ctv), 'typing.Callable[..., str]') - - def test_ellipsis_in_generic(self): - # Shouldn't crash; see https://github.com/python/typing/issues/259 - typing.List[Callable[..., str]] - - -XK = TypeVar('XK', unicode, bytes) -XV = TypeVar('XV') - - -class SimpleMapping(Generic[XK, XV]): - - def __getitem__(self, key): - pass - - def __setitem__(self, key, value): - pass - - def get(self, key, default=None): - pass - - -class MySimpleMapping(SimpleMapping[XK, XV]): - - def __init__(self): - self.store = {} - - def __getitem__(self, key): - return self.store[key] - - def __setitem__(self, key, value): - self.store[key] = value - - def get(self, key, default=None): - try: - return self.store[key] - except KeyError: - return default - - -class ProtocolTests(BaseTestCase): - - def test_basic_protocol(self): - @runtime_checkable - class P(Protocol): - def meth(self): - pass - class C(object): pass - class D(object): - def meth(self): - pass - def f(): - pass - self.assertIsSubclass(D, P) - self.assertIsInstance(D(), P) - self.assertNotIsSubclass(C, P) - self.assertNotIsInstance(C(), P) - self.assertNotIsSubclass(types.FunctionType, P) - self.assertNotIsInstance(f, P) - - def test_everything_implements_empty_protocol(self): - @runtime_checkable - class Empty(Protocol): pass - class C(object): pass - def f(): - pass - for thing in (object, type, tuple, C, types.FunctionType): - self.assertIsSubclass(thing, Empty) - for thing in (object(), 1, (), typing, f): - self.assertIsInstance(thing, Empty) - - def test_function_implements_protocol(self): - @runtime_checkable - class Function(Protocol): - def __call__(self, *args, **kwargs): - pass - def f(): - pass - self.assertIsInstance(f, Function) - - def test_no_inheritance_from_nominal(self): - class C(object): pass - class BP(Protocol): pass - with self.assertRaises(TypeError): - class P(C, Protocol): - pass - with self.assertRaises(TypeError): - class P(Protocol, C): - pass - with self.assertRaises(TypeError): - class P(BP, C, Protocol): - pass - class D(BP, C): pass - class E(C, BP): pass - self.assertNotIsInstance(D(), E) - self.assertNotIsInstance(E(), D) - - def test_no_instantiation(self): - class P(Protocol): pass - with self.assertRaises(TypeError): - P() - class C(P): pass - self.assertIsInstance(C(), C) - T = typing.TypeVar('T') - class PG(Protocol[T]): pass - with self.assertRaises(TypeError): - PG() - with self.assertRaises(TypeError): - PG[int]() - with self.assertRaises(TypeError): - PG[T]() - class CG(PG[T]): pass - self.assertIsInstance(CG[int](), CG) - - def test_cannot_instantiate_abstract(self): - @runtime_checkable - class P(Protocol): - @abc.abstractmethod - def ameth(self): - raise NotImplementedError - class B(P): - pass - class C(B): - def ameth(self): - return 26 - with self.assertRaises(TypeError): - B() - self.assertIsInstance(C(), P) - - def test_subprotocols_extending(self): - class P1(Protocol): - def meth1(self): - pass - @runtime_checkable - class P2(P1, Protocol): - def meth2(self): - pass - class C(object): - def meth1(self): - pass - def meth2(self): - pass - class C1(object): - def meth1(self): - pass - class C2(object): - def meth2(self): - pass - self.assertNotIsInstance(C1(), P2) - self.assertNotIsInstance(C2(), P2) - self.assertNotIsSubclass(C1, P2) - self.assertNotIsSubclass(C2, P2) - self.assertIsInstance(C(), P2) - self.assertIsSubclass(C, P2) - - def test_subprotocols_merging(self): - class P1(Protocol): - def meth1(self): - pass - class P2(Protocol): - def meth2(self): - pass - @runtime_checkable - class P(P1, P2, Protocol): - pass - class C(object): - def meth1(self): - pass - def meth2(self): - pass - class C1(object): - def meth1(self): - pass - class C2(object): - def meth2(self): - pass - self.assertNotIsInstance(C1(), P) - self.assertNotIsInstance(C2(), P) - self.assertNotIsSubclass(C1, P) - self.assertNotIsSubclass(C2, P) - self.assertIsInstance(C(), P) - self.assertIsSubclass(C, P) - - def test_protocols_issubclass(self): - T = typing.TypeVar('T') - @runtime_checkable - class P(Protocol): - def x(self): pass - @runtime_checkable - class PG(Protocol[T]): - def x(self): pass - class BadP(Protocol): - def x(self): pass - class BadPG(Protocol[T]): - def x(self): pass - class C(object): - def x(self): pass - self.assertIsSubclass(C, P) - self.assertIsSubclass(C, PG) - self.assertIsSubclass(BadP, PG) - self.assertIsSubclass(PG[int], PG) - self.assertIsSubclass(BadPG[int], P) - self.assertIsSubclass(BadPG[T], PG) - with self.assertRaises(TypeError): - issubclass(C, PG[T]) - with self.assertRaises(TypeError): - issubclass(C, PG[C]) - with self.assertRaises(TypeError): - issubclass(C, BadP) - with self.assertRaises(TypeError): - issubclass(C, BadPG) - with self.assertRaises(TypeError): - issubclass(P, PG[T]) - with self.assertRaises(TypeError): - issubclass(PG, PG[int]) - - def test_protocols_issubclass_non_callable(self): - class C(object): - x = 1 - @runtime_checkable - class PNonCall(Protocol): - x = 1 - with self.assertRaises(TypeError): - issubclass(C, PNonCall) - self.assertIsInstance(C(), PNonCall) - PNonCall.register(C) - with self.assertRaises(TypeError): - issubclass(C, PNonCall) - self.assertIsInstance(C(), PNonCall) - # check that non-protocol subclasses are not affected - class D(PNonCall): pass - self.assertNotIsSubclass(C, D) - self.assertNotIsInstance(C(), D) - D.register(C) - self.assertIsSubclass(C, D) - self.assertIsInstance(C(), D) - with self.assertRaises(TypeError): - issubclass(D, PNonCall) - - def test_protocols_isinstance(self): - T = typing.TypeVar('T') - @runtime_checkable - class P(Protocol): - def meth(x): pass - @runtime_checkable - class PG(Protocol[T]): - def meth(x): pass - class BadP(Protocol): - def meth(x): pass - class BadPG(Protocol[T]): - def meth(x): pass - class C(object): - def meth(x): pass - self.assertIsInstance(C(), P) - self.assertIsInstance(C(), PG) - with self.assertRaises(TypeError): - isinstance(C(), PG[T]) - with self.assertRaises(TypeError): - isinstance(C(), PG[C]) - with self.assertRaises(TypeError): - isinstance(C(), BadP) - with self.assertRaises(TypeError): - isinstance(C(), BadPG) - - def test_protocols_isinstance_init(self): - T = typing.TypeVar('T') - @runtime_checkable - class P(Protocol): - x = 1 - @runtime_checkable - class PG(Protocol[T]): - x = 1 - class C(object): - def __init__(self, x): - self.x = x - self.assertIsInstance(C(1), P) - self.assertIsInstance(C(1), PG) - - def test_protocol_checks_after_subscript(self): - class P(Protocol[T]): pass - class C(P[T]): pass - class Old1: pass - class New1(object): pass - class Old2: pass - class New2(object): pass - CA = C[Any] # noqa - - self.assertNotIsInstance(Old1(), C) - self.assertNotIsInstance(New1(), C) - self.assertNotIsSubclass(Old2, C) - self.assertNotIsSubclass(New2, C) - - class D1(C[Any]): pass - class D2(C[Any]): pass - CI = C[int] # noqa - - self.assertIsInstance(D1(), C) - self.assertIsSubclass(D2, C) - - def test_protocols_support_register(self): - @runtime_checkable - class P(Protocol): - x = 1 - class PM(Protocol): - def meth(self): pass - class D(PM): pass - class C(object): pass - D.register(C) - P.register(C) - self.assertIsInstance(C(), P) - self.assertIsInstance(C(), D) - - def test_none_on_non_callable_doesnt_block_implementation(self): - @runtime_checkable - class P(Protocol): - x = 1 - class A(object): - x = 1 - class B(A): - x = None - class C(object): - def __init__(self): - self.x = None - self.assertIsInstance(B(), P) - self.assertIsInstance(C(), P) - - def test_none_on_callable_blocks_implementation(self): - @runtime_checkable - class P(Protocol): - def x(self): pass - class A(object): - def x(self): pass - class B(A): - x = None - class C(object): - def __init__(self): - self.x = None - self.assertNotIsInstance(B(), P) - self.assertNotIsInstance(C(), P) - - def test_non_protocol_subclasses(self): - class P(Protocol): - x = 1 - @runtime_checkable - class PR(Protocol): - def meth(self): pass - class NonP(P): - x = 1 - class NonPR(PR): pass - class C(object): - x = 1 - class D(object): - def meth(self): pass - self.assertNotIsInstance(C(), NonP) - self.assertNotIsInstance(D(), NonPR) - self.assertNotIsSubclass(C, NonP) - self.assertNotIsSubclass(D, NonPR) - self.assertIsInstance(NonPR(), PR) - self.assertIsSubclass(NonPR, PR) - - def test_custom_subclasshook(self): - class P(Protocol): - x = 1 - class OKClass(object): pass - class BadClass(object): - x = 1 - class C(P): - @classmethod - def __subclasshook__(cls, other): - return other.__name__.startswith("OK") - self.assertIsInstance(OKClass(), C) - self.assertNotIsInstance(BadClass(), C) - self.assertIsSubclass(OKClass, C) - self.assertNotIsSubclass(BadClass, C) - - def test_issubclass_fails_correctly(self): - @runtime_checkable - class P(Protocol): - x = 1 - class C: pass - with self.assertRaises(TypeError): - issubclass(C(), P) - - def test_defining_generic_protocols(self): - T = typing.TypeVar('T') - S = typing.TypeVar('S') - @runtime_checkable - class PR(Protocol[T, S]): - def meth(self): pass - class P(PR[int, T], Protocol[T]): - y = 1 - self.assertIsSubclass(PR[int, T], PR) - self.assertIsSubclass(P[str], PR) - with self.assertRaises(TypeError): - PR[int] - with self.assertRaises(TypeError): - P[int, str] - with self.assertRaises(TypeError): - PR[int, 1] - with self.assertRaises(TypeError): - PR[int, ClassVar] - class C(PR[int, T]): pass - self.assertIsInstance(C[str](), C) - - def test_defining_generic_protocols_old_style(self): - T = typing.TypeVar('T') - S = typing.TypeVar('S') - @runtime_checkable - class PR(Protocol, typing.Generic[T, S]): - def meth(self): pass - class P(PR[int, str], Protocol): - y = 1 - self.assertIsSubclass(PR[int, str], PR) - self.assertIsSubclass(P, PR) - with self.assertRaises(TypeError): - PR[int] - with self.assertRaises(TypeError): - PR[int, 1] - class P1(Protocol, typing.Generic[T]): - def bar(self, x): pass - class P2(typing.Generic[T], Protocol): - def bar(self, x): pass - @runtime_checkable - class PSub(P1[str], Protocol): - x = 1 - class Test(object): - x = 1 - def bar(self, x): - return x - self.assertIsInstance(Test(), PSub) - with self.assertRaises(TypeError): - PR[int, ClassVar] - - def test_init_called(self): - T = typing.TypeVar('T') - class P(Protocol[T]): pass - class C(P[T]): - def __init__(self): - self.test = 'OK' - self.assertEqual(C[int]().test, 'OK') - - def test_protocols_bad_subscripts(self): - T = typing.TypeVar('T') - S = typing.TypeVar('S') - with self.assertRaises(TypeError): - class P(Protocol[T, T]): pass - with self.assertRaises(TypeError): - class P(Protocol[int]): pass - with self.assertRaises(TypeError): - class P(Protocol[T], Protocol[S]): pass - with self.assertRaises(TypeError): - class P(Protocol[T], typing.Mapping[T, S]): pass - - def test_generic_protocols_repr(self): - T = typing.TypeVar('T') - S = typing.TypeVar('S') - class P(Protocol[T, S]): pass - self.assertTrue(repr(P).endswith('P')) - self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) - self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) - - def test_generic_protocols_eq(self): - T = typing.TypeVar('T') - S = typing.TypeVar('S') - class P(Protocol[T, S]): pass - self.assertEqual(P, P) - self.assertEqual(P[int, T], P[int, T]) - self.assertEqual(P[T, T][typing.Tuple[T, S]][int, str], - P[typing.Tuple[int, str], typing.Tuple[int, str]]) - - def test_generic_protocols_special_from_generic(self): - T = typing.TypeVar('T') - class P(Protocol[T]): pass - self.assertEqual(P.__parameters__, (T,)) - self.assertIs(P.__args__, None) - self.assertIs(P.__origin__, None) - self.assertEqual(P[int].__parameters__, ()) - self.assertEqual(P[int].__args__, (int,)) - self.assertIs(P[int].__origin__, P) - - def test_generic_protocols_special_from_protocol(self): - @runtime_checkable - class PR(Protocol): - x = 1 - class P(Protocol): - def meth(self): - pass - T = typing.TypeVar('T') - class PG(Protocol[T]): - x = 1 - def meth(self): - pass - self.assertTrue(P._is_protocol) - self.assertTrue(PR._is_protocol) - self.assertTrue(PG._is_protocol) - with self.assertRaises(AttributeError): - self.assertFalse(P._is_runtime_protocol) - self.assertTrue(PR._is_runtime_protocol) - self.assertTrue(PG[int]._is_protocol) - self.assertEqual(P._get_protocol_attrs(), {'meth'}) - self.assertEqual(PR._get_protocol_attrs(), {'x'}) - self.assertEqual(frozenset(PG._get_protocol_attrs()), - frozenset({'x', 'meth'})) - self.assertEqual(frozenset(PG[int]._get_protocol_attrs()), - frozenset({'x', 'meth'})) - - def test_no_runtime_deco_on_nominal(self): - with self.assertRaises(TypeError): - @runtime_checkable - class C(object): pass - class Proto(Protocol): - x = 1 - with self.assertRaises(TypeError): - @runtime_checkable - class Concrete(Proto): - pass - - def test_none_treated_correctly(self): - @runtime_checkable - class P(Protocol): - x = None # type: int - class B(object): pass - self.assertNotIsInstance(B(), P) - class C(object): - x = 1 - class D(object): - x = None - self.assertIsInstance(C(), P) - self.assertIsInstance(D(), P) - class CI(object): - def __init__(self): - self.x = 1 - class DI(object): - def __init__(self): - self.x = None - self.assertIsInstance(C(), P) - self.assertIsInstance(D(), P) - - def test_protocols_in_unions(self): - class P(Protocol): - x = None # type: int - Alias = typing.Union[typing.Iterable, P] - Alias2 = typing.Union[P, typing.Iterable] - self.assertEqual(Alias, Alias2) - - def test_protocols_pickleable(self): - global P, CP # pickle wants to reference the class by name - T = typing.TypeVar('T') - - @runtime_checkable - class P(Protocol[T]): - x = 1 - class CP(P[int]): - pass - - c = CP() - c.foo = 42 - c.bar = 'abc' - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(c, proto) - x = pickle.loads(z) - self.assertEqual(x.foo, 42) - self.assertEqual(x.bar, 'abc') - self.assertEqual(x.x, 1) - self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) - s = pickle.dumps(P) - D = pickle.loads(s) - class E(object): - x = 1 - self.assertIsInstance(E(), D) - - def test_supports_int(self): - self.assertIsSubclass(int, typing.SupportsInt) - self.assertNotIsSubclass(str, typing.SupportsInt) - - def test_supports_float(self): - self.assertIsSubclass(float, typing.SupportsFloat) - self.assertNotIsSubclass(str, typing.SupportsFloat) - - def test_supports_complex(self): - - # Note: complex itself doesn't have __complex__. - class C(object): - def __complex__(self): - return 0j - - self.assertIsSubclass(C, typing.SupportsComplex) - self.assertNotIsSubclass(str, typing.SupportsComplex) - - def test_supports_abs(self): - self.assertIsSubclass(float, typing.SupportsAbs) - self.assertIsSubclass(int, typing.SupportsAbs) - self.assertNotIsSubclass(str, typing.SupportsAbs) - - def test_reversible(self): - self.assertIsSubclass(list, typing.Reversible) - self.assertNotIsSubclass(int, typing.Reversible) - - def test_supports_index(self): - self.assertIsSubclass(int, typing.SupportsIndex) - self.assertNotIsSubclass(str, typing.SupportsIndex) - - def test_protocol_instance_works(self): - self.assertIsInstance(0, typing.SupportsAbs) - self.assertNotIsInstance('no', typing.SupportsAbs) - class C1(typing.SupportsInt): - def __int__(self): - return 42 - class C2(C1): - pass - c = C2() - self.assertIsInstance(c, C1) - - def test_collections_protocols_allowed(self): - @runtime_checkable - class Custom(collections.Iterable, Protocol): - def close(self): pass - - class A(object): pass - class B(object): - def __iter__(self): - return [] - def close(self): - return 0 - - self.assertIsSubclass(B, Custom) - self.assertNotIsSubclass(A, Custom) - - -class GenericTests(BaseTestCase): - - def test_basics(self): - X = SimpleMapping[str, Any] - self.assertEqual(X.__parameters__, ()) - with self.assertRaises(TypeError): - X[unicode] - with self.assertRaises(TypeError): - X[unicode, unicode] - Y = SimpleMapping[XK, unicode] - self.assertEqual(Y.__parameters__, (XK,)) - Y[unicode] - with self.assertRaises(TypeError): - Y[unicode, unicode] - self.assertIsSubclass(SimpleMapping[str, int], SimpleMapping) - - def test_generic_errors(self): - T = TypeVar('T') - S = TypeVar('S') - with self.assertRaises(TypeError): - Generic[T]() - with self.assertRaises(TypeError): - Generic[T][T] - with self.assertRaises(TypeError): - Generic[T][S] - with self.assertRaises(TypeError): - isinstance([], List[int]) - with self.assertRaises(TypeError): - issubclass(list, List[int]) - with self.assertRaises(TypeError): - class NewGeneric(Generic): pass - with self.assertRaises(TypeError): - class MyGeneric(Generic[T], Generic[S]): pass - with self.assertRaises(TypeError): - class MyGeneric(List[T], Generic[S]): pass - - def test_init(self): - T = TypeVar('T') - S = TypeVar('S') - with self.assertRaises(TypeError): - Generic[T, T] - with self.assertRaises(TypeError): - Generic[T, S, T] - - def test_repr(self): - self.assertEqual(repr(SimpleMapping), - __name__ + '.' + 'SimpleMapping') - self.assertEqual(repr(MySimpleMapping), - __name__ + '.' + 'MySimpleMapping') - - def test_chain_repr(self): - T = TypeVar('T') - S = TypeVar('S') - - class C(Generic[T]): - pass - - X = C[Tuple[S, T]] - self.assertEqual(X, C[Tuple[S, T]]) - self.assertNotEqual(X, C[Tuple[T, S]]) - - Y = X[T, int] - self.assertEqual(Y, X[T, int]) - self.assertNotEqual(Y, X[S, int]) - self.assertNotEqual(Y, X[T, str]) - - Z = Y[str] - self.assertEqual(Z, Y[str]) - self.assertNotEqual(Z, Y[int]) - self.assertNotEqual(Z, Y[T]) - - self.assertTrue(str(Z).endswith( - '.C[typing.Tuple[str, int]]')) - - def test_new_repr(self): - T = TypeVar('T') - U = TypeVar('U', covariant=True) - S = TypeVar('S') - - self.assertEqual(repr(List), 'typing.List') - self.assertEqual(repr(List[T]), 'typing.List[~T]') - self.assertEqual(repr(List[U]), 'typing.List[+U]') - self.assertEqual(repr(List[S][T][int]), 'typing.List[int]') - self.assertEqual(repr(List[int]), 'typing.List[int]') - - def test_new_repr_complex(self): - T = TypeVar('T') - TS = TypeVar('TS') - - self.assertEqual(repr(typing.Mapping[T, TS][TS, T]), 'typing.Mapping[~TS, ~T]') - self.assertEqual(repr(List[Tuple[T, TS]][int, T]), - 'typing.List[typing.Tuple[int, ~T]]') - self.assertEqual( - repr(List[Tuple[T, T]][List[int]]), - 'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]' - ) - - def test_new_repr_bare(self): - T = TypeVar('T') - self.assertEqual(repr(Generic[T]), 'typing.Generic[~T]') - self.assertEqual(repr(typing.Protocol[T]), 'typing.Protocol[~T]') - class C(typing.Dict[Any, Any]): pass - # this line should just work - repr(C.__mro__) - - def test_dict(self): - T = TypeVar('T') - - class B(Generic[T]): - pass - - b = B() - b.foo = 42 - self.assertEqual(b.__dict__, {'foo': 42}) - - class C(B[int]): - pass - - c = C() - c.bar = 'abc' - self.assertEqual(c.__dict__, {'bar': 'abc'}) - - def test_subscripted_generics_as_proxies(self): - T = TypeVar('T') - class C(Generic[T]): - x = 'def' - self.assertEqual(C[int].x, 'def') - self.assertEqual(C[C[int]].x, 'def') - C[C[int]].x = 'changed' - self.assertEqual(C.x, 'changed') - self.assertEqual(C[str].x, 'changed') - C[List[str]].z = 'new' - self.assertEqual(C.z, 'new') - self.assertEqual(C[Tuple[int]].z, 'new') - - self.assertEqual(C().x, 'changed') - self.assertEqual(C[Tuple[str]]().z, 'new') - - class D(C[T]): - pass - self.assertEqual(D[int].x, 'changed') - self.assertEqual(D.z, 'new') - D.z = 'from derived z' - D[int].x = 'from derived x' - self.assertEqual(C.x, 'changed') - self.assertEqual(C[int].z, 'new') - self.assertEqual(D.x, 'from derived x') - self.assertEqual(D[str].z, 'from derived z') - - def test_abc_registry_kept(self): - T = TypeVar('T') - class C(Generic[T]): pass - C.register(int) - self.assertIsInstance(1, C) - C[int] - self.assertIsInstance(1, C) - - def test_false_subclasses(self): - class MyMapping(MutableMapping[str, str]): pass - self.assertNotIsInstance({}, MyMapping) - self.assertNotIsSubclass(dict, MyMapping) - - def test_abc_bases(self): - class MM(MutableMapping[str, str]): - def __getitem__(self, k): - return None - def __setitem__(self, k, v): - pass - def __delitem__(self, k): - pass - def __iter__(self): - return iter(()) - def __len__(self): - return 0 - # this should just work - MM().update() - self.assertIsInstance(MM(), collections.MutableMapping) - self.assertIsInstance(MM(), MutableMapping) - self.assertNotIsInstance(MM(), List) - self.assertNotIsInstance({}, MM) - - def test_multiple_bases(self): - class MM1(MutableMapping[str, str], collections.MutableMapping): - pass - with self.assertRaises(TypeError): - # consistent MRO not possible - class MM2(collections.MutableMapping, MutableMapping[str, str]): - pass - - def test_orig_bases(self): - T = TypeVar('T') - class C(typing.Dict[str, T]): pass - self.assertEqual(C.__orig_bases__, (typing.Dict[str, T],)) - - def test_naive_runtime_checks(self): - def naive_dict_check(obj, tp): - # Check if a dictionary conforms to Dict type - if len(tp.__parameters__) > 0: - raise NotImplementedError - if tp.__args__: - KT, VT = tp.__args__ - return all( - isinstance(k, KT) and isinstance(v, VT) - for k, v in obj.items() - ) - self.assertTrue(naive_dict_check({'x': 1}, typing.Dict[typing.Text, int])) - self.assertFalse(naive_dict_check({1: 'x'}, typing.Dict[typing.Text, int])) - with self.assertRaises(NotImplementedError): - naive_dict_check({1: 'x'}, typing.Dict[typing.Text, T]) - - def naive_generic_check(obj, tp): - # Check if an instance conforms to the generic class - if not hasattr(obj, '__orig_class__'): - raise NotImplementedError - return obj.__orig_class__ == tp - class Node(Generic[T]): pass - self.assertTrue(naive_generic_check(Node[int](), Node[int])) - self.assertFalse(naive_generic_check(Node[str](), Node[int])) - self.assertFalse(naive_generic_check(Node[str](), List)) - with self.assertRaises(NotImplementedError): - naive_generic_check([1, 2, 3], Node[int]) - - def naive_list_base_check(obj, tp): - # Check if list conforms to a List subclass - return all(isinstance(x, tp.__orig_bases__[0].__args__[0]) - for x in obj) - class C(List[int]): pass - self.assertTrue(naive_list_base_check([1, 2, 3], C)) - self.assertFalse(naive_list_base_check(['a', 'b'], C)) - - def test_multi_subscr_base(self): - T = TypeVar('T') - U = TypeVar('U') - V = TypeVar('V') - class C(List[T][U][V]): pass - class D(C, List[T][U][V]): pass - self.assertEqual(C.__parameters__, (V,)) - self.assertEqual(D.__parameters__, (V,)) - self.assertEqual(C[int].__parameters__, ()) - self.assertEqual(D[int].__parameters__, ()) - self.assertEqual(C[int].__args__, (int,)) - self.assertEqual(D[int].__args__, (int,)) - self.assertEqual(C.__bases__, (List,)) - self.assertEqual(D.__bases__, (C, List)) - self.assertEqual(C.__orig_bases__, (List[T][U][V],)) - self.assertEqual(D.__orig_bases__, (C, List[T][U][V])) - - def test_subscript_meta(self): - T = TypeVar('T') - self.assertEqual(Type[GenericMeta], Type[GenericMeta]) - self.assertEqual(Union[T, int][GenericMeta], Union[GenericMeta, int]) - self.assertEqual(Callable[..., GenericMeta].__args__, (Ellipsis, GenericMeta)) - - def test_generic_hashes(self): - import mod_generics_cache - class A(Generic[T]): - __module__ = 'test_typing' - - class B(Generic[T]): - class A(Generic[T]): - pass - - self.assertEqual(A, A) - self.assertEqual(mod_generics_cache.A[str], mod_generics_cache.A[str]) - self.assertEqual(B.A, B.A) - self.assertEqual(mod_generics_cache.B.A[B.A[str]], - mod_generics_cache.B.A[B.A[str]]) - - self.assertNotEqual(A, B.A) - self.assertNotEqual(A, mod_generics_cache.A) - self.assertNotEqual(A, mod_generics_cache.B.A) - self.assertNotEqual(B.A, mod_generics_cache.A) - self.assertNotEqual(B.A, mod_generics_cache.B.A) - - self.assertNotEqual(A[str], B.A[str]) - self.assertNotEqual(A[List[Any]], B.A[List[Any]]) - self.assertNotEqual(A[str], mod_generics_cache.A[str]) - self.assertNotEqual(A[str], mod_generics_cache.B.A[str]) - self.assertNotEqual(B.A[int], mod_generics_cache.A[int]) - self.assertNotEqual(B.A[List[Any]], mod_generics_cache.B.A[List[Any]]) - - self.assertNotEqual(Tuple[A[str]], Tuple[B.A[str]]) - self.assertNotEqual(Tuple[A[List[Any]]], Tuple[B.A[List[Any]]]) - self.assertNotEqual(Union[str, A[str]], Union[str, mod_generics_cache.A[str]]) - self.assertNotEqual(Union[A[str], A[str]], - Union[A[str], mod_generics_cache.A[str]]) - self.assertNotEqual(typing.FrozenSet[A[str]], - typing.FrozenSet[mod_generics_cache.B.A[str]]) - - self.assertTrue(repr(Tuple[A[str]]).endswith('test_typing.A[str]]')) - self.assertTrue(repr(Tuple[mod_generics_cache.A[str]]) - .endswith('mod_generics_cache.A[str]]')) - - def test_extended_generic_rules_eq(self): - T = TypeVar('T') - U = TypeVar('U') - self.assertEqual(Tuple[T, T][int], Tuple[int, int]) - self.assertEqual(typing.Iterable[Tuple[T, T]][T], typing.Iterable[Tuple[T, T]]) - with self.assertRaises(TypeError): - Tuple[T, int][()] - with self.assertRaises(TypeError): - Tuple[T, U][T, ...] - - self.assertEqual(Union[T, int][int], int) - self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str]) - class Base(object): pass - class Derived(Base): pass - self.assertEqual(Union[T, Base][Derived], Base) - with self.assertRaises(TypeError): - Union[T, int][1] - - self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT]) - self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]]) - with self.assertRaises(TypeError): - Callable[[T], U][..., int] - with self.assertRaises(TypeError): - Callable[[T], U][[], int] - - def test_extended_generic_rules_repr(self): - T = TypeVar('T') - self.assertEqual(repr(Union[Tuple, Callable]).replace('typing.', ''), - 'Union[Tuple, Callable]') - self.assertEqual(repr(Union[Tuple, Tuple[int]]).replace('typing.', ''), - 'Tuple') - self.assertEqual(repr(Callable[..., Optional[T]][int]).replace('typing.', ''), - 'Callable[..., Union[int, NoneType]]') - self.assertEqual(repr(Callable[[], List[T]][int]).replace('typing.', ''), - 'Callable[[], List[int]]') - - def test_generic_forvard_ref(self): - LLT = List[List['CC']] - class CC: pass - self.assertEqual(typing._eval_type(LLT, globals(), locals()), List[List[CC]]) - T = TypeVar('T') - AT = Tuple[T, ...] - self.assertIs(typing._eval_type(AT, globals(), locals()), AT) - CT = Callable[..., List[T]] - self.assertIs(typing._eval_type(CT, globals(), locals()), CT) - - def test_extended_generic_rules_subclassing(self): - class T1(Tuple[T, KT]): pass - class T2(Tuple[T, ...]): pass - class C1(Callable[[T], T]): pass - class C2(Callable[..., int]): - def __call__(self): - return None - - self.assertEqual(T1.__parameters__, (T, KT)) - self.assertEqual(T1[int, str].__args__, (int, str)) - self.assertEqual(T1[int, T].__origin__, T1) - - self.assertEqual(T2.__parameters__, (T,)) - with self.assertRaises(TypeError): - T1[int] - with self.assertRaises(TypeError): - T2[int, str] - - self.assertEqual(repr(C1[int]).split('.')[-1], 'C1[int]') - self.assertEqual(C2.__parameters__, ()) - self.assertIsInstance(C2(), collections.Callable) - self.assertIsSubclass(C2, collections.Callable) - self.assertIsSubclass(C1, collections.Callable) - self.assertIsInstance(T1(), tuple) - self.assertIsSubclass(T2, tuple) - self.assertIsSubclass(Tuple[int, ...], typing.Sequence) - self.assertIsSubclass(Tuple[int, ...], typing.Iterable) - - def test_fail_with_bare_union(self): - with self.assertRaises(TypeError): - List[Union] - with self.assertRaises(TypeError): - Tuple[Optional] - with self.assertRaises(TypeError): - ClassVar[ClassVar] - with self.assertRaises(TypeError): - List[ClassVar[int]] - - def test_fail_with_bare_generic(self): - T = TypeVar('T') - with self.assertRaises(TypeError): - List[Generic] - with self.assertRaises(TypeError): - Tuple[Generic[T]] - with self.assertRaises(TypeError): - List[typing.Protocol] - with self.assertRaises(TypeError): - isinstance(1, Generic) - - def test_type_erasure_special(self): - T = TypeVar('T') - # this is the only test that checks type caching - self.clear_caches() - class MyTup(Tuple[T, T]): pass - self.assertIs(MyTup[int]().__class__, MyTup) - self.assertIs(MyTup[int]().__orig_class__, MyTup[int]) - class MyCall(Callable[..., T]): - def __call__(self): return None - self.assertIs(MyCall[T]().__class__, MyCall) - self.assertIs(MyCall[T]().__orig_class__, MyCall[T]) - class MyDict(typing.Dict[T, T]): pass - self.assertIs(MyDict[int]().__class__, MyDict) - self.assertIs(MyDict[int]().__orig_class__, MyDict[int]) - class MyDef(typing.DefaultDict[str, T]): pass - self.assertIs(MyDef[int]().__class__, MyDef) - self.assertIs(MyDef[int]().__orig_class__, MyDef[int]) - - def test_all_repr_eq_any(self): - objs = (getattr(typing, el) for el in typing.__all__) - for obj in objs: - self.assertNotEqual(repr(obj), '') - self.assertEqual(obj, obj) - if getattr(obj, '__parameters__', None) and len(obj.__parameters__) == 1: - self.assertEqual(obj[Any].__args__, (Any,)) - if isinstance(obj, type): - for base in obj.__mro__: - self.assertNotEqual(repr(base), '') - self.assertEqual(base, base) - - def test_pickle(self): - global C # pickle wants to reference the class by name - T = TypeVar('T') - - class B(Generic[T]): - pass - - class C(B[int]): - pass - - c = C() - c.foo = 42 - c.bar = 'abc' - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(c, proto) - x = pickle.loads(z) - self.assertEqual(x.foo, 42) - self.assertEqual(x.bar, 'abc') - self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) - simples = [Any, Union, Tuple, Callable, ClassVar, List, typing.Iterable] - for s in simples: - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(s, proto) - x = pickle.loads(z) - self.assertEqual(s, x) - - def test_copy_and_deepcopy(self): - T = TypeVar('T') - class Node(Generic[T]): pass - things = [ - Any, - Callable[..., T], - Callable[[int], int], - ClassVar[List[T]], - ClassVar[int], - List['T'], - Node[Any], - Node[T], - Node[int], - Tuple['T', 'T'], - Tuple[Any, Any], - Tuple[T, int], - Union['T', int], - Union[T, int], - typing.Dict[T, Any], - typing.Dict[int, str], - typing.Iterable[Any], - typing.Iterable[T], - typing.Iterable[int], - typing.Mapping['T', int] - ] - for t in things: - self.assertEqual(t, deepcopy(t)) - self.assertEqual(t, copy(t)) - - def test_copy_generic_instances(self): - T = TypeVar('T') - class C(Generic[T]): - def __init__(self, attr): - self.attr = attr - - c = C(42) - self.assertEqual(copy(c).attr, 42) - self.assertEqual(deepcopy(c).attr, 42) - self.assertIsNot(copy(c), c) - self.assertIsNot(deepcopy(c), c) - c.attr = 1 - self.assertEqual(copy(c).attr, 1) - self.assertEqual(deepcopy(c).attr, 1) - ci = C[int](42) - self.assertEqual(copy(ci).attr, 42) - self.assertEqual(deepcopy(ci).attr, 42) - self.assertIsNot(copy(ci), ci) - self.assertIsNot(deepcopy(ci), ci) - ci.attr = 1 - self.assertEqual(copy(ci).attr, 1) - self.assertEqual(deepcopy(ci).attr, 1) - self.assertEqual(ci.__orig_class__, C[int]) - - def test_weakref_all(self): - T = TypeVar('T') - things = [Any, Union[T, int], Callable[..., T], Tuple[Any, Any], - Optional[List[int]], typing.Mapping[int, str], - typing.re.Match[bytes], typing.Iterable['whatever']] - for t in things: - self.assertEqual(weakref.ref(t)(), t) - - def test_parameterized_slots(self): - T = TypeVar('T') - class C(Generic[T]): - __slots__ = ('potato',) - - c = C() - c_int = C[int]() - self.assertEqual(C.__slots__, C[str].__slots__) - - c.potato = 0 - c_int.potato = 0 - with self.assertRaises(AttributeError): - c.tomato = 0 - with self.assertRaises(AttributeError): - c_int.tomato = 0 - - self.assertEqual(typing._eval_type(C['C'], globals(), locals()), C[C]) - self.assertEqual(typing._eval_type(C['C'], globals(), locals()).__slots__, - C.__slots__) - self.assertEqual(copy(C[int]), deepcopy(C[int])) - - def test_parameterized_slots_dict(self): - T = TypeVar('T') - class D(Generic[T]): - __slots__ = {'banana': 42} - - d = D() - d_int = D[int]() - self.assertEqual(D.__slots__, D[str].__slots__) - - d.banana = 'yes' - d_int.banana = 'yes' - with self.assertRaises(AttributeError): - d.foobar = 'no' - with self.assertRaises(AttributeError): - d_int.foobar = 'no' - - def test_errors(self): - with self.assertRaises(TypeError): - B = SimpleMapping[XK, Any] - - class C(Generic[B]): - pass - - def test_repr_2(self): - PY32 = sys.version_info[:2] < (3, 3) - - class C(Generic[T]): - pass - - self.assertEqual(C.__module__, __name__) - if not PY32: - self.assertEqual(C.__qualname__, - 'GenericTests.test_repr_2..C') - self.assertEqual(repr(C).split('.')[-1], 'C') - X = C[int] - self.assertEqual(X.__module__, __name__) - if not PY32: - self.assertTrue(X.__qualname__.endswith('..C')) - self.assertEqual(repr(X).split('.')[-1], 'C[int]') - - class Y(C[int]): - pass - - self.assertEqual(Y.__module__, __name__) - if not PY32: - self.assertEqual(Y.__qualname__, - 'GenericTests.test_repr_2..Y') - self.assertEqual(repr(Y).split('.')[-1], 'Y') - - def test_eq_1(self): - self.assertEqual(Generic, Generic) - self.assertEqual(Generic[T], Generic[T]) - self.assertNotEqual(Generic[KT], Generic[VT]) - - def test_eq_2(self): - - class A(Generic[T]): - pass - - class B(Generic[T]): - pass - - self.assertEqual(A, A) - self.assertNotEqual(A, B) - self.assertEqual(A[T], A[T]) - self.assertNotEqual(A[T], B[T]) - - def test_multiple_inheritance(self): - - class A(Generic[T, VT]): - pass - - class B(Generic[KT, T]): - pass - - class C(A[T, VT], Generic[VT, T, KT], B[KT, T]): - pass - - self.assertEqual(C.__parameters__, (VT, T, KT)) - - def test_nested(self): - - G = Generic - - class Visitor(G[T]): - - a = None - - def set(self, a): - self.a = a - - def get(self): - return self.a - - def visit(self): - return self.a - - V = Visitor[typing.List[int]] - - class IntListVisitor(V): - - def append(self, x): - self.a.append(x) - - a = IntListVisitor() - a.set([]) - a.append(1) - a.append(42) - self.assertEqual(a.get(), [1, 42]) - - def test_type_erasure(self): - T = TypeVar('T') - - class Node(Generic[T]): - def __init__(self, label, - left=None, - right=None): - self.label = label # type: T - self.left = left # type: Optional[Node[T]] - self.right = right # type: Optional[Node[T]] - - def foo(x): - a = Node(x) - b = Node[T](x) - c = Node[Any](x) - self.assertIs(type(a), Node) - self.assertIs(type(b), Node) - self.assertIs(type(c), Node) - self.assertEqual(a.label, x) - self.assertEqual(b.label, x) - self.assertEqual(c.label, x) - - foo(42) - - def test_implicit_any(self): - T = TypeVar('T') - - class C(Generic[T]): - pass - - class D(C): - pass - - self.assertEqual(D.__parameters__, ()) - - with self.assertRaises(Exception): - D[int] - with self.assertRaises(Exception): - D[Any] - with self.assertRaises(Exception): - D[T] - - def test_new_with_args(self): - - class A(Generic[T]): - pass - - class B(object): - def __new__(cls, arg): - # call object.__new__ - obj = super(B, cls).__new__(cls) - obj.arg = arg - return obj - - # mro: C, A, Generic, B, object - class C(A, B): - pass - - c = C('foo') - self.assertEqual(c.arg, 'foo') - - def test_new_with_args2(self): - - class A(object): - def __init__(self, arg): - self.from_a = arg - # call object - super(A, self).__init__() - - # mro: C, Generic, A, object - class C(Generic[T], A): - def __init__(self, arg): - self.from_c = arg - # call Generic - super(C, self).__init__(arg) - - c = C('foo') - self.assertEqual(c.from_a, 'foo') - self.assertEqual(c.from_c, 'foo') - - def test_new_no_args(self): - - class A(Generic[T]): - pass - - with self.assertRaises(TypeError): - A('foo') - - class B(object): - def __new__(cls): - # call object - obj = super(B, cls).__new__(cls) - obj.from_b = 'b' - return obj - - # mro: C, A, Generic, B, object - class C(A, B): - def __init__(self, arg): - self.arg = arg - - def __new__(cls, arg): - # call A - obj = super(C, cls).__new__(cls) - obj.from_c = 'c' - return obj - - c = C('foo') - self.assertEqual(c.arg, 'foo') - self.assertEqual(c.from_b, 'b') - self.assertEqual(c.from_c, 'c') - - -class ClassVarTests(BaseTestCase): - - def test_basics(self): - with self.assertRaises(TypeError): - ClassVar[1] - with self.assertRaises(TypeError): - ClassVar[int, str] - with self.assertRaises(TypeError): - ClassVar[int][str] - - def test_repr(self): - self.assertEqual(repr(ClassVar), 'typing.ClassVar') - cv = ClassVar[int] - self.assertEqual(repr(cv), 'typing.ClassVar[int]') - cv = ClassVar[Employee] - self.assertEqual(repr(cv), 'typing.ClassVar[%s.Employee]' % __name__) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(ClassVar)): - pass - with self.assertRaises(TypeError): - class C(type(ClassVar[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - ClassVar() - with self.assertRaises(TypeError): - type(ClassVar)() - with self.assertRaises(TypeError): - type(ClassVar[Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, ClassVar[int]) - with self.assertRaises(TypeError): - issubclass(int, ClassVar) - - -class FinalTests(BaseTestCase): - - def test_basics(self): - with self.assertRaises(TypeError): - Final[1] - with self.assertRaises(TypeError): - Final[int, str] - with self.assertRaises(TypeError): - Final[int][str] - - def test_repr(self): - self.assertEqual(repr(Final), 'typing.Final') - cv = Final[int] - self.assertEqual(repr(cv), 'typing.Final[int]') - cv = Final[Employee] - self.assertEqual(repr(cv), 'typing.Final[%s.Employee]' % __name__) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(Final)): - pass - with self.assertRaises(TypeError): - class C(type(Final[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - Final() - with self.assertRaises(TypeError): - type(Final)() - with self.assertRaises(TypeError): - type(Final[typing.Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, Final[int]) - with self.assertRaises(TypeError): - issubclass(int, Final) - - -class LiteralTests(BaseTestCase): - def test_basics(self): - Literal[1] - Literal[1, 2, 3] - Literal["x", "y", "z"] - Literal[None] - - def test_illegal_parameters_do_not_raise_runtime_errors(self): - # Type checkers should reject these types, but we do not - # raise errors at runtime to maintain maximium flexibility - Literal[int] - Literal[Literal[1, 2], Literal[4, 5]] - Literal[3j + 2, ..., ()] - Literal[b"foo", u"bar"] - Literal[{"foo": 3, "bar": 4}] - Literal[T] - - def test_literals_inside_other_types(self): - typing.List[Literal[1, 2, 3]] - typing.List[Literal[("foo", "bar", "baz")]] - - def test_repr(self): - self.assertEqual(repr(Literal[1]), "typing.Literal[1]") - self.assertEqual(repr(Literal[1, True, "foo"]), "typing.Literal[1, True, u'foo']") - self.assertEqual(repr(Literal[int]), "typing.Literal[int]") - self.assertEqual(repr(Literal), "typing.Literal") - self.assertEqual(repr(Literal[None]), "typing.Literal[None]") - - def test_cannot_init(self): - with self.assertRaises(TypeError): - Literal() - with self.assertRaises(TypeError): - Literal[1]() - with self.assertRaises(TypeError): - type(Literal)() - with self.assertRaises(TypeError): - type(Literal[1])() - - def test_no_isinstance_or_issubclass(self): - with self.assertRaises(TypeError): - isinstance(1, Literal[1]) - with self.assertRaises(TypeError): - isinstance(int, Literal[1]) - with self.assertRaises(TypeError): - issubclass(1, Literal[1]) - with self.assertRaises(TypeError): - issubclass(int, Literal[1]) - - def test_no_subclassing(self): - with self.assertRaises(TypeError): - class Foo(Literal[1]): pass - with self.assertRaises(TypeError): - class Bar(Literal): pass - - def test_no_multiple_subscripts(self): - with self.assertRaises(TypeError): - Literal[1][1] - - -class CastTests(BaseTestCase): - - def test_basics(self): - self.assertEqual(cast(int, 42), 42) - self.assertEqual(cast(float, 42), 42) - self.assertIs(type(cast(float, 42)), int) - self.assertEqual(cast(Any, 42), 42) - self.assertEqual(cast(list, 42), 42) - self.assertEqual(cast(Union[str, float], 42), 42) - self.assertEqual(cast(AnyStr, 42), 42) - self.assertEqual(cast(None, 42), 42) - - def test_errors(self): - # Bogus calls are not expected to fail. - cast(42, 42) - cast('hello', 42) - - -class ForwardRefTests(BaseTestCase): - - def test_forwardref_instance_type_error(self): - fr = typing._ForwardRef('int') - with self.assertRaises(TypeError): - isinstance(42, fr) - - def test_syntax_error(self): - - with self.assertRaises(SyntaxError): - Generic['/T'] - - def test_forwardref_subclass_type_error(self): - fr = typing._ForwardRef('int') - with self.assertRaises(TypeError): - issubclass(int, fr) - - def test_forward_equality(self): - fr = typing._ForwardRef('int') - self.assertEqual(fr, typing._ForwardRef('int')) - self.assertNotEqual(List['int'], List[int]) - - def test_forward_repr(self): - self.assertEqual(repr(List['int']), "typing.List[_ForwardRef(%r)]" % 'int') - - -class OverloadTests(BaseTestCase): - - def test_overload_fails(self): - from typing import overload - - with self.assertRaises(RuntimeError): - - @overload - def blah(): - pass - - blah() - - def test_overload_succeeds(self): - from typing import overload - - @overload - def blah(): - pass - - def blah(): - pass - - blah() - - -class CollectionsAbcTests(BaseTestCase): - - def test_hashable(self): - self.assertIsInstance(42, typing.Hashable) - self.assertNotIsInstance([], typing.Hashable) - - def test_iterable(self): - self.assertIsInstance([], typing.Iterable) - # Due to ABC caching, the second time takes a separate code - # path and could fail. So call this a few times. - self.assertIsInstance([], typing.Iterable) - self.assertIsInstance([], typing.Iterable) - self.assertNotIsInstance(42, typing.Iterable) - # Just in case, also test issubclass() a few times. - self.assertIsSubclass(list, typing.Iterable) - self.assertIsSubclass(list, typing.Iterable) - - def test_iterator(self): - it = iter([]) - self.assertIsInstance(it, typing.Iterator) - self.assertNotIsInstance(42, typing.Iterator) - - def test_sized(self): - self.assertIsInstance([], typing.Sized) - self.assertNotIsInstance(42, typing.Sized) - - def test_container(self): - self.assertIsInstance([], typing.Container) - self.assertNotIsInstance(42, typing.Container) - - def test_abstractset(self): - self.assertIsInstance(set(), typing.AbstractSet) - self.assertNotIsInstance(42, typing.AbstractSet) - - def test_mutableset(self): - self.assertIsInstance(set(), typing.MutableSet) - self.assertNotIsInstance(frozenset(), typing.MutableSet) - - def test_mapping(self): - self.assertIsInstance({}, typing.Mapping) - self.assertNotIsInstance(42, typing.Mapping) - - def test_mutablemapping(self): - self.assertIsInstance({}, typing.MutableMapping) - self.assertNotIsInstance(42, typing.MutableMapping) - - def test_sequence(self): - self.assertIsInstance([], typing.Sequence) - self.assertNotIsInstance(42, typing.Sequence) - - def test_mutablesequence(self): - self.assertIsInstance([], typing.MutableSequence) - self.assertNotIsInstance((), typing.MutableSequence) - - def test_bytestring(self): - self.assertIsInstance(b'', typing.ByteString) - self.assertIsInstance(bytearray(b''), typing.ByteString) - - def test_list(self): - self.assertIsSubclass(list, typing.List) - - def test_deque(self): - self.assertIsSubclass(collections.deque, typing.Deque) - class MyDeque(typing.Deque[int]): pass - self.assertIsInstance(MyDeque(), collections.deque) - - def test_counter(self): - self.assertIsSubclass(collections.Counter, typing.Counter) - - def test_set(self): - self.assertIsSubclass(set, typing.Set) - self.assertNotIsSubclass(frozenset, typing.Set) - - def test_frozenset(self): - self.assertIsSubclass(frozenset, typing.FrozenSet) - self.assertNotIsSubclass(set, typing.FrozenSet) - - def test_dict(self): - self.assertIsSubclass(dict, typing.Dict) - - def test_no_list_instantiation(self): - with self.assertRaises(TypeError): - typing.List() - with self.assertRaises(TypeError): - typing.List[T]() - with self.assertRaises(TypeError): - typing.List[int]() - - def test_list_subclass(self): - - class MyList(typing.List[int]): - pass - - a = MyList() - self.assertIsInstance(a, MyList) - self.assertIsInstance(a, typing.Sequence) - - self.assertIsSubclass(MyList, list) - self.assertNotIsSubclass(list, MyList) - - def test_no_dict_instantiation(self): - with self.assertRaises(TypeError): - typing.Dict() - with self.assertRaises(TypeError): - typing.Dict[KT, VT]() - with self.assertRaises(TypeError): - typing.Dict[str, int]() - - def test_dict_subclass(self): - - class MyDict(typing.Dict[str, int]): - pass - - d = MyDict() - self.assertIsInstance(d, MyDict) - self.assertIsInstance(d, typing.MutableMapping) - - self.assertIsSubclass(MyDict, dict) - self.assertNotIsSubclass(dict, MyDict) - - def test_defaultdict_instantiation(self): - self.assertIs(type(typing.DefaultDict()), collections.defaultdict) - self.assertIs(type(typing.DefaultDict[KT, VT]()), collections.defaultdict) - self.assertIs(type(typing.DefaultDict[str, int]()), collections.defaultdict) - - def test_defaultdict_subclass(self): - - class MyDefDict(typing.DefaultDict[str, int]): - pass - - dd = MyDefDict() - self.assertIsInstance(dd, MyDefDict) - - self.assertIsSubclass(MyDefDict, collections.defaultdict) - self.assertNotIsSubclass(collections.defaultdict, MyDefDict) - - def test_deque_instantiation(self): - self.assertIs(type(typing.Deque()), collections.deque) - self.assertIs(type(typing.Deque[T]()), collections.deque) - self.assertIs(type(typing.Deque[int]()), collections.deque) - class D(typing.Deque[T]): pass - self.assertIs(type(D[int]()), D) - - def test_counter_instantiation(self): - self.assertIs(type(typing.Counter()), collections.Counter) - self.assertIs(type(typing.Counter[T]()), collections.Counter) - self.assertIs(type(typing.Counter[int]()), collections.Counter) - class C(typing.Counter[T]): pass - self.assertIs(type(C[int]()), C) - - def test_counter_subclass_instantiation(self): - - class MyCounter(typing.Counter[int]): - pass - - d = MyCounter() - self.assertIsInstance(d, MyCounter) - self.assertIsInstance(d, typing.Counter) - self.assertIsInstance(d, collections.Counter) - - def test_no_set_instantiation(self): - with self.assertRaises(TypeError): - typing.Set() - with self.assertRaises(TypeError): - typing.Set[T]() - with self.assertRaises(TypeError): - typing.Set[int]() - - def test_set_subclass_instantiation(self): - - class MySet(typing.Set[int]): - pass - - d = MySet() - self.assertIsInstance(d, MySet) - - def test_no_frozenset_instantiation(self): - with self.assertRaises(TypeError): - typing.FrozenSet() - with self.assertRaises(TypeError): - typing.FrozenSet[T]() - with self.assertRaises(TypeError): - typing.FrozenSet[int]() - - def test_frozenset_subclass_instantiation(self): - - class MyFrozenSet(typing.FrozenSet[int]): - pass - - d = MyFrozenSet() - self.assertIsInstance(d, MyFrozenSet) - - def test_no_tuple_instantiation(self): - with self.assertRaises(TypeError): - Tuple() - with self.assertRaises(TypeError): - Tuple[T]() - with self.assertRaises(TypeError): - Tuple[int]() - - def test_generator(self): - def foo(): - yield 42 - g = foo() - self.assertIsSubclass(type(g), typing.Generator) - - def test_no_generator_instantiation(self): - with self.assertRaises(TypeError): - typing.Generator() - with self.assertRaises(TypeError): - typing.Generator[T, T, T]() - with self.assertRaises(TypeError): - typing.Generator[int, int, int]() - - def test_subclassing(self): - - class MMA(typing.MutableMapping): - pass - - with self.assertRaises(TypeError): # It's abstract - MMA() - - class MMC(MMA): - def __getitem__(self, k): - return None - def __setitem__(self, k, v): - pass - def __delitem__(self, k): - pass - def __iter__(self): - return iter(()) - def __len__(self): - return 0 - - self.assertEqual(len(MMC()), 0) - assert callable(MMC.update) - self.assertIsInstance(MMC(), typing.Mapping) - - class MMB(typing.MutableMapping[KT, VT]): - def __getitem__(self, k): - return None - def __setitem__(self, k, v): - pass - def __delitem__(self, k): - pass - def __iter__(self): - return iter(()) - def __len__(self): - return 0 - - self.assertEqual(len(MMB()), 0) - self.assertEqual(len(MMB[str, str]()), 0) - self.assertEqual(len(MMB[KT, VT]()), 0) - - self.assertNotIsSubclass(dict, MMA) - self.assertNotIsSubclass(dict, MMB) - - self.assertIsSubclass(MMA, typing.Mapping) - self.assertIsSubclass(MMB, typing.Mapping) - self.assertIsSubclass(MMC, typing.Mapping) - - self.assertIsInstance(MMB[KT, VT](), typing.Mapping) - self.assertIsInstance(MMB[KT, VT](), collections.Mapping) - - self.assertIsSubclass(MMA, collections.Mapping) - self.assertIsSubclass(MMB, collections.Mapping) - self.assertIsSubclass(MMC, collections.Mapping) - - self.assertIsSubclass(MMB[str, str], typing.Mapping) - self.assertIsSubclass(MMC, MMA) - - class It(typing.Iterable): pass - self.assertNotIsSubclass(list, It) - - class G(typing.Generator[int, int, int]): pass - def g(): yield 0 - self.assertIsSubclass(G, typing.Generator) - self.assertIsSubclass(G, typing.Iterable) - if hasattr(collections, 'Generator'): - self.assertIsSubclass(G, collections.Generator) - self.assertIsSubclass(G, collections.Iterable) - self.assertNotIsSubclass(type(g), G) - - def test_subclassing_subclasshook(self): - - class Base(typing.Iterable): - @classmethod - def __subclasshook__(cls, other): - if other.__name__ == 'Foo': - return True - else: - return False - - class C(Base): pass - class Foo: pass - class Bar: pass - self.assertIsSubclass(Foo, Base) - self.assertIsSubclass(Foo, C) - self.assertNotIsSubclass(Bar, C) - - def test_subclassing_register(self): - - class A(typing.Container): pass - class B(A): pass - - class C: pass - A.register(C) - self.assertIsSubclass(C, A) - self.assertNotIsSubclass(C, B) - - class D: pass - B.register(D) - self.assertIsSubclass(D, A) - self.assertIsSubclass(D, B) - - class M(): pass - collections.MutableMapping.register(M) - self.assertIsSubclass(M, typing.Mapping) - - def test_collections_as_base(self): - - class M(collections.Mapping): pass - self.assertIsSubclass(M, typing.Mapping) - self.assertIsSubclass(M, typing.Iterable) - - class S(collections.MutableSequence): pass - self.assertIsSubclass(S, typing.MutableSequence) - self.assertIsSubclass(S, typing.Iterable) - - class It(collections.Iterable): pass - self.assertIsSubclass(It, typing.Iterable) - - class A(collections.Mapping): pass - class B: pass - A.register(B) - self.assertIsSubclass(B, typing.Mapping) - - -class OtherABCTests(BaseTestCase): - - def test_contextmanager(self): - @contextlib.contextmanager - def manager(): - yield 42 - - cm = manager() - self.assertIsInstance(cm, typing.ContextManager) - self.assertNotIsInstance(42, typing.ContextManager) - - -class TypeTests(BaseTestCase): - - def test_type_basic(self): - - class User(object): pass - class BasicUser(User): pass - class ProUser(User): pass - - def new_user(user_class): - # type: (Type[User]) -> User - return user_class() - - new_user(BasicUser) - - def test_type_typevar(self): - - class User(object): pass - class BasicUser(User): pass - class ProUser(User): pass - - global U - U = TypeVar('U', bound=User) - - def new_user(user_class): - # type: (Type[U]) -> U - return user_class() - - new_user(BasicUser) - - def test_type_optional(self): - A = Optional[Type[BaseException]] # noqa - - def foo(a): - # type: (A) -> Optional[BaseException] - if a is None: - return None - else: - return a() - - assert isinstance(foo(KeyboardInterrupt), KeyboardInterrupt) - assert foo(None) is None - - -class NewTypeTests(BaseTestCase): - - def test_basic(self): - UserId = NewType('UserId', int) - UserName = NewType('UserName', str) - self.assertIsInstance(UserId(5), int) - self.assertIsInstance(UserName('Joe'), type('Joe')) - self.assertEqual(UserId(5) + 1, 6) - - def test_errors(self): - UserId = NewType('UserId', int) - UserName = NewType('UserName', str) - with self.assertRaises(TypeError): - issubclass(UserId, int) - with self.assertRaises(TypeError): - class D(UserName): - pass - - -class NamedTupleTests(BaseTestCase): - - def test_basics(self): - Emp = NamedTuple('Emp', [('name', str), ('id', int)]) - self.assertIsSubclass(Emp, tuple) - joe = Emp('Joe', 42) - jim = Emp(name='Jim', id=1) - self.assertIsInstance(joe, Emp) - self.assertIsInstance(joe, tuple) - self.assertEqual(joe.name, 'Joe') - self.assertEqual(joe.id, 42) - self.assertEqual(jim.name, 'Jim') - self.assertEqual(jim.id, 1) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp._fields, ('name', 'id')) - self.assertEqual(Emp._field_types, dict(name=str, id=int)) - - def test_pickle(self): - global Emp # pickle wants to reference the class by name - Emp = NamedTuple('Emp', [('name', str), ('id', int)]) - jane = Emp('jane', 37) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(jane, proto) - jane2 = pickle.loads(z) - self.assertEqual(jane2, jane) - - -class TypedDictTests(BaseTestCase): - - def test_basics_iterable_syntax(self): - Emp = TypedDict(b'Emp', {'name': str, 'id': int}) - self.assertIsSubclass(Emp, dict) - self.assertIsSubclass(Emp, typing.MutableMapping) - if sys.version_info[0] >= 3: - import collections.abc - self.assertNotIsSubclass(Emp, collections.abc.Sequence) - jim = Emp(name='Jim', id=1) - self.assertIs(type(jim), dict) - self.assertEqual(jim['name'], 'Jim') - self.assertEqual(jim['id'], 1) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp.__module__, __name__) - self.assertEqual(Emp.__bases__, (dict,)) - self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) - self.assertEqual(Emp.__total__, True) - - def test_basics_keywords_syntax(self): - Emp = TypedDict(b'Emp', name=str, id=int) - self.assertIsSubclass(Emp, dict) - self.assertIsSubclass(Emp, typing.MutableMapping) - if sys.version_info[0] >= 3: - import collections.abc - self.assertNotIsSubclass(Emp, collections.abc.Sequence) - jim = Emp(name='Jim', id=1) - self.assertIs(type(jim), dict) - self.assertEqual(jim['name'], 'Jim') - self.assertEqual(jim['id'], 1) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp.__module__, __name__) - self.assertEqual(Emp.__bases__, (dict,)) - self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) - self.assertEqual(Emp.__total__, True) - - def test_typeddict_errors(self): - Emp = TypedDict(b'Emp', {'name': str, 'id': int}) - self.assertEqual(TypedDict.__module__, 'typing') - jim = Emp(name='Jim', id=1) - with self.assertRaises(TypeError): - isinstance({}, Emp) - with self.assertRaises(TypeError): - isinstance(jim, Emp) - with self.assertRaises(TypeError): - issubclass(dict, Emp) - with self.assertRaises(TypeError): - TypedDict('Hi', x=1) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int), ('y', 1)]) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int)], y=int) - - def test_pickle(self): - global EmpD # pickle wants to reference the class by name - EmpD = TypedDict(b'EmpD', name=str, id=int) - jane = EmpD({'name': 'jane', 'id': 37}) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(jane, proto) - jane2 = pickle.loads(z) - self.assertEqual(jane2, jane) - self.assertEqual(jane2, {'name': 'jane', 'id': 37}) - ZZ = pickle.dumps(EmpD, proto) - EmpDnew = pickle.loads(ZZ) - self.assertEqual(EmpDnew({'name': 'jane', 'id': 37}), jane) - - def test_optional(self): - EmpD = TypedDict(b'EmpD', name=str, id=int) - - self.assertEqual(typing.Optional[EmpD], typing.Union[None, EmpD]) - self.assertNotEqual(typing.List[EmpD], typing.Tuple[EmpD]) - - def test_total(self): - D = TypedDict(b'D', {'x': int}, total=False) - self.assertEqual(D(), {}) - self.assertEqual(D(x=1), {'x': 1}) - self.assertEqual(D.__total__, False) - - -class IOTests(BaseTestCase): - - def test_io_submodule(self): - from typing.io import IO, TextIO, BinaryIO, __all__, __name__ - self.assertIs(IO, typing.IO) - self.assertIs(TextIO, typing.TextIO) - self.assertIs(BinaryIO, typing.BinaryIO) - self.assertEqual(set(__all__), set(['IO', 'TextIO', 'BinaryIO'])) - self.assertEqual(__name__, 'typing.io') - - -class RETests(BaseTestCase): - # Much of this is really testing _TypeAlias. - - def test_basics(self): - pat = re.compile('[a-z]+', re.I) - self.assertIsSubclass(pat.__class__, Pattern) - self.assertIsSubclass(type(pat), Pattern) - self.assertIsInstance(pat, Pattern) - - mat = pat.search('12345abcde.....') - self.assertIsSubclass(mat.__class__, Match) - self.assertIsSubclass(type(mat), Match) - self.assertIsInstance(mat, Match) - - # these should just work - Pattern[Union[str, bytes]] - Match[Union[bytes, str]] - - def test_alias_equality(self): - self.assertEqual(Pattern[str], Pattern[str]) - self.assertNotEqual(Pattern[str], Pattern[bytes]) - self.assertNotEqual(Pattern[str], Match[str]) - self.assertNotEqual(Pattern[str], str) - - def test_errors(self): - with self.assertRaises(TypeError): - # Doesn't fit AnyStr. - Pattern[int] - with self.assertRaises(TypeError): - # Can't change type vars? - Match[T] - m = Match[Union[str, bytes]] - with self.assertRaises(TypeError): - # Too complicated? - m[str] - with self.assertRaises(TypeError): - # We don't support isinstance(). - isinstance(42, Pattern[str]) - with self.assertRaises(TypeError): - # We don't support issubclass(). - issubclass(Pattern[bytes], Pattern[str]) - - def test_repr(self): - self.assertEqual(repr(Pattern), 'Pattern[~AnyStr]') - self.assertEqual(repr(Pattern[unicode]), 'Pattern[unicode]') - self.assertEqual(repr(Pattern[str]), 'Pattern[str]') - self.assertEqual(repr(Match), 'Match[~AnyStr]') - self.assertEqual(repr(Match[unicode]), 'Match[unicode]') - self.assertEqual(repr(Match[str]), 'Match[str]') - - def test_re_submodule(self): - from typing.re import Match, Pattern, __all__, __name__ - self.assertIs(Match, typing.Match) - self.assertIs(Pattern, typing.Pattern) - self.assertEqual(set(__all__), set(['Match', 'Pattern'])) - self.assertEqual(__name__, 'typing.re') - - def test_cannot_subclass(self): - with self.assertRaises(TypeError) as ex: - - class A(typing.Match): - pass - - self.assertEqual(str(ex.exception), - "Cannot subclass typing._TypeAlias") - - -class AllTests(BaseTestCase): - """Tests for __all__.""" - - def test_all(self): - from typing import __all__ as a - # Just spot-check the first and last of every category. - self.assertIn('AbstractSet', a) - self.assertIn('ValuesView', a) - self.assertIn('cast', a) - self.assertIn('overload', a) - # Check that io and re are not exported. - self.assertNotIn('io', a) - self.assertNotIn('re', a) - # Spot-check that stdlib modules aren't exported. - self.assertNotIn('os', a) - self.assertNotIn('sys', a) - # Check that Text is defined. - self.assertIn('Text', a) - # Check previously missing class. - self.assertIn('SupportsComplex', a) - - def test_respect_no_type_check(self): - @typing.no_type_check - class NoTpCheck(object): - class Inn(object): - def __init__(self, x): - # type: (this is not actually a type) -> None # noqa - pass - self.assertTrue(NoTpCheck.__no_type_check__) - self.assertTrue(NoTpCheck.Inn.__init__.__no_type_check__) - - def test_get_type_hints_dummy(self): - - def foo(x): - # type: (int) -> int - return x + 1 - - self.assertIsNone(typing.get_type_hints(foo)) - - def test_typing_compiles_with_opt(self): - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'typing.py') - try: - subprocess.check_output([sys.executable, '-OO', file_path], - stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - self.fail('Module does not compile with optimize=2 (-OO flag).') - - -if __name__ == '__main__': - main() diff --git a/python2/typing.py b/python2/typing.py deleted file mode 100644 index dd16d9af9..000000000 --- a/python2/typing.py +++ /dev/null @@ -1,2550 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import abc -from abc import abstractmethod, abstractproperty -import collections -import functools -import re as stdlib_re # Avoid confusion with the re we export. -import sys -import types -import copy -try: - import collections.abc as collections_abc -except ImportError: - import collections as collections_abc # Fallback for PY3.2. - - -# Please keep __all__ alphabetized within each category. -__all__ = [ - # Super-special typing primitives. - 'Any', - 'Callable', - 'ClassVar', - 'Final', - 'Generic', - 'Literal', - 'Optional', - 'Protocol', - 'Tuple', - 'Type', - 'TypeVar', - 'Union', - - # ABCs (from collections.abc). - 'AbstractSet', # collections.abc.Set. - 'GenericMeta', # subclass of abc.ABCMeta and a metaclass - # for 'Generic' and ABCs below. - 'ByteString', - 'Container', - 'ContextManager', - 'Hashable', - 'ItemsView', - 'Iterable', - 'Iterator', - 'KeysView', - 'Mapping', - 'MappingView', - 'MutableMapping', - 'MutableSequence', - 'MutableSet', - 'Sequence', - 'Sized', - 'ValuesView', - - # Structural checks, a.k.a. protocols. - 'Reversible', - 'SupportsAbs', - 'SupportsComplex', - 'SupportsFloat', - 'SupportsIndex', - 'SupportsInt', - - # Concrete collection types. - 'Counter', - 'Deque', - 'Dict', - 'DefaultDict', - 'List', - 'Set', - 'FrozenSet', - 'NamedTuple', # Not really a type. - 'TypedDict', # Not really a type. - 'Generator', - - # One-off things. - 'AnyStr', - 'cast', - 'final', - 'get_type_hints', - 'NewType', - 'no_type_check', - 'no_type_check_decorator', - 'NoReturn', - 'overload', - 'runtime_checkable', - 'Text', - 'TYPE_CHECKING', -] - -# The pseudo-submodules 're' and 'io' are part of the public -# namespace, but excluded from __all__ because they might stomp on -# legitimate imports of those modules. - - -def _qualname(x): - if sys.version_info[:2] >= (3, 3): - return x.__qualname__ - else: - # Fall back to just name. - return x.__name__ - - -def _trim_name(nm): - whitelist = ('_TypeAlias', '_ForwardRef', '_TypingBase', '_FinalTypingBase') - if nm.startswith('_') and nm not in whitelist: - nm = nm[1:] - return nm - - -class TypingMeta(type): - """Metaclass for most types defined in typing module - (not a part of public API). - - This also defines a dummy constructor (all the work for most typing - constructs is done in __new__) and a nicer repr(). - """ - - _is_protocol = False - - def __new__(cls, name, bases, namespace): - return super(TypingMeta, cls).__new__(cls, str(name), bases, namespace) - - @classmethod - def assert_no_subclassing(cls, bases): - for base in bases: - if isinstance(base, cls): - raise TypeError("Cannot subclass %s" % - (', '.join(map(_type_repr, bases)) or '()')) - - def __init__(self, *args, **kwds): - pass - - def _eval_type(self, globalns, localns): - """Override this in subclasses to interpret forward references. - - For example, List['C'] is internally stored as - List[_ForwardRef('C')], which should evaluate to List[C], - where C is an object found in globalns or localns (searching - localns first, of course). - """ - return self - - def _get_type_vars(self, tvars): - pass - - def __repr__(self): - qname = _trim_name(_qualname(self)) - return '%s.%s' % (self.__module__, qname) - - -class _TypingBase(object): - """Internal indicator of special typing constructs.""" - __metaclass__ = TypingMeta - __slots__ = ('__weakref__',) - - def __init__(self, *args, **kwds): - pass - - def __new__(cls, *args, **kwds): - """Constructor. - - This only exists to give a better error message in case - someone tries to subclass a special typing object (not a good idea). - """ - if (len(args) == 3 and - isinstance(args[0], str) and - isinstance(args[1], tuple)): - # Close enough. - raise TypeError("Cannot subclass %r" % cls) - return super(_TypingBase, cls).__new__(cls) - - # Things that are not classes also need these. - def _eval_type(self, globalns, localns): - return self - - def _get_type_vars(self, tvars): - pass - - def __repr__(self): - cls = type(self) - qname = _trim_name(_qualname(cls)) - return '%s.%s' % (cls.__module__, qname) - - def __call__(self, *args, **kwds): - raise TypeError("Cannot instantiate %r" % type(self)) - - -class _FinalTypingBase(_TypingBase): - """Internal mix-in class to prevent instantiation. - - Prevents instantiation unless _root=True is given in class call. - It is used to create pseudo-singleton instances Any, Union, Optional, etc. - """ - - __slots__ = () - - def __new__(cls, *args, **kwds): - self = super(_FinalTypingBase, cls).__new__(cls, *args, **kwds) - if '_root' in kwds and kwds['_root'] is True: - return self - raise TypeError("Cannot instantiate %r" % cls) - - def __reduce__(self): - return _trim_name(type(self).__name__) - - -class _ForwardRef(_TypingBase): - """Internal wrapper to hold a forward reference.""" - - __slots__ = ('__forward_arg__', '__forward_code__', - '__forward_evaluated__', '__forward_value__') - - def __init__(self, arg): - super(_ForwardRef, self).__init__(arg) - if not isinstance(arg, basestring): - raise TypeError('Forward reference must be a string -- got %r' % (arg,)) - try: - code = compile(arg, '', 'eval') - except SyntaxError: - raise SyntaxError('Forward reference must be an expression -- got %r' % - (arg,)) - self.__forward_arg__ = arg - self.__forward_code__ = code - self.__forward_evaluated__ = False - self.__forward_value__ = None - - def _eval_type(self, globalns, localns): - if not self.__forward_evaluated__ or localns is not globalns: - if globalns is None and localns is None: - globalns = localns = {} - elif globalns is None: - globalns = localns - elif localns is None: - localns = globalns - self.__forward_value__ = _type_check( - eval(self.__forward_code__, globalns, localns), - "Forward references must evaluate to types.") - self.__forward_evaluated__ = True - return self.__forward_value__ - - def __eq__(self, other): - if not isinstance(other, _ForwardRef): - return NotImplemented - return (self.__forward_arg__ == other.__forward_arg__ and - self.__forward_value__ == other.__forward_value__) - - def __hash__(self): - return hash((self.__forward_arg__, self.__forward_value__)) - - def __instancecheck__(self, obj): - raise TypeError("Forward references cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Forward references cannot be used with issubclass().") - - def __repr__(self): - return '_ForwardRef(%r)' % (self.__forward_arg__,) - - -class _TypeAlias(_TypingBase): - """Internal helper class for defining generic variants of concrete types. - - Note that this is not a type; let's call it a pseudo-type. It cannot - be used in instance and subclass checks in parameterized form, i.e. - ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning - ``False``. - """ - - __slots__ = ('name', 'type_var', 'impl_type', 'type_checker') - - def __init__(self, name, type_var, impl_type, type_checker): - """Initializer. - - Args: - name: The name, e.g. 'Pattern'. - type_var: The type parameter, e.g. AnyStr, or the - specific type, e.g. str. - impl_type: The implementation type. - type_checker: Function that takes an impl_type instance. - and returns a value that should be a type_var instance. - """ - assert isinstance(name, basestring), repr(name) - assert isinstance(impl_type, type), repr(impl_type) - assert not isinstance(impl_type, TypingMeta), repr(impl_type) - assert isinstance(type_var, (type, _TypingBase)), repr(type_var) - self.name = name - self.type_var = type_var - self.impl_type = impl_type - self.type_checker = type_checker - - def __repr__(self): - return "%s[%s]" % (self.name, _type_repr(self.type_var)) - - def __getitem__(self, parameter): - if not isinstance(self.type_var, TypeVar): - raise TypeError("%s cannot be further parameterized." % self) - if self.type_var.__constraints__ and isinstance(parameter, type): - if not issubclass(parameter, self.type_var.__constraints__): - raise TypeError("%s is not a valid substitution for %s." % - (parameter, self.type_var)) - if isinstance(parameter, TypeVar) and parameter is not self.type_var: - raise TypeError("%s cannot be re-parameterized." % self) - return self.__class__(self.name, parameter, - self.impl_type, self.type_checker) - - def __eq__(self, other): - if not isinstance(other, _TypeAlias): - return NotImplemented - return self.name == other.name and self.type_var == other.type_var - - def __hash__(self): - return hash((self.name, self.type_var)) - - def __instancecheck__(self, obj): - if not isinstance(self.type_var, TypeVar): - raise TypeError("Parameterized type aliases cannot be used " - "with isinstance().") - return isinstance(obj, self.impl_type) - - def __subclasscheck__(self, cls): - if not isinstance(self.type_var, TypeVar): - raise TypeError("Parameterized type aliases cannot be used " - "with issubclass().") - return issubclass(cls, self.impl_type) - - -def _get_type_vars(types, tvars): - for t in types: - if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): - t._get_type_vars(tvars) - - -def _type_vars(types): - tvars = [] - _get_type_vars(types, tvars) - return tuple(tvars) - - -def _eval_type(t, globalns, localns): - if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): - return t._eval_type(globalns, localns) - return t - - -def _type_check(arg, msg): - """Check that the argument is a type, and return it (internal helper). - - As a special case, accept None and return type(None) instead. - Also, _TypeAlias instances (e.g. Match, Pattern) are acceptable. - - The msg argument is a human-readable error message, e.g. - - "Union[arg, ...]: arg should be a type." - - We append the repr() of the actual value (truncated to 100 chars). - """ - if arg is None: - return type(None) - if isinstance(arg, basestring): - arg = _ForwardRef(arg) - if ( - isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or - not isinstance(arg, (type, _TypingBase)) and not callable(arg) - ): - raise TypeError(msg + " Got %.100r." % (arg,)) - # Bare Union etc. are not valid as type arguments - if ( - type(arg).__name__ in ('_Union', '_Optional') and - not getattr(arg, '__origin__', None) or - isinstance(arg, TypingMeta) and arg._gorg in (Generic, Protocol) - ): - raise TypeError("Plain %s is not valid as type argument" % arg) - return arg - - -def _type_repr(obj): - """Return the repr() of an object, special-casing types (internal helper). - - If obj is a type, we return a shorter version than the default - type.__repr__, based on the module and qualified name, which is - typically enough to uniquely identify a type. For everything - else, we fall back on repr(obj). - """ - if isinstance(obj, type) and not isinstance(obj, TypingMeta): - if obj.__module__ == '__builtin__': - return _qualname(obj) - return '%s.%s' % (obj.__module__, _qualname(obj)) - if obj is Ellipsis: - return '...' - if isinstance(obj, types.FunctionType): - return obj.__name__ - return repr(obj) - - -class ClassVarMeta(TypingMeta): - """Metaclass for _ClassVar""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(ClassVarMeta, cls).__new__(cls, name, bases, namespace) - return self - - -class _ClassVar(_FinalTypingBase): - """Special type construct to mark class variables. - - An annotation wrapped in ClassVar indicates that a given - attribute is intended to be used as a class variable and - should not be set on instances of that class. Usage:: - - class Starship: - stats = {} # type: ClassVar[Dict[str, int]] # class variable - damage = 10 # type: int # instance variable - - ClassVar accepts only types and cannot be further subscribed. - - Note that ClassVar is not a class itself, and should not - be used with isinstance() or issubclass(). - """ - - __metaclass__ = ClassVarMeta - __slots__ = ('__type__',) - - def __init__(self, tp=None, _root=False): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(_type_check(item, - '{} accepts only types.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - return type(self)(_eval_type(self.__type__, globalns, localns), - _root=True) - - def __repr__(self): - r = super(_ClassVar, self).__repr__() - if self.__type__ is not None: - r += '[{}]'.format(_type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _ClassVar): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - -ClassVar = _ClassVar(_root=True) - - -class _FinalMeta(TypingMeta): - """Metaclass for _Final""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(_FinalMeta, cls).__new__(cls, name, bases, namespace) - return self - - -class _Final(_FinalTypingBase): - """A special typing construct to indicate that a name - cannot be re-assigned or overridden in a subclass. - For example: - - MAX_SIZE: Final = 9000 - MAX_SIZE += 1 # Error reported by type checker - - class Connection: - TIMEOUT: Final[int] = 10 - class FastConnector(Connection): - TIMEOUT = 1 # Error reported by type checker - - There is no runtime checking of these properties. - """ - - __metaclass__ = _FinalMeta - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(_type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = _eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super(_Final, self).__repr__() - if self.__type__ is not None: - r += '[{}]'.format(_type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _Final): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - -Final = _Final(_root=True) - - -def final(f): - """This decorator can be used to indicate to type checkers that - the decorated method cannot be overridden, and decorated class - cannot be subclassed. For example: - - class Base: - @final - def done(self) -> None: - ... - class Sub(Base): - def done(self) -> None: # Error reported by type checker - ... - @final - class Leaf: - ... - class Other(Leaf): # Error reported by type checker - ... - - There is no runtime checking of these properties. - """ - return f - - -class _LiteralMeta(TypingMeta): - """Metaclass for _Literal""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(_LiteralMeta, cls).__new__(cls, name, bases, namespace) - return self - - -class _Literal(_FinalTypingBase): - """A type that can be used to indicate to type checkers that the - corresponding value has a value literally equivalent to the - provided parameter. For example: - - var: Literal[4] = 4 - - The type checker understands that 'var' is literally equal to the - value 4 and no other value. - - Literal[...] cannot be subclassed. There is no runtime checking - verifying that the parameter is actually a value instead of a type. - """ - - __metaclass__ = _LiteralMeta - __slots__ = ('__values__',) - - def __init__(self, values=None, **kwds): - self.__values__ = values - - def __getitem__(self, item): - cls = type(self) - if self.__values__ is None: - if not isinstance(item, tuple): - item = (item,) - return cls(values=item, - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - return self - - def __repr__(self): - r = super(_Literal, self).__repr__() - if self.__values__ is not None: - r += '[{}]'.format(', '.join(map(_type_repr, self.__values__))) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__values__)) - - def __eq__(self, other): - if not isinstance(other, _Literal): - return NotImplemented - if self.__values__ is not None: - return self.__values__ == other.__values__ - return self is other - - -Literal = _Literal(_root=True) - - -class AnyMeta(TypingMeta): - """Metaclass for Any.""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(AnyMeta, cls).__new__(cls, name, bases, namespace) - return self - - -class _Any(_FinalTypingBase): - """Special type indicating an unconstrained type. - - - Any is compatible with every type. - - Any assumed to have all methods. - - All values assumed to be instances of Any. - - Note that all the above statements are true from the point of view of - static type checkers. At runtime, Any should not be used with instance - or class checks. - """ - __metaclass__ = AnyMeta - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("Any cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Any cannot be used with issubclass().") - - -Any = _Any(_root=True) - - -class NoReturnMeta(TypingMeta): - """Metaclass for NoReturn.""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(NoReturnMeta, cls).__new__(cls, name, bases, namespace) - return self - - -class _NoReturn(_FinalTypingBase): - """Special type indicating functions that never return. - Example:: - - from typing import NoReturn - - def stop() -> NoReturn: - raise Exception('no way') - - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - __metaclass__ = NoReturnMeta - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - -NoReturn = _NoReturn(_root=True) - - -class TypeVarMeta(TypingMeta): - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - return super(TypeVarMeta, cls).__new__(cls, name, bases, namespace) - - -class TypeVar(_TypingBase): - """Type variable. - - Usage:: - - T = TypeVar('T') # Can be anything - A = TypeVar('A', str, bytes) # Must be str or bytes - - Type variables exist primarily for the benefit of static type - checkers. They serve as the parameters for generic types as well - as for generic function definitions. See class Generic for more - information on generic types. Generic functions work as follows: - - def repeat(x: T, n: int) -> List[T]: - '''Return a list containing n references to x.''' - return [x]*n - - def longest(x: A, y: A) -> A: - '''Return the longest of two strings.''' - return x if len(x) >= len(y) else y - - The latter example's signature is essentially the overloading - of (str, str) -> str and (bytes, bytes) -> bytes. Also note - that if the arguments are instances of some subclass of str, - the return type is still plain str. - - At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError. - - Type variables defined with covariant=True or contravariant=True - can be used do declare covariant or contravariant generic types. - See PEP 484 for more details. By default generic types are invariant - in all type variables. - - Type variables can be introspected. e.g.: - - T.__name__ == 'T' - T.__constraints__ == () - T.__covariant__ == False - T.__contravariant__ = False - A.__constraints__ == (str, bytes) - """ - - __metaclass__ = TypeVarMeta - __slots__ = ('__name__', '__bound__', '__constraints__', - '__covariant__', '__contravariant__') - - def __init__(self, name, *constraints, **kwargs): - super(TypeVar, self).__init__(name, *constraints, **kwargs) - bound = kwargs.get('bound', None) - covariant = kwargs.get('covariant', False) - contravariant = kwargs.get('contravariant', False) - self.__name__ = name - if covariant and contravariant: - raise ValueError("Bivariant types are not supported.") - self.__covariant__ = bool(covariant) - self.__contravariant__ = bool(contravariant) - if constraints and bound is not None: - raise TypeError("Constraints cannot be combined with bound=...") - if constraints and len(constraints) == 1: - raise TypeError("A single constraint is not allowed") - msg = "TypeVar(name, constraint, ...): constraints must be types." - self.__constraints__ = tuple(_type_check(t, msg) for t in constraints) - if bound: - self.__bound__ = _type_check(bound, "Bound must be a type.") - else: - self.__bound__ = None - - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - - def __repr__(self): - if self.__covariant__: - prefix = '+' - elif self.__contravariant__: - prefix = '-' - else: - prefix = '~' - return prefix + self.__name__ - - def __instancecheck__(self, instance): - raise TypeError("Type variables cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Type variables cannot be used with issubclass().") - - -# Some unconstrained type variables. These are used by the container types. -# (These are not for export.) -T = TypeVar('T') # Any type. -KT = TypeVar('KT') # Key type. -VT = TypeVar('VT') # Value type. -T_co = TypeVar('T_co', covariant=True) # Any type covariant containers. -V_co = TypeVar('V_co', covariant=True) # Any type covariant containers. -VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers. -T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. - -# A useful type variable with constraints. This represents string types. -# (This one *is* for export!) -AnyStr = TypeVar('AnyStr', bytes, unicode) - - -def _replace_arg(arg, tvars, args): - """An internal helper function: replace arg if it is a type variable - found in tvars with corresponding substitution from args or - with corresponding substitution sub-tree if arg is a generic type. - """ - - if tvars is None: - tvars = [] - if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)): - return arg._subs_tree(tvars, args) - if isinstance(arg, TypeVar): - for i, tvar in enumerate(tvars): - if arg == tvar: - return args[i] - return arg - - -# Special typing constructs Union, Optional, Generic, Callable and Tuple -# use three special attributes for internal bookkeeping of generic types: -# * __parameters__ is a tuple of unique free type parameters of a generic -# type, for example, Dict[T, T].__parameters__ == (T,); -# * __origin__ keeps a reference to a type that was subscripted, -# e.g., Union[T, int].__origin__ == Union; -# * __args__ is a tuple of all arguments used in subscripting, -# e.g., Dict[T, int].__args__ == (T, int). - - -def _subs_tree(cls, tvars=None, args=None): - """An internal helper function: calculate substitution tree - for generic cls after replacing its type parameters with - substitutions in tvars -> args (if any). - Repeat the same following __origin__'s. - - Return a list of arguments with all possible substitutions - performed. Arguments that are generic classes themselves are represented - as tuples (so that no new classes are created by this function). - For example: _subs_tree(List[Tuple[int, T]][str]) == [(Tuple, int, str)] - """ - - if cls.__origin__ is None: - return cls - # Make of chain of origins (i.e. cls -> cls.__origin__) - current = cls.__origin__ - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ - # Replace type variables in __args__ if asked ... - tree_args = [] - for arg in cls.__args__: - tree_args.append(_replace_arg(arg, tvars, args)) - # ... then continue replacing down the origin chain. - for ocls in orig_chain: - new_tree_args = [] - for arg in ocls.__args__: - new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) - tree_args = new_tree_args - return tree_args - - -def _remove_dups_flatten(parameters): - """An internal helper for Union creation and substitution: flatten Union's - among parameters, then remove duplicates and strict subclasses. - """ - - # Flatten out Union[Union[...], ...]. - params = [] - for p in parameters: - if isinstance(p, _Union) and p.__origin__ is Union: - params.extend(p.__args__) - elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union: - params.extend(p[1:]) - else: - params.append(p) - # Weed out strict duplicates, preserving the first of each occurrence. - all_params = set(params) - if len(all_params) < len(params): - new_params = [] - for t in params: - if t in all_params: - new_params.append(t) - all_params.remove(t) - params = new_params - assert not all_params, all_params - # Weed out subclasses. - # E.g. Union[int, Employee, Manager] == Union[int, Employee]. - # If object is present it will be sole survivor among proper classes. - # Never discard type variables. - # (In particular, Union[str, AnyStr] != AnyStr.) - all_params = set(params) - for t1 in params: - if not isinstance(t1, type): - continue - if any(isinstance(t2, type) and issubclass(t1, t2) - for t2 in all_params - {t1} - if not (isinstance(t2, GenericMeta) and - t2.__origin__ is not None)): - all_params.remove(t1) - return tuple(t for t in params if t in all_params) - - -def _check_generic(cls, parameters): - # Check correct count for parameters of a generic cls (internal helper). - if not cls.__parameters__: - raise TypeError("%s is not a generic class" % repr(cls)) - alen = len(parameters) - elen = len(cls.__parameters__) - if alen != elen: - raise TypeError("Too %s parameters for %s; actual %s, expected %s" % - ("many" if alen > elen else "few", repr(cls), alen, elen)) - - -_cleanups = [] - - -def _tp_cache(func): - maxsize = 128 - cache = {} - _cleanups.append(cache.clear) - - @functools.wraps(func) - def inner(*args): - key = args - try: - return cache[key] - except TypeError: - # Assume it's an unhashable argument. - return func(*args) - except KeyError: - value = func(*args) - if len(cache) >= maxsize: - # If the cache grows too much, just start over. - cache.clear() - cache[key] = value - return value - - return inner - - -class UnionMeta(TypingMeta): - """Metaclass for Union.""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - return super(UnionMeta, cls).__new__(cls, name, bases, namespace) - - -class _Union(_FinalTypingBase): - """Union type; Union[X, Y] means either X or Y. - - To define a union, use e.g. Union[int, str]. Details: - - - The arguments must be types and there must be at least one. - - - None as an argument is a special case and is replaced by - type(None). - - - Unions of unions are flattened, e.g.:: - - Union[Union[int, str], float] == Union[int, str, float] - - - Unions of a single argument vanish, e.g.:: - - Union[int] == int # The constructor actually returns int - - - Redundant arguments are skipped, e.g.:: - - Union[int, str, int] == Union[int, str] - - - When comparing unions, the argument order is ignored, e.g.:: - - Union[int, str] == Union[str, int] - - - When two arguments have a subclass relationship, the least - derived argument is kept, e.g.:: - - class Employee: pass - class Manager(Employee): pass - Union[int, Employee, Manager] == Union[int, Employee] - Union[Manager, int, Employee] == Union[int, Employee] - Union[Employee, Manager] == Employee - - - Similar for object:: - - Union[int, object] == object - - - You cannot subclass or instantiate a union. - - - You can use Optional[X] as a shorthand for Union[X, None]. - """ - - __metaclass__ = UnionMeta - __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__') - - def __new__(cls, parameters=None, origin=None, *args, **kwds): - self = super(_Union, cls).__new__(cls, parameters, origin, *args, **kwds) - if origin is None: - self.__parameters__ = None - self.__args__ = None - self.__origin__ = None - self.__tree_hash__ = hash(frozenset(('Union',))) - return self - if not isinstance(parameters, tuple): - raise TypeError("Expected parameters=") - if origin is Union: - parameters = _remove_dups_flatten(parameters) - # It's not a union if there's only one type left. - if len(parameters) == 1: - return parameters[0] - self.__parameters__ = _type_vars(parameters) - self.__args__ = parameters - self.__origin__ = origin - # Pre-calculate the __hash__ on instantiation. - # This improves speed for complex substitutions. - subs_tree = self._subs_tree() - if isinstance(subs_tree, tuple): - self.__tree_hash__ = hash(frozenset(subs_tree)) - else: - self.__tree_hash__ = hash(subs_tree) - return self - - def _eval_type(self, globalns, localns): - if self.__args__ is None: - return self - ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__) - ev_origin = _eval_type(self.__origin__, globalns, localns) - if ev_args == self.__args__ and ev_origin == self.__origin__: - # Everything is already evaluated. - return self - return self.__class__(ev_args, ev_origin, _root=True) - - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - _get_type_vars(self.__parameters__, tvars) - - def __repr__(self): - if self.__origin__ is None: - return super(_Union, self).__repr__() - tree = self._subs_tree() - if not isinstance(tree, tuple): - return repr(tree) - return tree[0]._tree_repr(tree) - - def _tree_repr(self, tree): - arg_list = [] - for arg in tree[1:]: - if not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - return super(_Union, self).__repr__() + '[%s]' % ', '.join(arg_list) - - @_tp_cache - def __getitem__(self, parameters): - if parameters == (): - raise TypeError("Cannot take a Union of no types.") - if not isinstance(parameters, tuple): - parameters = (parameters,) - if self.__origin__ is None: - msg = "Union[arg, ...]: each arg must be a type." - else: - msg = "Parameters to generic types must be types." - parameters = tuple(_type_check(p, msg) for p in parameters) - if self is not Union: - _check_generic(self, parameters) - return self.__class__(parameters, origin=self, _root=True) - - def _subs_tree(self, tvars=None, args=None): - if self is Union: - return Union # Nothing to substitute - tree_args = _subs_tree(self, tvars, args) - tree_args = _remove_dups_flatten(tree_args) - if len(tree_args) == 1: - return tree_args[0] # Union of a single type is that type - return (Union,) + tree_args - - def __eq__(self, other): - if isinstance(other, _Union): - return self.__tree_hash__ == other.__tree_hash__ - elif self is not Union: - return self._subs_tree() == other - else: - return self is other - - def __hash__(self): - return self.__tree_hash__ - - def __instancecheck__(self, obj): - raise TypeError("Unions cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Unions cannot be used with issubclass().") - - -Union = _Union(_root=True) - - -class OptionalMeta(TypingMeta): - """Metaclass for Optional.""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - return super(OptionalMeta, cls).__new__(cls, name, bases, namespace) - - -class _Optional(_FinalTypingBase): - """Optional type. - - Optional[X] is equivalent to Union[X, None]. - """ - - __metaclass__ = OptionalMeta - __slots__ = () - - @_tp_cache - def __getitem__(self, arg): - arg = _type_check(arg, "Optional[t] requires a single type.") - return Union[arg, type(None)] - - -Optional = _Optional(_root=True) - - -def _next_in_mro(cls): - """Helper for Generic.__new__. - - Returns the class after the last occurrence of Generic or - Generic[...] in cls.__mro__. - """ - next_in_mro = object - # Look for the last occurrence of Generic or Generic[...]. - for i, c in enumerate(cls.__mro__[:-1]): - if isinstance(c, GenericMeta) and c._gorg is Generic: - next_in_mro = cls.__mro__[i + 1] - return next_in_mro - - -def _make_subclasshook(cls): - """Construct a __subclasshook__ callable that incorporates - the associated __extra__ class in subclass checks performed - against cls. - """ - if isinstance(cls.__extra__, abc.ABCMeta): - # The logic mirrors that of ABCMeta.__subclasscheck__. - # Registered classes need not be checked here because - # cls and its extra share the same _abc_registry. - def __extrahook__(cls, subclass): - res = cls.__extra__.__subclasshook__(subclass) - if res is not NotImplemented: - return res - if cls.__extra__ in getattr(subclass, '__mro__', ()): - return True - for scls in cls.__extra__.__subclasses__(): - if isinstance(scls, GenericMeta): - continue - if issubclass(subclass, scls): - return True - return NotImplemented - else: - # For non-ABC extras we'll just call issubclass(). - def __extrahook__(cls, subclass): - if cls.__extra__ and issubclass(subclass, cls.__extra__): - return True - return NotImplemented - return classmethod(__extrahook__) - - -class GenericMeta(TypingMeta, abc.ABCMeta): - """Metaclass for generic types. - - This is a metaclass for typing.Generic and generic ABCs defined in - typing module. User defined subclasses of GenericMeta can override - __new__ and invoke super().__new__. Note that GenericMeta.__new__ - has strict rules on what is allowed in its bases argument: - * plain Generic is disallowed in bases; - * Generic[...] should appear in bases at most once; - * if Generic[...] is present, then it should list all type variables - that appear in other bases. - In addition, type of all generic bases is erased, e.g., C[int] is - stripped to plain C. - """ - - def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None, orig_bases=None): - """Create a new generic class. GenericMeta.__new__ accepts - keyword arguments that are used for internal bookkeeping, therefore - an override should pass unused keyword arguments to super(). - """ - if tvars is not None: - # Called from __getitem__() below. - assert origin is not None - assert all(isinstance(t, TypeVar) for t in tvars), tvars - else: - # Called from class statement. - assert tvars is None, tvars - assert args is None, args - assert origin is None, origin - - # Get the full set of tvars from the bases. - tvars = _type_vars(bases) - # Look for Generic[T1, ..., Tn]. - # If found, tvars must be a subset of it. - # If not found, tvars is it. - # Also check for and reject plain Generic, - # and reject multiple Generic[...]. - gvars = None - for base in bases: - if base is Generic: - raise TypeError("Cannot inherit from plain Generic") - if (isinstance(base, GenericMeta) and - base.__origin__ in (Generic, Protocol)): - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...] or" - " Protocol[...] multiple times.") - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - raise TypeError( - "Some type variables (%s) " - "are not listed in %s[%s]" % - (", ".join(str(t) for t in tvars if t not in gvarset), - "Generic" if any(b.__origin__ is Generic - for b in bases) else "Protocol", - ", ".join(str(g) for g in gvars))) - tvars = gvars - - initial_bases = bases - if extra is None: - extra = namespace.get('__extra__') - if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) - - # remove bare Generic from bases if there are other generic bases - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) - namespace.update({'__origin__': origin, '__extra__': extra}) - self = super(GenericMeta, cls).__new__(cls, name, bases, namespace) - super(GenericMeta, self).__setattr__('_gorg', - self if not origin else origin._gorg) - - self.__parameters__ = tvars - # Be prepared that GenericMeta will be subclassed by TupleMeta - # and CallableMeta, those two allow ..., (), or [] in __args___. - self.__args__ = tuple(Ellipsis if a is _TypingEllipsis else - () if a is _TypingEmpty else - a for a in args) if args else None - # Speed hack (https://github.com/python/typing/issues/196). - self.__next_in_mro__ = _next_in_mro(self) - # Preserve base classes on subclassing (__bases__ are type erased now). - if orig_bases is None: - self.__orig_bases__ = initial_bases - - # This allows unparameterized generic collections to be used - # with issubclass() and isinstance() in the same way as their - # collections.abc counterparts (e.g., isinstance([], Iterable)). - if ( - '__subclasshook__' not in namespace and extra or - # allow overriding - getattr(self.__subclasshook__, '__name__', '') == '__extrahook__' - ): - self.__subclasshook__ = _make_subclasshook(self) - - if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2. - self.__qualname__ = origin.__qualname__ - self.__tree_hash__ = (hash(self._subs_tree()) if origin else - super(GenericMeta, self).__hash__()) - return self - - def __init__(self, *args, **kwargs): - super(GenericMeta, self).__init__(*args, **kwargs) - if isinstance(self.__extra__, abc.ABCMeta): - self._abc_registry = self.__extra__._abc_registry - self._abc_cache = self.__extra__._abc_cache - elif self.__origin__ is not None: - self._abc_registry = self.__origin__._abc_registry - self._abc_cache = self.__origin__._abc_cache - - # _abc_negative_cache and _abc_negative_cache_version - # realised as descriptors, since GenClass[t1, t2, ...] always - # share subclass info with GenClass. - # This is an important memory optimization. - @property - def _abc_negative_cache(self): - if isinstance(self.__extra__, abc.ABCMeta): - return self.__extra__._abc_negative_cache - return self._gorg._abc_generic_negative_cache - - @_abc_negative_cache.setter - def _abc_negative_cache(self, value): - if self.__origin__ is None: - if isinstance(self.__extra__, abc.ABCMeta): - self.__extra__._abc_negative_cache = value - else: - self._abc_generic_negative_cache = value - - @property - def _abc_negative_cache_version(self): - if isinstance(self.__extra__, abc.ABCMeta): - return self.__extra__._abc_negative_cache_version - return self._gorg._abc_generic_negative_cache_version - - @_abc_negative_cache_version.setter - def _abc_negative_cache_version(self, value): - if self.__origin__ is None: - if isinstance(self.__extra__, abc.ABCMeta): - self.__extra__._abc_negative_cache_version = value - else: - self._abc_generic_negative_cache_version = value - - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - _get_type_vars(self.__parameters__, tvars) - - def _eval_type(self, globalns, localns): - ev_origin = (self.__origin__._eval_type(globalns, localns) - if self.__origin__ else None) - ev_args = tuple(_eval_type(a, globalns, localns) for a - in self.__args__) if self.__args__ else None - if ev_origin == self.__origin__ and ev_args == self.__args__: - return self - return self.__class__(self.__name__, - self.__bases__, - dict(self.__dict__), - tvars=_type_vars(ev_args) if ev_args else None, - args=ev_args, - origin=ev_origin, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - def __repr__(self): - if self.__origin__ is None: - return super(GenericMeta, self).__repr__() - return self._tree_repr(self._subs_tree()) - - def _tree_repr(self, tree): - arg_list = [] - for arg in tree[1:]: - if arg == (): - arg_list.append('()') - elif not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - return super(GenericMeta, self).__repr__() + '[%s]' % ', '.join(arg_list) - - def _subs_tree(self, tvars=None, args=None): - if self.__origin__ is None: - return self - tree_args = _subs_tree(self, tvars, args) - return (self._gorg,) + tuple(tree_args) - - def __eq__(self, other): - if not isinstance(other, GenericMeta): - return NotImplemented - if self.__origin__ is None or other.__origin__ is None: - return self is other - return self.__tree_hash__ == other.__tree_hash__ - - def __hash__(self): - return self.__tree_hash__ - - @_tp_cache - def __getitem__(self, params): - if not isinstance(params, tuple): - params = (params,) - if not params and self._gorg is not Tuple: - raise TypeError( - "Parameter list to %s[...] cannot be empty" % _qualname(self)) - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if self in (Generic, Protocol): - # Generic can only be subscripted with unique type variables. - if not all(isinstance(p, TypeVar) for p in params): - raise TypeError( - "Parameters to %s[...] must all be type variables" % self.__name__) - if len(set(params)) != len(params): - raise TypeError( - "Parameters to %s[...] must all be unique" % self.__name__) - tvars = params - args = params - elif self in (Tuple, Callable): - tvars = _type_vars(params) - args = params - elif self.__origin__ in (Generic, Protocol): - # Can't subscript Generic[...] or Protocol[...]. - raise TypeError("Cannot subscript already-subscripted %s" % - repr(self)) - else: - # Subscripting a regular Generic subclass. - _check_generic(self, params) - tvars = _type_vars(params) - args = params - - prepend = (self,) if self.__origin__ is None else () - return self.__class__(self.__name__, - prepend + self.__bases__, - dict(self.__dict__), - tvars=tvars, - args=args, - origin=self, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - def __subclasscheck__(self, cls): - if self.__origin__ is not None: - # These should only be modules within the standard library. - # singledispatch is an exception, because it's a Python 2 backport - # of functools.singledispatch. - whitelist = ['abc', 'functools', 'singledispatch'] - if (sys._getframe(1).f_globals['__name__'] in whitelist or - # The second frame is needed for the case where we came - # from _ProtocolMeta.__subclasscheck__. - sys._getframe(2).f_globals['__name__'] in whitelist): - return False - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - if self is Generic: - raise TypeError("Class %r cannot be used with class " - "or instance checks" % self) - return super(GenericMeta, self).__subclasscheck__(cls) - - def __instancecheck__(self, instance): - # Since we extend ABC.__subclasscheck__ and - # ABC.__instancecheck__ inlines the cache checking done by the - # latter, we must extend __instancecheck__ too. For simplicity - # we just skip the cache check -- instance checks for generic - # classes are supposed to be rare anyways. - if hasattr(instance, "__class__"): - return issubclass(instance.__class__, self) - return False - - def __setattr__(self, attr, value): - # We consider all the subscripted genrics as proxies for original class - if ( - attr.startswith('__') and attr.endswith('__') or - attr.startswith('_abc_') - ): - super(GenericMeta, self).__setattr__(attr, value) - else: - super(GenericMeta, self._gorg).__setattr__(attr, value) - - -def _copy_generic(self): - """Hack to work around https://bugs.python.org/issue11480 on Python 2""" - return self.__class__(self.__name__, self.__bases__, dict(self.__dict__), - self.__parameters__, self.__args__, self.__origin__, - self.__extra__, self.__orig_bases__) - - -copy._copy_dispatch[GenericMeta] = _copy_generic - - -# Prevent checks for Generic to crash when defining Generic. -Generic = None - - -def _generic_new(base_cls, cls, *args, **kwds): - # Assure type is erased on instantiation, - # but attempt to store it in __orig_class__ - if cls.__origin__ is None: - if (base_cls.__new__ is object.__new__ and - cls.__init__ is not object.__init__): - return base_cls.__new__(cls) - else: - return base_cls.__new__(cls, *args, **kwds) - else: - origin = cls._gorg - if (base_cls.__new__ is object.__new__ and - cls.__init__ is not object.__init__): - obj = base_cls.__new__(origin) - else: - obj = base_cls.__new__(origin, *args, **kwds) - try: - obj.__orig_class__ = cls - except AttributeError: - pass - obj.__init__(*args, **kwds) - return obj - - -class Generic(object): - """Abstract base class for generic types. - - A generic type is typically declared by inheriting from - this class parameterized with one or more type variables. - For example, a generic mapping type might be defined as:: - - class Mapping(Generic[KT, VT]): - def __getitem__(self, key: KT) -> VT: - ... - # Etc. - - This class can then be used as follows:: - - def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: - try: - return mapping[key] - except KeyError: - return default - """ - - __metaclass__ = GenericMeta - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Generic: - raise TypeError("Type Generic cannot be instantiated; " - "it can be used only as a base class") - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - -class _TypingEmpty(object): - """Internal placeholder for () or []. Used by TupleMeta and CallableMeta - to allow empty list/tuple in specific places, without allowing them - to sneak in where prohibited. - """ - - -class _TypingEllipsis(object): - """Internal placeholder for ... (ellipsis).""" - - -class TupleMeta(GenericMeta): - """Metaclass for Tuple (internal).""" - - @_tp_cache - def __getitem__(self, parameters): - if self.__origin__ is not None or self._gorg is not Tuple: - # Normal generic rules apply if this is not the first subscription - # or a subscription of a subclass. - return super(TupleMeta, self).__getitem__(parameters) - if parameters == (): - return super(TupleMeta, self).__getitem__((_TypingEmpty,)) - if not isinstance(parameters, tuple): - parameters = (parameters,) - if len(parameters) == 2 and parameters[1] is Ellipsis: - msg = "Tuple[t, ...]: t must be a type." - p = _type_check(parameters[0], msg) - return super(TupleMeta, self).__getitem__((p, _TypingEllipsis)) - msg = "Tuple[t0, t1, ...]: each t must be a type." - parameters = tuple(_type_check(p, msg) for p in parameters) - return super(TupleMeta, self).__getitem__(parameters) - - def __instancecheck__(self, obj): - if self.__args__ is None: - return isinstance(obj, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with isinstance().") - - def __subclasscheck__(self, cls): - if self.__args__ is None: - return issubclass(cls, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with issubclass().") - - -copy._copy_dispatch[TupleMeta] = _copy_generic - - -class Tuple(tuple): - """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. - - Example: Tuple[T1, T2] is a tuple of two elements corresponding - to type variables T1 and T2. Tuple[int, float, str] is a tuple - of an int, a float and a string. - - To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. - """ - - __metaclass__ = TupleMeta - __extra__ = tuple - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Tuple: - raise TypeError("Type Tuple cannot be instantiated; " - "use tuple() instead") - return _generic_new(tuple, cls, *args, **kwds) - - -class CallableMeta(GenericMeta): - """ Metaclass for Callable.""" - - def __repr__(self): - if self.__origin__ is None: - return super(CallableMeta, self).__repr__() - return self._tree_repr(self._subs_tree()) - - def _tree_repr(self, tree): - if self._gorg is not Callable: - return super(CallableMeta, self)._tree_repr(tree) - # For actual Callable (not its subclass) we override - # super(CallableMeta, self)._tree_repr() for nice formatting. - arg_list = [] - for arg in tree[1:]: - if not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - if arg_list[0] == '...': - return repr(tree[0]) + '[..., %s]' % arg_list[1] - return (repr(tree[0]) + - '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) - - def __getitem__(self, parameters): - """A thin wrapper around __getitem_inner__ to provide the latter - with hashable arguments to improve speed. - """ - - if self.__origin__ is not None or self._gorg is not Callable: - return super(CallableMeta, self).__getitem__(parameters) - if not isinstance(parameters, tuple) or len(parameters) != 2: - raise TypeError("Callable must be used as " - "Callable[[arg, ...], result].") - args, result = parameters - if args is Ellipsis: - parameters = (Ellipsis, result) - else: - if not isinstance(args, list): - raise TypeError("Callable[args, result]: args must be a list." - " Got %.100r." % (args,)) - parameters = (tuple(args), result) - return self.__getitem_inner__(parameters) - - @_tp_cache - def __getitem_inner__(self, parameters): - args, result = parameters - msg = "Callable[args, result]: result must be a type." - result = _type_check(result, msg) - if args is Ellipsis: - return super(CallableMeta, self).__getitem__((_TypingEllipsis, result)) - msg = "Callable[[arg, ...], result]: each arg must be a type." - args = tuple(_type_check(arg, msg) for arg in args) - parameters = args + (result,) - return super(CallableMeta, self).__getitem__(parameters) - - -copy._copy_dispatch[CallableMeta] = _copy_generic - - -class Callable(object): - """Callable type; Callable[[int], str] is a function of (int) -> str. - - The subscription syntax must always be used with exactly two - values: the argument list and the return type. The argument list - must be a list of types or ellipsis; the return type must be a single type. - - There is no syntax to indicate optional or keyword arguments, - such function types are rarely used as callback types. - """ - - __metaclass__ = CallableMeta - __extra__ = collections_abc.Callable - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Callable: - raise TypeError("Type Callable cannot be instantiated; " - "use a non-abstract subclass instead") - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - -def cast(typ, val): - """Cast a value to a type. - - This returns the value unchanged. To the type checker this - signals that the return value has the designated type, but at - runtime we intentionally don't check anything (we want this - to be as fast as possible). - """ - return val - - -def _get_defaults(func): - """Internal helper to extract the default arguments, by name.""" - code = func.__code__ - pos_count = code.co_argcount - arg_names = code.co_varnames - arg_names = arg_names[:pos_count] - defaults = func.__defaults__ or () - kwdefaults = func.__kwdefaults__ - res = dict(kwdefaults) if kwdefaults else {} - pos_offset = pos_count - len(defaults) - for name, value in zip(arg_names[pos_offset:], defaults): - assert name not in res - res[name] = value - return res - - -def get_type_hints(obj, globalns=None, localns=None): - """In Python 2 this is not supported and always returns None.""" - return None - - -def no_type_check(arg): - """Decorator to indicate that annotations are not type hints. - - The argument must be a class or function; if it is a class, it - applies recursively to all methods and classes defined in that class - (but not to methods defined in its superclasses or subclasses). - - This mutates the function(s) or class(es) in place. - """ - if isinstance(arg, type): - arg_attrs = arg.__dict__.copy() - for attr, val in arg.__dict__.items(): - if val in arg.__bases__ + (arg,): - arg_attrs.pop(attr) - for obj in arg_attrs.values(): - if isinstance(obj, types.FunctionType): - obj.__no_type_check__ = True - if isinstance(obj, type): - no_type_check(obj) - try: - arg.__no_type_check__ = True - except TypeError: # built-in classes - pass - return arg - - -def no_type_check_decorator(decorator): - """Decorator to give another decorator the @no_type_check effect. - - This wraps the decorator with something that wraps the decorated - function in @no_type_check. - """ - - @functools.wraps(decorator) - def wrapped_decorator(*args, **kwds): - func = decorator(*args, **kwds) - func = no_type_check(func) - return func - - return wrapped_decorator - - -def _overload_dummy(*args, **kwds): - """Helper for @overload to raise when called.""" - raise NotImplementedError( - "You should not call an overloaded function. " - "A series of @overload-decorated functions " - "outside a stub module should always be followed " - "by an implementation that is not @overload-ed.") - - -def overload(func): - """Decorator for overloaded functions/methods. - - In a stub file, place two or more stub definitions for the same - function in a row, each decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - - In a non-stub file (i.e. a regular .py file), do the same but - follow it with an implementation. The implementation should *not* - be decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - def utf8(value): - # implementation goes here - """ - return _overload_dummy - - -_PROTO_WHITELIST = ['Callable', 'Iterable', 'Iterator', - 'Hashable', 'Sized', 'Container', 'Collection', - 'Reversible', 'ContextManager'] - - -class _ProtocolMeta(GenericMeta): - """Internal metaclass for Protocol. - - This exists so Protocol classes can be generic without deriving - from Generic. - """ - def __init__(cls, *args, **kwargs): - super(_ProtocolMeta, cls).__init__(*args, **kwargs) - if not cls.__dict__.get('_is_protocol', None): - cls._is_protocol = any(b is Protocol or - isinstance(b, _ProtocolMeta) and - b.__origin__ is Protocol - for b in cls.__bases__) - if cls._is_protocol: - for base in cls.__mro__[1:]: - if not (base in (object, Generic) or - base.__module__ == '_abcoll' and - base.__name__ in _PROTO_WHITELIST or - isinstance(base, TypingMeta) and base._is_protocol or - isinstance(base, GenericMeta) and base.__origin__ is Generic): - raise TypeError('Protocols can only inherit from other protocols,' - ' got %r' % base) - cls._callable_members_only = all(callable(getattr(cls, attr)) - for attr in cls._get_protocol_attrs()) - - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') - cls.__init__ = _no_init - - def _proto_hook(cls, other): - if not cls.__dict__.get('_is_protocol', None): - return NotImplemented - if not isinstance(other, type): - # Similar error as for issubclass(1, int) - # (also not a chance for old-style classes) - raise TypeError('issubclass() arg 1 must be a new-style class') - for attr in cls._get_protocol_attrs(): - for base in other.__mro__: - if attr in base.__dict__: - if base.__dict__[attr] is None: - return NotImplemented - break - else: - return NotImplemented - return True - if '__subclasshook__' not in cls.__dict__: - cls.__subclasshook__ = classmethod(_proto_hook) - - def __instancecheck__(self, instance): - # We need this method for situations where attributes are assigned in __init__ - if isinstance(instance, type): - # This looks like a fundamental limitation of Python 2. - # It cannot support runtime protocol metaclasses, On Python 2 classes - # cannot be correctly inspected as instances of protocols. - return False - if ((not getattr(self, '_is_protocol', False) or - self._callable_members_only) and - issubclass(instance.__class__, self)): - return True - if self._is_protocol: - if all(hasattr(instance, attr) and - (not callable(getattr(self, attr)) or - getattr(instance, attr) is not None) - for attr in self._get_protocol_attrs()): - return True - return super(GenericMeta, self).__instancecheck__(instance) - - def __subclasscheck__(self, cls): - if (self.__dict__.get('_is_protocol', None) and - not self.__dict__.get('_is_runtime_protocol', None)): - if (sys._getframe(1).f_globals['__name__'] in ['abc', 'functools'] or - # This is needed because we remove subclasses from unions on Python 2. - sys._getframe(2).f_globals['__name__'] == 'typing'): - return False - raise TypeError("Instance and class checks can only be used with" - " @runtime_checkable protocols") - if (self.__dict__.get('_is_runtime_protocol', None) and - not self._callable_members_only): - if sys._getframe(1).f_globals['__name__'] in ['abc', 'functools']: - return super(GenericMeta, self).__subclasscheck__(cls) - raise TypeError("Protocols with non-method members" - " don't support issubclass()") - return super(_ProtocolMeta, self).__subclasscheck__(cls) - - def _get_protocol_attrs(self): - attrs = set() - for base in self.__mro__[:-1]: # without object - if base.__name__ in ('Protocol', 'Generic'): - continue - annotations = getattr(base, '__annotations__', {}) - for attr in list(base.__dict__.keys()) + list(annotations.keys()): - if (not attr.startswith('_abc_') and attr not in ( - '__abstractmethods__', '__annotations__', '__weakref__', - '_is_protocol', '_is_runtime_protocol', '__dict__', - '__args__', '__slots__', '_get_protocol_attrs', - '__next_in_mro__', '__parameters__', '__origin__', - '__orig_bases__', '__extra__', '__tree_hash__', - '__doc__', '__subclasshook__', '__init__', '__new__', - '__module__', '_MutableMapping__marker', - '__metaclass__', '_gorg', '_callable_members_only')): - attrs.add(attr) - return attrs - - -class Protocol(object): - """Base class for protocol classes. Protocol classes are defined as:: - - class Proto(Protocol): - def meth(self): - # type: () -> int - pass - - Such classes are primarily used with static type checkers that recognize - structural subtyping (static duck-typing), for example:: - - class C: - def meth(self): - # type: () -> int - return 0 - - def func(x): - # type: (Proto) -> int - return x.meth() - - func(C()) # Passes static type check - - See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable - act as simple-minded runtime protocols that checks only the presence of - given attributes, ignoring their type signatures. - - Protocol classes can be generic, they are defined as:: - - class GenProto(Protocol[T]): - def meth(self): - # type: () -> T - pass - """ - - __metaclass__ = _ProtocolMeta - __slots__ = () - _is_protocol = True - - def __new__(cls, *args, **kwds): - if cls._gorg is Protocol: - raise TypeError("Type Protocol cannot be instantiated; " - "it can be used only as a base class") - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - -def runtime_checkable(cls): - """Mark a protocol class as a runtime protocol, so that it - can be used with isinstance() and issubclass(). Raise TypeError - if applied to a non-protocol class. - - This allows a simple-minded structural check very similar to the - one-offs in collections.abc such as Hashable. - """ - if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol: - raise TypeError('@runtime_checkable can be only applied to protocol classes,' - ' got %r' % cls) - cls._is_runtime_protocol = True - return cls - - -# Various ABCs mimicking those in collections.abc. -# A few are simply re-exported for completeness. - -Hashable = collections_abc.Hashable # Not generic. - - -class Iterable(Generic[T_co]): - __slots__ = () - __extra__ = collections_abc.Iterable - - -class Iterator(Iterable[T_co]): - __slots__ = () - __extra__ = collections_abc.Iterator - - -@runtime_checkable -class SupportsInt(Protocol): - __slots__ = () - - @abstractmethod - def __int__(self): - pass - - -@runtime_checkable -class SupportsFloat(Protocol): - __slots__ = () - - @abstractmethod - def __float__(self): - pass - - -@runtime_checkable -class SupportsComplex(Protocol): - __slots__ = () - - @abstractmethod - def __complex__(self): - pass - - -@runtime_checkable -class SupportsIndex(Protocol): - __slots__ = () - - @abstractmethod - def __index__(self): - pass - - -@runtime_checkable -class SupportsAbs(Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __abs__(self): - pass - - -if hasattr(collections_abc, 'Reversible'): - class Reversible(Iterable[T_co]): - __slots__ = () - __extra__ = collections_abc.Reversible -else: - @runtime_checkable - class Reversible(Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __reversed__(self): - pass - - -Sized = collections_abc.Sized # Not generic. - - -class Container(Generic[T_co]): - __slots__ = () - __extra__ = collections_abc.Container - - -# Callable was defined earlier. - - -class AbstractSet(Sized, Iterable[T_co], Container[T_co]): - __slots__ = () - __extra__ = collections_abc.Set - - -class MutableSet(AbstractSet[T]): - __slots__ = () - __extra__ = collections_abc.MutableSet - - -# NOTE: It is only covariant in the value type. -class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co]): - __slots__ = () - __extra__ = collections_abc.Mapping - - -class MutableMapping(Mapping[KT, VT]): - __slots__ = () - __extra__ = collections_abc.MutableMapping - - -if hasattr(collections_abc, 'Reversible'): - class Sequence(Sized, Reversible[T_co], Container[T_co]): - __slots__ = () - __extra__ = collections_abc.Sequence -else: - class Sequence(Sized, Iterable[T_co], Container[T_co]): - __slots__ = () - __extra__ = collections_abc.Sequence - - -class MutableSequence(Sequence[T]): - __slots__ = () - __extra__ = collections_abc.MutableSequence - - -class ByteString(Sequence[int]): - pass - - -ByteString.register(str) -ByteString.register(bytearray) - - -class List(list, MutableSequence[T]): - __slots__ = () - __extra__ = list - - def __new__(cls, *args, **kwds): - if cls._gorg is List: - raise TypeError("Type List cannot be instantiated; " - "use list() instead") - return _generic_new(list, cls, *args, **kwds) - - -class Deque(collections.deque, MutableSequence[T]): - __slots__ = () - __extra__ = collections.deque - - def __new__(cls, *args, **kwds): - if cls._gorg is Deque: - return collections.deque(*args, **kwds) - return _generic_new(collections.deque, cls, *args, **kwds) - - -class Set(set, MutableSet[T]): - __slots__ = () - __extra__ = set - - def __new__(cls, *args, **kwds): - if cls._gorg is Set: - raise TypeError("Type Set cannot be instantiated; " - "use set() instead") - return _generic_new(set, cls, *args, **kwds) - - -class FrozenSet(frozenset, AbstractSet[T_co]): - __slots__ = () - __extra__ = frozenset - - def __new__(cls, *args, **kwds): - if cls._gorg is FrozenSet: - raise TypeError("Type FrozenSet cannot be instantiated; " - "use frozenset() instead") - return _generic_new(frozenset, cls, *args, **kwds) - - -class MappingView(Sized, Iterable[T_co]): - __slots__ = () - __extra__ = collections_abc.MappingView - - -class KeysView(MappingView[KT], AbstractSet[KT]): - __slots__ = () - __extra__ = collections_abc.KeysView - - -class ItemsView(MappingView[Tuple[KT, VT_co]], - AbstractSet[Tuple[KT, VT_co]], - Generic[KT, VT_co]): - __slots__ = () - __extra__ = collections_abc.ItemsView - - -class ValuesView(MappingView[VT_co]): - __slots__ = () - __extra__ = collections_abc.ValuesView - - -class ContextManager(Generic[T_co]): - __slots__ = () - - def __enter__(self): - return self - - @abc.abstractmethod - def __exit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is ContextManager: - # In Python 3.6+, it is possible to set a method to None to - # explicitly indicate that the class does not implement an ABC - # (https://bugs.python.org/issue25958), but we do not support - # that pattern here because this fallback class is only used - # in Python 3.5 and earlier. - if (any("__enter__" in B.__dict__ for B in C.__mro__) and - any("__exit__" in B.__dict__ for B in C.__mro__)): - return True - return NotImplemented - - -class Dict(dict, MutableMapping[KT, VT]): - __slots__ = () - __extra__ = dict - - def __new__(cls, *args, **kwds): - if cls._gorg is Dict: - raise TypeError("Type Dict cannot be instantiated; " - "use dict() instead") - return _generic_new(dict, cls, *args, **kwds) - - -class DefaultDict(collections.defaultdict, MutableMapping[KT, VT]): - __slots__ = () - __extra__ = collections.defaultdict - - def __new__(cls, *args, **kwds): - if cls._gorg is DefaultDict: - return collections.defaultdict(*args, **kwds) - return _generic_new(collections.defaultdict, cls, *args, **kwds) - - -class Counter(collections.Counter, Dict[T, int]): - __slots__ = () - __extra__ = collections.Counter - - def __new__(cls, *args, **kwds): - if cls._gorg is Counter: - return collections.Counter(*args, **kwds) - return _generic_new(collections.Counter, cls, *args, **kwds) - - -# Determine what base class to use for Generator. -if hasattr(collections_abc, 'Generator'): - # Sufficiently recent versions of 3.5 have a Generator ABC. - _G_base = collections_abc.Generator -else: - # Fall back on the exact type. - _G_base = types.GeneratorType - - -class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co]): - __slots__ = () - __extra__ = _G_base - - def __new__(cls, *args, **kwds): - if cls._gorg is Generator: - raise TypeError("Type Generator cannot be instantiated; " - "create a subclass instead") - return _generic_new(_G_base, cls, *args, **kwds) - - -# Internal type variable used for Type[]. -CT_co = TypeVar('CT_co', covariant=True, bound=type) - - -# This is not a real generic class. Don't use outside annotations. -class Type(Generic[CT_co]): - """A special construct usable to annotate class objects. - - For example, suppose we have the following classes:: - - class User: ... # Abstract base for User classes - class BasicUser(User): ... - class ProUser(User): ... - class TeamUser(User): ... - - And a function that takes a class argument that's a subclass of - User and returns an instance of the corresponding class:: - - U = TypeVar('U', bound=User) - def new_user(user_class: Type[U]) -> U: - user = user_class() - # (Here we could write the user object to a database) - return user - - joe = new_user(BasicUser) - - At this point the type checker knows that joe has type BasicUser. - """ - __slots__ = () - __extra__ = type - - -def NamedTuple(typename, fields): - """Typed version of namedtuple. - - Usage:: - - Employee = typing.NamedTuple('Employee', [('name', str), ('id', int)]) - - This is equivalent to:: - - Employee = collections.namedtuple('Employee', ['name', 'id']) - - The resulting class has one extra attribute: _field_types, - giving a dict mapping field names to types. (The field names - are in the _fields attribute, which is part of the namedtuple - API.) - """ - fields = [(n, t) for n, t in fields] - cls = collections.namedtuple(typename, [n for n, t in fields]) - cls._field_types = dict(fields) - # Set the module to the caller's module (otherwise it'd be 'typing'). - try: - cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - return cls - - -def _check_fails(cls, other): - try: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools', 'typing']: - # Typed dicts are only for static structural subtyping. - raise TypeError('TypedDict does not support instance and class checks') - except (AttributeError, ValueError): - pass - return False - - -def _dict_new(cls, *args, **kwargs): - return dict(*args, **kwargs) - - -def _typeddict_new(cls, _typename, _fields=None, **kwargs): - total = kwargs.pop('total', True) - if _fields is None: - _fields = kwargs - elif kwargs: - raise TypeError("TypedDict takes either a dict or keyword arguments," - " but not both") - - ns = {'__annotations__': dict(_fields), '__total__': total} - try: - # Setting correct module is necessary to make typed dict classes pickleable. - ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - - return _TypedDictMeta(_typename, (), ns) - - -class _TypedDictMeta(type): - def __new__(cls, name, bases, ns, total=True): - # Create new typed dict class object. - # This method is called directly when TypedDict is subclassed, - # or via _typeddict_new when TypedDict is instantiated. This way - # TypedDict supports all three syntaxes described in its docstring. - # Subclasses and instances of TypedDict return actual dictionaries - # via _dict_new. - ns['__new__'] = _typeddict_new if name == b'TypedDict' else _dict_new - tp_dict = super(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns) - - anns = ns.get('__annotations__', {}) - msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" - anns = {n: _type_check(tp, msg) for n, tp in anns.items()} - for base in bases: - anns.update(base.__dict__.get('__annotations__', {})) - tp_dict.__annotations__ = anns - if not hasattr(tp_dict, '__total__'): - tp_dict.__total__ = total - return tp_dict - - __instancecheck__ = __subclasscheck__ = _check_fails - - -TypedDict = _TypedDictMeta(b'TypedDict', (dict,), {}) -TypedDict.__module__ = __name__ -TypedDict.__doc__ = \ - """A simple typed name space. At runtime it is equivalent to a plain dict. - - TypedDict creates a dictionary type that expects all of its - instances to have a certain set of keys, with each key - associated with a value of a consistent type. This expectation - is not checked at runtime but is only enforced by type checkers. - Usage:: - - Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) - - a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK - b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check - - assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') - - The type info could be accessed via Point2D.__annotations__. TypedDict - supports an additional equivalent form:: - - Point2D = TypedDict('Point2D', x=int, y=int, label=str) - """ - - -def NewType(name, tp): - """NewType creates simple unique types with almost zero - runtime overhead. NewType(name, tp) is considered a subtype of tp - by static type checkers. At runtime, NewType(name, tp) returns - a dummy function that simply returns its argument. Usage:: - - UserId = NewType('UserId', int) - - def name_by_id(user_id): - # type: (UserId) -> str - ... - - UserId('user') # Fails type check - - name_by_id(42) # Fails type check - name_by_id(UserId(42)) # OK - - num = UserId(5) + 1 # type: int - """ - - def new_type(x): - return x - - # Some versions of Python 2 complain because of making all strings unicode - new_type.__name__ = str(name) - new_type.__supertype__ = tp - return new_type - - -# Python-version-specific alias (Python 2: unicode; Python 3: str) -Text = unicode - - -# Constant that's True when type checking, but False here. -TYPE_CHECKING = False - - -class IO(Generic[AnyStr]): - """Generic base class for TextIO and BinaryIO. - - This is an abstract, generic version of the return of open(). - - NOTE: This does not distinguish between the different possible - classes (text vs. binary, read vs. write vs. read/write, - append-only, unbuffered). The TextIO and BinaryIO subclasses - below capture the distinctions between text vs. binary, which is - pervasive in the interface; however we currently do not offer a - way to track the other distinctions in the type system. - """ - - __slots__ = () - - @abstractproperty - def mode(self): - pass - - @abstractproperty - def name(self): - pass - - @abstractmethod - def close(self): - pass - - @abstractproperty - def closed(self): - pass - - @abstractmethod - def fileno(self): - pass - - @abstractmethod - def flush(self): - pass - - @abstractmethod - def isatty(self): - pass - - @abstractmethod - def read(self, n=-1): - pass - - @abstractmethod - def readable(self): - pass - - @abstractmethod - def readline(self, limit=-1): - pass - - @abstractmethod - def readlines(self, hint=-1): - pass - - @abstractmethod - def seek(self, offset, whence=0): - pass - - @abstractmethod - def seekable(self): - pass - - @abstractmethod - def tell(self): - pass - - @abstractmethod - def truncate(self, size=None): - pass - - @abstractmethod - def writable(self): - pass - - @abstractmethod - def write(self, s): - pass - - @abstractmethod - def writelines(self, lines): - pass - - @abstractmethod - def __enter__(self): - pass - - @abstractmethod - def __exit__(self, type, value, traceback): - pass - - -class BinaryIO(IO[bytes]): - """Typed version of the return of open() in binary mode.""" - - __slots__ = () - - @abstractmethod - def write(self, s): - pass - - @abstractmethod - def __enter__(self): - pass - - -class TextIO(IO[unicode]): - """Typed version of the return of open() in text mode.""" - - __slots__ = () - - @abstractproperty - def buffer(self): - pass - - @abstractproperty - def encoding(self): - pass - - @abstractproperty - def errors(self): - pass - - @abstractproperty - def line_buffering(self): - pass - - @abstractproperty - def newlines(self): - pass - - @abstractmethod - def __enter__(self): - pass - - -class io(object): - """Wrapper namespace for IO generic classes.""" - - __all__ = ['IO', 'TextIO', 'BinaryIO'] - IO = IO - TextIO = TextIO - BinaryIO = BinaryIO - - -io.__name__ = __name__ + b'.io' -sys.modules[io.__name__] = io - - -Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')), - lambda p: p.pattern) -Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')), - lambda m: m.re.pattern) - - -class re(object): - """Wrapper namespace for re type aliases.""" - - __all__ = ['Pattern', 'Match'] - Pattern = Pattern - Match = Match - - -re.__name__ = __name__ + b'.re' -sys.modules[re.__name__] = re diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 637292647..000000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -license-file = LICENSE diff --git a/setup.py b/setup.py deleted file mode 100644 index 49578939c..000000000 --- a/setup.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -import sys -from setuptools import setup - -if sys.version_info < (2, 7, 0) or (3, 0, 0) <= sys.version_info < (3, 4, 0): - sys.stderr.write('ERROR: You need Python 2.7 or 3.4+ ' - 'to install the typing package.\n') - exit(1) - -version = '3.10.0.0' -description = 'Type Hints for Python' -long_description = '''\ -Typing -- Type Hints for Python - -This is a backport of the standard library typing module to Python -versions older than 3.5. (See note below for newer versions.) - -Typing defines a standard notation for Python function and variable -type annotations. The notation can be used for documenting code in a -concise, standard format, and it has been designed to also be used by -static and runtime type checkers, static analyzers, IDEs and other -tools. - -NOTE: in Python 3.5 and later, the typing module lives in the stdlib, -and installing this package has NO EFFECT, because stdlib takes higher -precedence than the installation directory. To get a newer version of -the typing module in Python 3.5 or later, you have to upgrade to a -newer Python (bugfix) version. For example, typing in Python 3.6.0 is -missing the definition of 'Type' -- upgrading to 3.6.2 will fix this. - -Also note that most improvements to the typing module in Python 3.7 -will not be included in this package, since Python 3.7 has some -built-in support that is not present in older versions (See PEP 560.) - -For package maintainers, it is preferred to use -``typing;python_version<"3.5"`` if your package requires it to support -earlier Python versions. This will avoid shadowing the stdlib typing -module when your package is installed via ``pip install -t .`` on -Python 3.5 or later. -''' - -package_dir = {2: 'python2', 3: 'src'}[sys.version_info.major] - -classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Python Software Foundation License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', - 'Topic :: Software Development', -] - -setup(name='typing', - version=version, - description=description, - long_description=long_description, - author='Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Ivan Levkivskyi', - author_email='jukka.lehtosalo@iki.fi', - url='https://docs.python.org/3/library/typing.html', - project_urls={'Source': 'https://github.com/python/typing'}, - license='PSF', - keywords='typing function annotations type hints hinting checking ' - 'checker typehints typehinting typechecking backport', - package_dir={'': package_dir}, - py_modules=['typing'], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <3.5', - classifiers=classifiers) diff --git a/src/mod_generics_cache.py b/src/mod_generics_cache.py deleted file mode 100644 index 51cef0f03..000000000 --- a/src/mod_generics_cache.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Module for testing the behavior of generics across different modules.""" - -import sys -from textwrap import dedent -from typing import TypeVar, Generic, Optional - - -if sys.version_info[:2] >= (3, 6): - exec(dedent(""" - default_a: Optional['A'] = None - default_b: Optional['B'] = None - - T = TypeVar('T') - - class A(Generic[T]): - some_b: 'B' - - class B(Generic[T]): - class A(Generic[T]): - pass - - my_inner_a1: 'B.A' - my_inner_a2: A - my_outer_a: 'A' # unless somebody calls get_type_hints with localns=B.__dict__ - """)) -else: # This should stay in sync with the syntax above. - __annotations__ = dict( - default_a=Optional['A'], - default_b=Optional['B'], - ) - default_a = None - default_b = None - - T = TypeVar('T') - - class A(Generic[T]): - __annotations__ = dict( - some_b='B' - ) - - class B(Generic[T]): - class A(Generic[T]): - pass - - __annotations__ = dict( - my_inner_a1='B.A', - my_inner_a2=A, - my_outer_a='A' # unless somebody calls get_type_hints with localns=B.__dict__ - ) diff --git a/src/test_typing.py b/src/test_typing.py deleted file mode 100644 index a987a8dc7..000000000 --- a/src/test_typing.py +++ /dev/null @@ -1,2815 +0,0 @@ -import contextlib -import collections -import os -import pickle -import re -import subprocess -import sys -from unittest import TestCase, main, skipUnless, SkipTest, expectedFailure -from copy import copy, deepcopy - -from typing import Any, NoReturn -from typing import TypeVar, AnyStr -from typing import T, KT, VT # Not in __all__. -from typing import Union, Optional -from typing import Tuple, List, MutableMapping, Iterator -from typing import Callable -from typing import Generic, ClassVar, GenericMeta -from typing import cast -from typing import get_type_hints -from typing import no_type_check, no_type_check_decorator -from typing import Type -from typing import NewType -from typing import NamedTuple -from typing import IO, TextIO, BinaryIO -from typing import Pattern, Match -import abc -import typing -import weakref -try: - import collections.abc as collections_abc -except ImportError: - import collections as collections_abc # Fallback for PY3.2. - - -try: - import mod_generics_cache -except ImportError: - # try to use the builtin one, Python 3.5+ - from test import mod_generics_cache - - -PY36 = sys.version_info[:2] >= (3, 6) - - -class BaseTestCase(TestCase): - - def assertIsSubclass(self, cls, class_or_tuple, msg=None): - if not issubclass(cls, class_or_tuple): - message = '%r is not a subclass of %r' % (cls, class_or_tuple) - if msg is not None: - message += ' : %s' % msg - raise self.failureException(message) - - def assertNotIsSubclass(self, cls, class_or_tuple, msg=None): - if issubclass(cls, class_or_tuple): - message = '%r is a subclass of %r' % (cls, class_or_tuple) - if msg is not None: - message += ' : %s' % msg - raise self.failureException(message) - - def clear_caches(self): - for f in typing._cleanups: - f() - - -class Employee: - pass - - -class Manager(Employee): - pass - - -class Founder(Employee): - pass - - -class ManagingFounder(Manager, Founder): - pass - - -class AnyTests(BaseTestCase): - - def test_any_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(42, Any) - - def test_any_subclass_type_error(self): - with self.assertRaises(TypeError): - issubclass(Employee, Any) - with self.assertRaises(TypeError): - issubclass(Any, Employee) - - def test_repr(self): - self.assertEqual(repr(Any), 'typing.Any') - - def test_errors(self): - with self.assertRaises(TypeError): - issubclass(42, Any) - with self.assertRaises(TypeError): - Any[int] # Any is not a generic type. - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class A(Any): - pass - with self.assertRaises(TypeError): - class A(type(Any)): - pass - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - Any() - with self.assertRaises(TypeError): - type(Any)() - - def test_any_works_with_alias(self): - # These expressions must simply not fail. - typing.Match[Any] - typing.Pattern[Any] - typing.IO[Any] - - -class NoReturnTests(BaseTestCase): - - def test_noreturn_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(42, NoReturn) - - def test_noreturn_subclass_type_error(self): - with self.assertRaises(TypeError): - issubclass(Employee, NoReturn) - with self.assertRaises(TypeError): - issubclass(NoReturn, Employee) - - def test_repr(self): - self.assertEqual(repr(NoReturn), 'typing.NoReturn') - - def test_not_generic(self): - with self.assertRaises(TypeError): - NoReturn[int] - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class A(NoReturn): - pass - with self.assertRaises(TypeError): - class A(type(NoReturn)): - pass - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - NoReturn() - with self.assertRaises(TypeError): - type(NoReturn)() - - -class TypeVarTests(BaseTestCase): - - def test_basic_plain(self): - T = TypeVar('T') - # T equals itself. - self.assertEqual(T, T) - # T is an instance of TypeVar - self.assertIsInstance(T, TypeVar) - - def test_typevar_instance_type_error(self): - T = TypeVar('T') - with self.assertRaises(TypeError): - isinstance(42, T) - - def test_typevar_subclass_type_error(self): - T = TypeVar('T') - with self.assertRaises(TypeError): - issubclass(int, T) - with self.assertRaises(TypeError): - issubclass(T, int) - - def test_constrained_error(self): - with self.assertRaises(TypeError): - X = TypeVar('X', int) - X - - def test_union_unique(self): - X = TypeVar('X') - Y = TypeVar('Y') - self.assertNotEqual(X, Y) - self.assertEqual(Union[X], X) - self.assertNotEqual(Union[X], Union[X, Y]) - self.assertEqual(Union[X, X], X) - self.assertNotEqual(Union[X, int], Union[X]) - self.assertNotEqual(Union[X, int], Union[int]) - self.assertEqual(Union[X, int].__args__, (X, int)) - self.assertEqual(Union[X, int].__parameters__, (X,)) - self.assertIs(Union[X, int].__origin__, Union) - - def test_union_constrained(self): - A = TypeVar('A', str, bytes) - self.assertNotEqual(Union[A, str], Union[A]) - - def test_repr(self): - self.assertEqual(repr(T), '~T') - self.assertEqual(repr(KT), '~KT') - self.assertEqual(repr(VT), '~VT') - self.assertEqual(repr(AnyStr), '~AnyStr') - T_co = TypeVar('T_co', covariant=True) - self.assertEqual(repr(T_co), '+T_co') - T_contra = TypeVar('T_contra', contravariant=True) - self.assertEqual(repr(T_contra), '-T_contra') - - def test_no_redefinition(self): - self.assertNotEqual(TypeVar('T'), TypeVar('T')) - self.assertNotEqual(TypeVar('T', int, str), TypeVar('T', int, str)) - - def test_cannot_subclass_vars(self): - with self.assertRaises(TypeError): - class V(TypeVar('T')): - pass - - def test_cannot_subclass_var_itself(self): - with self.assertRaises(TypeError): - class V(TypeVar): - pass - - def test_cannot_instantiate_vars(self): - with self.assertRaises(TypeError): - TypeVar('A')() - - def test_bound_errors(self): - with self.assertRaises(TypeError): - TypeVar('X', bound=42) - with self.assertRaises(TypeError): - TypeVar('X', str, float, bound=Employee) - - def test_no_bivariant(self): - with self.assertRaises(ValueError): - TypeVar('T', covariant=True, contravariant=True) - - -class UnionTests(BaseTestCase): - - def test_basics(self): - u = Union[int, float] - self.assertNotEqual(u, Union) - - def test_subclass_error(self): - with self.assertRaises(TypeError): - issubclass(int, Union) - with self.assertRaises(TypeError): - issubclass(Union, int) - with self.assertRaises(TypeError): - issubclass(int, Union[int, str]) - with self.assertRaises(TypeError): - issubclass(Union[int, str], int) - - def test_union_any(self): - u = Union[Any] - self.assertEqual(u, Any) - u1 = Union[int, Any] - u2 = Union[Any, int] - u3 = Union[Any, object] - self.assertEqual(u1, u2) - self.assertNotEqual(u1, Any) - self.assertNotEqual(u2, Any) - self.assertNotEqual(u3, Any) - - def test_union_object(self): - u = Union[object] - self.assertEqual(u, object) - u = Union[int, object] - self.assertEqual(u, object) - u = Union[object, int] - self.assertEqual(u, object) - - def test_unordered(self): - u1 = Union[int, float] - u2 = Union[float, int] - self.assertEqual(u1, u2) - - def test_single_class_disappears(self): - t = Union[Employee] - self.assertIs(t, Employee) - - def test_base_class_disappears(self): - u = Union[Employee, Manager, int] - self.assertEqual(u, Union[int, Employee]) - u = Union[Manager, int, Employee] - self.assertEqual(u, Union[int, Employee]) - u = Union[Employee, Manager] - self.assertIs(u, Employee) - - def test_union_union(self): - u = Union[int, float] - v = Union[u, Employee] - self.assertEqual(v, Union[int, float, Employee]) - - def test_repr(self): - self.assertEqual(repr(Union), 'typing.Union') - u = Union[Employee, int] - self.assertEqual(repr(u), 'typing.Union[%s.Employee, int]' % __name__) - u = Union[int, Employee] - self.assertEqual(repr(u), 'typing.Union[int, %s.Employee]' % __name__) - T = TypeVar('T') - u = Union[T, int][int] - self.assertEqual(repr(u), repr(int)) - u = Union[List[int], int] - self.assertEqual(repr(u), 'typing.Union[typing.List[int], int]') - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(Union): - pass - with self.assertRaises(TypeError): - class C(type(Union)): - pass - with self.assertRaises(TypeError): - class C(Union[int, str]): - pass - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - Union() - with self.assertRaises(TypeError): - type(Union)() - u = Union[int, float] - with self.assertRaises(TypeError): - u() - with self.assertRaises(TypeError): - type(u)() - - def test_union_generalization(self): - self.assertFalse(Union[str, typing.Iterable[int]] == str) - self.assertFalse(Union[str, typing.Iterable[int]] == typing.Iterable[int]) - self.assertTrue(Union[str, typing.Iterable] == typing.Iterable) - - def test_union_compare_other(self): - self.assertNotEqual(Union, object) - self.assertNotEqual(Union, Any) - self.assertNotEqual(ClassVar, Union) - self.assertNotEqual(Optional, Union) - self.assertNotEqual([None], Optional) - self.assertNotEqual(Optional, typing.Mapping) - self.assertNotEqual(Optional[typing.MutableMapping], Union) - - def test_optional(self): - o = Optional[int] - u = Union[int, None] - self.assertEqual(o, u) - - def test_empty(self): - with self.assertRaises(TypeError): - Union[()] - - def test_union_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(42, Union[int, str]) - - def test_no_eval_union(self): - u = Union[int, str] - def f(x: u): ... - self.assertIs(get_type_hints(f)['x'], u) - - def test_function_repr_union(self): - def fun() -> int: ... - self.assertEqual(repr(Union[fun, int]), 'typing.Union[fun, int]') - - def test_union_str_pattern(self): - # Shouldn't crash; see http://bugs.python.org/issue25390 - A = Union[str, Pattern] - A - - def test_etree(self): - # See https://github.com/python/typing/issues/229 - # (Only relevant for Python 2.) - try: - from xml.etree.cElementTree import Element - except ImportError: - raise SkipTest("cElementTree not found") - Union[Element, str] # Shouldn't crash - - def Elem(*args): - return Element(*args) - - Union[Elem, str] # Nor should this - - -class TupleTests(BaseTestCase): - - def test_basics(self): - with self.assertRaises(TypeError): - issubclass(Tuple, Tuple[int, str]) - with self.assertRaises(TypeError): - issubclass(tuple, Tuple[int, str]) - - class TP(tuple): ... - self.assertTrue(issubclass(tuple, Tuple)) - self.assertTrue(issubclass(TP, Tuple)) - - def test_equality(self): - self.assertEqual(Tuple[int], Tuple[int]) - self.assertEqual(Tuple[int, ...], Tuple[int, ...]) - self.assertNotEqual(Tuple[int], Tuple[int, int]) - self.assertNotEqual(Tuple[int], Tuple[int, ...]) - - def test_tuple_subclass(self): - class MyTuple(tuple): - pass - self.assertTrue(issubclass(MyTuple, Tuple)) - - def test_tuple_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance((0, 0), Tuple[int, int]) - self.assertIsInstance((0, 0), Tuple) - - def test_repr(self): - self.assertEqual(repr(Tuple), 'typing.Tuple') - self.assertEqual(repr(Tuple[()]), 'typing.Tuple[()]') - self.assertEqual(repr(Tuple[int, float]), 'typing.Tuple[int, float]') - self.assertEqual(repr(Tuple[int, ...]), 'typing.Tuple[int, ...]') - - def test_errors(self): - with self.assertRaises(TypeError): - issubclass(42, Tuple) - with self.assertRaises(TypeError): - issubclass(42, Tuple[int]) - - -class CallableTests(BaseTestCase): - - def test_self_subclass(self): - with self.assertRaises(TypeError): - self.assertTrue(issubclass(type(lambda x: x), Callable[[int], int])) - self.assertTrue(issubclass(type(lambda x: x), Callable)) - - def test_eq_hash(self): - self.assertEqual(Callable[[int], int], Callable[[int], int]) - self.assertEqual(len({Callable[[int], int], Callable[[int], int]}), 1) - self.assertNotEqual(Callable[[int], int], Callable[[int], str]) - self.assertNotEqual(Callable[[int], int], Callable[[str], int]) - self.assertNotEqual(Callable[[int], int], Callable[[int, int], int]) - self.assertNotEqual(Callable[[int], int], Callable[[], int]) - self.assertNotEqual(Callable[[int], int], Callable) - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - Callable() - with self.assertRaises(TypeError): - type(Callable)() - c = Callable[[int], str] - with self.assertRaises(TypeError): - c() - with self.assertRaises(TypeError): - type(c)() - - def test_callable_wrong_forms(self): - with self.assertRaises(TypeError): - Callable[[...], int] - with self.assertRaises(TypeError): - Callable[(), int] - with self.assertRaises(TypeError): - Callable[[()], int] - with self.assertRaises(TypeError): - Callable[[int, 1], 2] - with self.assertRaises(TypeError): - Callable[int] - - def test_callable_instance_works(self): - def f(): - pass - self.assertIsInstance(f, Callable) - self.assertNotIsInstance(None, Callable) - - def test_callable_instance_type_error(self): - def f(): - pass - with self.assertRaises(TypeError): - self.assertIsInstance(f, Callable[[], None]) - with self.assertRaises(TypeError): - self.assertIsInstance(f, Callable[[], Any]) - with self.assertRaises(TypeError): - self.assertNotIsInstance(None, Callable[[], None]) - with self.assertRaises(TypeError): - self.assertNotIsInstance(None, Callable[[], Any]) - - def test_repr(self): - ct0 = Callable[[], bool] - self.assertEqual(repr(ct0), 'typing.Callable[[], bool]') - ct2 = Callable[[str, float], int] - self.assertEqual(repr(ct2), 'typing.Callable[[str, float], int]') - ctv = Callable[..., str] - self.assertEqual(repr(ctv), 'typing.Callable[..., str]') - - def test_callable_with_ellipsis(self): - - def foo(a: Callable[..., T]): - pass - - self.assertEqual(get_type_hints(foo, globals(), locals()), - {'a': Callable[..., T]}) - - def test_ellipsis_in_generic(self): - # Shouldn't crash; see https://github.com/python/typing/issues/259 - typing.List[Callable[..., str]] - - -XK = TypeVar('XK', str, bytes) -XV = TypeVar('XV') - - -class SimpleMapping(Generic[XK, XV]): - - def __getitem__(self, key: XK) -> XV: - ... - - def __setitem__(self, key: XK, value: XV): - ... - - def get(self, key: XK, default: XV = None) -> XV: - ... - - -class MySimpleMapping(SimpleMapping[XK, XV]): - - def __init__(self): - self.store = {} - - def __getitem__(self, key: str): - return self.store[key] - - def __setitem__(self, key: str, value): - self.store[key] = value - - def get(self, key: str, default=None): - try: - return self.store[key] - except KeyError: - return default - - -class ProtocolTests(BaseTestCase): - - def test_supports_int(self): - self.assertIsSubclass(int, typing.SupportsInt) - self.assertNotIsSubclass(str, typing.SupportsInt) - - def test_supports_float(self): - self.assertIsSubclass(float, typing.SupportsFloat) - self.assertNotIsSubclass(str, typing.SupportsFloat) - - def test_supports_complex(self): - - # Note: complex itself doesn't have __complex__. - class C: - def __complex__(self): - return 0j - - self.assertIsSubclass(C, typing.SupportsComplex) - self.assertNotIsSubclass(str, typing.SupportsComplex) - - def test_supports_bytes(self): - - # Note: bytes itself doesn't have __bytes__. - class B: - def __bytes__(self): - return b'' - - self.assertIsSubclass(B, typing.SupportsBytes) - self.assertNotIsSubclass(str, typing.SupportsBytes) - - def test_supports_abs(self): - self.assertIsSubclass(float, typing.SupportsAbs) - self.assertIsSubclass(int, typing.SupportsAbs) - self.assertNotIsSubclass(str, typing.SupportsAbs) - - def test_supports_round(self): - issubclass(float, typing.SupportsRound) - self.assertIsSubclass(float, typing.SupportsRound) - self.assertIsSubclass(int, typing.SupportsRound) - self.assertNotIsSubclass(str, typing.SupportsRound) - - def test_reversible(self): - self.assertIsSubclass(list, typing.Reversible) - self.assertNotIsSubclass(int, typing.Reversible) - - def test_supports_index(self): - self.assertIsSubclass(int, typing.SupportsIndex) - self.assertNotIsSubclass(str, typing.SupportsIndex) - - def test_protocol_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(0, typing.SupportsAbs) - class C1(typing.SupportsInt): - def __int__(self) -> int: - return 42 - class C2(C1): - pass - c = C2() - self.assertIsInstance(c, C1) - - -class GenericTests(BaseTestCase): - - def test_basics(self): - X = SimpleMapping[str, Any] - self.assertEqual(X.__parameters__, ()) - with self.assertRaises(TypeError): - X[str] - with self.assertRaises(TypeError): - X[str, str] - Y = SimpleMapping[XK, str] - self.assertEqual(Y.__parameters__, (XK,)) - Y[str] - with self.assertRaises(TypeError): - Y[str, str] - self.assertIsSubclass(SimpleMapping[str, int], SimpleMapping) - - def test_generic_errors(self): - T = TypeVar('T') - S = TypeVar('S') - with self.assertRaises(TypeError): - Generic[T]() - with self.assertRaises(TypeError): - Generic[T][T] - with self.assertRaises(TypeError): - Generic[T][S] - with self.assertRaises(TypeError): - isinstance([], List[int]) - with self.assertRaises(TypeError): - issubclass(list, List[int]) - with self.assertRaises(TypeError): - class NewGeneric(Generic): ... - with self.assertRaises(TypeError): - class MyGeneric(Generic[T], Generic[S]): ... - with self.assertRaises(TypeError): - class MyGeneric(List[T], Generic[S]): ... - - def test_init(self): - T = TypeVar('T') - S = TypeVar('S') - with self.assertRaises(TypeError): - Generic[T, T] - with self.assertRaises(TypeError): - Generic[T, S, T] - - @skipUnless(PY36, "__init_subclass__ support required") - def test_init_subclass(self): - class X(typing.Generic[T]): - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - cls.attr = 42 - class Y(X): - pass - self.assertEqual(Y.attr, 42) - with self.assertRaises(AttributeError): - X.attr - X.attr = 1 - Y.attr = 2 - class Z(Y): - pass - class W(X[int]): - pass - self.assertEqual(Y.attr, 2) - self.assertEqual(Z.attr, 42) - self.assertEqual(W.attr, 42) - - def test_repr(self): - self.assertEqual(repr(SimpleMapping), - __name__ + '.' + 'SimpleMapping') - self.assertEqual(repr(MySimpleMapping), - __name__ + '.' + 'MySimpleMapping') - - def test_chain_repr(self): - T = TypeVar('T') - S = TypeVar('S') - - class C(Generic[T]): - pass - - X = C[Tuple[S, T]] - self.assertEqual(X, C[Tuple[S, T]]) - self.assertNotEqual(X, C[Tuple[T, S]]) - - Y = X[T, int] - self.assertEqual(Y, X[T, int]) - self.assertNotEqual(Y, X[S, int]) - self.assertNotEqual(Y, X[T, str]) - - Z = Y[str] - self.assertEqual(Z, Y[str]) - self.assertNotEqual(Z, Y[int]) - self.assertNotEqual(Z, Y[T]) - - self.assertTrue(str(Z).endswith( - '.C[typing.Tuple[str, int]]')) - - def test_new_repr(self): - T = TypeVar('T') - U = TypeVar('U', covariant=True) - S = TypeVar('S') - - self.assertEqual(repr(List), 'typing.List') - self.assertEqual(repr(List[T]), 'typing.List[~T]') - self.assertEqual(repr(List[U]), 'typing.List[+U]') - self.assertEqual(repr(List[S][T][int]), 'typing.List[int]') - self.assertEqual(repr(List[int]), 'typing.List[int]') - - def test_new_repr_complex(self): - T = TypeVar('T') - TS = TypeVar('TS') - - self.assertEqual(repr(typing.Mapping[T, TS][TS, T]), 'typing.Mapping[~TS, ~T]') - self.assertEqual(repr(List[Tuple[T, TS]][int, T]), - 'typing.List[typing.Tuple[int, ~T]]') - self.assertEqual( - repr(List[Tuple[T, T]][List[int]]), - 'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]' - ) - - def test_new_repr_bare(self): - T = TypeVar('T') - self.assertEqual(repr(Generic[T]), 'typing.Generic[~T]') - self.assertEqual(repr(typing._Protocol[T]), 'typing.Protocol[~T]') - class C(typing.Dict[Any, Any]): ... - # this line should just work - repr(C.__mro__) - - def test_dict(self): - T = TypeVar('T') - - class B(Generic[T]): - pass - - b = B() - b.foo = 42 - self.assertEqual(b.__dict__, {'foo': 42}) - - class C(B[int]): - pass - - c = C() - c.bar = 'abc' - self.assertEqual(c.__dict__, {'bar': 'abc'}) - - def test_subscripted_generics_as_proxies(self): - T = TypeVar('T') - class C(Generic[T]): - x = 'def' - self.assertEqual(C[int].x, 'def') - self.assertEqual(C[C[int]].x, 'def') - C[C[int]].x = 'changed' - self.assertEqual(C.x, 'changed') - self.assertEqual(C[str].x, 'changed') - C[List[str]].z = 'new' - self.assertEqual(C.z, 'new') - self.assertEqual(C[Tuple[int]].z, 'new') - - self.assertEqual(C().x, 'changed') - self.assertEqual(C[Tuple[str]]().z, 'new') - - class D(C[T]): - pass - self.assertEqual(D[int].x, 'changed') - self.assertEqual(D.z, 'new') - D.z = 'from derived z' - D[int].x = 'from derived x' - self.assertEqual(C.x, 'changed') - self.assertEqual(C[int].z, 'new') - self.assertEqual(D.x, 'from derived x') - self.assertEqual(D[str].z, 'from derived z') - - def test_abc_registry_kept(self): - T = TypeVar('T') - class C(Generic[T]): ... - C.register(int) - self.assertIsInstance(1, C) - C[int] - self.assertIsInstance(1, C) - - def test_false_subclasses(self): - class MyMapping(MutableMapping[str, str]): pass - self.assertNotIsInstance({}, MyMapping) - self.assertNotIsSubclass(dict, MyMapping) - - def test_abc_bases(self): - class MM(MutableMapping[str, str]): - def __getitem__(self, k): - return None - def __setitem__(self, k, v): - pass - def __delitem__(self, k): - pass - def __iter__(self): - return iter(()) - def __len__(self): - return 0 - # this should just work - MM().update() - self.assertIsInstance(MM(), collections_abc.MutableMapping) - self.assertIsInstance(MM(), MutableMapping) - self.assertNotIsInstance(MM(), List) - self.assertNotIsInstance({}, MM) - - def test_multiple_bases(self): - class MM1(MutableMapping[str, str], collections_abc.MutableMapping): - pass - with self.assertRaises(TypeError): - # consistent MRO not possible - class MM2(collections_abc.MutableMapping, MutableMapping[str, str]): - pass - - def test_orig_bases(self): - T = TypeVar('T') - class C(typing.Dict[str, T]): ... - self.assertEqual(C.__orig_bases__, (typing.Dict[str, T],)) - - def test_naive_runtime_checks(self): - def naive_dict_check(obj, tp): - # Check if a dictionary conforms to Dict type - if len(tp.__parameters__) > 0: - raise NotImplementedError - if tp.__args__: - KT, VT = tp.__args__ - return all( - isinstance(k, KT) and isinstance(v, VT) - for k, v in obj.items() - ) - self.assertTrue(naive_dict_check({'x': 1}, typing.Dict[str, int])) - self.assertFalse(naive_dict_check({1: 'x'}, typing.Dict[str, int])) - with self.assertRaises(NotImplementedError): - naive_dict_check({1: 'x'}, typing.Dict[str, T]) - - def naive_generic_check(obj, tp): - # Check if an instance conforms to the generic class - if not hasattr(obj, '__orig_class__'): - raise NotImplementedError - return obj.__orig_class__ == tp - class Node(Generic[T]): ... - self.assertTrue(naive_generic_check(Node[int](), Node[int])) - self.assertFalse(naive_generic_check(Node[str](), Node[int])) - self.assertFalse(naive_generic_check(Node[str](), List)) - with self.assertRaises(NotImplementedError): - naive_generic_check([1, 2, 3], Node[int]) - - def naive_list_base_check(obj, tp): - # Check if list conforms to a List subclass - return all(isinstance(x, tp.__orig_bases__[0].__args__[0]) - for x in obj) - class C(List[int]): ... - self.assertTrue(naive_list_base_check([1, 2, 3], C)) - self.assertFalse(naive_list_base_check(['a', 'b'], C)) - - def test_multi_subscr_base(self): - T = TypeVar('T') - U = TypeVar('U') - V = TypeVar('V') - class C(List[T][U][V]): ... - class D(C, List[T][U][V]): ... - self.assertEqual(C.__parameters__, (V,)) - self.assertEqual(D.__parameters__, (V,)) - self.assertEqual(C[int].__parameters__, ()) - self.assertEqual(D[int].__parameters__, ()) - self.assertEqual(C[int].__args__, (int,)) - self.assertEqual(D[int].__args__, (int,)) - self.assertEqual(C.__bases__, (List,)) - self.assertEqual(D.__bases__, (C, List)) - self.assertEqual(C.__orig_bases__, (List[T][U][V],)) - self.assertEqual(D.__orig_bases__, (C, List[T][U][V])) - - def test_subscript_meta(self): - T = TypeVar('T') - self.assertEqual(Type[GenericMeta], Type[GenericMeta]) - self.assertEqual(Union[T, int][GenericMeta], Union[GenericMeta, int]) - self.assertEqual(Callable[..., GenericMeta].__args__, (Ellipsis, GenericMeta)) - - def test_generic_hashes(self): - class A(Generic[T]): - ... - - class B(Generic[T]): - class A(Generic[T]): - ... - - self.assertEqual(A, A) - self.assertEqual(mod_generics_cache.A[str], mod_generics_cache.A[str]) - self.assertEqual(B.A, B.A) - self.assertEqual(mod_generics_cache.B.A[B.A[str]], - mod_generics_cache.B.A[B.A[str]]) - - self.assertNotEqual(A, B.A) - self.assertNotEqual(A, mod_generics_cache.A) - self.assertNotEqual(A, mod_generics_cache.B.A) - self.assertNotEqual(B.A, mod_generics_cache.A) - self.assertNotEqual(B.A, mod_generics_cache.B.A) - - self.assertNotEqual(A[str], B.A[str]) - self.assertNotEqual(A[List[Any]], B.A[List[Any]]) - self.assertNotEqual(A[str], mod_generics_cache.A[str]) - self.assertNotEqual(A[str], mod_generics_cache.B.A[str]) - self.assertNotEqual(B.A[int], mod_generics_cache.A[int]) - self.assertNotEqual(B.A[List[Any]], mod_generics_cache.B.A[List[Any]]) - - self.assertNotEqual(Tuple[A[str]], Tuple[B.A[str]]) - self.assertNotEqual(Tuple[A[List[Any]]], Tuple[B.A[List[Any]]]) - self.assertNotEqual(Union[str, A[str]], Union[str, mod_generics_cache.A[str]]) - self.assertNotEqual(Union[A[str], A[str]], - Union[A[str], mod_generics_cache.A[str]]) - self.assertNotEqual(typing.FrozenSet[A[str]], - typing.FrozenSet[mod_generics_cache.B.A[str]]) - - if sys.version_info[:2] > (3, 2): - self.assertTrue(repr(Tuple[A[str]]).endswith('.A[str]]')) - self.assertTrue(repr(Tuple[B.A[str]]).endswith('.B.A[str]]')) - self.assertTrue(repr(Tuple[mod_generics_cache.A[str]]) - .endswith('mod_generics_cache.A[str]]')) - self.assertTrue(repr(Tuple[mod_generics_cache.B.A[str]]) - .endswith('mod_generics_cache.B.A[str]]')) - - def test_extended_generic_rules_eq(self): - T = TypeVar('T') - U = TypeVar('U') - self.assertEqual(Tuple[T, T][int], Tuple[int, int]) - self.assertEqual(typing.Iterable[Tuple[T, T]][T], typing.Iterable[Tuple[T, T]]) - with self.assertRaises(TypeError): - Tuple[T, int][()] - with self.assertRaises(TypeError): - Tuple[T, U][T, ...] - - self.assertEqual(Union[T, int][int], int) - self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str]) - class Base: ... - class Derived(Base): ... - self.assertEqual(Union[T, Base][Derived], Base) - with self.assertRaises(TypeError): - Union[T, int][1] - - self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT]) - self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]]) - with self.assertRaises(TypeError): - Callable[[T], U][..., int] - with self.assertRaises(TypeError): - Callable[[T], U][[], int] - - def test_extended_generic_rules_repr(self): - T = TypeVar('T') - self.assertEqual(repr(Union[Tuple, Callable]).replace('typing.', ''), - 'Union[Tuple, Callable]') - self.assertEqual(repr(Union[Tuple, Tuple[int]]).replace('typing.', ''), - 'Tuple') - self.assertEqual(repr(Callable[..., Optional[T]][int]).replace('typing.', ''), - 'Callable[..., Union[int, NoneType]]') - self.assertEqual(repr(Callable[[], List[T]][int]).replace('typing.', ''), - 'Callable[[], List[int]]') - - def test_generic_forward_ref(self): - def foobar(x: List[List['CC']]): ... - class CC: ... - self.assertEqual( - get_type_hints(foobar, globals(), locals()), - {'x': List[List[CC]]} - ) - T = TypeVar('T') - AT = Tuple[T, ...] - def barfoo(x: AT): ... - self.assertIs(get_type_hints(barfoo, globals(), locals())['x'], AT) - CT = Callable[..., List[T]] - def barfoo2(x: CT): ... - self.assertIs(get_type_hints(barfoo2, globals(), locals())['x'], CT) - - def test_extended_generic_rules_subclassing(self): - class T1(Tuple[T, KT]): ... - class T2(Tuple[T, ...]): ... - class C1(Callable[[T], T]): ... - class C2(Callable[..., int]): - def __call__(self): - return None - - self.assertEqual(T1.__parameters__, (T, KT)) - self.assertEqual(T1[int, str].__args__, (int, str)) - self.assertEqual(T1[int, T].__origin__, T1) - - self.assertEqual(T2.__parameters__, (T,)) - with self.assertRaises(TypeError): - T1[int] - with self.assertRaises(TypeError): - T2[int, str] - - self.assertEqual(repr(C1[int]).split('.')[-1], 'C1[int]') - self.assertEqual(C2.__parameters__, ()) - self.assertIsInstance(C2(), collections_abc.Callable) - self.assertIsSubclass(C2, collections_abc.Callable) - self.assertIsSubclass(C1, collections_abc.Callable) - self.assertIsInstance(T1(), tuple) - self.assertIsSubclass(T2, tuple) - self.assertIsSubclass(Tuple[int, ...], typing.Sequence) - self.assertIsSubclass(Tuple[int, ...], typing.Iterable) - - def test_fail_with_bare_union(self): - with self.assertRaises(TypeError): - List[Union] - with self.assertRaises(TypeError): - Tuple[Optional] - with self.assertRaises(TypeError): - ClassVar[ClassVar] - with self.assertRaises(TypeError): - List[ClassVar[int]] - - def test_fail_with_bare_generic(self): - T = TypeVar('T') - with self.assertRaises(TypeError): - List[Generic] - with self.assertRaises(TypeError): - Tuple[Generic[T]] - with self.assertRaises(TypeError): - List[typing._Protocol] - with self.assertRaises(TypeError): - isinstance(1, Generic) - - def test_type_erasure_special(self): - T = TypeVar('T') - # this is the only test that checks type caching - self.clear_caches() - class MyTup(Tuple[T, T]): ... - self.assertIs(MyTup[int]().__class__, MyTup) - self.assertIs(MyTup[int]().__orig_class__, MyTup[int]) - class MyCall(Callable[..., T]): - def __call__(self): return None - self.assertIs(MyCall[T]().__class__, MyCall) - self.assertIs(MyCall[T]().__orig_class__, MyCall[T]) - class MyDict(typing.Dict[T, T]): ... - self.assertIs(MyDict[int]().__class__, MyDict) - self.assertIs(MyDict[int]().__orig_class__, MyDict[int]) - class MyDef(typing.DefaultDict[str, T]): ... - self.assertIs(MyDef[int]().__class__, MyDef) - self.assertIs(MyDef[int]().__orig_class__, MyDef[int]) - # ChainMap was added in 3.3 - if sys.version_info >= (3, 3): - class MyChain(typing.ChainMap[str, T]): ... - self.assertIs(MyChain[int]().__class__, MyChain) - self.assertIs(MyChain[int]().__orig_class__, MyChain[int]) - - def test_all_repr_eq_any(self): - objs = (getattr(typing, el) for el in typing.__all__) - for obj in objs: - self.assertNotEqual(repr(obj), '') - self.assertEqual(obj, obj) - if getattr(obj, '__parameters__', None) and len(obj.__parameters__) == 1: - self.assertEqual(obj[Any].__args__, (Any,)) - if isinstance(obj, type): - for base in obj.__mro__: - self.assertNotEqual(repr(base), '') - self.assertEqual(base, base) - - def test_substitution_helper(self): - T = TypeVar('T') - KT = TypeVar('KT') - VT = TypeVar('VT') - class Map(Generic[KT, VT]): - def meth(self, k: KT, v: VT): ... - StrMap = Map[str, T] - obj = StrMap[int]() - - new_args = typing._subs_tree(obj.__orig_class__) - new_annots = {k: typing._replace_arg(v, type(obj).__parameters__, new_args) - for k, v in obj.meth.__annotations__.items()} - - self.assertEqual(new_annots, {'k': str, 'v': int}) - - def test_pickle(self): - global C # pickle wants to reference the class by name - T = TypeVar('T') - - class B(Generic[T]): - pass - - class C(B[int]): - pass - - c = C() - c.foo = 42 - c.bar = 'abc' - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(c, proto) - x = pickle.loads(z) - self.assertEqual(x.foo, 42) - self.assertEqual(x.bar, 'abc') - self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) - simples = [Any, Union, Tuple, Callable, ClassVar, List, typing.Iterable] - for s in simples: - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(s, proto) - x = pickle.loads(z) - self.assertEqual(s, x) - - def test_copy_and_deepcopy(self): - T = TypeVar('T') - class Node(Generic[T]): ... - things = [Union[T, int], Tuple[T, int], Callable[..., T], Callable[[int], int], - Tuple[Any, Any], Node[T], Node[int], Node[Any], typing.Iterable[T], - typing.Iterable[Any], typing.Iterable[int], typing.Dict[int, str], - typing.Dict[T, Any], ClassVar[int], ClassVar[List[T]], Tuple['T', 'T'], - Union['T', int], List['T'], typing.Mapping['T', int]] - for t in things + [Any]: - self.assertEqual(t, copy(t)) - self.assertEqual(t, deepcopy(t)) - if sys.version_info >= (3, 3): - # From copy module documentation: - # It does "copy" functions and classes (shallow and deeply), by returning - # the original object unchanged; this is compatible with the way these - # are treated by the pickle module. - self.assertTrue(t is copy(t)) - self.assertTrue(t is deepcopy(t)) - - def test_copy_generic_instances(self): - T = TypeVar('T') - class C(Generic[T]): - def __init__(self, attr: T) -> None: - self.attr = attr - - c = C(42) - self.assertEqual(copy(c).attr, 42) - self.assertEqual(deepcopy(c).attr, 42) - self.assertIsNot(copy(c), c) - self.assertIsNot(deepcopy(c), c) - c.attr = 1 - self.assertEqual(copy(c).attr, 1) - self.assertEqual(deepcopy(c).attr, 1) - ci = C[int](42) - self.assertEqual(copy(ci).attr, 42) - self.assertEqual(deepcopy(ci).attr, 42) - self.assertIsNot(copy(ci), ci) - self.assertIsNot(deepcopy(ci), ci) - ci.attr = 1 - self.assertEqual(copy(ci).attr, 1) - self.assertEqual(deepcopy(ci).attr, 1) - self.assertEqual(ci.__orig_class__, C[int]) - - def test_weakref_all(self): - T = TypeVar('T') - things = [Any, Union[T, int], Callable[..., T], Tuple[Any, Any], - Optional[List[int]], typing.Mapping[int, str], - typing.re.Match[bytes], typing.Iterable['whatever']] - for t in things: - self.assertEqual(weakref.ref(t)(), t) - - def test_parameterized_slots(self): - T = TypeVar('T') - class C(Generic[T]): - __slots__ = ('potato',) - - c = C() - c_int = C[int]() - self.assertEqual(C.__slots__, C[str].__slots__) - - c.potato = 0 - c_int.potato = 0 - with self.assertRaises(AttributeError): - c.tomato = 0 - with self.assertRaises(AttributeError): - c_int.tomato = 0 - - def foo(x: C['C']): ... - self.assertEqual(get_type_hints(foo, globals(), locals())['x'], C[C]) - self.assertEqual(get_type_hints(foo, globals(), locals())['x'].__slots__, - C.__slots__) - self.assertEqual(copy(C[int]), deepcopy(C[int])) - - def test_parameterized_slots_dict(self): - T = TypeVar('T') - class D(Generic[T]): - __slots__ = {'banana': 42} - - d = D() - d_int = D[int]() - self.assertEqual(D.__slots__, D[str].__slots__) - - d.banana = 'yes' - d_int.banana = 'yes' - with self.assertRaises(AttributeError): - d.foobar = 'no' - with self.assertRaises(AttributeError): - d_int.foobar = 'no' - - def test_errors(self): - with self.assertRaises(TypeError): - B = SimpleMapping[XK, Any] - - class C(Generic[B]): - pass - - def test_repr_2(self): - PY32 = sys.version_info[:2] < (3, 3) - - class C(Generic[T]): - pass - - self.assertEqual(C.__module__, __name__) - if not PY32: - self.assertEqual(C.__qualname__, - 'GenericTests.test_repr_2..C') - self.assertEqual(repr(C).split('.')[-1], 'C') - X = C[int] - self.assertEqual(X.__module__, __name__) - if not PY32: - self.assertTrue(X.__qualname__.endswith('..C')) - self.assertEqual(repr(X).split('.')[-1], 'C[int]') - - class Y(C[int]): - pass - - self.assertEqual(Y.__module__, __name__) - if not PY32: - self.assertEqual(Y.__qualname__, - 'GenericTests.test_repr_2..Y') - self.assertEqual(repr(Y).split('.')[-1], 'Y') - - def test_eq_1(self): - self.assertEqual(Generic, Generic) - self.assertEqual(Generic[T], Generic[T]) - self.assertNotEqual(Generic[KT], Generic[VT]) - - def test_eq_2(self): - - class A(Generic[T]): - pass - - class B(Generic[T]): - pass - - self.assertEqual(A, A) - self.assertNotEqual(A, B) - self.assertEqual(A[T], A[T]) - self.assertNotEqual(A[T], B[T]) - - def test_multiple_inheritance(self): - - class A(Generic[T, VT]): - pass - - class B(Generic[KT, T]): - pass - - class C(A[T, VT], Generic[VT, T, KT], B[KT, T]): - pass - - self.assertEqual(C.__parameters__, (VT, T, KT)) - - def test_nested(self): - - G = Generic - - class Visitor(G[T]): - - a = None - - def set(self, a: T): - self.a = a - - def get(self): - return self.a - - def visit(self) -> T: - return self.a - - V = Visitor[typing.List[int]] - - class IntListVisitor(V): - - def append(self, x: int): - self.a.append(x) - - a = IntListVisitor() - a.set([]) - a.append(1) - a.append(42) - self.assertEqual(a.get(), [1, 42]) - - def test_type_erasure(self): - T = TypeVar('T') - - class Node(Generic[T]): - def __init__(self, label: T, - left: 'Node[T]' = None, - right: 'Node[T]' = None): - self.label = label # type: T - self.left = left # type: Optional[Node[T]] - self.right = right # type: Optional[Node[T]] - - def foo(x: T): - a = Node(x) - b = Node[T](x) - c = Node[Any](x) - self.assertIs(type(a), Node) - self.assertIs(type(b), Node) - self.assertIs(type(c), Node) - self.assertEqual(a.label, x) - self.assertEqual(b.label, x) - self.assertEqual(c.label, x) - - foo(42) - - def test_implicit_any(self): - T = TypeVar('T') - - class C(Generic[T]): - pass - - class D(C): - pass - - self.assertEqual(D.__parameters__, ()) - - with self.assertRaises(Exception): - D[int] - with self.assertRaises(Exception): - D[Any] - with self.assertRaises(Exception): - D[T] - - def test_new_with_args(self): - - class A(Generic[T]): - pass - - class B: - def __new__(cls, arg): - # call object - obj = super().__new__(cls) - obj.arg = arg - return obj - - # mro: C, A, Generic, B, object - class C(A, B): - pass - - c = C('foo') - self.assertEqual(c.arg, 'foo') - - def test_new_with_args2(self): - - class A: - def __init__(self, arg): - self.from_a = arg - # call object - super().__init__() - - # mro: C, Generic, A, object - class C(Generic[T], A): - def __init__(self, arg): - self.from_c = arg - # call Generic - super().__init__(arg) - - c = C('foo') - self.assertEqual(c.from_a, 'foo') - self.assertEqual(c.from_c, 'foo') - - def test_new_no_args(self): - - class A(Generic[T]): - pass - - with self.assertRaises(TypeError): - A('foo') - - class B: - def __new__(cls): - # call object - obj = super().__new__(cls) - obj.from_b = 'b' - return obj - - # mro: C, A, Generic, B, object - class C(A, B): - def __init__(self, arg): - self.arg = arg - - def __new__(cls, arg): - # call A - obj = super().__new__(cls) - obj.from_c = 'c' - return obj - - c = C('foo') - self.assertEqual(c.arg, 'foo') - self.assertEqual(c.from_b, 'b') - self.assertEqual(c.from_c, 'c') - - -class ClassVarTests(BaseTestCase): - - def test_basics(self): - with self.assertRaises(TypeError): - ClassVar[1] - with self.assertRaises(TypeError): - ClassVar[int, str] - with self.assertRaises(TypeError): - ClassVar[int][str] - - def test_repr(self): - self.assertEqual(repr(ClassVar), 'typing.ClassVar') - cv = ClassVar[int] - self.assertEqual(repr(cv), 'typing.ClassVar[int]') - cv = ClassVar[Employee] - self.assertEqual(repr(cv), 'typing.ClassVar[%s.Employee]' % __name__) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(ClassVar)): - pass - with self.assertRaises(TypeError): - class C(type(ClassVar[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - ClassVar() - with self.assertRaises(TypeError): - type(ClassVar)() - with self.assertRaises(TypeError): - type(ClassVar[Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, ClassVar[int]) - with self.assertRaises(TypeError): - issubclass(int, ClassVar) - - -class CastTests(BaseTestCase): - - def test_basics(self): - self.assertEqual(cast(int, 42), 42) - self.assertEqual(cast(float, 42), 42) - self.assertIs(type(cast(float, 42)), int) - self.assertEqual(cast(Any, 42), 42) - self.assertEqual(cast(list, 42), 42) - self.assertEqual(cast(Union[str, float], 42), 42) - self.assertEqual(cast(AnyStr, 42), 42) - self.assertEqual(cast(None, 42), 42) - - def test_errors(self): - # Bogus calls are not expected to fail. - cast(42, 42) - cast('hello', 42) - - -class ForwardRefTests(BaseTestCase): - - def test_basics(self): - - class Node(Generic[T]): - - def __init__(self, label: T): - self.label = label - self.left = self.right = None - - def add_both(self, - left: 'Optional[Node[T]]', - right: 'Node[T]' = None, - stuff: int = None, - blah=None): - self.left = left - self.right = right - - def add_left(self, node: Optional['Node[T]']): - self.add_both(node, None) - - def add_right(self, node: 'Node[T]' = None): - self.add_both(None, node) - - t = Node[int] - both_hints = get_type_hints(t.add_both, globals(), locals()) - self.assertEqual(both_hints['left'], Optional[Node[T]]) - self.assertEqual(both_hints['right'], Optional[Node[T]]) - self.assertEqual(both_hints['left'], both_hints['right']) - self.assertEqual(both_hints['stuff'], Optional[int]) - self.assertNotIn('blah', both_hints) - - left_hints = get_type_hints(t.add_left, globals(), locals()) - self.assertEqual(left_hints['node'], Optional[Node[T]]) - - right_hints = get_type_hints(t.add_right, globals(), locals()) - self.assertEqual(right_hints['node'], Optional[Node[T]]) - - def test_forwardref_instance_type_error(self): - fr = typing._ForwardRef('int') - with self.assertRaises(TypeError): - isinstance(42, fr) - - def test_forwardref_subclass_type_error(self): - fr = typing._ForwardRef('int') - with self.assertRaises(TypeError): - issubclass(int, fr) - - def test_forward_equality(self): - fr = typing._ForwardRef('int') - self.assertEqual(fr, typing._ForwardRef('int')) - self.assertNotEqual(List['int'], List[int]) - - def test_forward_equality_gth(self): - c1 = typing._ForwardRef('C') - c1_gth = typing._ForwardRef('C') - c2 = typing._ForwardRef('C') - c2_gth = typing._ForwardRef('C') - - class C: - pass - def foo(a: c1_gth, b: c2_gth): - pass - - self.assertEqual(get_type_hints(foo, globals(), locals()), {'a': C, 'b': C}) - self.assertEqual(c1, c2) - self.assertEqual(c1, c1_gth) - self.assertEqual(c1_gth, c2_gth) - self.assertEqual(List[c1], List[c1_gth]) - self.assertNotEqual(List[c1], List[C]) - self.assertNotEqual(List[c1_gth], List[C]) - self.assertEqual(Union[c1, c1_gth], Union[c1]) - self.assertEqual(Union[c1, c1_gth, int], Union[c1, int]) - - def test_forward_equality_hash(self): - c1 = typing._ForwardRef('int') - c1_gth = typing._ForwardRef('int') - c2 = typing._ForwardRef('int') - c2_gth = typing._ForwardRef('int') - - def foo(a: c1_gth, b: c2_gth): - pass - get_type_hints(foo, globals(), locals()) - - self.assertEqual(hash(c1), hash(c2)) - self.assertEqual(hash(c1_gth), hash(c2_gth)) - self.assertEqual(hash(c1), hash(c1_gth)) - - def test_forward_equality_namespace(self): - class A: - pass - def namespace1(): - a = typing._ForwardRef('A') - def fun(x: a): - pass - get_type_hints(fun, globals(), locals()) - return a - - def namespace2(): - a = typing._ForwardRef('A') - - class A: - pass - def fun(x: a): - pass - - get_type_hints(fun, globals(), locals()) - return a - - self.assertEqual(namespace1(), namespace1()) - self.assertNotEqual(namespace1(), namespace2()) - - def test_forward_repr(self): - self.assertEqual(repr(List['int']), "typing.List[_ForwardRef('int')]") - - def test_union_forward(self): - - def foo(a: Union['T']): - pass - - self.assertEqual(get_type_hints(foo, globals(), locals()), - {'a': Union[T]}) - - def test_tuple_forward(self): - - def foo(a: Tuple['T']): - pass - - self.assertEqual(get_type_hints(foo, globals(), locals()), - {'a': Tuple[T]}) - - def test_forward_recursion_actually(self): - def namespace1(): - a = typing._ForwardRef('A') - A = a - def fun(x: a): pass - - ret = get_type_hints(fun, globals(), locals()) - return a - - def namespace2(): - a = typing._ForwardRef('A') - A = a - def fun(x: a): pass - - ret = get_type_hints(fun, globals(), locals()) - return a - - def cmp(o1, o2): - return o1 == o2 - - r1 = namespace1() - r2 = namespace2() - self.assertIsNot(r1, r2) - - try: - exc = RecursionError - except NameError: - exc = RuntimeError - self.assertRaises(exc, cmp, r1, r2) - - def test_union_forward_recursion(self): - ValueList = List['Value'] - Value = Union[str, ValueList] - - def c(foo: List[Value]): - pass - def d(foo: Union[Value, ValueList]): - pass - def e(foo: Union[List[Value], ValueList]): - pass - def f(foo: Union[Value, List[Value], ValueList]): - pass - - self.assertEqual(get_type_hints(c, globals(), locals()), - get_type_hints(c, globals(), locals())) - self.assertEqual(get_type_hints(c, globals(), locals()), - {'foo': List[Union[str, List[Union[str, List['Value']]]]]}) - self.assertEqual(get_type_hints(d, globals(), locals()), - {'foo': Union[str, List[Union[str, List['Value']]]]}) - self.assertEqual(get_type_hints(e, globals(), locals()), - {'foo': Union[ - List[Union[str, List[Union[str, List['Value']]]]], - List[Union[str, List['Value']]] - ]}) - self.assertEqual(get_type_hints(f, globals(), locals()), - {'foo': Union[ - str, - List[Union[str, List['Value']]], - List[Union[str, List[Union[str, List['Value']]]]] - ]}) - - def test_callable_forward(self): - - def foo(a: Callable[['T'], 'T']): - pass - - self.assertEqual(get_type_hints(foo, globals(), locals()), - {'a': Callable[[T], T]}) - - def test_callable_with_ellipsis_forward(self): - - def foo(a: 'Callable[..., T]'): - pass - - self.assertEqual(get_type_hints(foo, globals(), locals()), - {'a': Callable[..., T]}) - - def test_syntax_error(self): - - with self.assertRaises(SyntaxError): - Generic['/T'] - - def test_delayed_syntax_error(self): - - def foo(a: 'Node[T'): # noqa - pass - - with self.assertRaises(SyntaxError): - get_type_hints(foo) - - def test_type_error(self): - - def foo(a: Tuple['42']): - pass - - with self.assertRaises(TypeError): - get_type_hints(foo) - - def test_name_error(self): - - def foo(a: 'Noode[T]'): # noqa - pass - - with self.assertRaises(NameError): - get_type_hints(foo, locals()) - - def test_no_type_check(self): - - @no_type_check - def foo(a: 'whatevers') -> {}: # noqa - pass - - th = get_type_hints(foo) - self.assertEqual(th, {}) - - def test_no_type_check_class(self): - - @no_type_check - class C: - def foo(a: 'whatevers') -> {}: # noqa - pass - - cth = get_type_hints(C.foo) - self.assertEqual(cth, {}) - ith = get_type_hints(C().foo) - self.assertEqual(ith, {}) - - def test_no_type_check_no_bases(self): - class C: - def meth(self, x: int): ... - @no_type_check - class D(C): - c = C - # verify that @no_type_check never affects bases - self.assertEqual(get_type_hints(C.meth), {'x': int}) - - def test_meta_no_type_check(self): - - @no_type_check_decorator - def magic_decorator(deco): - return deco - - self.assertEqual(magic_decorator.__name__, 'magic_decorator') - - @magic_decorator - def foo(a: 'whatevers') -> {}: # noqa - pass - - @magic_decorator - class C: - def foo(a: 'whatevers') -> {}: # noqa - pass - - self.assertEqual(foo.__name__, 'foo') - th = get_type_hints(foo) - self.assertEqual(th, {}) - cth = get_type_hints(C.foo) - self.assertEqual(cth, {}) - ith = get_type_hints(C().foo) - self.assertEqual(ith, {}) - - def test_default_globals(self): - code = ("class C:\n" - " def foo(self, a: 'C') -> 'D': pass\n" - "class D:\n" - " def bar(self, b: 'D') -> C: pass\n" - ) - ns = {} - exec(code, ns) - hints = get_type_hints(ns['C'].foo) - self.assertEqual(hints, {'a': ns['C'], 'return': ns['D']}) - - -class OverloadTests(BaseTestCase): - - def test_overload_fails(self): - from typing import overload - - with self.assertRaises(RuntimeError): - - @overload - def blah(): - pass - - blah() - - def test_overload_succeeds(self): - from typing import overload - - @overload - def blah(): - pass - - def blah(): - pass - - blah() - - -ASYNCIO = sys.version_info[:2] >= (3, 5) - -ASYNCIO_TESTS = """ -import asyncio - -T_a = TypeVar('T_a') - -class AwaitableWrapper(typing.Awaitable[T_a]): - - def __init__(self, value): - self.value = value - - def __await__(self) -> typing.Iterator[T_a]: - yield - return self.value - -class AsyncIteratorWrapper(typing.AsyncIterator[T_a]): - - def __init__(self, value: typing.Iterable[T_a]): - self.value = value - - def __aiter__(self) -> typing.AsyncIterator[T_a]: - return self - - @asyncio.coroutine - def __anext__(self) -> T_a: - data = yield from self.value - if data: - return data - else: - raise StopAsyncIteration - -class ACM: - async def __aenter__(self) -> int: - return 42 - async def __aexit__(self, etype, eval, tb): - return None -""" - -if ASYNCIO: - try: - exec(ASYNCIO_TESTS) - except ImportError: - ASYNCIO = False -else: - # fake names for the sake of static analysis - asyncio = None - AwaitableWrapper = AsyncIteratorWrapper = ACM = object - -PY36_TESTS = """ -from test import ann_module, ann_module2, ann_module3 -from typing import AsyncContextManager - -class A: - y: float -class B(A): - x: ClassVar[Optional['B']] = None - y: int - b: int -class CSub(B): - z: ClassVar['CSub'] = B() -class G(Generic[T]): - lst: ClassVar[List[T]] = [] - -class NoneAndForward: - parent: 'NoneAndForward' - meaning: None - -class CoolEmployee(NamedTuple): - name: str - cool: int - -class CoolEmployeeWithDefault(NamedTuple): - name: str - cool: int = 0 - -class XMeth(NamedTuple): - x: int - def double(self): - return 2 * self.x - -class XRepr(NamedTuple): - x: int - y: int = 1 - def __str__(self): - return f'{self.x} -> {self.y}' - def __add__(self, other): - return 0 - -class HasForeignBaseClass(mod_generics_cache.A): - some_xrepr: 'XRepr' - other_a: 'mod_generics_cache.A' - -async def g_with(am: AsyncContextManager[int]): - x: int - async with am as x: - return x - -try: - g_with(ACM()).send(None) -except StopIteration as e: - assert e.args[0] == 42 -""" - -if PY36: - exec(PY36_TESTS) -else: - # fake names for the sake of static analysis - ann_module = ann_module2 = ann_module3 = None - A = B = CSub = G = CoolEmployee = CoolEmployeeWithDefault = object - XMeth = XRepr = NoneAndForward = HasForeignBaseClass = object - -gth = get_type_hints - - -class GetTypeHintTests(BaseTestCase): - def test_get_type_hints_from_various_objects(self): - # For invalid objects should fail with TypeError (not AttributeError etc). - with self.assertRaises(TypeError): - gth(123) - with self.assertRaises(TypeError): - gth('abc') - with self.assertRaises(TypeError): - gth(None) - - @skipUnless(PY36, 'Python 3.6 required') - def test_get_type_hints_modules(self): - ann_module_type_hints = {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str} - self.assertEqual(gth(ann_module), ann_module_type_hints) - self.assertEqual(gth(ann_module2), {}) - self.assertEqual(gth(ann_module3), {}) - - @skipUnless(PY36, 'Python 3.6 required') - @expectedFailure - def test_get_type_hints_modules_forwardref(self): - # FIXME: This currently exposes a bug in typing. Cached forward references - # don't account for the case where there are multiple types of the same - # name coming from different modules in the same program. - mgc_hints = {'default_a': Optional[mod_generics_cache.A], - 'default_b': Optional[mod_generics_cache.B]} - self.assertEqual(gth(mod_generics_cache), mgc_hints) - - @skipUnless(PY36, 'Python 3.6 required') - def test_get_type_hints_classes(self): - self.assertEqual(gth(ann_module.C), # gth will find the right globalns - {'y': Optional[ann_module.C]}) - self.assertIsInstance(gth(ann_module.j_class), dict) - self.assertEqual(gth(ann_module.M), {'123': 123, 'o': type}) - self.assertEqual(gth(ann_module.D), - {'j': str, 'k': str, 'y': Optional[ann_module.C]}) - self.assertEqual(gth(ann_module.Y), {'z': int}) - self.assertEqual(gth(ann_module.h_class), - {'y': Optional[ann_module.C]}) - self.assertEqual(gth(ann_module.S), {'x': str, 'y': str}) - self.assertEqual(gth(ann_module.foo), {'x': int}) - self.assertEqual(gth(NoneAndForward), - {'parent': NoneAndForward, 'meaning': type(None)}) - self.assertEqual(gth(HasForeignBaseClass), - {'some_xrepr': XRepr, 'other_a': mod_generics_cache.A, - 'some_b': mod_generics_cache.B}) - self.assertEqual(gth(XRepr.__new__), - {'x': int, 'y': int}) - self.assertEqual(gth(mod_generics_cache.B), - {'my_inner_a1': mod_generics_cache.B.A, - 'my_inner_a2': mod_generics_cache.B.A, - 'my_outer_a': mod_generics_cache.A}) - - @skipUnless(PY36, 'Python 3.6 required') - def test_respect_no_type_check(self): - @no_type_check - class NoTpCheck: - class Inn: - def __init__(self, x: 'not a type'): ... # noqa - self.assertTrue(NoTpCheck.__no_type_check__) - self.assertTrue(NoTpCheck.Inn.__init__.__no_type_check__) - self.assertEqual(gth(ann_module2.NTC.meth), {}) - class ABase(Generic[T]): - def meth(x: int): ... - @no_type_check - class Der(ABase): ... - self.assertEqual(gth(ABase.meth), {'x': int}) - - def test_get_type_hints_for_builtins(self): - # Should not fail for built-in classes and functions. - self.assertEqual(gth(int), {}) - self.assertEqual(gth(type), {}) - self.assertEqual(gth(dir), {}) - self.assertEqual(gth(len), {}) - self.assertEqual(gth(object.__str__), {}) - self.assertEqual(gth(object().__str__), {}) - self.assertEqual(gth(str.join), {}) - - def test_previous_behavior(self): - def testf(x, y): ... - testf.__annotations__['x'] = 'int' - self.assertEqual(gth(testf), {'x': int}) - def testg(x: None): ... - self.assertEqual(gth(testg), {'x': type(None)}) - - def test_get_type_hints_for_object_with_annotations(self): - class A: ... - class B: ... - b = B() - b.__annotations__ = {'x': 'A'} - self.assertEqual(gth(b, locals()), {'x': A}) - - @skipUnless(PY36, 'Python 3.6 required') - def test_get_type_hints_ClassVar(self): - self.assertEqual(gth(ann_module2.CV, ann_module2.__dict__), - {'var': typing.ClassVar[ann_module2.CV]}) - self.assertEqual(gth(B, globals()), - {'y': int, 'x': ClassVar[Optional[B]], 'b': int}) - self.assertEqual(gth(CSub, globals()), - {'z': ClassVar[CSub], 'y': int, 'b': int, - 'x': ClassVar[Optional[B]]}) - self.assertEqual(gth(G), {'lst': ClassVar[List[T]]}) - - -class CollectionsAbcTests(BaseTestCase): - - def test_hashable(self): - self.assertIsInstance(42, typing.Hashable) - self.assertNotIsInstance([], typing.Hashable) - - def test_iterable(self): - self.assertIsInstance([], typing.Iterable) - # Due to ABC caching, the second time takes a separate code - # path and could fail. So call this a few times. - self.assertIsInstance([], typing.Iterable) - self.assertIsInstance([], typing.Iterable) - self.assertNotIsInstance(42, typing.Iterable) - # Just in case, also test issubclass() a few times. - self.assertIsSubclass(list, typing.Iterable) - self.assertIsSubclass(list, typing.Iterable) - - def test_iterator(self): - it = iter([]) - self.assertIsInstance(it, typing.Iterator) - self.assertNotIsInstance(42, typing.Iterator) - - @skipUnless(ASYNCIO, 'Python 3.5 and multithreading required') - def test_awaitable(self): - ns = {} - exec( - "async def foo() -> typing.Awaitable[int]:\n" - " return await AwaitableWrapper(42)\n", - globals(), ns) - foo = ns['foo'] - g = foo() - self.assertIsInstance(g, typing.Awaitable) - self.assertNotIsInstance(foo, typing.Awaitable) - g.send(None) # Run foo() till completion, to avoid warning. - - @skipUnless(ASYNCIO, 'Python 3.5 and multithreading required') - def test_coroutine(self): - ns = {} - exec( - "async def foo():\n" - " return\n", - globals(), ns) - foo = ns['foo'] - g = foo() - self.assertIsInstance(g, typing.Coroutine) - with self.assertRaises(TypeError): - isinstance(g, typing.Coroutine[int]) - self.assertNotIsInstance(foo, typing.Coroutine) - try: - g.send(None) - except StopIteration: - pass - - @skipUnless(ASYNCIO, 'Python 3.5 and multithreading required') - def test_async_iterable(self): - base_it = range(10) # type: Iterator[int] - it = AsyncIteratorWrapper(base_it) - self.assertIsInstance(it, typing.AsyncIterable) - self.assertIsInstance(it, typing.AsyncIterable) - self.assertNotIsInstance(42, typing.AsyncIterable) - - @skipUnless(ASYNCIO, 'Python 3.5 and multithreading required') - def test_async_iterator(self): - base_it = range(10) # type: Iterator[int] - it = AsyncIteratorWrapper(base_it) - self.assertIsInstance(it, typing.AsyncIterator) - self.assertNotIsInstance(42, typing.AsyncIterator) - - def test_sized(self): - self.assertIsInstance([], typing.Sized) - self.assertNotIsInstance(42, typing.Sized) - - def test_container(self): - self.assertIsInstance([], typing.Container) - self.assertNotIsInstance(42, typing.Container) - - def test_collection(self): - if hasattr(typing, 'Collection'): - self.assertIsInstance(tuple(), typing.Collection) - self.assertIsInstance(frozenset(), typing.Collection) - self.assertIsSubclass(dict, typing.Collection) - self.assertNotIsInstance(42, typing.Collection) - - def test_abstractset(self): - self.assertIsInstance(set(), typing.AbstractSet) - self.assertNotIsInstance(42, typing.AbstractSet) - - def test_mutableset(self): - self.assertIsInstance(set(), typing.MutableSet) - self.assertNotIsInstance(frozenset(), typing.MutableSet) - - def test_mapping(self): - self.assertIsInstance({}, typing.Mapping) - self.assertNotIsInstance(42, typing.Mapping) - - def test_mutablemapping(self): - self.assertIsInstance({}, typing.MutableMapping) - self.assertNotIsInstance(42, typing.MutableMapping) - - def test_sequence(self): - self.assertIsInstance([], typing.Sequence) - self.assertNotIsInstance(42, typing.Sequence) - - def test_mutablesequence(self): - self.assertIsInstance([], typing.MutableSequence) - self.assertNotIsInstance((), typing.MutableSequence) - - def test_bytestring(self): - self.assertIsInstance(b'', typing.ByteString) - self.assertIsInstance(bytearray(b''), typing.ByteString) - - def test_list(self): - self.assertIsSubclass(list, typing.List) - - def test_deque(self): - self.assertIsSubclass(collections.deque, typing.Deque) - class MyDeque(typing.Deque[int]): ... - self.assertIsInstance(MyDeque(), collections.deque) - - def test_counter(self): - self.assertIsSubclass(collections.Counter, typing.Counter) - - def test_set(self): - self.assertIsSubclass(set, typing.Set) - self.assertNotIsSubclass(frozenset, typing.Set) - - def test_frozenset(self): - self.assertIsSubclass(frozenset, typing.FrozenSet) - self.assertNotIsSubclass(set, typing.FrozenSet) - - def test_dict(self): - self.assertIsSubclass(dict, typing.Dict) - - def test_no_list_instantiation(self): - with self.assertRaises(TypeError): - typing.List() - with self.assertRaises(TypeError): - typing.List[T]() - with self.assertRaises(TypeError): - typing.List[int]() - - def test_list_subclass(self): - - class MyList(typing.List[int]): - pass - - a = MyList() - self.assertIsInstance(a, MyList) - self.assertIsInstance(a, typing.Sequence) - - self.assertIsSubclass(MyList, list) - self.assertNotIsSubclass(list, MyList) - - def test_no_dict_instantiation(self): - with self.assertRaises(TypeError): - typing.Dict() - with self.assertRaises(TypeError): - typing.Dict[KT, VT]() - with self.assertRaises(TypeError): - typing.Dict[str, int]() - - def test_dict_subclass(self): - - class MyDict(typing.Dict[str, int]): - pass - - d = MyDict() - self.assertIsInstance(d, MyDict) - self.assertIsInstance(d, typing.MutableMapping) - - self.assertIsSubclass(MyDict, dict) - self.assertNotIsSubclass(dict, MyDict) - - def test_defaultdict_instantiation(self): - self.assertIs(type(typing.DefaultDict()), collections.defaultdict) - self.assertIs(type(typing.DefaultDict[KT, VT]()), collections.defaultdict) - self.assertIs(type(typing.DefaultDict[str, int]()), collections.defaultdict) - - def test_defaultdict_subclass(self): - - class MyDefDict(typing.DefaultDict[str, int]): - pass - - dd = MyDefDict() - self.assertIsInstance(dd, MyDefDict) - - self.assertIsSubclass(MyDefDict, collections.defaultdict) - self.assertNotIsSubclass(collections.defaultdict, MyDefDict) - - @skipUnless(sys.version_info >= (3, 3), 'ChainMap was added in 3.3') - def test_chainmap_instantiation(self): - self.assertIs(type(typing.ChainMap()), collections.ChainMap) - self.assertIs(type(typing.ChainMap[KT, VT]()), collections.ChainMap) - self.assertIs(type(typing.ChainMap[str, int]()), collections.ChainMap) - class CM(typing.ChainMap[KT, VT]): ... - self.assertIs(type(CM[int, str]()), CM) - - @skipUnless(sys.version_info >= (3, 3), 'ChainMap was added in 3.3') - def test_chainmap_subclass(self): - - class MyChainMap(typing.ChainMap[str, int]): - pass - - cm = MyChainMap() - self.assertIsInstance(cm, MyChainMap) - - self.assertIsSubclass(MyChainMap, collections.ChainMap) - self.assertNotIsSubclass(collections.ChainMap, MyChainMap) - - def test_deque_instantiation(self): - self.assertIs(type(typing.Deque()), collections.deque) - self.assertIs(type(typing.Deque[T]()), collections.deque) - self.assertIs(type(typing.Deque[int]()), collections.deque) - class D(typing.Deque[T]): ... - self.assertIs(type(D[int]()), D) - - def test_counter_instantiation(self): - self.assertIs(type(typing.Counter()), collections.Counter) - self.assertIs(type(typing.Counter[T]()), collections.Counter) - self.assertIs(type(typing.Counter[int]()), collections.Counter) - class C(typing.Counter[T]): ... - self.assertIs(type(C[int]()), C) - - def test_counter_subclass_instantiation(self): - - class MyCounter(typing.Counter[int]): - pass - - d = MyCounter() - self.assertIsInstance(d, MyCounter) - self.assertIsInstance(d, typing.Counter) - self.assertIsInstance(d, collections.Counter) - - def test_no_set_instantiation(self): - with self.assertRaises(TypeError): - typing.Set() - with self.assertRaises(TypeError): - typing.Set[T]() - with self.assertRaises(TypeError): - typing.Set[int]() - - def test_set_subclass_instantiation(self): - - class MySet(typing.Set[int]): - pass - - d = MySet() - self.assertIsInstance(d, MySet) - - def test_no_frozenset_instantiation(self): - with self.assertRaises(TypeError): - typing.FrozenSet() - with self.assertRaises(TypeError): - typing.FrozenSet[T]() - with self.assertRaises(TypeError): - typing.FrozenSet[int]() - - def test_frozenset_subclass_instantiation(self): - - class MyFrozenSet(typing.FrozenSet[int]): - pass - - d = MyFrozenSet() - self.assertIsInstance(d, MyFrozenSet) - - def test_no_tuple_instantiation(self): - with self.assertRaises(TypeError): - Tuple() - with self.assertRaises(TypeError): - Tuple[T]() - with self.assertRaises(TypeError): - Tuple[int]() - - def test_generator(self): - def foo(): - yield 42 - g = foo() - self.assertIsSubclass(type(g), typing.Generator) - - def test_no_generator_instantiation(self): - with self.assertRaises(TypeError): - typing.Generator() - with self.assertRaises(TypeError): - typing.Generator[T, T, T]() - with self.assertRaises(TypeError): - typing.Generator[int, int, int]() - - @skipUnless(PY36, 'Python 3.6 required') - def test_async_generator(self): - ns = {} - exec("async def f():\n" - " yield 42\n", globals(), ns) - g = ns['f']() - self.assertIsSubclass(type(g), typing.AsyncGenerator) - - @skipUnless(PY36, 'Python 3.6 required') - def test_no_async_generator_instantiation(self): - with self.assertRaises(TypeError): - typing.AsyncGenerator() - with self.assertRaises(TypeError): - typing.AsyncGenerator[T, T]() - with self.assertRaises(TypeError): - typing.AsyncGenerator[int, int]() - - def test_subclassing(self): - - class MMA(typing.MutableMapping): - pass - - with self.assertRaises(TypeError): # It's abstract - MMA() - - class MMC(MMA): - def __getitem__(self, k): - return None - def __setitem__(self, k, v): - pass - def __delitem__(self, k): - pass - def __iter__(self): - return iter(()) - def __len__(self): - return 0 - - self.assertEqual(len(MMC()), 0) - assert callable(MMC.update) - self.assertIsInstance(MMC(), typing.Mapping) - - class MMB(typing.MutableMapping[KT, VT]): - def __getitem__(self, k): - return None - def __setitem__(self, k, v): - pass - def __delitem__(self, k): - pass - def __iter__(self): - return iter(()) - def __len__(self): - return 0 - - self.assertEqual(len(MMB()), 0) - self.assertEqual(len(MMB[str, str]()), 0) - self.assertEqual(len(MMB[KT, VT]()), 0) - - self.assertNotIsSubclass(dict, MMA) - self.assertNotIsSubclass(dict, MMB) - - self.assertIsSubclass(MMA, typing.Mapping) - self.assertIsSubclass(MMB, typing.Mapping) - self.assertIsSubclass(MMC, typing.Mapping) - - self.assertIsInstance(MMB[KT, VT](), typing.Mapping) - self.assertIsInstance(MMB[KT, VT](), collections_abc.Mapping) - - self.assertIsSubclass(MMA, collections_abc.Mapping) - self.assertIsSubclass(MMB, collections_abc.Mapping) - self.assertIsSubclass(MMC, collections_abc.Mapping) - - self.assertIsSubclass(MMB[str, str], typing.Mapping) - self.assertIsSubclass(MMC, MMA) - - class It(typing.Iterable): ... - self.assertNotIsSubclass(list, It) - - class G(typing.Generator[int, int, int]): ... - def g(): yield 0 - self.assertIsSubclass(G, typing.Generator) - self.assertIsSubclass(G, typing.Iterable) - if hasattr(collections_abc, 'Generator'): - self.assertIsSubclass(G, collections_abc.Generator) - self.assertIsSubclass(G, collections_abc.Iterable) - self.assertNotIsSubclass(type(g), G) - - @skipUnless(PY36, 'Python 3.6 required') - def test_subclassing_async_generator(self): - class G(typing.AsyncGenerator[int, int]): - def asend(self, value): - pass - def athrow(self, typ, val=None, tb=None): - pass - - ns = {} - exec('async def g(): yield 0', globals(), ns) - g = ns['g'] - self.assertIsSubclass(G, typing.AsyncGenerator) - self.assertIsSubclass(G, typing.AsyncIterable) - self.assertIsSubclass(G, collections_abc.AsyncGenerator) - self.assertIsSubclass(G, collections_abc.AsyncIterable) - self.assertNotIsSubclass(type(g), G) - - instance = G() - self.assertIsInstance(instance, typing.AsyncGenerator) - self.assertIsInstance(instance, typing.AsyncIterable) - self.assertIsInstance(instance, collections_abc.AsyncGenerator) - self.assertIsInstance(instance, collections_abc.AsyncIterable) - self.assertNotIsInstance(type(g), G) - self.assertNotIsInstance(g, G) - - def test_subclassing_subclasshook(self): - - class Base(typing.Iterable): - @classmethod - def __subclasshook__(cls, other): - if other.__name__ == 'Foo': - return True - else: - return False - - class C(Base): ... - class Foo: ... - class Bar: ... - self.assertIsSubclass(Foo, Base) - self.assertIsSubclass(Foo, C) - self.assertNotIsSubclass(Bar, C) - - def test_subclassing_register(self): - - class A(typing.Container): ... - class B(A): ... - - class C: ... - A.register(C) - self.assertIsSubclass(C, A) - self.assertNotIsSubclass(C, B) - - class D: ... - B.register(D) - self.assertIsSubclass(D, A) - self.assertIsSubclass(D, B) - - class M(): ... - collections_abc.MutableMapping.register(M) - self.assertIsSubclass(M, typing.Mapping) - - def test_collections_as_base(self): - - class M(collections_abc.Mapping): ... - self.assertIsSubclass(M, typing.Mapping) - self.assertIsSubclass(M, typing.Iterable) - - class S(collections_abc.MutableSequence): ... - self.assertIsSubclass(S, typing.MutableSequence) - self.assertIsSubclass(S, typing.Iterable) - - class It(collections_abc.Iterable): ... - self.assertIsSubclass(It, typing.Iterable) - - class A(collections_abc.Mapping, metaclass=abc.ABCMeta): ... - class B: ... - A.register(B) - self.assertIsSubclass(B, typing.Mapping) - - -class OtherABCTests(BaseTestCase): - - def test_contextmanager(self): - @contextlib.contextmanager - def manager(): - yield 42 - - cm = manager() - self.assertIsInstance(cm, typing.ContextManager) - self.assertNotIsInstance(42, typing.ContextManager) - - @skipUnless(ASYNCIO, 'Python 3.5 required') - def test_async_contextmanager(self): - class NotACM: - pass - self.assertIsInstance(ACM(), typing.AsyncContextManager) - self.assertNotIsInstance(NotACM(), typing.AsyncContextManager) - @contextlib.contextmanager - def manager(): - yield 42 - - cm = manager() - self.assertNotIsInstance(cm, typing.AsyncContextManager) - self.assertEqual(typing.AsyncContextManager[int].__args__, (int,)) - with self.assertRaises(TypeError): - isinstance(42, typing.AsyncContextManager[int]) - with self.assertRaises(TypeError): - typing.AsyncContextManager[int, str] - - -class TypeTests(BaseTestCase): - - def test_type_basic(self): - - class User: pass - class BasicUser(User): pass - class ProUser(User): pass - - def new_user(user_class: Type[User]) -> User: - return user_class() - - new_user(BasicUser) - - def test_type_typevar(self): - - class User: pass - class BasicUser(User): pass - class ProUser(User): pass - - U = TypeVar('U', bound=User) - - def new_user(user_class: Type[U]) -> U: - return user_class() - - new_user(BasicUser) - - def test_type_optional(self): - A = Optional[Type[BaseException]] - - def foo(a: A) -> Optional[BaseException]: - if a is None: - return None - else: - return a() - - assert isinstance(foo(KeyboardInterrupt), KeyboardInterrupt) - assert foo(None) is None - - -class NewTypeTests(BaseTestCase): - - def test_basic(self): - UserId = NewType('UserId', int) - UserName = NewType('UserName', str) - self.assertIsInstance(UserId(5), int) - self.assertIsInstance(UserName('Joe'), str) - self.assertEqual(UserId(5) + 1, 6) - - def test_errors(self): - UserId = NewType('UserId', int) - UserName = NewType('UserName', str) - with self.assertRaises(TypeError): - issubclass(UserId, int) - with self.assertRaises(TypeError): - class D(UserName): - pass - - -class NamedTupleTests(BaseTestCase): - - def test_basics(self): - Emp = NamedTuple('Emp', [('name', str), ('id', int)]) - self.assertIsSubclass(Emp, tuple) - joe = Emp('Joe', 42) - jim = Emp(name='Jim', id=1) - self.assertIsInstance(joe, Emp) - self.assertIsInstance(joe, tuple) - self.assertEqual(joe.name, 'Joe') - self.assertEqual(joe.id, 42) - self.assertEqual(jim.name, 'Jim') - self.assertEqual(jim.id, 1) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp._fields, ('name', 'id')) - self.assertEqual(Emp.__annotations__, - collections.OrderedDict([('name', str), ('id', int)])) - self.assertIs(Emp._field_types, Emp.__annotations__) - - def test_namedtuple_pyversion(self): - if sys.version_info[:2] < (3, 6): - with self.assertRaises(TypeError): - NamedTuple('Name', one=int, other=str) - with self.assertRaises(TypeError): - class NotYet(NamedTuple): - whatever = 0 - - @skipUnless(PY36, 'Python 3.6 required') - def test_annotation_usage(self): - tim = CoolEmployee('Tim', 9000) - self.assertIsInstance(tim, CoolEmployee) - self.assertIsInstance(tim, tuple) - self.assertEqual(tim.name, 'Tim') - self.assertEqual(tim.cool, 9000) - self.assertEqual(CoolEmployee.__name__, 'CoolEmployee') - self.assertEqual(CoolEmployee._fields, ('name', 'cool')) - self.assertEqual(CoolEmployee.__annotations__, - collections.OrderedDict(name=str, cool=int)) - self.assertIs(CoolEmployee._field_types, CoolEmployee.__annotations__) - - @skipUnless(PY36, 'Python 3.6 required') - def test_annotation_usage_with_default(self): - jelle = CoolEmployeeWithDefault('Jelle') - self.assertIsInstance(jelle, CoolEmployeeWithDefault) - self.assertIsInstance(jelle, tuple) - self.assertEqual(jelle.name, 'Jelle') - self.assertEqual(jelle.cool, 0) - cooler_employee = CoolEmployeeWithDefault('Sjoerd', 1) - self.assertEqual(cooler_employee.cool, 1) - - self.assertEqual(CoolEmployeeWithDefault.__name__, 'CoolEmployeeWithDefault') - self.assertEqual(CoolEmployeeWithDefault._fields, ('name', 'cool')) - self.assertEqual(CoolEmployeeWithDefault._field_types, dict(name=str, cool=int)) - self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0)) - - with self.assertRaises(TypeError): - exec(""" -class NonDefaultAfterDefault(NamedTuple): - x: int = 3 - y: int -""") - - @skipUnless(PY36, 'Python 3.6 required') - def test_annotation_usage_with_methods(self): - self.assertEqual(XMeth(1).double(), 2) - self.assertEqual(XMeth(42).x, XMeth(42)[0]) - self.assertEqual(str(XRepr(42)), '42 -> 1') - self.assertEqual(XRepr(1, 2) + XRepr(3), 0) - - with self.assertRaises(AttributeError): - exec(""" -class XMethBad(NamedTuple): - x: int - def _fields(self): - return 'no chance for this' -""") - - with self.assertRaises(AttributeError): - exec(""" -class XMethBad2(NamedTuple): - x: int - def _source(self): - return 'no chance for this as well' -""") - - @skipUnless(PY36, 'Python 3.6 required') - def test_namedtuple_keyword_usage(self): - LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int) - nick = LocalEmployee('Nick', 25) - self.assertIsInstance(nick, tuple) - self.assertEqual(nick.name, 'Nick') - self.assertEqual(LocalEmployee.__name__, 'LocalEmployee') - self.assertEqual(LocalEmployee._fields, ('name', 'age')) - self.assertEqual(LocalEmployee.__annotations__, dict(name=str, age=int)) - self.assertIs(LocalEmployee._field_types, LocalEmployee.__annotations__) - with self.assertRaises(TypeError): - NamedTuple('Name', [('x', int)], y=str) - with self.assertRaises(TypeError): - NamedTuple('Name', x=1, y='a') - - @skipUnless(PY36, 'Python 3.6 required') - def test_namedtuple_special_keyword_names(self): - NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list) - self.assertEqual(NT.__name__, 'NT') - self.assertEqual(NT._fields, ('cls', 'self', 'typename', 'fields')) - a = NT(cls=str, self=42, typename='foo', fields=[('bar', tuple)]) - self.assertEqual(a.cls, str) - self.assertEqual(a.self, 42) - self.assertEqual(a.typename, 'foo') - self.assertEqual(a.fields, [('bar', tuple)]) - - @skipUnless(PY36, 'Python 3.6 required') - def test_namedtuple_errors(self): - with self.assertRaises(TypeError): - NamedTuple.__new__() - with self.assertRaises(TypeError): - NamedTuple() - with self.assertRaises(TypeError): - NamedTuple('Emp', [('name', str)], None) - with self.assertRaises(ValueError): - NamedTuple('Emp', [('_name', str)]) - - with self.assertWarns(DeprecationWarning): - Emp = NamedTuple(typename='Emp', name=str, id=int) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp._fields, ('name', 'id')) - - with self.assertWarns(DeprecationWarning): - Emp = NamedTuple('Emp', fields=[('name', str), ('id', int)]) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp._fields, ('name', 'id')) - - def test_pickle(self): - global Emp # pickle wants to reference the class by name - Emp = NamedTuple('Emp', [('name', str), ('id', int)]) - jane = Emp('jane', 37) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(jane, proto) - jane2 = pickle.loads(z) - self.assertEqual(jane2, jane) - - -class IOTests(BaseTestCase): - - def test_io(self): - - def stuff(a: IO) -> AnyStr: - return a.readline() - - a = stuff.__annotations__['a'] - self.assertEqual(a.__parameters__, (AnyStr,)) - - def test_textio(self): - - def stuff(a: TextIO) -> str: - return a.readline() - - a = stuff.__annotations__['a'] - self.assertEqual(a.__parameters__, ()) - - def test_binaryio(self): - - def stuff(a: BinaryIO) -> bytes: - return a.readline() - - a = stuff.__annotations__['a'] - self.assertEqual(a.__parameters__, ()) - - def test_io_submodule(self): - from typing.io import IO, TextIO, BinaryIO, __all__, __name__ - self.assertIs(IO, typing.IO) - self.assertIs(TextIO, typing.TextIO) - self.assertIs(BinaryIO, typing.BinaryIO) - self.assertEqual(set(__all__), set(['IO', 'TextIO', 'BinaryIO'])) - self.assertEqual(__name__, 'typing.io') - - -class RETests(BaseTestCase): - # Much of this is really testing _TypeAlias. - - def test_basics(self): - pat = re.compile('[a-z]+', re.I) - self.assertIsSubclass(pat.__class__, Pattern) - self.assertIsSubclass(type(pat), Pattern) - self.assertIsInstance(pat, Pattern) - - mat = pat.search('12345abcde.....') - self.assertIsSubclass(mat.__class__, Match) - self.assertIsSubclass(type(mat), Match) - self.assertIsInstance(mat, Match) - - # these should just work - Pattern[Union[str, bytes]] - Match[Union[bytes, str]] - - def test_alias_equality(self): - self.assertEqual(Pattern[str], Pattern[str]) - self.assertNotEqual(Pattern[str], Pattern[bytes]) - self.assertNotEqual(Pattern[str], Match[str]) - self.assertNotEqual(Pattern[str], str) - - def test_errors(self): - with self.assertRaises(TypeError): - # Doesn't fit AnyStr. - Pattern[int] - with self.assertRaises(TypeError): - # Can't change type vars? - Match[T] - m = Match[Union[str, bytes]] - with self.assertRaises(TypeError): - # Too complicated? - m[str] - with self.assertRaises(TypeError): - # We don't support isinstance(). - isinstance(42, Pattern[str]) - with self.assertRaises(TypeError): - # We don't support issubclass(). - issubclass(Pattern[bytes], Pattern[str]) - - def test_repr(self): - self.assertEqual(repr(Pattern), 'Pattern[~AnyStr]') - self.assertEqual(repr(Pattern[str]), 'Pattern[str]') - self.assertEqual(repr(Pattern[bytes]), 'Pattern[bytes]') - self.assertEqual(repr(Match), 'Match[~AnyStr]') - self.assertEqual(repr(Match[str]), 'Match[str]') - self.assertEqual(repr(Match[bytes]), 'Match[bytes]') - - def test_re_submodule(self): - from typing.re import Match, Pattern, __all__, __name__ - self.assertIs(Match, typing.Match) - self.assertIs(Pattern, typing.Pattern) - self.assertEqual(set(__all__), set(['Match', 'Pattern'])) - self.assertEqual(__name__, 'typing.re') - - def test_cannot_subclass(self): - with self.assertRaises(TypeError) as ex: - - class A(typing.Match): - pass - - self.assertEqual(str(ex.exception), - "Cannot subclass typing._TypeAlias") - - -class AllTests(BaseTestCase): - """Tests for __all__.""" - - def test_all(self): - from typing import __all__ as a - # Just spot-check the first and last of every category. - self.assertIn('AbstractSet', a) - self.assertIn('ValuesView', a) - self.assertIn('cast', a) - self.assertIn('overload', a) - if hasattr(contextlib, 'AbstractContextManager'): - self.assertIn('ContextManager', a) - # Check that io and re are not exported. - self.assertNotIn('io', a) - self.assertNotIn('re', a) - # Spot-check that stdlib modules aren't exported. - self.assertNotIn('os', a) - self.assertNotIn('sys', a) - # Check that Text is defined. - self.assertIn('Text', a) - # Check previously missing classes. - self.assertIn('SupportsBytes', a) - self.assertIn('SupportsComplex', a) - - def test_typing_compiles_with_opt(self): - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'typing.py') - try: - subprocess.check_output([sys.executable, '-OO', file_path], - stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - self.fail('Module does not compile with optimize=2 (-OO flag).') - - -if __name__ == '__main__': - main() diff --git a/src/typing.py b/src/typing.py deleted file mode 100644 index 70c59d851..000000000 --- a/src/typing.py +++ /dev/null @@ -1,2454 +0,0 @@ -import abc -from abc import abstractmethod, abstractproperty -import collections -import contextlib -import functools -import re as stdlib_re # Avoid confusion with the re we export. -import sys -import types -try: - import collections.abc as collections_abc -except ImportError: - import collections as collections_abc # Fallback for PY3.2. -if sys.version_info[:2] >= (3, 6): - import _collections_abc # Needed for private function _check_methods # noqa -try: - from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType -except ImportError: - WrapperDescriptorType = type(object.__init__) - MethodWrapperType = type(object().__str__) - MethodDescriptorType = type(str.join) - - -# Please keep __all__ alphabetized within each category. -__all__ = [ - # Super-special typing primitives. - 'Any', - 'Callable', - 'ClassVar', - 'Generic', - 'Optional', - 'Tuple', - 'Type', - 'TypeVar', - 'Union', - - # ABCs (from collections.abc). - 'AbstractSet', # collections.abc.Set. - 'GenericMeta', # subclass of abc.ABCMeta and a metaclass - # for 'Generic' and ABCs below. - 'ByteString', - 'Container', - 'ContextManager', - 'Hashable', - 'ItemsView', - 'Iterable', - 'Iterator', - 'KeysView', - 'Mapping', - 'MappingView', - 'MutableMapping', - 'MutableSequence', - 'MutableSet', - 'Sequence', - 'Sized', - 'ValuesView', - # The following are added depending on presence - # of their non-generic counterparts in stdlib: - # Awaitable, - # AsyncIterator, - # AsyncIterable, - # Coroutine, - # Collection, - # AsyncGenerator, - # AsyncContextManager - - # Structural checks, a.k.a. protocols. - 'Reversible', - 'SupportsAbs', - 'SupportsBytes', - 'SupportsComplex', - 'SupportsFloat', - 'SupportsIndex', - 'SupportsInt', - 'SupportsRound', - - # Concrete collection types. - 'Counter', - 'Deque', - 'Dict', - 'DefaultDict', - 'List', - 'Set', - 'FrozenSet', - 'NamedTuple', # Not really a type. - 'Generator', - - # One-off things. - 'AnyStr', - 'cast', - 'get_type_hints', - 'NewType', - 'no_type_check', - 'no_type_check_decorator', - 'NoReturn', - 'overload', - 'Text', - 'TYPE_CHECKING', -] - -# The pseudo-submodules 're' and 'io' are part of the public -# namespace, but excluded from __all__ because they might stomp on -# legitimate imports of those modules. - - -def _qualname(x): - if sys.version_info[:2] >= (3, 3): - return x.__qualname__ - else: - # Fall back to just name. - return x.__name__ - - -def _trim_name(nm): - whitelist = ('_TypeAlias', '_ForwardRef', '_TypingBase', '_FinalTypingBase') - if nm.startswith('_') and nm not in whitelist: - nm = nm[1:] - return nm - - -class TypingMeta(type): - """Metaclass for most types defined in typing module - (not a part of public API). - - This overrides __new__() to require an extra keyword parameter - '_root', which serves as a guard against naive subclassing of the - typing classes. Any legitimate class defined using a metaclass - derived from TypingMeta must pass _root=True. - - This also defines a dummy constructor (all the work for most typing - constructs is done in __new__) and a nicer repr(). - """ - - _is_protocol = False - - def __new__(cls, name, bases, namespace, *, _root=False): - if not _root: - raise TypeError("Cannot subclass %s" % - (', '.join(map(_type_repr, bases)) or '()')) - return super().__new__(cls, name, bases, namespace) - - def __init__(self, *args, **kwds): - pass - - def _eval_type(self, globalns, localns): - """Override this in subclasses to interpret forward references. - - For example, List['C'] is internally stored as - List[_ForwardRef('C')], which should evaluate to List[C], - where C is an object found in globalns or localns (searching - localns first, of course). - """ - return self - - def _get_type_vars(self, tvars): - pass - - def __repr__(self): - qname = _trim_name(_qualname(self)) - return '%s.%s' % (self.__module__, qname) - - -class _TypingBase(metaclass=TypingMeta, _root=True): - """Internal indicator of special typing constructs.""" - - __slots__ = ('__weakref__',) - - def __init__(self, *args, **kwds): - pass - - def __new__(cls, *args, **kwds): - """Constructor. - - This only exists to give a better error message in case - someone tries to subclass a special typing object (not a good idea). - """ - if (len(args) == 3 and - isinstance(args[0], str) and - isinstance(args[1], tuple)): - # Close enough. - raise TypeError("Cannot subclass %r" % cls) - return super().__new__(cls) - - # Things that are not classes also need these. - def _eval_type(self, globalns, localns): - return self - - def _get_type_vars(self, tvars): - pass - - def __repr__(self): - cls = type(self) - qname = _trim_name(_qualname(cls)) - return '%s.%s' % (cls.__module__, qname) - - def __call__(self, *args, **kwds): - raise TypeError("Cannot instantiate %r" % type(self)) - - -class _FinalTypingBase(_TypingBase, _root=True): - """Internal mix-in class to prevent instantiation. - - Prevents instantiation unless _root=True is given in class call. - It is used to create pseudo-singleton instances Any, Union, Optional, etc. - """ - - __slots__ = () - - def __new__(cls, *args, _root=False, **kwds): - self = super().__new__(cls, *args, **kwds) - if _root is True: - return self - raise TypeError("Cannot instantiate %r" % cls) - - def __reduce__(self): - return _trim_name(type(self).__name__) - - -class _ForwardRef(_TypingBase, _root=True): - """Internal wrapper to hold a forward reference.""" - - __slots__ = ('__forward_arg__', '__forward_code__', - '__forward_evaluated__', '__forward_value__') - - def __init__(self, arg): - super().__init__(arg) - if not isinstance(arg, str): - raise TypeError('Forward reference must be a string -- got %r' % (arg,)) - try: - code = compile(arg, '', 'eval') - except SyntaxError: - raise SyntaxError('Forward reference must be an expression -- got %r' % - (arg,)) - self.__forward_arg__ = arg - self.__forward_code__ = code - self.__forward_evaluated__ = False - self.__forward_value__ = None - - def _eval_type(self, globalns, localns): - if not self.__forward_evaluated__ or localns is not globalns: - if globalns is None and localns is None: - globalns = localns = {} - elif globalns is None: - globalns = localns - elif localns is None: - localns = globalns - self.__forward_value__ = _type_check( - eval(self.__forward_code__, globalns, localns), - "Forward references must evaluate to types.") - self.__forward_evaluated__ = True - return self.__forward_value__ - - def __eq__(self, other): - if not isinstance(other, _ForwardRef): - return NotImplemented - if self.__forward_evaluated__ and other.__forward_evaluated__: - return (self.__forward_arg__ == other.__forward_arg__ and - self.__forward_value__ == other.__forward_value__) - return self.__forward_arg__ == other.__forward_arg__ - - def __hash__(self): - return hash(self.__forward_arg__) - - def __instancecheck__(self, obj): - raise TypeError("Forward references cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Forward references cannot be used with issubclass().") - - def __repr__(self): - return '_ForwardRef(%r)' % (self.__forward_arg__,) - - -class _TypeAlias(_TypingBase, _root=True): - """Internal helper class for defining generic variants of concrete types. - - Note that this is not a type; let's call it a pseudo-type. It cannot - be used in instance and subclass checks in parameterized form, i.e. - ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning - ``False``. - """ - - __slots__ = ('name', 'type_var', 'impl_type', 'type_checker') - - def __init__(self, name, type_var, impl_type, type_checker): - """Initializer. - - Args: - name: The name, e.g. 'Pattern'. - type_var: The type parameter, e.g. AnyStr, or the - specific type, e.g. str. - impl_type: The implementation type. - type_checker: Function that takes an impl_type instance. - and returns a value that should be a type_var instance. - """ - assert isinstance(name, str), repr(name) - assert isinstance(impl_type, type), repr(impl_type) - assert not isinstance(impl_type, TypingMeta), repr(impl_type) - assert isinstance(type_var, (type, _TypingBase)), repr(type_var) - self.name = name - self.type_var = type_var - self.impl_type = impl_type - self.type_checker = type_checker - - def __repr__(self): - return "%s[%s]" % (self.name, _type_repr(self.type_var)) - - def __getitem__(self, parameter): - if not isinstance(self.type_var, TypeVar): - raise TypeError("%s cannot be further parameterized." % self) - if self.type_var.__constraints__ and isinstance(parameter, type): - if not issubclass(parameter, self.type_var.__constraints__): - raise TypeError("%s is not a valid substitution for %s." % - (parameter, self.type_var)) - if isinstance(parameter, TypeVar) and parameter is not self.type_var: - raise TypeError("%s cannot be re-parameterized." % self) - return self.__class__(self.name, parameter, - self.impl_type, self.type_checker) - - def __eq__(self, other): - if not isinstance(other, _TypeAlias): - return NotImplemented - return self.name == other.name and self.type_var == other.type_var - - def __hash__(self): - return hash((self.name, self.type_var)) - - def __instancecheck__(self, obj): - if not isinstance(self.type_var, TypeVar): - raise TypeError("Parameterized type aliases cannot be used " - "with isinstance().") - return isinstance(obj, self.impl_type) - - def __subclasscheck__(self, cls): - if not isinstance(self.type_var, TypeVar): - raise TypeError("Parameterized type aliases cannot be used " - "with issubclass().") - return issubclass(cls, self.impl_type) - - -def _get_type_vars(types, tvars): - for t in types: - if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): - t._get_type_vars(tvars) - - -def _type_vars(types): - tvars = [] - _get_type_vars(types, tvars) - return tuple(tvars) - - -def _eval_type(t, globalns, localns): - if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): - return t._eval_type(globalns, localns) - return t - - -def _type_check(arg, msg): - """Check that the argument is a type, and return it (internal helper). - - As a special case, accept None and return type(None) instead. - Also, _TypeAlias instances (e.g. Match, Pattern) are acceptable. - - The msg argument is a human-readable error message, e.g. - - "Union[arg, ...]: arg should be a type." - - We append the repr() of the actual value (truncated to 100 chars). - """ - if arg is None: - return type(None) - if isinstance(arg, str): - arg = _ForwardRef(arg) - if ( - isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or - not isinstance(arg, (type, _TypingBase)) and not callable(arg) - ): - raise TypeError(msg + " Got %.100r." % (arg,)) - # Bare Union etc. are not valid as type arguments - if ( - type(arg).__name__ in ('_Union', '_Optional') and - not getattr(arg, '__origin__', None) or - isinstance(arg, TypingMeta) and arg._gorg in (Generic, _Protocol) - ): - raise TypeError("Plain %s is not valid as type argument" % arg) - return arg - - -def _type_repr(obj): - """Return the repr() of an object, special-casing types (internal helper). - - If obj is a type, we return a shorter version than the default - type.__repr__, based on the module and qualified name, which is - typically enough to uniquely identify a type. For everything - else, we fall back on repr(obj). - """ - if isinstance(obj, type) and not isinstance(obj, TypingMeta): - if obj.__module__ == 'builtins': - return _qualname(obj) - return '%s.%s' % (obj.__module__, _qualname(obj)) - if obj is ...: - return('...') - if isinstance(obj, types.FunctionType): - return obj.__name__ - return repr(obj) - - -class _Any(_FinalTypingBase, _root=True): - """Special type indicating an unconstrained type. - - - Any is compatible with every type. - - Any assumed to have all methods. - - All values assumed to be instances of Any. - - Note that all the above statements are true from the point of view of - static type checkers. At runtime, Any should not be used with instance - or class checks. - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("Any cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Any cannot be used with issubclass().") - - -Any = _Any(_root=True) - - -class _NoReturn(_FinalTypingBase, _root=True): - """Special type indicating functions that never return. - Example:: - - from typing import NoReturn - - def stop() -> NoReturn: - raise Exception('no way') - - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - -NoReturn = _NoReturn(_root=True) - - -class TypeVar(_TypingBase, _root=True): - """Type variable. - - Usage:: - - T = TypeVar('T') # Can be anything - A = TypeVar('A', str, bytes) # Must be str or bytes - - Type variables exist primarily for the benefit of static type - checkers. They serve as the parameters for generic types as well - as for generic function definitions. See class Generic for more - information on generic types. Generic functions work as follows: - - def repeat(x: T, n: int) -> List[T]: - '''Return a list containing n references to x.''' - return [x]*n - - def longest(x: A, y: A) -> A: - '''Return the longest of two strings.''' - return x if len(x) >= len(y) else y - - The latter example's signature is essentially the overloading - of (str, str) -> str and (bytes, bytes) -> bytes. Also note - that if the arguments are instances of some subclass of str, - the return type is still plain str. - - At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError. - - Type variables defined with covariant=True or contravariant=True - can be used do declare covariant or contravariant generic types. - See PEP 484 for more details. By default generic types are invariant - in all type variables. - - Type variables can be introspected. e.g.: - - T.__name__ == 'T' - T.__constraints__ == () - T.__covariant__ == False - T.__contravariant__ = False - A.__constraints__ == (str, bytes) - """ - - __slots__ = ('__name__', '__bound__', '__constraints__', - '__covariant__', '__contravariant__') - - def __init__(self, name, *constraints, bound=None, - covariant=False, contravariant=False): - super().__init__(name, *constraints, bound=bound, - covariant=covariant, contravariant=contravariant) - self.__name__ = name - if covariant and contravariant: - raise ValueError("Bivariant types are not supported.") - self.__covariant__ = bool(covariant) - self.__contravariant__ = bool(contravariant) - if constraints and bound is not None: - raise TypeError("Constraints cannot be combined with bound=...") - if constraints and len(constraints) == 1: - raise TypeError("A single constraint is not allowed") - msg = "TypeVar(name, constraint, ...): constraints must be types." - self.__constraints__ = tuple(_type_check(t, msg) for t in constraints) - if bound: - self.__bound__ = _type_check(bound, "Bound must be a type.") - else: - self.__bound__ = None - - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - - def __repr__(self): - if self.__covariant__: - prefix = '+' - elif self.__contravariant__: - prefix = '-' - else: - prefix = '~' - return prefix + self.__name__ - - def __instancecheck__(self, instance): - raise TypeError("Type variables cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Type variables cannot be used with issubclass().") - - -# Some unconstrained type variables. These are used by the container types. -# (These are not for export.) -T = TypeVar('T') # Any type. -KT = TypeVar('KT') # Key type. -VT = TypeVar('VT') # Value type. -T_co = TypeVar('T_co', covariant=True) # Any type covariant containers. -V_co = TypeVar('V_co', covariant=True) # Any type covariant containers. -VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers. -T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. - -# A useful type variable with constraints. This represents string types. -# (This one *is* for export!) -AnyStr = TypeVar('AnyStr', bytes, str) - - -def _replace_arg(arg, tvars, args): - """An internal helper function: replace arg if it is a type variable - found in tvars with corresponding substitution from args or - with corresponding substitution sub-tree if arg is a generic type. - """ - - if tvars is None: - tvars = [] - if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)): - return arg._subs_tree(tvars, args) - if isinstance(arg, TypeVar): - for i, tvar in enumerate(tvars): - if arg == tvar: - return args[i] - return arg - - -# Special typing constructs Union, Optional, Generic, Callable and Tuple -# use three special attributes for internal bookkeeping of generic types: -# * __parameters__ is a tuple of unique free type parameters of a generic -# type, for example, Dict[T, T].__parameters__ == (T,); -# * __origin__ keeps a reference to a type that was subscripted, -# e.g., Union[T, int].__origin__ == Union; -# * __args__ is a tuple of all arguments used in subscripting, -# e.g., Dict[T, int].__args__ == (T, int). - - -def _subs_tree(cls, tvars=None, args=None): - """An internal helper function: calculate substitution tree - for generic cls after replacing its type parameters with - substitutions in tvars -> args (if any). - Repeat the same following __origin__'s. - - Return a list of arguments with all possible substitutions - performed. Arguments that are generic classes themselves are represented - as tuples (so that no new classes are created by this function). - For example: _subs_tree(List[Tuple[int, T]][str]) == [(Tuple, int, str)] - """ - - if cls.__origin__ is None: - return cls - # Make of chain of origins (i.e. cls -> cls.__origin__) - current = cls.__origin__ - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ - # Replace type variables in __args__ if asked ... - tree_args = [] - for arg in cls.__args__: - tree_args.append(_replace_arg(arg, tvars, args)) - # ... then continue replacing down the origin chain. - for ocls in orig_chain: - new_tree_args = [] - for arg in ocls.__args__: - new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) - tree_args = new_tree_args - return tree_args - - -def _remove_dups_flatten(parameters): - """An internal helper for Union creation and substitution: flatten Union's - among parameters, then remove duplicates and strict subclasses. - """ - - # Flatten out Union[Union[...], ...]. - params = [] - for p in parameters: - if isinstance(p, _Union) and p.__origin__ is Union: - params.extend(p.__args__) - elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union: - params.extend(p[1:]) - else: - params.append(p) - # Weed out strict duplicates, preserving the first of each occurrence. - all_params = set(params) - if len(all_params) < len(params): - new_params = [] - for t in params: - if t in all_params: - new_params.append(t) - all_params.remove(t) - params = new_params - assert not all_params, all_params - # Weed out subclasses. - # E.g. Union[int, Employee, Manager] == Union[int, Employee]. - # If object is present it will be sole survivor among proper classes. - # Never discard type variables. - # (In particular, Union[str, AnyStr] != AnyStr.) - all_params = set(params) - for t1 in params: - if not isinstance(t1, type): - continue - if any(isinstance(t2, type) and issubclass(t1, t2) - for t2 in all_params - {t1} - if not (isinstance(t2, GenericMeta) and - t2.__origin__ is not None)): - all_params.remove(t1) - return tuple(t for t in params if t in all_params) - - -def _check_generic(cls, parameters): - # Check correct count for parameters of a generic cls (internal helper). - if not cls.__parameters__: - raise TypeError("%s is not a generic class" % repr(cls)) - alen = len(parameters) - elen = len(cls.__parameters__) - if alen != elen: - raise TypeError("Too %s parameters for %s; actual %s, expected %s" % - ("many" if alen > elen else "few", repr(cls), alen, elen)) - - -_cleanups = [] - - -def _tp_cache(func): - """Internal wrapper caching __getitem__ of generic types with a fallback to - original function for non-hashable arguments. - """ - - cached = functools.lru_cache()(func) - _cleanups.append(cached.cache_clear) - - @functools.wraps(func) - def inner(*args, **kwds): - try: - return cached(*args, **kwds) - except TypeError: - pass # All real errors (not unhashable args) are raised below. - return func(*args, **kwds) - return inner - - -class _Union(_FinalTypingBase, _root=True): - """Union type; Union[X, Y] means either X or Y. - - To define a union, use e.g. Union[int, str]. Details: - - - The arguments must be types and there must be at least one. - - - None as an argument is a special case and is replaced by - type(None). - - - Unions of unions are flattened, e.g.:: - - Union[Union[int, str], float] == Union[int, str, float] - - - Unions of a single argument vanish, e.g.:: - - Union[int] == int # The constructor actually returns int - - - Redundant arguments are skipped, e.g.:: - - Union[int, str, int] == Union[int, str] - - - When comparing unions, the argument order is ignored, e.g.:: - - Union[int, str] == Union[str, int] - - - When two arguments have a subclass relationship, the least - derived argument is kept, e.g.:: - - class Employee: pass - class Manager(Employee): pass - Union[int, Employee, Manager] == Union[int, Employee] - Union[Manager, int, Employee] == Union[int, Employee] - Union[Employee, Manager] == Employee - - - Similar for object:: - - Union[int, object] == object - - - You cannot subclass or instantiate a union. - - - You can use Optional[X] as a shorthand for Union[X, None]. - """ - - __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__') - - def __new__(cls, parameters=None, origin=None, *args, _root=False): - self = super().__new__(cls, parameters, origin, *args, _root=_root) - if origin is None: - self.__parameters__ = None - self.__args__ = None - self.__origin__ = None - self.__tree_hash__ = hash(frozenset(('Union',))) - return self - if not isinstance(parameters, tuple): - raise TypeError("Expected parameters=") - if origin is Union: - parameters = _remove_dups_flatten(parameters) - # It's not a union if there's only one type left. - if len(parameters) == 1: - return parameters[0] - self.__parameters__ = _type_vars(parameters) - self.__args__ = parameters - self.__origin__ = origin - # Pre-calculate the __hash__ on instantiation. - # This improves speed for complex substitutions. - subs_tree = self._subs_tree() - if isinstance(subs_tree, tuple): - self.__tree_hash__ = hash(frozenset(subs_tree)) - else: - self.__tree_hash__ = hash(subs_tree) - return self - - def _eval_type(self, globalns, localns): - if self.__args__ is None: - return self - ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__) - ev_origin = _eval_type(self.__origin__, globalns, localns) - if ev_args == self.__args__ and ev_origin == self.__origin__: - # Everything is already evaluated. - return self - return self.__class__(ev_args, ev_origin, _root=True) - - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - _get_type_vars(self.__parameters__, tvars) - - def __repr__(self): - if self.__origin__ is None: - return super().__repr__() - tree = self._subs_tree() - if not isinstance(tree, tuple): - return repr(tree) - return tree[0]._tree_repr(tree) - - def _tree_repr(self, tree): - arg_list = [] - for arg in tree[1:]: - if not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - return super().__repr__() + '[%s]' % ', '.join(arg_list) - - @_tp_cache - def __getitem__(self, parameters): - if parameters == (): - raise TypeError("Cannot take a Union of no types.") - if not isinstance(parameters, tuple): - parameters = (parameters,) - if self.__origin__ is None: - msg = "Union[arg, ...]: each arg must be a type." - else: - msg = "Parameters to generic types must be types." - parameters = tuple(_type_check(p, msg) for p in parameters) - if self is not Union: - _check_generic(self, parameters) - return self.__class__(parameters, origin=self, _root=True) - - def _subs_tree(self, tvars=None, args=None): - if self is Union: - return Union # Nothing to substitute - tree_args = _subs_tree(self, tvars, args) - tree_args = _remove_dups_flatten(tree_args) - if len(tree_args) == 1: - return tree_args[0] # Union of a single type is that type - return (Union,) + tree_args - - def __eq__(self, other): - if isinstance(other, _Union): - return self.__tree_hash__ == other.__tree_hash__ - elif self is not Union: - return self._subs_tree() == other - else: - return self is other - - def __hash__(self): - return self.__tree_hash__ - - def __instancecheck__(self, obj): - raise TypeError("Unions cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Unions cannot be used with issubclass().") - - -Union = _Union(_root=True) - - -class _Optional(_FinalTypingBase, _root=True): - """Optional type. - - Optional[X] is equivalent to Union[X, None]. - """ - - __slots__ = () - - @_tp_cache - def __getitem__(self, arg): - arg = _type_check(arg, "Optional[t] requires a single type.") - return Union[arg, type(None)] - - -Optional = _Optional(_root=True) - - -def _next_in_mro(cls): - """Helper for Generic.__new__. - - Returns the class after the last occurrence of Generic or - Generic[...] in cls.__mro__. - """ - next_in_mro = object - # Look for the last occurrence of Generic or Generic[...]. - for i, c in enumerate(cls.__mro__[:-1]): - if isinstance(c, GenericMeta) and c._gorg is Generic: - next_in_mro = cls.__mro__[i + 1] - return next_in_mro - - -def _make_subclasshook(cls): - """Construct a __subclasshook__ callable that incorporates - the associated __extra__ class in subclass checks performed - against cls. - """ - if isinstance(cls.__extra__, abc.ABCMeta): - # The logic mirrors that of ABCMeta.__subclasscheck__. - # Registered classes need not be checked here because - # cls and its extra share the same _abc_registry. - def __extrahook__(subclass): - res = cls.__extra__.__subclasshook__(subclass) - if res is not NotImplemented: - return res - if cls.__extra__ in subclass.__mro__: - return True - for scls in cls.__extra__.__subclasses__(): - if isinstance(scls, GenericMeta): - continue - if issubclass(subclass, scls): - return True - return NotImplemented - else: - # For non-ABC extras we'll just call issubclass(). - def __extrahook__(subclass): - if cls.__extra__ and issubclass(subclass, cls.__extra__): - return True - return NotImplemented - return __extrahook__ - - -def _no_slots_copy(dct): - """Internal helper: copy class __dict__ and clean slots class variables. - (They will be re-created if necessary by normal class machinery.) - """ - dict_copy = dict(dct) - if '__slots__' in dict_copy: - for slot in dict_copy['__slots__']: - dict_copy.pop(slot, None) - return dict_copy - - -class GenericMeta(TypingMeta, abc.ABCMeta): - """Metaclass for generic types. - - This is a metaclass for typing.Generic and generic ABCs defined in - typing module. User defined subclasses of GenericMeta can override - __new__ and invoke super().__new__. Note that GenericMeta.__new__ - has strict rules on what is allowed in its bases argument: - * plain Generic is disallowed in bases; - * Generic[...] should appear in bases at most once; - * if Generic[...] is present, then it should list all type variables - that appear in other bases. - In addition, type of all generic bases is erased, e.g., C[int] is - stripped to plain C. - """ - - def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None, orig_bases=None): - """Create a new generic class. GenericMeta.__new__ accepts - keyword arguments that are used for internal bookkeeping, therefore - an override should pass unused keyword arguments to super(). - """ - if tvars is not None: - # Called from __getitem__() below. - assert origin is not None - assert all(isinstance(t, TypeVar) for t in tvars), tvars - else: - # Called from class statement. - assert tvars is None, tvars - assert args is None, args - assert origin is None, origin - - # Get the full set of tvars from the bases. - tvars = _type_vars(bases) - # Look for Generic[T1, ..., Tn]. - # If found, tvars must be a subset of it. - # If not found, tvars is it. - # Also check for and reject plain Generic, - # and reject multiple Generic[...]. - gvars = None - for base in bases: - if base is Generic: - raise TypeError("Cannot inherit from plain Generic") - if (isinstance(base, GenericMeta) and - base.__origin__ is Generic): - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...] multiple types.") - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - raise TypeError( - "Some type variables (%s) " - "are not listed in Generic[%s]" % - (", ".join(str(t) for t in tvars if t not in gvarset), - ", ".join(str(g) for g in gvars))) - tvars = gvars - - initial_bases = bases - if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) - - # remove bare Generic from bases if there are other generic bases - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) - namespace.update({'__origin__': origin, '__extra__': extra, - '_gorg': None if not origin else origin._gorg}) - self = super().__new__(cls, name, bases, namespace, _root=True) - super(GenericMeta, self).__setattr__('_gorg', - self if not origin else origin._gorg) - self.__parameters__ = tvars - # Be prepared that GenericMeta will be subclassed by TupleMeta - # and CallableMeta, those two allow ..., (), or [] in __args___. - self.__args__ = tuple(... if a is _TypingEllipsis else - () if a is _TypingEmpty else - a for a in args) if args else None - # Speed hack (https://github.com/python/typing/issues/196). - self.__next_in_mro__ = _next_in_mro(self) - # Preserve base classes on subclassing (__bases__ are type erased now). - if orig_bases is None: - self.__orig_bases__ = initial_bases - - # This allows unparameterized generic collections to be used - # with issubclass() and isinstance() in the same way as their - # collections.abc counterparts (e.g., isinstance([], Iterable)). - if ( - '__subclasshook__' not in namespace and extra or - # allow overriding - getattr(self.__subclasshook__, '__name__', '') == '__extrahook__' - ): - self.__subclasshook__ = _make_subclasshook(self) - if isinstance(extra, abc.ABCMeta): - self._abc_registry = extra._abc_registry - self._abc_cache = extra._abc_cache - elif origin is not None: - self._abc_registry = origin._abc_registry - self._abc_cache = origin._abc_cache - - if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2. - self.__qualname__ = origin.__qualname__ - self.__tree_hash__ = (hash(self._subs_tree()) if origin else - super(GenericMeta, self).__hash__()) - return self - - # _abc_negative_cache and _abc_negative_cache_version - # realised as descriptors, since GenClass[t1, t2, ...] always - # share subclass info with GenClass. - # This is an important memory optimization. - @property - def _abc_negative_cache(self): - if isinstance(self.__extra__, abc.ABCMeta): - return self.__extra__._abc_negative_cache - return self._gorg._abc_generic_negative_cache - - @_abc_negative_cache.setter - def _abc_negative_cache(self, value): - if self.__origin__ is None: - if isinstance(self.__extra__, abc.ABCMeta): - self.__extra__._abc_negative_cache = value - else: - self._abc_generic_negative_cache = value - - @property - def _abc_negative_cache_version(self): - if isinstance(self.__extra__, abc.ABCMeta): - return self.__extra__._abc_negative_cache_version - return self._gorg._abc_generic_negative_cache_version - - @_abc_negative_cache_version.setter - def _abc_negative_cache_version(self, value): - if self.__origin__ is None: - if isinstance(self.__extra__, abc.ABCMeta): - self.__extra__._abc_negative_cache_version = value - else: - self._abc_generic_negative_cache_version = value - - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - _get_type_vars(self.__parameters__, tvars) - - def _eval_type(self, globalns, localns): - ev_origin = (self.__origin__._eval_type(globalns, localns) - if self.__origin__ else None) - ev_args = tuple(_eval_type(a, globalns, localns) for a - in self.__args__) if self.__args__ else None - if ev_origin == self.__origin__ and ev_args == self.__args__: - return self - return self.__class__(self.__name__, - self.__bases__, - _no_slots_copy(self.__dict__), - tvars=_type_vars(ev_args) if ev_args else None, - args=ev_args, - origin=ev_origin, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - def __repr__(self): - if self.__origin__ is None: - return super().__repr__() - return self._tree_repr(self._subs_tree()) - - def _tree_repr(self, tree): - arg_list = [] - for arg in tree[1:]: - if arg == (): - arg_list.append('()') - elif not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - return super().__repr__() + '[%s]' % ', '.join(arg_list) - - def _subs_tree(self, tvars=None, args=None): - if self.__origin__ is None: - return self - tree_args = _subs_tree(self, tvars, args) - return (self._gorg,) + tuple(tree_args) - - def __eq__(self, other): - if not isinstance(other, GenericMeta): - return NotImplemented - if self.__origin__ is None or other.__origin__ is None: - return self is other - return self.__tree_hash__ == other.__tree_hash__ - - def __hash__(self): - return self.__tree_hash__ - - @_tp_cache - def __getitem__(self, params): - if not isinstance(params, tuple): - params = (params,) - if not params and self._gorg is not Tuple: - raise TypeError( - "Parameter list to %s[...] cannot be empty" % _qualname(self)) - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if self is Generic: - # Generic can only be subscripted with unique type variables. - if not all(isinstance(p, TypeVar) for p in params): - raise TypeError( - "Parameters to Generic[...] must all be type variables") - if len(set(params)) != len(params): - raise TypeError( - "Parameters to Generic[...] must all be unique") - tvars = params - args = params - elif self in (Tuple, Callable): - tvars = _type_vars(params) - args = params - elif self is _Protocol: - # _Protocol is internal, don't check anything. - tvars = params - args = params - elif self.__origin__ in (Generic, _Protocol): - # Can't subscript Generic[...] or _Protocol[...]. - raise TypeError("Cannot subscript already-subscripted %s" % - repr(self)) - else: - # Subscripting a regular Generic subclass. - _check_generic(self, params) - tvars = _type_vars(params) - args = params - - prepend = (self,) if self.__origin__ is None else () - return self.__class__(self.__name__, - prepend + self.__bases__, - _no_slots_copy(self.__dict__), - tvars=tvars, - args=args, - origin=self, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - def __subclasscheck__(self, cls): - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False - if self is Generic: - raise TypeError("Class %r cannot be used with class " - "or instance checks" % self) - return super().__subclasscheck__(cls) - - def __instancecheck__(self, instance): - # Since we extend ABC.__subclasscheck__ and - # ABC.__instancecheck__ inlines the cache checking done by the - # latter, we must extend __instancecheck__ too. For simplicity - # we just skip the cache check -- instance checks for generic - # classes are supposed to be rare anyways. - return issubclass(instance.__class__, self) - - def __setattr__(self, attr, value): - # We consider all the subscripted generics as proxies for original class - if ( - attr.startswith('__') and attr.endswith('__') or - attr.startswith('_abc_') or - self._gorg is None # The class is not fully created, see #typing/506 - ): - super(GenericMeta, self).__setattr__(attr, value) - else: - super(GenericMeta, self._gorg).__setattr__(attr, value) - - -# Prevent checks for Generic to crash when defining Generic. -Generic = None - - -def _generic_new(base_cls, cls, *args, **kwds): - # Assure type is erased on instantiation, - # but attempt to store it in __orig_class__ - if cls.__origin__ is None: - if (base_cls.__new__ is object.__new__ and - cls.__init__ is not object.__init__): - return base_cls.__new__(cls) - else: - return base_cls.__new__(cls, *args, **kwds) - else: - origin = cls._gorg - if (base_cls.__new__ is object.__new__ and - cls.__init__ is not object.__init__): - obj = base_cls.__new__(origin) - else: - obj = base_cls.__new__(origin, *args, **kwds) - try: - obj.__orig_class__ = cls - except AttributeError: - pass - obj.__init__(*args, **kwds) - return obj - - -class Generic(metaclass=GenericMeta): - """Abstract base class for generic types. - - A generic type is typically declared by inheriting from - this class parameterized with one or more type variables. - For example, a generic mapping type might be defined as:: - - class Mapping(Generic[KT, VT]): - def __getitem__(self, key: KT) -> VT: - ... - # Etc. - - This class can then be used as follows:: - - def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: - try: - return mapping[key] - except KeyError: - return default - """ - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Generic: - raise TypeError("Type Generic cannot be instantiated; " - "it can be used only as a base class") - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - -class _TypingEmpty: - """Internal placeholder for () or []. Used by TupleMeta and CallableMeta - to allow empty list/tuple in specific places, without allowing them - to sneak in where prohibited. - """ - - -class _TypingEllipsis: - """Internal placeholder for ... (ellipsis).""" - - -class TupleMeta(GenericMeta): - """Metaclass for Tuple (internal).""" - - @_tp_cache - def __getitem__(self, parameters): - if self.__origin__ is not None or self._gorg is not Tuple: - # Normal generic rules apply if this is not the first subscription - # or a subscription of a subclass. - return super().__getitem__(parameters) - if parameters == (): - return super().__getitem__((_TypingEmpty,)) - if not isinstance(parameters, tuple): - parameters = (parameters,) - if len(parameters) == 2 and parameters[1] is ...: - msg = "Tuple[t, ...]: t must be a type." - p = _type_check(parameters[0], msg) - return super().__getitem__((p, _TypingEllipsis)) - msg = "Tuple[t0, t1, ...]: each t must be a type." - parameters = tuple(_type_check(p, msg) for p in parameters) - return super().__getitem__(parameters) - - def __instancecheck__(self, obj): - if self.__args__ is None: - return isinstance(obj, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with isinstance().") - - def __subclasscheck__(self, cls): - if self.__args__ is None: - return issubclass(cls, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with issubclass().") - - -class Tuple(tuple, extra=tuple, metaclass=TupleMeta): - """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. - - Example: Tuple[T1, T2] is a tuple of two elements corresponding - to type variables T1 and T2. Tuple[int, float, str] is a tuple - of an int, a float and a string. - - To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. - """ - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Tuple: - raise TypeError("Type Tuple cannot be instantiated; " - "use tuple() instead") - return _generic_new(tuple, cls, *args, **kwds) - - -class CallableMeta(GenericMeta): - """Metaclass for Callable (internal).""" - - def __repr__(self): - if self.__origin__ is None: - return super().__repr__() - return self._tree_repr(self._subs_tree()) - - def _tree_repr(self, tree): - if self._gorg is not Callable: - return super()._tree_repr(tree) - # For actual Callable (not its subclass) we override - # super()._tree_repr() for nice formatting. - arg_list = [] - for arg in tree[1:]: - if not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - if arg_list[0] == '...': - return repr(tree[0]) + '[..., %s]' % arg_list[1] - return (repr(tree[0]) + - '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) - - def __getitem__(self, parameters): - """A thin wrapper around __getitem_inner__ to provide the latter - with hashable arguments to improve speed. - """ - - if self.__origin__ is not None or self._gorg is not Callable: - return super().__getitem__(parameters) - if not isinstance(parameters, tuple) or len(parameters) != 2: - raise TypeError("Callable must be used as " - "Callable[[arg, ...], result].") - args, result = parameters - if args is Ellipsis: - parameters = (Ellipsis, result) - else: - if not isinstance(args, list): - raise TypeError("Callable[args, result]: args must be a list." - " Got %.100r." % (args,)) - parameters = (tuple(args), result) - return self.__getitem_inner__(parameters) - - @_tp_cache - def __getitem_inner__(self, parameters): - args, result = parameters - msg = "Callable[args, result]: result must be a type." - result = _type_check(result, msg) - if args is Ellipsis: - return super().__getitem__((_TypingEllipsis, result)) - msg = "Callable[[arg, ...], result]: each arg must be a type." - args = tuple(_type_check(arg, msg) for arg in args) - parameters = args + (result,) - return super().__getitem__(parameters) - - -class Callable(extra=collections_abc.Callable, metaclass=CallableMeta): - """Callable type; Callable[[int], str] is a function of (int) -> str. - - The subscription syntax must always be used with exactly two - values: the argument list and the return type. The argument list - must be a list of types or ellipsis; the return type must be a single type. - - There is no syntax to indicate optional or keyword arguments, - such function types are rarely used as callback types. - """ - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Callable: - raise TypeError("Type Callable cannot be instantiated; " - "use a non-abstract subclass instead") - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - -class _ClassVar(_FinalTypingBase, _root=True): - """Special type construct to mark class variables. - - An annotation wrapped in ClassVar indicates that a given - attribute is intended to be used as a class variable and - should not be set on instances of that class. Usage:: - - class Starship: - stats: ClassVar[Dict[str, int]] = {} # class variable - damage: int = 10 # instance variable - - ClassVar accepts only types and cannot be further subscribed. - - Note that ClassVar is not a class itself, and should not - be used with isinstance() or issubclass(). - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(_type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = _eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(_type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _ClassVar): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - -ClassVar = _ClassVar(_root=True) - - -def cast(typ, val): - """Cast a value to a type. - - This returns the value unchanged. To the type checker this - signals that the return value has the designated type, but at - runtime we intentionally don't check anything (we want this - to be as fast as possible). - """ - return val - - -def _get_defaults(func): - """Internal helper to extract the default arguments, by name.""" - try: - code = func.__code__ - except AttributeError: - # Some built-in functions don't have __code__, __defaults__, etc. - return {} - pos_count = code.co_argcount - arg_names = code.co_varnames - arg_names = arg_names[:pos_count] - defaults = func.__defaults__ or () - kwdefaults = func.__kwdefaults__ - res = dict(kwdefaults) if kwdefaults else {} - pos_offset = pos_count - len(defaults) - for name, value in zip(arg_names[pos_offset:], defaults): - assert name not in res - res[name] = value - return res - - -_allowed_types = (types.FunctionType, types.BuiltinFunctionType, - types.MethodType, types.ModuleType, - WrapperDescriptorType, MethodWrapperType, MethodDescriptorType) - - -def get_type_hints(obj, globalns=None, localns=None): - """Return type hints for an object. - - This is often the same as obj.__annotations__, but it handles - forward references encoded as string literals, and if necessary - adds Optional[t] if a default value equal to None is set. - - The argument may be a module, class, method, or function. The annotations - are returned as a dictionary. For classes, annotations include also - inherited members. - - TypeError is raised if the argument is not of a type that can contain - annotations, and an empty dictionary is returned if no annotations are - present. - - BEWARE -- the behavior of globalns and localns is counterintuitive - (unless you are familiar with how eval() and exec() work). The - search order is locals first, then globals. - - - If no dict arguments are passed, an attempt is made to use the - globals from obj (or the respective module's globals for classes), - and these are also used as the locals. If the object does not appear - to have globals, an empty dictionary is used. - - - If one dict argument is passed, it is used for both globals and - locals. - - - If two dict arguments are passed, they specify globals and - locals, respectively. - """ - - if getattr(obj, '__no_type_check__', None): - return {} - # Classes require a special treatment. - if isinstance(obj, type): - hints = {} - for base in reversed(obj.__mro__): - if globalns is None: - base_globals = sys.modules[base.__module__].__dict__ - else: - base_globals = globalns - ann = base.__dict__.get('__annotations__', {}) - for name, value in ann.items(): - if value is None: - value = type(None) - if isinstance(value, str): - value = _ForwardRef(value) - value = _eval_type(value, base_globals, localns) - hints[name] = value - return hints - - if globalns is None: - if isinstance(obj, types.ModuleType): - globalns = obj.__dict__ - else: - globalns = getattr(obj, '__globals__', {}) - if localns is None: - localns = globalns - elif localns is None: - localns = globalns - hints = getattr(obj, '__annotations__', None) - if hints is None: - # Return empty annotations for something that _could_ have them. - if isinstance(obj, _allowed_types): - return {} - else: - raise TypeError('{!r} is not a module, class, method, ' - 'or function.'.format(obj)) - defaults = _get_defaults(obj) - hints = dict(hints) - for name, value in hints.items(): - if value is None: - value = type(None) - if isinstance(value, str): - value = _ForwardRef(value) - value = _eval_type(value, globalns, localns) - if name in defaults and defaults[name] is None: - value = Optional[value] - hints[name] = value - return hints - - -def no_type_check(arg): - """Decorator to indicate that annotations are not type hints. - - The argument must be a class or function; if it is a class, it - applies recursively to all methods and classes defined in that class - (but not to methods defined in its superclasses or subclasses). - - This mutates the function(s) or class(es) in place. - """ - if isinstance(arg, type): - arg_attrs = arg.__dict__.copy() - for attr, val in arg.__dict__.items(): - if val in arg.__bases__ + (arg,): - arg_attrs.pop(attr) - for obj in arg_attrs.values(): - if isinstance(obj, types.FunctionType): - obj.__no_type_check__ = True - if isinstance(obj, type): - no_type_check(obj) - try: - arg.__no_type_check__ = True - except TypeError: # built-in classes - pass - return arg - - -def no_type_check_decorator(decorator): - """Decorator to give another decorator the @no_type_check effect. - - This wraps the decorator with something that wraps the decorated - function in @no_type_check. - """ - - @functools.wraps(decorator) - def wrapped_decorator(*args, **kwds): - func = decorator(*args, **kwds) - func = no_type_check(func) - return func - - return wrapped_decorator - - -def _overload_dummy(*args, **kwds): - """Helper for @overload to raise when called.""" - raise NotImplementedError( - "You should not call an overloaded function. " - "A series of @overload-decorated functions " - "outside a stub module should always be followed " - "by an implementation that is not @overload-ed.") - - -def overload(func): - """Decorator for overloaded functions/methods. - - In a stub file, place two or more stub definitions for the same - function in a row, each decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - - In a non-stub file (i.e. a regular .py file), do the same but - follow it with an implementation. The implementation should *not* - be decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - def utf8(value): - # implementation goes here - """ - return _overload_dummy - - -class _ProtocolMeta(GenericMeta): - """Internal metaclass for _Protocol. - - This exists so _Protocol classes can be generic without deriving - from Generic. - """ - - def __instancecheck__(self, obj): - if _Protocol not in self.__bases__: - return super().__instancecheck__(obj) - raise TypeError("Protocols cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - if not self._is_protocol: - # No structural checks since this isn't a protocol. - return NotImplemented - - if self is _Protocol: - # Every class is a subclass of the empty protocol. - return True - - # Find all attributes defined in the protocol. - attrs = self._get_protocol_attrs() - - for attr in attrs: - if not any(attr in d.__dict__ for d in cls.__mro__): - return False - return True - - def _get_protocol_attrs(self): - # Get all Protocol base classes. - protocol_bases = [] - for c in self.__mro__: - if getattr(c, '_is_protocol', False) and c.__name__ != '_Protocol': - protocol_bases.append(c) - - # Get attributes included in protocol. - attrs = set() - for base in protocol_bases: - for attr in base.__dict__.keys(): - # Include attributes not defined in any non-protocol bases. - for c in self.__mro__: - if (c is not base and attr in c.__dict__ and - not getattr(c, '_is_protocol', False)): - break - else: - if (not attr.startswith('_abc_') and - attr != '__abstractmethods__' and - attr != '__annotations__' and - attr != '__weakref__' and - attr != '_is_protocol' and - attr != '_gorg' and - attr != '__dict__' and - attr != '__args__' and - attr != '__slots__' and - attr != '_get_protocol_attrs' and - attr != '__next_in_mro__' and - attr != '__parameters__' and - attr != '__origin__' and - attr != '__orig_bases__' and - attr != '__extra__' and - attr != '__tree_hash__' and - attr != '__module__'): - attrs.add(attr) - - return attrs - - -class _Protocol(metaclass=_ProtocolMeta): - """Internal base class for protocol classes. - - This implements a simple-minded structural issubclass check - (similar but more general than the one-offs in collections.abc - such as Hashable). - """ - - __slots__ = () - - _is_protocol = True - - -# Various ABCs mimicking those in collections.abc. -# A few are simply re-exported for completeness. - -Hashable = collections_abc.Hashable # Not generic. - - -if hasattr(collections_abc, 'Awaitable'): - class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): - __slots__ = () - - __all__.append('Awaitable') - - -if hasattr(collections_abc, 'Coroutine'): - class Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co], - extra=collections_abc.Coroutine): - __slots__ = () - - __all__.append('Coroutine') - - -if hasattr(collections_abc, 'AsyncIterable'): - - class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): - __slots__ = () - - class AsyncIterator(AsyncIterable[T_co], - extra=collections_abc.AsyncIterator): - __slots__ = () - - __all__.append('AsyncIterable') - __all__.append('AsyncIterator') - - -class Iterable(Generic[T_co], extra=collections_abc.Iterable): - __slots__ = () - - -class Iterator(Iterable[T_co], extra=collections_abc.Iterator): - __slots__ = () - - -class SupportsInt(_Protocol): - __slots__ = () - - @abstractmethod - def __int__(self) -> int: - pass - - -class SupportsFloat(_Protocol): - __slots__ = () - - @abstractmethod - def __float__(self) -> float: - pass - - -class SupportsComplex(_Protocol): - __slots__ = () - - @abstractmethod - def __complex__(self) -> complex: - pass - - -class SupportsBytes(_Protocol): - __slots__ = () - - @abstractmethod - def __bytes__(self) -> bytes: - pass - - -class SupportsIndex(_Protocol): - __slots__ = () - - @abstractmethod - def __index__(self) -> int: - pass - - -class SupportsAbs(_Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __abs__(self) -> T_co: - pass - - -class SupportsRound(_Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __round__(self, ndigits: int = 0) -> T_co: - pass - - -if hasattr(collections_abc, 'Reversible'): - class Reversible(Iterable[T_co], extra=collections_abc.Reversible): - __slots__ = () -else: - class Reversible(_Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __reversed__(self) -> 'Iterator[T_co]': - pass - - -Sized = collections_abc.Sized # Not generic. - - -class Container(Generic[T_co], extra=collections_abc.Container): - __slots__ = () - - -if hasattr(collections_abc, 'Collection'): - class Collection(Sized, Iterable[T_co], Container[T_co], - extra=collections_abc.Collection): - __slots__ = () - - __all__.append('Collection') - - -# Callable was defined earlier. - -if hasattr(collections_abc, 'Collection'): - class AbstractSet(Collection[T_co], - extra=collections_abc.Set): - __slots__ = () -else: - class AbstractSet(Sized, Iterable[T_co], Container[T_co], - extra=collections_abc.Set): - __slots__ = () - - -class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): - __slots__ = () - - -# NOTE: It is only covariant in the value type. -if hasattr(collections_abc, 'Collection'): - class Mapping(Collection[KT], Generic[KT, VT_co], - extra=collections_abc.Mapping): - __slots__ = () -else: - class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co], - extra=collections_abc.Mapping): - __slots__ = () - - -class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): - __slots__ = () - - -if hasattr(collections_abc, 'Reversible'): - if hasattr(collections_abc, 'Collection'): - class Sequence(Reversible[T_co], Collection[T_co], - extra=collections_abc.Sequence): - __slots__ = () - else: - class Sequence(Sized, Reversible[T_co], Container[T_co], - extra=collections_abc.Sequence): - __slots__ = () -else: - class Sequence(Sized, Iterable[T_co], Container[T_co], - extra=collections_abc.Sequence): - __slots__ = () - - -class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence): - __slots__ = () - - -class ByteString(Sequence[int], extra=collections_abc.ByteString): - __slots__ = () - - -class List(list, MutableSequence[T], extra=list): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is List: - raise TypeError("Type List cannot be instantiated; " - "use list() instead") - return _generic_new(list, cls, *args, **kwds) - - -class Deque(collections.deque, MutableSequence[T], extra=collections.deque): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Deque: - return collections.deque(*args, **kwds) - return _generic_new(collections.deque, cls, *args, **kwds) - - -class Set(set, MutableSet[T], extra=set): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Set: - raise TypeError("Type Set cannot be instantiated; " - "use set() instead") - return _generic_new(set, cls, *args, **kwds) - - -class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is FrozenSet: - raise TypeError("Type FrozenSet cannot be instantiated; " - "use frozenset() instead") - return _generic_new(frozenset, cls, *args, **kwds) - - -class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): - __slots__ = () - - -class KeysView(MappingView[KT], AbstractSet[KT], - extra=collections_abc.KeysView): - __slots__ = () - - -class ItemsView(MappingView[Tuple[KT, VT_co]], - AbstractSet[Tuple[KT, VT_co]], - Generic[KT, VT_co], - extra=collections_abc.ItemsView): - __slots__ = () - - -class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): - __slots__ = () - - -if hasattr(contextlib, 'AbstractContextManager'): - class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager): - __slots__ = () -else: - class ContextManager(Generic[T_co]): - __slots__ = () - - def __enter__(self): - return self - - @abc.abstractmethod - def __exit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is ContextManager: - # In Python 3.6+, it is possible to set a method to None to - # explicitly indicate that the class does not implement an ABC - # (https://bugs.python.org/issue25958), but we do not support - # that pattern here because this fallback class is only used - # in Python 3.5 and earlier. - if (any("__enter__" in B.__dict__ for B in C.__mro__) and - any("__exit__" in B.__dict__ for B in C.__mro__)): - return True - return NotImplemented - - -if hasattr(contextlib, 'AbstractAsyncContextManager'): - class AsyncContextManager(Generic[T_co], - extra=contextlib.AbstractAsyncContextManager): - __slots__ = () - - __all__.append('AsyncContextManager') -elif sys.version_info[:2] >= (3, 5): - exec(""" -class AsyncContextManager(Generic[T_co]): - __slots__ = () - - async def __aenter__(self): - return self - - @abc.abstractmethod - async def __aexit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncContextManager: - if sys.version_info[:2] >= (3, 6): - return _collections_abc._check_methods(C, "__aenter__", "__aexit__") - if (any("__aenter__" in B.__dict__ for B in C.__mro__) and - any("__aexit__" in B.__dict__ for B in C.__mro__)): - return True - return NotImplemented - -__all__.append('AsyncContextManager') -""") - - -class Dict(dict, MutableMapping[KT, VT], extra=dict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Dict: - raise TypeError("Type Dict cannot be instantiated; " - "use dict() instead") - return _generic_new(dict, cls, *args, **kwds) - - -class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], - extra=collections.defaultdict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is DefaultDict: - return collections.defaultdict(*args, **kwds) - return _generic_new(collections.defaultdict, cls, *args, **kwds) - - -class Counter(collections.Counter, Dict[T, int], extra=collections.Counter): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Counter: - return collections.Counter(*args, **kwds) - return _generic_new(collections.Counter, cls, *args, **kwds) - - -if hasattr(collections, 'ChainMap'): - # ChainMap only exists in 3.3+ - __all__.append('ChainMap') - - class ChainMap(collections.ChainMap, MutableMapping[KT, VT], - extra=collections.ChainMap): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is ChainMap: - return collections.ChainMap(*args, **kwds) - return _generic_new(collections.ChainMap, cls, *args, **kwds) - - -# Determine what base class to use for Generator. -if hasattr(collections_abc, 'Generator'): - # Sufficiently recent versions of 3.5 have a Generator ABC. - _G_base = collections_abc.Generator -else: - # Fall back on the exact type. - _G_base = types.GeneratorType - - -class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co], - extra=_G_base): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Generator: - raise TypeError("Type Generator cannot be instantiated; " - "create a subclass instead") - return _generic_new(_G_base, cls, *args, **kwds) - - -if hasattr(collections_abc, 'AsyncGenerator'): - class AsyncGenerator(AsyncIterator[T_co], Generic[T_co, T_contra], - extra=collections_abc.AsyncGenerator): - __slots__ = () - - __all__.append('AsyncGenerator') - - -# Internal type variable used for Type[]. -CT_co = TypeVar('CT_co', covariant=True, bound=type) - - -# This is not a real generic class. Don't use outside annotations. -class Type(Generic[CT_co], extra=type): - """A special construct usable to annotate class objects. - - For example, suppose we have the following classes:: - - class User: ... # Abstract base for User classes - class BasicUser(User): ... - class ProUser(User): ... - class TeamUser(User): ... - - And a function that takes a class argument that's a subclass of - User and returns an instance of the corresponding class:: - - U = TypeVar('U', bound=User) - def new_user(user_class: Type[U]) -> U: - user = user_class() - # (Here we could write the user object to a database) - return user - - joe = new_user(BasicUser) - - At this point the type checker knows that joe has type BasicUser. - """ - - __slots__ = () - - -def _make_nmtuple(name, types): - msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" - types = [(n, _type_check(t, msg)) for n, t in types] - nm_tpl = collections.namedtuple(name, [n for n, t in types]) - # Prior to PEP 526, only _field_types attribute was assigned. - # Now, both __annotations__ and _field_types are used to maintain compatibility. - nm_tpl.__annotations__ = nm_tpl._field_types = collections.OrderedDict(types) - try: - nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - return nm_tpl - - -_PY36 = sys.version_info[:2] >= (3, 6) - -# attributes prohibited to set in NamedTuple class syntax -_prohibited = ('__new__', '__init__', '__slots__', '__getnewargs__', - '_fields', '_field_defaults', '_field_types', - '_make', '_replace', '_asdict', '_source') - -_special = ('__module__', '__name__', '__qualname__', '__annotations__') - - -class NamedTupleMeta(type): - - def __new__(cls, typename, bases, ns): - if ns.get('_root', False): - return super().__new__(cls, typename, bases, ns) - if not _PY36: - raise TypeError("Class syntax for NamedTuple is only supported" - " in Python 3.6+") - types = ns.get('__annotations__', {}) - nm_tpl = _make_nmtuple(typename, types.items()) - defaults = [] - defaults_dict = {} - for field_name in types: - if field_name in ns: - default_value = ns[field_name] - defaults.append(default_value) - defaults_dict[field_name] = default_value - elif defaults: - raise TypeError("Non-default namedtuple field {field_name} cannot " - "follow default field(s) {default_names}" - .format(field_name=field_name, - default_names=', '.join(defaults_dict.keys()))) - nm_tpl.__new__.__annotations__ = collections.OrderedDict(types) - nm_tpl.__new__.__defaults__ = tuple(defaults) - nm_tpl._field_defaults = defaults_dict - # update from user namespace without overriding special namedtuple attributes - for key in ns: - if key in _prohibited: - raise AttributeError("Cannot overwrite NamedTuple attribute " + key) - elif key not in _special and key not in nm_tpl._fields: - setattr(nm_tpl, key, ns[key]) - return nm_tpl - - -class NamedTuple(metaclass=NamedTupleMeta): - """Typed version of namedtuple. - - Usage in Python versions >= 3.6:: - - class Employee(NamedTuple): - name: str - id: int - - This is equivalent to:: - - Employee = collections.namedtuple('Employee', ['name', 'id']) - - The resulting class has extra __annotations__ and _field_types - attributes, giving an ordered dict mapping field names to types. - __annotations__ should be preferred, while _field_types - is kept to maintain pre PEP 526 compatibility. (The field names - are in the _fields attribute, which is part of the namedtuple - API.) Alternative equivalent keyword syntax is also accepted:: - - Employee = NamedTuple('Employee', name=str, id=int) - - In Python versions <= 3.5 use:: - - Employee = NamedTuple('Employee', [('name', str), ('id', int)]) - """ - _root = True - - def __new__(*args, **kwargs): - if kwargs and not _PY36: - raise TypeError("Keyword syntax for NamedTuple is only supported" - " in Python 3.6+") - if not args: - raise TypeError('NamedTuple.__new__(): not enough arguments') - _, args = args[0], args[1:] # allow the "cls" keyword be passed - if args: - typename, args = args[0], args[1:] # allow the "typename" keyword be passed - elif 'typename' in kwargs: - typename = kwargs.pop('typename') - import warnings - warnings.warn("Passing 'typename' as keyword argument is deprecated", - DeprecationWarning, stacklevel=2) - else: - raise TypeError("NamedTuple.__new__() missing 1 required positional " - "argument: 'typename'") - if args: - try: - fields, = args # allow the "fields" keyword be passed - except ValueError: - raise TypeError('NamedTuple.__new__() takes from 2 to 3 ' - 'positional arguments but {} ' - 'were given'.format(len(args) + 2)) - elif 'fields' in kwargs and len(kwargs) == 1: - fields = kwargs.pop('fields') - import warnings - warnings.warn("Passing 'fields' as keyword argument is deprecated", - DeprecationWarning, stacklevel=2) - else: - fields = None - - if fields is None: - fields = kwargs.items() - elif kwargs: - raise TypeError("Either list of fields or keywords" - " can be provided to NamedTuple, not both") - return _make_nmtuple(typename, fields) - - __new__.__text_signature__ = '($cls, typename, fields=None, /, **kwargs)' - - -def NewType(name, tp): - """NewType creates simple unique types with almost zero - runtime overhead. NewType(name, tp) is considered a subtype of tp - by static type checkers. At runtime, NewType(name, tp) returns - a dummy function that simply returns its argument. Usage:: - - UserId = NewType('UserId', int) - - def name_by_id(user_id: UserId) -> str: - ... - - UserId('user') # Fails type check - - name_by_id(42) # Fails type check - name_by_id(UserId(42)) # OK - - num = UserId(5) + 1 # type: int - """ - - def new_type(x): - return x - - new_type.__name__ = name - new_type.__supertype__ = tp - return new_type - - -# Python-version-specific alias (Python 2: unicode; Python 3: str) -Text = str - - -# Constant that's True when type checking, but False here. -TYPE_CHECKING = False - - -class IO(Generic[AnyStr]): - """Generic base class for TextIO and BinaryIO. - - This is an abstract, generic version of the return of open(). - - NOTE: This does not distinguish between the different possible - classes (text vs. binary, read vs. write vs. read/write, - append-only, unbuffered). The TextIO and BinaryIO subclasses - below capture the distinctions between text vs. binary, which is - pervasive in the interface; however we currently do not offer a - way to track the other distinctions in the type system. - """ - - __slots__ = () - - @abstractproperty - def mode(self) -> str: - pass - - @abstractproperty - def name(self) -> str: - pass - - @abstractmethod - def close(self) -> None: - pass - - @abstractproperty - def closed(self) -> bool: - pass - - @abstractmethod - def fileno(self) -> int: - pass - - @abstractmethod - def flush(self) -> None: - pass - - @abstractmethod - def isatty(self) -> bool: - pass - - @abstractmethod - def read(self, n: int = -1) -> AnyStr: - pass - - @abstractmethod - def readable(self) -> bool: - pass - - @abstractmethod - def readline(self, limit: int = -1) -> AnyStr: - pass - - @abstractmethod - def readlines(self, hint: int = -1) -> List[AnyStr]: - pass - - @abstractmethod - def seek(self, offset: int, whence: int = 0) -> int: - pass - - @abstractmethod - def seekable(self) -> bool: - pass - - @abstractmethod - def tell(self) -> int: - pass - - @abstractmethod - def truncate(self, size: int = None) -> int: - pass - - @abstractmethod - def writable(self) -> bool: - pass - - @abstractmethod - def write(self, s: AnyStr) -> int: - pass - - @abstractmethod - def writelines(self, lines: List[AnyStr]) -> None: - pass - - @abstractmethod - def __enter__(self) -> 'IO[AnyStr]': - pass - - @abstractmethod - def __exit__(self, type, value, traceback) -> None: - pass - - -class BinaryIO(IO[bytes]): - """Typed version of the return of open() in binary mode.""" - - __slots__ = () - - @abstractmethod - def write(self, s: Union[bytes, bytearray]) -> int: - pass - - @abstractmethod - def __enter__(self) -> 'BinaryIO': - pass - - -class TextIO(IO[str]): - """Typed version of the return of open() in text mode.""" - - __slots__ = () - - @abstractproperty - def buffer(self) -> BinaryIO: - pass - - @abstractproperty - def encoding(self) -> str: - pass - - @abstractproperty - def errors(self) -> Optional[str]: - pass - - @abstractproperty - def line_buffering(self) -> bool: - pass - - @abstractproperty - def newlines(self) -> Any: - pass - - @abstractmethod - def __enter__(self) -> 'TextIO': - pass - - -class io: - """Wrapper namespace for IO generic classes.""" - - __all__ = ['IO', 'TextIO', 'BinaryIO'] - IO = IO - TextIO = TextIO - BinaryIO = BinaryIO - - -io.__name__ = __name__ + '.io' -sys.modules[io.__name__] = io - - -Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')), - lambda p: p.pattern) -Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')), - lambda m: m.re.pattern) - - -class re: - """Wrapper namespace for re type aliases.""" - - __all__ = ['Pattern', 'Match'] - Pattern = Pattern - Match = Match - - -re.__name__ = __name__ + '.re' -sys.modules[re.__name__] = re From 5cc1d2c840d9a99aeeb442ea6bf71d7ce2dee523 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 16 Sep 2021 15:52:34 +0200 Subject: [PATCH 046/539] Drop Python 2 support for typing_extensions (#893) Update README accordingly --- .flake8 | 1 - .github/workflows/ci.yml | 24 +- typing_extensions/MANIFEST.in | 2 - typing_extensions/README.rst | 54 +-- typing_extensions/setup.py | 16 +- .../src_py2/test_typing_extensions.py | 456 ------------------ .../src_py2/typing_extensions.py | 283 ----------- typing_extensions/tox.ini | 5 +- 8 files changed, 22 insertions(+), 819 deletions(-) delete mode 100644 typing_extensions/src_py2/test_typing_extensions.py delete mode 100644 typing_extensions/src_py2/typing_extensions.py diff --git a/.flake8 b/.flake8 index b61006a00..a40ac7fff 100644 --- a/.flake8 +++ b/.flake8 @@ -14,5 +14,4 @@ ignore = exclude = # tests have more relaxed formatting rules # and its own specific config in .flake8-tests - typing_extensions/src_py2/test_typing_extensions.py, typing_extensions/src_py3/test_typing_extensions.py, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e9d09ce6..7ccde29a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,28 +8,6 @@ permissions: contents: read jobs: - tests-27: - name: Run tests (2.7) - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 2.7 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r test-requirements.txt - - - name: Test typing_extensions - run: | - pip install typing - pytest typing_extensions/src_py2 - tests: name: Run tests @@ -94,4 +72,4 @@ jobs: run: flake8 - name: Lint tests - run: flake8 --config=.flake8-tests typing_extensions/src_py2/test_typing_extensions.py typing_extensions/src_py3/test_typing_extensions.py + run: flake8 --config=.flake8-tests typing_extensions/src_py3/test_typing_extensions.py diff --git a/typing_extensions/MANIFEST.in b/typing_extensions/MANIFEST.in index feda4cf1e..a727bf4b7 100644 --- a/typing_extensions/MANIFEST.in +++ b/typing_extensions/MANIFEST.in @@ -1,5 +1,3 @@ include LICENSE README.rst include src_py3/typing_extensions.py include src_py3/test_typing_extensions.py -include src_py2/typing_extensions.py -include src_py2/test_typing_extensions.py diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 4166510a7..879596e4c 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -9,13 +9,7 @@ Typing Extensions Overview ======== -The ``typing`` module was added to the standard library in Python 3.5 on -a provisional basis and will no longer be provisional in Python 3.7. However, -this means users of Python 3.5 - 3.6 who are unable to upgrade will not be -able to take advantage of new types added to the ``typing`` module, such as -``typing.Text`` or ``typing.Coroutine``. - -The ``typing_extensions`` module contains both backports of these changes +The ``typing_extensions`` module contains both backports of ``typing`` features as well as experimental types that will eventually be added to the ``typing`` module, such as ``Protocol`` (see PEP 544 for details about protocols and static duck typing) or ``TypedDict`` (see PEP 589). @@ -30,11 +24,17 @@ Included items This module currently contains the following: -All Python versions: --------------------- - +- ``Annotated`` +- ``AsyncContextManager`` +- ``AsyncGenerator`` +- ``AsyncIterable`` +- ``AsyncIterator`` +- ``Awaitable`` +- ``ChainMap`` - ``ClassVar`` +- ``Concatenate`` - ``ContextManager`` +- ``Coroutine`` - ``Counter`` - ``DefaultDict`` - ``Deque`` @@ -45,38 +45,17 @@ All Python versions: - ``NoReturn`` - ``overload`` (note that older versions of ``typing`` only let you use ``overload`` in stubs) - ``OrderedDict`` -- ``Protocol`` (except on Python 3.5.0) -- ``runtime_checkable`` (except on Python 3.5.0) +- ``ParamSpec`` +- ``ParamSpecArgs`` +- ``ParamSpecKwargs`` +- ``Protocol`` +- ``runtime_checkable`` - ``Text`` - ``Type`` - ``TypedDict`` - ``TypeAlias`` -- ``TYPE_CHECKING`` - -Python 3.4+ only: ------------------ - -- ``ChainMap`` -- ``ParamSpec`` -- ``Concatenate`` -- ``ParamSpecArgs`` -- ``ParamSpecKwargs`` - ``TypeGuard`` - -Python 3.5+ only: ------------------ - -- ``Annotated`` (except on Python 3.5.0-3.5.2) -- ``AsyncIterable`` -- ``AsyncIterator`` -- ``AsyncContextManager`` -- ``Awaitable`` -- ``Coroutine`` - -Python 3.6+ only: ------------------ - -- ``AsyncGenerator`` +- ``TYPE_CHECKING`` Other Notes and Limitations =========================== @@ -101,4 +80,3 @@ To run tests, navigate into the appropriate source directory and run ``test_typing_extensions.py``. You will also need to install the latest version of ``typing`` if you are using a version of Python that does not include ``typing`` as a part of the standard library. - diff --git a/typing_extensions/setup.py b/typing_extensions/setup.py index 806a8bf92..46a4227c1 100644 --- a/typing_extensions/setup.py +++ b/typing_extensions/setup.py @@ -4,8 +4,8 @@ import sys from setuptools import setup -if sys.version_info < (2, 7, 0) or (3, 0, 0) <= sys.version_info < (3, 6, 0): - sys.stderr.write('ERROR: You need Python 2.7 or 3.6+ ' +if sys.version_info < (3, 6, 0): + sys.stderr.write('ERROR: You need Python 3.6+ ' 'to install typing_extensions.\n') exit(1) @@ -31,7 +31,6 @@ 'Intended Audience :: Developers', 'License :: OSI Approved :: Python Software Foundation License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', @@ -40,13 +39,6 @@ 'Topic :: Software Development', ] -if sys.version_info.major == 2: - package_dir = 'src_py2' -elif sys.version_info.major == 3: - package_dir = 'src_py3' -else: - raise AssertionError() - setup(name='typing_extensions', version=version, description=description, @@ -57,7 +49,7 @@ license='PSF', keywords='typing function annotations type hints hinting checking ' 'checker typehints typehinting typechecking backport', - package_dir={'': package_dir}, + package_dir={'': 'src_py3'}, py_modules=['typing_extensions'], classifiers=classifiers, - install_requires=["typing >= 3.7.4; python_version < '3.5'"]) + ) diff --git a/typing_extensions/src_py2/test_typing_extensions.py b/typing_extensions/src_py2/test_typing_extensions.py deleted file mode 100644 index 5c21a6df8..000000000 --- a/typing_extensions/src_py2/test_typing_extensions.py +++ /dev/null @@ -1,456 +0,0 @@ -import sys -import os -import contextlib -import collections -import subprocess -from unittest import TestCase, main - -from typing_extensions import Annotated, NoReturn, ClassVar, IntVar -from typing_extensions import ContextManager, Counter, Deque, DefaultDict -from typing_extensions import NewType, TypeAlias, overload -from typing import Dict, List -import typing -import typing_extensions - - -T = typing.TypeVar('T') -KT = typing.TypeVar('KT') -VT = typing.TypeVar('VT') - - -class BaseTestCase(TestCase): - - def assertIsSubclass(self, cls, class_or_tuple, msg=None): - if not issubclass(cls, class_or_tuple): - message = '%r is not a subclass of %r' % (cls, class_or_tuple) - if msg is not None: - message += ' : %s' % msg - raise self.failureException(message) - - def assertNotIsSubclass(self, cls, class_or_tuple, msg=None): - if issubclass(cls, class_or_tuple): - message = '%r is a subclass of %r' % (cls, class_or_tuple) - if msg is not None: - message += ' : %s' % msg - raise self.failureException(message) - - -class Employee(object): - pass - - -class NoReturnTests(BaseTestCase): - - def test_noreturn_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(42, NoReturn) - - def test_noreturn_subclass_type_error(self): - with self.assertRaises(TypeError): - issubclass(Employee, NoReturn) - with self.assertRaises(TypeError): - issubclass(NoReturn, Employee) - - def test_repr(self): - if hasattr(typing, 'NoReturn'): - self.assertEqual(repr(NoReturn), 'typing.NoReturn') - else: - self.assertEqual(repr(NoReturn), 'typing_extensions.NoReturn') - - def test_not_generic(self): - with self.assertRaises(TypeError): - NoReturn[int] - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class A(NoReturn): - pass - with self.assertRaises(TypeError): - class A(type(NoReturn)): - pass - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - NoReturn() - with self.assertRaises(TypeError): - type(NoReturn)() - - -class ClassVarTests(BaseTestCase): - - def test_basics(self): - with self.assertRaises(TypeError): - ClassVar[1] - with self.assertRaises(TypeError): - ClassVar[int, str] - with self.assertRaises(TypeError): - ClassVar[int][str] - - def test_repr(self): - self.assertEqual(repr(ClassVar), 'typing.ClassVar') - cv = ClassVar[int] - self.assertEqual(repr(cv), 'typing.ClassVar[int]') - cv = ClassVar[Employee] - self.assertEqual(repr(cv), 'typing.ClassVar[%s.Employee]' % __name__) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(ClassVar)): - pass - with self.assertRaises(TypeError): - class C(type(ClassVar[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - ClassVar() - with self.assertRaises(TypeError): - type(ClassVar)() - with self.assertRaises(TypeError): - type(ClassVar[typing.Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, ClassVar[int]) - with self.assertRaises(TypeError): - issubclass(int, ClassVar) - - -class IntVarTests(BaseTestCase): - def test_valid(self): - T_ints = IntVar("T_ints") # noqa - - def test_invalid(self): - with self.assertRaises(TypeError): - T_ints = IntVar("T_ints", int) - with self.assertRaises(TypeError): - T_ints = IntVar("T_ints", bound=int) - with self.assertRaises(TypeError): - T_ints = IntVar("T_ints", covariant=True) # noqa - - -class CollectionsAbcTests(BaseTestCase): - - def test_isinstance_collections(self): - self.assertNotIsInstance(1, collections.Mapping) - self.assertNotIsInstance(1, collections.Iterable) - self.assertNotIsInstance(1, collections.Container) - self.assertNotIsInstance(1, collections.Sized) - with self.assertRaises(TypeError): - isinstance(collections.deque(), typing_extensions.Deque[int]) - with self.assertRaises(TypeError): - issubclass(collections.Counter, typing_extensions.Counter[str]) - - def test_contextmanager(self): - @contextlib.contextmanager - def manager(): - yield 42 - - cm = manager() - self.assertIsInstance(cm, ContextManager) - self.assertNotIsInstance(42, ContextManager) - - with self.assertRaises(TypeError): - isinstance(42, ContextManager[int]) - with self.assertRaises(TypeError): - isinstance(cm, ContextManager[int]) - with self.assertRaises(TypeError): - issubclass(type(cm), ContextManager[int]) - - def test_counter(self): - self.assertIsSubclass(collections.Counter, Counter) - self.assertIs(type(Counter()), collections.Counter) - self.assertIs(type(Counter[T]()), collections.Counter) - self.assertIs(type(Counter[int]()), collections.Counter) - - class A(Counter[int]): pass - class B(Counter[T]): pass - - self.assertIsInstance(A(), collections.Counter) - self.assertIs(type(B[int]()), B) - self.assertEqual(B.__bases__, (typing_extensions.Counter,)) - - def test_deque(self): - self.assertIsSubclass(collections.deque, Deque) - self.assertIs(type(Deque()), collections.deque) - self.assertIs(type(Deque[T]()), collections.deque) - self.assertIs(type(Deque[int]()), collections.deque) - - class A(Deque[int]): pass - class B(Deque[T]): pass - - self.assertIsInstance(A(), collections.deque) - self.assertIs(type(B[int]()), B) - - def test_defaultdict_instantiation(self): - self.assertIsSubclass(collections.defaultdict, DefaultDict) - self.assertIs(type(DefaultDict()), collections.defaultdict) - self.assertIs(type(DefaultDict[KT, VT]()), collections.defaultdict) - self.assertIs(type(DefaultDict[str, int]()), collections.defaultdict) - - class A(DefaultDict[str, int]): pass - class B(DefaultDict[KT, VT]): pass - - self.assertIsInstance(A(), collections.defaultdict) - self.assertIs(type(B[str, int]()), B) - - -class NewTypeTests(BaseTestCase): - - def test_basic(self): - UserId = NewType('UserId', int) - UserName = NewType('UserName', str) - self.assertIsInstance(UserId(5), int) - self.assertIsInstance(UserName('Joe'), type('Joe')) - self.assertEqual(UserId(5) + 1, 6) - - def test_errors(self): - UserId = NewType('UserId', int) - UserName = NewType('UserName', str) - with self.assertRaises(TypeError): - issubclass(UserId, int) - with self.assertRaises(TypeError): - class D(UserName): - pass - - -class OverloadTests(BaseTestCase): - - def test_overload_fails(self): - with self.assertRaises(RuntimeError): - @overload - def blah(): - pass - - blah() - - def test_overload_succeeds(self): - @overload - def blah(): - pass - - def blah(): - pass - - blah() - - -class AnnotatedTests(BaseTestCase): - - def test_repr(self): - self.assertEqual( - repr(Annotated[int, 4, 5]), - "typing_extensions.Annotated[int, 4, 5]" - ) - self.assertEqual( - repr(Annotated[List[int], 4, 5]), - "typing_extensions.Annotated[typing.List[int], 4, 5]" - ) - self.assertEqual(repr(Annotated), "typing_extensions.Annotated") - - def test_flatten(self): - A = Annotated[Annotated[int, 4], 5] - self.assertEqual(A, Annotated[int, 4, 5]) - self.assertEqual(A.__metadata__, (4, 5)) - - def test_specialize(self): - L = Annotated[List[T], "my decoration"] - LI = Annotated[List[int], "my decoration"] - self.assertEqual(L[int], Annotated[List[int], "my decoration"]) - self.assertEqual(L[int].__metadata__, ("my decoration",)) - with self.assertRaises(TypeError): - LI[int] - with self.assertRaises(TypeError): - L[int, float] - - def test_hash_eq(self): - self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5]) - self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4]) - self.assertEqual( - {Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]}, - {Annotated[int, 4, 5], Annotated[T, 4, 5]} - ) - - def test_instantiate(self): - class C: - classvar = 4 - - def __init__(self, x): - self.x = x - - def __eq__(self, other): - if not isinstance(other, C): - return NotImplemented - return other.x == self.x - - A = Annotated[C, "a decoration"] - a = A(5) - c = C(5) - self.assertEqual(a, c) - self.assertEqual(a.x, c.x) - self.assertEqual(a.classvar, c.classvar) - - def test_instantiate_generic(self): - MyCount = Annotated[typing_extensions.Counter[T], "my decoration"] - self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1}) - self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1}) - - def test_cannot_instantiate_forward(self): - A = Annotated["int", (5, 6)] - with self.assertRaises(TypeError): - A(5) - - def test_cannot_instantiate_type_var(self): - A = Annotated[T, (5, 6)] - with self.assertRaises(TypeError): - A(5) - - def test_cannot_getattr_typevar(self): - with self.assertRaises(AttributeError): - Annotated[T, (5, 7)].x - - def test_attr_passthrough(self): - class C: - classvar = 4 - - A = Annotated[C, "a decoration"] - self.assertEqual(A.classvar, 4) - A.x = 5 - self.assertEqual(C.x, 5) - - def test_hash_eq(self): - self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5]) - self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4]) - self.assertEqual( - {Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]}, - {Annotated[int, 4, 5], Annotated[T, 4, 5]} - ) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(Annotated): - pass - - def test_cannot_check_instance(self): - with self.assertRaises(TypeError): - isinstance(5, Annotated[int, "positive"]) - - def test_cannot_check_subclass(self): - with self.assertRaises(TypeError): - issubclass(int, Annotated[int, "positive"]) - - def test_subst(self): - dec = "a decoration" - dec2 = "another decoration" - - S = Annotated[T, dec2] - self.assertEqual(S[int], Annotated[int, dec2]) - - self.assertEqual(S[Annotated[int, dec]], Annotated[int, dec, dec2]) - L = Annotated[List[T], dec] - - self.assertEqual(L[int], Annotated[List[int], dec]) - with self.assertRaises(TypeError): - L[int, int] - - self.assertEqual(S[L[int]], Annotated[List[int], dec, dec2]) - - D = Annotated[Dict[KT, VT], dec] - self.assertEqual(D[str, int], Annotated[Dict[str, int], dec]) - with self.assertRaises(TypeError): - D[int] - - It = Annotated[int, dec] - with self.assertRaises(TypeError): - It[None] - - LI = L[int] - with self.assertRaises(TypeError): - LI[None] - - def test_annotated_in_other_types(self): - X = List[Annotated[T, 5]] - self.assertEqual(X[int], List[Annotated[int, 5]]) - - -class TypeAliasTests(BaseTestCase): - def test_canonical_usage(self): - Alias = Employee # type: TypeAlias - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - TypeAlias() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(42, TypeAlias) - - def test_no_issubclass(self): - with self.assertRaises(TypeError): - issubclass(Employee, TypeAlias) - - with self.assertRaises(TypeError): - issubclass(TypeAlias, Employee) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(TypeAlias): - pass - - with self.assertRaises(TypeError): - class C(type(TypeAlias)): - pass - - def test_repr(self): - if hasattr(typing, 'TypeAlias'): - self.assertEqual(repr(TypeAlias), 'typing.TypeAlias') - self.assertEqual(repr(type(TypeAlias)), 'typing.TypeAlias') - else: - self.assertEqual(repr(TypeAlias), 'typing_extensions.TypeAlias') - self.assertEqual(repr(type(TypeAlias)), 'typing_extensions.TypeAlias') - - def test_cannot_subscript(self): - with self.assertRaises(TypeError): - TypeAlias[int] - - -class AllTests(BaseTestCase): - - def test_typing_extensions_includes_standard(self): - a = typing_extensions.__all__ - self.assertIn('ClassVar', a) - self.assertIn('Type', a) - self.assertIn('Counter', a) - self.assertIn('DefaultDict', a) - self.assertIn('Deque', a) - self.assertIn('NewType', a) - self.assertIn('overload', a) - self.assertIn('Text', a) - self.assertIn('TYPE_CHECKING', a) - - def test_typing_extensions_defers_when_possible(self): - exclude = {'overload', 'Text', 'TYPE_CHECKING', 'Final'} - for item in typing_extensions.__all__: - if item not in exclude and hasattr(typing, item): - self.assertIs( - getattr(typing_extensions, item), - getattr(typing, item)) - - def test_typing_extensions_compiles_with_opt(self): - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'typing_extensions.py') - try: - subprocess.check_output('{} -OO {}'.format(sys.executable, - file_path), - stderr=subprocess.STDOUT, - shell=True) - except subprocess.CalledProcessError: - self.fail('Module does not compile with optimize=2 (-OO flag).') - - -if __name__ == '__main__': - main() diff --git a/typing_extensions/src_py2/typing_extensions.py b/typing_extensions/src_py2/typing_extensions.py deleted file mode 100644 index 62d1e46c7..000000000 --- a/typing_extensions/src_py2/typing_extensions.py +++ /dev/null @@ -1,283 +0,0 @@ -import abc -import typing -from typing import ( # noqa - # These are imported for re-export. - ClassVar, Type, Generic, Callable, GenericMeta, TypingMeta, - Counter, DefaultDict, Deque, TypeVar, Tuple, Final, final, - NewType, overload, Text, TYPE_CHECKING, Literal, TypedDict, Protocol, - SupportsIndex, - runtime_checkable, - # We use internal typing helpers here, but this significantly reduces - # code duplication. (Also this is only until Protocol is in typing.) - _type_vars, _tp_cache, _type_check, -) - -# Please keep __all__ alphabetized within each category. -__all__ = [ - # Super-special typing primitives. - 'ClassVar', - 'Final', - 'Protocol', - 'Type', - 'TypedDict', - - # Concrete collection types. - 'ContextManager', - 'Counter', - 'Deque', - 'DefaultDict', - - # Structural checks, a.k.a. protocols. - 'SupportsIndex', - - # One-off things. - 'final', - 'IntVar', - 'Literal', - 'NewType', - 'overload', - 'runtime_checkable', - 'Text', - 'TYPE_CHECKING', -] - - -if hasattr(typing, 'NoReturn'): - NoReturn = typing.NoReturn -else: - # TODO: Remove once typing.py has been updated - class _NoReturnMeta(typing.TypingMeta): - """Metaclass for NoReturn.""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(_NoReturnMeta, cls).__new__(cls, name, bases, namespace) - return self - - class _NoReturn(typing._FinalTypingBase): - """Special type indicating functions that never return. - Example:: - from typing import NoReturn - def stop() -> NoReturn: - raise Exception('no way') - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - __metaclass__ = _NoReturnMeta - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - NoReturn = _NoReturn(_root=True) - - -T_co = typing.TypeVar('T_co', covariant=True) - -if hasattr(typing, 'ContextManager'): - ContextManager = typing.ContextManager -else: - # TODO: Remove once typing.py has been updated - class ContextManager(typing.Generic[T_co]): - __slots__ = () - - def __enter__(self): - return self - - @abc.abstractmethod - def __exit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is ContextManager: - # In Python 3.6+, it is possible to set a method to None to - # explicitly indicate that the class does not implement an ABC - # (https://bugs.python.org/issue25958), but we do not support - # that pattern here because this fallback class is only used - # in Python 3.5 and earlier. - if (any("__enter__" in B.__dict__ for B in C.__mro__) and - any("__exit__" in B.__dict__ for B in C.__mro__)): - return True - return NotImplemented - - -def IntVar(name): - return TypeVar(name) - - -def _is_dunder(name): - """Returns True if name is a __dunder_variable_name__.""" - return len(name) > 4 and name.startswith('__') and name.endswith('__') - - -class AnnotatedMeta(GenericMeta): - """Metaclass for Annotated""" - - def __new__(cls, name, bases, namespace, **kwargs): - if any(b is not object for b in bases): - raise TypeError("Cannot subclass %s" % Annotated) - return super(AnnotatedMeta, cls).__new__(cls, name, bases, namespace, **kwargs) - - @property - def __metadata__(self): - return self._subs_tree()[2] - - def _tree_repr(self, tree): - cls, origin, metadata = tree - if not isinstance(origin, tuple): - tp_repr = typing._type_repr(origin) - else: - tp_repr = origin[0]._tree_repr(origin) - metadata_reprs = ", ".join(repr(arg) for arg in metadata) - return '%s[%s, %s]' % (cls, tp_repr, metadata_reprs) - - def _subs_tree(self, tvars=None, args=None): - if self is Annotated: - return Annotated - res = super(AnnotatedMeta, self)._subs_tree(tvars=tvars, args=args) - # Flatten nested Annotated - if isinstance(res[1], tuple) and res[1][0] is Annotated: - sub_tp = res[1][1] - sub_annot = res[1][2] - return (Annotated, sub_tp, sub_annot + res[2]) - return res - - def _get_cons(self): - """Return the class used to create instance of this type.""" - if self.__origin__ is None: - raise TypeError("Cannot get the underlying type of a non-specialized " - "Annotated type.") - tree = self._subs_tree() - while isinstance(tree, tuple) and tree[0] is Annotated: - tree = tree[1] - if isinstance(tree, tuple): - return tree[0] - else: - return tree - - @_tp_cache - def __getitem__(self, params): - if not isinstance(params, tuple): - params = (params,) - if self.__origin__ is not None: # specializing an instantiated type - return super(AnnotatedMeta, self).__getitem__(params) - elif not isinstance(params, tuple) or len(params) < 2: - raise TypeError("Annotated[...] should be instantiated with at " - "least two arguments (a type and an annotation).") - else: - msg = "Annotated[t, ...]: t must be a type." - tp = typing._type_check(params[0], msg) - metadata = tuple(params[1:]) - return self.__class__( - self.__name__, - self.__bases__, - dict(self.__dict__), - tvars=_type_vars((tp,)), - # Metadata is a tuple so it won't be touched by _replace_args et al. - args=(tp, metadata), - origin=self, - ) - - def __call__(self, *args, **kwargs): - cons = self._get_cons() - result = cons(*args, **kwargs) - try: - result.__orig_class__ = self - except AttributeError: - pass - return result - - def __getattr__(self, attr): - # For simplicity we just don't relay all dunder names - if self.__origin__ is not None and not _is_dunder(attr): - return getattr(self._get_cons(), attr) - raise AttributeError(attr) - - def __setattr__(self, attr, value): - if _is_dunder(attr) or attr.startswith('_abc_'): - super(AnnotatedMeta, self).__setattr__(attr, value) - elif self.__origin__ is None: - raise AttributeError(attr) - else: - setattr(self._get_cons(), attr, value) - - -class Annotated(object): - """Add context specific metadata to a type. - - Example: Annotated[int, runtime_check.Unsigned] indicates to the - hypothetical runtime_check module that this type is an unsigned int. - Every other consumer of this type can ignore this metadata and treat - this type as int. - - The first argument to Annotated must be a valid type, the remaining - arguments are kept as a tuple in the __metadata__ field. - - Details: - - - It's an error to call `Annotated` with less than two arguments. - - Nested Annotated are flattened:: - - Annotated[Annotated[int, Ann1, Ann2], Ann3] == Annotated[int, Ann1, Ann2, Ann3] - - - Instantiating an annotated type is equivalent to instantiating the - underlying type:: - - Annotated[C, Ann1](5) == C(5) - - - Annotated can be used as a generic type alias:: - - Optimized = Annotated[T, runtime.Optimize()] - Optimized[int] == Annotated[int, runtime.Optimize()] - - OptimizedList = Annotated[List[T], runtime.Optimize()] - OptimizedList[int] == Annotated[List[int], runtime.Optimize()] - """ - __metaclass__ = AnnotatedMeta - __slots__ = () - - -class _TypeAliasMeta(typing.TypingMeta): - """Metaclass for TypeAlias""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(_TypeAliasMeta, cls).__new__(cls, name, bases, namespace) - return self - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - -class _TypeAliasBase(typing._FinalTypingBase): - """Special marker indicating that an assignment should - be recognized as a proper type alias definition by type - checkers. - - For example:: - - Predicate = Callable[..., bool] # type: TypeAlias - - It's invalid when used anywhere except as in the example above. - """ - __metaclass__ = _TypeAliasMeta - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("TypeAlias cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeAlias cannot be used with issubclass().") - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - -TypeAlias = _TypeAliasBase(_root=True) - -# This alias exists for backwards compatibility. -runtime = runtime_checkable diff --git a/typing_extensions/tox.ini b/typing_extensions/tox.ini index 7bb6156d7..892280f98 100644 --- a/typing_extensions/tox.ini +++ b/typing_extensions/tox.ini @@ -1,9 +1,6 @@ [tox] -envlist = py27, py34, py35, py36, py37, py38, py39 +envlist = py36, py37, py38, py39 [testenv] changedir = src_py3 commands = python -m unittest discover - -[testenv:py27] -changedir = src_py2 From 796a0b699ab00df9e82a476740265b1ad5fae64d Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 5 Oct 2021 16:33:59 +0200 Subject: [PATCH 047/539] Check with stable Python 3.10 (#902) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ccde29a1..70c69cff9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.10-dev, 3.9, 3.8, 3.7, 3.6] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] runs-on: ubuntu-latest From 85ce96b18a3c4da2697f00aa7a2ae36ede9e916a Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Fri, 8 Oct 2021 00:11:00 +0200 Subject: [PATCH 048/539] Convert libraries document to ReST (#904) Link the document and rename it slightly --- docs/index.rst | 1 + docs/libraries.md | 395 --------------------------- docs/libraries.rst | 650 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 651 insertions(+), 395 deletions(-) delete mode 100644 docs/libraries.md create mode 100644 docs/libraries.rst diff --git a/docs/index.rst b/docs/index.rst index dba85dd8d..2c44b7f8f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,7 @@ Static Typing with Python :caption: Contents: typing Module Documentation + libraries stubs diff --git a/docs/libraries.md b/docs/libraries.md deleted file mode 100644 index 283fb5196..000000000 --- a/docs/libraries.md +++ /dev/null @@ -1,395 +0,0 @@ -# Typing Guidance for Python Libraries - -Much of Python’s popularity can be attributed to the rich collection of Python libraries available to developers. Authors of these libraries play an important role in improving the experience for Python developers. This document provides some recommendations and guidance for Python library authors. - -These recommendations are intended to provide the following benefits: - -1. Consumers of libraries should have a great coding experience with fast and accurate completion suggestions, class and function documentation, signature help (including parameter default values), hover text, and auto-imports. This should happen by default without needing to download extra packages and without any special configuration. These features should be consistent across the Python ecosystem regardless of a developer’s choice of editor, IDE, notebook environment, etc. -2. Consumers of libraries should be able to rely on complete and accurate type information so static type checkers can detect and report type inconsistencies and other violations of the interface contract. -3. Library authors should be able to specify a well-defined interface contract that is enforced by tools. This allows a library implementation to evolve and improve without breaking consumers of the library. -4. Library authors should have the benefits of static type checking to produce high-quality, bug-free implementations. - - -## Inlined Type Annotations and Type Stubs -[PEP 561](https://www.python.org/dev/peps/pep-0561/) documents several ways type information can be delivered for a library: inlined type annotations, type stub files included in the package, a separate companion type stub package, and type stubs in the typeshed repository. Some of these options fall short on delivering the benefits above. We therefore provide the following more specific guidance to library authors. - -*All libraries should include inlined type annotations for the functions, classes, methods, and constants that comprise the public interface for the library.* - -Inlined type annotations should be included directly within the source code that ships with the package. Of the options listed in PEP 561, inlined type annotations offer the most benefits. They typically require the least effort to add and maintain, they are always consistent with the implementation, and docstrings and default parameter values are readily available, allowing language servers to enhance the development experience. - -There are cases where inlined type annotations are not possible — most notably when a library’s exposed functionality is implemented in a language other than Python. - -*Libraries that expose symbols implemented in languages other than Python should include stub (“.pyi”) files that describe the types for those symbols. These stubs should also contain docstrings and default parameter values.* - -In many existing type stubs (such as those found in typeshed), default parameter values are replaced with with “...” and all docstrings are removed. We recommend that default values and docstrings remain within the type stub file so language servers can display this information to developers. - - -## Library Interface -[PEP 561](https://www.python.org/dev/peps/pep-0561/) indicates that a “py.typed” marker file must be included in the package if the author wishes to support type checking of their code. - -If a “py.typed” module is present, a type checker will treat all modules within that package (i.e. all files that end in “.py” or “.pyi”) as importable unless the file name begins with an underscore. These modules comprise the supported interface for the library. - -Each module exposes a set of symbols. Some of these symbols are considered “private” — implementation details that are not part of the library’s interface. Type checkers like pyright use the following rules to determine which symbols are visible outside of the package. - -* Symbols whose names begin with an underscore (but are not dunder names) are considered private. -* Imported symbols are considered private by default. If they use the “import A as A” (a redundant module alias), “from X import A as A” (a redundant symbol alias), or “from . import A” forms, symbol “A” is not private unless the name begins with an underscore. If a file `__init__.py` uses form “from .A import X”, symbol “A” is treated likewise. If a wildcard import (of the form “from X import *”) is used, all symbols referenced by the wildcard are not private. -* A module can expose an `__all__` symbol at the module level that provides a list of names that are considered part of the interface. This overrides all other rules above, allowing imported symbols or symbols whose names begin with an underscore to be included in the interface. -* Local variables within a function (including nested functions) are always considered private. - -The following idioms are supported for defining the values contained within `__all__`. These restrictions allow type checkers to statically determine the value of `__all__`. - -* `__all__ = ('a', b')` -* `__all__ = ['a', b']` -* `__all__ += ['a', b']` -* `__all__ += submodule.__all__` -* `__all__.extend(['a', b'])` -* `__all__.extend(submodule.__all__)` -* `__all__.append('a')` -* `__all__.remove('a')` - - -## Type Completeness -A “py.typed” library is said to be “type complete” if all of the symbols that comprise its interface have type annotations that refer to types that are fully known. Private symbols are exempt. - -A “known type” is defined as follows: - -Classes: - -* All class variables, instance variables, and methods that are “visible” (not overridden) are annotated and refer to known types -* If a class is a subclass of a generic class, type arguments are provided for each generic type parameter, and these type arguments are known types - -Functions and Methods: - -* All input parameters have type annotations that refer to known types -* The return parameter is annotated and refers to a known type -* The result of applying one or more decorators results in a known type - -Type Aliases: - -* All of the types referenced by the type alias are known - -Variables: - -* All variables have type annotations that refer to known types - -Type annotations can be omitted in a few specific cases where the type is obvious from the context: - -* Constants that are assigned simple literal values (e.g. `RED = '#F00'` or `MAX_TIMEOUT = 50` or `room_temperature: Final = 20`). A constant is a symbol that is assigned only once and is either annotated with `Final` or is named in all-caps. A constant that is not assigned a simple literal value requires explicit annotations, preferably with a `Final` annotation (e.g. `WOODWINDS: Final[List[str]] = ['Oboe', 'Bassoon']`). -* Enum values within an Enum class do not require annotations because they take on the type of the Enum class. -* Type aliases do not require annotations. A type alias is a symbol that is defined at a module level with a single assignment where the assigned value is an instantiable type, as opposed to a class instance (e.g. `Foo = Callable[[Literal["a", "b"]], Union[int, str]]` or `Bar = Optional[MyGenericClass[int]]`). -* The “self” parameter in an instance method and the “cls” parameter in a class method do not require an explicit annotation. -* The return type for an `__init__` method does not need to be specified, since it is always `None`. -* The following module-level symbols do not require type annotations: `__all__`,`__author__`, `__copyright__`, `__email__`, `__license__`, `__title__`, `__uri__`, `__version__`. -* The following class-level symbols do not require type annotations: `__class__`, `__dict__`, `__doc__`, `__module__`, `__slots__`. - -### Examples of known and unknown types -```python - -# Variable with unknown type -a = [3, 4, 5] - -# Variable with known type -a: List[int] = [3, 4, 5] - -# Type alias with partially unknown type (because type -# arguments are missing for list and dict) -DictOrList = Union[list, dict] - -# Type alias with known type -DictOrList = Union[List[Any], Dict[str, Any]] - -# Generic type alias with known type -_T = TypeVar("_T") -DictOrList = Union[List[_T], Dict[str, _T]] - -# Function with known type -def func(a: Optional[int], b: Dict[str, float] = {}) -> None: - pass - -# Function with partially unknown type (because type annotations -# are missing for input parameters and return type) -def func(a, b): - pass - -# Function with partially unknown type (because of missing -# type args on Dict) -def func(a: int, b: Dict) -> None: - pass - -# Function with partially unknown type (because return type -# annotation is missing) -def func(a: int, b: Dict[str, float]): - pass - -# Decorator with partially unknown type (because type annotations -# are missing for input parameters and return type) -def my_decorator(func): - return func - -# Function with partially unknown type (because type is obscured -# by untyped decorator) -@my_decorator -def func(a: int) -> str: - pass - - -# Class with known type -class MyClass: - height: float = 2.0 - - def __init__(self, name: str, age: int): - self.age: int = age - - @property - def name(self) -> str: - ... - -# Class with partially unknown type -class MyClass: - # Missing type annotation for class variable - height = 2.0 - - # Missing input parameter annotations - def __init__(self, name, age): - # Missing type annotation for instance variable - self.age = age - - # Missing return type annotation - @property - def name(self): - ... - -# Class with partially unknown type -class BaseClass: - # Missing type annotation - height = 2.0 - - # Missing type annotation - def get_stuff(self): - ... - -# Class with known type (because it overrides all symbols -# exposed by BaseClass that have incomplete types) -class DerivedClass(BaseClass): - height: float - - def get_stuff(self) -> str: - ... - -# Class with partially unknown type because base class -# (dict) is generic, and type arguments are not specified. -class DictSubclass(dict): - pass - -``` - -## Verifying Type Completeness -Pyright provides a feature that allows library authors to verify type completeness for a “py.typed” package. To use this feature, create a clean Python environment and install your package along with all of the other dependent packages. Run the CLI version of pyright with the `--verifytypes` option. - -`pyright --verifytypes ` - -Pyright will analyze the library, identify all symbols that comprise the interface to the library and emit errors for any symbols whose types are unknown. It also produces a “type completeness score” which is the percentage of symbols with known types. - -To see additional details (including a full list of symbols in the library), append the `--verbose` option. - -The `--verifytypes` option can be combined with `--outputjson` to emit the results in a form that can be consumed by other tools. - -The `--verifytypes` feature can be integrated into a continuous integration (CI) system to verify that a library remains “type complete”. - -If the `--verifytypes` option is combined with `--ignoreexternal`, any incomplete types that are imported from other external packages are ignored. This allows library authors to focus on adding type annotations for the code that is directly under their control. - - -### Improving Type Completeness - -Here are some tips for increasing the type completeness score for your library: - -* If your package includes tests or sample code, consider removing them from the distribution. If there is good reason to include them, consider placing them in a directory that begins with an underscore so they are not considered part of your library’s interface. -* If your package includes submodules that are meant to be implementation details, rename those files to begin with an underscore. -* If a symbol is not intended to be part of the library’s interface and is considered an implementation detail, rename it such that it begins with an underscore. It will then be considered private and excluded from the type completeness check. -* If your package exposes types from other libraries, work with the maintainers of these other libraries to achieve type completeness. - - -## Best Practices for Inlined Types - -### Wide vs. Narrow Types -In type theory, when comparing two types that are related to each other, the “wider” type is the one that is more general, and the “narrower” type is more specific. For example, `Sequence[str]` is a wider type than `List[str]` because all `List` objects are also `Sequence` objects, but the converse is not true. A subclass is narrower than a class it derives from. A union of types is wider than the individual types that comprise the union. - -In general, a function input parameter should be annotated with the widest possible type supported by the implementation. For example, if the implementation requires the caller to provide an iterable collection of strings, the parameter should be annotated as `Iterable[str]`, not as `List[str]`. The latter type is narrower than necessary, so if a user attempts to pass a tuple of strings (which is supported by the implementation), a type checker will complain about a type incompatibility. - -As a specific application of the “use the widest type possible” rule, libraries should generally use immutable forms of container types instead of mutable forms (unless the function needs to modify the container). Use `Sequence` rather than `List`, `Mapping` rather than `Dict`, etc. Immutable containers allow for more flexibility because their type parameters are covariant rather than invariant. A parameter that is typed as `Sequence[Union[str, int]]` can accept a `List[int]`, `Sequence[str]`, and a `Sequence[int]`. But a parameter typed as `List[Union[str, int]]` is much more restrictive and accepts only a `List[Union[str, int]]`. - -### Overloads -If a function or method can return multiple different types and those types can be determined based on the presence or types of certain parameters, use the `@overload` mechanism defined in [PEP 484](https://www.python.org/dev/peps/pep-0484/#id45). When overloads are used within a “.py” file, they must appear prior to the function implementation, which should not have an `@overload` decorator. - -### Keyword-only Parameters -If a function or method is intended to take parameters that are specified only by name, use the keyword-only separator ("*"). - -```python -def create_user(age: int, *, dob: Optional[date] = None): - ... -``` - -### Annotating Decorators -Decorators modify the behavior of a class or a function. Providing annotations for decorators is straightforward if the decorator retains the original signature of the decorated function. - -```python -_F = TypeVar("_F", bound=Callable[..., Any]) - -def simple_decorator(_func: _F) -> _F: - """ - Simple decorators are invoked without parentheses like this: - @simple_decorator - def my_function(): ... - """ - ... - -def complex_decorator(*, mode: str) -> Callable[[_F], _F]: - """ - Complex decorators are invoked with arguments like this: - @complex_decorator(mode="easy") - def my_function(): ... - """ - ... -``` - -Decorators that mutate the signature of the decorated function present challenges for type annotations. The `ParamSpec` and `Concatenate` mechanisms described in [PEP 612](https://www.python.org/dev/peps/pep-0612/) provide some help here, but these are available only in Python 3.10 and newer. More complex signature mutations may require type annotations that erase the original signature, thus blinding type checkers and other tools that provide signature assistance. As such, library authors are discouraged from creating decorators that mutate function signatures in this manner. - -### Generic Classes and Functions -Classes and functions that can operate in a generic manner on various types should declare themselves as generic using the mechanisms described in [PEP 484](https://www.python.org/dev/peps/pep-0484/). This includes the use of `TypeVar` symbols. Typically, a `TypeVar` should be private to the file that declares it, and should therefore begin with an underscore. - -### Type Aliases -Type aliases are symbols that refer to other types. Generic type aliases (those that refer to unspecialized generic classes) are supported by most type checkers. Pyright also provides support for recursive type aliases. - -[PEP 613](https://www.python.org/dev/peps/pep-0613/) provides a way to explicitly designate a symbol as a type alias using the new TypeAlias annotation. - -```python -# Simple type alias -FamilyPet = Union[Cat, Dog, GoldFish] - -# Generic type alias -ListOrTuple = Union[List[_T], Tuple[_T, ...]] - -# Recursive type alias -TreeNode = Union[LeafNode, List["TreeNode"]] - -# Explicit type alias using PEP 613 syntax -StrOrInt: TypeAlias = Union[str, int] -``` - -### Abstract Classes and Methods -Classes that must be subclassed should derive from `ABC`, and methods or properties that must be overridden should be decorated with the `@abstractmethod` decorator. This allows type checkers to validate that the required methods have been overridden and provide developers with useful error messages when they are not. It is customary to implement an abstract method by raising a `NotImplementedError` exception. - -```python -from abc import ABC, abstractmethod - -class Hashable(ABC): - @property - @abstractmethod - def hash_value(self) -> int: - """Subclasses must override""" - raise NotImplementedError() - - @abstractmethod - def print(self) -> str: - """Subclasses must override""" - raise NotImplementedError() -``` - -### Final Classes and Methods -Classes that are not intended to be subclassed should be decorated as `@final` as described in [PEP 591](https://www.python.org/dev/peps/pep-0591/). The same decorator can also be used to specify methods that cannot be overridden by subclasses. - -### Literals -Type annotations should make use of the Literal type where appropriate, as described in [PEP 586](https://www.python.org/dev/peps/pep-0586/). Literals allow for more type specificity than their non-literal counterparts. - -### Constants -Constant values (those that are read-only) can be specified using the Final annotation as described in [PEP 591](https://www.python.org/dev/peps/pep-0591/). - -Type checkers will also typically treat variables that are named using all upper-case characters as constants. - -In both cases, it is OK to omit the declared type of a constant if it is assigned a literal str, int, float, bool or None value. In such cases, the type inference rules are clear and unambiguous, and adding a literal type annotation would be redundant. - -```python -# All-caps constant with inferred type -COLOR_FORMAT_RGB = "rgb" - -# All-caps constant with explicit type -COLOR_FORMAT_RGB: Literal["rgb"] = "rgb" -LATEST_VERSION: Tuple[int, int] = (4, 5) - -# Final variable with inferred type -ColorFormatRgb: Final = "rgb" - -# Final variable with explicit type -ColorFormatRgb: Final[Literal["rgb"]] = "rgb" -LATEST_VERSION: Final[Tuple[int, int]] = (4, 5) -``` - -### Typed Dictionaries, Data Classes, and Named Tuples -If your library runs only on newer versions of Python, you are encouraged to use some of the new type-friendly classes. - -NamedTuple (described in [PEP 484](https://www.python.org/dev/peps/pep-0484/)) is preferred over namedtuple. - -Data classes (described in [PEP 557](https://www.python.org/dev/peps/pep-0557/)) is preferred over untyped dictionaries. - -TypedDict (described in [PEP 589](https://www.python.org/dev/peps/pep-0589/)) is preferred over untyped dictionaries. - - -## Compatibility with Older Python Versions -Each new version of Python from 3.5 onward has introduced new typing constructs. This presents a challenge for library authors who want to maintain runtime compatibility with older versions of Python. This section documents several techniques that can be used to add types while maintaining backward compatibility. - -### Quoted Annotations -Type annotations for variables, parameters, and return types can be placed in quotes. The Python interpreter will then ignore them, whereas a type checker will interpret them as type annotations. - -```python -# Older versions of Python do not support subscripting -# for the OrderedDict type, so the annotation must be -# enclosed in quotes. -def get_config(self) -> "OrderedDict[str, str]": - return self._config -``` - -### Type Comment Annotations -Python 3.0 introduced syntax for parameter and return type annotations, as specified in [PEP 484](https://www.python.org/dev/peps/pep-0484/). Python 3.6 introduced support for variable type annotations, as specified in [PEP 526](https://www.python.org/dev/peps/pep-0526/). - -If you need to support older versions of Python, type annotations can still be provided as “type comments”. These comments take the form # type: . - -```python -class Foo: - # Variable type comments go at the end of the line - # where the variable is assigned. - timeout = None # type: Optional[int] - - # Function type comments can be specified on the - # line after the function signature. - def send_message(self, name, length): - # type: (str, int) -> None - ... - - # Function type comments can also specify the type - # of each parameter on its own line. - def receive_message( - self, - name, # type: str - length # type: int - ): - # type: () -> Message - ... -``` - -### typing_extensions -New type features that require runtime support are typically included in the stdlib `typing` module. Where possible, these new features are back-ported to a runtime library called `typing_extensions` that works with older Python runtimes. - -### TYPE_CHECKING -The `typing` module exposes a variable called `TYPE_CHECKING` which has a value of False within the Python runtime but a value of True when the type checker is performing its analysis. This allows type checking statements to be conditionalized. - -Care should be taken when using `TYPE_CHECKING` because behavioral changes between type checking and runtime could mask problems that the type checker would otherwise catch. - - -## Non-Standard Type Behaviors -Type annotations provide a way to annotate typical type behaviors, but some classes implement specialized, non-standard behaviors that cannot be described using standard type annotations. For now, such types need to be annotated as Any, which is unfortunate because the benefits of static typing are lost. - - -## Docstrings -Docstrings should be provided for all classes, functions, and methods in the interface. They should be formatted according to [PEP 257](https://www.python.org/dev/peps/pep-0257/). - -There is currently no single agreed-upon standard for function and method docstrings, but several common variants have emerged. We recommend using one of these variants. diff --git a/docs/libraries.rst b/docs/libraries.rst new file mode 100644 index 000000000..0c1942c37 --- /dev/null +++ b/docs/libraries.rst @@ -0,0 +1,650 @@ +*********************** +Typing Python Libraries +*********************** + +Much of Python’s popularity can be attributed to the rich collection of +Python libraries available to developers. Authors of these libraries +play an important role in improving the experience for Python +developers. This document provides some recommendations and guidance for +Python library authors. + +These recommendations are intended to provide the following benefits: + +1. Consumers of libraries should have a great coding experience with + fast and accurate completion suggestions, class and function + documentation, signature help (including parameter default values), + hover text, and auto-imports. This should happen by default without + needing to download extra packages and without any special + configuration. These features should be consistent across the Python + ecosystem regardless of a developer’s choice of editor, IDE, notebook + environment, etc. +2. Consumers of libraries should be able to rely on complete and + accurate type information so static type checkers can detect and + report type inconsistencies and other violations of the interface + contract. +3. Library authors should be able to specify a well-defined interface + contract that is enforced by tools. This allows a library + implementation to evolve and improve without breaking consumers of + the library. +4. Library authors should have the benefits of static type checking to + produce high-quality, bug-free implementations. + +Inlined Type Annotations and Type Stubs +======================================= + +`PEP 561 `__ documents +several ways type information can be delivered for a library: inlined +type annotations, type stub files included in the package, a separate +companion type stub package, and type stubs in the typeshed repository. +Some of these options fall short on delivering the benefits above. We +therefore provide the following more specific guidance to library +authors. + +.. note:: + All libraries should include inlined type annotations for the + functions, classes, methods, and constants that comprise the public + interface for the library. + +Inlined type annotations should be included directly within the source +code that ships with the package. Of the options listed in PEP 561, +inlined type annotations offer the most benefits. They typically require +the least effort to add and maintain, they are always consistent with +the implementation, and docstrings and default parameter values are +readily available, allowing language servers to enhance the development +experience. + +There are cases where inlined type annotations are not possible — most +notably when a library’s exposed functionality is implemented in a +language other than Python. + +.. note:: + Libraries that expose symbols implemented in languages other than + Python should include stub (``.pyi``) files that describe the types for + those symbols. These stubs should also contain docstrings and default + parameter values. + +In many existing type stubs (such as those found in typeshed), default +parameter values are replaced with with ``...`` and all docstrings are +removed. We recommend that default values and docstrings remain within +the type stub file so language servers can display this information to +developers. + +Library Interface +================= + +`PEP 561 `__ indicates that a +``py.typed`` marker file must be included in the package if the author +wishes to support type checking of their code. + +If a ``py.typed`` module is present, a type checker will treat all modules +within that package (i.e. all files that end in ``.py`` or ``.pyi``) as +importable unless the file name begins with an underscore. These modules +comprise the supported interface for the library. + +Each module exposes a set of symbols. Some of these symbols are +considered "private” — implementation details that are not part of the +library’s interface. Type checkers like pyright use the following rules +to determine which symbols are visible outside of the package. + +- Symbols whose names begin with an underscore (but are not dunder + names) are considered private. +- Imported symbols are considered private by default. If they use the + ``import A as A`` (a redundant module alias), ``from X import A as A`` (a + redundant symbol alias), or ``from . import A`` forms, symbol ``A`` is + not private unless the name begins with an underscore. If a file + ``__init__.py`` uses form ``from .A import X``, symbol ``A`` is treated + likewise. If a wildcard import (of the form ``from X import *``) is + used, all symbols referenced by the wildcard are not private. +- A module can expose an ``__all__`` symbol at the module level that + provides a list of names that are considered part of the interface. + This overrides all other rules above, allowing imported symbols or + symbols whose names begin with an underscore to be included in the + interface. +- Local variables within a function (including nested functions) are + always considered private. + +The following idioms are supported for defining the values contained +within ``__all__``. These restrictions allow type checkers to statically +determine the value of ``__all__``. + +- ``__all__ = ('a', b')`` +- ``__all__ = ['a', b']`` +- ``__all__ += ['a', b']`` +- ``__all__ += submodule.__all__`` +- ``__all__.extend(['a', b'])`` +- ``__all__.extend(submodule.__all__)`` +- ``__all__.append('a')`` +- ``__all__.remove('a')`` + +Type Completeness +================= + +A “py.typed” library is said to be “type complete” if all of the symbols +that comprise its interface have type annotations that refer to types +that are fully known. Private symbols are exempt. + +A “known type” is defined as follows: + +Classes: + +- All class variables, instance variables, and methods that are + “visible” (not overridden) are annotated and refer to known types +- If a class is a subclass of a generic class, type arguments are + provided for each generic type parameter, and these type arguments + are known types + +Functions and Methods: + +- All input parameters have type annotations that refer to known types +- The return parameter is annotated and refers to a known type +- The result of applying one or more decorators results in a known type + +Type Aliases: + +- All of the types referenced by the type alias are known + +Variables: + +- All variables have type annotations that refer to known types + +Type annotations can be omitted in a few specific cases where the type +is obvious from the context: + +- Constants that are assigned simple literal values + (e.g. ``RED = '#F00'`` or ``MAX_TIMEOUT = 50`` or + ``room_temperature: Final = 20``). A constant is a symbol that is + assigned only once and is either annotated with ``Final`` or is named + in all-caps. A constant that is not assigned a simple literal value + requires explicit annotations, preferably with a ``Final`` annotation + (e.g. ``WOODWINDS: Final[List[str]] = ['Oboe', 'Bassoon']``). +- Enum values within an Enum class do not require annotations because + they take on the type of the Enum class. +- Type aliases do not require annotations. A type alias is a symbol + that is defined at a module level with a single assignment where the + assigned value is an instantiable type, as opposed to a class + instance + (e.g. ``Foo = Callable[[Literal["a", "b"]], Union[int, str]]`` or + ``Bar = Optional[MyGenericClass[int]]``). +- The “self” parameter in an instance method and the “cls” parameter in + a class method do not require an explicit annotation. +- The return type for an ``__init__`` method does not need to be + specified, since it is always ``None``. +- The following module-level symbols do not require type annotations: + ``__all__``,\ ``__author__``, ``__copyright__``, ``__email__``, + ``__license__``, ``__title__``, ``__uri__``, ``__version__``. +- The following class-level symbols do not require type annotations: + ``__class__``, ``__dict__``, ``__doc__``, ``__module__``, + ``__slots__``. + +Examples of known and unknown types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: python + + + # Variable with unknown type + a = [3, 4, 5] + + # Variable with known type + a: List[int] = [3, 4, 5] + + # Type alias with partially unknown type (because type + # arguments are missing for list and dict) + DictOrList = Union[list, dict] + + # Type alias with known type + DictOrList = Union[List[Any], Dict[str, Any]] + + # Generic type alias with known type + _T = TypeVar("_T") + DictOrList = Union[List[_T], Dict[str, _T]] + + # Function with known type + def func(a: Optional[int], b: Dict[str, float] = {}) -> None: + pass + + # Function with partially unknown type (because type annotations + # are missing for input parameters and return type) + def func(a, b): + pass + + # Function with partially unknown type (because of missing + # type args on Dict) + def func(a: int, b: Dict) -> None: + pass + + # Function with partially unknown type (because return type + # annotation is missing) + def func(a: int, b: Dict[str, float]): + pass + + # Decorator with partially unknown type (because type annotations + # are missing for input parameters and return type) + def my_decorator(func): + return func + + # Function with partially unknown type (because type is obscured + # by untyped decorator) + @my_decorator + def func(a: int) -> str: + pass + + + # Class with known type + class MyClass: + height: float = 2.0 + + def __init__(self, name: str, age: int): + self.age: int = age + + @property + def name(self) -> str: + ... + + # Class with partially unknown type + class MyClass: + # Missing type annotation for class variable + height = 2.0 + + # Missing input parameter annotations + def __init__(self, name, age): + # Missing type annotation for instance variable + self.age = age + + # Missing return type annotation + @property + def name(self): + ... + + # Class with partially unknown type + class BaseClass: + # Missing type annotation + height = 2.0 + + # Missing type annotation + def get_stuff(self): + ... + + # Class with known type (because it overrides all symbols + # exposed by BaseClass that have incomplete types) + class DerivedClass(BaseClass): + height: float + + def get_stuff(self) -> str: + ... + + # Class with partially unknown type because base class + # (dict) is generic, and type arguments are not specified. + class DictSubclass(dict): + pass + +Verifying Type Completeness +=========================== + +Pyright provides a feature that allows library authors to verify type +completeness for a “py.typed” package. To use this feature, create a +clean Python environment and install your package along with all of the +other dependent packages. Run the CLI version of pyright with the +``--verifytypes`` option. + +``pyright --verifytypes `` + +Pyright will analyze the library, identify all symbols that comprise the +interface to the library and emit errors for any symbols whose types are +unknown. It also produces a “type completeness score” which is the +percentage of symbols with known types. + +To see additional details (including a full list of symbols in the +library), append the ``--verbose`` option. + +The ``--verifytypes`` option can be combined with ``--outputjson`` to +emit the results in a form that can be consumed by other tools. + +The ``--verifytypes`` feature can be integrated into a continuous +integration (CI) system to verify that a library remains “type +complete”. + +If the ``--verifytypes`` option is combined with ``--ignoreexternal``, +any incomplete types that are imported from other external packages are +ignored. This allows library authors to focus on adding type annotations +for the code that is directly under their control. + +Improving Type Completeness +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here are some tips for increasing the type completeness score for your +library: + +- If your package includes tests or sample code, consider removing them + from the distribution. If there is good reason to include them, + consider placing them in a directory that begins with an underscore + so they are not considered part of your library’s interface. +- If your package includes submodules that are meant to be + implementation details, rename those files to begin with an + underscore. +- If a symbol is not intended to be part of the library’s interface and + is considered an implementation detail, rename it such that it begins + with an underscore. It will then be considered private and excluded + from the type completeness check. +- If your package exposes types from other libraries, work with the + maintainers of these other libraries to achieve type completeness. + +Best Practices for Inlined Types +================================ + +Wide vs. Narrow Types +~~~~~~~~~~~~~~~~~~~~~ + +In type theory, when comparing two types that are related to each other, +the “wider” type is the one that is more general, and the “narrower” +type is more specific. For example, ``Sequence[str]`` is a wider type +than ``List[str]`` because all ``List`` objects are also ``Sequence`` +objects, but the converse is not true. A subclass is narrower than a +class it derives from. A union of types is wider than the individual +types that comprise the union. + +In general, a function input parameter should be annotated with the +widest possible type supported by the implementation. For example, if +the implementation requires the caller to provide an iterable collection +of strings, the parameter should be annotated as ``Iterable[str]``, not +as ``List[str]``. The latter type is narrower than necessary, so if a +user attempts to pass a tuple of strings (which is supported by the +implementation), a type checker will complain about a type +incompatibility. + +As a specific application of the “use the widest type possible” rule, +libraries should generally use immutable forms of container types +instead of mutable forms (unless the function needs to modify the +container). Use ``Sequence`` rather than ``List``, ``Mapping`` rather +than ``Dict``, etc. Immutable containers allow for more flexibility +because their type parameters are covariant rather than invariant. A +parameter that is typed as ``Sequence[Union[str, int]]`` can accept a +``List[int]``, ``Sequence[str]``, and a ``Sequence[int]``. But a +parameter typed as ``List[Union[str, int]]`` is much more restrictive +and accepts only a ``List[Union[str, int]]``. + +Overloads +~~~~~~~~~ + +If a function or method can return multiple different types and those +types can be determined based on the presence or types of certain +parameters, use the ``@overload`` mechanism defined in `PEP +484 `__. When overloads +are used within a “.py” file, they must appear prior to the function +implementation, which should not have an ``@overload`` decorator. + +Keyword-only Parameters +~~~~~~~~~~~~~~~~~~~~~~~ + +If a function or method is intended to take parameters that are +specified only by name, use the keyword-only separator (``*``). + +.. code:: python + + def create_user(age: int, *, dob: Optional[date] = None): + ... + +Annotating Decorators +~~~~~~~~~~~~~~~~~~~~~ + +Decorators modify the behavior of a class or a function. Providing +annotations for decorators is straightforward if the decorator retains +the original signature of the decorated function. + +.. code:: python + + _F = TypeVar("_F", bound=Callable[..., Any]) + + def simple_decorator(_func: _F) -> _F: + """ + Simple decorators are invoked without parentheses like this: + @simple_decorator + def my_function(): ... + """ + ... + + def complex_decorator(*, mode: str) -> Callable[[_F], _F]: + """ + Complex decorators are invoked with arguments like this: + @complex_decorator(mode="easy") + def my_function(): ... + """ + ... + +Decorators that mutate the signature of the decorated function present +challenges for type annotations. The ``ParamSpec`` and ``Concatenate`` +mechanisms described in `PEP +612 `__ provide some help +here, but these are available only in Python 3.10 and newer. More +complex signature mutations may require type annotations that erase the +original signature, thus blinding type checkers and other tools that +provide signature assistance. As such, library authors are discouraged +from creating decorators that mutate function signatures in this manner. + +Generic Classes and Functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Classes and functions that can operate in a generic manner on various +types should declare themselves as generic using the mechanisms +described in `PEP 484 `__. +This includes the use of ``TypeVar`` symbols. Typically, a ``TypeVar`` +should be private to the file that declares it, and should therefore +begin with an underscore. + +Type Aliases +~~~~~~~~~~~~ + +Type aliases are symbols that refer to other types. Generic type aliases +(those that refer to unspecialized generic classes) are supported by +most type checkers. Pyright also provides support for recursive type +aliases. + +`PEP 613 `__ provides a way +to explicitly designate a symbol as a type alias using the new TypeAlias +annotation. + +.. code:: python + + # Simple type alias + FamilyPet = Union[Cat, Dog, GoldFish] + + # Generic type alias + ListOrTuple = Union[List[_T], Tuple[_T, ...]] + + # Recursive type alias + TreeNode = Union[LeafNode, List["TreeNode"]] + + # Explicit type alias using PEP 613 syntax + StrOrInt: TypeAlias = Union[str, int] + +Abstract Classes and Methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Classes that must be subclassed should derive from ``ABC``, and methods +or properties that must be overridden should be decorated with the +``@abstractmethod`` decorator. This allows type checkers to validate +that the required methods have been overridden and provide developers +with useful error messages when they are not. It is customary to +implement an abstract method by raising a ``NotImplementedError`` +exception. + +.. code:: python + + from abc import ABC, abstractmethod + + class Hashable(ABC): + @property + @abstractmethod + def hash_value(self) -> int: + """Subclasses must override""" + raise NotImplementedError() + + @abstractmethod + def print(self) -> str: + """Subclasses must override""" + raise NotImplementedError() + +Final Classes and Methods +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Classes that are not intended to be subclassed should be decorated as +``@final`` as described in `PEP +591 `__. The same decorator +can also be used to specify methods that cannot be overridden by +subclasses. + +Literals +~~~~~~~~ + +Type annotations should make use of the Literal type where appropriate, +as described in `PEP 586 `__. +Literals allow for more type specificity than their non-literal +counterparts. + +Constants +~~~~~~~~~ + +Constant values (those that are read-only) can be specified using the +Final annotation as described in `PEP +591 `__. + +Type checkers will also typically treat variables that are named using +all upper-case characters as constants. + +In both cases, it is OK to omit the declared type of a constant if it is +assigned a literal str, int, float, bool or None value. In such cases, +the type inference rules are clear and unambiguous, and adding a literal +type annotation would be redundant. + +.. code:: python + + # All-caps constant with inferred type + COLOR_FORMAT_RGB = "rgb" + + # All-caps constant with explicit type + COLOR_FORMAT_RGB: Literal["rgb"] = "rgb" + LATEST_VERSION: Tuple[int, int] = (4, 5) + + # Final variable with inferred type + ColorFormatRgb: Final = "rgb" + + # Final variable with explicit type + ColorFormatRgb: Final[Literal["rgb"]] = "rgb" + LATEST_VERSION: Final[Tuple[int, int]] = (4, 5) + +Typed Dictionaries, Data Classes, and Named Tuples +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your library runs only on newer versions of Python, you are +encouraged to use some of the new type-friendly classes. + +NamedTuple (described in `PEP +484 `__) is preferred over +namedtuple. + +Data classes (described in `PEP +557 `__) is preferred over +untyped dictionaries. + +TypedDict (described in `PEP +589 `__) is preferred over +untyped dictionaries. + +Compatibility with Older Python Versions +======================================== + +Each new version of Python from 3.5 onward has introduced new typing +constructs. This presents a challenge for library authors who want to +maintain runtime compatibility with older versions of Python. This +section documents several techniques that can be used to add types while +maintaining backward compatibility. + +Quoted Annotations +~~~~~~~~~~~~~~~~~~ + +Type annotations for variables, parameters, and return types can be +placed in quotes. The Python interpreter will then ignore them, whereas +a type checker will interpret them as type annotations. + +.. code:: python + + # Older versions of Python do not support subscripting + # for the OrderedDict type, so the annotation must be + # enclosed in quotes. + def get_config(self) -> "OrderedDict[str, str]": + return self._config + +Type Comment Annotations +~~~~~~~~~~~~~~~~~~~~~~~~ + +Python 3.0 introduced syntax for parameter and return type annotations, +as specified in `PEP 484 `__. +Python 3.6 introduced support for variable type annotations, as +specified in `PEP 526 `__. + +If you need to support older versions of Python, type annotations can +still be provided as “type comments”. These comments take the form # +type: . + +.. code:: python + + class Foo: + # Variable type comments go at the end of the line + # where the variable is assigned. + timeout = None # type: Optional[int] + + # Function type comments can be specified on the + # line after the function signature. + def send_message(self, name, length): + # type: (str, int) -> None + ... + + # Function type comments can also specify the type + # of each parameter on its own line. + def receive_message( + self, + name, # type: str + length # type: int + ): + # type: () -> Message + ... + +typing_extensions +~~~~~~~~~~~~~~~~~ + +New type features that require runtime support are typically included in +the stdlib ``typing`` module. Where possible, these new features are +back-ported to a runtime library called ``typing_extensions`` that works +with older Python runtimes. + +TYPE_CHECKING +~~~~~~~~~~~~~ + +The ``typing`` module exposes a variable called ``TYPE_CHECKING`` which +has a value of False within the Python runtime but a value of True when +the type checker is performing its analysis. This allows type checking +statements to be conditionalized. + +Care should be taken when using ``TYPE_CHECKING`` because behavioral +changes between type checking and runtime could mask problems that the +type checker would otherwise catch. + +Non-Standard Type Behaviors +=========================== + +Type annotations provide a way to annotate typical type behaviors, but +some classes implement specialized, non-standard behaviors that cannot +be described using standard type annotations. For now, such types need +to be annotated as Any, which is unfortunate because the benefits of +static typing are lost. + +Docstrings +========== + +Docstrings should be provided for all classes, functions, and methods in +the interface. They should be formatted according to `PEP +257 `__. + +There is currently no single agreed-upon standard for function and +method docstrings, but several common variants have emerged. We +recommend using one of these variants. From e3bbd5e749527b41a177d64b2daa32c75bf5bfc8 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 2 Nov 2021 21:47:10 +0100 Subject: [PATCH 049/539] Upgrade Sphinx (#917) --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 35236bd90..4aa5fce07 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,7 +3,7 @@ # Sphinx version is pinned so that new versions that introduce new warnings # won't suddenly cause build failures. Updating the version is fine as long # as no warnings are raised by doing so. -sphinx==3.2.1 +sphinx==4.2.0 # The theme used by the documentation is stored separately, so we need # to install that as well. From 1fe5d43886b7f1c735eb556247dd1361f26edff6 Mon Sep 17 00:00:00 2001 From: Shannon Zhu Date: Tue, 2 Nov 2021 13:48:02 -0700 Subject: [PATCH 050/539] Move individual pages from toplevel docs (#916) --- docs/README.rst | 6 ++++++ docs/index.rst | 4 ++-- docs/{ => source}/libraries.rst | 0 docs/{ => source}/stubs.rst | 0 4 files changed, 8 insertions(+), 2 deletions(-) rename docs/{ => source}/libraries.rst (100%) rename docs/{ => source}/stubs.rst (100%) diff --git a/docs/README.rst b/docs/README.rst index 359fc0314..50c7fa4b5 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -1,6 +1,12 @@ Python Typing Documentation ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Reading the docs +================= + +The live documentation for Python's static typing can be found at +[typing.readthedocs.io](https://typing.readthedocs.io/). + Building the docs ================= diff --git a/docs/index.rst b/docs/index.rst index 2c44b7f8f..ba8384549 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,8 +7,8 @@ Static Typing with Python :caption: Contents: typing Module Documentation - libraries - stubs + source/libraries + source/stubs Indices and tables diff --git a/docs/libraries.rst b/docs/source/libraries.rst similarity index 100% rename from docs/libraries.rst rename to docs/source/libraries.rst diff --git a/docs/stubs.rst b/docs/source/stubs.rst similarity index 100% rename from docs/stubs.rst rename to docs/source/stubs.rst From 6eebf9f5f249293b824abcafed1183c68c5b3371 Mon Sep 17 00:00:00 2001 From: Shannon Zhu Date: Tue, 2 Nov 2021 13:57:31 -0700 Subject: [PATCH 051/539] Fix link in README (#918) --- docs/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.rst b/docs/README.rst index 50c7fa4b5..6b431c811 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -5,7 +5,7 @@ Reading the docs ================= The live documentation for Python's static typing can be found at -[typing.readthedocs.io](https://typing.readthedocs.io/). +`typing.readthedocs.io `_. Building the docs ================= From 7d797bad1f65b3233bb2c31cec26d986e954603c Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 4 Nov 2021 19:09:47 +0100 Subject: [PATCH 052/539] Use Semantic Versioning for typing_extensions (#907) Bump version to 4.0.0-pre --- CONTRIBUTING.md | 16 +++++----------- typing_extensions/CHANGELOG | 4 ++++ typing_extensions/MANIFEST.in | 2 +- typing_extensions/README.rst | 7 +++++++ typing_extensions/setup.cfg | 1 + typing_extensions/setup.py | 2 -- 6 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 typing_extensions/CHANGELOG diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a26372d55..17e6df3b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ Code in this repository should follow CPython's style guidelines and contributors need to sign the PSF Contributor Agreement. -# typing_extensions +# typing\_extensions The `typing_extensions` module provides a way to access new features from the standard library `typing` module in older versions of Python. For example, Python 3.10 adds @@ -18,19 +18,13 @@ standard library, so that users can experiment with them before they are added t standard library. Such features should ideally already be specified in a PEP or draft PEP. -`typing_extensions` still supports all Python versions supported by `typing`, down to -Python 2.7 and 3.4. However, it is OK to omit support for Python versions that have -reached end of life if doing so is too difficult or otherwise does not make sense. For -example, `typing_extensions.AsyncGenerator` only exists on Python 3.6 and higher, -because async generators were added to the language in 3.6. +`typing_extensions` supports Python versions 3.6 an up. # Versioning scheme -The version number of `typing_extensions` indicates the version of the standard library `typing` -module that is reflected in the backport. For example, `typing_extensions` version -3.10.0.0 includes features from the Python 3.10.0 standard library's `typing` module. A -new release that doesn't include any new standard library features would be called -3.10.0.1. +Starting with version 4.0.0, `typing_extensions` uses +[Semantic Versioning](https://semver.org/). The major version is incremented for all +backwards-incompatible changes. # Workflow for PyPI releases diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG new file mode 100644 index 000000000..d8820afa0 --- /dev/null +++ b/typing_extensions/CHANGELOG @@ -0,0 +1,4 @@ +# Changes in version 4.0.0 + +Starting with version 4.0.0, typing_extensions uses Semantic Versioning. +See the README for more information. diff --git a/typing_extensions/MANIFEST.in b/typing_extensions/MANIFEST.in index a727bf4b7..c399b170d 100644 --- a/typing_extensions/MANIFEST.in +++ b/typing_extensions/MANIFEST.in @@ -1,3 +1,3 @@ -include LICENSE README.rst +include CHANGELOG LICENSE README.rst include src_py3/typing_extensions.py include src_py3/test_typing_extensions.py diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 879596e4c..a30c31193 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -19,6 +19,13 @@ the ``typing`` module from PyPi instead of using this one unless specifically writing code that must be compatible with multiple Python versions or requires experimental types. +Starting with version 4.0.0, ``typing_extensions`` uses +`Semantic Versioning `_. The +major version is incremented for all backwards-incompatible changes, including +dropping support for older Python versions. Therefore, it's safe to depend +on ``typing_extensions`` like this: ``typing_extensions >=x.y, <(x+1)``, +where ``x.y`` is the first version that includes all features you need. + Included items ============== diff --git a/typing_extensions/setup.cfg b/typing_extensions/setup.cfg index 328dc21ca..1d80ff91f 100644 --- a/typing_extensions/setup.cfg +++ b/typing_extensions/setup.cfg @@ -1,4 +1,5 @@ [metadata] +version = 4.0.0-pre license-file = LICENSE [options] diff --git a/typing_extensions/setup.py b/typing_extensions/setup.py index 46a4227c1..dde798433 100644 --- a/typing_extensions/setup.py +++ b/typing_extensions/setup.py @@ -9,7 +9,6 @@ 'to install typing_extensions.\n') exit(1) -version = '3.10.0.2' description = 'Backported and Experimental Type Hints for Python 3.6+' long_description = '''\ Typing Extensions -- Backported and Experimental Type Hints for Python @@ -40,7 +39,6 @@ ] setup(name='typing_extensions', - version=version, description=description, long_description=long_description, author='Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee', From d2981209e30d24fce7fa0c40241d15103c3c69da Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Thu, 11 Nov 2021 10:17:23 +0000 Subject: [PATCH 053/539] typing_extensions: Drop Python 2.7, modernize build (#931) * Drop Python 2.7 support -- tests, linting * Adopt pyproject.toml and a build backend (flit) * Add Python 3.7+ types to README.rst --- .flake8 | 2 - .flake8-tests | 2 - CONTRIBUTING.md | 15 +++---- test-requirements.txt | 9 ++--- typing_extensions/README.rst | 10 ++++- typing_extensions/pyproject.toml | 67 ++++++++++++++++++++++++++++++++ typing_extensions/setup.cfg | 6 --- typing_extensions/setup.py | 53 ------------------------- 8 files changed, 86 insertions(+), 78 deletions(-) create mode 100644 typing_extensions/pyproject.toml delete mode 100644 typing_extensions/setup.cfg delete mode 100644 typing_extensions/setup.py diff --git a/.flake8 b/.flake8 index a40ac7fff..be291da98 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,5 @@ [flake8] -# fake builtins for python2/* -builtins = basestring, unicode max-line-length = 90 ignore = # irrelevant plugins diff --git a/.flake8-tests b/.flake8-tests index dab93cc62..5a97fe897 100644 --- a/.flake8-tests +++ b/.flake8-tests @@ -6,8 +6,6 @@ # This will be possibly merged in the future. [flake8] -# fake builtins for python2/* -builtins = basestring, unicode max-line-length = 100 ignore = # temporary ignores until we sort it out diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17e6df3b4..895ab8d91 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ standard library, so that users can experiment with them before they are added t standard library. Such features should ideally already be specified in a PEP or draft PEP. -`typing_extensions` supports Python versions 3.6 an up. +`typing_extensions` supports Python versions 3.6 and up. # Versioning scheme @@ -30,17 +30,14 @@ backwards-incompatible changes. - Ensure that GitHub Actions reports no errors. -- Update the version number in `setup.py`. +- Update the version number in `pyproject.toml`. - Build the source and wheel distributions: - - `pip3 install -U setuptools wheel` - - `pip2 install -U setuptools wheel` - - `rm -rf dist/ build/` - - `python3 setup.py sdist bdist_wheel` - - `rm -rf build/` (Works around - `a Wheel bug `\_) - - `python2 setup.py bdist_wheel` + - `pip3 install -U flit` + - `cd typing_extensions` + - `rm -rf dist/` + - `flit build --no-setup-py` - Install the built distributions locally and test (if you were using `tox`, you already tested the source distribution). diff --git a/test-requirements.txt b/test-requirements.txt index 13a0c968f..e29654168 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,5 +1,4 @@ -flake8; python_version >= '3.6' -flake8-bugbear; python_version >= '3.6' -flake8-pyi; python_version >= '3.6' -pytest==4.6.11; python_version < '3.0' -pytest; python_version >= '3.0' +flake8 +flake8-bugbear +flake8-pyi +pytest diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index a30c31193..c89fc0425 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -50,7 +50,7 @@ This module currently contains the following: - ``Literal`` - ``NewType`` - ``NoReturn`` -- ``overload`` (note that older versions of ``typing`` only let you use ``overload`` in stubs) +- ``overload`` - ``OrderedDict`` - ``ParamSpec`` - ``ParamSpecArgs`` @@ -64,6 +64,14 @@ This module currently contains the following: - ``TypeGuard`` - ``TYPE_CHECKING`` +Python 3.7+ +----------- + +- ``get_origin`` +- ``get_args`` +- ``get_type_hints`` + + Other Notes and Limitations =========================== diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml new file mode 100644 index 000000000..2ea4171b5 --- /dev/null +++ b/typing_extensions/pyproject.toml @@ -0,0 +1,67 @@ +# Build system requirements. +[build-system] +requires = ["flit_core >=3.4,<4"] +build-backend = "flit_core.buildapi" + +# Project metadata +[project] +name = "typing_extensions" +version = "4.0.0-pre" +description = "Backported and Experimental Type Hints for Python 3.6+" +readme.text = """\ +Typing Extensions -- Backported and Experimental Type Hints for Python + +The ``typing`` module was added to the standard library in Python 3.5, but +many new features have been added to the module since then. +This means users of older Python versions who are unable to upgrade will not be +able to take advantage of new types added to the ``typing`` module, such as +``typing.Protocol`` or ``typing.TypedDict``. + +The ``typing_extensions`` module contains backports of these changes. +Experimental types that may eventually be added to the ``typing`` +module are also included in ``typing_extensions``. +""" +readme.content-type = "text/x-rst" +requires-python = ">=3.6" +urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" +license.file = "LICENSE" +keywords = [ + "annotations", + "backport", + "checker", + "checking", + "function", + "hinting", + "hints", + "type", + "typechecking", + "typehinting", + "typehints", + "typing" +] +# Classifiers list: https://pypi.org/classifiers/ +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: Python Software Foundation License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Software Development" +] + +# Project metadata -- authors. Flit stores this as a list of dicts, so it can't +# be inline above. +[[project.authors]] +name = "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" +email = "levkivskyi@gmail.com" + +# This tells Flit that the module is stored in the src_py3 directory. +[tool.flit.module] +name = "src_py3/typing_extensions" diff --git a/typing_extensions/setup.cfg b/typing_extensions/setup.cfg deleted file mode 100644 index 1d80ff91f..000000000 --- a/typing_extensions/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[metadata] -version = 4.0.0-pre -license-file = LICENSE - -[options] -python_version = >=3.6 diff --git a/typing_extensions/setup.py b/typing_extensions/setup.py deleted file mode 100644 index dde798433..000000000 --- a/typing_extensions/setup.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -import sys -from setuptools import setup - -if sys.version_info < (3, 6, 0): - sys.stderr.write('ERROR: You need Python 3.6+ ' - 'to install typing_extensions.\n') - exit(1) - -description = 'Backported and Experimental Type Hints for Python 3.6+' -long_description = '''\ -Typing Extensions -- Backported and Experimental Type Hints for Python - -The ``typing`` module was added to the standard library in Python 3.5, but -many new features have been added to the module since then. -This means users of older Python versions who are unable to upgrade will not be -able to take advantage of new types added to the ``typing`` module, such as -``typing.Protocol`` or ``typing.TypedDict``. - -The ``typing_extensions`` module contains backports of these changes. -Experimental types that will eventually be added to the ``typing`` -module are also included in ``typing_extensions``. -''' - -classifiers = [ - 'Development Status :: 3 - Alpha', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Python Software Foundation License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Topic :: Software Development', -] - -setup(name='typing_extensions', - description=description, - long_description=long_description, - author='Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee', - author_email='levkivskyi@gmail.com', - url='https://github.com/python/typing/blob/master/typing_extensions/README.rst', - license='PSF', - keywords='typing function annotations type hints hinting checking ' - 'checker typehints typehinting typechecking backport', - package_dir={'': 'src_py3'}, - py_modules=['typing_extensions'], - classifiers=classifiers, - ) From 6992984178107c66d85cd81945be28b054db8d7c Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Thu, 11 Nov 2021 15:20:37 +0000 Subject: [PATCH 054/539] Clean-up `typing_extensions` -- `typing_extensions` (#932) * Remove compatibility constants for old Python versions. * Remove obsolete code branches. * Use f-strings. * Use new style super(). * Inline typing imports. --- typing_extensions/CHANGELOG | 18 + .../src_py3/test_typing_extensions.py | 1391 ++++++-------- .../src_py3/typing_extensions.py | 1698 +++++------------ 3 files changed, 1101 insertions(+), 2006 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index d8820afa0..ac8f868a8 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -2,3 +2,21 @@ Starting with version 4.0.0, typing_extensions uses Semantic Versioning. See the README for more information. + +Dropped support for Python versions 3.5 and older. + +Simplified backports for Python 3.6.0 and newer. +Patch by Adam Turner (@AA-Turner). + +## Removed in version 4.0.0 + +The following non-exported but non-private names have been removed as they are +unneeded for supporting Python 3.6 and newer. + +- TypingMeta +- OLD_GENERICS +- SUBS_TREE +- HAVE_ANNOTATED +- HAVE_PROTOCOLS +- V_co +- VT_co diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 75bdb8ebb..ff786022f 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -3,35 +3,28 @@ import abc import contextlib import collections +import collections.abc import pickle import subprocess import types from unittest import TestCase, main, skipUnless, skipIf +from test import ann_module, ann_module2, ann_module3 +import typing from typing import TypeVar, Optional, Union from typing import T, KT, VT # Not in __all__. -from typing import Tuple, List, Dict, Iterator, Callable -from typing import Generic +from typing import Tuple, List, Dict, Iterable, Iterator, Callable +from typing import Generic, NamedTuple from typing import no_type_check +import typing_extensions from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard - -try: - from typing_extensions import Protocol, runtime, runtime_checkable -except ImportError: - pass -try: - from typing_extensions import Annotated -except ImportError: - pass +from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager +from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload try: from typing_extensions import get_type_hints except ImportError: from typing import get_type_hints -import typing -import typing_extensions -import collections.abc as collections_abc - PEP_560 = sys.version_info[:3] >= (3, 7, 0) OLD_GENERICS = False @@ -40,27 +33,11 @@ except ImportError: OLD_GENERICS = True -# We assume Python versions *below* 3.5.0 will have the most -# up-to-date version of the typing module installed. Since -# the typing module isn't a part of the standard library in older -# versions of Python, those users are likely to have a reasonably -# modern version of `typing` installed from PyPi. -TYPING_LATEST = sys.version_info[:3] < (3, 5, 0) - # Flags used to mark tests that only apply after a specific # version of the typing module. -TYPING_3_5_1 = TYPING_LATEST or sys.version_info[:3] >= (3, 5, 1) -TYPING_3_5_3 = TYPING_LATEST or sys.version_info[:3] >= (3, 5, 3) -TYPING_3_6_1 = TYPING_LATEST or sys.version_info[:3] >= (3, 6, 1) -TYPING_3_10_0 = TYPING_LATEST or sys.version_info[:3] >= (3, 10, 0) -TYPING_3_11_0 = TYPING_LATEST or sys.version_info[:3] >= (3, 11, 0) - -# For typing versions where issubclass(...) and -# isinstance(...) checks are forbidden. -# -# See https://github.com/python/typing/issues/136 -# and https://github.com/python/typing/pull/283 -SUBCLASS_CHECK_FORBIDDEN = TYPING_3_5_3 +TYPING_3_6_1 = sys.version_info[:3] >= (3, 6, 1) +TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) +TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) # For typing versions where instantiating collection # types are allowed. @@ -68,29 +45,20 @@ # See https://github.com/python/typing/issues/367 CAN_INSTANTIATE_COLLECTIONS = TYPING_3_6_1 -# For Python versions supporting async/await and friends. -ASYNCIO = sys.version_info[:2] >= (3, 5) - -# For checks reliant on Python 3.6 syntax changes (e.g. classvar) -PY36 = sys.version_info[:2] >= (3, 6) - -# Protocols are hard to backport to the original version of typing 3.5.0 -HAVE_PROTOCOLS = sys.version_info[:3] != (3, 5, 0) - class BaseTestCase(TestCase): def assertIsSubclass(self, cls, class_or_tuple, msg=None): if not issubclass(cls, class_or_tuple): - message = '%r is not a subclass of %r' % (cls, class_or_tuple) + message = f'{cls!r} is not a subclass of {repr(class_or_tuple)}' if msg is not None: - message += ' : %s' % msg + message += f' : {msg}' raise self.failureException(message) def assertNotIsSubclass(self, cls, class_or_tuple, msg=None): if issubclass(cls, class_or_tuple): - message = '%r is a subclass of %r' % (cls, class_or_tuple) + message = f'{cls!r} is a subclass of {repr(class_or_tuple)}' if msg is not None: - message += ' : %s' % msg + message += f' : {msg}' raise self.failureException(message) @@ -108,7 +76,6 @@ def test_noreturn_subclass_type_error_1(self): with self.assertRaises(TypeError): issubclass(Employee, NoReturn) - @skipUnless(SUBCLASS_CHECK_FORBIDDEN, "Behavior added in typing 3.5.3") def test_noreturn_subclass_type_error_2(self): with self.assertRaises(TypeError): issubclass(NoReturn, Employee) @@ -127,10 +94,9 @@ def test_cannot_subclass(self): with self.assertRaises(TypeError): class A(NoReturn): pass - if SUBCLASS_CHECK_FORBIDDEN: - with self.assertRaises(TypeError): - class A(type(NoReturn)): - pass + with self.assertRaises(TypeError): + class A(type(NoReturn)): + pass def test_cannot_instantiate(self): with self.assertRaises(TypeError): @@ -158,9 +124,8 @@ def test_repr(self): cv = ClassVar[int] self.assertEqual(repr(cv), mod_name + '.ClassVar[int]') cv = ClassVar[Employee] - self.assertEqual(repr(cv), mod_name + '.ClassVar[%s.Employee]' % __name__) + self.assertEqual(repr(cv), mod_name + f'.ClassVar[{__name__}.Employee]') - @skipUnless(SUBCLASS_CHECK_FORBIDDEN, "Behavior added in typing 3.5.3") def test_cannot_subclass(self): with self.assertRaises(TypeError): class C(type(ClassVar)): @@ -203,9 +168,8 @@ def test_repr(self): cv = Final[int] self.assertEqual(repr(cv), mod_name + '.Final[int]') cv = Final[Employee] - self.assertEqual(repr(cv), mod_name + '.Final[%s.Employee]' % __name__) + self.assertEqual(repr(cv), mod_name + f'.Final[{__name__}.Employee]') - @skipUnless(SUBCLASS_CHECK_FORBIDDEN, "Behavior added in typing 3.5.3") def test_cannot_subclass(self): with self.assertRaises(TypeError): class C(type(Final)): @@ -308,8 +272,6 @@ def test_no_multiple_subscripts(self): class OverloadTests(BaseTestCase): def test_overload_fails(self): - from typing_extensions import overload - with self.assertRaises(RuntimeError): @overload @@ -319,8 +281,6 @@ def blah(): blah() def test_overload_succeeds(self): - from typing_extensions import overload - @overload def blah(): pass @@ -331,9 +291,6 @@ def blah(): blah() -ASYNCIO_TESTS = """ -from typing import Iterable -from typing_extensions import Awaitable, AsyncIterator T_a = TypeVar('T_a') @@ -364,24 +321,11 @@ async def __anext__(self) -> T_a: class ACM: async def __aenter__(self) -> int: return 42 + async def __aexit__(self, etype, eval, tb): return None -""" - -if ASYNCIO: - try: - exec(ASYNCIO_TESTS) - except ImportError: - ASYNCIO = False -else: - # fake names for the sake of static analysis - asyncio = None - AwaitableWrapper = AsyncIteratorWrapper = ACM = object - -PY36_TESTS = """ -from test import ann_module, ann_module2, ann_module3 -from typing_extensions import AsyncContextManager -from typing import NamedTuple + + class A: y: float @@ -404,8 +348,10 @@ class NoneAndForward: class XRepr(NamedTuple): x: int y: int = 1 + def __str__(self): return f'{self.x} -> {self.y}' + def __add__(self, other): return 0 @@ -448,23 +394,12 @@ class Animal(BaseAnimal, total=False): class Cat(Animal): fur_color: str -""" - -if PY36: - exec(PY36_TESTS) -else: - # fake names for the sake of static analysis - ann_module = ann_module2 = ann_module3 = None - A = B = CSub = G = CoolEmployee = CoolEmployeeWithDefault = object - XMeth = XRepr = HasCallProtocol = NoneAndForward = Loop = object - Point2D = Point2Dor3D = LabelPoint2D = Options = object - BaseAnimal = Animal = Cat = object + gth = get_type_hints class GetTypeHintTests(BaseTestCase): - @skipUnless(PY36, 'Python 3.6 required') def test_get_type_hints_modules(self): ann_module_type_hints = {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str} if (TYPING_3_11_0 @@ -475,7 +410,6 @@ def test_get_type_hints_modules(self): self.assertEqual(gth(ann_module2), {}) self.assertEqual(gth(ann_module3), {}) - @skipUnless(PY36, 'Python 3.6 required') def test_get_type_hints_classes(self): self.assertEqual(gth(ann_module.C, ann_module.__dict__), {'y': Optional[ann_module.C]}) @@ -491,7 +425,6 @@ def test_get_type_hints_classes(self): self.assertEqual(gth(NoneAndForward, globals()), {'parent': NoneAndForward, 'meaning': type(None)}) - @skipUnless(PY36, 'Python 3.6 required') def test_respect_no_type_check(self): @no_type_check class NoTpCheck: @@ -506,7 +439,6 @@ def meth(x: int): ... class Der(ABase): ... self.assertEqual(gth(ABase.meth), {'x': int}) - @skipUnless(PY36, 'Python 3.6 required') def test_get_type_hints_ClassVar(self): self.assertEqual(gth(ann_module2.CV, ann_module2.__dict__), {'var': ClassVar[ann_module2.CV]}) @@ -517,7 +449,6 @@ def test_get_type_hints_ClassVar(self): 'x': ClassVar[Optional[B]]}) self.assertEqual(gth(G), {'lst': ClassVar[List[T]]}) - @skipUnless(PY36, 'Python 3.6 required') def test_final_forward_ref(self): self.assertEqual(gth(Loop, globals())['attr'], Final[Loop]) self.assertNotEqual(gth(Loop, globals())['attr'], Final[int]) @@ -601,17 +532,15 @@ class C(Generic[T]): pass class CollectionsAbcTests(BaseTestCase): def test_isinstance_collections(self): - self.assertNotIsInstance(1, collections_abc.Mapping) - self.assertNotIsInstance(1, collections_abc.Iterable) - self.assertNotIsInstance(1, collections_abc.Container) - self.assertNotIsInstance(1, collections_abc.Sized) - if SUBCLASS_CHECK_FORBIDDEN: - with self.assertRaises(TypeError): - isinstance(collections.deque(), typing_extensions.Deque[int]) - with self.assertRaises(TypeError): - issubclass(collections.Counter, typing_extensions.Counter[str]) + self.assertNotIsInstance(1, collections.abc.Mapping) + self.assertNotIsInstance(1, collections.abc.Iterable) + self.assertNotIsInstance(1, collections.abc.Container) + self.assertNotIsInstance(1, collections.abc.Sized) + with self.assertRaises(TypeError): + isinstance(collections.deque(), typing_extensions.Deque[int]) + with self.assertRaises(TypeError): + issubclass(collections.Counter, typing_extensions.Counter[str]) - @skipUnless(ASYNCIO, 'Python 3.5 and multithreading required') def test_awaitable(self): ns = {} exec( @@ -624,7 +553,6 @@ def test_awaitable(self): self.assertNotIsInstance(foo, typing_extensions.Awaitable) g.send(None) # Run foo() till completion, to avoid warning. - @skipUnless(ASYNCIO, 'Python 3.5 and multithreading required') def test_coroutine(self): ns = {} exec( @@ -642,7 +570,6 @@ def test_coroutine(self): except StopIteration: pass - @skipUnless(ASYNCIO, 'Python 3.5 and multithreading required') def test_async_iterable(self): base_it = range(10) # type: Iterator[int] it = AsyncIteratorWrapper(base_it) @@ -650,12 +577,10 @@ def test_async_iterable(self): self.assertIsInstance(it, typing_extensions.AsyncIterable) self.assertNotIsInstance(42, typing_extensions.AsyncIterable) - @skipUnless(ASYNCIO, 'Python 3.5 and multithreading required') def test_async_iterator(self): base_it = range(10) # type: Iterator[int] it = AsyncIteratorWrapper(base_it) - if TYPING_3_5_1: - self.assertIsInstance(it, typing_extensions.AsyncIterator) + self.assertIsInstance(it, typing_extensions.AsyncIterator) self.assertNotIsInstance(42, typing_extensions.AsyncIterator) def test_deque(self): @@ -687,8 +612,7 @@ class MyDefDict(typing_extensions.DefaultDict[str, int]): self.assertIsInstance(dd, MyDefDict) self.assertIsSubclass(MyDefDict, collections.defaultdict) - if TYPING_3_5_3: - self.assertNotIsSubclass(collections.defaultdict, MyDefDict) + self.assertNotIsSubclass(collections.defaultdict, MyDefDict) @skipUnless(CAN_INSTANTIATE_COLLECTIONS, "Behavior added in typing 3.6.1") def test_ordereddict_instantiation(self): @@ -711,16 +635,14 @@ class MyOrdDict(typing_extensions.OrderedDict[str, int]): self.assertIsInstance(od, MyOrdDict) self.assertIsSubclass(MyOrdDict, collections.OrderedDict) - if TYPING_3_5_3: - self.assertNotIsSubclass(collections.OrderedDict, MyOrdDict) + self.assertNotIsSubclass(collections.OrderedDict, MyOrdDict) def test_chainmap_instantiation(self): self.assertIs(type(typing_extensions.ChainMap()), collections.ChainMap) self.assertIs(type(typing_extensions.ChainMap[KT, VT]()), collections.ChainMap) self.assertIs(type(typing_extensions.ChainMap[str, int]()), collections.ChainMap) class CM(typing_extensions.ChainMap[KT, VT]): ... - if TYPING_3_5_3: - self.assertIs(type(CM[int, str]()), CM) + self.assertIs(type(CM[int, str]()), CM) def test_chainmap_subclass(self): @@ -731,28 +653,25 @@ class MyChainMap(typing_extensions.ChainMap[str, int]): self.assertIsInstance(cm, MyChainMap) self.assertIsSubclass(MyChainMap, collections.ChainMap) - if TYPING_3_5_3: - self.assertNotIsSubclass(collections.ChainMap, MyChainMap) + self.assertNotIsSubclass(collections.ChainMap, MyChainMap) def test_deque_instantiation(self): self.assertIs(type(typing_extensions.Deque()), collections.deque) self.assertIs(type(typing_extensions.Deque[T]()), collections.deque) self.assertIs(type(typing_extensions.Deque[int]()), collections.deque) class D(typing_extensions.Deque[T]): ... - if TYPING_3_5_3: - self.assertIs(type(D[int]()), D) + self.assertIs(type(D[int]()), D) def test_counter_instantiation(self): self.assertIs(type(typing_extensions.Counter()), collections.Counter) self.assertIs(type(typing_extensions.Counter[T]()), collections.Counter) self.assertIs(type(typing_extensions.Counter[int]()), collections.Counter) class C(typing_extensions.Counter[T]): ... - if TYPING_3_5_3: - self.assertIs(type(C[int]()), C) - if not PEP_560: - self.assertEqual(C.__bases__, (typing_extensions.Counter,)) - else: - self.assertEqual(C.__bases__, (collections.Counter, typing.Generic)) + self.assertIs(type(C[int]()), C) + if not PEP_560: + self.assertEqual(C.__bases__, (typing_extensions.Counter,)) + else: + self.assertEqual(C.__bases__, (collections.Counter, typing.Generic)) def test_counter_subclass_instantiation(self): @@ -762,10 +681,8 @@ class MyCounter(typing_extensions.Counter[int]): d = MyCounter() self.assertIsInstance(d, MyCounter) self.assertIsInstance(d, collections.Counter) - if TYPING_3_5_1: - self.assertIsInstance(d, typing_extensions.Counter) + self.assertIsInstance(d, typing_extensions.Counter) - @skipUnless(PY36, 'Python 3.6 required') def test_async_generator(self): ns = {} exec("async def f():\n" @@ -773,7 +690,6 @@ def test_async_generator(self): g = ns['f']() self.assertIsSubclass(type(g), typing_extensions.AsyncGenerator) - @skipUnless(PY36, 'Python 3.6 required') def test_no_async_generator_instantiation(self): with self.assertRaises(TypeError): typing_extensions.AsyncGenerator() @@ -782,7 +698,6 @@ def test_no_async_generator_instantiation(self): with self.assertRaises(TypeError): typing_extensions.AsyncGenerator[int, int]() - @skipUnless(PY36, 'Python 3.6 required') def test_subclassing_async_generator(self): class G(typing_extensions.AsyncGenerator[int, int]): def asend(self, value): @@ -795,15 +710,15 @@ def athrow(self, typ, val=None, tb=None): g = ns['g'] self.assertIsSubclass(G, typing_extensions.AsyncGenerator) self.assertIsSubclass(G, typing_extensions.AsyncIterable) - self.assertIsSubclass(G, collections_abc.AsyncGenerator) - self.assertIsSubclass(G, collections_abc.AsyncIterable) + self.assertIsSubclass(G, collections.abc.AsyncGenerator) + self.assertIsSubclass(G, collections.abc.AsyncIterable) self.assertNotIsSubclass(type(g), G) instance = G() self.assertIsInstance(instance, typing_extensions.AsyncGenerator) self.assertIsInstance(instance, typing_extensions.AsyncIterable) - self.assertIsInstance(instance, collections_abc.AsyncGenerator) - self.assertIsInstance(instance, collections_abc.AsyncIterable) + self.assertIsInstance(instance, collections.abc.AsyncGenerator) + self.assertIsInstance(instance, collections.abc.AsyncIterable) self.assertNotIsInstance(type(g), G) self.assertNotIsInstance(g, G) @@ -819,7 +734,6 @@ def manager(): self.assertIsInstance(cm, typing_extensions.ContextManager) self.assertNotIsInstance(42, typing_extensions.ContextManager) - @skipUnless(ASYNCIO, 'Python 3.5 required') def test_async_contextmanager(self): class NotACM: pass @@ -831,8 +745,7 @@ def manager(): cm = manager() self.assertNotIsInstance(cm, typing_extensions.AsyncContextManager) - if TYPING_3_5_3: - self.assertEqual(typing_extensions.AsyncContextManager[int].__args__, (int,)) + self.assertEqual(typing_extensions.AsyncContextManager[int].__args__, (int,)) if TYPING_3_6_1: with self.assertRaises(TypeError): isinstance(42, typing_extensions.AsyncContextManager[int]) @@ -866,8 +779,6 @@ def new_user(user_class: Type[U]) -> U: new_user(BasicUser) - @skipUnless(sys.version_info[:3] != (3, 5, 2), - 'Python 3.5.2 has a somewhat buggy Type impl') def test_type_optional(self): A = Optional[Type[BaseException]] @@ -900,7 +811,6 @@ class D(UserName): pass -PY36_PROTOCOL_TESTS = """ class Coordinate(Protocol): x: int y: int @@ -927,6 +837,7 @@ class Position(XAxis, YAxis, Protocol): @runtime class Proto(Protocol): attr: int + def meth(self, arg: str) -> int: ... @@ -935,6 +846,7 @@ class Concrete(Proto): class Other: attr: int = 1 + def meth(self, arg: str) -> int: if arg == 'this': return 1 @@ -943,598 +855,584 @@ def meth(self, arg: str) -> int: class NT(NamedTuple): x: int y: int -""" -if PY36: - exec(PY36_PROTOCOL_TESTS) -else: - # fake names for the sake of static analysis - Coordinate = Point = MyPoint = BadPoint = NT = object - XAxis = YAxis = Position = Proto = Concrete = Other = object +class ProtocolTests(BaseTestCase): + + def test_basic_protocol(self): + @runtime + class P(Protocol): + def meth(self): + pass + class C: pass + class D: + def meth(self): + pass + def f(): + pass + self.assertIsSubclass(D, P) + self.assertIsInstance(D(), P) + self.assertNotIsSubclass(C, P) + self.assertNotIsInstance(C(), P) + self.assertNotIsSubclass(types.FunctionType, P) + self.assertNotIsInstance(f, P) + + def test_everything_implements_empty_protocol(self): + @runtime + class Empty(Protocol): pass + class C: pass + def f(): + pass + for thing in (object, type, tuple, C, types.FunctionType): + self.assertIsSubclass(thing, Empty) + for thing in (object(), 1, (), typing, f): + self.assertIsInstance(thing, Empty) -if HAVE_PROTOCOLS: - class ProtocolTests(BaseTestCase): + def test_function_implements_protocol(self): + def f(): + pass + self.assertIsInstance(f, HasCallProtocol) - def test_basic_protocol(self): - @runtime - class P(Protocol): - def meth(self): - pass - class C: pass - class D: - def meth(self): - pass - def f(): + def test_no_inheritance_from_nominal(self): + class C: pass + class BP(Protocol): pass + with self.assertRaises(TypeError): + class P(C, Protocol): pass - self.assertIsSubclass(D, P) - self.assertIsInstance(D(), P) - self.assertNotIsSubclass(C, P) - self.assertNotIsInstance(C(), P) - self.assertNotIsSubclass(types.FunctionType, P) - self.assertNotIsInstance(f, P) - - def test_everything_implements_empty_protocol(self): - @runtime - class Empty(Protocol): pass - class C: pass - def f(): + with self.assertRaises(TypeError): + class P(Protocol, C): pass - for thing in (object, type, tuple, C, types.FunctionType): - self.assertIsSubclass(thing, Empty) - for thing in (object(), 1, (), typing, f): - self.assertIsInstance(thing, Empty) - - @skipUnless(PY36, 'Python 3.6 required') - def test_function_implements_protocol(self): - def f(): + with self.assertRaises(TypeError): + class P(BP, C, Protocol): pass - self.assertIsInstance(f, HasCallProtocol) + class D(BP, C): pass + class E(C, BP): pass + self.assertNotIsInstance(D(), E) + self.assertNotIsInstance(E(), D) - def test_no_inheritance_from_nominal(self): - class C: pass - class BP(Protocol): pass - with self.assertRaises(TypeError): - class P(C, Protocol): - pass - with self.assertRaises(TypeError): - class P(Protocol, C): - pass - with self.assertRaises(TypeError): - class P(BP, C, Protocol): - pass - class D(BP, C): pass - class E(C, BP): pass - self.assertNotIsInstance(D(), E) - self.assertNotIsInstance(E(), D) - - def test_no_instantiation(self): - class P(Protocol): pass - with self.assertRaises(TypeError): - P() - class C(P): pass - self.assertIsInstance(C(), C) - T = TypeVar('T') - class PG(Protocol[T]): pass - with self.assertRaises(TypeError): - PG() - with self.assertRaises(TypeError): - PG[int]() - with self.assertRaises(TypeError): - PG[T]() - class CG(PG[T]): pass - self.assertIsInstance(CG[int](), CG) + def test_no_instantiation(self): + class P(Protocol): pass + with self.assertRaises(TypeError): + P() + class C(P): pass + self.assertIsInstance(C(), C) + T = TypeVar('T') + class PG(Protocol[T]): pass + with self.assertRaises(TypeError): + PG() + with self.assertRaises(TypeError): + PG[int]() + with self.assertRaises(TypeError): + PG[T]() + class CG(PG[T]): pass + self.assertIsInstance(CG[int](), CG) - def test_cannot_instantiate_abstract(self): - @runtime - class P(Protocol): - @abc.abstractmethod - def ameth(self) -> int: - raise NotImplementedError - class B(P): - pass - class C(B): - def ameth(self) -> int: - return 26 - with self.assertRaises(TypeError): - B() - self.assertIsInstance(C(), P) + def test_cannot_instantiate_abstract(self): + @runtime + class P(Protocol): + @abc.abstractmethod + def ameth(self) -> int: + raise NotImplementedError + class B(P): + pass + class C(B): + def ameth(self) -> int: + return 26 + with self.assertRaises(TypeError): + B() + self.assertIsInstance(C(), P) - def test_subprotocols_extending(self): - class P1(Protocol): - def meth1(self): - pass - @runtime - class P2(P1, Protocol): - def meth2(self): - pass - class C: - def meth1(self): - pass - def meth2(self): - pass - class C1: - def meth1(self): - pass - class C2: - def meth2(self): - pass - self.assertNotIsInstance(C1(), P2) - self.assertNotIsInstance(C2(), P2) - self.assertNotIsSubclass(C1, P2) - self.assertNotIsSubclass(C2, P2) - self.assertIsInstance(C(), P2) - self.assertIsSubclass(C, P2) - - def test_subprotocols_merging(self): - class P1(Protocol): - def meth1(self): - pass - class P2(Protocol): - def meth2(self): - pass - @runtime - class P(P1, P2, Protocol): + def test_subprotocols_extending(self): + class P1(Protocol): + def meth1(self): pass - class C: - def meth1(self): - pass - def meth2(self): - pass - class C1: - def meth1(self): - pass - class C2: - def meth2(self): - pass - self.assertNotIsInstance(C1(), P) - self.assertNotIsInstance(C2(), P) - self.assertNotIsSubclass(C1, P) - self.assertNotIsSubclass(C2, P) - self.assertIsInstance(C(), P) - self.assertIsSubclass(C, P) - - def test_protocols_issubclass(self): - T = TypeVar('T') - @runtime - class P(Protocol): - def x(self): ... - @runtime - class PG(Protocol[T]): - def x(self): ... - class BadP(Protocol): - def x(self): ... - class BadPG(Protocol[T]): - def x(self): ... - class C: - def x(self): ... - self.assertIsSubclass(C, P) - self.assertIsSubclass(C, PG) - self.assertIsSubclass(BadP, PG) - if not PEP_560: - self.assertIsSubclass(PG[int], PG) - self.assertIsSubclass(BadPG[int], P) - self.assertIsSubclass(BadPG[T], PG) - with self.assertRaises(TypeError): - issubclass(C, PG[T]) - with self.assertRaises(TypeError): - issubclass(C, PG[C]) - with self.assertRaises(TypeError): - issubclass(C, BadP) - with self.assertRaises(TypeError): - issubclass(C, BadPG) - with self.assertRaises(TypeError): - issubclass(P, PG[T]) - with self.assertRaises(TypeError): - issubclass(PG, PG[int]) + @runtime + class P2(P1, Protocol): + def meth2(self): + pass + class C: + def meth1(self): + pass + def meth2(self): + pass + class C1: + def meth1(self): + pass + class C2: + def meth2(self): + pass + self.assertNotIsInstance(C1(), P2) + self.assertNotIsInstance(C2(), P2) + self.assertNotIsSubclass(C1, P2) + self.assertNotIsSubclass(C2, P2) + self.assertIsInstance(C(), P2) + self.assertIsSubclass(C, P2) + + def test_subprotocols_merging(self): + class P1(Protocol): + def meth1(self): + pass + class P2(Protocol): + def meth2(self): + pass + @runtime + class P(P1, P2, Protocol): + pass + class C: + def meth1(self): + pass + def meth2(self): + pass + class C1: + def meth1(self): + pass + class C2: + def meth2(self): + pass + self.assertNotIsInstance(C1(), P) + self.assertNotIsInstance(C2(), P) + self.assertNotIsSubclass(C1, P) + self.assertNotIsSubclass(C2, P) + self.assertIsInstance(C(), P) + self.assertIsSubclass(C, P) + + def test_protocols_issubclass(self): + T = TypeVar('T') + @runtime + class P(Protocol): + def x(self): ... + @runtime + class PG(Protocol[T]): + def x(self): ... + class BadP(Protocol): + def x(self): ... + class BadPG(Protocol[T]): + def x(self): ... + class C: + def x(self): ... + self.assertIsSubclass(C, P) + self.assertIsSubclass(C, PG) + self.assertIsSubclass(BadP, PG) + if not PEP_560: + self.assertIsSubclass(PG[int], PG) + self.assertIsSubclass(BadPG[int], P) + self.assertIsSubclass(BadPG[T], PG) + with self.assertRaises(TypeError): + issubclass(C, PG[T]) + with self.assertRaises(TypeError): + issubclass(C, PG[C]) + with self.assertRaises(TypeError): + issubclass(C, BadP) + with self.assertRaises(TypeError): + issubclass(C, BadPG) + with self.assertRaises(TypeError): + issubclass(P, PG[T]) + with self.assertRaises(TypeError): + issubclass(PG, PG[int]) - def test_protocols_issubclass_non_callable(self): - class C: - x = 1 - @runtime - class PNonCall(Protocol): - x = 1 - with self.assertRaises(TypeError): - issubclass(C, PNonCall) - self.assertIsInstance(C(), PNonCall) - PNonCall.register(C) - with self.assertRaises(TypeError): - issubclass(C, PNonCall) - self.assertIsInstance(C(), PNonCall) - # check that non-protocol subclasses are not affected - class D(PNonCall): ... - self.assertNotIsSubclass(C, D) - self.assertNotIsInstance(C(), D) - D.register(C) - self.assertIsSubclass(C, D) - self.assertIsInstance(C(), D) - with self.assertRaises(TypeError): - issubclass(D, PNonCall) + def test_protocols_issubclass_non_callable(self): + class C: + x = 1 + @runtime + class PNonCall(Protocol): + x = 1 + with self.assertRaises(TypeError): + issubclass(C, PNonCall) + self.assertIsInstance(C(), PNonCall) + PNonCall.register(C) + with self.assertRaises(TypeError): + issubclass(C, PNonCall) + self.assertIsInstance(C(), PNonCall) + # check that non-protocol subclasses are not affected + class D(PNonCall): ... + self.assertNotIsSubclass(C, D) + self.assertNotIsInstance(C(), D) + D.register(C) + self.assertIsSubclass(C, D) + self.assertIsInstance(C(), D) + with self.assertRaises(TypeError): + issubclass(D, PNonCall) - def test_protocols_isinstance(self): - T = TypeVar('T') - @runtime - class P(Protocol): - def meth(x): ... - @runtime - class PG(Protocol[T]): - def meth(x): ... - class BadP(Protocol): - def meth(x): ... - class BadPG(Protocol[T]): - def meth(x): ... - class C: - def meth(x): ... - self.assertIsInstance(C(), P) - self.assertIsInstance(C(), PG) - with self.assertRaises(TypeError): - isinstance(C(), PG[T]) - with self.assertRaises(TypeError): - isinstance(C(), PG[C]) - with self.assertRaises(TypeError): - isinstance(C(), BadP) - with self.assertRaises(TypeError): - isinstance(C(), BadPG) - - @skipUnless(PY36, 'Python 3.6 required') - def test_protocols_isinstance_py36(self): - class APoint: - def __init__(self, x, y, label): - self.x = x - self.y = y - self.label = label - class BPoint: - label = 'B' - def __init__(self, x, y): - self.x = x - self.y = y - class C: - def __init__(self, attr): - self.attr = attr - def meth(self, arg): - return 0 - class Bad: pass - self.assertIsInstance(APoint(1, 2, 'A'), Point) - self.assertIsInstance(BPoint(1, 2), Point) - self.assertNotIsInstance(MyPoint(), Point) - self.assertIsInstance(BPoint(1, 2), Position) - self.assertIsInstance(Other(), Proto) - self.assertIsInstance(Concrete(), Proto) - self.assertIsInstance(C(42), Proto) - self.assertNotIsInstance(Bad(), Proto) - self.assertNotIsInstance(Bad(), Point) - self.assertNotIsInstance(Bad(), Position) - self.assertNotIsInstance(Bad(), Concrete) - self.assertNotIsInstance(Other(), Concrete) - self.assertIsInstance(NT(1, 2), Position) - - def test_protocols_isinstance_init(self): - T = TypeVar('T') - @runtime - class P(Protocol): - x = 1 - @runtime - class PG(Protocol[T]): - x = 1 - class C: - def __init__(self, x): - self.x = x - self.assertIsInstance(C(1), P) - self.assertIsInstance(C(1), PG) + def test_protocols_isinstance(self): + T = TypeVar('T') + @runtime + class P(Protocol): + def meth(x): ... + @runtime + class PG(Protocol[T]): + def meth(x): ... + class BadP(Protocol): + def meth(x): ... + class BadPG(Protocol[T]): + def meth(x): ... + class C: + def meth(x): ... + self.assertIsInstance(C(), P) + self.assertIsInstance(C(), PG) + with self.assertRaises(TypeError): + isinstance(C(), PG[T]) + with self.assertRaises(TypeError): + isinstance(C(), PG[C]) + with self.assertRaises(TypeError): + isinstance(C(), BadP) + with self.assertRaises(TypeError): + isinstance(C(), BadPG) - def test_protocols_support_register(self): - @runtime - class P(Protocol): - x = 1 - class PM(Protocol): - def meth(self): pass - class D(PM): pass - class C: pass - D.register(C) - P.register(C) - self.assertIsInstance(C(), P) - self.assertIsInstance(C(), D) + def test_protocols_isinstance_py36(self): + class APoint: + def __init__(self, x, y, label): + self.x = x + self.y = y + self.label = label + class BPoint: + label = 'B' + def __init__(self, x, y): + self.x = x + self.y = y + class C: + def __init__(self, attr): + self.attr = attr + def meth(self, arg): + return 0 + class Bad: pass + self.assertIsInstance(APoint(1, 2, 'A'), Point) + self.assertIsInstance(BPoint(1, 2), Point) + self.assertNotIsInstance(MyPoint(), Point) + self.assertIsInstance(BPoint(1, 2), Position) + self.assertIsInstance(Other(), Proto) + self.assertIsInstance(Concrete(), Proto) + self.assertIsInstance(C(42), Proto) + self.assertNotIsInstance(Bad(), Proto) + self.assertNotIsInstance(Bad(), Point) + self.assertNotIsInstance(Bad(), Position) + self.assertNotIsInstance(Bad(), Concrete) + self.assertNotIsInstance(Other(), Concrete) + self.assertIsInstance(NT(1, 2), Position) + + def test_protocols_isinstance_init(self): + T = TypeVar('T') + @runtime + class P(Protocol): + x = 1 + @runtime + class PG(Protocol[T]): + x = 1 + class C: + def __init__(self, x): + self.x = x + self.assertIsInstance(C(1), P) + self.assertIsInstance(C(1), PG) - def test_none_on_non_callable_doesnt_block_implementation(self): - @runtime - class P(Protocol): - x = 1 - class A: - x = 1 - class B(A): - x = None - class C: - def __init__(self): - self.x = None - self.assertIsInstance(B(), P) - self.assertIsInstance(C(), P) - - def test_none_on_callable_blocks_implementation(self): - @runtime - class P(Protocol): - def x(self): ... - class A: - def x(self): ... - class B(A): - x = None - class C: - def __init__(self): - self.x = None - self.assertNotIsInstance(B(), P) - self.assertNotIsInstance(C(), P) - - def test_non_protocol_subclasses(self): - class P(Protocol): - x = 1 - @runtime - class PR(Protocol): - def meth(self): pass - class NonP(P): - x = 1 - class NonPR(PR): pass - class C: - x = 1 - class D: - def meth(self): pass - self.assertNotIsInstance(C(), NonP) - self.assertNotIsInstance(D(), NonPR) - self.assertNotIsSubclass(C, NonP) - self.assertNotIsSubclass(D, NonPR) - self.assertIsInstance(NonPR(), PR) - self.assertIsSubclass(NonPR, PR) - - def test_custom_subclasshook(self): - class P(Protocol): - x = 1 - class OKClass: pass - class BadClass: - x = 1 - class C(P): - @classmethod - def __subclasshook__(cls, other): - return other.__name__.startswith("OK") - self.assertIsInstance(OKClass(), C) - self.assertNotIsInstance(BadClass(), C) - self.assertIsSubclass(OKClass, C) - self.assertNotIsSubclass(BadClass, C) - - def test_issubclass_fails_correctly(self): - @runtime - class P(Protocol): - x = 1 - class C: pass - with self.assertRaises(TypeError): - issubclass(C(), P) + def test_protocols_support_register(self): + @runtime + class P(Protocol): + x = 1 + class PM(Protocol): + def meth(self): pass + class D(PM): pass + class C: pass + D.register(C) + P.register(C) + self.assertIsInstance(C(), P) + self.assertIsInstance(C(), D) + + def test_none_on_non_callable_doesnt_block_implementation(self): + @runtime + class P(Protocol): + x = 1 + class A: + x = 1 + class B(A): + x = None + class C: + def __init__(self): + self.x = None + self.assertIsInstance(B(), P) + self.assertIsInstance(C(), P) + + def test_none_on_callable_blocks_implementation(self): + @runtime + class P(Protocol): + def x(self): ... + class A: + def x(self): ... + class B(A): + x = None + class C: + def __init__(self): + self.x = None + self.assertNotIsInstance(B(), P) + self.assertNotIsInstance(C(), P) - @skipUnless(not OLD_GENERICS, "New style generics required") - def test_defining_generic_protocols(self): - T = TypeVar('T') - S = TypeVar('S') - @runtime - class PR(Protocol[T, S]): - def meth(self): pass - class P(PR[int, T], Protocol[T]): - y = 1 - self.assertIsSubclass(PR[int, T], PR) - self.assertIsSubclass(P[str], PR) - with self.assertRaises(TypeError): - PR[int] - with self.assertRaises(TypeError): - P[int, str] + def test_non_protocol_subclasses(self): + class P(Protocol): + x = 1 + @runtime + class PR(Protocol): + def meth(self): pass + class NonP(P): + x = 1 + class NonPR(PR): pass + class C: + x = 1 + class D: + def meth(self): pass + self.assertNotIsInstance(C(), NonP) + self.assertNotIsInstance(D(), NonPR) + self.assertNotIsSubclass(C, NonP) + self.assertNotIsSubclass(D, NonPR) + self.assertIsInstance(NonPR(), PR) + self.assertIsSubclass(NonPR, PR) + + def test_custom_subclasshook(self): + class P(Protocol): + x = 1 + class OKClass: pass + class BadClass: + x = 1 + class C(P): + @classmethod + def __subclasshook__(cls, other): + return other.__name__.startswith("OK") + self.assertIsInstance(OKClass(), C) + self.assertNotIsInstance(BadClass(), C) + self.assertIsSubclass(OKClass, C) + self.assertNotIsSubclass(BadClass, C) + + def test_issubclass_fails_correctly(self): + @runtime + class P(Protocol): + x = 1 + class C: pass + with self.assertRaises(TypeError): + issubclass(C(), P) + + @skipUnless(not OLD_GENERICS, "New style generics required") + def test_defining_generic_protocols(self): + T = TypeVar('T') + S = TypeVar('S') + @runtime + class PR(Protocol[T, S]): + def meth(self): pass + class P(PR[int, T], Protocol[T]): + y = 1 + self.assertIsSubclass(PR[int, T], PR) + self.assertIsSubclass(P[str], PR) + with self.assertRaises(TypeError): + PR[int] + with self.assertRaises(TypeError): + P[int, str] + with self.assertRaises(TypeError): + PR[int, 1] + with self.assertRaises(TypeError): + PR[int, ClassVar] + class C(PR[int, T]): pass + self.assertIsInstance(C[str](), C) + + def test_defining_generic_protocols_old_style(self): + T = TypeVar('T') + S = TypeVar('S') + @runtime + class PR(Protocol, Generic[T, S]): + def meth(self): pass + class P(PR[int, str], Protocol): + y = 1 + if not PEP_560: + self.assertIsSubclass(PR[int, str], PR) + else: with self.assertRaises(TypeError): - PR[int, 1] - if TYPING_3_5_3: - with self.assertRaises(TypeError): - PR[int, ClassVar] - class C(PR[int, T]): pass - self.assertIsInstance(C[str](), C) - - def test_defining_generic_protocols_old_style(self): - T = TypeVar('T') - S = TypeVar('S') - @runtime - class PR(Protocol, Generic[T, S]): - def meth(self): pass - class P(PR[int, str], Protocol): - y = 1 - if not PEP_560: self.assertIsSubclass(PR[int, str], PR) - else: - with self.assertRaises(TypeError): - self.assertIsSubclass(PR[int, str], PR) - self.assertIsSubclass(P, PR) - with self.assertRaises(TypeError): - PR[int] - if not TYPING_3_10_0: - with self.assertRaises(TypeError): - PR[int, 1] - class P1(Protocol, Generic[T]): - def bar(self, x: T) -> str: ... - class P2(Generic[T], Protocol): - def bar(self, x: T) -> str: ... - @runtime - class PSub(P1[str], Protocol): - x = 1 - class Test: - x = 1 - def bar(self, x: str) -> str: - return x - self.assertIsInstance(Test(), PSub) - if TYPING_3_5_3 and not TYPING_3_10_0: - with self.assertRaises(TypeError): - PR[int, ClassVar] - - def test_init_called(self): - T = TypeVar('T') - class P(Protocol[T]): pass - class C(P[T]): - def __init__(self): - self.test = 'OK' - self.assertEqual(C[int]().test, 'OK') - - @skipUnless(not OLD_GENERICS, "New style generics required") - def test_protocols_bad_subscripts(self): - T = TypeVar('T') - S = TypeVar('S') - with self.assertRaises(TypeError): - class P(Protocol[T, T]): pass - with self.assertRaises(TypeError): - class P(Protocol[int]): pass + self.assertIsSubclass(P, PR) + with self.assertRaises(TypeError): + PR[int] + if not TYPING_3_10_0: with self.assertRaises(TypeError): - class P(Protocol[T], Protocol[S]): pass + PR[int, 1] + class P1(Protocol, Generic[T]): + def bar(self, x: T) -> str: ... + class P2(Generic[T], Protocol): + def bar(self, x: T) -> str: ... + @runtime + class PSub(P1[str], Protocol): + x = 1 + class Test: + x = 1 + def bar(self, x: str) -> str: + return x + self.assertIsInstance(Test(), PSub) + if not TYPING_3_10_0: with self.assertRaises(TypeError): - class P(typing.Mapping[T, S], Protocol[T]): pass - - @skipUnless(TYPING_3_5_3, 'New style __repr__ and __eq__ only') - def test_generic_protocols_repr(self): - T = TypeVar('T') - S = TypeVar('S') - class P(Protocol[T, S]): pass - # After PEP 560 unsubscripted generics have a standard repr. - if not PEP_560: - self.assertTrue(repr(P).endswith('P')) - self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) - self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) - - @skipUnless(TYPING_3_5_3, 'New style __repr__ and __eq__ only') - def test_generic_protocols_eq(self): - T = TypeVar('T') - S = TypeVar('S') - class P(Protocol[T, S]): pass - self.assertEqual(P, P) - self.assertEqual(P[int, T], P[int, T]) - self.assertEqual(P[T, T][Tuple[T, S]][int, str], - P[Tuple[int, str], Tuple[int, str]]) - - @skipUnless(not OLD_GENERICS, "New style generics required") - def test_generic_protocols_special_from_generic(self): - T = TypeVar('T') - class P(Protocol[T]): pass - self.assertEqual(P.__parameters__, (T,)) - self.assertIs(P.__args__, None) - self.assertIs(P.__origin__, None) - self.assertEqual(P[int].__parameters__, ()) - self.assertEqual(P[int].__args__, (int,)) - self.assertIs(P[int].__origin__, P) - - def test_generic_protocols_special_from_protocol(self): - @runtime - class PR(Protocol): - x = 1 - class P(Protocol): - def meth(self): - pass - T = TypeVar('T') - class PG(Protocol[T]): - x = 1 - def meth(self): - pass - self.assertTrue(P._is_protocol) - self.assertTrue(PR._is_protocol) - self.assertTrue(PG._is_protocol) - if hasattr(typing, 'Protocol'): + PR[int, ClassVar] + + def test_init_called(self): + T = TypeVar('T') + class P(Protocol[T]): pass + class C(P[T]): + def __init__(self): + self.test = 'OK' + self.assertEqual(C[int]().test, 'OK') + + @skipUnless(not OLD_GENERICS, "New style generics required") + def test_protocols_bad_subscripts(self): + T = TypeVar('T') + S = TypeVar('S') + with self.assertRaises(TypeError): + class P(Protocol[T, T]): pass + with self.assertRaises(TypeError): + class P(Protocol[int]): pass + with self.assertRaises(TypeError): + class P(Protocol[T], Protocol[S]): pass + with self.assertRaises(TypeError): + class P(typing.Mapping[T, S], Protocol[T]): pass + + def test_generic_protocols_repr(self): + T = TypeVar('T') + S = TypeVar('S') + class P(Protocol[T, S]): pass + # After PEP 560 unsubscripted generics have a standard repr. + if not PEP_560: + self.assertTrue(repr(P).endswith('P')) + self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) + self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) + + def test_generic_protocols_eq(self): + T = TypeVar('T') + S = TypeVar('S') + class P(Protocol[T, S]): pass + self.assertEqual(P, P) + self.assertEqual(P[int, T], P[int, T]) + self.assertEqual(P[T, T][Tuple[T, S]][int, str], + P[Tuple[int, str], Tuple[int, str]]) + + @skipUnless(not OLD_GENERICS, "New style generics required") + def test_generic_protocols_special_from_generic(self): + T = TypeVar('T') + class P(Protocol[T]): pass + self.assertEqual(P.__parameters__, (T,)) + self.assertIs(P.__args__, None) + self.assertIs(P.__origin__, None) + self.assertEqual(P[int].__parameters__, ()) + self.assertEqual(P[int].__args__, (int,)) + self.assertIs(P[int].__origin__, P) + + def test_generic_protocols_special_from_protocol(self): + @runtime + class PR(Protocol): + x = 1 + class P(Protocol): + def meth(self): + pass + T = TypeVar('T') + class PG(Protocol[T]): + x = 1 + def meth(self): + pass + self.assertTrue(P._is_protocol) + self.assertTrue(PR._is_protocol) + self.assertTrue(PG._is_protocol) + if hasattr(typing, 'Protocol'): + self.assertFalse(P._is_runtime_protocol) + else: + with self.assertRaises(AttributeError): self.assertFalse(P._is_runtime_protocol) - else: - with self.assertRaises(AttributeError): - self.assertFalse(P._is_runtime_protocol) - self.assertTrue(PR._is_runtime_protocol) - self.assertTrue(PG[int]._is_protocol) - self.assertEqual(typing_extensions._get_protocol_attrs(P), {'meth'}) - self.assertEqual(typing_extensions._get_protocol_attrs(PR), {'x'}) - self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG)), + self.assertTrue(PR._is_runtime_protocol) + self.assertTrue(PG[int]._is_protocol) + self.assertEqual(typing_extensions._get_protocol_attrs(P), {'meth'}) + self.assertEqual(typing_extensions._get_protocol_attrs(PR), {'x'}) + self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG)), + frozenset({'x', 'meth'})) + if not PEP_560: + self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG[int])), frozenset({'x', 'meth'})) - if not PEP_560: - self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG[int])), - frozenset({'x', 'meth'})) - - def test_no_runtime_deco_on_nominal(self): - with self.assertRaises(TypeError): - @runtime - class C: pass - class Proto(Protocol): - x = 1 - with self.assertRaises(TypeError): - @runtime - class Concrete(Proto): - pass - def test_none_treated_correctly(self): + def test_no_runtime_deco_on_nominal(self): + with self.assertRaises(TypeError): @runtime - class P(Protocol): - x = None # type: int - class B(object): pass - self.assertNotIsInstance(B(), P) - class C: - x = 1 - class D: - x = None - self.assertIsInstance(C(), P) - self.assertIsInstance(D(), P) - class CI: - def __init__(self): - self.x = 1 - class DI: - def __init__(self): - self.x = None - self.assertIsInstance(C(), P) - self.assertIsInstance(D(), P) - - def test_protocols_in_unions(self): - class P(Protocol): - x = None # type: int - Alias = typing.Union[typing.Iterable, P] - Alias2 = typing.Union[P, typing.Iterable] - self.assertEqual(Alias, Alias2) - - def test_protocols_pickleable(self): - global P, CP # pickle wants to reference the class by name - T = TypeVar('T') - + class C: pass + class Proto(Protocol): + x = 1 + with self.assertRaises(TypeError): @runtime - class P(Protocol[T]): - x = 1 - class CP(P[int]): + class Concrete(Proto): pass - c = CP() - c.foo = 42 - c.bar = 'abc' - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(c, proto) - x = pickle.loads(z) - self.assertEqual(x.foo, 42) - self.assertEqual(x.bar, 'abc') - self.assertEqual(x.x, 1) - self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) - s = pickle.dumps(P) - D = pickle.loads(s) - class E: - x = 1 - self.assertIsInstance(E(), D) - - def test_collections_protocols_allowed(self): - @runtime_checkable - class Custom(collections.abc.Iterable, Protocol): - def close(self): pass - - class A: ... - class B: - def __iter__(self): - return [] - def close(self): - return 0 - - self.assertIsSubclass(B, Custom) - self.assertNotIsSubclass(A, Custom) - - def test_no_init_same_for_different_protocol_implementations(self): - class CustomProtocolWithoutInitA(Protocol): - pass + def test_none_treated_correctly(self): + @runtime + class P(Protocol): + x = None # type: int + class B(object): pass + self.assertNotIsInstance(B(), P) + class C: + x = 1 + class D: + x = None + self.assertIsInstance(C(), P) + self.assertIsInstance(D(), P) + class CI: + def __init__(self): + self.x = 1 + class DI: + def __init__(self): + self.x = None + self.assertIsInstance(C(), P) + self.assertIsInstance(D(), P) + + def test_protocols_in_unions(self): + class P(Protocol): + x = None # type: int + Alias = typing.Union[typing.Iterable, P] + Alias2 = typing.Union[P, typing.Iterable] + self.assertEqual(Alias, Alias2) + + def test_protocols_pickleable(self): + global P, CP # pickle wants to reference the class by name + T = TypeVar('T') - class CustomProtocolWithoutInitB(Protocol): - pass + @runtime + class P(Protocol[T]): + x = 1 + class CP(P[int]): + pass - self.assertEqual(CustomProtocolWithoutInitA.__init__, CustomProtocolWithoutInitB.__init__) + c = CP() + c.foo = 42 + c.bar = 'abc' + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(c, proto) + x = pickle.loads(z) + self.assertEqual(x.foo, 42) + self.assertEqual(x.bar, 'abc') + self.assertEqual(x.x, 1) + self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) + s = pickle.dumps(P) + D = pickle.loads(s) + class E: + x = 1 + self.assertIsInstance(E(), D) + + def test_collections_protocols_allowed(self): + @runtime_checkable + class Custom(collections.abc.Iterable, Protocol): + def close(self): pass + + class A: ... + class B: + def __iter__(self): + return [] + def close(self): + return 0 + + self.assertIsSubclass(B, Custom) + self.assertNotIsSubclass(A, Custom) + + def test_no_init_same_for_different_protocol_implementations(self): + class CustomProtocolWithoutInitA(Protocol): + pass + + class CustomProtocolWithoutInitB(Protocol): + pass + + self.assertEqual(CustomProtocolWithoutInitA.__init__, CustomProtocolWithoutInitB.__init__) class TypedDictTests(BaseTestCase): @@ -1543,9 +1441,7 @@ def test_basics_iterable_syntax(self): Emp = TypedDict('Emp', {'name': str, 'id': int}) self.assertIsSubclass(Emp, dict) self.assertIsSubclass(Emp, typing.MutableMapping) - if sys.version_info[0] >= 3: - import collections.abc - self.assertNotIsSubclass(Emp, collections.abc.Sequence) + self.assertNotIsSubclass(Emp, collections.abc.Sequence) jim = Emp(name='Jim', id=1) self.assertIs(type(jim), dict) self.assertEqual(jim['name'], 'Jim') @@ -1560,9 +1456,7 @@ def test_basics_keywords_syntax(self): Emp = TypedDict('Emp', name=str, id=int) self.assertIsSubclass(Emp, dict) self.assertIsSubclass(Emp, typing.MutableMapping) - if sys.version_info[0] >= 3: - import collections.abc - self.assertNotIsSubclass(Emp, collections.abc.Sequence) + self.assertNotIsSubclass(Emp, collections.abc.Sequence) jim = Emp(name='Jim', id=1) self.assertIs(type(jim), dict) self.assertEqual(jim['name'], 'Jim') @@ -1627,7 +1521,6 @@ def test_typeddict_errors(self): with self.assertRaises(TypeError): TypedDict('Hi', [('x', int)], y=int) - @skipUnless(PY36, 'Python 3.6 required') def test_py36_class_syntax_usage(self): self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D') self.assertEqual(LabelPoint2D.__module__, __name__) @@ -1668,19 +1561,16 @@ def test_total(self): self.assertEqual(D.__required_keys__, frozenset()) self.assertEqual(D.__optional_keys__, {'x'}) - if PY36: - self.assertEqual(Options(), {}) - self.assertEqual(Options(log_level=2), {'log_level': 2}) - self.assertEqual(Options.__total__, False) - self.assertEqual(Options.__required_keys__, frozenset()) - self.assertEqual(Options.__optional_keys__, {'log_level', 'log_path'}) + self.assertEqual(Options(), {}) + self.assertEqual(Options(log_level=2), {'log_level': 2}) + self.assertEqual(Options.__total__, False) + self.assertEqual(Options.__required_keys__, frozenset()) + self.assertEqual(Options.__optional_keys__, {'log_level', 'log_path'}) - @skipUnless(PY36, 'Python 3.6 required') def test_optional_keys(self): assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y']) assert Point2Dor3D.__optional_keys__ == frozenset(['z']) - @skipUnless(PY36, 'Python 3.6 required') def test_keys_inheritance(self): assert BaseAnimal.__required_keys__ == frozenset(['name']) assert BaseAnimal.__optional_keys__ == frozenset([]) @@ -1704,7 +1594,6 @@ def test_keys_inheritance(self): } -@skipUnless(TYPING_3_5_3, "Python >= 3.5.3 required") class AnnotatedTests(BaseTestCase): def test_repr(self): @@ -1947,7 +1836,6 @@ def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]": class TypeAliasTests(BaseTestCase): - @skipUnless(PY36, 'Python 3.6 required') def test_canonical_usage_with_variable_annotation(self): ns = {} exec('Alias: TypeAlias = Employee', globals(), ns) @@ -1967,19 +1855,17 @@ def test_no_issubclass(self): with self.assertRaises(TypeError): issubclass(Employee, TypeAlias) - if SUBCLASS_CHECK_FORBIDDEN: - with self.assertRaises(TypeError): - issubclass(TypeAlias, Employee) + with self.assertRaises(TypeError): + issubclass(TypeAlias, Employee) def test_cannot_subclass(self): with self.assertRaises(TypeError): class C(TypeAlias): pass - if SUBCLASS_CHECK_FORBIDDEN: - with self.assertRaises(TypeError): - class C(type(TypeAlias)): - pass + with self.assertRaises(TypeError): + class C(type(TypeAlias)): + pass def test_repr(self): if hasattr(typing, 'TypeAlias'): @@ -2017,16 +1903,11 @@ def test_valid_uses(self): P = ParamSpec('P') T = TypeVar('T') C1 = typing.Callable[P, int] - # Callable in Python 3.5.2 might be bugged when collecting __args__. - # https://github.com/python/cpython/blob/91185fe0284a04162e0b3425b53be49bdbfad67d/Lib/typing.py#L1026 - PY_3_5_2 = sys.version_info[:3] == (3, 5, 2) - if not PY_3_5_2: - self.assertEqual(C1.__args__, (P, int)) - self.assertEqual(C1.__parameters__, (P,)) + self.assertEqual(C1.__args__, (P, int)) + self.assertEqual(C1.__parameters__, (P,)) C2 = typing.Callable[P, T] - if not PY_3_5_2: - self.assertEqual(C2.__args__, (P, T)) - self.assertEqual(C2.__parameters__, (P, T)) + self.assertEqual(C2.__args__, (P, T)) + self.assertEqual(C2.__parameters__, (P, T)) # Test collections.abc.Callable too. @@ -2089,7 +1970,7 @@ def test_pickle(self): P_co = ParamSpec('P_co', covariant=True) P_contra = ParamSpec('P_contra', contravariant=True) for proto in range(pickle.HIGHEST_PROTOCOL): - with self.subTest('Pickle protocol {proto}'.format(proto=proto)): + with self.subTest(f'Pickle protocol {proto}'): for paramspec in (P, P_co, P_contra): z = pickle.loads(pickle.dumps(paramspec, proto)) self.assertEqual(z.__name__, paramspec.__name__) @@ -2163,15 +2044,14 @@ def test_repr(self): mod_name = 'typing' else: mod_name = 'typing_extensions' - self.assertEqual(repr(TypeGuard), '{}.TypeGuard'.format(mod_name)) + self.assertEqual(repr(TypeGuard), f'{mod_name}.TypeGuard') cv = TypeGuard[int] - self.assertEqual(repr(cv), '{}.TypeGuard[int]'.format(mod_name)) + self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[int]') cv = TypeGuard[Employee] - self.assertEqual(repr(cv), '{}.TypeGuard[{}.Employee]'.format(mod_name, __name__)) + self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[{__name__}.Employee]') cv = TypeGuard[Tuple[int]] - self.assertEqual(repr(cv), '{}.TypeGuard[typing.Tuple[int]]'.format(mod_name)) + self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[typing.Tuple[int]]') - @skipUnless(SUBCLASS_CHECK_FORBIDDEN, "Behavior added in typing 3.5.3") def test_cannot_subclass(self): with self.assertRaises(TypeError): class C(type(TypeGuard)): @@ -2214,24 +2094,20 @@ def test_typing_extensions_includes_standard(self): self.assertIn('ParamSpec', a) self.assertIn("Concatenate", a) - if TYPING_3_5_3: - self.assertIn('Annotated', a) + self.assertIn('Annotated', a) if PEP_560: self.assertIn('get_type_hints', a) - if ASYNCIO: - self.assertIn('Awaitable', a) - self.assertIn('AsyncIterator', a) - self.assertIn('AsyncIterable', a) - self.assertIn('Coroutine', a) - self.assertIn('AsyncContextManager', a) + self.assertIn('Awaitable', a) + self.assertIn('AsyncIterator', a) + self.assertIn('AsyncIterable', a) + self.assertIn('Coroutine', a) + self.assertIn('AsyncContextManager', a) - if PY36: - self.assertIn('AsyncGenerator', a) + self.assertIn('AsyncGenerator', a) - if TYPING_3_5_3: - self.assertIn('Protocol', a) - self.assertIn('runtime', a) + self.assertIn('Protocol', a) + self.assertIn('runtime', a) # Check that all objects in `__all__` are present in the module for name in a: @@ -2258,8 +2134,7 @@ def test_typing_extensions_compiles_with_opt(self): file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'typing_extensions.py') try: - subprocess.check_output('{} -OO {}'.format(sys.executable, - file_path), + subprocess.check_output(f'{sys.executable} -OO {file_path}', stderr=subprocess.STDOUT, shell=True) except subprocess.CalledProcessError: diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 184da0ad1..1e96d1460 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -1,50 +1,20 @@ import abc import collections -import contextlib +import collections.abc +import operator import sys import typing -import collections.abc as collections_abc -import operator - -# These are used by Protocol implementation -# We use internal typing helpers here, but this significantly reduces -# code duplication. (Also this is only until Protocol is in typing.) -from typing import Generic, Callable, TypeVar, Tuple # After PEP 560, internal typing API was substantially reworked. # This is especially important for Protocol class which uses internal APIs -# quite extensivelly. +# quite extensively. PEP_560 = sys.version_info[:3] >= (3, 7, 0) if PEP_560: - GenericMeta = TypingMeta = type - from typing import _GenericAlias + GenericMeta = type else: - from typing import GenericMeta, TypingMeta -OLD_GENERICS = False -try: - from typing import _type_vars, _next_in_mro, _type_check -except ImportError: - OLD_GENERICS = True -try: - from typing import _subs_tree # noqa - SUBS_TREE = True -except ImportError: - SUBS_TREE = False -try: - from typing import _tp_cache -except ImportError: - def _tp_cache(x): - return x -try: - from typing import _TypingEllipsis, _TypingEmpty -except ImportError: - class _TypingEllipsis: - pass - - class _TypingEmpty: - pass - + # 3.6 + from typing import GenericMeta, _type_vars # noqa # The two functions below are copies of typing internal helpers. # They are needed by _ProtocolMeta @@ -60,56 +30,12 @@ def _no_slots_copy(dct): def _check_generic(cls, parameters): if not cls.__parameters__: - raise TypeError("%s is not a generic class" % repr(cls)) + raise TypeError(f"{cls} is not a generic class") alen = len(parameters) elen = len(cls.__parameters__) if alen != elen: - raise TypeError("Too %s parameters for %s; actual %s, expected %s" % - ("many" if alen > elen else "few", repr(cls), alen, elen)) - - -if hasattr(typing, '_generic_new'): - _generic_new = typing._generic_new -else: - # Note: The '_generic_new(...)' function is used as a part of the - # process of creating a generic type and was added to the typing module - # as of Python 3.5.3. - # - # We've defined '_generic_new(...)' below to exactly match the behavior - # implemented in older versions of 'typing' bundled with Python 3.5.0 to - # 3.5.2. This helps eliminate redundancy when defining collection types - # like 'Deque' later. - # - # See https://github.com/python/typing/pull/308 for more details -- in - # particular, compare and contrast the definition of types like - # 'typing.List' before and after the merge. - - def _generic_new(base_cls, cls, *args, **kwargs): - return base_cls.__new__(cls, *args, **kwargs) - -# See https://github.com/python/typing/pull/439 -if hasattr(typing, '_geqv'): - from typing import _geqv - _geqv_defined = True -else: - _geqv = None - _geqv_defined = False - -if sys.version_info[:2] >= (3, 6): - import _collections_abc - _check_methods_in_mro = _collections_abc._check_methods -else: - def _check_methods_in_mro(C, *methods): - mro = C.__mro__ - for method in methods: - for B in mro: - if method in B.__dict__: - if B.__dict__[method] is None: - return NotImplemented - break - else: - return NotImplemented - return True + raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments for {cls};" + f" actual {alen}, expected {elen}") # Please keep __all__ alphabetized within each category. @@ -122,15 +48,13 @@ def _check_methods_in_mro(C, *methods): 'Type', # ABCs (from collections.abc). - # The following are added depending on presence - # of their non-generic counterparts in stdlib: - # 'Awaitable', - # 'AsyncIterator', - # 'AsyncIterable', - # 'Coroutine', - # 'AsyncGenerator', - # 'AsyncContextManager', - # 'ChainMap', + 'Awaitable', + 'AsyncIterator', + 'AsyncIterable', + 'Coroutine', + 'AsyncGenerator', + 'AsyncContextManager', + 'ChainMap', # Concrete collection types. 'ContextManager', @@ -144,38 +68,29 @@ def _check_methods_in_mro(C, *methods): 'SupportsIndex', # One-off things. + 'Annotated', 'final', 'IntVar', 'Literal', 'NewType', 'overload', + 'Protocol', + 'runtime', + 'runtime_checkable', 'Text', 'TypeAlias', 'TypeGuard', 'TYPE_CHECKING', ] -# Annotated relies on substitution trees of pep 560. It will not work for -# versions of typing older than 3.5.3 -HAVE_ANNOTATED = PEP_560 or SUBS_TREE - if PEP_560: __all__.extend(["get_args", "get_origin", "get_type_hints"]) -if HAVE_ANNOTATED: - __all__.append("Annotated") - -# Protocols are hard to backport to the original version of typing 3.5.0 -HAVE_PROTOCOLS = sys.version_info[:3] != (3, 5, 0) - -if HAVE_PROTOCOLS: - __all__.extend(['Protocol', 'runtime', 'runtime_checkable']) - - -# TODO +# 3.6.2+ if hasattr(typing, 'NoReturn'): NoReturn = typing.NoReturn -elif hasattr(typing, '_FinalTypingBase'): +# 3.6.0-3.6.1 +else: class _NoReturn(typing._FinalTypingBase, _root=True): """Special type indicating functions that never return. Example:: @@ -197,32 +112,6 @@ def __subclasscheck__(self, cls): raise TypeError("NoReturn cannot be used with issubclass().") NoReturn = _NoReturn(_root=True) -else: - class _NoReturnMeta(typing.TypingMeta): - """Metaclass for NoReturn""" - def __new__(cls, name, bases, namespace, _root=False): - return super().__new__(cls, name, bases, namespace, _root=_root) - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - class NoReturn(typing.Final, metaclass=_NoReturnMeta, _root=True): - """Special type indicating functions that never return. - Example:: - - from typing import NoReturn - - def stop() -> NoReturn: - raise Exception('no way') - - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - __slots__ = () - # Some unconstrained type variables. These are used by the container types. # (These are not for export.) @@ -230,142 +119,15 @@ def stop() -> NoReturn: KT = typing.TypeVar('KT') # Key type. VT = typing.TypeVar('VT') # Value type. T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers. -V_co = typing.TypeVar('V_co', covariant=True) # Any type covariant containers. -VT_co = typing.TypeVar('VT_co', covariant=True) # Value type covariant containers. T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant. - -if hasattr(typing, 'ClassVar'): - ClassVar = typing.ClassVar -elif hasattr(typing, '_FinalTypingBase'): - class _ClassVar(typing._FinalTypingBase, _root=True): - """Special type construct to mark class variables. - - An annotation wrapped in ClassVar indicates that a given - attribute is intended to be used as a class variable and - should not be set on instances of that class. Usage:: - - class Starship: - stats: ClassVar[Dict[str, int]] = {} # class variable - damage: int = 10 # instance variable - - ClassVar accepts only types and cannot be further subscribed. - - Note that ClassVar is not a class itself, and should not - be used with isinstance() or issubclass(). - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _ClassVar): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - ClassVar = _ClassVar(_root=True) -else: - class _ClassVarMeta(typing.TypingMeta): - """Metaclass for ClassVar""" - - def __new__(cls, name, bases, namespace, tp=None, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) - if tp is not None: - self.__type__ = tp - return self - - def __instancecheck__(self, obj): - raise TypeError("ClassVar cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("ClassVar cannot be used with issubclass().") - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is not None: - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - param = typing._type_check( - item, - '{} accepts only single type.'.format(cls.__name__[1:])) - return cls(self.__name__, self.__bases__, - dict(self.__dict__), tp=param, _root=True) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(self.__name__, self.__bases__, - dict(self.__dict__), tp=self.__type__, - _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, ClassVar): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - class ClassVar(typing.Final, metaclass=_ClassVarMeta, _root=True): - """Special type construct to mark class variables. - - An annotation wrapped in ClassVar indicates that a given - attribute is intended to be used as a class variable and - should not be set on instances of that class. Usage:: - - class Starship: - stats: ClassVar[Dict[str, int]] = {} # class variable - damage: int = 10 # instance variable - - ClassVar accepts only types and cannot be further subscribed. - - Note that ClassVar is not a class itself, and should not - be used with isinstance() or issubclass(). - """ - - __type__ = None +ClassVar = typing.ClassVar # On older versions of typing there is an internal class named "Final". +# 3.8+ if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): Final = typing.Final +# 3.7 elif sys.version_info[:2] >= (3, 7): class _FinalForm(typing._SpecialForm, _root=True): @@ -374,8 +136,8 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - '{} accepts only single type'.format(self._name)) - return _GenericAlias(self, (item,)) + f'{self._name} accepts only single type') + return typing._GenericAlias(self, (item,)) Final = _FinalForm('Final', doc="""A special typing construct to indicate that a name @@ -391,7 +153,8 @@ class FastConnector(Connection): TIMEOUT = 1 # Error reported by type checker There is no runtime checking of these properties.""") -elif hasattr(typing, '_FinalTypingBase'): +# 3.6 +else: class _Final(typing._FinalTypingBase, _root=True): """A special typing construct to indicate that a name cannot be re-assigned or overridden in a subclass. @@ -417,10 +180,9 @@ def __getitem__(self, item): cls = type(self) if self.__type__ is None: return cls(typing._type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), + f'{cls.__name__[1:]} accepts only single type.'), _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) + raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') def _eval_type(self, globalns, localns): new_tp = typing._eval_type(self.__type__, globalns, localns) @@ -431,7 +193,7 @@ def _eval_type(self, globalns, localns): def __repr__(self): r = super().__repr__() if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) + r += f'[{typing._type_repr(self.__type__)}]' return r def __hash__(self): @@ -445,79 +207,12 @@ def __eq__(self, other): return self is other Final = _Final(_root=True) -else: - class _FinalMeta(typing.TypingMeta): - """Metaclass for Final""" - - def __new__(cls, name, bases, namespace, tp=None, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) - if tp is not None: - self.__type__ = tp - return self - - def __instancecheck__(self, obj): - raise TypeError("Final cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Final cannot be used with issubclass().") - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is not None: - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - param = typing._type_check( - item, - '{} accepts only single type.'.format(cls.__name__[1:])) - return cls(self.__name__, self.__bases__, - dict(self.__dict__), tp=param, _root=True) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(self.__name__, self.__bases__, - dict(self.__dict__), tp=self.__type__, - _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, Final): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - class Final(typing.Final, metaclass=_FinalMeta, _root=True): - """A special typing construct to indicate that a name - cannot be re-assigned or overridden in a subclass. - For example: - - MAX_SIZE: Final = 9000 - MAX_SIZE += 1 # Error reported by type checker - - class Connection: - TIMEOUT: Final[int] = 10 - class FastConnector(Connection): - TIMEOUT = 1 # Error reported by type checker - - There is no runtime checking of these properties. - """ - - __type__ = None +# 3.8+ if hasattr(typing, 'final'): final = typing.final +# 3.6-3.7 else: def final(f): """This decorator can be used to indicate to type checkers that @@ -543,11 +238,13 @@ class Other(Leaf): # Error reported by type checker def IntVar(name): - return TypeVar(name) + return typing.TypeVar(name) +# 3.8+: if hasattr(typing, 'Literal'): Literal = typing.Literal +# 3.7: elif sys.version_info[:2] >= (3, 7): class _LiteralForm(typing._SpecialForm, _root=True): @@ -555,7 +252,7 @@ def __repr__(self): return 'typing_extensions.' + self._name def __getitem__(self, parameters): - return _GenericAlias(self, parameters) + return typing._GenericAlias(self, parameters) Literal = _LiteralForm('Literal', doc="""A type that can be used to indicate to type checkers @@ -570,7 +267,8 @@ def __getitem__(self, parameters): Literal[...] cannot be subclassed. There is no runtime checking verifying that the parameter is actually a value instead of a type.""") -elif hasattr(typing, '_FinalTypingBase'): +# 3.6: +else: class _Literal(typing._FinalTypingBase, _root=True): """A type that can be used to indicate to type checkers that the corresponding value has a value literally equivalent to the @@ -596,8 +294,7 @@ def __getitem__(self, values): if not isinstance(values, tuple): values = (values,) return cls(values, _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) + raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') def _eval_type(self, globalns, localns): return self @@ -605,7 +302,7 @@ def _eval_type(self, globalns, localns): def __repr__(self): r = super().__repr__() if self.__values__ is not None: - r += '[{}]'.format(', '.join(map(typing._type_repr, self.__values__))) + r += f'[{", ".join(map(typing._type_repr, self.__values__))}]' return r def __hash__(self): @@ -619,161 +316,18 @@ def __eq__(self, other): return self is other Literal = _Literal(_root=True) -else: - class _LiteralMeta(typing.TypingMeta): - """Metaclass for Literal""" - - def __new__(cls, name, bases, namespace, values=None, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) - if values is not None: - self.__values__ = values - return self - - def __instancecheck__(self, obj): - raise TypeError("Literal cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Literal cannot be used with issubclass().") - - def __getitem__(self, item): - cls = type(self) - if self.__values__ is not None: - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - if not isinstance(item, tuple): - item = (item,) - return cls(self.__name__, self.__bases__, - dict(self.__dict__), values=item, _root=True) - - def _eval_type(self, globalns, localns): - return self - - def __repr__(self): - r = super().__repr__() - if self.__values__ is not None: - r += '[{}]'.format(', '.join(map(typing._type_repr, self.__values__))) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__values__)) - - def __eq__(self, other): - if not isinstance(other, Literal): - return NotImplemented - if self.__values__ is not None: - return self.__values__ == other.__values__ - return self is other - - class Literal(typing.Final, metaclass=_LiteralMeta, _root=True): - """A type that can be used to indicate to type checkers that the - corresponding value has a value literally equivalent to the - provided parameter. For example: - - var: Literal[4] = 4 - - The type checker understands that 'var' is literally equal to the - value 4 and no other value. - - Literal[...] cannot be subclassed. There is no runtime checking - verifying that the parameter is actually a value instead of a type. - """ - - __values__ = None -def _overload_dummy(*args, **kwds): - """Helper for @overload to raise when called.""" - raise NotImplementedError( - "You should not call an overloaded function. " - "A series of @overload-decorated functions " - "outside a stub module should always be followed " - "by an implementation that is not @overload-ed.") - - -def overload(func): - """Decorator for overloaded functions/methods. - - In a stub file, place two or more stub definitions for the same - function in a row, each decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - - In a non-stub file (i.e. a regular .py file), do the same but - follow it with an implementation. The implementation should *not* - be decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - def utf8(value): - # implementation goes here - """ - return _overload_dummy +_overload_dummy = typing._overload_dummy # noqa +overload = typing.overload # This is not a real generic class. Don't use outside annotations. -if hasattr(typing, 'Type'): - Type = typing.Type -else: - # Internal type variable used for Type[]. - CT_co = typing.TypeVar('CT_co', covariant=True, bound=type) - - class Type(typing.Generic[CT_co], extra=type): - """A special construct usable to annotate class objects. - - For example, suppose we have the following classes:: - - class User: ... # Abstract base for User classes - class BasicUser(User): ... - class ProUser(User): ... - class TeamUser(User): ... - - And a function that takes a class argument that's a subclass of - User and returns an instance of the corresponding class:: - - U = TypeVar('U', bound=User) - def new_user(user_class: Type[U]) -> U: - user = user_class() - # (Here we could write the user object to a database) - return user - joe = new_user(BasicUser) - - At this point the type checker knows that joe has type BasicUser. - """ - - __slots__ = () - +Type = typing.Type # Various ABCs mimicking those in collections.abc. # A few are simply re-exported for completeness. -def _define_guard(type_name): - """ - Returns True if the given type isn't defined in typing but - is defined in collections_abc. - - Adds the type to __all__ if the collection is found in either - typing or collection_abc. - """ - if hasattr(typing, type_name): - __all__.append(type_name) - globals()[type_name] = getattr(typing, type_name) - return False - elif hasattr(collections_abc, type_name): - __all__.append(type_name) - return True - else: - return False - class _ExtensionsGenericMeta(GenericMeta): def __subclasscheck__(self, subclass): @@ -782,12 +336,11 @@ def __subclasscheck__(self, subclass): between collections, typing, and typing_extensions on older versions of Python, see https://github.com/python/typing/issues/501. """ - if sys.version_info[:3] >= (3, 5, 3) or sys.version_info[:3] < (3, 5, 0): - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False + if self.__origin__ is not None: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") + return False if not self.__extra__: return super().__subclasscheck__(subclass) res = self.__extra__.__subclasshook__(subclass) @@ -803,45 +356,15 @@ def __subclasscheck__(self, subclass): return False -if _define_guard('Awaitable'): - class Awaitable(typing.Generic[T_co], metaclass=_ExtensionsGenericMeta, - extra=collections_abc.Awaitable): - __slots__ = () - - -if _define_guard('Coroutine'): - class Coroutine(Awaitable[V_co], typing.Generic[T_co, T_contra, V_co], - metaclass=_ExtensionsGenericMeta, - extra=collections_abc.Coroutine): - __slots__ = () - - -if _define_guard('AsyncIterable'): - class AsyncIterable(typing.Generic[T_co], - metaclass=_ExtensionsGenericMeta, - extra=collections_abc.AsyncIterable): - __slots__ = () - - -if _define_guard('AsyncIterator'): - class AsyncIterator(AsyncIterable[T_co], - metaclass=_ExtensionsGenericMeta, - extra=collections_abc.AsyncIterator): - __slots__ = () - +Awaitable = typing.Awaitable +Coroutine = typing.Coroutine +AsyncIterable = typing.AsyncIterable +AsyncIterator = typing.AsyncIterator +# 3.6.1+ if hasattr(typing, 'Deque'): Deque = typing.Deque -elif _geqv_defined: - class Deque(collections.deque, typing.MutableSequence[T], - metaclass=_ExtensionsGenericMeta, - extra=collections.deque): - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, Deque): - return collections.deque(*args, **kwds) - return _generic_new(collections.deque, cls, *args, **kwds) +# 3.6.0 else: class Deque(collections.deque, typing.MutableSequence[T], metaclass=_ExtensionsGenericMeta, @@ -851,114 +374,41 @@ class Deque(collections.deque, typing.MutableSequence[T], def __new__(cls, *args, **kwds): if cls._gorg is Deque: return collections.deque(*args, **kwds) - return _generic_new(collections.deque, cls, *args, **kwds) + return typing._generic_new(collections.deque, cls, *args, **kwds) +ContextManager = typing.ContextManager +# 3.6.2+ +if hasattr(typing, 'AsyncContextManager'): + AsyncContextManager = typing.AsyncContextManager +# 3.6.0-3.6.1 +else: + from _collections_abc import _check_methods as _check_methods_in_mro # noqa -if hasattr(typing, 'ContextManager'): - ContextManager = typing.ContextManager -elif hasattr(contextlib, 'AbstractContextManager'): - class ContextManager(typing.Generic[T_co], - metaclass=_ExtensionsGenericMeta, - extra=contextlib.AbstractContextManager): - __slots__ = () -else: - class ContextManager(typing.Generic[T_co]): + class AsyncContextManager(typing.Generic[T_co]): __slots__ = () - def __enter__(self): + async def __aenter__(self): return self @abc.abstractmethod - def __exit__(self, exc_type, exc_value, traceback): + async def __aexit__(self, exc_type, exc_value, traceback): return None @classmethod def __subclasshook__(cls, C): - if cls is ContextManager: - # In Python 3.6+, it is possible to set a method to None to - # explicitly indicate that the class does not implement an ABC - # (https://bugs.python.org/issue25958), but we do not support - # that pattern here because this fallback class is only used - # in Python 3.5 and earlier. - if (any("__enter__" in B.__dict__ for B in C.__mro__) and - any("__exit__" in B.__dict__ for B in C.__mro__)): - return True + if cls is AsyncContextManager: + return _check_methods_in_mro(C, "__aenter__", "__aexit__") return NotImplemented +DefaultDict = typing.DefaultDict -if hasattr(typing, 'AsyncContextManager'): - AsyncContextManager = typing.AsyncContextManager - __all__.append('AsyncContextManager') -elif hasattr(contextlib, 'AbstractAsyncContextManager'): - class AsyncContextManager(typing.Generic[T_co], - metaclass=_ExtensionsGenericMeta, - extra=contextlib.AbstractAsyncContextManager): - __slots__ = () - - __all__.append('AsyncContextManager') -elif sys.version_info[:2] >= (3, 5): - exec(""" -class AsyncContextManager(typing.Generic[T_co]): - __slots__ = () - - async def __aenter__(self): - return self - - @abc.abstractmethod - async def __aexit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncContextManager: - return _check_methods_in_mro(C, "__aenter__", "__aexit__") - return NotImplemented - -__all__.append('AsyncContextManager') -""") - - -if hasattr(typing, 'DefaultDict'): - DefaultDict = typing.DefaultDict -elif _geqv_defined: - class DefaultDict(collections.defaultdict, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.defaultdict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, DefaultDict): - return collections.defaultdict(*args, **kwds) - return _generic_new(collections.defaultdict, cls, *args, **kwds) -else: - class DefaultDict(collections.defaultdict, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.defaultdict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is DefaultDict: - return collections.defaultdict(*args, **kwds) - return _generic_new(collections.defaultdict, cls, *args, **kwds) - - +# 3.7.2+ if hasattr(typing, 'OrderedDict'): OrderedDict = typing.OrderedDict +# 3.7.0-3.7.2 elif (3, 7, 0) <= sys.version_info[:3] < (3, 7, 2): OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) -elif _geqv_defined: - class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.OrderedDict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, OrderedDict): - return collections.OrderedDict(*args, **kwds) - return _generic_new(collections.OrderedDict, cls, *args, **kwds) +# 3.6 else: class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], metaclass=_ExtensionsGenericMeta, @@ -969,44 +419,12 @@ class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], def __new__(cls, *args, **kwds): if cls._gorg is OrderedDict: return collections.OrderedDict(*args, **kwds) - return _generic_new(collections.OrderedDict, cls, *args, **kwds) - + return typing._generic_new(collections.OrderedDict, cls, *args, **kwds) +# 3.6.2+ if hasattr(typing, 'Counter'): Counter = typing.Counter -elif (3, 5, 0) <= sys.version_info[:3] <= (3, 5, 1): - assert _geqv_defined - _TInt = typing.TypeVar('_TInt') - - class _CounterMeta(typing.GenericMeta): - """Metaclass for Counter""" - def __getitem__(self, item): - return super().__getitem__((item, int)) - - class Counter(collections.Counter, - typing.Dict[T, int], - metaclass=_CounterMeta, - extra=collections.Counter): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, Counter): - return collections.Counter(*args, **kwds) - return _generic_new(collections.Counter, cls, *args, **kwds) - -elif _geqv_defined: - class Counter(collections.Counter, - typing.Dict[T, int], - metaclass=_ExtensionsGenericMeta, extra=collections.Counter): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, Counter): - return collections.Counter(*args, **kwds) - return _generic_new(collections.Counter, cls, *args, **kwds) - +# 3.6.0-3.6.1 else: class Counter(collections.Counter, typing.Dict[T, int], @@ -1017,88 +435,36 @@ class Counter(collections.Counter, def __new__(cls, *args, **kwds): if cls._gorg is Counter: return collections.Counter(*args, **kwds) - return _generic_new(collections.Counter, cls, *args, **kwds) - + return typing._generic_new(collections.Counter, cls, *args, **kwds) +# 3.6.1+ if hasattr(typing, 'ChainMap'): ChainMap = typing.ChainMap - __all__.append('ChainMap') elif hasattr(collections, 'ChainMap'): - # ChainMap only exists in 3.3+ - if _geqv_defined: - class ChainMap(collections.ChainMap, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.ChainMap): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, ChainMap): - return collections.ChainMap(*args, **kwds) - return _generic_new(collections.ChainMap, cls, *args, **kwds) - else: - class ChainMap(collections.ChainMap, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.ChainMap): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is ChainMap: - return collections.ChainMap(*args, **kwds) - return _generic_new(collections.ChainMap, cls, *args, **kwds) - - __all__.append('ChainMap') + class ChainMap(collections.ChainMap, typing.MutableMapping[KT, VT], + metaclass=_ExtensionsGenericMeta, + extra=collections.ChainMap): + __slots__ = () -if _define_guard('AsyncGenerator'): + def __new__(cls, *args, **kwds): + if cls._gorg is ChainMap: + return collections.ChainMap(*args, **kwds) + return typing._generic_new(collections.ChainMap, cls, *args, **kwds) + +# 3.6.1+ +if hasattr(typing, 'AsyncGenerator'): + AsyncGenerator = typing.AsyncGenerator +# 3.6.0 +else: class AsyncGenerator(AsyncIterator[T_co], typing.Generic[T_co, T_contra], metaclass=_ExtensionsGenericMeta, - extra=collections_abc.AsyncGenerator): + extra=collections.abc.AsyncGenerator): __slots__ = () - -if hasattr(typing, 'NewType'): - NewType = typing.NewType -else: - def NewType(name, tp): - """NewType creates simple unique types with almost zero - runtime overhead. NewType(name, tp) is considered a subtype of tp - by static type checkers. At runtime, NewType(name, tp) returns - a dummy function that simply returns its argument. Usage:: - - UserId = NewType('UserId', int) - - def name_by_id(user_id: UserId) -> str: - ... - - UserId('user') # Fails type check - - name_by_id(42) # Fails type check - name_by_id(UserId(42)) # OK - - num = UserId(5) + 1 # type: int - """ - - def new_type(x): - return x - - new_type.__name__ = name - new_type.__supertype__ = tp - return new_type - - -if hasattr(typing, 'Text'): - Text = typing.Text -else: - Text = str - - -if hasattr(typing, 'TYPE_CHECKING'): - TYPE_CHECKING = typing.TYPE_CHECKING -else: - # Constant that's True when type checking, but False here. - TYPE_CHECKING = False +NewType = typing.NewType +Text = typing.Text +TYPE_CHECKING = typing.TYPE_CHECKING def _gorg(cls): @@ -1111,16 +477,6 @@ def _gorg(cls): return cls -if OLD_GENERICS: - def _next_in_mro(cls): # noqa - """This function exists for compatibility with old typing versions.""" - next_in_mro = object - for i, c in enumerate(cls.__mro__[:-1]): - if isinstance(c, GenericMeta) and _gorg(c) is Generic: - next_in_mro = cls.__mro__[i + 1] - return next_in_mro - - _PROTO_WHITELIST = ['Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable', 'AsyncIterator', 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', @@ -1150,257 +506,12 @@ def _is_callable_members_only(cls): return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls)) +# 3.8+ if hasattr(typing, 'Protocol'): Protocol = typing.Protocol -elif HAVE_PROTOCOLS and not PEP_560: - - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') - - class _ProtocolMeta(GenericMeta): - """Internal metaclass for Protocol. - - This exists so Protocol classes can be generic without deriving - from Generic. - """ - if not OLD_GENERICS: - def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None, orig_bases=None): - # This is just a version copied from GenericMeta.__new__ that - # includes "Protocol" special treatment. (Comments removed for brevity.) - assert extra is None # Protocols should not have extra - if tvars is not None: - assert origin is not None - assert all(isinstance(t, TypeVar) for t in tvars), tvars - else: - tvars = _type_vars(bases) - gvars = None - for base in bases: - if base is Generic: - raise TypeError("Cannot inherit from plain Generic") - if (isinstance(base, GenericMeta) and - base.__origin__ in (Generic, Protocol)): - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...] or" - " Protocol[...] multiple times.") - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - raise TypeError( - "Some type variables (%s) " - "are not listed in %s[%s]" % - (", ".join(str(t) for t in tvars if t not in gvarset), - "Generic" if any(b.__origin__ is Generic - for b in bases) else "Protocol", - ", ".join(str(g) for g in gvars))) - tvars = gvars - - initial_bases = bases - if (extra is not None and type(extra) is abc.ABCMeta and - extra not in bases): - bases = (extra,) + bases - bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b - for b in bases) - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) - namespace.update({'__origin__': origin, '__extra__': extra}) - self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, - _root=True) - super(GenericMeta, self).__setattr__('_gorg', - self if not origin else - _gorg(origin)) - self.__parameters__ = tvars - self.__args__ = tuple(... if a is _TypingEllipsis else - () if a is _TypingEmpty else - a for a in args) if args else None - self.__next_in_mro__ = _next_in_mro(self) - if orig_bases is None: - self.__orig_bases__ = initial_bases - elif origin is not None: - self._abc_registry = origin._abc_registry - self._abc_cache = origin._abc_cache - if hasattr(self, '_subs_tree'): - self.__tree_hash__ = (hash(self._subs_tree()) if origin else - super(GenericMeta, self).__hash__()) - return self - - def __init__(cls, *args, **kwargs): - super().__init__(*args, **kwargs) - if not cls.__dict__.get('_is_protocol', None): - cls._is_protocol = any(b is Protocol or - isinstance(b, _ProtocolMeta) and - b.__origin__ is Protocol - for b in cls.__bases__) - if cls._is_protocol: - for base in cls.__mro__[1:]: - if not (base in (object, Generic) or - base.__module__ == 'collections.abc' and - base.__name__ in _PROTO_WHITELIST or - isinstance(base, TypingMeta) and base._is_protocol or - isinstance(base, GenericMeta) and - base.__origin__ is Generic): - raise TypeError('Protocols can only inherit from other' - ' protocols, got %r' % base) - - cls.__init__ = _no_init - - def _proto_hook(other): - if not cls.__dict__.get('_is_protocol', None): - return NotImplemented - if not isinstance(other, type): - # Same error as for issubclass(1, int) - raise TypeError('issubclass() arg 1 must be a class') - for attr in _get_protocol_attrs(cls): - for base in other.__mro__: - if attr in base.__dict__: - if base.__dict__[attr] is None: - return NotImplemented - break - annotations = getattr(base, '__annotations__', {}) - if (isinstance(annotations, typing.Mapping) and - attr in annotations and - isinstance(other, _ProtocolMeta) and - other._is_protocol): - break - else: - return NotImplemented - return True - if '__subclasshook__' not in cls.__dict__: - cls.__subclasshook__ = _proto_hook - - def __instancecheck__(self, instance): - # We need this method for situations where attributes are - # assigned in __init__. - if ((not getattr(self, '_is_protocol', False) or - _is_callable_members_only(self)) and - issubclass(instance.__class__, self)): - return True - if self._is_protocol: - if all(hasattr(instance, attr) and - (not callable(getattr(self, attr, None)) or - getattr(instance, attr) is not None) - for attr in _get_protocol_attrs(self)): - return True - return super(GenericMeta, self).__instancecheck__(instance) - - def __subclasscheck__(self, cls): - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False - if (self.__dict__.get('_is_protocol', None) and - not self.__dict__.get('_is_runtime_protocol', None)): - if sys._getframe(1).f_globals['__name__'] in ['abc', - 'functools', - 'typing']: - return False - raise TypeError("Instance and class checks can only be used with" - " @runtime protocols") - if (self.__dict__.get('_is_runtime_protocol', None) and - not _is_callable_members_only(self)): - if sys._getframe(1).f_globals['__name__'] in ['abc', - 'functools', - 'typing']: - return super(GenericMeta, self).__subclasscheck__(cls) - raise TypeError("Protocols with non-method members" - " don't support issubclass()") - return super(GenericMeta, self).__subclasscheck__(cls) - - if not OLD_GENERICS: - @_tp_cache - def __getitem__(self, params): - # We also need to copy this from GenericMeta.__getitem__ to get - # special treatment of "Protocol". (Comments removed for brevity.) - if not isinstance(params, tuple): - params = (params,) - if not params and _gorg(self) is not Tuple: - raise TypeError( - "Parameter list to %s[...] cannot be empty" % self.__qualname__) - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if self in (Generic, Protocol): - if not all(isinstance(p, TypeVar) for p in params): - raise TypeError( - "Parameters to %r[...] must all be type variables" % self) - if len(set(params)) != len(params): - raise TypeError( - "Parameters to %r[...] must all be unique" % self) - tvars = params - args = params - elif self in (Tuple, Callable): - tvars = _type_vars(params) - args = params - elif self.__origin__ in (Generic, Protocol): - raise TypeError("Cannot subscript already-subscripted %s" % - repr(self)) - else: - _check_generic(self, params) - tvars = _type_vars(params) - args = params - - prepend = (self,) if self.__origin__ is None else () - return self.__class__(self.__name__, - prepend + self.__bases__, - _no_slots_copy(self.__dict__), - tvars=tvars, - args=args, - origin=self, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - class Protocol(metaclass=_ProtocolMeta): - """Base class for protocol classes. Protocol classes are defined as:: - - class Proto(Protocol): - def meth(self) -> int: - ... - - Such classes are primarily used with static type checkers that recognize - structural subtyping (static duck-typing), for example:: - - class C: - def meth(self) -> int: - return 0 - - def func(x: Proto) -> int: - return x.meth() - - func(C()) # Passes static type check - - See PEP 544 for details. Protocol classes decorated with - @typing_extensions.runtime act as simple-minded runtime protocol that checks - only the presence of given attributes, ignoring their type signatures. - - Protocol classes can be generic, they are defined as:: - - class GenProto({bases}): - def meth(self) -> T: - ... - """ - __slots__ = () - _is_protocol = True - - def __new__(cls, *args, **kwds): - if _gorg(cls) is Protocol: - raise TypeError("Type Protocol cannot be instantiated; " - "it can be used only as a base class") - if OLD_GENERICS: - return _generic_new(_next_in_mro(cls), cls, *args, **kwds) - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - if Protocol.__doc__ is not None: - Protocol.__doc__ = Protocol.__doc__.format(bases="Protocol, Generic[T]" if - OLD_GENERICS else "Protocol[T]") - - +# 3.7 elif PEP_560: - from typing import _type_check, _collect_type_vars # noqa + from typing import _collect_type_vars # noqa def _no_init(self, *args, **kwargs): if type(self)._is_protocol: @@ -1413,14 +524,14 @@ def __instancecheck__(cls, instance): # We need this method for situations where attributes are # assigned in __init__. if ((not getattr(cls, '_is_protocol', False) or - _is_callable_members_only(cls)) and + _is_callable_members_only(cls)) and issubclass(instance.__class__, cls)): return True if cls._is_protocol: if all(hasattr(instance, attr) and - (not callable(getattr(cls, attr, None)) or - getattr(instance, attr) is not None) - for attr in _get_protocol_attrs(cls)): + (not callable(getattr(cls, attr, None)) or + getattr(instance, attr) is not None) + for attr in _get_protocol_attrs(cls)): return True return super().__instancecheck__(instance) @@ -1465,38 +576,38 @@ def __new__(cls, *args, **kwds): "it can only be used as a base class") return super().__new__(cls) - @_tp_cache + @typing._tp_cache def __class_getitem__(cls, params): if not isinstance(params, tuple): params = (params,) - if not params and cls is not Tuple: + if not params and cls is not typing.Tuple: raise TypeError( - "Parameter list to {}[...] cannot be empty".format(cls.__qualname__)) + f"Parameter list to {cls.__qualname__}[...] cannot be empty") msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) + params = tuple(typing._type_check(p, msg) for p in params) # noqa if cls is Protocol: # Generic can only be subscripted with unique type variables. - if not all(isinstance(p, TypeVar) for p in params): + if not all(isinstance(p, typing.TypeVar) for p in params): i = 0 - while isinstance(params[i], TypeVar): + while isinstance(params[i], typing.TypeVar): i += 1 raise TypeError( "Parameters to Protocol[...] must all be type variables." - " Parameter {} is {}".format(i + 1, params[i])) + f" Parameter {i + 1} is {params[i]}") if len(set(params)) != len(params): raise TypeError( "Parameters to Protocol[...] must all be unique") else: # Subscripting a regular Generic subclass. _check_generic(cls, params) - return _GenericAlias(cls, params) + return typing._GenericAlias(cls, params) def __init_subclass__(cls, *args, **kwargs): tvars = [] if '__orig_bases__' in cls.__dict__: - error = Generic in cls.__orig_bases__ + error = typing.Generic in cls.__orig_bases__ else: - error = Generic in cls.__bases__ + error = typing.Generic in cls.__bases__ if error: raise TypeError("Cannot inherit from plain Generic") if '__orig_bases__' in cls.__dict__: @@ -1508,10 +619,10 @@ def __init_subclass__(cls, *args, **kwargs): # and reject multiple Generic[...] and/or Protocol[...]. gvars = None for base in cls.__orig_bases__: - if (isinstance(base, _GenericAlias) and - base.__origin__ in (Generic, Protocol)): + if (isinstance(base, typing._GenericAlias) and + base.__origin__ in (typing.Generic, Protocol)): # for error messages - the_base = 'Generic' if base.__origin__ is Generic else 'Protocol' + the_base = base.__origin__.__name__ if gvars is not None: raise TypeError( "Cannot inherit from Generic[...]" @@ -1525,30 +636,164 @@ def __init_subclass__(cls, *args, **kwargs): if not tvarset <= gvarset: s_vars = ', '.join(str(t) for t in tvars if t not in gvarset) s_args = ', '.join(str(g) for g in gvars) - raise TypeError("Some type variables ({}) are" - " not listed in {}[{}]".format(s_vars, - the_base, s_args)) + raise TypeError(f"Some type variables ({s_vars}) are" + f" not listed in {the_base}[{s_args}]") tvars = gvars cls.__parameters__ = tuple(tvars) # Determine if this is a protocol or a concrete subclass. if not cls.__dict__.get('_is_protocol', None): - cls._is_protocol = any(b is Protocol for b in cls.__bases__) + cls._is_protocol = any(b is Protocol for b in cls.__bases__) + + # Set (or override) the protocol subclass hook. + def _proto_hook(other): + if not cls.__dict__.get('_is_protocol', None): + return NotImplemented + if not getattr(cls, '_is_runtime_protocol', False): + if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: + return NotImplemented + raise TypeError("Instance and class checks can only be used with" + " @runtime protocols") + if not _is_callable_members_only(cls): + if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: + return NotImplemented + raise TypeError("Protocols with non-method members" + " don't support issubclass()") + if not isinstance(other, type): + # Same error as for issubclass(1, int) + raise TypeError('issubclass() arg 1 must be a class') + for attr in _get_protocol_attrs(cls): + for base in other.__mro__: + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + annotations = getattr(base, '__annotations__', {}) + if (isinstance(annotations, typing.Mapping) and + attr in annotations and + isinstance(other, _ProtocolMeta) and + other._is_protocol): + break + else: + return NotImplemented + return True + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook + + # We have nothing more to do for non-protocols. + if not cls._is_protocol: + return + + # Check consistency of bases. + for base in cls.__bases__: + if not (base in (object, typing.Generic) or + base.__module__ == 'collections.abc' and + base.__name__ in _PROTO_WHITELIST or + isinstance(base, _ProtocolMeta) and base._is_protocol): + raise TypeError('Protocols can only inherit from other' + f' protocols, got {repr(base)}') + cls.__init__ = _no_init +# 3.6 +else: + from typing import _next_in_mro, _type_check # noqa + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + + class _ProtocolMeta(GenericMeta): + """Internal metaclass for Protocol. + + This exists so Protocol classes can be generic without deriving + from Generic. + """ + def __new__(cls, name, bases, namespace, + tvars=None, args=None, origin=None, extra=None, orig_bases=None): + # This is just a version copied from GenericMeta.__new__ that + # includes "Protocol" special treatment. (Comments removed for brevity.) + assert extra is None # Protocols should not have extra + if tvars is not None: + assert origin is not None + assert all(isinstance(t, typing.TypeVar) for t in tvars), tvars + else: + tvars = _type_vars(bases) + gvars = None + for base in bases: + if base is typing.Generic: + raise TypeError("Cannot inherit from plain Generic") + if (isinstance(base, GenericMeta) and + base.__origin__ in (typing.Generic, Protocol)): + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...] or" + " Protocol[...] multiple times.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + s_vars = ", ".join(str(t) for t in tvars if t not in gvarset) + s_args = ", ".join(str(g) for g in gvars) + cls_name = "Generic" if any(b.__origin__ is typing.Generic + for b in bases) else "Protocol" + raise TypeError(f"Some type variables ({s_vars}) are" + f" not listed in {cls_name}[{s_args}]") + tvars = gvars + + initial_bases = bases + if (extra is not None and type(extra) is abc.ABCMeta and + extra not in bases): + bases = (extra,) + bases + bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b + for b in bases) + if any(isinstance(b, GenericMeta) and b is not typing.Generic for b in bases): + bases = tuple(b for b in bases if b is not typing.Generic) + namespace.update({'__origin__': origin, '__extra__': extra}) + self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, + _root=True) + super(GenericMeta, self).__setattr__('_gorg', + self if not origin else + _gorg(origin)) + self.__parameters__ = tvars + self.__args__ = tuple(... if a is typing._TypingEllipsis else + () if a is typing._TypingEmpty else + a for a in args) if args else None + self.__next_in_mro__ = _next_in_mro(self) + if orig_bases is None: + self.__orig_bases__ = initial_bases + elif origin is not None: + self._abc_registry = origin._abc_registry + self._abc_cache = origin._abc_cache + if hasattr(self, '_subs_tree'): + self.__tree_hash__ = (hash(self._subs_tree()) if origin else + super(GenericMeta, self).__hash__()) + return self + + def __init__(cls, *args, **kwargs): + super().__init__(*args, **kwargs) + if not cls.__dict__.get('_is_protocol', None): + cls._is_protocol = any(b is Protocol or + isinstance(b, _ProtocolMeta) and + b.__origin__ is Protocol + for b in cls.__bases__) + if cls._is_protocol: + for base in cls.__mro__[1:]: + if not (base in (object, typing.Generic) or + base.__module__ == 'collections.abc' and + base.__name__ in _PROTO_WHITELIST or + isinstance(base, typing.TypingMeta) and base._is_protocol or + isinstance(base, GenericMeta) and + base.__origin__ is typing.Generic): + raise TypeError(f'Protocols can only inherit from other' + f' protocols, got {repr(base)}') + + cls.__init__ = _no_init - # Set (or override) the protocol subclass hook. def _proto_hook(other): if not cls.__dict__.get('_is_protocol', None): return NotImplemented - if not getattr(cls, '_is_runtime_protocol', False): - if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: - return NotImplemented - raise TypeError("Instance and class checks can only be used with" - " @runtime protocols") - if not _is_callable_members_only(cls): - if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: - return NotImplemented - raise TypeError("Protocols with non-method members" - " don't support issubclass()") if not isinstance(other, type): # Same error as for issubclass(1, int) raise TypeError('issubclass() arg 1 must be a class') @@ -1570,24 +815,129 @@ def _proto_hook(other): if '__subclasshook__' not in cls.__dict__: cls.__subclasshook__ = _proto_hook - # We have nothing more to do for non-protocols. - if not cls._is_protocol: - return + def __instancecheck__(self, instance): + # We need this method for situations where attributes are + # assigned in __init__. + if ((not getattr(self, '_is_protocol', False) or + _is_callable_members_only(self)) and + issubclass(instance.__class__, self)): + return True + if self._is_protocol: + if all(hasattr(instance, attr) and + (not callable(getattr(self, attr, None)) or + getattr(instance, attr) is not None) + for attr in _get_protocol_attrs(self)): + return True + return super(GenericMeta, self).__instancecheck__(instance) - # Check consistency of bases. - for base in cls.__bases__: - if not (base in (object, Generic) or - base.__module__ == 'collections.abc' and - base.__name__ in _PROTO_WHITELIST or - isinstance(base, _ProtocolMeta) and base._is_protocol): - raise TypeError('Protocols can only inherit from other' - ' protocols, got %r' % base) - cls.__init__ = _no_init + def __subclasscheck__(self, cls): + if self.__origin__ is not None: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") + return False + if (self.__dict__.get('_is_protocol', None) and + not self.__dict__.get('_is_runtime_protocol', None)): + if sys._getframe(1).f_globals['__name__'] in ['abc', + 'functools', + 'typing']: + return False + raise TypeError("Instance and class checks can only be used with" + " @runtime protocols") + if (self.__dict__.get('_is_runtime_protocol', None) and + not _is_callable_members_only(self)): + if sys._getframe(1).f_globals['__name__'] in ['abc', + 'functools', + 'typing']: + return super(GenericMeta, self).__subclasscheck__(cls) + raise TypeError("Protocols with non-method members" + " don't support issubclass()") + return super(GenericMeta, self).__subclasscheck__(cls) + + @typing._tp_cache + def __getitem__(self, params): + # We also need to copy this from GenericMeta.__getitem__ to get + # special treatment of "Protocol". (Comments removed for brevity.) + if not isinstance(params, tuple): + params = (params,) + if not params and _gorg(self) is not typing.Tuple: + raise TypeError( + f"Parameter list to {self.__qualname__}[...] cannot be empty") + msg = "Parameters to generic types must be types." + params = tuple(_type_check(p, msg) for p in params) + if self in (typing.Generic, Protocol): + if not all(isinstance(p, typing.TypeVar) for p in params): + raise TypeError( + f"Parameters to {repr(self)}[...] must all be type variables") + if len(set(params)) != len(params): + raise TypeError( + f"Parameters to {repr(self)}[...] must all be unique") + tvars = params + args = params + elif self in (typing.Tuple, typing.Callable): + tvars = _type_vars(params) + args = params + elif self.__origin__ in (typing.Generic, Protocol): + raise TypeError(f"Cannot subscript already-subscripted {repr(self)}") + else: + _check_generic(self, params) + tvars = _type_vars(params) + args = params + + prepend = (self,) if self.__origin__ is None else () + return self.__class__(self.__name__, + prepend + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=tvars, + args=args, + origin=self, + extra=self.__extra__, + orig_bases=self.__orig_bases__) + + class Protocol(metaclass=_ProtocolMeta): + """Base class for protocol classes. Protocol classes are defined as:: + + class Proto(Protocol): + def meth(self) -> int: + ... + + Such classes are primarily used with static type checkers that recognize + structural subtyping (static duck-typing), for example:: + + class C: + def meth(self) -> int: + return 0 + + def func(x: Proto) -> int: + return x.meth() + + func(C()) # Passes static type check + + See PEP 544 for details. Protocol classes decorated with + @typing_extensions.runtime act as simple-minded runtime protocol that checks + only the presence of given attributes, ignoring their type signatures. + + Protocol classes can be generic, they are defined as:: + + class GenProto(Protocol[T]): + def meth(self) -> T: + ... + """ + __slots__ = () + _is_protocol = True + + def __new__(cls, *args, **kwds): + if _gorg(cls) is Protocol: + raise TypeError("Type Protocol cannot be instantiated; " + "it can be used only as a base class") + return typing._generic_new(cls.__next_in_mro__, cls, *args, **kwds) +# 3.8+ if hasattr(typing, 'runtime_checkable'): runtime_checkable = typing.runtime_checkable -elif HAVE_PROTOCOLS: +# 3.6-3.7 +else: def runtime_checkable(cls): """Mark a protocol class as a runtime protocol, so that it can be used with isinstance() and issubclass(). Raise TypeError @@ -1598,19 +948,20 @@ def runtime_checkable(cls): """ if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol: raise TypeError('@runtime_checkable can be only applied to protocol classes,' - ' got %r' % cls) + f' got {cls!r}') cls._is_runtime_protocol = True return cls -if HAVE_PROTOCOLS: - # Exists for backwards compatibility. - runtime = runtime_checkable +# Exists for backwards compatibility. +runtime = runtime_checkable +# 3.8+ if hasattr(typing, 'SupportsIndex'): SupportsIndex = typing.SupportsIndex -elif HAVE_PROTOCOLS: +# 3.6-3.7 +else: @runtime_checkable class SupportsIndex(Protocol): __slots__ = () @@ -1665,8 +1016,8 @@ def _typeddict_new(*args, total=True, **kwargs): fields, = args # allow the "_fields" keyword be passed except ValueError: raise TypeError('TypedDict.__new__() takes from 2 to 3 ' - 'positional arguments but {} ' - 'were given'.format(len(args) + 2)) + f'positional arguments but {len(args) + 2} ' + 'were given') elif '_fields' in kwargs and len(kwargs) == 1: fields = kwargs.pop('_fields') import warnings @@ -1695,10 +1046,7 @@ def _typeddict_new(*args, total=True, **kwargs): class _TypedDictMeta(type): def __init__(cls, name, bases, ns, total=True): - # In Python 3.4 and 3.5 the __init__ method also needs to support the - # keyword arguments. - # See https://www.python.org/dev/peps/pep-0487/#implementation-details - super(_TypedDictMeta, cls).__init__(name, bases, ns) + super().__init__(name, bases, ns) def __new__(cls, name, bases, ns, total=True): # Create new typed dict class object. @@ -1708,7 +1056,7 @@ def __new__(cls, name, bases, ns, total=True): # Subclasses and instances of TypedDict return actual dictionaries # via _dict_new. ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new - tp_dict = super(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns) + tp_dict = super().__new__(cls, name, (dict,), ns) annotations = {} own_annotations = ns.get('__annotations__', {}) @@ -1780,6 +1128,7 @@ class Point2D(TypedDict): # Not exported and not a public API, but needed for get_origin() and get_args() # to work. _AnnotatedAlias = typing._AnnotatedAlias +# 3.7-3.8 elif PEP_560: class _AnnotatedAlias(typing._GenericAlias, _root=True): """Runtime representation of an annotated type. @@ -1802,10 +1151,8 @@ def copy_with(self, params): return _AnnotatedAlias(new_type, self.__metadata__) def __repr__(self): - return "typing_extensions.Annotated[{}, {}]".format( - typing._type_repr(self.__origin__), - ", ".join(repr(a) for a in self.__metadata__) - ) + return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, " + f"{', '.join(repr(a) for a in self.__metadata__)}]") def __reduce__(self): return operator.getitem, ( @@ -1860,7 +1207,7 @@ class Annotated: def __new__(cls, *args, **kwargs): raise TypeError("Type Annotated cannot be instantiated.") - @_tp_cache + @typing._tp_cache def __class_getitem__(cls, params): if not isinstance(params, tuple) or len(params) < 2: raise TypeError("Annotated[...] should be used " @@ -1873,7 +1220,7 @@ def __class_getitem__(cls, params): def __init_subclass__(cls, *args, **kwargs): raise TypeError( - "Cannot subclass {}.Annotated".format(cls.__module__) + f"Cannot subclass {cls.__module__}.Annotated" ) def _strip_annotations(t): @@ -1925,8 +1272,8 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): if include_extras: return hint return {k: _strip_annotations(t) for k, t in hint.items()} - -elif HAVE_ANNOTATED: +# 3.6 +else: def _is_dunder(name): """Returns True if name is a __dunder_variable_name__.""" @@ -1955,7 +1302,7 @@ def _tree_repr(self, tree): else: tp_repr = origin[0]._tree_repr(origin) metadata_reprs = ", ".join(repr(arg) for arg in metadata) - return '%s[%s, %s]' % (cls, tp_repr, metadata_reprs) + return f'{cls}[{tp_repr}, {metadata_reprs}]' def _subs_tree(self, tvars=None, args=None): # noqa if self is Annotated: @@ -1981,7 +1328,7 @@ def _get_cons(self): else: return tree - @_tp_cache + @typing._tp_cache def __getitem__(self, params): if not isinstance(params, tuple): params = (params,) @@ -2067,23 +1414,23 @@ class Annotated(metaclass=AnnotatedMeta): """ # Python 3.8 has get_origin() and get_args() but those implementations aren't -# Annotated-aware, so we can't use those, only Python 3.9 versions will do. -# Similarly, Python 3.9's implementation doesn't support ParamSpecArgs and -# ParamSpecKwargs. +# Annotated-aware, so we can't use those. Python 3.9's versions don't support +# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. if sys.version_info[:2] >= (3, 10): get_origin = typing.get_origin get_args = typing.get_args +# 3.7-3.9 elif PEP_560: try: # 3.9+ from typing import _BaseGenericAlias except ImportError: - _BaseGenericAlias = _GenericAlias + _BaseGenericAlias = typing._GenericAlias try: # 3.9+ from typing import GenericAlias except ImportError: - GenericAlias = _GenericAlias + GenericAlias = typing._GenericAlias def get_origin(tp): """Get the unsubscripted version of a type. @@ -2102,11 +1449,11 @@ def get_origin(tp): """ if isinstance(tp, _AnnotatedAlias): return Annotated - if isinstance(tp, (_GenericAlias, GenericAlias, _BaseGenericAlias, + if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, ParamSpecArgs, ParamSpecKwargs)): return tp.__origin__ - if tp is Generic: - return Generic + if tp is typing.Generic: + return typing.Generic return None def get_args(tp): @@ -2122,7 +1469,7 @@ def get_args(tp): """ if isinstance(tp, _AnnotatedAlias): return (tp.__origin__,) + tp.__metadata__ - if isinstance(tp, (_GenericAlias, GenericAlias)): + if isinstance(tp, (typing._GenericAlias, GenericAlias)): if getattr(tp, "_special", False): return () res = tp.__args__ @@ -2132,8 +1479,10 @@ def get_args(tp): return () +# 3.10+ if hasattr(typing, 'TypeAlias'): TypeAlias = typing.TypeAlias +# 3.9 elif sys.version_info[:2] >= (3, 9): class _TypeAliasForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -2151,8 +1500,8 @@ def TypeAlias(self, parameters): It's invalid when used anywhere except as in the example above. """ - raise TypeError("{} is not subscriptable".format(self)) - + raise TypeError(f"{self} is not subscriptable") +# 3.7-3.8 elif sys.version_info[:2] >= (3, 7): class _TypeAliasForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -2169,8 +1518,8 @@ def __repr__(self): It's invalid when used anywhere except as in the example above.""") - -elif hasattr(typing, '_FinalTypingBase'): +# 3.6 +else: class _TypeAliasMeta(typing.TypingMeta): """Metaclass for TypeAlias""" @@ -2200,37 +1549,13 @@ def __repr__(self): return 'typing_extensions.TypeAlias' TypeAlias = _TypeAliasBase(_root=True) -else: - class _TypeAliasMeta(typing.TypingMeta): - """Metaclass for TypeAlias""" - - def __instancecheck__(self, obj): - raise TypeError("TypeAlias cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeAlias cannot be used with issubclass().") - - def __call__(self, *args, **kwargs): - raise TypeError("Cannot instantiate TypeAlias") - - class TypeAlias(metaclass=_TypeAliasMeta, _root=True): - """Special marker indicating that an assignment should - be recognized as a proper type alias definition by type - checkers. - - For example:: - - Predicate: TypeAlias = Callable[..., bool] - - It's invalid when used anywhere except as in the example above. - """ - __slots__ = () # Python 3.10+ has PEP 612 if hasattr(typing, 'ParamSpecArgs'): ParamSpecArgs = typing.ParamSpecArgs ParamSpecKwargs = typing.ParamSpecKwargs +# 3.6-3.9 else: class _Immutable: """Mixin to indicate that object should not be copied.""" @@ -2258,7 +1583,7 @@ def __init__(self, origin): self.__origin__ = origin def __repr__(self): - return "{}.args".format(self.__origin__.__name__) + return f"{self.__origin__.__name__}.args" class ParamSpecKwargs(_Immutable): """The kwargs for a ParamSpec object. @@ -2276,10 +1601,12 @@ def __init__(self, origin): self.__origin__ = origin def __repr__(self): - return "{}.kwargs".format(self.__origin__.__name__) + return f"{self.__origin__.__name__}.kwargs" +# 3.10+ if hasattr(typing, 'ParamSpec'): ParamSpec = typing.ParamSpec +# 3.6-3.9 else: # Inherits from list as a workaround for Callable checks in Python < 3.9.2. @@ -2331,7 +1658,7 @@ def add_two(x: float, y: float) -> float: """ # Trick Generic __parameters__. - __class__ = TypeVar + __class__ = typing.TypeVar @property def args(self): @@ -2382,12 +1709,13 @@ def __call__(self, *args, **kwargs): pass if not PEP_560: - # Only needed in 3.6 and lower. + # Only needed in 3.6. def _get_type_vars(self, tvars): if self not in tvars: tvars.append(self) +# 3.6-3.9 if not hasattr(typing, 'Concatenate'): # Inherits from list as a workaround for Callable checks in Python < 3.9.2. class _ConcatenateGenericAlias(list): @@ -2395,18 +1723,13 @@ class _ConcatenateGenericAlias(list): # Trick Generic into looking into this for __parameters__. if PEP_560: __class__ = typing._GenericAlias - elif sys.version_info[:3] == (3, 5, 2): - __class__ = typing.TypingMeta else: __class__ = typing._TypingBase # Flag in 3.8. _special = False # Attribute in 3.6 and earlier. - if sys.version_info[:3] == (3, 5, 2): - _gorg = typing.GenericMeta - else: - _gorg = typing.Generic + _gorg = typing.Generic def __init__(self, origin, args): super().__init__(args) @@ -2415,9 +1738,8 @@ def __init__(self, origin, args): def __repr__(self): _type_repr = typing._type_repr - return '{origin}[{args}]' \ - .format(origin=_type_repr(self.__origin__), - args=', '.join(_type_repr(arg) for arg in self.__args__)) + return (f'{_type_repr(self.__origin__)}' + f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]') def __hash__(self): return hash((self.__origin__, self.__args__)) @@ -2429,17 +1751,18 @@ def __call__(self, *args, **kwargs): @property def __parameters__(self): return tuple( - tp for tp in self.__args__ if isinstance(tp, (TypeVar, ParamSpec)) + tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) ) if not PEP_560: - # Only required in 3.6 and lower. + # Only required in 3.6. def _get_type_vars(self, tvars): if self.__origin__ and self.__parameters__: typing._get_type_vars(self.__parameters__, tvars) -@_tp_cache +# 3.6-3.9 +@typing._tp_cache def _concatenate_getitem(self, parameters): if parameters == (): raise TypeError("Cannot take a Concatenate of no types.") @@ -2453,9 +1776,11 @@ def _concatenate_getitem(self, parameters): return _ConcatenateGenericAlias(self, parameters) +# 3.10+ if hasattr(typing, 'Concatenate'): Concatenate = typing.Concatenate _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa +# 3.9 elif sys.version_info[:2] >= (3, 9): @_TypeAliasForm def Concatenate(self, parameters): @@ -2470,7 +1795,7 @@ def Concatenate(self, parameters): See PEP 612 for detailed information. """ return _concatenate_getitem(self, parameters) - +# 3.7-8 elif sys.version_info[:2] >= (3, 7): class _ConcatenateForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -2491,8 +1816,8 @@ def __getitem__(self, parameters): See PEP 612 for detailed information. """) - -elif hasattr(typing, '_FinalTypingBase'): +# 3.6 +else: class _ConcatenateAliasMeta(typing.TypingMeta): """Metaclass for Concatenate.""" @@ -2527,38 +1852,11 @@ def __getitem__(self, parameters): return _concatenate_getitem(self, parameters) Concatenate = _ConcatenateAliasBase(_root=True) -# For 3.5.0 - 3.5.2 -else: - class _ConcatenateAliasMeta(typing.TypingMeta): - """Metaclass for Concatenate.""" - - def __instancecheck__(self, obj): - raise TypeError("TypeAlias cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeAlias cannot be used with issubclass().") - - def __call__(self, *args, **kwargs): - raise TypeError("Cannot instantiate TypeAlias") - - def __getitem__(self, parameters): - return _concatenate_getitem(self, parameters) - - class Concatenate(metaclass=_ConcatenateAliasMeta, _root=True): - """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a - higher order function which adds, removes or transforms parameters of a - callable. - - For example:: - - Callable[Concatenate[int, P], int] - - See PEP 612 for detailed information. - """ - __slots__ = () +# 3.10+ if hasattr(typing, 'TypeGuard'): TypeGuard = typing.TypeGuard +# 3.9 elif sys.version_info[:2] >= (3, 9): class _TypeGuardForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -2608,9 +1906,9 @@ def is_str(val: Union[str, float]): ``TypeGuard`` also works with type variables. For more information, see PEP 647 (User-Defined Type Guards). """ - item = typing._type_check(parameters, '{} accepts only single type.'.format(self)) - return _GenericAlias(self, (item,)) - + item = typing._type_check(parameters, f'{self} accepts only single type.') + return typing._GenericAlias(self, (item,)) +# 3.7-3.8 elif sys.version_info[:2] >= (3, 7): class _TypeGuardForm(typing._SpecialForm, _root=True): @@ -2619,8 +1917,8 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - '{} accepts only a single type'.format(self._name)) - return _GenericAlias(self, (item,)) + f'{self._name} accepts only a single type') + return typing._GenericAlias(self, (item,)) TypeGuard = _TypeGuardForm( 'TypeGuard', @@ -2666,7 +1964,8 @@ def is_str(val: Union[str, float]): ``TypeGuard`` also works with type variables. For more information, see PEP 647 (User-Defined Type Guards). """) -elif hasattr(typing, '_FinalTypingBase'): +# 3.6 +else: class _TypeGuard(typing._FinalTypingBase, _root=True): """Special typing form used to annotate the return type of a user-defined type guard function. ``TypeGuard`` only accepts a single type argument. @@ -2720,10 +2019,9 @@ def __getitem__(self, item): cls = type(self) if self.__type__ is None: return cls(typing._type_check(item, - '{} accepts only a single type.'.format(cls.__name__[1:])), + f'{cls.__name__[1:]} accepts only a single type.'), _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) + raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') def _eval_type(self, globalns, localns): new_tp = typing._eval_type(self.__type__, globalns, localns) @@ -2734,7 +2032,7 @@ def _eval_type(self, globalns, localns): def __repr__(self): r = super().__repr__() if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) + r += f'[{typing._type_repr(self.__type__)}]' return r def __hash__(self): @@ -2748,99 +2046,3 @@ def __eq__(self, other): return self is other TypeGuard = _TypeGuard(_root=True) -else: - class _TypeGuardMeta(typing.TypingMeta): - """Metaclass for TypeGuard""" - - def __new__(cls, name, bases, namespace, tp=None, _root=False): - self = super().__new__(cls, name, bases, namespace, _root=_root) - if tp is not None: - self.__type__ = tp - return self - - def __instancecheck__(self, obj): - raise TypeError("TypeGuard cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeGuard cannot be used with issubclass().") - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is not None: - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - param = typing._type_check( - item, - '{} accepts only single type.'.format(cls.__name__[1:])) - return cls(self.__name__, self.__bases__, - dict(self.__dict__), tp=param, _root=True) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(self.__name__, self.__bases__, - dict(self.__dict__), tp=self.__type__, - _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not hasattr(other, "__type__"): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - class TypeGuard(typing.Final, metaclass=_TypeGuardMeta, _root=True): - """Special typing form used to annotate the return type of a user-defined - type guard function. ``TypeGuard`` only accepts a single type argument. - At runtime, functions marked this way should return a boolean. - - ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static - type checkers to determine a more precise type of an expression within a - program's code flow. Usually type narrowing is done by analyzing - conditional code flow and applying the narrowing to a block of code. The - conditional expression here is sometimes referred to as a "type guard". - - Sometimes it would be convenient to use a user-defined boolean function - as a type guard. Such a function should use ``TypeGuard[...]`` as its - return type to alert static type checkers to this intention. - - Using ``-> TypeGuard`` tells the static type checker that for a given - function: - - 1. The return value is a boolean. - 2. If the return value is ``True``, the type of its argument - is the type inside ``TypeGuard``. - - For example:: - - def is_str(val: Union[str, float]): - # "isinstance" type guard - if isinstance(val, str): - # Type of ``val`` is narrowed to ``str`` - ... - else: - # Else, type of ``val`` is narrowed to ``float``. - ... - - Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower - form of ``TypeA`` (it can even be a wider form) and this may lead to - type-unsafe results. The main reason is to allow for things like - narrowing ``List[object]`` to ``List[str]`` even though the latter is not - a subtype of the former, since ``List`` is invariant. The responsibility of - writing type-safe type guards is left to the user. - - ``TypeGuard`` also works with type variables. For more information, see - PEP 647 (User-Defined Type Guards). - """ - __type__ = None From 8fd49b5fb255d91a7a065e15b049dc4e4c030238 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 11 Nov 2021 21:56:51 +0100 Subject: [PATCH 055/539] Use unittest for testing instead of pytest (#935) Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> --- .github/workflows/ci.yml | 19 +++++-------------- test-requirements.txt | 1 - 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70c69cff9..758b3162d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,21 +26,12 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Load pip cache - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/test-requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - - name: Install dependencies - run: | - pip install --upgrade pip - pip install -r test-requirements.txt - - name: Test typing_extensions - run: pytest typing_extensions/src_py3 + run: | + # Be wary of running `pip install` here, since it becomes easy for us to + # accidentally pick up typing_extensions as installed by a dependency + cd typing_extensions/src_py3 + python -m unittest test_typing_extensions.py linting: name: Lint diff --git a/test-requirements.txt b/test-requirements.txt index e29654168..658ae0a52 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,3 @@ flake8 flake8-bugbear flake8-pyi -pytest From 60aa1e20c07af2f0165485a2cdbbd23a5265a194 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe <50501825+Gobot1234@users.noreply.github.com> Date: Fri, 12 Nov 2021 02:23:29 +0000 Subject: [PATCH 056/539] Add PEP 673 Self type (#933) --- typing_extensions/CHANGELOG | 4 ++ .../src_py3/test_typing_extensions.py | 38 +++++++++- .../src_py3/typing_extensions.py | 72 +++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index ac8f868a8..4c3a51d6c 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -8,6 +8,10 @@ Dropped support for Python versions 3.5 and older. Simplified backports for Python 3.6.0 and newer. Patch by Adam Turner (@AA-Turner). +## Added in version 4.0.0 + +- Runtime support for PEP 673 and `typing_extensions.Self`. + ## Removed in version 4.0.0 The following non-exported but non-private names have been removed as they are diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index ff786022f..841d08cfc 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -16,7 +16,7 @@ from typing import Generic, NamedTuple from typing import no_type_check import typing_extensions -from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict +from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload @@ -2075,6 +2075,42 @@ def test_no_isinstance(self): issubclass(int, TypeGuard) + +class SelfTests(BaseTestCase): + def test_basics(self): + class Foo: + def bar(self) -> Self: ... + + self.assertEqual(gth(Foo.bar), {'return': Self}) + + def test_repr(self): + if hasattr(typing, 'Self'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Self), '{}.Self'.format(mod_name)) + + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + Self[int] + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Self)): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Self() + with self.assertRaises(TypeError): + type(Self)() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Self) + with self.assertRaises(TypeError): + issubclass(int, Self) + class AllTests(BaseTestCase): def test_typing_extensions_includes_standard(self): diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 1e96d1460..3e5b488c2 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -45,6 +45,7 @@ def _check_generic(cls, parameters): 'Concatenate', 'Final', 'ParamSpec', + 'Self', 'Type', # ABCs (from collections.abc). @@ -2046,3 +2047,74 @@ def __eq__(self, other): return self is other TypeGuard = _TypeGuard(_root=True) + + +if hasattr(typing, "Self"): + Self = typing.Self + +elif sys.version_info[:2] >= (3, 9): + class _SelfForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_SelfForm + def Self(self, params): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + raise TypeError(f"{self} is not subscriptable") + +elif sys.version_info[:2] >= (3, 7): + class _SelfForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + Self = _SelfForm( + "Self", + doc="""Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + ) +else: + class _Self(typing._FinalTypingBase, _root=True): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass().") + + Self = _Self(_root=True) From 1118e9ddd1a72576d7f7425dea226a377e64cbc5 Mon Sep 17 00:00:00 2001 From: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 12 Nov 2021 04:34:15 +0100 Subject: [PATCH 057/539] Make library guidelines checker agnostic (#934) --- docs/source/libraries.rst | 41 +++++++++------------------------------ 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 0c1942c37..330e00878 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -83,7 +83,7 @@ comprise the supported interface for the library. Each module exposes a set of symbols. Some of these symbols are considered "private” — implementation details that are not part of the -library’s interface. Type checkers like pyright use the following rules +library’s interface. Type checkers can use the following rules to determine which symbols are visible outside of the package. - Symbols whose names begin with an underscore (but are not dunder @@ -119,11 +119,13 @@ determine the value of ``__all__``. Type Completeness ================= -A “py.typed” library is said to be “type complete” if all of the symbols +A “py.typed” library should aim to be type complete so that type +checking and inspection can work to their full extent. Here we say that a +library is “type complete” if all of the symbols that comprise its interface have type annotations that refer to types that are fully known. Private symbols are exempt. -A “known type” is defined as follows: +The following are best practice recommendations for how to define “type complete”: Classes: @@ -281,33 +283,9 @@ Examples of known and unknown types Verifying Type Completeness =========================== -Pyright provides a feature that allows library authors to verify type -completeness for a “py.typed” package. To use this feature, create a -clean Python environment and install your package along with all of the -other dependent packages. Run the CLI version of pyright with the -``--verifytypes`` option. - -``pyright --verifytypes `` - -Pyright will analyze the library, identify all symbols that comprise the -interface to the library and emit errors for any symbols whose types are -unknown. It also produces a “type completeness score” which is the -percentage of symbols with known types. - -To see additional details (including a full list of symbols in the -library), append the ``--verbose`` option. - -The ``--verifytypes`` option can be combined with ``--outputjson`` to -emit the results in a form that can be consumed by other tools. - -The ``--verifytypes`` feature can be integrated into a continuous -integration (CI) system to verify that a library remains “type -complete”. - -If the ``--verifytypes`` option is combined with ``--ignoreexternal``, -any incomplete types that are imported from other external packages are -ignored. This allows library authors to focus on adding type annotations -for the code that is directly under their control. +Some type checkers provide features that allows library authors to verify type +completeness for a “py.typed” package. E.g. Pyright has a special +`command line flag `_ for this. Improving Type Completeness ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -436,8 +414,7 @@ Type Aliases Type aliases are symbols that refer to other types. Generic type aliases (those that refer to unspecialized generic classes) are supported by -most type checkers. Pyright also provides support for recursive type -aliases. +most type checkers. `PEP 613 `__ provides a way to explicitly designate a symbol as a type alias using the new TypeAlias From 5c98e79c676b51688b5e27ccac2010d23cb235ad Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 14 Nov 2021 06:22:30 -0800 Subject: [PATCH 058/539] Add PEP 655 Required and NotRequired to typing_extensions (#937) Co-authored-by: David Foster --- typing_extensions/CHANGELOG | 16 +- typing_extensions/README.rst | 2 + .../src_py3/test_typing_extensions.py | 90 +++++++++- .../src_py3/typing_extensions.py | 160 ++++++++++++++++++ 4 files changed, 259 insertions(+), 9 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 4c3a51d6c..897d9ad66 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,16 +1,16 @@ # Changes in version 4.0.0 -Starting with version 4.0.0, typing_extensions uses Semantic Versioning. -See the README for more information. - -Dropped support for Python versions 3.5 and older. - -Simplified backports for Python 3.6.0 and newer. -Patch by Adam Turner (@AA-Turner). +- Starting with version 4.0.0, typing_extensions uses Semantic Versioning. + See the README for more information. +- Dropped support for Python versions 3.5 and older. +- Simplified backports for Python 3.6.0 and newer. Patch by Adam Turner (@AA-Turner). ## Added in version 4.0.0 -- Runtime support for PEP 673 and `typing_extensions.Self`. +- Runtime support for PEP 673 and `typing_extensions.Self`. Patch by + James Hilton-Balfe (@Gobot1234). +- Runtime support for PEP 655 and `typing_extensions.Required` and `NotRequired`. + Patch by David Foster (@davidfstr). ## Removed in version 4.0.0 diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index c89fc0425..769535e6f 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -50,12 +50,14 @@ This module currently contains the following: - ``Literal`` - ``NewType`` - ``NoReturn`` +- ``NotRequired`` - ``overload`` - ``OrderedDict`` - ``ParamSpec`` - ``ParamSpecArgs`` - ``ParamSpecKwargs`` - ``Protocol`` +- ``Required`` - ``runtime_checkable`` - ``Text`` - ``Type`` diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 841d08cfc..2fc5b3f2a 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -18,7 +18,7 @@ import typing_extensions from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard -from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager +from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload try: from typing_extensions import get_type_hints @@ -193,6 +193,94 @@ def test_no_isinstance(self): issubclass(int, Final) +class RequiredTests(BaseTestCase): + + def test_basics(self): + with self.assertRaises(TypeError): + Required[1] + with self.assertRaises(TypeError): + Required[int, str] + with self.assertRaises(TypeError): + Required[int][str] + + def test_repr(self): + if hasattr(typing, 'Required'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Required), mod_name + '.Required') + cv = Required[int] + self.assertEqual(repr(cv), mod_name + '.Required[int]') + cv = Required[Employee] + self.assertEqual(repr(cv), mod_name + '.Required[%s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Required)): + pass + with self.assertRaises(TypeError): + class C(type(Required[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Required() + with self.assertRaises(TypeError): + type(Required)() + with self.assertRaises(TypeError): + type(Required[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Required[int]) + with self.assertRaises(TypeError): + issubclass(int, Required) + + +class NotRequiredTests(BaseTestCase): + + def test_basics(self): + with self.assertRaises(TypeError): + NotRequired[1] + with self.assertRaises(TypeError): + NotRequired[int, str] + with self.assertRaises(TypeError): + NotRequired[int][str] + + def test_repr(self): + if hasattr(typing, 'NotRequired'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(NotRequired), mod_name + '.NotRequired') + cv = NotRequired[int] + self.assertEqual(repr(cv), mod_name + '.NotRequired[int]') + cv = NotRequired[Employee] + self.assertEqual(repr(cv), mod_name + '.NotRequired[%s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(NotRequired)): + pass + with self.assertRaises(TypeError): + class C(type(NotRequired[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + NotRequired() + with self.assertRaises(TypeError): + type(NotRequired)() + with self.assertRaises(TypeError): + type(NotRequired[Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, NotRequired[int]) + with self.assertRaises(TypeError): + issubclass(int, NotRequired) + + class IntVarTests(BaseTestCase): def test_valid(self): T_ints = IntVar("T_ints") # noqa diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 3e5b488c2..d50f710e8 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2118,3 +2118,163 @@ def __subclasscheck__(self, cls): raise TypeError(f"{self} cannot be used with issubclass().") Self = _Self(_root=True) + + +if hasattr(typing, 'Required'): + Required = typing.Required + NotRequired = typing.NotRequired +elif sys.version_info[:2] >= (3, 9): + class _ExtensionsSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_ExtensionsSpecialForm + def Required(self, parameters): + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ + item = typing._type_check(parameters, f'{self._name} accepts only single type') + return typing._GenericAlias(self, (item,)) + + @_ExtensionsSpecialForm + def NotRequired(self, parameters): + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ + item = typing._type_check(parameters, f'{self._name} accepts only single type') + return typing._GenericAlias(self, (item,)) + +elif sys.version_info[:2] >= (3, 7): + class _RequiredForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + '{} accepts only single type'.format(self._name)) + return typing._GenericAlias(self, (item,)) + + Required = _RequiredForm( + 'Required', + doc="""A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """) + NotRequired = _RequiredForm( + 'NotRequired', + doc="""A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """) +else: + # NOTE: Modeled after _Final's implementation when _FinalTypingBase available + class _MaybeRequired(typing._FinalTypingBase, _root=True): + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + '{} accepts only single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(typing._type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _Final): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + class _Required(_MaybeRequired, _root=True): + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ + + class _NotRequired(_MaybeRequired, _root=True): + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ + + Required = _Required(_root=True) + NotRequired = _NotRequired(_root=True) From 1d1270e48a827cb42ab57dd7b2d6ee71d371941d Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Sun, 14 Nov 2021 15:23:03 +0100 Subject: [PATCH 059/539] Create issue templates (#921) --- .github/ISSUE_TEMPLATE/documentation-issue.md | 10 ++++++++++ .github/ISSUE_TEMPLATE/new-typing-feature.md | 10 ++++++++++ .github/ISSUE_TEMPLATE/other-issue.md | 10 ++++++++++ .github/ISSUE_TEMPLATE/typing-extensions-issue.md | 10 ++++++++++ 4 files changed, 40 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/documentation-issue.md create mode 100644 .github/ISSUE_TEMPLATE/new-typing-feature.md create mode 100644 .github/ISSUE_TEMPLATE/other-issue.md create mode 100644 .github/ISSUE_TEMPLATE/typing-extensions-issue.md diff --git a/.github/ISSUE_TEMPLATE/documentation-issue.md b/.github/ISSUE_TEMPLATE/documentation-issue.md new file mode 100644 index 000000000..6122c8f56 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation-issue.md @@ -0,0 +1,10 @@ +--- +name: Documentation issue +about: Report a problem or suggest changes for the documentation at https://typing.readthedocs.io/ +title: '' +labels: 'topic: documentation' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/new-typing-feature.md b/.github/ISSUE_TEMPLATE/new-typing-feature.md new file mode 100644 index 000000000..733df29ea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new-typing-feature.md @@ -0,0 +1,10 @@ +--- +name: New typing feature +about: Suggest a new feature for Python's typing system +title: '' +labels: 'topic: feature' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/other-issue.md b/.github/ISSUE_TEMPLATE/other-issue.md new file mode 100644 index 000000000..484282c71 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other-issue.md @@ -0,0 +1,10 @@ +--- +name: Other issue +about: Report any other issue +title: '' +labels: 'topic: other' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/typing-extensions-issue.md b/.github/ISSUE_TEMPLATE/typing-extensions-issue.md new file mode 100644 index 000000000..226796e69 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/typing-extensions-issue.md @@ -0,0 +1,10 @@ +--- +name: typing-extensions issue +about: Report a problem or suggest changes for the typing-extensions library +title: '' +labels: 'topic: typing-extensions' +assignees: '' + +--- + + From b53cb9a0a85576290fa549324d0673a1b3969897 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 14 Nov 2021 07:22:59 -0800 Subject: [PATCH 060/539] prepare release 4.0.0 (#941) --- typing_extensions/CHANGELOG | 9 +++++++-- typing_extensions/pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 897d9ad66..aa6a8508f 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,8 +1,8 @@ -# Changes in version 4.0.0 +# Release 4.0.0 (November 14, 2021) - Starting with version 4.0.0, typing_extensions uses Semantic Versioning. See the README for more information. -- Dropped support for Python versions 3.5 and older. +- Dropped support for Python versions 3.5 and older, including Python 2.7. - Simplified backports for Python 3.6.0 and newer. Patch by Adam Turner (@AA-Turner). ## Added in version 4.0.0 @@ -24,3 +24,8 @@ unneeded for supporting Python 3.6 and newer. - HAVE_PROTOCOLS - V_co - VT_co + +# Previous releases + +Prior to release 4.0.0 we did not provide a changelog. Please check +the Git history for details. diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 2ea4171b5..bf594d142 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi" # Project metadata [project] name = "typing_extensions" -version = "4.0.0-pre" +version = "4.0.0" description = "Backported and Experimental Type Hints for Python 3.6+" readme.text = """\ Typing Extensions -- Backported and Experimental Type Hints for Python From 97d466ff74fc64a5c56987c27f07120fe427fb88 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Mon, 15 Nov 2021 00:12:39 +0100 Subject: [PATCH 061/539] Add `Self` type to the README of typing-extensions (#942) --- typing_extensions/README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 769535e6f..54b7eb7bf 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -59,6 +59,7 @@ This module currently contains the following: - ``Protocol`` - ``Required`` - ``runtime_checkable`` +- ``Self`` - ``Text`` - ``Type`` - ``TypedDict`` From 37aa31fc1531283329f0eecafc20c38931100e8a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 19 Nov 2021 00:46:35 -0800 Subject: [PATCH 062/539] Documentation: Add links to typing PEPs (#949) --- docs/index.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index ba8384549..e013ea6e5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -58,3 +58,25 @@ Linters and Formatters * `flake8-pyi `_, a plugin for the `flake8 `_ linter that adds support for type stubs. + +Typing PEPs +=========== + +* `PEP 483 `, background on type hints +* `PEP 484 `, type hints +* `PEP 526 `, variable annotations and ``ClassVar`` +* `PEP 544 `, ``Protocol`` +* `PEP 561 `, distributing typed packages +* `PEP 563 `, ``from __future__ import annotations`` +* `PEP 585 `, subscriptable generics in the standard library +* `PEP 586 `, ``Literal`` +* `PEP 589 `, ``TypedDict`` +* `PEP 591 `, ``Final`` +* `PEP 593 `, ``Annotated`` +* `PEP 604 `, union syntax with ``|`` +* `PEP 612 `, ``ParamSpec`` +* `PEP 613 `, ``TypeAlias`` +* `PEP 646 `, variadic generics and ``TypeVarTuple`` +* `PEP 647 `, ``TypeGuard`` +* `PEP 655 ` (draft), ``Required`` and ``NotRequired`` +* `PEP 673 ` (draft), ``Self`` From 93a51a644201b627ff960dfd17800515760f1ad2 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 19 Nov 2021 05:15:23 -0800 Subject: [PATCH 063/539] Update typing-extensions README (#951) - Add an explicit inclusion policy: new stuff can go in here as soon as there is a PEP - Remove obsolete discussion of the typing PyPI package - Organize the list of contents by Python version, so it's easier to find the interesting bits. Maybe we should deprecate the stuff that was new in 3.5/3.6 at some point. --- typing_extensions/README.rst | 121 ++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 52 deletions(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 54b7eb7bf..3730457f4 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -9,15 +9,19 @@ Typing Extensions Overview ======== -The ``typing_extensions`` module contains both backports of ``typing`` features -as well as experimental types that will eventually be added to the ``typing`` -module, such as ``Protocol`` (see PEP 544 for details about protocols and -static duck typing) or ``TypedDict`` (see PEP 589). - -Users of other Python versions should continue to install and use -the ``typing`` module from PyPi instead of using this one unless -specifically writing code that must be compatible with multiple Python -versions or requires experimental types. +The ``typing_extensions`` module serves two related purposes: + +- Enable use of new type system features on older Python versions. For example, + ``typing.TypeGuard`` is new in Python 3.10, but ``typing_extensions`` allows + users on Python 3.6 through 3.9 to use it too. +- Enable experimentation with new type system PEPs before they are accepted and + added to the ``typing`` module. + +New features may be added to ``typing_extensions`` as soon as they are specified +in a PEP that has been added to the `python/peps `_ +repository. If the PEP is accepted, the feature will then be added to ``typing`` +for the next CPython release. No typing PEP has been rejected so far, so we +haven't yet figured out how to deal with that possibility. Starting with version 4.0.0, ``typing_extensions`` uses `Semantic Versioning `_. The @@ -31,49 +35,62 @@ Included items This module currently contains the following: -- ``Annotated`` -- ``AsyncContextManager`` -- ``AsyncGenerator`` -- ``AsyncIterable`` -- ``AsyncIterator`` -- ``Awaitable`` -- ``ChainMap`` -- ``ClassVar`` -- ``Concatenate`` -- ``ContextManager`` -- ``Coroutine`` -- ``Counter`` -- ``DefaultDict`` -- ``Deque`` -- ``final`` -- ``Final`` -- ``Literal`` -- ``NewType`` -- ``NoReturn`` -- ``NotRequired`` -- ``overload`` -- ``OrderedDict`` -- ``ParamSpec`` -- ``ParamSpecArgs`` -- ``ParamSpecKwargs`` -- ``Protocol`` -- ``Required`` -- ``runtime_checkable`` -- ``Self`` -- ``Text`` -- ``Type`` -- ``TypedDict`` -- ``TypeAlias`` -- ``TypeGuard`` -- ``TYPE_CHECKING`` - -Python 3.7+ ------------ - -- ``get_origin`` -- ``get_args`` -- ``get_type_hints`` - +- Experimental features + + - ``NotRequired`` (see PEP 655) + - ``Required`` (see PEP 655) + - ``Self`` (see PEP 673) + +- In ``typing`` since Python 3.10 + + - ``Concatenate`` (see PEP 612) + - ``ParamSpec`` (see PEP 612) + - ``ParamSpecArgs`` (see PEP 612) + - ``ParamSpecKwargs`` (see PEP 612) + - ``TypeAlias`` (see PEP 610) + - ``TypeGuard`` (see PEP 647) + +- In ``typing`` since Python 3.9 + + - ``Annotated`` (see PEP 593) + +- In ``typing`` since Python 3.8 + + - ``final`` (see PEP 591) + - ``Final`` (see PEP 591) + - ``Literal`` (see PEP 586) + - ``Protocol`` (see PEP 544) + - ``runtime_checkable`` (see PEP 544) + - ``TypedDict`` (see PEP 589) + - ``get_origin`` (``typing_extensions`` provides this function only in Python 3.7+) + - ``get_args`` (``typing_extensions`` provides this function only in Python 3.7+) + +- In ``typing`` since Python 3.7 + + - ``OrderedDict`` + +- In ``typing`` since Python 3.5 or 3.6 (see `the typing documentation + `_ for details) + + - ``AsyncContextManager`` + - ``AsyncGenerator`` + - ``AsyncIterable`` + - ``AsyncIterator`` + - ``Awaitable`` + - ``ChainMap`` + - ``ClassVar`` (see PEP 526) + - ``ContextManager`` + - ``Coroutine`` + - ``Counter`` + - ``DefaultDict`` + - ``Deque`` + - ``NewType`` + - ``NoReturn`` + - ``overload`` + - ``Text`` + - ``Type`` + - ``TYPE_CHECKING`` + - ``get_type_hints`` (``typing_extensions`` provides this function only in Python 3.7+) Other Notes and Limitations =========================== From fefa5350c21fff901dd522b648d86703f6db0cf1 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Fri, 19 Nov 2021 14:16:21 +0100 Subject: [PATCH 064/539] Link fixes (#952) --- docs/index.rst | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index e013ea6e5..c3a92fc13 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -62,21 +62,21 @@ Linters and Formatters Typing PEPs =========== -* `PEP 483 `, background on type hints -* `PEP 484 `, type hints -* `PEP 526 `, variable annotations and ``ClassVar`` -* `PEP 544 `, ``Protocol`` -* `PEP 561 `, distributing typed packages -* `PEP 563 `, ``from __future__ import annotations`` -* `PEP 585 `, subscriptable generics in the standard library -* `PEP 586 `, ``Literal`` -* `PEP 589 `, ``TypedDict`` -* `PEP 591 `, ``Final`` -* `PEP 593 `, ``Annotated`` -* `PEP 604 `, union syntax with ``|`` -* `PEP 612 `, ``ParamSpec`` -* `PEP 613 `, ``TypeAlias`` -* `PEP 646 `, variadic generics and ``TypeVarTuple`` -* `PEP 647 `, ``TypeGuard`` -* `PEP 655 ` (draft), ``Required`` and ``NotRequired`` -* `PEP 673 ` (draft), ``Self`` +* `PEP 483 `_, background on type hints +* `PEP 484 `_, type hints +* `PEP 526 `_, variable annotations and ``ClassVar`` +* `PEP 544 `_, ``Protocol`` +* `PEP 561 `_, distributing typed packages +* `PEP 563 `_, ``from __future__ import annotations`` +* `PEP 585 `_, subscriptable generics in the standard library +* `PEP 586 `_, ``Literal`` +* `PEP 589 `_, ``TypedDict`` +* `PEP 591 `_, ``Final`` +* `PEP 593 `_, ``Annotated`` +* `PEP 604 `_, union syntax with ``|`` +* `PEP 612 `_, ``ParamSpec`` +* `PEP 613 `_, ``TypeAlias`` +* `PEP 646 `_, variadic generics and ``TypeVarTuple`` +* `PEP 647 `_, ``TypeGuard`` +* `PEP 655 `_ (draft), ``Required`` and ``NotRequired`` +* `PEP 673 `_ (draft), ``Self`` From 28b64f4fea5d9a1c3b3df7dd306892615f0ff728 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 24 Nov 2021 14:07:06 -0800 Subject: [PATCH 065/539] Required/NotRequired: fix typo (#962) --- typing_extensions/src_py3/typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index d50f710e8..15fec259d 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2239,7 +2239,7 @@ def __hash__(self): return hash((type(self).__name__, self.__type__)) def __eq__(self, other): - if not isinstance(other, _Final): + if not isinstance(other, type(self)): return NotImplemented if self.__type__ is not None: return self.__type__ == other.__type__ From caa9cdf5d03d7ab04b971494f0cde2ebc8b1b297 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Thu, 25 Nov 2021 23:29:03 +0000 Subject: [PATCH 066/539] Rename src_py3 to src (#965) --- .flake8 | 2 +- .github/workflows/ci.yml | 4 +- .github/workflows/package.yml | 71 +++++++++++++++++++ CONTRIBUTING.md | 4 +- typing_extensions/MANIFEST.in | 4 +- typing_extensions/pyproject.toml | 4 -- .../test_typing_extensions.py | 0 .../{src_py3 => src}/typing_extensions.py | 0 typing_extensions/tox.ini | 2 +- 9 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/package.yml rename typing_extensions/{src_py3 => src}/test_typing_extensions.py (100%) rename typing_extensions/{src_py3 => src}/typing_extensions.py (100%) diff --git a/.flake8 b/.flake8 index be291da98..53cf55a09 100644 --- a/.flake8 +++ b/.flake8 @@ -12,4 +12,4 @@ ignore = exclude = # tests have more relaxed formatting rules # and its own specific config in .flake8-tests - typing_extensions/src_py3/test_typing_extensions.py, + typing_extensions/src/test_typing_extensions.py, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 758b3162d..80b40924f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: run: | # Be wary of running `pip install` here, since it becomes easy for us to # accidentally pick up typing_extensions as installed by a dependency - cd typing_extensions/src_py3 + cd typing_extensions/src python -m unittest test_typing_extensions.py linting: @@ -63,4 +63,4 @@ jobs: run: flake8 - name: Lint tests - run: flake8 --config=.flake8-tests typing_extensions/src_py3/test_typing_extensions.py + run: flake8 --config=.flake8-tests typing_extensions/src/test_typing_extensions.py diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 000000000..25f958684 --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,71 @@ +name: Test packaging + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + wheel: + name: Test wheel install + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3 + + - name: Install pypa/build + run: | + # Be wary of running `pip install` here, since it becomes easy for us to + # accidentally pick up typing_extensions as installed by a dependency + python -m pip install --upgrade build + python -m pip list + + - name: Build and install wheel + run: | + cd typing_extensions + python -m build . + export path_to_file=$(find dist -type f -name "typing_extensions-*.whl") + echo "::notice::Installing wheel: $path_to_file" + pip install -vvv $path_to_file + python -m pip list + + - name: Attempt to import typing_extensions + run: python -c "import typing_extensions; print(typing_extensions.__all__)" + + sdist: + name: Test sdist install + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3 + + - name: Install pypa/build + run: | + # Be wary of running `pip install` here, since it becomes easy for us to + # accidentally pick up typing_extensions as installed by a dependency + python -m pip install --upgrade build + python -m pip list + + - name: Build and install sdist + run: | + cd typing_extensions + python -m build . + export path_to_file=$(find dist -type f -name "typing_extensions-*.tar.gz") + echo "::notice::Installing sdist: $path_to_file" + pip install -vvv $path_to_file + python -m pip list + + - name: Attempt to import typing_extensions + run: python -c "import typing_extensions; print(typing_extensions.__all__)" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 895ab8d91..f76b51df8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,10 +34,10 @@ backwards-incompatible changes. - Build the source and wheel distributions: - - `pip3 install -U flit` + - `python -m pip install --upgrade build` - `cd typing_extensions` - `rm -rf dist/` - - `flit build --no-setup-py` + - `python -m build .` - Install the built distributions locally and test (if you were using `tox`, you already tested the source distribution). diff --git a/typing_extensions/MANIFEST.in b/typing_extensions/MANIFEST.in index c399b170d..e3e1b70c1 100644 --- a/typing_extensions/MANIFEST.in +++ b/typing_extensions/MANIFEST.in @@ -1,3 +1,3 @@ include CHANGELOG LICENSE README.rst -include src_py3/typing_extensions.py -include src_py3/test_typing_extensions.py +include src/typing_extensions.py +include src/test_typing_extensions.py diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index bf594d142..da3ebee88 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -61,7 +61,3 @@ classifiers = [ [[project.authors]] name = "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" email = "levkivskyi@gmail.com" - -# This tells Flit that the module is stored in the src_py3 directory. -[tool.flit.module] -name = "src_py3/typing_extensions" diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py similarity index 100% rename from typing_extensions/src_py3/test_typing_extensions.py rename to typing_extensions/src/test_typing_extensions.py diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src/typing_extensions.py similarity index 100% rename from typing_extensions/src_py3/typing_extensions.py rename to typing_extensions/src/typing_extensions.py diff --git a/typing_extensions/tox.ini b/typing_extensions/tox.ini index 892280f98..0c5959b2b 100644 --- a/typing_extensions/tox.ini +++ b/typing_extensions/tox.ini @@ -2,5 +2,5 @@ envlist = py36, py37, py38, py39 [testenv] -changedir = src_py3 +changedir = src commands = python -m unittest discover From 345e89226e09f32b0525c3a3ae8f9e9476465aa6 Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Sat, 27 Nov 2021 07:42:08 -0800 Subject: [PATCH 067/539] Link to English version of Pycharm's landing site (#967) The previous link sent the user to the German version of the site --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index c3a92fc13..ecbb63088 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -43,7 +43,7 @@ Type Checkers Development Environments ------------------------ -* `PyCharm `_, an IDE that supports +* `PyCharm `_, an IDE that supports type stubs both for type checking and code completion. * `Visual Studio Code `_, a code editor that supports type checking using mypy, pyright, or the From 273fe266abe09373a4d379f21c8b78d208864d94 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 28 Nov 2021 07:58:27 -0800 Subject: [PATCH 068/539] prepare changelog (#969) --- typing_extensions/CHANGELOG | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index aa6a8508f..26b06c3a1 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,3 +1,9 @@ +# Unreleased + +- Fix broken sdist in release 4.0.0. Patch by Adam Turner (@AA-Turner). +- Fix equality comparison for `Required` and `NotRequired`. Patch by + Jelle Zijlstra (@jellezijlstra). + # Release 4.0.0 (November 14, 2021) - Starting with version 4.0.0, typing_extensions uses Semantic Versioning. From 0c8e55f1631177d0965015e18f56518604b2f519 Mon Sep 17 00:00:00 2001 From: Chris Wesseling Date: Mon, 29 Nov 2021 04:10:51 +0100 Subject: [PATCH 069/539] Vendor typing._SpecialForm to fool typing._type_check (#966) Adds a local copy of _SpecialForm in our namespace, so typing._type_check won't raise TypeError. (#964) Co-authored-by: James Hilton-Balfe <50501825+Gobot1234@users.noreply.github.com> --- .../src/test_typing_extensions.py | 6 ++ typing_extensions/src/typing_extensions.py | 62 ++++++++++++------- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 2fc5b3f2a..731f97317 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -2199,6 +2199,12 @@ def test_no_isinstance(self): with self.assertRaises(TypeError): issubclass(int, Self) + def test_alias(self): + TupleSelf = Tuple[Self, Self] + class Alias: + def return_tuple(self) -> TupleSelf: + return (self, self) + class AllTests(BaseTestCase): def test_typing_extensions_includes_standard(self): diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 15fec259d..9f1c7aa31 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -2048,40 +2048,55 @@ def __eq__(self, other): TypeGuard = _TypeGuard(_root=True) - if hasattr(typing, "Self"): Self = typing.Self +elif sys.version_info[:2] >= (3, 7): + # Vendored from cpython typing._SpecialFrom + class _SpecialForm(typing._Final, _root=True): + __slots__ = ('_name', '__doc__', '_getitem') + + def __init__(self, getitem): + self._getitem = getitem + self._name = getitem.__name__ + self.__doc__ = getitem.__doc__ + + def __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name + + raise AttributeError(item) + + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass {self!r}") -elif sys.version_info[:2] >= (3, 9): - class _SelfForm(typing._SpecialForm, _root=True): def __repr__(self): - return 'typing_extensions.' + self._name + return f'typing_extensions.{self._name}' - @_SelfForm - def Self(self, params): - """Used to spell the type of "self" in classes. + def __reduce__(self): + return self._name - Example:: + def __call__(self, *args, **kwds): + raise TypeError(f"Cannot instantiate {self!r}") - from typing import Self + def __or__(self, other): + return typing.Union[self, other] - class ReturnsSelf: - def parse(self, data: bytes) -> Self: - ... - return self + def __ror__(self, other): + return typing.Union[other, self] - """ + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance()") - raise TypeError(f"{self} is not subscriptable") + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass()") -elif sys.version_info[:2] >= (3, 7): - class _SelfForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name + @typing._tp_cache + def __getitem__(self, parameters): + return self._getitem(self, parameters) - Self = _SelfForm( - "Self", - doc="""Used to spell the type of "self" in classes. + @_SpecialForm + def Self(self, params): + """Used to spell the type of "self" in classes. Example:: @@ -2093,7 +2108,8 @@ def parse(self, data: bytes) -> Self: return self """ - ) + + raise TypeError(f"{self} is not subscriptable") else: class _Self(typing._FinalTypingBase, _root=True): """Used to spell the type of "self" in classes. From a2371460d184c96aab7a69acc47fd059f875e3b4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 30 Nov 2021 17:42:06 -0800 Subject: [PATCH 070/539] prepare release 4.0.1 (#974) --- typing_extensions/CHANGELOG | 4 +++- typing_extensions/pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 26b06c3a1..24fe698dc 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,8 +1,10 @@ -# Unreleased +# Release 4.0.1 (November 30, 2021) - Fix broken sdist in release 4.0.0. Patch by Adam Turner (@AA-Turner). - Fix equality comparison for `Required` and `NotRequired`. Patch by Jelle Zijlstra (@jellezijlstra). +- Fix usage of `Self` as a type argument. Patch by Chris Wesseling + (@CharString) and James Hilton-Balfe (@Gobot1234). # Release 4.0.0 (November 14, 2021) diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index da3ebee88..073444a8f 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi" # Project metadata [project] name = "typing_extensions" -version = "4.0.0" +version = "4.0.1" description = "Backported and Experimental Type Hints for Python 3.6+" readme.text = """\ Typing Extensions -- Backported and Experimental Type Hints for Python From ad6d085d6d37ca8cf581431522b7dfc586784a66 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 17 Dec 2021 12:48:30 -0800 Subject: [PATCH 071/539] Add `NoReturn` to `__all__` (#983) Came up in python/typeshed#6619 --- typing_extensions/src/typing_extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 9f1c7aa31..455e3195f 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -82,6 +82,7 @@ def _check_generic(cls, parameters): 'TypeAlias', 'TypeGuard', 'TYPE_CHECKING', + 'NoReturn', ] if PEP_560: From 80cfad0255d2a4b12690aa9f76299d755b51b4e2 Mon Sep 17 00:00:00 2001 From: Shannon Zhu Date: Tue, 21 Dec 2021 10:21:27 -0800 Subject: [PATCH 072/539] Initial full TOC structure for typing docs content (#919) --- docs/index.rst | 9 ++-- docs/source/annotations.rst | 68 ++++++++++++++++++++++++++++++ docs/source/basics.rst | 3 ++ docs/source/dependencies.rst | 12 ++++++ docs/source/faq.rst | 23 ++++++++++ docs/source/inference.rst | 28 ++++++++++++ docs/source/introduction.rst | 41 ++++++++++++++++++ docs/source/libraries.rst | 2 + docs/source/reference.rst | 15 +++++++ docs/source/type_compatibility.rst | 17 ++++++++ docs/source/type_system.rst | 58 +++++++++++++++++++++++++ 11 files changed, 273 insertions(+), 3 deletions(-) create mode 100644 docs/source/annotations.rst create mode 100644 docs/source/basics.rst create mode 100644 docs/source/dependencies.rst create mode 100644 docs/source/faq.rst create mode 100644 docs/source/inference.rst create mode 100644 docs/source/introduction.rst create mode 100644 docs/source/reference.rst create mode 100644 docs/source/type_compatibility.rst create mode 100644 docs/source/type_system.rst diff --git a/docs/index.rst b/docs/index.rst index ecbb63088..ed9787089 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,9 +6,12 @@ Static Typing with Python :maxdepth: 2 :caption: Contents: - typing Module Documentation - source/libraries - source/stubs + source/introduction + +.. toctree:: + :maxdepth: 3 + + source/reference Indices and tables diff --git a/docs/source/annotations.rst b/docs/source/annotations.rst new file mode 100644 index 000000000..ea5c471fa --- /dev/null +++ b/docs/source/annotations.rst @@ -0,0 +1,68 @@ +**************** +Type Annotations +**************** + +Functions +========= + +Parameters +---------- + +Asynchronous Functions +---------------------- + +Generators +---------- + +Lambdas +------- + + +Classes +======= + +Overloads and Overrides +----------------------- + +Instance vs. Class Attributes +----------------------------- + +Final attributes +---------------- + +Abstract base classes +--------------------- + + +Globals +======= + + +Attributes +========== + + +Locals +====== + +Empty Containers +---------------- + + +Runtime Considerations +====================== + +Comment vs. Inline Support +-------------------------- + +Forward References +------------------ + +Generic types that don't implement `__getitem__` +------------------------------------------------ + +Conditioning on static type checking +------------------------------------ + +from `__future__` import annotations +------------------------------------ diff --git a/docs/source/basics.rst b/docs/source/basics.rst new file mode 100644 index 000000000..9b2bf52a1 --- /dev/null +++ b/docs/source/basics.rst @@ -0,0 +1,3 @@ +********** +The Basics +********** diff --git a/docs/source/dependencies.rst b/docs/source/dependencies.rst new file mode 100644 index 000000000..b16fa639f --- /dev/null +++ b/docs/source/dependencies.rst @@ -0,0 +1,12 @@ +************************** +Libraries and Dependencies +************************** + +:doc:`libraries` + +Typeshed +======== + + +Placeholder Stubs +================= diff --git a/docs/source/faq.rst b/docs/source/faq.rst new file mode 100644 index 000000000..906e27790 --- /dev/null +++ b/docs/source/faq.rst @@ -0,0 +1,23 @@ +************************** +Frequently Asked Questions +************************** + + +Why Types? +========== + + +Unsupported Python Features +=========================== + + +Common Errors +============= + + +Typing PEPs +=========== + + +Relevant Talks and Resources +============================ diff --git a/docs/source/inference.rst b/docs/source/inference.rst new file mode 100644 index 000000000..291dec48f --- /dev/null +++ b/docs/source/inference.rst @@ -0,0 +1,28 @@ +************** +Type Inference +************** + +Rules for local inference +========================= + +Explicit vs. Implicit Local Types +--------------------------------- + +Changing Types +============== + +Asserts +------- + +Casts +----- + +Type Guards +----------- + + +Protocols and Duck Typing +========================= + +Callback Protocols +------------------ diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst new file mode 100644 index 000000000..fb973559d --- /dev/null +++ b/docs/source/introduction.rst @@ -0,0 +1,41 @@ +******************************* +Introduction to Types in Python +******************************* + + +Background +========== + +How to read type annotations +---------------------------- + +When and why types are useful +----------------------------- + + +Gradual Typing: Static Types in a Dynamic Language +================================================== + +Opt-in type checking +-------------------- + +Type stubs +---------- + +:doc:`stubs` + +Strategies for increasing coverage +---------------------------------- + + +Getting Started +=============== + +Python type checkers +-------------------- + +How to annotate an existing codebase +------------------------------------ + +Typeshed +-------- diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 330e00878..371c1f74f 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -1,3 +1,5 @@ +.. _libraries: + *********************** Typing Python Libraries *********************** diff --git a/docs/source/reference.rst b/docs/source/reference.rst new file mode 100644 index 000000000..a5f321ed0 --- /dev/null +++ b/docs/source/reference.rst @@ -0,0 +1,15 @@ +********************* +Type System Reference +********************* + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + basics + type_system + annotations + inference + type_compatibility + dependencies + faq diff --git a/docs/source/type_compatibility.rst b/docs/source/type_compatibility.rst new file mode 100644 index 000000000..43ebd32b5 --- /dev/null +++ b/docs/source/type_compatibility.rst @@ -0,0 +1,17 @@ +****************** +Type Compatibility +****************** + + +The Class Hierarchy +=================== + +Mutable Containers +------------------ + +Comparing Callables: Covariance and Contravariance +-------------------------------------------------- + + +Metaclasses +=========== diff --git a/docs/source/type_system.rst b/docs/source/type_system.rst new file mode 100644 index 000000000..6d7954b82 --- /dev/null +++ b/docs/source/type_system.rst @@ -0,0 +1,58 @@ +*************** +The Type System +*************** + +Built-in Types +============== + +`typing Module Documentation `__ + +Built-in Primitives +------------------- + +Built-in operators +------------------ + +Advanced Types +-------------- + + +Simple User-Defined Types +========================= + + +Type Aliases +============ + +Recursive Aliases +----------------- + + +Data Structures +=============== + +Typed Dictionary +---------------- + +Dataclass +--------- + + +Generic Types +============= + + +Type Variables +============== + +TypeVar +------- + +Variadics +--------- + +ParamSpec +^^^^^^^^^ + +TypeVarTuple +^^^^^^^^^^^^ From 9c7e30be0f71ff542ba3fa039fad820236e91d6e Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Wed, 22 Dec 2021 05:40:33 -0500 Subject: [PATCH 073/539] Modify docs to comment out unpopulated toctrees (#987) Prior to this, the toctree directives pointed at numerous planned but unpopulated docs. The result is a mostly empty site with the real content buried. To revert without discarding work, the toctrees which list empty files are commented out, and new tables of contents are added to link to `libraries` and `stubs` (the existing, populated sections). Stub docs are left in place, producing sphinx warnings about unlinked docs. These are expected. The link to the typing module docs has been restored, now in a dedicated section. --- docs/index.rst | 16 ++++++++++------ docs/source/reference.rst | 21 ++++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index ed9787089..e5cd9ade6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,12 +1,12 @@ ************************* Static Typing with Python ************************* - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - source/introduction +.. +.. .. toctree:: +.. :maxdepth: 2 +.. :caption: Contents: +.. +.. source/introduction .. toctree:: :maxdepth: 3 @@ -20,6 +20,10 @@ Indices and tables * :ref:`genindex` * :ref:`search` +Python Language Documentation +============================= + +* `typing Module Documentation `_ Discussions and Support ======================= diff --git a/docs/source/reference.rst b/docs/source/reference.rst index a5f321ed0..4fbccec2b 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -6,10 +6,17 @@ Type System Reference :maxdepth: 2 :caption: Contents: - basics - type_system - annotations - inference - type_compatibility - dependencies - faq + libraries + stubs + + +.. The following pages are desired in a new TOC which will cover multiple +.. topics. For now, they are not linked because the pages are empty. +.. +.. basics +.. type_system +.. annotations +.. inference +.. type_compatibility +.. dependencies +.. faq From ae2633465e647da3a99ebabd4b27e33b1b39036a Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 22 Dec 2021 17:00:56 +0100 Subject: [PATCH 074/539] Improve TOC structure (#988) * Add section headers to the toc tree * Add a "Guides" section and put the libraries document there * Move the link to the typing documentation into the "Reference" section Cf. #845 --- docs/index.rst | 23 +++++++++++++++-------- docs/source/guides.rst | 9 +++++++++ docs/source/reference.rst | 3 +-- 3 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 docs/source/guides.rst diff --git a/docs/index.rst b/docs/index.rst index e5cd9ade6..7da222b11 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,18 +1,30 @@ ************************* Static Typing with Python ************************* + +.. Introduction +.. ============ .. .. .. toctree:: .. :maxdepth: 2 -.. :caption: Contents: .. .. source/introduction +Guides +====== + .. toctree:: - :maxdepth: 3 + :maxdepth: 2 - source/reference + source/guides +Reference +========= + +.. toctree:: + :maxdepth: 2 + + source/reference Indices and tables ================== @@ -20,11 +32,6 @@ Indices and tables * :ref:`genindex` * :ref:`search` -Python Language Documentation -============================= - -* `typing Module Documentation `_ - Discussions and Support ======================= diff --git a/docs/source/guides.rst b/docs/source/guides.rst new file mode 100644 index 000000000..9d435b798 --- /dev/null +++ b/docs/source/guides.rst @@ -0,0 +1,9 @@ +****************** +Type System Guides +****************** + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + libraries diff --git a/docs/source/reference.rst b/docs/source/reference.rst index 4fbccec2b..d18df3aa1 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -6,9 +6,8 @@ Type System Reference :maxdepth: 2 :caption: Contents: - libraries stubs - + typing Module Documentation .. The following pages are desired in a new TOC which will cover multiple .. topics. For now, they are not linked because the pages are empty. From 45895708e2afcef001650f7565e119a9d650f3d7 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 22 Dec 2021 17:08:42 +0100 Subject: [PATCH 075/539] Script to mail regular issue summaries to typing-sig (#929) Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> --- scripts/requirements.txt | 2 + scripts/typing-summary.py | 144 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 scripts/requirements.txt create mode 100755 scripts/typing-summary.py diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 000000000..d5f8f29d7 --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1,2 @@ +requests>=2.25.0,<3 +types-requests>=2.25.0,<3 diff --git a/scripts/typing-summary.py b/scripts/typing-summary.py new file mode 100755 index 000000000..f5f9825d0 --- /dev/null +++ b/scripts/typing-summary.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 + +""" +Generate a summary of last week's issues tagged with "topic: feature". + +The summary will include a list of new and changed issues and is sent each +Monday at 0200 CE(S)T to the typing-sig mailing list. Due to limitation +with GitHub Actions, the mail is sent from a private server, currently +maintained by @srittau. +""" + +from __future__ import annotations + +import datetime +from dataclasses import dataclass +from typing import Any, Iterable, Sequence + +import requests + +ISSUES_API_URL = "https://api.github.com/repos/python/typing/issues" +ISSUES_URL = "https://github.com/python/typing/issues?q=label%3A%22topic%3A+feature%22" +ISSUES_LABEL = "topic: feature" +SENDER_EMAIL = "Typing Bot " +RECEIVER_EMAIL = "typing-sig@python.org" + + +@dataclass +class Issue: + number: int + title: str + url: str + created: datetime.datetime + user: str + pull_request: bool = False + + +def main() -> None: + since = previous_week_start() + issues = fetch_issues(since) + new, updated = split_issues(issues, since) + print_summary(since, new, updated) + + +def previous_week_start() -> datetime.date: + today = datetime.date.today() + return today - datetime.timedelta(days=today.weekday() + 7) + + +def fetch_issues(since: datetime.date) -> list[Issue]: + """Return (new, updated) issues.""" + j = requests.get( + ISSUES_API_URL, + params={ + "labels": ISSUES_LABEL, + "since": f"{since:%Y-%m-%d}T00:00:00Z", + "per_page": "100", + "state": "open", + }, + headers={"Accept": "application/vnd.github.v3+json"}, + ).json() + assert isinstance(j, list) + return [parse_issue(j_i) for j_i in j] + + +def parse_issue(j: Any) -> Issue: + number = j["number"] + title = j["title"] + url = j["html_url"] + created_at = datetime.datetime.fromisoformat(j["created_at"][:-1]) + user = j["user"]["login"] + pull_request = "pull_request" in j + assert isinstance(number, int) + assert isinstance(title, str) + assert isinstance(url, str) + assert isinstance(user, str) + return Issue(number, title, url, created_at, user, pull_request) + + +def split_issues( + issues: Iterable[Issue], since: datetime.date +) -> tuple[list[Issue], list[Issue]]: + new = [] + updated = [] + for issue in issues: + if issue.created.date() >= since: + new.append(issue) + else: + updated.append(issue) + new.sort(key=lambda i: i.number) + updated.sort(key=lambda i: i.number) + return new, updated + + +def print_summary( + since: datetime.date, new: Sequence[Issue], changed: Sequence[Issue] +) -> None: + print(f"From: {SENDER_EMAIL}") + print(f"To: {RECEIVER_EMAIL}") + print(f"Subject: Opened and changed typing issues week {since:%G-W%V}") + print() + print(generate_mail(new, changed)) + + +def generate_mail(new: Sequence[Issue], changed: Sequence[Issue]) -> str: + if len(new) == 0 and len(changed) == 0: + s = ( + "No issues or pull requests with the label 'topic: feature' were opened\n" + "or updated last week in the typing repository on GitHub.\n\n" + ) + else: + s = ( + "The following is an overview of all issues and pull requests in the\n" + "typing repository on GitHub with the label 'topic: feature'\n" + "that were opened or updated last week, excluding closed issues.\n\n" + "---------------------------------------------------\n\n" + ) + if len(new) > 0: + s += "The following issues and pull requests were opened last week: \n\n" + s += "".join(generate_issue_text(issue) for issue in new) + s += "\n---------------------------------------------------\n\n" + if len(changed) > 0: + s += "The following issues and pull requests were updated last week: \n\n" + s += "".join(generate_issue_text(issue) for issue in changed) + s += "\n---------------------------------------------------\n\n" + s += ( + "All issues and pull requests with the label 'topic: feature'\n" + "can be viewed under the following URL:\n\n" + ) + s += ISSUES_URL + return s + + +def generate_issue_text(issue: Issue) -> str: + s = f"#{issue.number:<5} " + if issue.pull_request: + s += "[PR] " + s += f"{issue.title}\n" + s += f" opened by @{issue.user}\n" + s += f" {issue.url}\n" + return s + + +if __name__ == "__main__": + main() From fbd81cd5e0a4061102e2b93de38cf8e0e63ad6bc Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 1 Jan 2022 15:40:16 +0300 Subject: [PATCH 076/539] Create .editorconfig (#998) --- .editorconfig | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..26fb67037 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*.{py,pyi,rst,md,yml,yaml,toml,json}] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space + +[*.{py,pyi,toml,json}] +indent_size = 4 + +[*.{yml,yaml}] +indent_size = 2 From 2370371c7258f0b1432c34498ad742783503750d Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 4 Jan 2022 21:14:39 +0300 Subject: [PATCH 077/539] Suggest to use `_` prefix for stubs-only types (#1005) --- docs/source/stubs.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index d5ad68506..ae4567052 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -577,19 +577,21 @@ public method for which a library does not provided a usable runtime type:: from typing import Protocol - class Readable(Protocol): + class _Readable(Protocol): def read(self) -> str: ... - def get_reader() -> Readable: ... + def get_reader() -> _Readable: ... Structural Types ---------------- -As seen in the example with ``Readable`` in the previous section, a common use +As seen in the example with ``_Readable`` in the previous section, a common use of stub-only objects is to model types that are best described by their structure. These objects are called protocols [#pep544]_, and it is encouraged to use them freely to describe simple structural types. +It is `recommended `_ to prefix stubs-only object names with ``_``. + Incomplete Stubs ---------------- @@ -921,6 +923,8 @@ No:: ... def to_int3(x: str) -> int: pass +.. _private-definitions: + Private Definitions ------------------- From def3e45c691a77882bdebf791ab4e90da9a91f6a Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 4 Jan 2022 19:29:28 +0100 Subject: [PATCH 078/539] Fix link (#1007) --- docs/source/stubs.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index ae4567052..24987d414 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -590,7 +590,7 @@ of stub-only objects is to model types that are best described by their structure. These objects are called protocols [#pep544]_, and it is encouraged to use them freely to describe simple structural types. -It is `recommended `_ to prefix stubs-only object names with ``_``. +It is `recommended <#private-definitions>`_ to prefix stubs-only object names with ``_``. Incomplete Stubs ---------------- From a7a8d4824bc2dc2e51e252cfc166d3dbc7057d72 Mon Sep 17 00:00:00 2001 From: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> Date: Sat, 8 Jan 2022 02:39:10 -0500 Subject: [PATCH 079/539] Fix PEP number for `TypeAlias` (#1008) Per the Python docs and PEPs, `TypeAlias` appears to have been defined in PEP 613, not PEP 610. --- typing_extensions/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 3730457f4..5b4ebdd96 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -47,7 +47,7 @@ This module currently contains the following: - ``ParamSpec`` (see PEP 612) - ``ParamSpecArgs`` (see PEP 612) - ``ParamSpecKwargs`` (see PEP 612) - - ``TypeAlias`` (see PEP 610) + - ``TypeAlias`` (see PEP 613) - ``TypeGuard`` (see PEP 647) - In ``typing`` since Python 3.9 From 90b03abe37d3ebe585842f9dd31382af0150e9a0 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sun, 9 Jan 2022 18:18:44 +0000 Subject: [PATCH 080/539] Update workflow file (#1017) * Test on Python 3.11 pre-releases * Update linter config --- .github/workflows/ci.yml | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80b40924f..dbfb82ee8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11-dev"] runs-on: ubuntu-latest @@ -29,7 +29,7 @@ jobs: - name: Test typing_extensions run: | # Be wary of running `pip install` here, since it becomes easy for us to - # accidentally pick up typing_extensions as installed by a dependency + # accidentally pick up typing_extensions as installed by a dependency cd typing_extensions/src python -m unittest test_typing_extensions.py @@ -40,19 +40,12 @@ jobs: steps: - uses: actions/checkout@v2 - - - name: Set up Python + - name: Set up Python 3 uses: actions/setup-python@v2 with: - python-version: 3.9 - - - name: Load pip cache - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/test-requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- + python-version: 3 + cache: "pip" + cache-dependency-path: "test-requirements.txt" - name: Install dependencies run: | From d06c813af39e4d498b7f084d3b48aa12e4102c37 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Fri, 14 Jan 2022 22:55:29 +0100 Subject: [PATCH 081/539] Don't increase the major version when dropping support for a Python version (#1024) As pointed out in #1023, there is no risk of incompatibility, since the requires-python field will prevent installation on older Python versions. --- typing_extensions/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 5b4ebdd96..5ba083ca2 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -25,8 +25,8 @@ haven't yet figured out how to deal with that possibility. Starting with version 4.0.0, ``typing_extensions`` uses `Semantic Versioning `_. The -major version is incremented for all backwards-incompatible changes, including -dropping support for older Python versions. Therefore, it's safe to depend +major version is incremented for all backwards-incompatible changes. +Therefore, it's safe to depend on ``typing_extensions`` like this: ``typing_extensions >=x.y, <(x+1)``, where ``x.y`` is the first version that includes all features you need. From 0c397ed24103f2bdb72684a473ccb4bdd59f6495 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 15 Jan 2022 14:12:38 -0800 Subject: [PATCH 082/539] Remove obsolete note (#1025) typing exists in all supported Python versions --- typing_extensions/README.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 5ba083ca2..2dacd6524 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -112,6 +112,4 @@ Running tests ============= To run tests, navigate into the appropriate source directory and run -``test_typing_extensions.py``. You will also need to install the latest -version of ``typing`` if you are using a version of Python that does not -include ``typing`` as a part of the standard library. +``test_typing_extensions.py``. From c6ce6b1b287fc47264ca63c6819dcd01f294e754 Mon Sep 17 00:00:00 2001 From: Chris Moradi <37349208+chrismoradi@users.noreply.github.com> Date: Sat, 15 Jan 2022 15:22:16 -0800 Subject: [PATCH 083/539] Add is_typeddict from Python 3.10 (#1016) --- typing_extensions/CHANGELOG | 5 ++++ typing_extensions/README.rst | 1 + .../src/test_typing_extensions.py | 25 ++++++++++++++++++- typing_extensions/src/typing_extensions.py | 24 ++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 24fe698dc..bc1c2c834 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,3 +1,8 @@ +# Release 4.x.x + +- Add `is_typeddict`. Patch by Chris Moradi (@chrismoradi) and James + Hilton-Balfe (@Gobot1234). + # Release 4.0.1 (November 30, 2021) - Fix broken sdist in release 4.0.0. Patch by Adam Turner (@AA-Turner). diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 2dacd6524..270cff45a 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -49,6 +49,7 @@ This module currently contains the following: - ``ParamSpecKwargs`` (see PEP 612) - ``TypeAlias`` (see PEP 613) - ``TypeGuard`` (see PEP 647) + - ``is_typeddict`` - In ``typing`` since Python 3.9 diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 731f97317..186aa7a17 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -19,7 +19,7 @@ from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired -from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload +from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, is_typeddict try: from typing_extensions import get_type_hints except ImportError: @@ -36,6 +36,7 @@ # Flags used to mark tests that only apply after a specific # version of the typing module. TYPING_3_6_1 = sys.version_info[:3] >= (3, 6, 1) +TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0) TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) @@ -1681,6 +1682,28 @@ def test_keys_inheritance(self): 'voice': str, } + def test_is_typeddict(self): + assert is_typeddict(Point2D) is True + assert is_typeddict(Point2Dor3D) is True + assert is_typeddict(Union[str, int]) is False + # classes, not instances + assert is_typeddict(Point2D()) is False + + @skipUnless(TYPING_3_8_0, "Python 3.8+ required") + def test_is_typeddict_against_typeddict_from_typing(self): + Point = typing.TypedDict('Point', {'x': int, 'y': int}) + + class PointDict2D(typing.TypedDict): + x: int + y: int + + class PointDict3D(PointDict2D, total=False): + z: int + + assert is_typeddict(Point) is True + assert is_typeddict(PointDict2D) is True + assert is_typeddict(PointDict3D) is True + class AnnotatedTests(BaseTestCase): diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 455e3195f..b5a4654fe 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -72,6 +72,7 @@ def _check_generic(cls, parameters): 'Annotated', 'final', 'IntVar', + 'is_typeddict', 'Literal', 'NewType', 'overload', @@ -979,6 +980,7 @@ def __index__(self) -> int: # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 TypedDict = typing.TypedDict + _TypedDictMeta = typing._TypedDictMeta else: def _check_fails(cls, other): try: @@ -1123,6 +1125,28 @@ class Point2D(TypedDict): """ +if hasattr(typing, "is_typeddict"): + is_typeddict = typing.is_typeddict +else: + if hasattr(typing, "_TypedDictMeta"): + _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta) + else: + _TYPEDDICT_TYPES = (_TypedDictMeta,) + + def is_typeddict(tp): + """Check if an annotation is a TypedDict class + + For example:: + class Film(TypedDict): + title: str + year: int + + is_typeddict(Film) # => True + is_typeddict(Union[list, str]) # => False + """ + return isinstance(tp, tuple(_TYPEDDICT_TYPES)) + + # Python 3.9+ has PEP 593 (Annotated and modified get_type_hints) if hasattr(typing, 'Annotated'): Annotated = typing.Annotated From 465953f8d4afced0b9a6d7aab84415b3478ee76a Mon Sep 17 00:00:00 2001 From: Chris Moradi <37349208+chrismoradi@users.noreply.github.com> Date: Sat, 15 Jan 2022 15:29:16 -0800 Subject: [PATCH 084/539] Update tox config to add Python 3.10, support PEP517 build-backend (#1018) --- typing_extensions/tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typing_extensions/tox.ini b/typing_extensions/tox.ini index 0c5959b2b..08869364b 100644 --- a/typing_extensions/tox.ini +++ b/typing_extensions/tox.ini @@ -1,5 +1,6 @@ [tox] -envlist = py36, py37, py38, py39 +isolated_build = True +envlist = py36, py37, py38, py39, py310 [testenv] changedir = src From 86fab7591e74063f9dd7700ed446bd77fd276849 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 15 Jan 2022 15:35:49 -0800 Subject: [PATCH 085/539] @final: backport bpo-46342 (#1026) --- .github/workflows/ci.yml | 1 + typing_extensions/CHANGELOG | 3 + typing_extensions/README.rst | 12 +++ .../src/test_typing_extensions.py | 83 ++++++++++++++++++- typing_extensions/src/typing_extensions.py | 18 +++- 5 files changed, 111 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbfb82ee8..5a89af4d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Test typing_extensions + continue-on-error: ${{ matrix.python-version == '3.11-dev' }} run: | # Be wary of running `pip install` here, since it becomes easy for us to # accidentally pick up typing_extensions as installed by a dependency diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index bc1c2c834..e8522911e 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,8 @@ # Release 4.x.x +- The `@final` decorator now sets the `__final__` attribute on the + decorated object to allow runtime introspection. Backport from + bpo-46342. - Add `is_typeddict`. Patch by Chris Moradi (@chrismoradi) and James Hilton-Balfe (@Gobot1234). diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 270cff45a..d5d4128f4 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -96,6 +96,18 @@ This module currently contains the following: Other Notes and Limitations =========================== +Certain objects were changed after they were added to ``typing``, and +``typing_extensions`` provides a backport even on newer Python versions: + +- ``TypedDict`` does not store runtime information + about which (if any) keys are non-required in Python 3.8, and does not + honor the "total" keyword with old-style ``TypedDict()`` in Python + 3.9.0 and 3.9.1. +- ``get_origin`` and ``get_args`` lack support for ``Annotated`` in + Python 3.8 and lack support for ``ParamSpecArgs`` and ``ParamSpecKwargs`` + in 3.9. +- ``@final`` was changed in Python 3.11 to set the ``.__final__`` attribute. + There are a few types whose interface was modified between different versions of typing. For example, ``typing.Sequence`` was modified to subclass ``typing.Reversible`` as of Python 3.5.3. diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 186aa7a17..d740976c7 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -4,6 +4,8 @@ import contextlib import collections import collections.abc +from functools import lru_cache +import inspect import pickle import subprocess import types @@ -19,7 +21,7 @@ from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired -from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, is_typeddict +from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict try: from typing_extensions import get_type_hints except ImportError: @@ -2186,7 +2188,6 @@ def test_no_isinstance(self): issubclass(int, TypeGuard) - class SelfTests(BaseTestCase): def test_basics(self): class Foo: @@ -2228,6 +2229,81 @@ class Alias: def return_tuple(self) -> TupleSelf: return (self, self) + +class FinalDecoratorTests(BaseTestCase): + def test_final_unmodified(self): + def func(x): ... + self.assertIs(func, final(func)) + + def test_dunder_final(self): + @final + def func(): ... + @final + class Cls: ... + self.assertIs(True, func.__final__) + self.assertIs(True, Cls.__final__) + + class Wrapper: + __slots__ = ("func",) + def __init__(self, func): + self.func = func + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + # Check that no error is thrown if the attribute + # is not writable. + @final + @Wrapper + def wrapped(): ... + self.assertIsInstance(wrapped, Wrapper) + self.assertIs(False, hasattr(wrapped, "__final__")) + + class Meta(type): + @property + def __final__(self): return "can't set me" + @final + class WithMeta(metaclass=Meta): ... + self.assertEqual(WithMeta.__final__, "can't set me") + + # Builtin classes throw TypeError if you try to set an + # attribute. + final(int) + self.assertIs(False, hasattr(int, "__final__")) + + # Make sure it works with common builtin decorators + class Methods: + @final + @classmethod + def clsmethod(cls): ... + + @final + @staticmethod + def stmethod(): ... + + # The other order doesn't work because property objects + # don't allow attribute assignment. + @property + @final + def prop(self): ... + + @final + @lru_cache() + def cached(self): ... + + # Use getattr_static because the descriptor returns the + # underlying function, which doesn't have __final__. + self.assertIs( + True, + inspect.getattr_static(Methods, "clsmethod").__final__ + ) + self.assertIs( + True, + inspect.getattr_static(Methods, "stmethod").__final__ + ) + self.assertIs(True, Methods.prop.fget.__final__) + self.assertIs(True, Methods.cached.__final__) + + class AllTests(BaseTestCase): def test_typing_extensions_includes_standard(self): @@ -2277,6 +2353,8 @@ def test_typing_extensions_defers_when_possible(self): } if sys.version_info < (3, 10): exclude |= {'get_args', 'get_origin'} + if sys.version_info < (3, 11): + exclude.add('final') for item in typing_extensions.__all__: if item not in exclude and hasattr(typing, item): self.assertIs( @@ -2294,5 +2372,6 @@ def test_typing_extensions_compiles_with_opt(self): self.fail('Module does not compile with optimize=2 (-OO flag).') + if __name__ == '__main__': main() diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index b5a4654fe..7a03f1f37 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -212,11 +212,12 @@ def __eq__(self, other): Final = _Final(_root=True) -# 3.8+ -if hasattr(typing, 'final'): +if sys.version_info >= (3, 11): final = typing.final -# 3.6-3.7 else: + # @final exists in 3.8+, but we backport it for all versions + # before 3.11 to keep support for the __final__ attribute. + # See https://bugs.python.org/issue46342 def final(f): """This decorator can be used to indicate to type checkers that the decorated method cannot be overridden, and decorated class @@ -235,8 +236,17 @@ class Leaf: class Other(Leaf): # Error reported by type checker ... - There is no runtime checking of these properties. + There is no runtime checking of these properties. The decorator + sets the ``__final__`` attribute to ``True`` on the decorated object + to allow runtime introspection. """ + try: + f.__final__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass return f From d7d91a90ed8004910be96f3aa076d067619c69e1 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sun, 16 Jan 2022 17:29:10 +0300 Subject: [PATCH 086/539] Backport tests from `test_typing.py` (#1028) As promised in https://bugs.python.org/issue46386 Backported: - https://github.com/python/cpython/pull/30619 Not backported: - https://github.com/python/cpython/pull/30613 (because `ParamSpec` is already tested with `pickle` using all protocols) --- .../src/test_typing_extensions.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index d740976c7..ab6a84de1 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -2117,14 +2117,37 @@ class MyClass: ... def test_valid_uses(self): P = ParamSpec('P') T = TypeVar('T') - C1 = typing.Callable[Concatenate[int, P], int] - C2 = typing.Callable[Concatenate[int, T, P], T] + + C1 = Callable[Concatenate[int, P], int] + C2 = Callable[Concatenate[int, T, P], T] # Test collections.abc.Callable too. if sys.version_info[:2] >= (3, 9): C3 = collections.abc.Callable[Concatenate[int, P], int] C4 = collections.abc.Callable[Concatenate[int, T, P], T] + def test_invalid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') + + with self.assertRaisesRegex( + TypeError, + 'Cannot take a Concatenate of no types', + ): + Concatenate[()] + + with self.assertRaisesRegex( + TypeError, + 'The last parameter to Concatenate should be a ParamSpec variable', + ): + Concatenate[P, T] + + with self.assertRaisesRegex( + TypeError, + 'each arg must be a type', + ): + Concatenate[1, P] + def test_basic_introspection(self): P = ParamSpec('P') C1 = Concatenate[int, P] From 523cf0233edc7a29502fbd30dc6bf641a7408e16 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Mon, 17 Jan 2022 17:35:20 +0200 Subject: [PATCH 087/539] PEP 655 Add `Required` and `NotRequired` to `__all__` (#1031) --- typing_extensions/CHANGELOG | 2 ++ typing_extensions/src/typing_extensions.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index e8522911e..e8bb7bf61 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,7 @@ # Release 4.x.x +- Add missed `Required` and `NotRequired` to `__all__`. Patch by + Yuri Karabas (@uriyyo). - The `@final` decorator now sets the `__final__` attribute on the decorated object to allow runtime introspection. Backport from bpo-46342. diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 7a03f1f37..2e0b3d78b 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -84,6 +84,8 @@ def _check_generic(cls, parameters): 'TypeGuard', 'TYPE_CHECKING', 'NoReturn', + 'Required', + 'NotRequired', ] if PEP_560: From 3b53f0167e3d7d22e3199cca85da5ef5c6c6f4cd Mon Sep 17 00:00:00 2001 From: Gregory Beauregard Date: Tue, 25 Jan 2022 14:55:16 -0800 Subject: [PATCH 088/539] Annotated: backport bpo-46491 (#1049) --- typing_extensions/CHANGELOG | 2 ++ .../src/test_typing_extensions.py | 18 ++++++++++++++++++ typing_extensions/src/typing_extensions.py | 18 ++++++++++++++---- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index e8bb7bf61..026ba5984 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,7 @@ # Release 4.x.x +- `Annotated` can now wrap `ClassVar` and `Final`. Backport from + bpo-46491. Patch by Gregory Beauregard (@GBeauregard). - Add missed `Required` and `NotRequired` to `__all__`. Patch by Yuri Karabas (@uriyyo). - The `@final` decorator now sets the `__final__` attribute on the diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index ab6a84de1..f92bb135b 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -1799,6 +1799,24 @@ class C: A.x = 5 self.assertEqual(C.x, 5) + @skipIf(sys.version_info[:2] in ((3, 9), (3, 10)), "Waiting for bpo-46491 bugfix.") + def test_special_form_containment(self): + class C: + classvar: Annotated[ClassVar[int], "a decoration"] = 4 + const: Annotated[Final[int], "Const"] = 4 + + if sys.version_info[:2] >= (3, 7): + self.assertEqual(get_type_hints(C, globals())["classvar"], ClassVar[int]) + self.assertEqual(get_type_hints(C, globals())["const"], Final[int]) + else: + self.assertEqual( + get_type_hints(C, globals())["classvar"], + Annotated[ClassVar[int], "a decoration"] + ) + self.assertEqual( + get_type_hints(C, globals())["const"], Annotated[Final[int], "Const"] + ) + def test_hash_eq(self): self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 2e0b3d78b..dab53d439 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -1251,8 +1251,12 @@ def __class_getitem__(cls, params): raise TypeError("Annotated[...] should be used " "with at least two arguments (a type and an " "annotation).") - msg = "Annotated[t, ...]: t must be a type." - origin = typing._type_check(params[0], msg) + allowed_special_forms = (ClassVar, Final) + if get_origin(params[0]) in allowed_special_forms: + origin = params[0] + else: + msg = "Annotated[t, ...]: t must be a type." + origin = typing._type_check(params[0], msg) metadata = tuple(params[1:]) return _AnnotatedAlias(origin, metadata) @@ -1377,8 +1381,14 @@ def __getitem__(self, params): "with at least two arguments (a type and an " "annotation).") else: - msg = "Annotated[t, ...]: t must be a type." - tp = typing._type_check(params[0], msg) + if ( + isinstance(params[0], typing._TypingBase) and + type(params[0]).__name__ == "_ClassVar" + ): + tp = params[0] + else: + msg = "Annotated[t, ...]: t must be a type." + tp = typing._type_check(params[0], msg) metadata = tuple(params[1:]) return self.__class__( self.__name__, From c81192315d437c4dca5cf290ddc0ff0a2a04f42b Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Wed, 2 Feb 2022 15:37:43 +0300 Subject: [PATCH 089/539] Add a note about modules that should not be included into stubs (#1019) --- docs/source/stubs.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index 24987d414..a7ab6dfed 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -524,6 +524,17 @@ Type Stub Content This section documents best practices on what elements to include or leave out of type stubs. +Modules excluded fom stubs +-------------------------- + +Not all modules should be included into stubs. + +It is recommended to exclude: + +1. Implementation details, with `multiprocessing/popen_spawn_win32.py `_ as a notable example +2. Modules that are not supposed to be imported, such as ``__main__.py`` +3. Protected modules that start with a single ``_`` char. However, when needed protected modules can still be added (see :ref:`undocumented-objects` section below) + Public Interface ---------------- @@ -539,6 +550,8 @@ The following should always be included: Other objects may be included if they are not prefixed with an underscore or if they are being used in practice. (See the next section.) +.. _undocumented-objects: + Undocumented Objects -------------------- From 59e5918a83b81afa54a3bd51f02d362c458b06f0 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 2 Feb 2022 09:46:22 -0800 Subject: [PATCH 090/539] add dataclass_transform (#1054) Co-authored-by: Erik De Bonte --- typing_extensions/CHANGELOG | 1 + typing_extensions/README.rst | 1 + .../src/test_typing_extensions.py | 78 +++++++++++++++++ typing_extensions/src/typing_extensions.py | 83 +++++++++++++++++++ 4 files changed, 163 insertions(+) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 026ba5984..fd1abb93e 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,6 @@ # Release 4.x.x +- Runtime support for PEP 681 and `typing_extensions.dataclass_transform`. - `Annotated` can now wrap `ClassVar` and `Final`. Backport from bpo-46491. Patch by Gregory Beauregard (@GBeauregard). - Add missed `Required` and `NotRequired` to `__all__`. Patch by diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index d5d4128f4..28081c3d2 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -37,6 +37,7 @@ This module currently contains the following: - Experimental features + - ``@dataclass_transform()`` (see PEP 681) - ``NotRequired`` (see PEP 655) - ``Required`` (see PEP 655) - ``Self`` (see PEP 673) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index f92bb135b..cb32dd077 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -22,6 +22,7 @@ from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict +from typing_extensions import dataclass_transform try: from typing_extensions import get_type_hints except ImportError: @@ -2345,6 +2346,83 @@ def cached(self): ... self.assertIs(True, Methods.cached.__final__) +class DataclassTransformTests(BaseTestCase): + def test_decorator(self): + def create_model(*, frozen: bool = False, kw_only: bool = True): + return lambda cls: cls + + decorated = dataclass_transform(kw_only_default=True, order_default=False)(create_model) + + class CustomerModel: + id: int + + self.assertIs(decorated, create_model) + self.assertEqual( + decorated.__dataclass_transform__, + { + "eq_default": True, + "order_default": False, + "kw_only_default": True, + "field_descriptors": (), + } + ) + self.assertIs( + decorated(frozen=True, kw_only=False)(CustomerModel), + CustomerModel + ) + + def test_base_class(self): + class ModelBase: + def __init_subclass__(cls, *, frozen: bool = False): ... + + Decorated = dataclass_transform(eq_default=True, order_default=True)(ModelBase) + + class CustomerModel(Decorated, frozen=True): + id: int + + self.assertIs(Decorated, ModelBase) + self.assertEqual( + Decorated.__dataclass_transform__, + { + "eq_default": True, + "order_default": True, + "kw_only_default": False, + "field_descriptors": (), + } + ) + self.assertIsSubclass(CustomerModel, Decorated) + + def test_metaclass(self): + class Field: ... + + class ModelMeta(type): + def __new__( + cls, name, bases, namespace, *, init: bool = True, + ): + return super().__new__(cls, name, bases, namespace) + + Decorated = dataclass_transform( + order_default=True, field_descriptors=(Field,) + )(ModelMeta) + + class ModelBase(metaclass=Decorated): ... + + class CustomerModel(ModelBase, init=False): + id: int + + self.assertIs(Decorated, ModelMeta) + self.assertEqual( + Decorated.__dataclass_transform__, + { + "eq_default": True, + "order_default": True, + "kw_only_default": False, + "field_descriptors": (Field,), + } + ) + self.assertIsInstance(CustomerModel, Decorated) + + class AllTests(BaseTestCase): def test_typing_extensions_includes_standard(self): diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index dab53d439..bcf169530 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -70,6 +70,7 @@ def _check_generic(cls, parameters): # One-off things. 'Annotated', + 'dataclass_transform', 'final', 'IntVar', 'is_typeddict', @@ -2341,3 +2342,85 @@ class Movie(TypedDict): Required = _Required(_root=True) NotRequired = _NotRequired(_root=True) + +if hasattr(typing, 'dataclass_transform'): + dataclass_transform = typing.dataclass_transform +else: + def dataclass_transform( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + field_descriptors: typing.Tuple[ + typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]], + ... + ] = (), + ) -> typing.Callable[[T], T]: + """Decorator that marks a function, class, or metaclass as providing + dataclass-like behavior. + + Example: + + from typing_extensions import dataclass_transform + + _T = TypeVar("_T") + + # Used on a decorator function + @dataclass_transform() + def create_model(cls: type[_T]) -> type[_T]: + ... + return cls + + @create_model + class CustomerModel: + id: int + name: str + + # Used on a base class + @dataclass_transform() + class ModelBase: ... + + class CustomerModel(ModelBase): + id: int + name: str + + # Used on a metaclass + @dataclass_transform() + class ModelMeta(type): ... + + class ModelBase(metaclass=ModelMeta): ... + + class CustomerModel(ModelBase): + id: int + name: str + + Each of the ``CustomerModel`` classes defined in this example will now + behave similarly to a dataclass created with the ``@dataclasses.dataclass`` + decorator. For example, the type checker will synthesize an ``__init__`` + method. + + The arguments to this decorator can be used to customize this behavior: + - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be + True or False if it is omitted by the caller. + - ``order_default`` indicates whether the ``order`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``kw_only_default`` indicates whether the ``kw_only`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``field_descriptors`` specifies a static list of supported classes + or functions, that describe fields, similar to ``dataclasses.field()``. + + At runtime, this decorator records its arguments in the + ``__dataclass_transform__`` attribute on the decorated object. + + See PEP 681 for details. + + """ + def decorator(cls_or_fn): + cls_or_fn.__dataclass_transform__ = { + "eq_default": eq_default, + "order_default": order_default, + "kw_only_default": kw_only_default, + "field_descriptors": field_descriptors, + } + return cls_or_fn + return decorator From eeb29ad833f992ea9f2f29b32723c05586cd0aec Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 2 Feb 2022 23:55:01 -0800 Subject: [PATCH 091/539] Add typing_extensions.reveal_type (#1055) --- typing_extensions/CHANGELOG | 1 + typing_extensions/README.rst | 4 ++++ .../src/test_typing_extensions.py | 8 ++++++- typing_extensions/src/typing_extensions.py | 24 +++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index fd1abb93e..975a0032d 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,6 @@ # Release 4.x.x +- Add `reveal_type`. Backport from bpo-46414. - Runtime support for PEP 681 and `typing_extensions.dataclass_transform`. - `Annotated` can now wrap `ClassVar` and `Final`. Backport from bpo-46491. Patch by Gregory Beauregard (@GBeauregard). diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 28081c3d2..961bdf93e 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -40,6 +40,10 @@ This module currently contains the following: - ``@dataclass_transform()`` (see PEP 681) - ``NotRequired`` (see PEP 655) - ``Required`` (see PEP 655) + +- In ``typing`` since Python 3.11 + + - ``reveal_type`` - ``Self`` (see PEP 673) - In ``typing`` since Python 3.10 diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index cb32dd077..03a75e4c4 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -22,7 +22,7 @@ from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict -from typing_extensions import dataclass_transform +from typing_extensions import dataclass_transform, reveal_type try: from typing_extensions import get_type_hints except ImportError: @@ -2346,6 +2346,12 @@ def cached(self): ... self.assertIs(True, Methods.cached.__final__) +class RevealTypeTests(BaseTestCase): + def test_reveal_type(self): + obj = object() + self.assertIs(obj, reveal_type(obj)) + + class DataclassTransformTests(BaseTestCase): def test_decorator(self): def create_model(*, frozen: bool = False, kw_only: bool = True): diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index bcf169530..b594e33f7 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -78,6 +78,7 @@ def _check_generic(cls, parameters): 'NewType', 'overload', 'Protocol', + 'reveal_type', 'runtime', 'runtime_checkable', 'Text', @@ -2343,6 +2344,29 @@ class Movie(TypedDict): Required = _Required(_root=True) NotRequired = _NotRequired(_root=True) +if hasattr(typing, "reveal_type"): + reveal_type = typing.reveal_type +else: + def reveal_type(__obj: T) -> T: + """Reveal the inferred type of a variable. + + When a static type checker encounters a call to ``reveal_type()``, + it will emit the inferred type of the argument:: + + x: int = 1 + reveal_type(x) + + Running a static type checker (e.g., ``mypy``) on this example + will produce output similar to 'Revealed type is "builtins.int"'. + + At runtime, the function prints the runtime type of the + argument and returns it unchanged. + + """ + print(f"Runtime type is {type(__obj).__name__!r}", file=sys.stderr) + return __obj + + if hasattr(typing, 'dataclass_transform'): dataclass_transform = typing.dataclass_transform else: From 31c318dd8399cb1966fd1560e2535d1e29a419d7 Mon Sep 17 00:00:00 2001 From: Gregory Beauregard Date: Tue, 8 Feb 2022 18:59:11 -0800 Subject: [PATCH 092/539] ParamSpec: backport bpo-46676 (#1059) --- typing_extensions/CHANGELOG | 2 ++ typing_extensions/src/test_typing_extensions.py | 9 +++++++++ typing_extensions/src/typing_extensions.py | 10 ++++++++++ 3 files changed, 21 insertions(+) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 975a0032d..c96bd34de 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,7 @@ # Release 4.x.x +- `ParamSpec` args and kwargs are now equal to themselves. Backport from + bpo-46676. Patch by Gregory Beauregard (@GBeauregard). - Add `reveal_type`. Backport from bpo-46414. - Runtime support for PEP 681 and `typing_extensions.dataclass_transform`. - `Annotated` can now wrap `ClassVar` and `Final`. Backport from diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 03a75e4c4..4e1d95ff6 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -2057,8 +2057,10 @@ def test_valid_uses(self): self.assertTrue(hasattr(P, 'args')) self.assertTrue(hasattr(P, 'kwargs')) + @skipIf((3, 10, 0) <= sys.version_info[:3] <= (3, 10, 2), "Needs bpo-46676.") def test_args_kwargs(self): P = ParamSpec('P') + P_2 = ParamSpec('P_2') # Note: not in dir(P) because of __class__ hacks self.assertTrue(hasattr(P, 'args')) self.assertTrue(hasattr(P, 'kwargs')) @@ -2066,6 +2068,13 @@ def test_args_kwargs(self): self.assertIsInstance(P.kwargs, ParamSpecKwargs) self.assertIs(P.args.__origin__, P) self.assertIs(P.kwargs.__origin__, P) + self.assertEqual(P.args, P.args) + self.assertEqual(P.kwargs, P.kwargs) + self.assertNotEqual(P.args, P_2.args) + self.assertNotEqual(P.kwargs, P_2.kwargs) + self.assertNotEqual(P.args, P.kwargs) + self.assertNotEqual(P.kwargs, P.args) + self.assertNotEqual(P.args, P_2.kwargs) self.assertEqual(repr(P.args), "P.args") self.assertEqual(repr(P.kwargs), "P.kwargs") diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index b594e33f7..27eaff0f8 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -1635,6 +1635,11 @@ def __init__(self, origin): def __repr__(self): return f"{self.__origin__.__name__}.args" + def __eq__(self, other): + if not isinstance(other, ParamSpecArgs): + return NotImplemented + return self.__origin__ == other.__origin__ + class ParamSpecKwargs(_Immutable): """The kwargs for a ParamSpec object. @@ -1653,6 +1658,11 @@ def __init__(self, origin): def __repr__(self): return f"{self.__origin__.__name__}.kwargs" + def __eq__(self, other): + if not isinstance(other, ParamSpecKwargs): + return NotImplemented + return self.__origin__ == other.__origin__ + # 3.10+ if hasattr(typing, 'ParamSpec'): ParamSpec = typing.ParamSpec From a45c7e9fdbaaace11905bf5d873e427749de119c Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 9 Feb 2022 18:16:06 -0800 Subject: [PATCH 093/539] Update list of PEPs (#1063) --- docs/index.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 7da222b11..1230e3402 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -93,4 +93,7 @@ Typing PEPs * `PEP 646 `_, variadic generics and ``TypeVarTuple`` * `PEP 647 `_, ``TypeGuard`` * `PEP 655 `_ (draft), ``Required`` and ``NotRequired`` -* `PEP 673 `_ (draft), ``Self`` +* `PEP 673 `_, ``Self`` +* `PEP 675 `_ (draft), ``LiteralString`` +* `PEP 677 `_ (draft), callable type syntax +* `PEP 681 `_, ``@dataclass_transform()`` From c1db137f573d4b47bc5a141fb8f048f2d7c75fcc Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 9 Feb 2022 18:18:43 -0800 Subject: [PATCH 094/539] PEP 681 is still draft (#1064) --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 1230e3402..695097f96 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -96,4 +96,4 @@ Typing PEPs * `PEP 673 `_, ``Self`` * `PEP 675 `_ (draft), ``LiteralString`` * `PEP 677 `_ (draft), callable type syntax -* `PEP 681 `_, ``@dataclass_transform()`` +* `PEP 681 `_ (draft), ``@dataclass_transform()`` From a53957cad88444d9a33e5906cef31c39fb0b919a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 10 Feb 2022 17:48:13 -0800 Subject: [PATCH 095/539] Add Never and assert_never (#1060) Backport of python/cpython#30842, with additional tests from @sobolevn's python/cpython#31222. --- typing_extensions/CHANGELOG | 1 + typing_extensions/README.rst | 2 + .../src/test_typing_extensions.py | 93 ++++++++++++---- typing_extensions/src/typing_extensions.py | 100 +++++++++++++++++- 4 files changed, 172 insertions(+), 24 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index c96bd34de..092e04a86 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,6 @@ # Release 4.x.x +- Add `Never` and `assert_never`. Backport from bpo-46475. - `ParamSpec` args and kwargs are now equal to themselves. Backport from bpo-46676. Patch by Gregory Beauregard (@GBeauregard). - Add `reveal_type`. Backport from bpo-46414. diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 961bdf93e..a83ed3c82 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -43,6 +43,8 @@ This module currently contains the following: - In ``typing`` since Python 3.11 + - ``assert_never`` + - ``Never`` - ``reveal_type`` - ``Self`` (see PEP 673) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 4e1d95ff6..911168810 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -12,7 +12,7 @@ from unittest import TestCase, main, skipUnless, skipIf from test import ann_module, ann_module2, ann_module3 import typing -from typing import TypeVar, Optional, Union +from typing import TypeVar, Optional, Union, Any from typing import T, KT, VT # Not in __all__. from typing import Tuple, List, Dict, Iterable, Iterator, Callable from typing import Generic, NamedTuple @@ -22,7 +22,7 @@ from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict -from typing_extensions import dataclass_transform, reveal_type +from typing_extensions import dataclass_transform, reveal_type, Never, assert_never try: from typing_extensions import get_type_hints except ImportError: @@ -70,43 +70,94 @@ class Employee: pass -class NoReturnTests(BaseTestCase): +class BottomTypeTestsMixin: + bottom_type: ClassVar[Any] - def test_noreturn_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(42, NoReturn) + def test_equality(self): + self.assertEqual(self.bottom_type, self.bottom_type) + self.assertIs(self.bottom_type, self.bottom_type) + self.assertNotEqual(self.bottom_type, None) - def test_noreturn_subclass_type_error_1(self): - with self.assertRaises(TypeError): - issubclass(Employee, NoReturn) + @skipUnless(PEP_560, "Python 3.7+ required") + def test_get_origin(self): + from typing_extensions import get_origin + self.assertIs(get_origin(self.bottom_type), None) - def test_noreturn_subclass_type_error_2(self): + def test_instance_type_error(self): with self.assertRaises(TypeError): - issubclass(NoReturn, Employee) + isinstance(42, self.bottom_type) - def test_repr(self): - if hasattr(typing, 'NoReturn'): - self.assertEqual(repr(NoReturn), 'typing.NoReturn') - else: - self.assertEqual(repr(NoReturn), 'typing_extensions.NoReturn') + def test_subclass_type_error(self): + with self.assertRaises(TypeError): + issubclass(Employee, self.bottom_type) + with self.assertRaises(TypeError): + issubclass(NoReturn, self.bottom_type) def test_not_generic(self): with self.assertRaises(TypeError): - NoReturn[int] + self.bottom_type[int] def test_cannot_subclass(self): with self.assertRaises(TypeError): - class A(NoReturn): + class A(self.bottom_type): pass with self.assertRaises(TypeError): - class A(type(NoReturn)): + class A(type(self.bottom_type)): pass def test_cannot_instantiate(self): with self.assertRaises(TypeError): - NoReturn() + self.bottom_type() with self.assertRaises(TypeError): - type(NoReturn)() + type(self.bottom_type)() + + +class NoReturnTests(BottomTypeTestsMixin, BaseTestCase): + bottom_type = NoReturn + + def test_repr(self): + if hasattr(typing, 'NoReturn'): + self.assertEqual(repr(NoReturn), 'typing.NoReturn') + else: + self.assertEqual(repr(NoReturn), 'typing_extensions.NoReturn') + + def test_get_type_hints(self): + def some(arg: NoReturn) -> NoReturn: ... + def some_str(arg: 'NoReturn') -> 'typing.NoReturn': ... + + expected = {'arg': NoReturn, 'return': NoReturn} + for target in [some, some_str]: + with self.subTest(target=target): + self.assertEqual(gth(target), expected) + + def test_not_equality(self): + self.assertNotEqual(NoReturn, Never) + self.assertNotEqual(Never, NoReturn) + + +class NeverTests(BottomTypeTestsMixin, BaseTestCase): + bottom_type = Never + + def test_repr(self): + if hasattr(typing, 'Never'): + self.assertEqual(repr(Never), 'typing.Never') + else: + self.assertEqual(repr(Never), 'typing_extensions.Never') + + def test_get_type_hints(self): + def some(arg: Never) -> Never: ... + def some_str(arg: 'Never') -> 'typing_extensions.Never': ... + + expected = {'arg': Never, 'return': Never} + for target in [some, some_str]: + with self.subTest(target=target): + self.assertEqual(gth(target), expected) + + +class AssertNeverTests(BaseTestCase): + def test_exception(self): + with self.assertRaises(AssertionError): + assert_never(None) class ClassVarTests(BaseTestCase): diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 27eaff0f8..b67efd0b1 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -70,6 +70,7 @@ def _check_generic(cls, parameters): # One-off things. 'Annotated', + 'assert_never', 'dataclass_transform', 'final', 'IntVar', @@ -85,6 +86,7 @@ def _check_generic(cls, parameters): 'TypeAlias', 'TypeGuard', 'TYPE_CHECKING', + 'Never', 'NoReturn', 'Required', 'NotRequired', @@ -2107,9 +2109,8 @@ def __eq__(self, other): TypeGuard = _TypeGuard(_root=True) -if hasattr(typing, "Self"): - Self = typing.Self -elif sys.version_info[:2] >= (3, 7): + +if sys.version_info[:2] >= (3, 7): # Vendored from cpython typing._SpecialFrom class _SpecialForm(typing._Final, _root=True): __slots__ = ('_name', '__doc__', '_getitem') @@ -2153,6 +2154,10 @@ def __subclasscheck__(self, cls): def __getitem__(self, parameters): return self._getitem(self, parameters) + +if hasattr(typing, "Self"): + Self = typing.Self +elif sys.version_info[:2] >= (3, 7): @_SpecialForm def Self(self, params): """Used to spell the type of "self" in classes. @@ -2195,6 +2200,69 @@ def __subclasscheck__(self, cls): Self = _Self(_root=True) +if hasattr(typing, "Never"): + Never = typing.Never +elif sys.version_info[:2] >= (3, 7): + @_SpecialForm + def Never(self, params): + """The bottom type, a type that has no members. + + This can be used to define a function that should never be + called, or a function that never returns:: + + from typing_extensions import Never + + def never_call_me(arg: Never) -> None: + pass + + def int_or_str(arg: int | str) -> None: + never_call_me(arg) # type checker error + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + never_call_me(arg) # ok, arg is of type Never + + """ + + raise TypeError(f"{self} is not subscriptable") +else: + class _Never(typing._FinalTypingBase, _root=True): + """The bottom type, a type that has no members. + + This can be used to define a function that should never be + called, or a function that never returns:: + + from typing_extensions import Never + + def never_call_me(arg: Never) -> None: + pass + + def int_or_str(arg: int | str) -> None: + never_call_me(arg) # type checker error + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + never_call_me(arg) # ok, arg is of type Never + + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass().") + + Never = _Never(_root=True) + + if hasattr(typing, 'Required'): Required = typing.Required NotRequired = typing.NotRequired @@ -2377,6 +2445,32 @@ def reveal_type(__obj: T) -> T: return __obj +if hasattr(typing, "assert_never"): + assert_never = typing.assert_never +else: + def assert_never(__arg: Never) -> Never: + """Assert to the type checker that a line of code is unreachable. + + Example:: + + def int_or_str(arg: int | str) -> None: + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + assert_never(arg) + + If a type checker finds that a call to assert_never() is + reachable, it will emit an error. + + At runtime, this throws an exception when called. + + """ + raise AssertionError("Expected code to be unreachable") + + if hasattr(typing, 'dataclass_transform'): dataclass_transform = typing.dataclass_transform else: From f6e827230b397c26a114638fd5cac54517905d4d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 10 Feb 2022 18:21:27 -0800 Subject: [PATCH 096/539] add LiteralString (PEP 675) (#1053) Co-authored-by: Nikita Sobolev --- typing_extensions/CHANGELOG | 1 + typing_extensions/README.rst | 1 + .../src/test_typing_extensions.py | 76 ++++++++++++++++++- typing_extensions/src/typing_extensions.py | 51 +++++++++++++ 4 files changed, 127 insertions(+), 2 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 092e04a86..b874ddc84 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,6 @@ # Release 4.x.x +- Runtime support for PEP 675 and `typing_extensions.LiteralString`. - Add `Never` and `assert_never`. Backport from bpo-46475. - `ParamSpec` args and kwargs are now equal to themselves. Backport from bpo-46676. Patch by Gregory Beauregard (@GBeauregard). diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index a83ed3c82..4430d6698 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -37,6 +37,7 @@ This module currently contains the following: - Experimental features + - ``LiteralString`` (see PEP 675) - ``@dataclass_transform()`` (see PEP 681) - ``NotRequired`` (see PEP 655) - ``Required`` (see PEP 655) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 911168810..c4db70eb9 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -22,7 +22,7 @@ from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict -from typing_extensions import dataclass_transform, reveal_type, Never, assert_never +from typing_extensions import dataclass_transform, reveal_type, Never, assert_never, LiteralString try: from typing_extensions import get_type_hints except ImportError: @@ -111,6 +111,11 @@ def test_cannot_instantiate(self): with self.assertRaises(TypeError): type(self.bottom_type)() + def test_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL): + pickled = pickle.dumps(self.bottom_type, protocol=proto) + self.assertIs(self.bottom_type, pickle.loads(pickled)) + class NoReturnTests(BottomTypeTestsMixin, BaseTestCase): bottom_type = NoReturn @@ -1896,7 +1901,8 @@ def test_cannot_check_subclass(self): def test_pickle(self): samples = [typing.Any, typing.Union[int, str], typing.Optional[str], Tuple[int, ...], - typing.Callable[[str], bytes]] + typing.Callable[[str], bytes], + Self, LiteralString, Never] for t in samples: x = Annotated[t, "a"] @@ -2290,6 +2296,67 @@ def test_no_isinstance(self): issubclass(int, TypeGuard) +class LiteralStringTests(BaseTestCase): + def test_basics(self): + class Foo: + def bar(self) -> LiteralString: ... + def baz(self) -> "LiteralString": ... + + self.assertEqual(gth(Foo.bar), {'return': LiteralString}) + self.assertEqual(gth(Foo.baz), {'return': LiteralString}) + + @skipUnless(PEP_560, "Python 3.7+ required") + def test_get_origin(self): + from typing_extensions import get_origin + self.assertIsNone(get_origin(LiteralString)) + + def test_repr(self): + if hasattr(typing, 'LiteralString'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(LiteralString), '{}.LiteralString'.format(mod_name)) + + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + LiteralString[int] + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(LiteralString)): + pass + with self.assertRaises(TypeError): + class C(LiteralString): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + LiteralString() + with self.assertRaises(TypeError): + type(LiteralString)() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, LiteralString) + with self.assertRaises(TypeError): + issubclass(int, LiteralString) + + def test_alias(self): + StringTuple = Tuple[LiteralString, LiteralString] + class Alias: + def return_tuple(self) -> StringTuple: + return ("foo", "pep" + "675") + + def test_typevar(self): + StrT = TypeVar("StrT", bound=LiteralString) + self.assertIs(StrT.__bound__, LiteralString) + + def test_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL): + pickled = pickle.dumps(LiteralString, protocol=proto) + self.assertIs(LiteralString, pickle.loads(pickled)) + + class SelfTests(BaseTestCase): def test_basics(self): class Foo: @@ -2331,6 +2398,11 @@ class Alias: def return_tuple(self) -> TupleSelf: return (self, self) + def test_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL): + pickled = pickle.dumps(Self, protocol=proto) + self.assertIs(Self, pickle.loads(pickled)) + class FinalDecoratorTests(BaseTestCase): def test_final_unmodified(self): diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index b67efd0b1..d0bcc3248 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -44,6 +44,7 @@ def _check_generic(cls, parameters): 'ClassVar', 'Concatenate', 'Final', + 'LiteralString', 'ParamSpec', 'Self', 'Type', @@ -2155,6 +2156,56 @@ def __getitem__(self, parameters): return self._getitem(self, parameters) +if hasattr(typing, "LiteralString"): + LiteralString = typing.LiteralString +elif sys.version_info[:2] >= (3, 7): + @_SpecialForm + def LiteralString(self, params): + """Represents an arbitrary literal string. + + Example:: + + from typing_extensions import LiteralString + + def query(sql: LiteralString) -> ...: + ... + + query("SELECT * FROM table") # ok + query(f"SELECT * FROM {input()}") # not ok + + See PEP 675 for details. + + """ + raise TypeError(f"{self} is not subscriptable") +else: + class _LiteralString(typing._FinalTypingBase, _root=True): + """Represents an arbitrary literal string. + + Example:: + + from typing_extensions import LiteralString + + def query(sql: LiteralString) -> ...: + ... + + query("SELECT * FROM table") # ok + query(f"SELECT * FROM {input()}") # not ok + + See PEP 675 for details. + + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass().") + + LiteralString = _LiteralString(_root=True) + + if hasattr(typing, "Self"): Self = typing.Self elif sys.version_info[:2] >= (3, 7): From 773f7595ab2f1d683a0ab211ca083cbc24a7af21 Mon Sep 17 00:00:00 2001 From: David C <47948262+d-k-bo@users.noreply.github.com> Date: Fri, 11 Feb 2022 16:45:05 +0100 Subject: [PATCH 097/539] PEP 655 Add interaction with __required_keys__, __optional_keys__ and get_type_hints() (#1057) * PEP 655 Add interaction w/ required/optional keys Change TypedDict to respect keys that are marked as Required or NotRequired (requires PEP 560). Make TypedDict and is_typeddict accessible if typing doesn't implement Required. * PEP 655 Add interaction with get_type_hints() Replace _strip_annotations() with _strip_extras() to strip Annotated, Required and NotRequired. Change get_type_hints() to pass include_extras=True to newer versions of typing.get_type_hints() and use _strip_extras(). Make get_type_hints accessible if typing doesn't implement Required. --- typing_extensions/CHANGELOG | 2 + .../src/test_typing_extensions.py | 39 ++++- typing_extensions/src/typing_extensions.py | 161 +++++++++++------- 3 files changed, 139 insertions(+), 63 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index b874ddc84..9178d9673 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,7 @@ # Release 4.x.x +- Add interaction of `Required` and `NotRequired` with `__required_keys__`, + `__optional_keys__` and `get_type_hints()`. Patch by David Cabot (@d-k-bo). - Runtime support for PEP 675 and `typing_extensions.LiteralString`. - Add `Never` and `assert_never`. Backport from bpo-46475. - `ParamSpec` args and kwargs are now equal to themselves. Backport from diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index c4db70eb9..68ba31fcc 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -543,6 +543,18 @@ class Animal(BaseAnimal, total=False): class Cat(Animal): fur_color: str +class TotalMovie(TypedDict): + title: str + year: NotRequired[int] + +class NontotalMovie(TypedDict, total=False): + title: Required[str] + year: int + +class AnnotatedMovie(TypedDict): + title: Annotated[Required[str], "foobar"] + year: NotRequired[Annotated[int, 2000]] + gth = get_type_hints @@ -1651,7 +1663,7 @@ def test_typeddict_create_errors(self): def test_typeddict_errors(self): Emp = TypedDict('Emp', {'name': str, 'id': int}) - if sys.version_info >= (3, 9, 2): + if hasattr(typing, "Required"): self.assertEqual(TypedDict.__module__, 'typing') else: self.assertEqual(TypedDict.__module__, 'typing_extensions') @@ -1719,6 +1731,15 @@ def test_optional_keys(self): assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y']) assert Point2Dor3D.__optional_keys__ == frozenset(['z']) + @skipUnless(PEP_560, "runtime support for Required and NotRequired requires PEP 560") + def test_required_notrequired_keys(self): + assert NontotalMovie.__required_keys__ == frozenset({'title'}) + assert NontotalMovie.__optional_keys__ == frozenset({'year'}) + + assert TotalMovie.__required_keys__ == frozenset({'title'}) + assert TotalMovie.__optional_keys__ == frozenset({'year'}) + + def test_keys_inheritance(self): assert BaseAnimal.__required_keys__ == frozenset(['name']) assert BaseAnimal.__optional_keys__ == frozenset([]) @@ -2023,6 +2044,19 @@ def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]": {'other': MySet[T], 'return': MySet[T]} ) + def test_get_type_hints_typeddict(self): + assert get_type_hints(TotalMovie) == {'title': str, 'year': int} + assert get_type_hints(TotalMovie, include_extras=True) == { + 'title': str, + 'year': NotRequired[int], + } + + assert get_type_hints(AnnotatedMovie) == {'title': str, 'year': int} + assert get_type_hints(AnnotatedMovie, include_extras=True) == { + 'title': Annotated[Required[str], "foobar"], + 'year': NotRequired[Annotated[int, 2000]], + } + class TypeAliasTests(BaseTestCase): def test_canonical_usage_with_variable_annotation(self): @@ -2606,7 +2640,8 @@ def test_typing_extensions_defers_when_possible(self): 'TypedDict', 'TYPE_CHECKING', 'Final', - 'get_type_hints' + 'get_type_hints', + 'is_typeddict', } if sys.version_info < (3, 10): exclude |= {'get_args', 'get_origin'} diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index d0bcc3248..ba80cdcc9 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -991,13 +991,16 @@ def __index__(self) -> int: pass -if sys.version_info >= (3, 9, 2): +if hasattr(typing, "Required"): # The standard library TypedDict in Python 3.8 does not store runtime information # about which (if any) keys are optional. See https://bugs.python.org/issue38834 # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 + # The standard library TypedDict below Python 3.11 does not store runtime + # information about optional and required keys when using Required or NotRequired. TypedDict = typing.TypedDict _TypedDictMeta = typing._TypedDictMeta + is_typeddict = typing.is_typeddict else: def _check_fails(cls, other): try: @@ -1081,7 +1084,6 @@ def __new__(cls, name, bases, ns, total=True): annotations = {} own_annotations = ns.get('__annotations__', {}) - own_annotation_keys = set(own_annotations.keys()) msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" own_annotations = { n: typing._type_check(tp, msg) for n, tp in own_annotations.items() @@ -1095,10 +1097,29 @@ def __new__(cls, name, bases, ns, total=True): optional_keys.update(base.__dict__.get('__optional_keys__', ())) annotations.update(own_annotations) - if total: - required_keys.update(own_annotation_keys) + if PEP_560: + for annotation_key, annotation_type in own_annotations.items(): + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is Required: + required_keys.add(annotation_key) + elif annotation_origin is NotRequired: + optional_keys.add(annotation_key) + elif total: + required_keys.add(annotation_key) + else: + optional_keys.add(annotation_key) else: - optional_keys.update(own_annotation_keys) + own_annotation_keys = set(own_annotations.keys()) + if total: + required_keys.update(own_annotation_keys) + else: + optional_keys.update(own_annotation_keys) tp_dict.__annotations__ = annotations tp_dict.__required_keys__ = frozenset(required_keys) @@ -1141,10 +1162,6 @@ class Point2D(TypedDict): syntax forms work for Python 2.7 and 3.2+ """ - -if hasattr(typing, "is_typeddict"): - is_typeddict = typing.is_typeddict -else: if hasattr(typing, "_TypedDictMeta"): _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta) else: @@ -1163,11 +1180,83 @@ class Film(TypedDict): """ return isinstance(tp, tuple(_TYPEDDICT_TYPES)) +if hasattr(typing, "Required"): + get_type_hints = typing.get_type_hints +elif PEP_560: + import functools + import types -# Python 3.9+ has PEP 593 (Annotated and modified get_type_hints) + # replaces _strip_annotations() + def _strip_extras(t): + """Strips Annotated, Required and NotRequired from a given type.""" + if isinstance(t, _AnnotatedAlias): + return _strip_extras(t.__origin__) + if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired): + return _strip_extras(t.__args__[0]) + if isinstance(t, typing._GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return t.copy_with(stripped_args) + if hasattr(types, "GenericAlias") and isinstance(t, types.GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return types.GenericAlias(t.__origin__, stripped_args) + if hasattr(types, "UnionType") and isinstance(t, types.UnionType): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return functools.reduce(operator.or_, stripped_args) + + return t + + def get_type_hints(obj, globalns=None, localns=None, include_extras=False): + """Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, adds Optional[t] if a + default value equal to None is set and recursively replaces all + 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T' + (unless 'include_extras=True'). + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + if hasattr(typing, "Annotated"): + hint = typing.get_type_hints( + obj, globalns=globalns, localns=localns, include_extras=True + ) + else: + hint = typing.get_type_hints(obj, globalns=globalns, localns=localns) + if include_extras: + return hint + return {k: _strip_extras(t) for k, t in hint.items()} + + +# Python 3.9+ has PEP 593 (Annotated) if hasattr(typing, 'Annotated'): Annotated = typing.Annotated - get_type_hints = typing.get_type_hints # Not exported and not a public API, but needed for get_origin() and get_args() # to work. _AnnotatedAlias = typing._AnnotatedAlias @@ -1269,56 +1358,6 @@ def __init_subclass__(cls, *args, **kwargs): raise TypeError( f"Cannot subclass {cls.__module__}.Annotated" ) - - def _strip_annotations(t): - """Strips the annotations from a given type. - """ - if isinstance(t, _AnnotatedAlias): - return _strip_annotations(t.__origin__) - if isinstance(t, typing._GenericAlias): - stripped_args = tuple(_strip_annotations(a) for a in t.__args__) - if stripped_args == t.__args__: - return t - res = t.copy_with(stripped_args) - res._special = t._special - return res - return t - - def get_type_hints(obj, globalns=None, localns=None, include_extras=False): - """Return type hints for an object. - - This is often the same as obj.__annotations__, but it handles - forward references encoded as string literals, adds Optional[t] if a - default value equal to None is set and recursively replaces all - 'Annotated[T, ...]' with 'T' (unless 'include_extras=True'). - - The argument may be a module, class, method, or function. The annotations - are returned as a dictionary. For classes, annotations include also - inherited members. - - TypeError is raised if the argument is not of a type that can contain - annotations, and an empty dictionary is returned if no annotations are - present. - - BEWARE -- the behavior of globalns and localns is counterintuitive - (unless you are familiar with how eval() and exec() work). The - search order is locals first, then globals. - - - If no dict arguments are passed, an attempt is made to use the - globals from obj (or the respective module's globals for classes), - and these are also used as the locals. If the object does not appear - to have globals, an empty dictionary is used. - - - If one dict argument is passed, it is used for both globals and - locals. - - - If two dict arguments are passed, they specify globals and - locals, respectively. - """ - hint = typing.get_type_hints(obj, globalns=globalns, localns=localns) - if include_extras: - return hint - return {k: _strip_annotations(t) for k, t in hint.items()} # 3.6 else: From 77e79377a51e8cbb489daa9a7dca2b425aa62451 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 11 Feb 2022 16:33:36 +0000 Subject: [PATCH 098/539] Update stubs.rst (#1069) It's no longer necessary to use `# type: ignore`s for many uses of `ParamSpec` (hooray!) --- docs/source/stubs.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index a7ab6dfed..161d679db 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -315,14 +315,15 @@ But:: def create_it(cls: _T) -> _T: ... # cls has type _T PEP 612 [#pep612]_ parameter specification variables (``ParamSpec``) -are supported in argument and return types, although -they need to be marked with ``# type: ignore`` to work with all -type checkers [#ts-4827]_:: +are supported in argument and return types:: _P = ParamSpec("_P") - _T = TypeVar("_T") + _R = TypeVar("_R") + + def foo(cb: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> _R: ... - def foo(cb: Callable[_P, _T]) -> Callable[_P, _T]: ... # type: ignore +However, ``Concatenate`` from PEP 612 is not yet supported; nor is using +a ``ParamSpec`` to parameterize a generic class. PEP 647 [#pep647]_ type guards are supported. @@ -1102,7 +1103,6 @@ Bugs .. [#ts-4819] typeshed issue #4819 -- PEP 604 tracker (https://github.com/python/typeshed/issues/4819) .. [#ts-4820] typeshed issue #4820 -- PEP 585 tracker (https://github.com/python/typeshed/issues/4820) -.. [#ts-4827] typeshed issue #4827 -- PEP 612 tracker (https://github.com/python/typeshed/issues/4827) .. [#ts-4913] typeshed issue #4913 -- PEP 613 tracker (https://github.com/python/typeshed/issues/4913) .. [#ts-4972] typeshed issue #4972 -- PEP 570 tracker (https://github.com/python/typeshed/issues/4972) From 46094aa9a7cef3faf9344095db1feed31c77d176 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 11 Feb 2022 09:20:42 -0800 Subject: [PATCH 099/539] Add guide to unreachable code (#1065) --- docs/source/guides.rst | 1 + docs/source/unreachable.rst | 148 ++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 docs/source/unreachable.rst diff --git a/docs/source/guides.rst b/docs/source/guides.rst index 9d435b798..c9af381c9 100644 --- a/docs/source/guides.rst +++ b/docs/source/guides.rst @@ -7,3 +7,4 @@ Type System Guides :caption: Contents: libraries + unreachable diff --git a/docs/source/unreachable.rst b/docs/source/unreachable.rst new file mode 100644 index 000000000..16c37d8f4 --- /dev/null +++ b/docs/source/unreachable.rst @@ -0,0 +1,148 @@ +.. _unreachable: + +******************************************** +Unreachable Code and Exhaustiveness Checking +******************************************** + +Sometimes it is necessary to write code that should never execute, and +sometimes we write code that we expect to execute, but that is actually +unreachable. The type checker can help in both cases. + +In this guide, we'll cover: + +- ``Never``, the primitive type used for unreachable code +- ``assert_never()``, a helper for exhaustiveness checking +- Directly marking code as unreachable +- Detecting unexpectedly unreachable code + +``Never`` and ``NoReturn`` +========================== + +Type theory has a concept of a +`bottom type `__, +a type that has no values. Concretely, this can be used to represent +the return type of a function that never returns, or the argument type +of a function that may never be called. You can also think of the +bottom type as a union with no members. + +The Python type system has long provided a type called ``NoReturn``. +While it was originally meant only for functions that never return, +this concept is naturally extended to the bottom type in general, and all +type checkers treat ``NoReturn`` as a general bottom type. + +To make the meaning of this type more explicit, Python 3.11 and +typing-extensions 4.1 add a new primitive, ``Never``. To type checkers, +it has the same meaning as ``NoReturn``. + +In this guide, we'll use ``Never`` for the bottom type, but if you cannot +use it yet, you can always use ``typing.NoReturn`` instead. + +``assert_never()`` and Exhaustiveness Checking +============================================== + +The ``Never`` type can be leveraged to perform static exhaustiveness checking, +where we use the type checker to make sure that we covered all possible +cases. For example, this can come up when code performs a separate action +for each member of an enum, or for each type in a union. + +To have the type checker do exhaustiveness checking for us, we call a +function with a parameter typed as ``Never``. The type checker will allow +this call only if it can prove that the code is not reachable. + +As an example, consider this simple calculator: + +.. code:: python + + import enum + from typing_extensions import Never + + def assert_never(arg: Never) -> Never: + raise AssertionError("Expected code to be unreachable") + + class Op(enum.Enum): + ADD = 1 + SUBTRACT = 2 + + def calculate(left: int, op: Op, right: int) -> int: + match op: + case Op.ADD: + return left + right + case Op.SUBTRACT: + return left - right + case _: + assert_never(op) + +The ``match`` statement covers all members of the ``Op`` enum, +so the ``assert_never()`` call is unreachable and the type checker +will accept this code. However, if you add another member to the +enum (say, ``MULTIPLY``) but don't update the ``match`` statement, +the type checker will give an error saying that you are not handling +the ``MULTIPLY`` case. + +Because the ``assert_never()`` helper function is frequently useful, +it is provided by the standard library as ``typing.assert_never`` +starting in Python 3.11, +and is also present in ``typing_extensions`` starting at version 4.1. +However, it is also possible to define a similar function in your own +code, for example if you want to customize the runtime error message. + +You can also use ``assert_never()`` with a sequence of ``if`` statements: + +.. code:: python + + def calculate(left: int, op: Op, right: int) -> int: + if op is Op.ADD: + return left + right + elif op is Op.SUBTRACT: + return left - right + else: + assert_never(op) + +Marking Code as Unreachable +======================= + +Sometimes a piece of code is unreachable, but the type system is not +powerful enough to recognize that. For example, consider a function that +finds the lowest unused street number in a street: + +.. code:: python + + import itertools + + def is_used(street: str, number: int) -> bool: + ... + + def lowest_unused(street: str) -> int: + for i in itertools.count(1): + if not is_used(street, i): + return i + assert False, "unreachable" + +Because ``itertools.count()`` is an infinite iterator, this function +will never reach the ``assert False`` statement. However, there is +no way for the type checker to know that, so without the ``assert False``, +the type checker will complain that the function is missing a return +statement. + +Note how this is different from ``assert_never()``: + +- If we used ``assert_never()`` in the ``lowest_unused()`` function, + the type checker would produce an error, because the type checker + cannot prove that the line is unreachable. +- If we used ``assert False`` instead of ``assert_never()`` in the + ``calculate()`` example above, we would not get the benefits of + exhaustiveness checking. If the code is actually reachable, + the type checker will not warn us and we could hit the assertion + at runtime. + +While ``assert False`` is the most idiomatic way to express this pattern, +any statement that ends execution will do. For example, you could raise +an exception or call a function that returns ``Never``. + +Detecting Unexpectedly Unreachable Code +======================================= + +Another possible problem is code that is supposed to execute, but that +can actually be statically determined to be unreachable. +Some type checkers have an option that enables warnings for code +detected as unreachable (e.g., ``--warn-unreachable`` in mypy). From 9403ccf18c826d2028841bea77ea66c3f6045f7b Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 11 Feb 2022 17:04:50 -0800 Subject: [PATCH 100/539] PEP 646 implementation (#963) --- typing_extensions/CHANGELOG | 2 + typing_extensions/README.rst | 14 +- .../src/test_typing_extensions.py | 147 +++++++- typing_extensions/src/typing_extensions.py | 314 ++++++++++++++++-- 4 files changed, 448 insertions(+), 29 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 9178d9673..363d63c1e 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,7 @@ # Release 4.x.x +- Runtime support for PEP 646, adding `typing_extensions.TypeVarTuple` + and `typing_extensions.Unpack`. - Add interaction of `Required` and `NotRequired` with `__required_keys__`, `__optional_keys__` and `get_type_hints()`. Patch by David Cabot (@d-k-bo). - Runtime support for PEP 675 and `typing_extensions.LiteralString`. diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 4430d6698..5db69d150 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -48,6 +48,8 @@ This module currently contains the following: - ``Never`` - ``reveal_type`` - ``Self`` (see PEP 673) + - ``TypeVarTuple`` (see PEP 646) + - ``Unpack`` (see PEP 646) - In ``typing`` since Python 3.10 @@ -124,9 +126,15 @@ These changes are _not_ backported to prevent subtle compatibility issues when mixing the differing implementations of modified classes. Certain types have incorrect runtime behavior due to limitations of older -versions of the typing module. For example, ``ParamSpec`` and ``Concatenate`` -will not work with ``get_args``, ``get_origin``. Certain PEP 612 special cases -in user-defined ``Generic``\ s are also not available. +versions of the typing module: + +- ``ParamSpec`` and ``Concatenate`` will not work with ``get_args`` and + ``get_origin``. Certain PEP 612 special cases in user-defined + ``Generic``\ s are also not available. +- ``Unpack`` from PEP 646 does not work properly with user-defined + ``Generic``\ s in Python 3.6: ``class X(Generic[Unpack[Ts]]):`` does + not work. + These types are only guaranteed to work for static type checking. Running tests diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 68ba31fcc..62a9fd7ab 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -22,7 +22,7 @@ from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict -from typing_extensions import dataclass_transform, reveal_type, Never, assert_never, LiteralString +from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString try: from typing_extensions import get_type_hints except ImportError: @@ -622,6 +622,7 @@ def test_get_origin(self): T = TypeVar('T') P = ParamSpec('P') + Ts = TypeVarTuple('Ts') class C(Generic[T]): pass self.assertIs(get_origin(C[int]), C) self.assertIs(get_origin(C[T]), C) @@ -642,11 +643,16 @@ class C(Generic[T]): pass self.assertIs(get_origin(list), None) self.assertIs(get_origin(P.args), P) self.assertIs(get_origin(P.kwargs), P) + self.assertIs(get_origin(Required[int]), Required) + self.assertIs(get_origin(NotRequired[int]), NotRequired) + self.assertIs(get_origin(Unpack[Ts]), Unpack) + self.assertIs(get_origin(Unpack), None) def test_get_args(self): from typing_extensions import get_args T = TypeVar('T') + Ts = TypeVarTuple('Ts') class C(Generic[T]): pass self.assertEqual(get_args(C[int]), (int,)) self.assertEqual(get_args(C[T]), (T,)) @@ -687,6 +693,10 @@ class C(Generic[T]): pass self.assertIn(get_args(Callable[P, int]), [(P, int), ([P], int)]) self.assertEqual(get_args(Callable[Concatenate[int, P], int]), (Concatenate[int, P], int)) + self.assertEqual(get_args(Required[int]), (int,)) + self.assertEqual(get_args(NotRequired[int]), (int,)) + self.assertEqual(get_args(Unpack[Ts]), (Ts,)) + self.assertEqual(get_args(Unpack), ()) class CollectionsAbcTests(BaseTestCase): @@ -2438,6 +2448,141 @@ def test_pickle(self): self.assertIs(Self, pickle.loads(pickled)) +class UnpackTests(BaseTestCase): + def test_basic_plain(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(Unpack[Ts], Unpack[Ts]) + with self.assertRaises(TypeError): + Unpack() + + def test_repr(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]') + + def test_cannot_subclass_vars(self): + with self.assertRaises(TypeError): + class V(Unpack[TypeVarTuple('Ts')]): + pass + + def test_tuple(self): + Ts = TypeVarTuple('Ts') + Tuple[Unpack[Ts]] + + def test_union(self): + Xs = TypeVarTuple('Xs') + Ys = TypeVarTuple('Ys') + self.assertEqual( + Union[Unpack[Xs]], + Unpack[Xs] + ) + self.assertNotEqual( + Union[Unpack[Xs]], + Union[Unpack[Xs], Unpack[Ys]] + ) + self.assertEqual( + Union[Unpack[Xs], Unpack[Xs]], + Unpack[Xs] + ) + self.assertNotEqual( + Union[Unpack[Xs], int], + Union[Unpack[Xs]] + ) + self.assertNotEqual( + Union[Unpack[Xs], int], + Union[int] + ) + self.assertEqual( + Union[Unpack[Xs], int].__args__, + (Unpack[Xs], int) + ) + self.assertEqual( + Union[Unpack[Xs], int].__parameters__, + (Xs,) + ) + self.assertIs( + Union[Unpack[Xs], int].__origin__, + Union + ) + + @skipUnless(PEP_560, "Unimplemented for 3.6") + def test_concatenation(self): + Xs = TypeVarTuple('Xs') + self.assertEqual(Tuple[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) + self.assertEqual(Tuple[Unpack[Xs], int].__args__, (Unpack[Xs], int)) + self.assertEqual(Tuple[int, Unpack[Xs], str].__args__, + (int, Unpack[Xs], str)) + class C(Generic[Unpack[Xs]]): pass + self.assertEqual(C[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) + self.assertEqual(C[Unpack[Xs], int].__args__, (Unpack[Xs], int)) + self.assertEqual(C[int, Unpack[Xs], str].__args__, + (int, Unpack[Xs], str)) + + @skipUnless(PEP_560, "Unimplemented for 3.6") + def test_class(self): + Ts = TypeVarTuple('Ts') + + class C(Generic[Unpack[Ts]]): pass + self.assertEqual(C[int].__args__, (int,)) + self.assertEqual(C[int, str].__args__, (int, str)) + + with self.assertRaises(TypeError): + class C(Generic[Unpack[Ts], int]): pass + + T1 = TypeVar('T') + T2 = TypeVar('T') + class C(Generic[T1, T2, Unpack[Ts]]): pass + self.assertEqual(C[int, str].__args__, (int, str)) + self.assertEqual(C[int, str, float].__args__, (int, str, float)) + self.assertEqual(C[int, str, float, bool].__args__, (int, str, float, bool)) + with self.assertRaises(TypeError): + C[int] + + +class TypeVarTupleTests(BaseTestCase): + + def test_basic_plain(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(Ts, Ts) + self.assertIsInstance(Ts, TypeVarTuple) + Xs = TypeVarTuple('Xs') + Ys = TypeVarTuple('Ys') + self.assertNotEqual(Xs, Ys) + + def test_repr(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(repr(Ts), 'Ts') + + def test_no_redefinition(self): + self.assertNotEqual(TypeVarTuple('Ts'), TypeVarTuple('Ts')) + + def test_cannot_subclass_vars(self): + with self.assertRaises(TypeError): + class V(TypeVarTuple('Ts')): + pass + + def test_cannot_subclass_var_itself(self): + with self.assertRaises(TypeError): + class V(TypeVarTuple): + pass + + def test_cannot_instantiate_vars(self): + Ts = TypeVarTuple('Ts') + with self.assertRaises(TypeError): + Ts() + + def test_tuple(self): + Ts = TypeVarTuple('Ts') + # Not legal at type checking time but we can't really check against it. + Tuple[Ts] + + def test_args_and_parameters(self): + Ts = TypeVarTuple('Ts') + + t = Tuple[tuple(Ts)] + self.assertEqual(t.__args__, (Ts.__unpacked__,)) + self.assertEqual(t.__parameters__, (Ts,)) + + class FinalDecoratorTests(BaseTestCase): def test_final_unmodified(self): def func(x): ... diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index ba80cdcc9..144bca76e 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -3,6 +3,7 @@ import collections.abc import operator import sys +import types as _types import typing # After PEP 560, internal typing API was substantially reworked. @@ -16,27 +17,6 @@ # 3.6 from typing import GenericMeta, _type_vars # noqa -# The two functions below are copies of typing internal helpers. -# They are needed by _ProtocolMeta - - -def _no_slots_copy(dct): - dict_copy = dict(dct) - if '__slots__' in dict_copy: - for slot in dict_copy['__slots__']: - dict_copy.pop(slot, None) - return dict_copy - - -def _check_generic(cls, parameters): - if not cls.__parameters__: - raise TypeError(f"{cls} is not a generic class") - alen = len(parameters) - elen = len(cls.__parameters__) - if alen != elen: - raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments for {cls};" - f" actual {alen}, expected {elen}") - # Please keep __all__ alphabetized within each category. __all__ = [ @@ -48,6 +28,8 @@ def _check_generic(cls, parameters): 'ParamSpec', 'Self', 'Type', + 'TypeVarTuple', + 'Unpack', # ABCs (from collections.abc). 'Awaitable', @@ -96,6 +78,88 @@ def _check_generic(cls, parameters): if PEP_560: __all__.extend(["get_args", "get_origin", "get_type_hints"]) +# The functions below are modified copies of typing internal helpers. +# They are needed by _ProtocolMeta and they provide support for PEP 646. + + +def _no_slots_copy(dct): + dict_copy = dict(dct) + if '__slots__' in dict_copy: + for slot in dict_copy['__slots__']: + dict_copy.pop(slot, None) + return dict_copy + + +_marker = object() + + +def _check_generic(cls, parameters, elen=_marker): + """Check correct count for parameters of a generic cls (internal helper). + This gives a nice error message in case of count mismatch. + """ + if not elen: + raise TypeError(f"{cls} is not a generic class") + if elen is _marker: + if not hasattr(cls, "__parameters__") or not cls.__parameters__: + raise TypeError(f"{cls} is not a generic class") + elen = len(cls.__parameters__) + alen = len(parameters) + if alen != elen: + if hasattr(cls, "__parameters__"): + parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] + num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters) + if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples): + return + raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};" + f" actual {alen}, expected {elen}") + + +if sys.version_info >= (3, 10): + def _should_collect_from_parameters(t): + return isinstance( + t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType) + ) +elif sys.version_info >= (3, 9): + def _should_collect_from_parameters(t): + return isinstance(t, (typing._GenericAlias, _types.GenericAlias)) +else: + def _should_collect_from_parameters(t): + return isinstance(t, typing._GenericAlias) and not t._special + + +def _collect_type_vars(types, typevar_types=None): + """Collect all type variable contained in types in order of + first appearance (lexicographic order). For example:: + + _collect_type_vars((T, List[S, T])) == (T, S) + """ + if typevar_types is None: + typevar_types = typing.TypeVar + tvars = [] + for t in types: + if ( + isinstance(t, typevar_types) and + t not in tvars and + not isinstance(t, _UnpackAlias) + ): + tvars.append(t) + if _should_collect_from_parameters(t): + tvars.extend([t for t in t.__parameters__ if t not in tvars]) + return tuple(tvars) + + +# We have to do some monkey patching to deal with the dual nature of +# Unpack/TypeVarTuple: +# - We want Unpack to be a kind of TypeVar so it gets accepted in +# Generic[Unpack[Ts]] +# - We want it to *not* be treated as a TypeVar for the purposes of +# counting generic parameters, so that when we subscript a generic, +# the runtime doesn't try to substitute the Unpack with the subscripted type. +if not hasattr(typing, "TypeVarTuple"): + typing._collect_type_vars = _collect_type_vars + typing._check_generic = _check_generic + + # 3.6.2+ if hasattr(typing, 'NoReturn'): NoReturn = typing.NoReturn @@ -531,7 +595,6 @@ def _is_callable_members_only(cls): Protocol = typing.Protocol # 3.7 elif PEP_560: - from typing import _collect_type_vars # noqa def _no_init(self, *args, **kwargs): if type(self)._is_protocol: @@ -619,7 +682,7 @@ def __class_getitem__(cls, params): "Parameters to Protocol[...] must all be unique") else: # Subscripting a regular Generic subclass. - _check_generic(cls, params) + _check_generic(cls, params, len(cls.__parameters__)) return typing._GenericAlias(cls, params) def __init_subclass__(cls, *args, **kwargs): @@ -631,7 +694,7 @@ def __init_subclass__(cls, *args, **kwargs): if error: raise TypeError("Cannot inherit from plain Generic") if '__orig_bases__' in cls.__dict__: - tvars = _collect_type_vars(cls.__orig_bases__) + tvars = typing._collect_type_vars(cls.__orig_bases__) # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn]. # If found, tvars must be a subset of it. # If not found, tvars is it. @@ -900,7 +963,7 @@ def __getitem__(self, params): elif self.__origin__ in (typing.Generic, Protocol): raise TypeError(f"Cannot subscript already-subscripted {repr(self)}") else: - _check_generic(self, params) + _check_generic(self, params, len(self.__parameters__)) tvars = _type_vars(params) args = params @@ -2512,6 +2575,207 @@ class Movie(TypedDict): Required = _Required(_root=True) NotRequired = _NotRequired(_root=True) + +if sys.version_info[:2] >= (3, 9): + class _UnpackSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + class _UnpackAlias(typing._GenericAlias, _root=True): + __class__ = typing.TypeVar + + @_UnpackSpecialForm + def Unpack(self, parameters): + """A special typing construct to unpack a variadic type. For example: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + + def add_batch_axis( + x: Array[Unpack[Shape]] + ) -> Array[Batch, Unpack[Shape]]: ... + + """ + item = typing._type_check(parameters, f'{self._name} accepts only single type') + return _UnpackAlias(self, (item,)) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + +elif sys.version_info[:2] >= (3, 7): + class _UnpackAlias(typing._GenericAlias, _root=True): + __class__ = typing.TypeVar + + class _UnpackForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only single type') + return _UnpackAlias(self, (item,)) + + Unpack = _UnpackForm( + 'Unpack', + doc="""A special typing construct to unpack a variadic type. For example: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + + def add_batch_axis( + x: Array[Unpack[Shape]] + ) -> Array[Batch, Unpack[Shape]]: ... + + """) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + +else: + # NOTE: Modeled after _Final's implementation when _FinalTypingBase available + class _Unpack(typing._FinalTypingBase, _root=True): + """A special typing construct to unpack a variadic type. For example: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + + def add_batch_axis( + x: Array[Unpack[Shape]] + ) -> Array[Batch, Unpack[Shape]]: ... + + """ + __slots__ = ('__type__',) + __class__ = typing.TypeVar + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + 'Unpack accepts only single type.'), + _root=True) + raise TypeError('Unpack cannot be further subscripted') + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(typing._type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _Unpack): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + # For 3.6 only + def _get_type_vars(self, tvars): + self.__type__._get_type_vars(tvars) + + Unpack = _Unpack(_root=True) + + def _is_unpack(obj): + return isinstance(obj, _Unpack) + + +class TypeVarTuple: + """Type variable tuple. + + Usage:: + + Ts = TypeVarTuple('Ts') + + In the same way that a normal type variable is a stand-in for a single + type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* type such as + ``Tuple[int, str]``. + + Type variable tuples can be used in ``Generic`` declarations. + Consider the following example:: + + class Array(Generic[*Ts]): ... + + The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, + where ``T1`` and ``T2`` are type variables. To use these type variables + as type parameters of ``Array``, we must *unpack* the type variable tuple using + the star operator: ``*Ts``. The signature of ``Array`` then behaves + as if we had simply written ``class Array(Generic[T1, T2]): ...``. + In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows + us to parameterise the class with an *arbitrary* number of type parameters. + + Type variable tuples can be used anywhere a normal ``TypeVar`` can. + This includes class definitions, as shown above, as well as function + signatures and variable annotations:: + + class Array(Generic[*Ts]): + + def __init__(self, shape: Tuple[*Ts]): + self._shape: Tuple[*Ts] = shape + + def get_shape(self) -> Tuple[*Ts]: + return self._shape + + shape = (Height(480), Width(640)) + x: Array[Height, Width] = Array(shape) + y = abs(x) # Inferred type is Array[Height, Width] + z = x + x # ... is Array[Height, Width] + x.get_shape() # ... is tuple[Height, Width] + + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + def __iter__(self): + yield self.__unpacked__ + + def __init__(self, name): + self.__name__ = name + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + self.__unpacked__ = Unpack[self] + + def __repr__(self): + return self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + def __init_subclass__(self, *args, **kwds): + if '_root' not in kwds: + raise TypeError("Cannot subclass special typing classes") + + if not PEP_560: + # Only needed in 3.6. + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) + + if hasattr(typing, "reveal_type"): reveal_type = typing.reveal_type else: From e982c967c94943327252bccdba4436cd624b301a Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Sat, 12 Feb 2022 03:12:06 +0100 Subject: [PATCH 101/539] Built-in generic now work without limitations (#1068) Closes: #1067 --- docs/source/stubs.rst | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index 161d679db..8408c8050 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -174,18 +174,20 @@ Type checkers support cyclic imports in stub files. Built-in Generics ----------------- -PEP 585 [#pep585]_ built-in generics are generally supported, with -the following exceptions [#ts-4820]_: +PEP 585 [#pep585]_ built-in generics are supported and should be used instead +of the corresponding types from ``typing``:: -* Built-in generics don't work in type aliases. -* Built-in generics don't work in base classes. -* ``type`` is not supported. -* Variable length tuples (``tuple[X, ...]``) are not supported. + from collections import defaultdict -In these cases, the appropriate types from ``typing`` must be used. + def foo(t: type[MyClass]) -> list[int]: ... + x: defaultdict[int] Using imports from ``collections.abc`` instead of ``typing`` is -generally possible and recommended. +generally possible and recommended:: + + from collections.abc import Iterable + + def foo(iter: Iterable[int]) -> None: ... Unions ------ @@ -1102,7 +1104,6 @@ Bugs ---- .. [#ts-4819] typeshed issue #4819 -- PEP 604 tracker (https://github.com/python/typeshed/issues/4819) -.. [#ts-4820] typeshed issue #4820 -- PEP 585 tracker (https://github.com/python/typeshed/issues/4820) .. [#ts-4913] typeshed issue #4913 -- PEP 613 tracker (https://github.com/python/typeshed/issues/4913) .. [#ts-4972] typeshed issue #4972 -- PEP 570 tracker (https://github.com/python/typeshed/issues/4972) From 16cf672885bbf1dfb44914b9f0f50929380b7b41 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 12 Feb 2022 13:27:07 -0800 Subject: [PATCH 102/539] prepare release 4.1.0 (#1072) --- typing_extensions/CHANGELOG | 2 +- typing_extensions/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 363d63c1e..831f6d826 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,4 +1,4 @@ -# Release 4.x.x +# Release 4.1.0 (February 12, 2022) - Runtime support for PEP 646, adding `typing_extensions.TypeVarTuple` and `typing_extensions.Unpack`. diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 073444a8f..38678866d 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi" # Project metadata [project] name = "typing_extensions" -version = "4.0.1" +version = "4.1.0" description = "Backported and Experimental Type Hints for Python 3.6+" readme.text = """\ Typing Extensions -- Backported and Experimental Type Hints for Python From 83ed5bfb8e06033f1b1e92df454da6d0d26e1474 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 13 Feb 2022 19:10:06 -0800 Subject: [PATCH 103/539] Fix Python 3.7.1 and run more versions in CI (#1076) --- .github/workflows/ci.yml | 6 ++++- typing_extensions/CHANGELOG | 5 ++++ .../src/test_typing_extensions.py | 8 +++++- typing_extensions/src/typing_extensions.py | 26 +++++++++---------- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a89af4d5..f0e6d67b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,11 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11-dev"] + # We try to test on the earliest available bugfix release of each + # Python version, because typing sometimes changed between bugfix releases. + # For available versions, see: + # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json + python-version: ["3.6", "3.6.7", "3.7", "3.7.1", "3.8", "3.8.0", "3.9", "3.9.0", "3.10", "3.10.0", "3.11-dev"] runs-on: ubuntu-latest diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 831f6d826..d27af2913 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,3 +1,8 @@ +# Release 4.1.1 (February 13, 2022) + +- Fix importing `typing_extensions` on Python 3.7.0 and 3.7.1. Original + patch by Nikita Sobolev (@sobolevn). + # Release 4.1.0 (February 12, 2022) - Runtime support for PEP 646, adding `typing_extensions.TypeVarTuple` diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 62a9fd7ab..a66a2f291 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -131,7 +131,13 @@ def some(arg: NoReturn) -> NoReturn: ... def some_str(arg: 'NoReturn') -> 'typing.NoReturn': ... expected = {'arg': NoReturn, 'return': NoReturn} - for target in [some, some_str]: + targets = [some] + + # On 3.7.0 and 3.7.1, https://github.com/python/cpython/pull/10772 + # wasn't applied yet and NoReturn fails _type_check. + if not ((3, 7, 0) <= sys.version_info < (3, 7, 2)): + targets.append(some_str) + for target in targets: with self.subTest(target=target): self.assertEqual(gth(target), expected) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 144bca76e..194731cd3 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -140,7 +140,7 @@ def _collect_type_vars(types, typevar_types=None): if ( isinstance(t, typevar_types) and t not in tvars and - not isinstance(t, _UnpackAlias) + not _is_unpack(t) ): tvars.append(t) if _should_collect_from_parameters(t): @@ -148,18 +148,6 @@ def _collect_type_vars(types, typevar_types=None): return tuple(tvars) -# We have to do some monkey patching to deal with the dual nature of -# Unpack/TypeVarTuple: -# - We want Unpack to be a kind of TypeVar so it gets accepted in -# Generic[Unpack[Ts]] -# - We want it to *not* be treated as a TypeVar for the purposes of -# counting generic parameters, so that when we subscript a generic, -# the runtime doesn't try to substitute the Unpack with the subscripted type. -if not hasattr(typing, "TypeVarTuple"): - typing._collect_type_vars = _collect_type_vars - typing._check_generic = _check_generic - - # 3.6.2+ if hasattr(typing, 'NoReturn'): NoReturn = typing.NoReturn @@ -2906,3 +2894,15 @@ def decorator(cls_or_fn): } return cls_or_fn return decorator + + +# We have to do some monkey patching to deal with the dual nature of +# Unpack/TypeVarTuple: +# - We want Unpack to be a kind of TypeVar so it gets accepted in +# Generic[Unpack[Ts]] +# - We want it to *not* be treated as a TypeVar for the purposes of +# counting generic parameters, so that when we subscript a generic, +# the runtime doesn't try to substitute the Unpack with the subscripted type. +if not hasattr(typing, "TypeVarTuple"): + typing._collect_type_vars = _collect_type_vars + typing._check_generic = _check_generic From b5dadf11d324e6302d1ca8cbfbdc8c8112189b14 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 13 Feb 2022 19:15:26 -0800 Subject: [PATCH 104/539] Prepare release 4.1.1 (#1077) --- typing_extensions/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 38678866d..354c20682 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi" # Project metadata [project] name = "typing_extensions" -version = "4.1.0" +version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" readme.text = """\ Typing Extensions -- Backported and Experimental Type Hints for Python From 1cb25b85dc8ddfb818923531bbaf1d1978715daa Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 14 Feb 2022 10:07:31 -0800 Subject: [PATCH 105/539] Improve release instructions (#1078) This is what I've actually been doing, but better to document it. --- CONTRIBUTING.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f76b51df8..63c5034f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,11 +30,17 @@ backwards-incompatible changes. - Ensure that GitHub Actions reports no errors. -- Update the version number in `pyproject.toml`. +- Update the version number in `typing_extensions/pyproject.toml` and in + `typing_extensions/CHANGELOG`. + +- Make sure your environment is up to date + + - `git checkout master` + - `git pull` + - `python -m pip install --upgrade build twine` - Build the source and wheel distributions: - - `python -m pip install --upgrade build` - `cd typing_extensions` - `rm -rf dist/` - `python -m build .` @@ -42,4 +48,8 @@ backwards-incompatible changes. - Install the built distributions locally and test (if you were using `tox`, you already tested the source distribution). -- Make sure twine is up to date, then run `twine upload dist/*`. +- Run `twine upload dist/*`. + +- Tag the release. The tag should be just the version number, e.g. `4.1.1`. + +- `git push --tags` From 02d79c28bcfc1971b7f540de5779860389b5f21c Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Mon, 21 Feb 2022 13:22:23 -0500 Subject: [PATCH 106/539] Document how to test type annotations (#1071) This is a slightly broader refactor than just testing. It also consolidates information about checking type coverage/completeness. This originates from a thread on the mypy tracker [1]. In terms of presentation, the goal is to present guidance and offer up several options, many of which were proposed by contributors to that thread. Several of the goals from that thread were not achieved here, including documentation covering stubgen and monkeytype, stubtest, and potentially more. However, the document is written such that it should be possible to add a section on "Generating Annotations" as was planned earlier. [1]: https://github.com/python/mypy/issues/11506 --- docs/source/libraries.rst | 27 ----- docs/source/quality.rst | 200 ++++++++++++++++++++++++++++++++++++++ docs/source/reference.rst | 1 + 3 files changed, 201 insertions(+), 27 deletions(-) create mode 100644 docs/source/quality.rst diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 371c1f74f..96e5370e0 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -282,33 +282,6 @@ Examples of known and unknown types class DictSubclass(dict): pass -Verifying Type Completeness -=========================== - -Some type checkers provide features that allows library authors to verify type -completeness for a “py.typed” package. E.g. Pyright has a special -`command line flag `_ for this. - -Improving Type Completeness -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here are some tips for increasing the type completeness score for your -library: - -- If your package includes tests or sample code, consider removing them - from the distribution. If there is good reason to include them, - consider placing them in a directory that begins with an underscore - so they are not considered part of your library’s interface. -- If your package includes submodules that are meant to be - implementation details, rename those files to begin with an - underscore. -- If a symbol is not intended to be part of the library’s interface and - is considered an implementation detail, rename it such that it begins - with an underscore. It will then be considered private and excluded - from the type completeness check. -- If your package exposes types from other libraries, work with the - maintainers of these other libraries to achieve type completeness. - Best Practices for Inlined Types ================================ diff --git a/docs/source/quality.rst b/docs/source/quality.rst new file mode 100644 index 000000000..328550c90 --- /dev/null +++ b/docs/source/quality.rst @@ -0,0 +1,200 @@ +.. _tools: + +******************************************** +Testing and Ensuring Type Annotation Quality +******************************************** + +Testing Annotation Accuracy +=========================== + +When creating a package with type annotations, authors may want to validate +that the annotations they publish meet their expectations. +This is especially important for library authors, for whom the published +annotations are part of the public interface to their package. + +There are several approaches to this problem, and this document will show +a few of them. + +.. note:: + + For simplicity, we will assume that type-checking is done with ``mypy``. + Many of these strategies can be applied to other type-checkers as well. + +Testing Using ``mypy --warn-unused-ignores`` +-------------------------------------------- + +Clever use of ``--warn-unused-ignores`` can be used to check that certain +expressions are or are not well-typed. + +The idea is to write normal python files which contain valid expressions along +with invalid expressions annotated with ``type: ignore`` comments. When +``mypy --warn-unused-ignores`` is run on these files, it should pass. +A directory of test files, ``typing_tests/``, can be maintained. + +This strategy does not offer strong guarantees about the types under test, but +it requires no additional tooling. + +If the following file is under test + +.. code-block:: python + + # foo.py + def bar(x: int) -> str: + return str(x) + +Then the following file tests ``foo.py``: + +.. code-block:: python + + bar(42) + bar("42") # type: ignore [arg-type] + bar(y=42) # type: ignore [call-arg] + r1: str = bar(42) + r2: int = bar(42) # type: ignore [assignment] + +Checking ``reveal_type`` output from ``mypy.api.run`` +----------------------------------------------------- + +``mypy`` provides a subpackage named ``api`` for invoking ``mypy`` from a +python process. In combination with ``reveal_type``, this can be used to write +a function which gets the ``reveal_type`` output from an expression. Once +that's obtained, tests can assert strings and regular expression matches +against it. + +This approach requires writing a set of helpers to provide a good testing +experience, and it runs mypy once per test case (which can be slow). +However, it builds only on ``mypy`` and the test framework of your choice. + +The following example could be integrated into a testsuite written in +any framework: + +.. code-block:: python + + import re + from mypy import api + + def get_reveal_type_output(filename): + result = api.run([filename]) + stdout = result[0] + match = re.search(r'note: Revealed type is "([^"]+)"', stdout) + assert match is not None + return match.group(1) + + +For example, we can use the above to provide a ``run_reveal_type`` pytest +fixture which generates a temporary file and uses it as the input to +``get_reveal_type_output``: + +.. code-block:: python + + import os + import pytest + + @pytest.fixture + def _in_tmp_path(tmp_path): + cur = os.getcwd() + try: + os.chdir(tmp_path) + yield + finally: + os.chdir(cur) + + @pytest.fixture + def run_reveal_type(tmp_path, _in_tmp_path): + content_path = tmp_path / "reveal_type_test.py" + + def func(code_snippet, *, preamble = ""): + content_path.write_text(preamble + f"reveal_type({code_snippet})") + return get_reveal_type_output("reveal_type_test.py") + + return func + + +For more details, see `the documentation on mypy.api +`_. + +pytest-mypy-plugins +------------------- + +`pytest-mypy-plugins `_ is +a plugin for ``pytest`` which defines typing test cases as YAML data. +The test cases are run through ``mypy`` and the output of ``reveal_type`` can +be asserted. + +This project supports complex typing arrangements like ``pytest`` parametrized +tests and per-test ``mypy`` configuration. It requires that you are using +``pytest`` to run your tests, and runs ``mypy`` in a subprocess per test case. + +This is an example of a parametrized test with ``pytest-mypy-plugins``: + +.. code-block:: yaml + + - case: with_params + parametrized: + - val: 1 + rt: builtins.int + - val: 1.0 + rt: builtins.float + main: | + reveal_type({[ val }}) # N: Revealed type is '{{ rt }}' + +Improving Type Completeness +=========================== + +One of the goals of many libraries is to ensure that they are "fully type +annotated", meaning that they provide complete and accurate type annotations +for all functions, classes, and objects. Having full annotations is referred to +as "type completeness" or "type coverage". + +Here are some tips for increasing the type completeness score for your +library: + +- Make type completeness an output of your testing process. Several type + checkers have options for generating useful output, warnings, or even + reports. +- If your package includes tests or sample code, consider removing them + from the distribution. If there is good reason to include them, + consider placing them in a directory that begins with an underscore + so they are not considered part of your library’s interface. +- If your package includes submodules that are meant to be + implementation details, rename those files to begin with an + underscore. +- If a symbol is not intended to be part of the library’s interface and + is considered an implementation detail, rename it such that it begins + with an underscore. It will then be considered private and excluded + from the type completeness check. +- If your package exposes types from other libraries, work with the + maintainers of these other libraries to achieve type completeness. + +.. warning:: + + The ways in which different type checkers evaluate and help you achieve + better type coverage may differ. Some of the above recommendations may or + may not be helpful to you, depending on which type checking tools you use. + +``mypy`` disallow options +------------------------- + +``mypy`` offers several options which can detect untyped code. +More details can be found in `the mypy documentation on these options +`_. + +Some basic usages which make ``mypy`` error on untyped data are:: + + mypy --disallow-untyped-defs + mypy --disallow-incomplete-defs + +``pyright`` type verification +----------------------------- + +pyright has a special command line flag, ``--verifytypes``, for verifying +type completeness. You can learn more about it from +`the pyright documentation on verifying type completeness +`_. + +``mypy`` reports +---------------- + +``mypy`` offers several options options for generating reports on its analysis. +See `the mypy documentation on report generation +`_ for details. diff --git a/docs/source/reference.rst b/docs/source/reference.rst index d18df3aa1..c7fc57ee6 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -7,6 +7,7 @@ Type System Reference :caption: Contents: stubs + quality typing Module Documentation .. The following pages are desired in a new TOC which will cover multiple From 95c9c2bab2034f0359e8457360d93a288a65e55d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 3 Mar 2022 06:32:01 +0100 Subject: [PATCH 107/539] Improve distribution package (#1097) --- typing_extensions/MANIFEST.in | 3 --- typing_extensions/pyproject.toml | 23 +++++++++-------------- 2 files changed, 9 insertions(+), 17 deletions(-) delete mode 100644 typing_extensions/MANIFEST.in diff --git a/typing_extensions/MANIFEST.in b/typing_extensions/MANIFEST.in deleted file mode 100644 index e3e1b70c1..000000000 --- a/typing_extensions/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include CHANGELOG LICENSE README.rst -include src/typing_extensions.py -include src/test_typing_extensions.py diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 354c20682..fd6e1859a 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -8,20 +8,7 @@ build-backend = "flit_core.buildapi" name = "typing_extensions" version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" -readme.text = """\ -Typing Extensions -- Backported and Experimental Type Hints for Python - -The ``typing`` module was added to the standard library in Python 3.5, but -many new features have been added to the module since then. -This means users of older Python versions who are unable to upgrade will not be -able to take advantage of new types added to the ``typing`` module, such as -``typing.Protocol`` or ``typing.TypedDict``. - -The ``typing_extensions`` module contains backports of these changes. -Experimental types that may eventually be added to the ``typing`` -module are also included in ``typing_extensions``. -""" -readme.content-type = "text/x-rst" +readme = "README.rst" requires-python = ">=3.6" urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" license.file = "LICENSE" @@ -61,3 +48,11 @@ classifiers = [ [[project.authors]] name = "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" email = "levkivskyi@gmail.com" + +[tool.flit.sdist] +include = [ + "CHANGELOG", + "README.rst", + "*/test*.py" +] +exclude = [] From 0ca0e458324ed27f96d81265faf94595f57d84af Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 17 Mar 2022 01:09:58 -0700 Subject: [PATCH 108/539] typing-extensions: Drop Python 3.6 (#1104) --- .github/workflows/ci.yml | 2 +- CONTRIBUTING.md | 2 +- typing_extensions/CHANGELOG | 4 + typing_extensions/README.rst | 9 +- typing_extensions/pyproject.toml | 2 +- .../src/test_typing_extensions.py | 90 +- typing_extensions/src/typing_extensions.py | 1285 ++--------------- 7 files changed, 148 insertions(+), 1246 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0e6d67b3..302b2cae6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: # Python version, because typing sometimes changed between bugfix releases. # For available versions, see: # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json - python-version: ["3.6", "3.6.7", "3.7", "3.7.1", "3.8", "3.8.0", "3.9", "3.9.0", "3.10", "3.10.0", "3.11-dev"] + python-version: ["3.7", "3.7.1", "3.8", "3.8.0", "3.9", "3.9.0", "3.10", "3.10.0", "3.11-dev"] runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63c5034f4..095e8262b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ standard library, so that users can experiment with them before they are added t standard library. Such features should ideally already be specified in a PEP or draft PEP. -`typing_extensions` supports Python versions 3.6 and up. +`typing_extensions` supports Python versions 3.7 and up. # Versioning scheme diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index d27af2913..f098464eb 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,3 +1,7 @@ +# Unreleased + +- Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). + # Release 4.1.1 (February 13, 2022) - Fix importing `typing_extensions` on Python 3.7.0 and 3.7.1. Original diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 5db69d150..bb8906895 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -30,6 +30,10 @@ Therefore, it's safe to depend on ``typing_extensions`` like this: ``typing_extensions >=x.y, <(x+1)``, where ``x.y`` is the first version that includes all features you need. +``typing_extensions`` supports Python versions 3.7 and higher. In the future, +support for older Python versions will be dropped some time after that version +reaches end of life. + Included items ============== @@ -101,7 +105,7 @@ This module currently contains the following: - ``Text`` - ``Type`` - ``TYPE_CHECKING`` - - ``get_type_hints`` (``typing_extensions`` provides this function only in Python 3.7+) + - ``get_type_hints`` Other Notes and Limitations =========================== @@ -131,9 +135,6 @@ versions of the typing module: - ``ParamSpec`` and ``Concatenate`` will not work with ``get_args`` and ``get_origin``. Certain PEP 612 special cases in user-defined ``Generic``\ s are also not available. -- ``Unpack`` from PEP 646 does not work properly with user-defined - ``Generic``\ s in Python 3.6: ``class X(Generic[Unpack[Ts]]):`` does - not work. These types are only guaranteed to work for static type checking. diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index fd6e1859a..fbd018017 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -9,7 +9,7 @@ name = "typing_extensions" version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" readme = "README.rst" -requires-python = ">=3.6" +requires-python = ">=3.7" urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" license.file = "LICENSE" keywords = [ diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index a66a2f291..20e35f431 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -23,32 +23,14 @@ from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString -try: - from typing_extensions import get_type_hints -except ImportError: - from typing import get_type_hints - -PEP_560 = sys.version_info[:3] >= (3, 7, 0) - -OLD_GENERICS = False -try: - from typing import _type_vars, _next_in_mro, _type_check # noqa -except ImportError: - OLD_GENERICS = True +from typing_extensions import get_type_hints, get_origin, get_args # Flags used to mark tests that only apply after a specific # version of the typing module. -TYPING_3_6_1 = sys.version_info[:3] >= (3, 6, 1) TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0) TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) -# For typing versions where instantiating collection -# types are allowed. -# -# See https://github.com/python/typing/issues/367 -CAN_INSTANTIATE_COLLECTIONS = TYPING_3_6_1 - class BaseTestCase(TestCase): def assertIsSubclass(self, cls, class_or_tuple, msg=None): @@ -78,9 +60,7 @@ def test_equality(self): self.assertIs(self.bottom_type, self.bottom_type) self.assertNotEqual(self.bottom_type, None) - @skipUnless(PEP_560, "Python 3.7+ required") def test_get_origin(self): - from typing_extensions import get_origin self.assertIs(get_origin(self.bottom_type), None) def test_instance_type_error(self): @@ -621,11 +601,8 @@ def test_final_forward_ref(self): self.assertNotEqual(gth(Loop, globals())['attr'], Final) -@skipUnless(PEP_560, "Python 3.7+ required") class GetUtilitiesTestCase(TestCase): def test_get_origin(self): - from typing_extensions import get_origin - T = TypeVar('T') P = ParamSpec('P') Ts = TypeVarTuple('Ts') @@ -655,8 +632,6 @@ class C(Generic[T]): pass self.assertIs(get_origin(Unpack), None) def test_get_args(self): - from typing_extensions import get_args - T = TypeVar('T') Ts = TypeVarTuple('Ts') class C(Generic[T]): pass @@ -767,7 +742,6 @@ class MyDeque(typing_extensions.Deque[int]): ... def test_counter(self): self.assertIsSubclass(collections.Counter, typing_extensions.Counter) - @skipUnless(CAN_INSTANTIATE_COLLECTIONS, "Behavior added in typing 3.6.1") def test_defaultdict_instantiation(self): self.assertIs( type(typing_extensions.DefaultDict()), @@ -790,7 +764,6 @@ class MyDefDict(typing_extensions.DefaultDict[str, int]): self.assertIsSubclass(MyDefDict, collections.defaultdict) self.assertNotIsSubclass(collections.defaultdict, MyDefDict) - @skipUnless(CAN_INSTANTIATE_COLLECTIONS, "Behavior added in typing 3.6.1") def test_ordereddict_instantiation(self): self.assertIs( type(typing_extensions.OrderedDict()), @@ -844,10 +817,7 @@ def test_counter_instantiation(self): self.assertIs(type(typing_extensions.Counter[int]()), collections.Counter) class C(typing_extensions.Counter[T]): ... self.assertIs(type(C[int]()), C) - if not PEP_560: - self.assertEqual(C.__bases__, (typing_extensions.Counter,)) - else: - self.assertEqual(C.__bases__, (collections.Counter, typing.Generic)) + self.assertEqual(C.__bases__, (collections.Counter, typing.Generic)) def test_counter_subclass_instantiation(self): @@ -922,9 +892,8 @@ def manager(): cm = manager() self.assertNotIsInstance(cm, typing_extensions.AsyncContextManager) self.assertEqual(typing_extensions.AsyncContextManager[int].__args__, (int,)) - if TYPING_3_6_1: - with self.assertRaises(TypeError): - isinstance(42, typing_extensions.AsyncContextManager[int]) + with self.assertRaises(TypeError): + isinstance(42, typing_extensions.AsyncContextManager[int]) with self.assertRaises(TypeError): typing_extensions.AsyncContextManager[int, str] @@ -1189,10 +1158,6 @@ def x(self): ... self.assertIsSubclass(C, P) self.assertIsSubclass(C, PG) self.assertIsSubclass(BadP, PG) - if not PEP_560: - self.assertIsSubclass(PG[int], PG) - self.assertIsSubclass(BadPG[int], P) - self.assertIsSubclass(BadPG[T], PG) with self.assertRaises(TypeError): issubclass(C, PG[T]) with self.assertRaises(TypeError): @@ -1383,7 +1348,6 @@ class C: pass with self.assertRaises(TypeError): issubclass(C(), P) - @skipUnless(not OLD_GENERICS, "New style generics required") def test_defining_generic_protocols(self): T = TypeVar('T') S = TypeVar('S') @@ -1392,16 +1356,19 @@ class PR(Protocol[T, S]): def meth(self): pass class P(PR[int, T], Protocol[T]): y = 1 - self.assertIsSubclass(PR[int, T], PR) - self.assertIsSubclass(P[str], PR) with self.assertRaises(TypeError): - PR[int] + issubclass(PR[int, T], PR) with self.assertRaises(TypeError): - P[int, str] + issubclass(P[str], PR) with self.assertRaises(TypeError): - PR[int, 1] + PR[int] with self.assertRaises(TypeError): - PR[int, ClassVar] + P[int, str] + if not TYPING_3_10_0: + with self.assertRaises(TypeError): + PR[int, 1] + with self.assertRaises(TypeError): + PR[int, ClassVar] class C(PR[int, T]): pass self.assertIsInstance(C[str](), C) @@ -1413,11 +1380,8 @@ class PR(Protocol, Generic[T, S]): def meth(self): pass class P(PR[int, str], Protocol): y = 1 - if not PEP_560: + with self.assertRaises(TypeError): self.assertIsSubclass(PR[int, str], PR) - else: - with self.assertRaises(TypeError): - self.assertIsSubclass(PR[int, str], PR) self.assertIsSubclass(P, PR) with self.assertRaises(TypeError): PR[int] @@ -1448,7 +1412,6 @@ def __init__(self): self.test = 'OK' self.assertEqual(C[int]().test, 'OK') - @skipUnless(not OLD_GENERICS, "New style generics required") def test_protocols_bad_subscripts(self): T = TypeVar('T') S = TypeVar('S') @@ -1465,9 +1428,6 @@ def test_generic_protocols_repr(self): T = TypeVar('T') S = TypeVar('S') class P(Protocol[T, S]): pass - # After PEP 560 unsubscripted generics have a standard repr. - if not PEP_560: - self.assertTrue(repr(P).endswith('P')) self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) @@ -1480,13 +1440,10 @@ class P(Protocol[T, S]): pass self.assertEqual(P[T, T][Tuple[T, S]][int, str], P[Tuple[int, str], Tuple[int, str]]) - @skipUnless(not OLD_GENERICS, "New style generics required") def test_generic_protocols_special_from_generic(self): T = TypeVar('T') class P(Protocol[T]): pass self.assertEqual(P.__parameters__, (T,)) - self.assertIs(P.__args__, None) - self.assertIs(P.__origin__, None) self.assertEqual(P[int].__parameters__, ()) self.assertEqual(P[int].__args__, (int,)) self.assertIs(P[int].__origin__, P) @@ -1517,9 +1474,6 @@ def meth(self): self.assertEqual(typing_extensions._get_protocol_attrs(PR), {'x'}) self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG)), frozenset({'x', 'meth'})) - if not PEP_560: - self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG[int])), - frozenset({'x', 'meth'})) def test_no_runtime_deco_on_nominal(self): with self.assertRaises(TypeError): @@ -1747,7 +1701,6 @@ def test_optional_keys(self): assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y']) assert Point2Dor3D.__optional_keys__ == frozenset(['z']) - @skipUnless(PEP_560, "runtime support for Required and NotRequired requires PEP 560") def test_required_notrequired_keys(self): assert NontotalMovie.__required_keys__ == frozenset({'title'}) assert NontotalMovie.__optional_keys__ == frozenset({'year'}) @@ -1821,16 +1774,14 @@ def test_flatten(self): A = Annotated[Annotated[int, 4], 5] self.assertEqual(A, Annotated[int, 4, 5]) self.assertEqual(A.__metadata__, (4, 5)) - if PEP_560: - self.assertEqual(A.__origin__, int) + self.assertEqual(A.__origin__, int) def test_specialize(self): L = Annotated[List[T], "my decoration"] LI = Annotated[List[int], "my decoration"] self.assertEqual(L[int], Annotated[List[int], "my decoration"]) self.assertEqual(L[int].__metadata__, ("my decoration",)) - if PEP_560: - self.assertEqual(L[int].__origin__, List[int]) + self.assertEqual(L[int].__origin__, List[int]) with self.assertRaises(TypeError): LI[int] with self.assertRaises(TypeError): @@ -1934,7 +1885,6 @@ def test_cannot_check_subclass(self): with self.assertRaises(TypeError): issubclass(int, Annotated[int, "positive"]) - @skipUnless(PEP_560, "pickle support was added with PEP 560") def test_pickle(self): samples = [typing.Any, typing.Union[int, str], typing.Optional[str], Tuple[int, ...], @@ -2000,7 +1950,6 @@ def test_annotated_in_other_types(self): self.assertEqual(X[int], List[Annotated[int, 5]]) -@skipUnless(PEP_560, "Python 3.7 required") class GetTypeHintsTests(BaseTestCase): def test_get_type_hints(self): def foobar(x: List['X']): ... @@ -2355,9 +2304,7 @@ def baz(self) -> "LiteralString": ... self.assertEqual(gth(Foo.bar), {'return': LiteralString}) self.assertEqual(gth(Foo.baz), {'return': LiteralString}) - @skipUnless(PEP_560, "Python 3.7+ required") def test_get_origin(self): - from typing_extensions import get_origin self.assertIsNone(get_origin(LiteralString)) def test_repr(self): @@ -2510,7 +2457,6 @@ def test_union(self): Union ) - @skipUnless(PEP_560, "Unimplemented for 3.6") def test_concatenation(self): Xs = TypeVarTuple('Xs') self.assertEqual(Tuple[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) @@ -2523,7 +2469,6 @@ class C(Generic[Unpack[Xs]]): pass self.assertEqual(C[int, Unpack[Xs], str].__args__, (int, Unpack[Xs], str)) - @skipUnless(PEP_560, "Unimplemented for 3.6") def test_class(self): Ts = TypeVarTuple('Ts') @@ -2766,8 +2711,7 @@ def test_typing_extensions_includes_standard(self): self.assertIn("Concatenate", a) self.assertIn('Annotated', a) - if PEP_560: - self.assertIn('get_type_hints', a) + self.assertIn('get_type_hints', a) self.assertIn('Awaitable', a) self.assertIn('AsyncIterator', a) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 194731cd3..5c43354ee 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -6,17 +6,6 @@ import types as _types import typing -# After PEP 560, internal typing API was substantially reworked. -# This is especially important for Protocol class which uses internal APIs -# quite extensively. -PEP_560 = sys.version_info[:3] >= (3, 7, 0) - -if PEP_560: - GenericMeta = type -else: - # 3.6 - from typing import GenericMeta, _type_vars # noqa - # Please keep __all__ alphabetized within each category. __all__ = [ @@ -56,6 +45,9 @@ 'assert_never', 'dataclass_transform', 'final', + 'get_args', + 'get_origin', + 'get_type_hints', 'IntVar', 'is_typeddict', 'Literal', @@ -75,21 +67,13 @@ 'NotRequired', ] -if PEP_560: - __all__.extend(["get_args", "get_origin", "get_type_hints"]) +# for backward compatibility +PEP_560 = True +GenericMeta = type # The functions below are modified copies of typing internal helpers. # They are needed by _ProtocolMeta and they provide support for PEP 646. - -def _no_slots_copy(dct): - dict_copy = dict(dct) - if '__slots__' in dict_copy: - for slot in dict_copy['__slots__']: - dict_copy.pop(slot, None) - return dict_copy - - _marker = object() @@ -148,32 +132,7 @@ def _collect_type_vars(types, typevar_types=None): return tuple(tvars) -# 3.6.2+ -if hasattr(typing, 'NoReturn'): - NoReturn = typing.NoReturn -# 3.6.0-3.6.1 -else: - class _NoReturn(typing._FinalTypingBase, _root=True): - """Special type indicating functions that never return. - Example:: - - from typing import NoReturn - - def stop() -> NoReturn: - raise Exception('no way') - - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - NoReturn = _NoReturn(_root=True) +NoReturn = typing.NoReturn # Some unconstrained type variables. These are used by the container types. # (These are not for export.) @@ -190,7 +149,7 @@ def __subclasscheck__(self, cls): if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): Final = typing.Final # 3.7 -elif sys.version_info[:2] >= (3, 7): +else: class _FinalForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -215,61 +174,6 @@ class FastConnector(Connection): TIMEOUT = 1 # Error reported by type checker There is no runtime checking of these properties.""") -# 3.6 -else: - class _Final(typing._FinalTypingBase, _root=True): - """A special typing construct to indicate that a name - cannot be re-assigned or overridden in a subclass. - For example: - - MAX_SIZE: Final = 9000 - MAX_SIZE += 1 # Error reported by type checker - - class Connection: - TIMEOUT: Final[int] = 10 - class FastConnector(Connection): - TIMEOUT = 1 # Error reported by type checker - - There is no runtime checking of these properties. - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - f'{cls.__name__[1:]} accepts only single type.'), - _root=True) - raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += f'[{typing._type_repr(self.__type__)}]' - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _Final): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - Final = _Final(_root=True) - if sys.version_info >= (3, 11): final = typing.final @@ -317,7 +221,7 @@ def IntVar(name): if hasattr(typing, 'Literal'): Literal = typing.Literal # 3.7: -elif sys.version_info[:2] >= (3, 7): +else: class _LiteralForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -339,55 +243,6 @@ def __getitem__(self, parameters): Literal[...] cannot be subclassed. There is no runtime checking verifying that the parameter is actually a value instead of a type.""") -# 3.6: -else: - class _Literal(typing._FinalTypingBase, _root=True): - """A type that can be used to indicate to type checkers that the - corresponding value has a value literally equivalent to the - provided parameter. For example: - - var: Literal[4] = 4 - - The type checker understands that 'var' is literally equal to the - value 4 and no other value. - - Literal[...] cannot be subclassed. There is no runtime checking - verifying that the parameter is actually a value instead of a type. - """ - - __slots__ = ('__values__',) - - def __init__(self, values=None, **kwds): - self.__values__ = values - - def __getitem__(self, values): - cls = type(self) - if self.__values__ is None: - if not isinstance(values, tuple): - values = (values,) - return cls(values, _root=True) - raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') - - def _eval_type(self, globalns, localns): - return self - - def __repr__(self): - r = super().__repr__() - if self.__values__ is not None: - r += f'[{", ".join(map(typing._type_repr, self.__values__))}]' - return r - - def __hash__(self): - return hash((type(self).__name__, self.__values__)) - - def __eq__(self, other): - if not isinstance(other, _Literal): - return NotImplemented - if self.__values__ is not None: - return self.__values__ == other.__values__ - return self is other - - Literal = _Literal(_root=True) _overload_dummy = typing._overload_dummy # noqa @@ -401,154 +256,30 @@ def __eq__(self, other): # A few are simply re-exported for completeness. -class _ExtensionsGenericMeta(GenericMeta): - def __subclasscheck__(self, subclass): - """This mimics a more modern GenericMeta.__subclasscheck__() logic - (that does not have problems with recursion) to work around interactions - between collections, typing, and typing_extensions on older - versions of Python, see https://github.com/python/typing/issues/501. - """ - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False - if not self.__extra__: - return super().__subclasscheck__(subclass) - res = self.__extra__.__subclasshook__(subclass) - if res is not NotImplemented: - return res - if self.__extra__ in subclass.__mro__: - return True - for scls in self.__extra__.__subclasses__(): - if isinstance(scls, GenericMeta): - continue - if issubclass(subclass, scls): - return True - return False - - Awaitable = typing.Awaitable Coroutine = typing.Coroutine AsyncIterable = typing.AsyncIterable AsyncIterator = typing.AsyncIterator - -# 3.6.1+ -if hasattr(typing, 'Deque'): - Deque = typing.Deque -# 3.6.0 -else: - class Deque(collections.deque, typing.MutableSequence[T], - metaclass=_ExtensionsGenericMeta, - extra=collections.deque): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Deque: - return collections.deque(*args, **kwds) - return typing._generic_new(collections.deque, cls, *args, **kwds) - +Deque = typing.Deque ContextManager = typing.ContextManager -# 3.6.2+ -if hasattr(typing, 'AsyncContextManager'): - AsyncContextManager = typing.AsyncContextManager -# 3.6.0-3.6.1 -else: - from _collections_abc import _check_methods as _check_methods_in_mro # noqa - - class AsyncContextManager(typing.Generic[T_co]): - __slots__ = () - - async def __aenter__(self): - return self - - @abc.abstractmethod - async def __aexit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncContextManager: - return _check_methods_in_mro(C, "__aenter__", "__aexit__") - return NotImplemented - +AsyncContextManager = typing.AsyncContextManager DefaultDict = typing.DefaultDict # 3.7.2+ if hasattr(typing, 'OrderedDict'): OrderedDict = typing.OrderedDict # 3.7.0-3.7.2 -elif (3, 7, 0) <= sys.version_info[:3] < (3, 7, 2): - OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) -# 3.6 -else: - class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.OrderedDict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is OrderedDict: - return collections.OrderedDict(*args, **kwds) - return typing._generic_new(collections.OrderedDict, cls, *args, **kwds) - -# 3.6.2+ -if hasattr(typing, 'Counter'): - Counter = typing.Counter -# 3.6.0-3.6.1 -else: - class Counter(collections.Counter, - typing.Dict[T, int], - metaclass=_ExtensionsGenericMeta, extra=collections.Counter): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Counter: - return collections.Counter(*args, **kwds) - return typing._generic_new(collections.Counter, cls, *args, **kwds) - -# 3.6.1+ -if hasattr(typing, 'ChainMap'): - ChainMap = typing.ChainMap -elif hasattr(collections, 'ChainMap'): - class ChainMap(collections.ChainMap, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.ChainMap): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is ChainMap: - return collections.ChainMap(*args, **kwds) - return typing._generic_new(collections.ChainMap, cls, *args, **kwds) - -# 3.6.1+ -if hasattr(typing, 'AsyncGenerator'): - AsyncGenerator = typing.AsyncGenerator -# 3.6.0 else: - class AsyncGenerator(AsyncIterator[T_co], typing.Generic[T_co, T_contra], - metaclass=_ExtensionsGenericMeta, - extra=collections.abc.AsyncGenerator): - __slots__ = () + OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) +Counter = typing.Counter +ChainMap = typing.ChainMap +AsyncGenerator = typing.AsyncGenerator NewType = typing.NewType Text = typing.Text TYPE_CHECKING = typing.TYPE_CHECKING -def _gorg(cls): - """This function exists for compatibility with old typing versions.""" - assert isinstance(cls, GenericMeta) - if hasattr(cls, '_gorg'): - return cls._gorg - while cls.__origin__ is not None: - cls = cls.__origin__ - return cls - - _PROTO_WHITELIST = ['Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable', 'AsyncIterator', 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', @@ -582,7 +313,7 @@ def _is_callable_members_only(cls): if hasattr(typing, 'Protocol'): Protocol = typing.Protocol # 3.7 -elif PEP_560: +else: def _no_init(self, *args, **kwargs): if type(self)._is_protocol: @@ -764,250 +495,12 @@ def _proto_hook(other): raise TypeError('Protocols can only inherit from other' f' protocols, got {repr(base)}') cls.__init__ = _no_init -# 3.6 -else: - from typing import _next_in_mro, _type_check # noqa - - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') - - class _ProtocolMeta(GenericMeta): - """Internal metaclass for Protocol. - - This exists so Protocol classes can be generic without deriving - from Generic. - """ - def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None, orig_bases=None): - # This is just a version copied from GenericMeta.__new__ that - # includes "Protocol" special treatment. (Comments removed for brevity.) - assert extra is None # Protocols should not have extra - if tvars is not None: - assert origin is not None - assert all(isinstance(t, typing.TypeVar) for t in tvars), tvars - else: - tvars = _type_vars(bases) - gvars = None - for base in bases: - if base is typing.Generic: - raise TypeError("Cannot inherit from plain Generic") - if (isinstance(base, GenericMeta) and - base.__origin__ in (typing.Generic, Protocol)): - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...] or" - " Protocol[...] multiple times.") - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - s_vars = ", ".join(str(t) for t in tvars if t not in gvarset) - s_args = ", ".join(str(g) for g in gvars) - cls_name = "Generic" if any(b.__origin__ is typing.Generic - for b in bases) else "Protocol" - raise TypeError(f"Some type variables ({s_vars}) are" - f" not listed in {cls_name}[{s_args}]") - tvars = gvars - - initial_bases = bases - if (extra is not None and type(extra) is abc.ABCMeta and - extra not in bases): - bases = (extra,) + bases - bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b - for b in bases) - if any(isinstance(b, GenericMeta) and b is not typing.Generic for b in bases): - bases = tuple(b for b in bases if b is not typing.Generic) - namespace.update({'__origin__': origin, '__extra__': extra}) - self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, - _root=True) - super(GenericMeta, self).__setattr__('_gorg', - self if not origin else - _gorg(origin)) - self.__parameters__ = tvars - self.__args__ = tuple(... if a is typing._TypingEllipsis else - () if a is typing._TypingEmpty else - a for a in args) if args else None - self.__next_in_mro__ = _next_in_mro(self) - if orig_bases is None: - self.__orig_bases__ = initial_bases - elif origin is not None: - self._abc_registry = origin._abc_registry - self._abc_cache = origin._abc_cache - if hasattr(self, '_subs_tree'): - self.__tree_hash__ = (hash(self._subs_tree()) if origin else - super(GenericMeta, self).__hash__()) - return self - - def __init__(cls, *args, **kwargs): - super().__init__(*args, **kwargs) - if not cls.__dict__.get('_is_protocol', None): - cls._is_protocol = any(b is Protocol or - isinstance(b, _ProtocolMeta) and - b.__origin__ is Protocol - for b in cls.__bases__) - if cls._is_protocol: - for base in cls.__mro__[1:]: - if not (base in (object, typing.Generic) or - base.__module__ == 'collections.abc' and - base.__name__ in _PROTO_WHITELIST or - isinstance(base, typing.TypingMeta) and base._is_protocol or - isinstance(base, GenericMeta) and - base.__origin__ is typing.Generic): - raise TypeError(f'Protocols can only inherit from other' - f' protocols, got {repr(base)}') - - cls.__init__ = _no_init - - def _proto_hook(other): - if not cls.__dict__.get('_is_protocol', None): - return NotImplemented - if not isinstance(other, type): - # Same error as for issubclass(1, int) - raise TypeError('issubclass() arg 1 must be a class') - for attr in _get_protocol_attrs(cls): - for base in other.__mro__: - if attr in base.__dict__: - if base.__dict__[attr] is None: - return NotImplemented - break - annotations = getattr(base, '__annotations__', {}) - if (isinstance(annotations, typing.Mapping) and - attr in annotations and - isinstance(other, _ProtocolMeta) and - other._is_protocol): - break - else: - return NotImplemented - return True - if '__subclasshook__' not in cls.__dict__: - cls.__subclasshook__ = _proto_hook - - def __instancecheck__(self, instance): - # We need this method for situations where attributes are - # assigned in __init__. - if ((not getattr(self, '_is_protocol', False) or - _is_callable_members_only(self)) and - issubclass(instance.__class__, self)): - return True - if self._is_protocol: - if all(hasattr(instance, attr) and - (not callable(getattr(self, attr, None)) or - getattr(instance, attr) is not None) - for attr in _get_protocol_attrs(self)): - return True - return super(GenericMeta, self).__instancecheck__(instance) - - def __subclasscheck__(self, cls): - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False - if (self.__dict__.get('_is_protocol', None) and - not self.__dict__.get('_is_runtime_protocol', None)): - if sys._getframe(1).f_globals['__name__'] in ['abc', - 'functools', - 'typing']: - return False - raise TypeError("Instance and class checks can only be used with" - " @runtime protocols") - if (self.__dict__.get('_is_runtime_protocol', None) and - not _is_callable_members_only(self)): - if sys._getframe(1).f_globals['__name__'] in ['abc', - 'functools', - 'typing']: - return super(GenericMeta, self).__subclasscheck__(cls) - raise TypeError("Protocols with non-method members" - " don't support issubclass()") - return super(GenericMeta, self).__subclasscheck__(cls) - - @typing._tp_cache - def __getitem__(self, params): - # We also need to copy this from GenericMeta.__getitem__ to get - # special treatment of "Protocol". (Comments removed for brevity.) - if not isinstance(params, tuple): - params = (params,) - if not params and _gorg(self) is not typing.Tuple: - raise TypeError( - f"Parameter list to {self.__qualname__}[...] cannot be empty") - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if self in (typing.Generic, Protocol): - if not all(isinstance(p, typing.TypeVar) for p in params): - raise TypeError( - f"Parameters to {repr(self)}[...] must all be type variables") - if len(set(params)) != len(params): - raise TypeError( - f"Parameters to {repr(self)}[...] must all be unique") - tvars = params - args = params - elif self in (typing.Tuple, typing.Callable): - tvars = _type_vars(params) - args = params - elif self.__origin__ in (typing.Generic, Protocol): - raise TypeError(f"Cannot subscript already-subscripted {repr(self)}") - else: - _check_generic(self, params, len(self.__parameters__)) - tvars = _type_vars(params) - args = params - - prepend = (self,) if self.__origin__ is None else () - return self.__class__(self.__name__, - prepend + self.__bases__, - _no_slots_copy(self.__dict__), - tvars=tvars, - args=args, - origin=self, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - class Protocol(metaclass=_ProtocolMeta): - """Base class for protocol classes. Protocol classes are defined as:: - - class Proto(Protocol): - def meth(self) -> int: - ... - - Such classes are primarily used with static type checkers that recognize - structural subtyping (static duck-typing), for example:: - - class C: - def meth(self) -> int: - return 0 - - def func(x: Proto) -> int: - return x.meth() - - func(C()) # Passes static type check - - See PEP 544 for details. Protocol classes decorated with - @typing_extensions.runtime act as simple-minded runtime protocol that checks - only the presence of given attributes, ignoring their type signatures. - - Protocol classes can be generic, they are defined as:: - - class GenProto(Protocol[T]): - def meth(self) -> T: - ... - """ - __slots__ = () - _is_protocol = True - - def __new__(cls, *args, **kwds): - if _gorg(cls) is Protocol: - raise TypeError("Type Protocol cannot be instantiated; " - "it can be used only as a base class") - return typing._generic_new(cls.__next_in_mro__, cls, *args, **kwds) # 3.8+ if hasattr(typing, 'runtime_checkable'): runtime_checkable = typing.runtime_checkable -# 3.6-3.7 +# 3.7 else: def runtime_checkable(cls): """Mark a protocol class as a runtime protocol, so that it @@ -1031,7 +524,7 @@ def runtime_checkable(cls): # 3.8+ if hasattr(typing, 'SupportsIndex'): SupportsIndex = typing.SupportsIndex -# 3.6-3.7 +# 3.7 else: @runtime_checkable class SupportsIndex(Protocol): @@ -1148,29 +641,22 @@ def __new__(cls, name, bases, ns, total=True): optional_keys.update(base.__dict__.get('__optional_keys__', ())) annotations.update(own_annotations) - if PEP_560: - for annotation_key, annotation_type in own_annotations.items(): - annotation_origin = get_origin(annotation_type) - if annotation_origin is Annotated: - annotation_args = get_args(annotation_type) - if annotation_args: - annotation_type = annotation_args[0] - annotation_origin = get_origin(annotation_type) - - if annotation_origin is Required: - required_keys.add(annotation_key) - elif annotation_origin is NotRequired: - optional_keys.add(annotation_key) - elif total: - required_keys.add(annotation_key) - else: - optional_keys.add(annotation_key) - else: - own_annotation_keys = set(own_annotations.keys()) - if total: - required_keys.update(own_annotation_keys) + for annotation_key, annotation_type in own_annotations.items(): + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is Required: + required_keys.add(annotation_key) + elif annotation_origin is NotRequired: + optional_keys.add(annotation_key) + elif total: + required_keys.add(annotation_key) else: - optional_keys.update(own_annotation_keys) + optional_keys.add(annotation_key) tp_dict.__annotations__ = annotations tp_dict.__required_keys__ = frozenset(required_keys) @@ -1233,7 +719,7 @@ class Film(TypedDict): if hasattr(typing, "Required"): get_type_hints = typing.get_type_hints -elif PEP_560: +else: import functools import types @@ -1312,7 +798,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): # to work. _AnnotatedAlias = typing._AnnotatedAlias # 3.7-3.8 -elif PEP_560: +else: class _AnnotatedAlias(typing._GenericAlias, _root=True): """Runtime representation of an annotated type. @@ -1409,198 +895,52 @@ def __init_subclass__(cls, *args, **kwargs): raise TypeError( f"Cannot subclass {cls.__module__}.Annotated" ) -# 3.6 -else: - - def _is_dunder(name): - """Returns True if name is a __dunder_variable_name__.""" - return len(name) > 4 and name.startswith('__') and name.endswith('__') - # Prior to Python 3.7 types did not have `copy_with`. A lot of the equality - # checks, argument expansion etc. are done on the _subs_tre. As a result we - # can't provide a get_type_hints function that strips out annotations. - - class AnnotatedMeta(typing.GenericMeta): - """Metaclass for Annotated""" +# Python 3.8 has get_origin() and get_args() but those implementations aren't +# Annotated-aware, so we can't use those. Python 3.9's versions don't support +# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. +if sys.version_info[:2] >= (3, 10): + get_origin = typing.get_origin + get_args = typing.get_args +# 3.7-3.9 +else: + try: + # 3.9+ + from typing import _BaseGenericAlias + except ImportError: + _BaseGenericAlias = typing._GenericAlias + try: + # 3.9+ + from typing import GenericAlias + except ImportError: + GenericAlias = typing._GenericAlias - def __new__(cls, name, bases, namespace, **kwargs): - if any(b is not object for b in bases): - raise TypeError("Cannot subclass " + str(Annotated)) - return super().__new__(cls, name, bases, namespace, **kwargs) + def get_origin(tp): + """Get the unsubscripted version of a type. - @property - def __metadata__(self): - return self._subs_tree()[2] + This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar + and Annotated. Return None for unsupported types. Examples:: - def _tree_repr(self, tree): - cls, origin, metadata = tree - if not isinstance(origin, tuple): - tp_repr = typing._type_repr(origin) - else: - tp_repr = origin[0]._tree_repr(origin) - metadata_reprs = ", ".join(repr(arg) for arg in metadata) - return f'{cls}[{tp_repr}, {metadata_reprs}]' - - def _subs_tree(self, tvars=None, args=None): # noqa - if self is Annotated: - return Annotated - res = super()._subs_tree(tvars=tvars, args=args) - # Flatten nested Annotated - if isinstance(res[1], tuple) and res[1][0] is Annotated: - sub_tp = res[1][1] - sub_annot = res[1][2] - return (Annotated, sub_tp, sub_annot + res[2]) - return res + get_origin(Literal[42]) is Literal + get_origin(int) is None + get_origin(ClassVar[int]) is ClassVar + get_origin(Generic) is Generic + get_origin(Generic[T]) is Generic + get_origin(Union[T, int]) is Union + get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P + """ + if isinstance(tp, _AnnotatedAlias): + return Annotated + if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, + ParamSpecArgs, ParamSpecKwargs)): + return tp.__origin__ + if tp is typing.Generic: + return typing.Generic + return None - def _get_cons(self): - """Return the class used to create instance of this type.""" - if self.__origin__ is None: - raise TypeError("Cannot get the underlying type of a " - "non-specialized Annotated type.") - tree = self._subs_tree() - while isinstance(tree, tuple) and tree[0] is Annotated: - tree = tree[1] - if isinstance(tree, tuple): - return tree[0] - else: - return tree - - @typing._tp_cache - def __getitem__(self, params): - if not isinstance(params, tuple): - params = (params,) - if self.__origin__ is not None: # specializing an instantiated type - return super().__getitem__(params) - elif not isinstance(params, tuple) or len(params) < 2: - raise TypeError("Annotated[...] should be instantiated " - "with at least two arguments (a type and an " - "annotation).") - else: - if ( - isinstance(params[0], typing._TypingBase) and - type(params[0]).__name__ == "_ClassVar" - ): - tp = params[0] - else: - msg = "Annotated[t, ...]: t must be a type." - tp = typing._type_check(params[0], msg) - metadata = tuple(params[1:]) - return self.__class__( - self.__name__, - self.__bases__, - _no_slots_copy(self.__dict__), - tvars=_type_vars((tp,)), - # Metadata is a tuple so it won't be touched by _replace_args et al. - args=(tp, metadata), - origin=self, - ) - - def __call__(self, *args, **kwargs): - cons = self._get_cons() - result = cons(*args, **kwargs) - try: - result.__orig_class__ = self - except AttributeError: - pass - return result - - def __getattr__(self, attr): - # For simplicity we just don't relay all dunder names - if self.__origin__ is not None and not _is_dunder(attr): - return getattr(self._get_cons(), attr) - raise AttributeError(attr) - - def __setattr__(self, attr, value): - if _is_dunder(attr) or attr.startswith('_abc_'): - super().__setattr__(attr, value) - elif self.__origin__ is None: - raise AttributeError(attr) - else: - setattr(self._get_cons(), attr, value) - - def __instancecheck__(self, obj): - raise TypeError("Annotated cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Annotated cannot be used with issubclass().") - - class Annotated(metaclass=AnnotatedMeta): - """Add context specific metadata to a type. - - Example: Annotated[int, runtime_check.Unsigned] indicates to the - hypothetical runtime_check module that this type is an unsigned int. - Every other consumer of this type can ignore this metadata and treat - this type as int. - - The first argument to Annotated must be a valid type, the remaining - arguments are kept as a tuple in the __metadata__ field. - - Details: - - - It's an error to call `Annotated` with less than two arguments. - - Nested Annotated are flattened:: - - Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] - - - Instantiating an annotated type is equivalent to instantiating the - underlying type:: - - Annotated[C, Ann1](5) == C(5) - - - Annotated can be used as a generic type alias:: - - Optimized = Annotated[T, runtime.Optimize()] - Optimized[int] == Annotated[int, runtime.Optimize()] - - OptimizedList = Annotated[List[T], runtime.Optimize()] - OptimizedList[int] == Annotated[List[int], runtime.Optimize()] - """ - -# Python 3.8 has get_origin() and get_args() but those implementations aren't -# Annotated-aware, so we can't use those. Python 3.9's versions don't support -# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. -if sys.version_info[:2] >= (3, 10): - get_origin = typing.get_origin - get_args = typing.get_args -# 3.7-3.9 -elif PEP_560: - try: - # 3.9+ - from typing import _BaseGenericAlias - except ImportError: - _BaseGenericAlias = typing._GenericAlias - try: - # 3.9+ - from typing import GenericAlias - except ImportError: - GenericAlias = typing._GenericAlias - - def get_origin(tp): - """Get the unsubscripted version of a type. - - This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar - and Annotated. Return None for unsupported types. Examples:: - - get_origin(Literal[42]) is Literal - get_origin(int) is None - get_origin(ClassVar[int]) is ClassVar - get_origin(Generic) is Generic - get_origin(Generic[T]) is Generic - get_origin(Union[T, int]) is Union - get_origin(List[Tuple[T, T]][int]) == list - get_origin(P.args) is P - """ - if isinstance(tp, _AnnotatedAlias): - return Annotated - if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, - ParamSpecArgs, ParamSpecKwargs)): - return tp.__origin__ - if tp is typing.Generic: - return typing.Generic - return None - - def get_args(tp): - """Get type arguments with all substitutions performed. + def get_args(tp): + """Get type arguments with all substitutions performed. For unions, basic simplifications used by Union constructor are performed. Examples:: @@ -1645,7 +985,7 @@ def TypeAlias(self, parameters): """ raise TypeError(f"{self} is not subscriptable") # 3.7-3.8 -elif sys.version_info[:2] >= (3, 7): +else: class _TypeAliasForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -1661,44 +1001,13 @@ def __repr__(self): It's invalid when used anywhere except as in the example above.""") -# 3.6 -else: - class _TypeAliasMeta(typing.TypingMeta): - """Metaclass for TypeAlias""" - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - class _TypeAliasBase(typing._FinalTypingBase, metaclass=_TypeAliasMeta, _root=True): - """Special marker indicating that an assignment should - be recognized as a proper type alias definition by type - checkers. - - For example:: - - Predicate: TypeAlias = Callable[..., bool] - - It's invalid when used anywhere except as in the example above. - """ - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("TypeAlias cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeAlias cannot be used with issubclass().") - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - TypeAlias = _TypeAliasBase(_root=True) # Python 3.10+ has PEP 612 if hasattr(typing, 'ParamSpecArgs'): ParamSpecArgs = typing.ParamSpecArgs ParamSpecKwargs = typing.ParamSpecKwargs -# 3.6-3.9 +# 3.7-3.9 else: class _Immutable: """Mixin to indicate that object should not be copied.""" @@ -1759,7 +1068,7 @@ def __eq__(self, other): # 3.10+ if hasattr(typing, 'ParamSpec'): ParamSpec = typing.ParamSpec -# 3.6-3.9 +# 3.7-3.9 else: # Inherits from list as a workaround for Callable checks in Python < 3.9.2. @@ -1861,28 +1170,17 @@ def __reduce__(self): def __call__(self, *args, **kwargs): pass - if not PEP_560: - # Only needed in 3.6. - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - -# 3.6-3.9 +# 3.7-3.9 if not hasattr(typing, 'Concatenate'): # Inherits from list as a workaround for Callable checks in Python < 3.9.2. class _ConcatenateGenericAlias(list): # Trick Generic into looking into this for __parameters__. - if PEP_560: - __class__ = typing._GenericAlias - else: - __class__ = typing._TypingBase + __class__ = typing._GenericAlias # Flag in 3.8. _special = False - # Attribute in 3.6 and earlier. - _gorg = typing.Generic def __init__(self, origin, args): super().__init__(args) @@ -1907,14 +1205,8 @@ def __parameters__(self): tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) ) - if not PEP_560: - # Only required in 3.6. - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - typing._get_type_vars(self.__parameters__, tvars) - -# 3.6-3.9 +# 3.7-3.9 @typing._tp_cache def _concatenate_getitem(self, parameters): if parameters == (): @@ -1949,7 +1241,7 @@ def Concatenate(self, parameters): """ return _concatenate_getitem(self, parameters) # 3.7-8 -elif sys.version_info[:2] >= (3, 7): +else: class _ConcatenateForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -1969,42 +1261,6 @@ def __getitem__(self, parameters): See PEP 612 for detailed information. """) -# 3.6 -else: - class _ConcatenateAliasMeta(typing.TypingMeta): - """Metaclass for Concatenate.""" - - def __repr__(self): - return 'typing_extensions.Concatenate' - - class _ConcatenateAliasBase(typing._FinalTypingBase, - metaclass=_ConcatenateAliasMeta, - _root=True): - """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a - higher order function which adds, removes or transforms parameters of a - callable. - - For example:: - - Callable[Concatenate[int, P], int] - - See PEP 612 for detailed information. - """ - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("Concatenate cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Concatenate cannot be used with issubclass().") - - def __repr__(self): - return 'typing_extensions.Concatenate' - - def __getitem__(self, parameters): - return _concatenate_getitem(self, parameters) - - Concatenate = _ConcatenateAliasBase(_root=True) # 3.10+ if hasattr(typing, 'TypeGuard'): @@ -2062,7 +1318,7 @@ def is_str(val: Union[str, float]): item = typing._type_check(parameters, f'{self} accepts only single type.') return typing._GenericAlias(self, (item,)) # 3.7-3.8 -elif sys.version_info[:2] >= (3, 7): +else: class _TypeGuardForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -2117,138 +1373,55 @@ def is_str(val: Union[str, float]): ``TypeGuard`` also works with type variables. For more information, see PEP 647 (User-Defined Type Guards). """) -# 3.6 -else: - class _TypeGuard(typing._FinalTypingBase, _root=True): - """Special typing form used to annotate the return type of a user-defined - type guard function. ``TypeGuard`` only accepts a single type argument. - At runtime, functions marked this way should return a boolean. - - ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static - type checkers to determine a more precise type of an expression within a - program's code flow. Usually type narrowing is done by analyzing - conditional code flow and applying the narrowing to a block of code. The - conditional expression here is sometimes referred to as a "type guard". - - Sometimes it would be convenient to use a user-defined boolean function - as a type guard. Such a function should use ``TypeGuard[...]`` as its - return type to alert static type checkers to this intention. - - Using ``-> TypeGuard`` tells the static type checker that for a given - function: - - 1. The return value is a boolean. - 2. If the return value is ``True``, the type of its argument - is the type inside ``TypeGuard``. - - For example:: - - def is_str(val: Union[str, float]): - # "isinstance" type guard - if isinstance(val, str): - # Type of ``val`` is narrowed to ``str`` - ... - else: - # Else, type of ``val`` is narrowed to ``float``. - ... - - Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower - form of ``TypeA`` (it can even be a wider form) and this may lead to - type-unsafe results. The main reason is to allow for things like - narrowing ``List[object]`` to ``List[str]`` even though the latter is not - a subtype of the former, since ``List`` is invariant. The responsibility of - writing type-safe type guards is left to the user. - - ``TypeGuard`` also works with type variables. For more information, see - PEP 647 (User-Defined Type Guards). - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - f'{cls.__name__[1:]} accepts only a single type.'), - _root=True) - raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += f'[{typing._type_repr(self.__type__)}]' - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _TypeGuard): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - TypeGuard = _TypeGuard(_root=True) +# Vendored from cpython typing._SpecialFrom +class _SpecialForm(typing._Final, _root=True): + __slots__ = ('_name', '__doc__', '_getitem') -if sys.version_info[:2] >= (3, 7): - # Vendored from cpython typing._SpecialFrom - class _SpecialForm(typing._Final, _root=True): - __slots__ = ('_name', '__doc__', '_getitem') + def __init__(self, getitem): + self._getitem = getitem + self._name = getitem.__name__ + self.__doc__ = getitem.__doc__ - def __init__(self, getitem): - self._getitem = getitem - self._name = getitem.__name__ - self.__doc__ = getitem.__doc__ - - def __getattr__(self, item): - if item in {'__name__', '__qualname__'}: - return self._name + def __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name - raise AttributeError(item) + raise AttributeError(item) - def __mro_entries__(self, bases): - raise TypeError(f"Cannot subclass {self!r}") + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass {self!r}") - def __repr__(self): - return f'typing_extensions.{self._name}' + def __repr__(self): + return f'typing_extensions.{self._name}' - def __reduce__(self): - return self._name + def __reduce__(self): + return self._name - def __call__(self, *args, **kwds): - raise TypeError(f"Cannot instantiate {self!r}") + def __call__(self, *args, **kwds): + raise TypeError(f"Cannot instantiate {self!r}") - def __or__(self, other): - return typing.Union[self, other] + def __or__(self, other): + return typing.Union[self, other] - def __ror__(self, other): - return typing.Union[other, self] + def __ror__(self, other): + return typing.Union[other, self] - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance()") + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance()") - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass()") + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass()") - @typing._tp_cache - def __getitem__(self, parameters): - return self._getitem(self, parameters) + @typing._tp_cache + def __getitem__(self, parameters): + return self._getitem(self, parameters) if hasattr(typing, "LiteralString"): LiteralString = typing.LiteralString -elif sys.version_info[:2] >= (3, 7): +else: @_SpecialForm def LiteralString(self, params): """Represents an arbitrary literal string. @@ -2267,38 +1440,11 @@ def query(sql: LiteralString) -> ...: """ raise TypeError(f"{self} is not subscriptable") -else: - class _LiteralString(typing._FinalTypingBase, _root=True): - """Represents an arbitrary literal string. - - Example:: - - from typing_extensions import LiteralString - - def query(sql: LiteralString) -> ...: - ... - - query("SELECT * FROM table") # ok - query(f"SELECT * FROM {input()}") # not ok - - See PEP 675 for details. - - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass().") - - LiteralString = _LiteralString(_root=True) if hasattr(typing, "Self"): Self = typing.Self -elif sys.version_info[:2] >= (3, 7): +else: @_SpecialForm def Self(self, params): """Used to spell the type of "self" in classes. @@ -2315,35 +1461,11 @@ def parse(self, data: bytes) -> Self: """ raise TypeError(f"{self} is not subscriptable") -else: - class _Self(typing._FinalTypingBase, _root=True): - """Used to spell the type of "self" in classes. - - Example:: - - from typing import Self - - class ReturnsSelf: - def parse(self, data: bytes) -> Self: - ... - return self - - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass().") - - Self = _Self(_root=True) if hasattr(typing, "Never"): Never = typing.Never -elif sys.version_info[:2] >= (3, 7): +else: @_SpecialForm def Never(self, params): """The bottom type, a type that has no members. @@ -2369,39 +1491,6 @@ def int_or_str(arg: int | str) -> None: """ raise TypeError(f"{self} is not subscriptable") -else: - class _Never(typing._FinalTypingBase, _root=True): - """The bottom type, a type that has no members. - - This can be used to define a function that should never be - called, or a function that never returns:: - - from typing_extensions import Never - - def never_call_me(arg: Never) -> None: - pass - - def int_or_str(arg: int | str) -> None: - never_call_me(arg) # type checker error - match arg: - case int(): - print("It's an int") - case str(): - print("It's a str") - case _: - never_call_me(arg) # ok, arg is of type Never - - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass().") - - Never = _Never(_root=True) if hasattr(typing, 'Required'): @@ -2449,7 +1538,7 @@ class Movie(TypedDict): item = typing._type_check(parameters, f'{self._name} accepts only single type') return typing._GenericAlias(self, (item,)) -elif sys.version_info[:2] >= (3, 7): +else: class _RequiredForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -2490,78 +1579,6 @@ class Movie(TypedDict): year=1999, ) """) -else: - # NOTE: Modeled after _Final's implementation when _FinalTypingBase available - class _MaybeRequired(typing._FinalTypingBase, _root=True): - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, type(self)): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - class _Required(_MaybeRequired, _root=True): - """A special typing construct to mark a key of a total=False TypedDict - as required. For example: - - class Movie(TypedDict, total=False): - title: Required[str] - year: int - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - - There is no runtime checking that a required key is actually provided - when instantiating a related TypedDict. - """ - - class _NotRequired(_MaybeRequired, _root=True): - """A special typing construct to mark a key of a TypedDict as - potentially missing. For example: - - class Movie(TypedDict): - title: str - year: NotRequired[int] - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - """ - - Required = _Required(_root=True) - NotRequired = _NotRequired(_root=True) if sys.version_info[:2] >= (3, 9): @@ -2590,7 +1607,7 @@ def add_batch_axis( def _is_unpack(obj): return isinstance(obj, _UnpackAlias) -elif sys.version_info[:2] >= (3, 7): +else: class _UnpackAlias(typing._GenericAlias, _root=True): __class__ = typing.TypeVar @@ -2619,64 +1636,6 @@ def add_batch_axis( def _is_unpack(obj): return isinstance(obj, _UnpackAlias) -else: - # NOTE: Modeled after _Final's implementation when _FinalTypingBase available - class _Unpack(typing._FinalTypingBase, _root=True): - """A special typing construct to unpack a variadic type. For example: - - Shape = TypeVarTuple('Shape') - Batch = NewType('Batch', int) - - def add_batch_axis( - x: Array[Unpack[Shape]] - ) -> Array[Batch, Unpack[Shape]]: ... - - """ - __slots__ = ('__type__',) - __class__ = typing.TypeVar - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - 'Unpack accepts only single type.'), - _root=True) - raise TypeError('Unpack cannot be further subscripted') - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _Unpack): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - # For 3.6 only - def _get_type_vars(self, tvars): - self.__type__._get_type_vars(tvars) - - Unpack = _Unpack(_root=True) - - def _is_unpack(obj): - return isinstance(obj, _Unpack) - class TypeVarTuple: """Type variable tuple. @@ -2757,12 +1716,6 @@ def __init_subclass__(self, *args, **kwds): if '_root' not in kwds: raise TypeError("Cannot subclass special typing classes") - if not PEP_560: - # Only needed in 3.6. - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - if hasattr(typing, "reveal_type"): reveal_type = typing.reveal_type From 3bcfc9ed3b0511e67119dc356fd736889e884fde Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Tue, 22 Mar 2022 14:45:53 -0700 Subject: [PATCH 109/539] test_typing_extensions: fix lint (#1111) --- typing_extensions/src/test_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 20e35f431..53a8343fa 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -2591,7 +2591,7 @@ def stmethod(): ... def prop(self): ... @final - @lru_cache() + @lru_cache() # noqa: B019 def cached(self): ... # Use getattr_static because the descriptor returns the From 07fb800b29127a46a9c422d4dbc4e4d96df69d3f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 22 Mar 2022 14:46:02 -0700 Subject: [PATCH 110/539] LiteralString, NotRequired, Required will be in 3.11 (#1110) --- typing_extensions/README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index bb8906895..d29d9f535 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -41,16 +41,16 @@ This module currently contains the following: - Experimental features - - ``LiteralString`` (see PEP 675) - ``@dataclass_transform()`` (see PEP 681) - - ``NotRequired`` (see PEP 655) - - ``Required`` (see PEP 655) - In ``typing`` since Python 3.11 - ``assert_never`` + - ``LiteralString`` (see PEP 675) - ``Never`` + - ``NotRequired`` (see PEP 655) - ``reveal_type`` + - ``Required`` (see PEP 655) - ``Self`` (see PEP 673) - ``TypeVarTuple`` (see PEP 646) - ``Unpack`` (see PEP 646) From db20497ae1be03d89e9cd083aa93cdff9c786831 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 22 Mar 2022 15:29:58 -0700 Subject: [PATCH 111/539] Add assert_type (#1103) --- typing_extensions/CHANGELOG | 1 + typing_extensions/README.rst | 1 + .../src/test_typing_extensions.py | 19 +++++++++++++++-- typing_extensions/src/typing_extensions.py | 21 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index f098464eb..a9a598045 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,6 @@ # Unreleased +- Add `typing.assert_type`. Backport from bpo-46480. - Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). # Release 4.1.1 (February 13, 2022) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index d29d9f535..9abed0442 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -46,6 +46,7 @@ This module currently contains the following: - In ``typing`` since Python 3.11 - ``assert_never`` + - ``assert_type`` - ``LiteralString`` (see PEP 675) - ``Never`` - ``NotRequired`` (see PEP 655) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 53a8343fa..b8fe5e352 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -12,7 +12,7 @@ from unittest import TestCase, main, skipUnless, skipIf from test import ann_module, ann_module2, ann_module3 import typing -from typing import TypeVar, Optional, Union, Any +from typing import TypeVar, Optional, Union, Any, AnyStr from typing import T, KT, VT # Not in __all__. from typing import Tuple, List, Dict, Iterable, Iterator, Callable from typing import Generic, NamedTuple @@ -23,7 +23,7 @@ from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString -from typing_extensions import get_type_hints, get_origin, get_args +from typing_extensions import assert_type, get_type_hints, get_origin, get_args # Flags used to mark tests that only apply after a specific # version of the typing module. @@ -425,6 +425,21 @@ def blah(): blah() +class AssertTypeTests(BaseTestCase): + + def test_basics(self): + arg = 42 + self.assertIs(assert_type(arg, int), arg) + self.assertIs(assert_type(arg, Union[str, float]), arg) + self.assertIs(assert_type(arg, AnyStr), arg) + self.assertIs(assert_type(arg, None), arg) + + def test_errors(self): + # Bogus calls are not expected to fail. + arg = 42 + self.assertIs(assert_type(arg, 42), arg) + self.assertIs(assert_type(arg, 'hello'), arg) + T_a = TypeVar('T_a') diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 5c43354ee..5698a76cc 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -717,6 +717,27 @@ class Film(TypedDict): """ return isinstance(tp, tuple(_TYPEDDICT_TYPES)) + +if hasattr(typing, "assert_type"): + assert_type = typing.assert_type + +else: + def assert_type(__val, __typ): + """Assert (to the type checker) that the value is of the given type. + + When the type checker encounters a call to assert_type(), it + emits an error if the value is not of the specified type:: + + def greet(name: str) -> None: + assert_type(name, str) # ok + assert_type(name, int) # type checker error + + At runtime this returns the first argument unchanged and otherwise + does nothing. + """ + return __val + + if hasattr(typing, "Required"): get_type_hints = typing.get_type_hints else: From 5eed34c3daf3b38edf4d1246b3c063f38579cbe7 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 5 Apr 2022 19:09:21 +0200 Subject: [PATCH 112/539] TypeAlias can now be used, add guidance (#1116) --- docs/source/stubs.rst | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index 8408c8050..539e8cb75 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -356,20 +356,28 @@ type stubs:: Aliases and NewType ------------------- -Type checkers should accept module-level and class-level aliases, e.g.:: +Type checkers should accept module-level type aliases, optionally using +``TypeAlias`` (PEP 613 [#pep613]_), e.g.:: _IntList = list[int] + _StrList: TypeAlias = list[str] + +Type checkers should also accept regular module-level or class-level aliases, +e.g.:: + + def a() -> None: ... + b = a class C: def f(self) -> int: ... g = f -An alias to a type may contain type variables. As per PEP 484 [#pep484]_, +A type alias may contain type variables. As per PEP 484 [#pep484]_, all type variables must be substituted when the alias is used:: _K = TypeVar("_K") _V = TypeVar("_V") - _MyMap = Dict[str, Dict[_K, _V]] + _MyMap: TypeAlias = dict[str, dict[_K, _V]] # either concrete types or other type variables can be substituted def f(x: _MyMap[str, _V]) -> _V: ... @@ -518,8 +526,6 @@ and should not be used in stubs: * Positional-only argument syntax (PEP 570 [#pep570]_). Instead, use the syntax described in the section :ref:`supported-functions`. [#ts-4972]_ -* ``TypeAlias`` (PEP 613 [#pep613]_). Instead, use a simple - assigment to define a type alias. [#ts-4913]_ Type Stub Content ================= @@ -827,7 +833,7 @@ No:: But the following is still necessary:: - TYPE_ALIAS = Optional[Union[str, int]] + TYPE_ALIAS: TypeAlias = Optional[Union[str, int]] Module Level Attributes ----------------------- @@ -847,6 +853,25 @@ No:: z = 0 # type: int a = ... # type: int +Type Aliases +------------ + +Use ``TypeAlias`` for type aliases (but not for regular aliases). + +Yes:: + + _IntList: TypeAlias = list[int] + g = os.stat + Path = pathlib.Path + ERROR = errno.EEXIST + +No:: + + _IntList = list[int] + g: TypeAlias = os.stat + Path: TypeAlias = pathlib.Path + ERROR: TypeAlias = errno.EEXIST + Classes ------- @@ -1104,7 +1129,6 @@ Bugs ---- .. [#ts-4819] typeshed issue #4819 -- PEP 604 tracker (https://github.com/python/typeshed/issues/4819) -.. [#ts-4913] typeshed issue #4913 -- PEP 613 tracker (https://github.com/python/typeshed/issues/4913) .. [#ts-4972] typeshed issue #4972 -- PEP 570 tracker (https://github.com/python/typeshed/issues/4972) Copyright From f9d318138fcec1f9724ee1808aeff3ed1bf976a5 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 5 Apr 2022 19:22:31 +0200 Subject: [PATCH 113/539] Don't list specific bugs with union short-hand syntax (#1119) --- docs/source/stubs.rst | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index 539e8cb75..ffd6c9b86 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -193,15 +193,12 @@ Unions ------ Declaring unions with ``Union`` and ``Optional`` is supported by all -type checkers. With the exception of type aliases [#ts-4819]_, the shorthand syntax +type checkers. With a few exceptions [#ts-4819]_, the shorthand syntax is also supported:: def foo(x: int | str) -> int | None: ... # recommended def foo(x: Union[int, str]) -> Optional[int]: ... # ok - TYPE_ALIAS = Union[int, str] # ok - TYPE_ALIAS = int | str # does not work with all type checkers - Module Level Attributes ----------------------- @@ -817,8 +814,7 @@ Shorthand Syntax Where possible, use shorthand syntax for unions instead of ``Union`` or ``Optional``. ``None`` should be the last -element of an union. See the Unions_ section for cases where -using the shorthand syntax is not possible. +element of an union. Yes:: @@ -831,10 +827,6 @@ No:: def bar(x: Optional[str]) -> Optional[int]: ... def baz(x: None | str) -> None: ... -But the following is still necessary:: - - TYPE_ALIAS: TypeAlias = Optional[Union[str, int]] - Module Level Attributes ----------------------- From 91691524757b82ddef8c14cef29f1cd186b40ca2 Mon Sep 17 00:00:00 2001 From: Redowan Delowar Date: Mon, 11 Apr 2022 08:27:03 +0600 Subject: [PATCH 114/539] Update the statuses of the typing PEPs (#1128) --- docs/index.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 695097f96..b5fe26888 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,10 +4,10 @@ Static Typing with Python .. Introduction .. ============ -.. +.. .. .. toctree:: .. :maxdepth: 2 -.. +.. .. source/introduction Guides @@ -92,8 +92,8 @@ Typing PEPs * `PEP 613 `_, ``TypeAlias`` * `PEP 646 `_, variadic generics and ``TypeVarTuple`` * `PEP 647 `_, ``TypeGuard`` -* `PEP 655 `_ (draft), ``Required`` and ``NotRequired`` +* `PEP 655 `_, ``Required`` and ``NotRequired`` * `PEP 673 `_, ``Self`` -* `PEP 675 `_ (draft), ``LiteralString`` -* `PEP 677 `_ (draft), callable type syntax +* `PEP 675 `_, ``LiteralString`` +* `PEP 677 `_ (rejected), callable type syntax * `PEP 681 `_ (draft), ``@dataclass_transform()`` From cba3a90903fd19a33ef65784744f164e9582b7ee Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 11 Apr 2022 10:37:18 -0700 Subject: [PATCH 115/539] Fix "accepts only single type" errors (#1130) - Add "a" to make the message grammatical - Add a trailing period because _type_check adds another sentence after it. For example, `Unpack[3]` on 3.10 currently fails with `TypeError: Unpack accepts only single type Got 3.`. - Use an f-string --- typing_extensions/src/typing_extensions.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 5698a76cc..bd2ab337f 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -157,7 +157,7 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - f'{self._name} accepts only single type') + f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) Final = _FinalForm('Final', @@ -1336,7 +1336,7 @@ def is_str(val: Union[str, float]): ``TypeGuard`` also works with type variables. For more information, see PEP 647 (User-Defined Type Guards). """ - item = typing._type_check(parameters, f'{self} accepts only single type.') + item = typing._type_check(parameters, f'{self} accepts only a single type.') return typing._GenericAlias(self, (item,)) # 3.7-3.8 else: @@ -1539,7 +1539,7 @@ class Movie(TypedDict, total=False): There is no runtime checking that a required key is actually provided when instantiating a related TypedDict. """ - item = typing._type_check(parameters, f'{self._name} accepts only single type') + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) @_ExtensionsSpecialForm @@ -1556,7 +1556,7 @@ class Movie(TypedDict): year=1999, ) """ - item = typing._type_check(parameters, f'{self._name} accepts only single type') + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) else: @@ -1566,7 +1566,7 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - '{} accepts only single type'.format(self._name)) + f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) Required = _RequiredForm( @@ -1622,7 +1622,7 @@ def add_batch_axis( ) -> Array[Batch, Unpack[Shape]]: ... """ - item = typing._type_check(parameters, f'{self._name} accepts only single type') + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') return _UnpackAlias(self, (item,)) def _is_unpack(obj): @@ -1638,7 +1638,7 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - f'{self._name} accepts only single type') + f'{self._name} accepts only a single type.') return _UnpackAlias(self, (item,)) Unpack = _UnpackForm( From e7bc381dc7a4dbc94441759af31f576bbd7302c6 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Apr 2022 23:16:04 +0100 Subject: [PATCH 116/539] Add `assert_type` to `__all__` (#1136) Looks like this is in `typing.__all__` but was missed out of `typing_extensions.__all__` --- typing_extensions/src/typing_extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index bd2ab337f..c959adbfe 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -43,6 +43,7 @@ # One-off things. 'Annotated', 'assert_never', + 'assert_type', 'dataclass_transform', 'final', 'get_args', From 2acaa5acd01aeabb295e961913c111a7df52656d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 15 Apr 2022 17:10:25 -0700 Subject: [PATCH 117/539] test that all names are present in __all__ (#1138) --- .../src/test_typing_extensions.py | 21 +++++++++++++++++++ typing_extensions/src/typing_extensions.py | 10 +++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index b8fe5e352..1439e5174 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -2743,6 +2743,27 @@ def test_typing_extensions_includes_standard(self): for name in a: self.assertTrue(hasattr(typing_extensions, name)) + def test_all_names_in___all__(self): + exclude = { + 'GenericMeta', + 'KT', + 'PEP_560', + 'T', + 'T_co', + 'T_contra', + 'VT', + } + actual_names = { + name for name in dir(typing_extensions) + if not name.startswith("_") + and not isinstance(getattr(typing_extensions, name), types.ModuleType) + } + # Make sure all public names are in __all__ + self.assertEqual({*exclude, *typing_extensions.__all__}, + actual_names) + # Make sure all excluded names actually exist + self.assertLessEqual(exclude, actual_names) + def test_typing_extensions_defers_when_possible(self): exclude = { 'overload', diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index c959adbfe..d5e404972 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -15,6 +15,8 @@ 'Final', 'LiteralString', 'ParamSpec', + 'ParamSpecArgs', + 'ParamSpecKwargs', 'Self', 'Type', 'TypeVarTuple', @@ -933,9 +935,9 @@ def __init_subclass__(cls, *args, **kwargs): _BaseGenericAlias = typing._GenericAlias try: # 3.9+ - from typing import GenericAlias + from typing import GenericAlias as _typing_GenericAlias except ImportError: - GenericAlias = typing._GenericAlias + _typing_GenericAlias = typing._GenericAlias def get_origin(tp): """Get the unsubscripted version of a type. @@ -954,7 +956,7 @@ def get_origin(tp): """ if isinstance(tp, _AnnotatedAlias): return Annotated - if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, + if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias, _BaseGenericAlias, ParamSpecArgs, ParamSpecKwargs)): return tp.__origin__ if tp is typing.Generic: @@ -974,7 +976,7 @@ def get_args(tp): """ if isinstance(tp, _AnnotatedAlias): return (tp.__origin__,) + tp.__metadata__ - if isinstance(tp, (typing._GenericAlias, GenericAlias)): + if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)): if getattr(tp, "_special", False): return () res = tp.__args__ From 35dff91370a382e312dd53d002d948e3efedb317 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:11:18 -0700 Subject: [PATCH 118/539] Add get_overloads() (#1140) Co-authored-by: Alex Waygood --- typing_extensions/CHANGELOG | 5 +- typing_extensions/README.rst | 6 ++ .../src/test_typing_extensions.py | 74 ++++++++++++++++++- typing_extensions/src/typing_extensions.py | 70 +++++++++++++++++- 4 files changed, 152 insertions(+), 3 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index a9a598045..970bbd4a6 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,6 +1,9 @@ # Unreleased -- Add `typing.assert_type`. Backport from bpo-46480. +- Add `typing_extensions.get_overloads` and + `typing_extensions.clear_overloads`, and add registry support to + `typing_extensions.overload`. Backport from python/cpython#89263. +- Add `typing_extensions.assert_type`. Backport from bpo-46480. - Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). # Release 4.1.1 (February 13, 2022) diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 9abed0442..3a23b7559 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -47,6 +47,8 @@ This module currently contains the following: - ``assert_never`` - ``assert_type`` + - ``clear_overloads`` + - ``get_overloads`` - ``LiteralString`` (see PEP 675) - ``Never`` - ``NotRequired`` (see PEP 655) @@ -122,6 +124,10 @@ Certain objects were changed after they were added to ``typing``, and Python 3.8 and lack support for ``ParamSpecArgs`` and ``ParamSpecKwargs`` in 3.9. - ``@final`` was changed in Python 3.11 to set the ``.__final__`` attribute. +- ``@overload`` was changed in Python 3.11 to make function overloads + introspectable at runtime. In order to access overloads with + ``typing_extensions.get_overloads()``, you must use + ``@typing_extensions.overload``. There are a few types whose interface was modified between different versions of typing. For example, ``typing.Sequence`` was modified to diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 1439e5174..ab03244d3 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -3,6 +3,7 @@ import abc import contextlib import collections +from collections import defaultdict import collections.abc from functools import lru_cache import inspect @@ -10,6 +11,7 @@ import subprocess import types from unittest import TestCase, main, skipUnless, skipIf +from unittest.mock import patch from test import ann_module, ann_module2, ann_module3 import typing from typing import TypeVar, Optional, Union, Any, AnyStr @@ -21,9 +23,10 @@ from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired -from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict +from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, final, is_typeddict from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString from typing_extensions import assert_type, get_type_hints, get_origin, get_args +from typing_extensions import clear_overloads, get_overloads, overload # Flags used to mark tests that only apply after a specific # version of the typing module. @@ -403,6 +406,20 @@ def test_no_multiple_subscripts(self): Literal[1][1] +class MethodHolder: + @classmethod + def clsmethod(cls): ... + @staticmethod + def stmethod(): ... + def method(self): ... + + +if TYPING_3_11_0: + registry_holder = typing +else: + registry_holder = typing_extensions + + class OverloadTests(BaseTestCase): def test_overload_fails(self): @@ -424,6 +441,61 @@ def blah(): blah() + def set_up_overloads(self): + def blah(): + pass + + overload1 = blah + overload(blah) + + def blah(): + pass + + overload2 = blah + overload(blah) + + def blah(): + pass + + return blah, [overload1, overload2] + + # Make sure we don't clear the global overload registry + @patch( + f"{registry_holder.__name__}._overload_registry", + defaultdict(lambda: defaultdict(dict)) + ) + def test_overload_registry(self): + registry = registry_holder._overload_registry + # The registry starts out empty + self.assertEqual(registry, {}) + + impl, overloads = self.set_up_overloads() + self.assertNotEqual(registry, {}) + self.assertEqual(list(get_overloads(impl)), overloads) + + def some_other_func(): pass + overload(some_other_func) + other_overload = some_other_func + def some_other_func(): pass + self.assertEqual(list(get_overloads(some_other_func)), [other_overload]) + + # Make sure that after we clear all overloads, the registry is + # completely empty. + clear_overloads() + self.assertEqual(registry, {}) + self.assertEqual(get_overloads(impl), []) + + # Querying a function with no overloads shouldn't change the registry. + def the_only_one(): pass + self.assertEqual(get_overloads(the_only_one), []) + self.assertEqual(registry, {}) + + def test_overload_registry_repeated(self): + for _ in range(2): + impl, overloads = self.set_up_overloads() + + self.assertEqual(list(get_overloads(impl)), overloads) + class AssertTypeTests(BaseTestCase): diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index d5e404972..491109991 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -1,6 +1,7 @@ import abc import collections import collections.abc +import functools import operator import sys import types as _types @@ -46,7 +47,9 @@ 'Annotated', 'assert_never', 'assert_type', + 'clear_overloads', 'dataclass_transform', + 'get_overloads', 'final', 'get_args', 'get_origin', @@ -249,7 +252,72 @@ def __getitem__(self, parameters): _overload_dummy = typing._overload_dummy # noqa -overload = typing.overload + + +if hasattr(typing, "get_overloads"): # 3.11+ + overload = typing.overload + get_overloads = typing.get_overloads + clear_overloads = typing.clear_overloads +else: + # {module: {qualname: {firstlineno: func}}} + _overload_registry = collections.defaultdict( + functools.partial(collections.defaultdict, dict) + ) + + def overload(func): + """Decorator for overloaded functions/methods. + + In a stub file, place two or more stub definitions for the same + function in a row, each decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + + In a non-stub file (i.e. a regular .py file), do the same but + follow it with an implementation. The implementation should *not* + be decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + def utf8(value): + # implementation goes here + + The overloads for a function can be retrieved at runtime using the + get_overloads() function. + """ + # classmethod and staticmethod + f = getattr(func, "__func__", func) + try: + _overload_registry[f.__module__][f.__qualname__][ + f.__code__.co_firstlineno + ] = func + except AttributeError: + # Not a normal function; ignore. + pass + return _overload_dummy + + def get_overloads(func): + """Return all defined overloads for *func* as a sequence.""" + # classmethod and staticmethod + f = getattr(func, "__func__", func) + if f.__module__ not in _overload_registry: + return [] + mod_dict = _overload_registry[f.__module__] + if f.__qualname__ not in mod_dict: + return [] + return list(mod_dict[f.__qualname__].values()) + + def clear_overloads(): + """Clear all overloads in the registry.""" + _overload_registry.clear() # This is not a real generic class. Don't use outside annotations. From b595c737c153a0d0f638be9d32469d043b7128a6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:17:25 -0700 Subject: [PATCH 119/539] dataclass_transform: accept **kwargs, rename field_descriptors (#1120) --- typing_extensions/CHANGELOG | 3 +++ .../src/test_typing_extensions.py | 18 +++++++++++++----- typing_extensions/src/typing_extensions.py | 10 ++++++---- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 970bbd4a6..550a14631 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,8 @@ # Unreleased +- Update `typing_extensions.dataclass_transform` to rename the + `field_descriptors` parameter to `field_specifiers` and accept + arbitrary keyword arguments. - Add `typing_extensions.get_overloads` and `typing_extensions.clear_overloads`, and add registry support to `typing_extensions.overload`. Backport from python/cpython#89263. diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index ab03244d3..8e11eb671 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -2718,7 +2718,8 @@ class CustomerModel: "eq_default": True, "order_default": False, "kw_only_default": True, - "field_descriptors": (), + "field_specifiers": (), + "kwargs": {}, } ) self.assertIs( @@ -2730,7 +2731,12 @@ def test_base_class(self): class ModelBase: def __init_subclass__(cls, *, frozen: bool = False): ... - Decorated = dataclass_transform(eq_default=True, order_default=True)(ModelBase) + Decorated = dataclass_transform( + eq_default=True, + order_default=True, + # Arbitrary unrecognized kwargs are accepted at runtime. + make_everything_awesome=True, + )(ModelBase) class CustomerModel(Decorated, frozen=True): id: int @@ -2742,7 +2748,8 @@ class CustomerModel(Decorated, frozen=True): "eq_default": True, "order_default": True, "kw_only_default": False, - "field_descriptors": (), + "field_specifiers": (), + "kwargs": {"make_everything_awesome": True}, } ) self.assertIsSubclass(CustomerModel, Decorated) @@ -2757,7 +2764,7 @@ def __new__( return super().__new__(cls, name, bases, namespace) Decorated = dataclass_transform( - order_default=True, field_descriptors=(Field,) + order_default=True, field_specifiers=(Field,) )(ModelMeta) class ModelBase(metaclass=Decorated): ... @@ -2772,7 +2779,8 @@ class CustomerModel(ModelBase, init=False): "eq_default": True, "order_default": True, "kw_only_default": False, - "field_descriptors": (Field,), + "field_specifiers": (Field,), + "kwargs": {}, } ) self.assertIsInstance(CustomerModel, Decorated) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 491109991..1e3e12829 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -1866,10 +1866,11 @@ def dataclass_transform( eq_default: bool = True, order_default: bool = False, kw_only_default: bool = False, - field_descriptors: typing.Tuple[ + field_specifiers: typing.Tuple[ typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]], ... ] = (), + **kwargs: typing.Any, ) -> typing.Callable[[T], T]: """Decorator that marks a function, class, or metaclass as providing dataclass-like behavior. @@ -1921,8 +1922,8 @@ class CustomerModel(ModelBase): assumed to be True or False if it is omitted by the caller. - ``kw_only_default`` indicates whether the ``kw_only`` parameter is assumed to be True or False if it is omitted by the caller. - - ``field_descriptors`` specifies a static list of supported classes - or functions, that describe fields, similar to ``dataclasses.field()``. + - ``field_specifiers`` specifies a static list of supported classes + or functions that describe fields, similar to ``dataclasses.field()``. At runtime, this decorator records its arguments in the ``__dataclass_transform__`` attribute on the decorated object. @@ -1935,7 +1936,8 @@ def decorator(cls_or_fn): "eq_default": eq_default, "order_default": order_default, "kw_only_default": kw_only_default, - "field_descriptors": field_descriptors, + "field_specifiers": field_specifiers, + "kwargs": kwargs, } return cls_or_fn return decorator From 2312c741154b61d5dfea3fa9f6b6f1adfb4b9aca Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:35:37 -0700 Subject: [PATCH 120/539] Add to the CHANGELOG (#1141) --- typing_extensions/CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 550a14631..569fe50c2 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,8 @@ # Unreleased +- Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. +- Improve "accepts only single type" error messages. +- Improve the distributed package. Patch by Marc Mueller (@cdce8p). - Update `typing_extensions.dataclass_transform` to rename the `field_descriptors` parameter to `field_specifiers` and accept arbitrary keyword arguments. From 783c8ca10ffb8b2cd00e62977c052f8d5a31bc3f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:58:25 -0700 Subject: [PATCH 121/539] Fix tests on Python 3.11 (#1139) - Defer to the PEP 646 implementation in typing.py on 3.11 - Adjust some tests accordingly. Noted a bug in https://github.com/python/cpython/pull/32341#issuecomment-1100466389 - typing._type_check() is more lenient in 3.11 and no longer rejects ints - The representation of the empty tuple type changed Tests pass for me on a 3.11 build from today now. --- typing_extensions/CHANGELOG | 1 + .../src/test_typing_extensions.py | 61 +++++---- typing_extensions/src/typing_extensions.py | 119 +++++++++--------- 3 files changed, 102 insertions(+), 79 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 569fe50c2..8900c5885 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,5 +1,6 @@ # Unreleased +- Re-export `typing.Unpack` and `typing.TypeVarTuple` on Python 3.11. - Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. - Improve "accepts only single type" error messages. - Improve the distributed package. Patch by Marc Mueller (@cdce8p). diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py index 8e11eb671..7f14f3f9d 100644 --- a/typing_extensions/src/test_typing_extensions.py +++ b/typing_extensions/src/test_typing_extensions.py @@ -32,6 +32,8 @@ # version of the typing module. TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0) TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) + +# 3.11 makes runtime type checks (_type_check) more lenient. TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) @@ -157,8 +159,9 @@ def test_exception(self): class ClassVarTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - ClassVar[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + ClassVar[1] with self.assertRaises(TypeError): ClassVar[int, str] with self.assertRaises(TypeError): @@ -201,8 +204,9 @@ def test_no_isinstance(self): class FinalTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - Final[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + Final[1] with self.assertRaises(TypeError): Final[int, str] with self.assertRaises(TypeError): @@ -245,8 +249,9 @@ def test_no_isinstance(self): class RequiredTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - Required[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + Required[1] with self.assertRaises(TypeError): Required[int, str] with self.assertRaises(TypeError): @@ -289,8 +294,9 @@ def test_no_isinstance(self): class NotRequiredTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - NotRequired[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + NotRequired[1] with self.assertRaises(TypeError): NotRequired[int, str] with self.assertRaises(TypeError): @@ -738,7 +744,10 @@ class C(Generic[T]): pass self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]]), (int, Callable[[Tuple[T, ...]], str])) self.assertEqual(get_args(Tuple[int, ...]), (int, ...)) - self.assertEqual(get_args(Tuple[()]), ((),)) + if TYPING_3_11_0: + self.assertEqual(get_args(Tuple[()]), ()) + else: + self.assertEqual(get_args(Tuple[()]), ((),)) self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three'])) self.assertEqual(get_args(List), ()) self.assertEqual(get_args(Tuple), ()) @@ -1731,10 +1740,12 @@ def test_typeddict_errors(self): isinstance(jim, Emp) with self.assertRaises(TypeError): issubclass(dict, Emp) - with self.assertRaises(TypeError): - TypedDict('Hi', x=1) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int), ('y', 1)]) + + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + TypedDict('Hi', x=1) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int), ('y', 1)]) with self.assertRaises(TypeError): TypedDict('Hi', [('x', int)], y=int) @@ -2313,11 +2324,12 @@ def test_invalid_uses(self): ): Concatenate[P, T] - with self.assertRaisesRegex( - TypeError, - 'each arg must be a type', - ): - Concatenate[1, P] + if not TYPING_3_11_0: + with self.assertRaisesRegex( + TypeError, + 'each arg must be a type', + ): + Concatenate[1, P] def test_basic_introspection(self): P = ParamSpec('P') @@ -2497,7 +2509,10 @@ def test_basic_plain(self): def test_repr(self): Ts = TypeVarTuple('Ts') - self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]') + if TYPING_3_11_0: + self.assertEqual(repr(Unpack[Ts]), '*Ts') + else: + self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]') def test_cannot_subclass_vars(self): with self.assertRaises(TypeError): @@ -2572,8 +2587,10 @@ class C(Generic[T1, T2, Unpack[Ts]]): pass self.assertEqual(C[int, str].__args__, (int, str)) self.assertEqual(C[int, str, float].__args__, (int, str, float)) self.assertEqual(C[int, str, float, bool].__args__, (int, str, float, bool)) - with self.assertRaises(TypeError): - C[int] + # TODO This should probably also fail on 3.11, pending changes to CPython. + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + C[int] class TypeVarTupleTests(BaseTestCase): @@ -2617,7 +2634,7 @@ def test_args_and_parameters(self): Ts = TypeVarTuple('Ts') t = Tuple[tuple(Ts)] - self.assertEqual(t.__args__, (Ts.__unpacked__,)) + self.assertEqual(t.__args__, (Unpack[Ts],)) self.assertEqual(t.__parameters__, (Ts,)) diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py index 1e3e12829..dc0388196 100644 --- a/typing_extensions/src/typing_extensions.py +++ b/typing_extensions/src/typing_extensions.py @@ -1673,7 +1673,9 @@ class Movie(TypedDict): """) -if sys.version_info[:2] >= (3, 9): +if hasattr(typing, "Unpack"): # 3.11+ + Unpack = typing.Unpack +elif sys.version_info[:2] >= (3, 9): class _UnpackSpecialForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -1729,84 +1731,87 @@ def _is_unpack(obj): return isinstance(obj, _UnpackAlias) -class TypeVarTuple: - """Type variable tuple. +if hasattr(typing, "TypeVarTuple"): # 3.11+ + TypeVarTuple = typing.TypeVarTuple +else: + class TypeVarTuple: + """Type variable tuple. - Usage:: + Usage:: - Ts = TypeVarTuple('Ts') + Ts = TypeVarTuple('Ts') - In the same way that a normal type variable is a stand-in for a single - type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* type such as - ``Tuple[int, str]``. + In the same way that a normal type variable is a stand-in for a single + type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* + type such as ``Tuple[int, str]``. - Type variable tuples can be used in ``Generic`` declarations. - Consider the following example:: + Type variable tuples can be used in ``Generic`` declarations. + Consider the following example:: - class Array(Generic[*Ts]): ... + class Array(Generic[*Ts]): ... - The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, - where ``T1`` and ``T2`` are type variables. To use these type variables - as type parameters of ``Array``, we must *unpack* the type variable tuple using - the star operator: ``*Ts``. The signature of ``Array`` then behaves - as if we had simply written ``class Array(Generic[T1, T2]): ...``. - In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows - us to parameterise the class with an *arbitrary* number of type parameters. + The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, + where ``T1`` and ``T2`` are type variables. To use these type variables + as type parameters of ``Array``, we must *unpack* the type variable tuple using + the star operator: ``*Ts``. The signature of ``Array`` then behaves + as if we had simply written ``class Array(Generic[T1, T2]): ...``. + In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows + us to parameterise the class with an *arbitrary* number of type parameters. - Type variable tuples can be used anywhere a normal ``TypeVar`` can. - This includes class definitions, as shown above, as well as function - signatures and variable annotations:: + Type variable tuples can be used anywhere a normal ``TypeVar`` can. + This includes class definitions, as shown above, as well as function + signatures and variable annotations:: - class Array(Generic[*Ts]): + class Array(Generic[*Ts]): - def __init__(self, shape: Tuple[*Ts]): - self._shape: Tuple[*Ts] = shape + def __init__(self, shape: Tuple[*Ts]): + self._shape: Tuple[*Ts] = shape - def get_shape(self) -> Tuple[*Ts]: - return self._shape + def get_shape(self) -> Tuple[*Ts]: + return self._shape - shape = (Height(480), Width(640)) - x: Array[Height, Width] = Array(shape) - y = abs(x) # Inferred type is Array[Height, Width] - z = x + x # ... is Array[Height, Width] - x.get_shape() # ... is tuple[Height, Width] + shape = (Height(480), Width(640)) + x: Array[Height, Width] = Array(shape) + y = abs(x) # Inferred type is Array[Height, Width] + z = x + x # ... is Array[Height, Width] + x.get_shape() # ... is tuple[Height, Width] - """ + """ - # Trick Generic __parameters__. - __class__ = typing.TypeVar + # Trick Generic __parameters__. + __class__ = typing.TypeVar - def __iter__(self): - yield self.__unpacked__ + def __iter__(self): + yield self.__unpacked__ - def __init__(self, name): - self.__name__ = name + def __init__(self, name): + self.__name__ = name - # for pickling: - try: - def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - def_mod = None - if def_mod != 'typing_extensions': - self.__module__ = def_mod + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod - self.__unpacked__ = Unpack[self] + self.__unpacked__ = Unpack[self] - def __repr__(self): - return self.__name__ + def __repr__(self): + return self.__name__ - def __hash__(self): - return object.__hash__(self) + def __hash__(self): + return object.__hash__(self) - def __eq__(self, other): - return self is other + def __eq__(self, other): + return self is other - def __reduce__(self): - return self.__name__ + def __reduce__(self): + return self.__name__ - def __init_subclass__(self, *args, **kwds): - if '_root' not in kwds: - raise TypeError("Cannot subclass special typing classes") + def __init_subclass__(self, *args, **kwds): + if '_root' not in kwds: + raise TypeError("Cannot subclass special typing classes") if hasattr(typing, "reveal_type"): From 9a39406fa45fbfa76eb5d35afb7863f5f727ad87 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 17 Apr 2022 14:21:53 -0700 Subject: [PATCH 122/539] prepare release 4.2.0 (#1144) --- typing_extensions/CHANGELOG | 2 +- typing_extensions/pyproject.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index 8900c5885..aa66e55c0 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -1,4 +1,4 @@ -# Unreleased +# Release 4.2.0 (April 17, 2022) - Re-export `typing.Unpack` and `typing.TypeVarTuple` on Python 3.11. - Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index fbd018017..217b9499a 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -6,8 +6,8 @@ build-backend = "flit_core.buildapi" # Project metadata [project] name = "typing_extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" readme = "README.rst" requires-python = ">=3.7" urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" @@ -28,7 +28,7 @@ keywords = [ ] # Classifiers list: https://pypi.org/classifiers/ classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: Python Software Foundation License", From 817c77fcef789e6287ffd21e1dc9835c28ec8751 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Mon, 18 Apr 2022 09:14:51 -0500 Subject: [PATCH 123/539] pyproject.toml: Add PyPI Project URLs (#1147) --- typing_extensions/pyproject.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 217b9499a..42cf758d4 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -10,7 +10,6 @@ version = "4.2.0" description = "Backported and Experimental Type Hints for Python 3.7+" readme = "README.rst" requires-python = ">=3.7" -urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" license.file = "LICENSE" keywords = [ "annotations", @@ -43,6 +42,14 @@ classifiers = [ "Topic :: Software Development" ] +[project.urls] +Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" +Repository = "https://github.com/python/typing" +Changes = "https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG" +Documentation = "https://typing.readthedocs.io/" +"Bug Tracker" = "https://github.com/python/typing/issues" +"Q & A" = "https://github.com/python/typing/discussions" + # Project metadata -- authors. Flit stores this as a list of dicts, so it can't # be inline above. [[project.authors]] From 465536968d321b3ba32c2bd51cffbf60c48d7af8 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 18 Apr 2022 16:30:22 +0200 Subject: [PATCH 124/539] Rename CHANGELOG to CHANGELOG.md (#1148) --- CONTRIBUTING.md | 2 +- typing_extensions/{CHANGELOG => CHANGELOG.md} | 0 typing_extensions/pyproject.toml | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename typing_extensions/{CHANGELOG => CHANGELOG.md} (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 095e8262b..e2b8d413f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ backwards-incompatible changes. - Ensure that GitHub Actions reports no errors. - Update the version number in `typing_extensions/pyproject.toml` and in - `typing_extensions/CHANGELOG`. + `typing_extensions/CHANGELOG.md`. - Make sure your environment is up to date diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG.md similarity index 100% rename from typing_extensions/CHANGELOG rename to typing_extensions/CHANGELOG.md diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 42cf758d4..9254f77e8 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -45,7 +45,7 @@ classifiers = [ [project.urls] Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" Repository = "https://github.com/python/typing" -Changes = "https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG" +Changes = "https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG.md" Documentation = "https://typing.readthedocs.io/" "Bug Tracker" = "https://github.com/python/typing/issues" "Q & A" = "https://github.com/python/typing/discussions" @@ -58,7 +58,7 @@ email = "levkivskyi@gmail.com" [tool.flit.sdist] include = [ - "CHANGELOG", + "CHANGELOG.md", "README.rst", "*/test*.py" ] From 2cf0d626d46f7b946875ea1a51417848443546b3 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 18 Apr 2022 16:59:03 +0200 Subject: [PATCH 125/539] Convert README.rst to README.md (#1150) Closes: #1149 --- typing_extensions/README.md | 143 +++++++++++++++++++++++++++++ typing_extensions/README.rst | 152 ------------------------------- typing_extensions/pyproject.toml | 14 +-- 3 files changed, 148 insertions(+), 161 deletions(-) create mode 100644 typing_extensions/README.md delete mode 100644 typing_extensions/README.rst diff --git a/typing_extensions/README.md b/typing_extensions/README.md new file mode 100644 index 000000000..54b93ddc5 --- /dev/null +++ b/typing_extensions/README.md @@ -0,0 +1,143 @@ +# Typing Extensions + +[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing) + +## Overview + +The `typing_extensions` module serves two related purposes: + +- Enable use of new type system features on older Python versions. For example, + `typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows + users on Python 3.6 through 3.9 to use it too. +- Enable experimentation with new type system PEPs before they are accepted and + added to the `typing` module. + +New features may be added to `typing_extensions` as soon as they are specified +in a PEP that has been added to the [python/peps](https://github.com/python/peps) +repository. If the PEP is accepted, the feature will then be added to `typing` +for the next CPython release. No typing PEP has been rejected so far, so we +haven't yet figured out how to deal with that possibility. + +Starting with version 4.0.0, `typing_extensions` uses +[Semantic Versioning](https://semver.org/). The +major version is incremented for all backwards-incompatible changes. +Therefore, it's safe to depend +on `typing_extensions` like this: `typing_extensions >=x.y, <(x+1)`, +where `x.y` is the first version that includes all features you need. + +`typing_extensions` supports Python versions 3.7 and higher. In the future, +support for older Python versions will be dropped some time after that version +reaches end of life. + +## Included items + +This module currently contains the following: + +- Experimental features + + - `@dataclass_transform()` (see PEP 681) + +- In `typing` since Python 3.11 + + - `assert_never` + - `assert_type` + - `clear_overloads` + - `get_overloads` + - `LiteralString` (see PEP 675) + - `Never` + - `NotRequired` (see PEP 655) + - `reveal_type` + - `Required` (see PEP 655) + - `Self` (see PEP 673) + - `TypeVarTuple` (see PEP 646) + - `Unpack` (see PEP 646) + +- In `typing` since Python 3.10 + + - `Concatenate` (see PEP 612) + - `ParamSpec` (see PEP 612) + - `ParamSpecArgs` (see PEP 612) + - `ParamSpecKwargs` (see PEP 612) + - `TypeAlias` (see PEP 613) + - `TypeGuard` (see PEP 647) + - `is_typeddict` + +- In `typing` since Python 3.9 + + - `Annotated` (see PEP 593) + +- In `typing` since Python 3.8 + + - `final` (see PEP 591) + - `Final` (see PEP 591) + - `Literal` (see PEP 586) + - `Protocol` (see PEP 544) + - `runtime_checkable` (see PEP 544) + - `TypedDict` (see PEP 589) + - `get_origin` (`typing_extensions` provides this function only in Python 3.7+) + - `get_args` (`typing_extensions` provides this function only in Python 3.7+) + +- In `typing` since Python 3.7 + + - `OrderedDict` + +- In `typing` since Python 3.5 or 3.6 (see [the typing documentation](https://docs.python.org/3.10/library/typing.html) for details) + + - `AsyncContextManager` + - `AsyncGenerator` + - `AsyncIterable` + - `AsyncIterator` + - `Awaitable` + - `ChainMap` + - `ClassVar` (see PEP 526) + - `ContextManager` + - `Coroutine` + - `Counter` + - `DefaultDict` + - `Deque` + - `NewType` + - `NoReturn` + - `overload` + - `Text` + - `Type` + - `TYPE_CHECKING` + - `get_type_hints` + +# Other Notes and Limitations + +Certain objects were changed after they were added to `typing`, and +`typing_extensions` provides a backport even on newer Python versions: + +- `TypedDict` does not store runtime information + about which (if any) keys are non-required in Python 3.8, and does not + honor the `total` keyword with old-style `TypedDict()` in Python + 3.9.0 and 3.9.1. +- `get_origin` and `get_args` lack support for `Annotated` in + Python 3.8 and lack support for `ParamSpecArgs` and `ParamSpecKwargs` + in 3.9. +- `@final` was changed in Python 3.11 to set the `.__final__` attribute. +- `@overload` was changed in Python 3.11 to make function overloads + introspectable at runtime. In order to access overloads with + `typing_extensions.get_overloads()`, you must use + `@typing_extensions.overload`. + +There are a few types whose interface was modified between different +versions of typing. For example, `typing.Sequence` was modified to +subclass `typing.Reversible` as of Python 3.5.3. + +These changes are _not_ backported to prevent subtle compatibility +issues when mixing the differing implementations of modified classes. + +Certain types have incorrect runtime behavior due to limitations of older +versions of the typing module: + +- `ParamSpec` and `Concatenate` will not work with `get_args` and + `get_origin`. Certain PEP 612 special cases in user-defined + `Generic`s are also not available. + +These types are only guaranteed to work for static type checking. + +## Running tests + +To run tests, navigate into the appropriate source directory and run +`test_typing_extensions.py`. diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst deleted file mode 100644 index 3a23b7559..000000000 --- a/typing_extensions/README.rst +++ /dev/null @@ -1,152 +0,0 @@ -================= -Typing Extensions -================= - -.. image:: https://badges.gitter.im/python/typing.svg - :alt: Chat at https://gitter.im/python/typing - :target: https://gitter.im/python/typing?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge - -Overview -======== - -The ``typing_extensions`` module serves two related purposes: - -- Enable use of new type system features on older Python versions. For example, - ``typing.TypeGuard`` is new in Python 3.10, but ``typing_extensions`` allows - users on Python 3.6 through 3.9 to use it too. -- Enable experimentation with new type system PEPs before they are accepted and - added to the ``typing`` module. - -New features may be added to ``typing_extensions`` as soon as they are specified -in a PEP that has been added to the `python/peps `_ -repository. If the PEP is accepted, the feature will then be added to ``typing`` -for the next CPython release. No typing PEP has been rejected so far, so we -haven't yet figured out how to deal with that possibility. - -Starting with version 4.0.0, ``typing_extensions`` uses -`Semantic Versioning `_. The -major version is incremented for all backwards-incompatible changes. -Therefore, it's safe to depend -on ``typing_extensions`` like this: ``typing_extensions >=x.y, <(x+1)``, -where ``x.y`` is the first version that includes all features you need. - -``typing_extensions`` supports Python versions 3.7 and higher. In the future, -support for older Python versions will be dropped some time after that version -reaches end of life. - -Included items -============== - -This module currently contains the following: - -- Experimental features - - - ``@dataclass_transform()`` (see PEP 681) - -- In ``typing`` since Python 3.11 - - - ``assert_never`` - - ``assert_type`` - - ``clear_overloads`` - - ``get_overloads`` - - ``LiteralString`` (see PEP 675) - - ``Never`` - - ``NotRequired`` (see PEP 655) - - ``reveal_type`` - - ``Required`` (see PEP 655) - - ``Self`` (see PEP 673) - - ``TypeVarTuple`` (see PEP 646) - - ``Unpack`` (see PEP 646) - -- In ``typing`` since Python 3.10 - - - ``Concatenate`` (see PEP 612) - - ``ParamSpec`` (see PEP 612) - - ``ParamSpecArgs`` (see PEP 612) - - ``ParamSpecKwargs`` (see PEP 612) - - ``TypeAlias`` (see PEP 613) - - ``TypeGuard`` (see PEP 647) - - ``is_typeddict`` - -- In ``typing`` since Python 3.9 - - - ``Annotated`` (see PEP 593) - -- In ``typing`` since Python 3.8 - - - ``final`` (see PEP 591) - - ``Final`` (see PEP 591) - - ``Literal`` (see PEP 586) - - ``Protocol`` (see PEP 544) - - ``runtime_checkable`` (see PEP 544) - - ``TypedDict`` (see PEP 589) - - ``get_origin`` (``typing_extensions`` provides this function only in Python 3.7+) - - ``get_args`` (``typing_extensions`` provides this function only in Python 3.7+) - -- In ``typing`` since Python 3.7 - - - ``OrderedDict`` - -- In ``typing`` since Python 3.5 or 3.6 (see `the typing documentation - `_ for details) - - - ``AsyncContextManager`` - - ``AsyncGenerator`` - - ``AsyncIterable`` - - ``AsyncIterator`` - - ``Awaitable`` - - ``ChainMap`` - - ``ClassVar`` (see PEP 526) - - ``ContextManager`` - - ``Coroutine`` - - ``Counter`` - - ``DefaultDict`` - - ``Deque`` - - ``NewType`` - - ``NoReturn`` - - ``overload`` - - ``Text`` - - ``Type`` - - ``TYPE_CHECKING`` - - ``get_type_hints`` - -Other Notes and Limitations -=========================== - -Certain objects were changed after they were added to ``typing``, and -``typing_extensions`` provides a backport even on newer Python versions: - -- ``TypedDict`` does not store runtime information - about which (if any) keys are non-required in Python 3.8, and does not - honor the "total" keyword with old-style ``TypedDict()`` in Python - 3.9.0 and 3.9.1. -- ``get_origin`` and ``get_args`` lack support for ``Annotated`` in - Python 3.8 and lack support for ``ParamSpecArgs`` and ``ParamSpecKwargs`` - in 3.9. -- ``@final`` was changed in Python 3.11 to set the ``.__final__`` attribute. -- ``@overload`` was changed in Python 3.11 to make function overloads - introspectable at runtime. In order to access overloads with - ``typing_extensions.get_overloads()``, you must use - ``@typing_extensions.overload``. - -There are a few types whose interface was modified between different -versions of typing. For example, ``typing.Sequence`` was modified to -subclass ``typing.Reversible`` as of Python 3.5.3. - -These changes are _not_ backported to prevent subtle compatibility -issues when mixing the differing implementations of modified classes. - -Certain types have incorrect runtime behavior due to limitations of older -versions of the typing module: - -- ``ParamSpec`` and ``Concatenate`` will not work with ``get_args`` and - ``get_origin``. Certain PEP 612 special cases in user-defined - ``Generic``\ s are also not available. - -These types are only guaranteed to work for static type checking. - -Running tests -============= - -To run tests, navigate into the appropriate source directory and run -``test_typing_extensions.py``. diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 9254f77e8..0d0cc39f8 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "flit_core.buildapi" name = "typing_extensions" version = "4.2.0" description = "Backported and Experimental Type Hints for Python 3.7+" -readme = "README.rst" +readme = "README.md" requires-python = ">=3.7" license.file = "LICENSE" keywords = [ @@ -23,7 +23,7 @@ keywords = [ "typechecking", "typehinting", "typehints", - "typing" + "typing", ] # Classifiers list: https://pypi.org/classifiers/ classifiers = [ @@ -39,11 +39,11 @@ classifiers = [ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", - "Topic :: Software Development" + "Topic :: Software Development", ] [project.urls] -Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" +Home = "https://github.com/python/typing/blob/master/typing_extensions/README.md" Repository = "https://github.com/python/typing" Changes = "https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG.md" Documentation = "https://typing.readthedocs.io/" @@ -57,9 +57,5 @@ name = "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" email = "levkivskyi@gmail.com" [tool.flit.sdist] -include = [ - "CHANGELOG.md", - "README.rst", - "*/test*.py" -] +include = ["CHANGELOG.md", "README.md", "*/test*.py"] exclude = [] From 2ab2eed0c6cb370e6f91da7e08c85e3699c8fead Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Tue, 19 Apr 2022 18:47:51 +0300 Subject: [PATCH 126/539] Remove deprecated py36 classifier (#1153) --- typing_extensions/pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 0d0cc39f8..1ba81ced0 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -34,7 +34,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", From a5adc015c105a182e2f58cdb69296c3c7e05a329 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 19 Apr 2022 19:37:03 +0200 Subject: [PATCH 127/539] Add a README.rst file back temporarily. (#1156) --- typing_extensions/README.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 typing_extensions/README.rst diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst new file mode 100644 index 000000000..d19c86e82 --- /dev/null +++ b/typing_extensions/README.rst @@ -0,0 +1 @@ +Please see `README.md `_ for the current version of the README file. From d69aa1ec1c0a508ac00cf024900aafdaffc43a27 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 19 Apr 2022 19:46:12 +0200 Subject: [PATCH 128/539] Change home URL to tree instead of README (#1157) The README is displayed below the file tree anyway, but this makes it easier to explore the repository. --- typing_extensions/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml index 1ba81ced0..e40ad8626 100644 --- a/typing_extensions/pyproject.toml +++ b/typing_extensions/pyproject.toml @@ -42,7 +42,7 @@ classifiers = [ ] [project.urls] -Home = "https://github.com/python/typing/blob/master/typing_extensions/README.md" +Home = "https://github.com/python/typing/tree/master/typing_extensions" Repository = "https://github.com/python/typing" Changes = "https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG.md" Documentation = "https://typing.readthedocs.io/" From 8ecc93fdae7ede5601e5318368af2cb5fedb29b5 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Thu, 28 Apr 2022 21:24:52 -0600 Subject: [PATCH 129/539] Make library docs more FAQ-like (#1164) --- docs/source/libraries.rst | 250 ++++++++++++++++++++++++++++---------- 1 file changed, 184 insertions(+), 66 deletions(-) diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 96e5370e0..72d161cf9 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -4,79 +4,200 @@ Typing Python Libraries *********************** -Much of Python’s popularity can be attributed to the rich collection of +Much of Python's popularity can be attributed to the rich collection of Python libraries available to developers. Authors of these libraries play an important role in improving the experience for Python developers. This document provides some recommendations and guidance for Python library authors. -These recommendations are intended to provide the following benefits: - -1. Consumers of libraries should have a great coding experience with - fast and accurate completion suggestions, class and function - documentation, signature help (including parameter default values), - hover text, and auto-imports. This should happen by default without - needing to download extra packages and without any special - configuration. These features should be consistent across the Python - ecosystem regardless of a developer’s choice of editor, IDE, notebook - environment, etc. -2. Consumers of libraries should be able to rely on complete and - accurate type information so static type checkers can detect and - report type inconsistencies and other violations of the interface - contract. -3. Library authors should be able to specify a well-defined interface - contract that is enforced by tools. This allows a library - implementation to evolve and improve without breaking consumers of - the library. -4. Library authors should have the benefits of static type checking to +Why provide type annotations? +============================= + +Providing type annotations has the following benefits: + +1. Type annotations help provide users of libraries a better coding + experience by enabling fast and accurate completion suggestions, class and + function documentation, signature help, hover text, auto-imports, etc. +2. Users of libraries are able to use static type checkers to detect issues + with their use of libraries. +3. Type annotations allow library authors to specify an interface contract that + is enforced by tools. This lets the library implementation evolve with less + fear that users are depending on implementation details. In the event of + changes to the library interface, type checkers are able to warn users when + their code is affected. +4. Library authors are able to use static type checking themselves to help produce high-quality, bug-free implementations. -Inlined Type Annotations and Type Stubs -======================================= +How to provide type annotations? +================================ -`PEP 561 `__ documents -several ways type information can be delivered for a library: inlined -type annotations, type stub files included in the package, a separate -companion type stub package, and type stubs in the typeshed repository. -Some of these options fall short on delivering the benefits above. We -therefore provide the following more specific guidance to library -authors. +:pep:`561` documents several ways type information can be provided for a +library: -.. note:: - All libraries should include inlined type annotations for the - functions, classes, methods, and constants that comprise the public - interface for the library. - -Inlined type annotations should be included directly within the source -code that ships with the package. Of the options listed in PEP 561, -inlined type annotations offer the most benefits. They typically require -the least effort to add and maintain, they are always consistent with -the implementation, and docstrings and default parameter values are -readily available, allowing language servers to enhance the development -experience. - -There are cases where inlined type annotations are not possible — most -notably when a library’s exposed functionality is implemented in a -language other than Python. +- inline type annotations (preferred) +- type stub files included in the package +- a separate companion type stub package +- type stubs in the typeshed repository + +Inline type annotations simply refers to the use of annotations within your +``.py`` files. In contrast, with type stub files, type information lives in +separate ``.pyi`` files; see :ref:`stubs` for more details. + +.. + TODO: link to a guide for writing stubs above + +We recommend using the inline type annotations approach, since it has the +following benefits: + +- Typically requires the least effort to add and maintain +- Users don't have to download additional packages +- Always remains consistent with the implementation +- Allows library authors to type check their own code +- Allows language servers to show users relevant details about the + implementation, such as docstrings and default parameter values + +However, there are cases where inlined type annotations are not possible — most +notably when a library's functionality is implemented in a language +other than Python. + +If you are not interested in providing type annotations for your library, you +could suggest users to contribute type stubs to the +`typeshed `__ project. + +Marking a package as providing type information +----------------------------------------------- + +As specified in :pep:`561`, tools will not treat your package as providing type +information unless it includes a special ``py.typed`` marker file. .. note:: - Libraries that expose symbols implemented in languages other than - Python should include stub (``.pyi``) files that describe the types for - those symbols. These stubs should also contain docstrings and default - parameter values. + Before marking a package as providing type information, it is best to ensure + that the library's interface is fully annotated. See :ref:`type_completeness` + for more details. + +Inline type annotations +^^^^^^^^^^^^^^^^^^^^^^^ + +A typical directory structure would look like: + +.. code-block:: text + + setup.py + my_great_package/ + __init__.py + stuff.py + py.typed + +It's important to ensure that the ``py.typed`` marker file is included in the +distributed package. If using ``setuptools``, this can be achieved like so: + +.. code-block:: python + + from setuptools import setup + + setup( + name="my_great_distribution", + version="0.1", + package_data={"my_great_package": ["py.typed"]}, + packages=["my_great_package"], + ) + + +Type stub files included in the package +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It's possible to include a mix of type stub files (``.pyi``) and inline type +annotations (``.py``). One use case for including type stub files in your +package is to provide types for extension modules in your library. A typical +directory structure would look like: + +.. code-block:: text + + setup.py + my_great_package/ + __init__.py + stuff.py + stuff.pyi + py.typed + +If using ``setuptools``, we can ensure the ``.pyi`` and ``py.typed`` files are +included like so: + +.. code-block:: python + + from setuptools import setup + + setup( + name="my_great_distribution", + version="0.1", + package_data={"my_great_package": ["py.typed", "stuff.pyi"]}, + packages=["my_great_package"], + ) -In many existing type stubs (such as those found in typeshed), default -parameter values are replaced with with ``...`` and all docstrings are -removed. We recommend that default values and docstrings remain within -the type stub file so language servers can display this information to -developers. +The presence of ``.pyi`` files does not affect the Python interpreter at runtime +in any way. However, static type checkers will only look at the ``.pyi`` file and +ignore the corresponding ``.py`` file. -Library Interface -================= +Companion type stub package +^^^^^^^^^^^^^^^^^^^^^^^^^^^ -`PEP 561 `__ indicates that a -``py.typed`` marker file must be included in the package if the author -wishes to support type checking of their code. +These are often referred to as "stub-only" packages. The name of the stub package +should be the name of the runtime package suffixed with ``-stubs``. The ``py.typed`` +marker file is not necessary for stub-only packages. This approach can be useful +to develop type stubs independently from your library. + +For example: + +.. code-block:: text + + setup.py + my_great_package-stubs/ + __init__.pyi + stuff.pyi + + +.. code-block:: python + + from setuptools import setup + + setup( + name="my_great_package-stubs", + version="0.1", + package_data={"my_great_package-stubs": ["__init__.pyi", "stuff.pyi"]}, + packages=["my_great_package-stubs"] + ) + + +Users are then able to install the stubs-only package separately to provide +types for the original library. + +Inclusion in sdist +^^^^^^^^^^^^^^^^^^ + +Note that to ensure inclusion of ``.pyi`` and ``py.typed`` files in an sdist +(.tar.gz archive), you may also need to modify the inclusion rules in your +``MANIFEST.in`` (see the +`packaging guide `__ +for more details on ``MANIFEST.in``). For example: + +.. code-block:: text + + global-include *.pyi + global-include py.typed + +.. _type_completeness: + +How much of my library needs types? +=================================== + +A "py.typed" library should aim to be type complete so that type +checking and inspection can work to their full extent. Here we say that a +library is “type complete” if all of the symbols +that comprise its interface have type annotations that refer to types +that are fully known. Private symbols are exempt. + +Library interface (public and private symbols) +---------------------------------------------- If a ``py.typed`` module is present, a type checker will treat all modules within that package (i.e. all files that end in ``.py`` or ``.pyi``) as @@ -119,13 +240,7 @@ determine the value of ``__all__``. - ``__all__.remove('a')`` Type Completeness -================= - -A “py.typed” library should aim to be type complete so that type -checking and inspection can work to their full extent. Here we say that a -library is “type complete” if all of the symbols -that comprise its interface have type annotations that refer to types -that are fully known. Private symbols are exempt. +----------------- The following are best practice recommendations for how to define “type complete”: @@ -282,6 +397,9 @@ Examples of known and unknown types class DictSubclass(dict): pass +.. + TODO: consider moving best practices to their own page? + Best Practices for Inlined Types ================================ From 11cf5d685217b63853640ebe57c61d6f9d78396b Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Thu, 28 Apr 2022 21:56:14 -0600 Subject: [PATCH 130/539] Minor changes to stub reference (#1166) --- docs/source/stubs.rst | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index ffd6c9b86..215c1a311 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -77,8 +77,9 @@ see PEP 561 [#pep561]_ for more information. The includes stubs for Python's standard library and several third-party packages. The stubs for the standard library are usually distributed with type checkers and do not require separate installation. Stubs for third-party libraries are -available on the `Python Package Index `_. A stub package for -a library called ``widget`` will be called ``types-widget``. +available on the `Python Package Index `_. +By convention, a stub package for a library called ``widget`` would be named +``types-widget``. Supported Constructs ==================== @@ -491,7 +492,7 @@ do not get interpreted by type checkers as enum members. Yes:: from enum import Enum - + class Color(Enum): RED: int BLUE: int @@ -508,7 +509,7 @@ Yes:: No:: from enum import Enum - + class Color(Enum): RED: int BLUE: int @@ -1005,27 +1006,6 @@ with the current type system or using the correct type is unergonomic. Use ``float`` instead of ``int | float``. Use ``None`` instead of ``Literal[None]``. -For argument types, -use ``bytes`` instead of ``bytes | memoryview | bytearray``. - -Use ``Text`` in stubs that support Python 2 when something accepts both -``str`` and ``unicode``. Avoid using ``Text`` in stubs or branches for -Python 3 only. - -Yes:: - - if sys.version_info < (3,): - def foo(s: Text) -> None: ... - else: - def foo(s: str, *, i: int) -> None: ... - def bar(s: Text) -> None: ... - -No:: - - if sys.version_info < (3,): - def foo(s: unicode) -> None: ... - else: - def foo(s: Text, *, i: int) -> None: ... For arguments, prefer protocols and abstract types (``Mapping``, ``Sequence``, ``Iterable``, etc.). If an argument accepts literally any value, From 8817cb32ea876314e914da7e428f8edba940754b Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Fri, 29 Apr 2022 17:31:08 -0600 Subject: [PATCH 131/539] Advice for writing and maintaining stub files (#1167) --- docs/source/guides.rst | 1 + docs/source/libraries.rst | 6 +- docs/source/quality.rst | 2 +- docs/source/writing_stubs.rst | 108 ++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 docs/source/writing_stubs.rst diff --git a/docs/source/guides.rst b/docs/source/guides.rst index c9af381c9..e85c5eb02 100644 --- a/docs/source/guides.rst +++ b/docs/source/guides.rst @@ -7,4 +7,5 @@ Type System Guides :caption: Contents: libraries + writing_stubs unreachable diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 72d161cf9..30049d1a2 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -41,10 +41,8 @@ library: Inline type annotations simply refers to the use of annotations within your ``.py`` files. In contrast, with type stub files, type information lives in -separate ``.pyi`` files; see :ref:`stubs` for more details. - -.. - TODO: link to a guide for writing stubs above +separate ``.pyi`` files; see :ref:`stubs` and :ref:`writing_stubs` for more +details. We recommend using the inline type annotations approach, since it has the following benefits: diff --git a/docs/source/quality.rst b/docs/source/quality.rst index 328550c90..4b3bd35b3 100644 --- a/docs/source/quality.rst +++ b/docs/source/quality.rst @@ -1,4 +1,4 @@ -.. _tools: +.. _testing: ******************************************** Testing and Ensuring Type Annotation Quality diff --git a/docs/source/writing_stubs.rst b/docs/source/writing_stubs.rst new file mode 100644 index 000000000..381bd73a3 --- /dev/null +++ b/docs/source/writing_stubs.rst @@ -0,0 +1,108 @@ +.. _writing_stubs: + +********************************** +Writing and Maintaining Stub Files +********************************** + +Stub files are a means of providing type information for Python modules. +For a full reference, refer to :ref:`stubs`. + +Maintaining stubs can be a little cumbersome because they are separated from the +implementation. This page lists some tools that make writing and maintaining +stubs less painful. + +Tools for generating stubs +========================== + +stubgen +------- + +stubgen is a tool bundled with `mypy `__ +that can be used to generate basic stubs. These stubs serve as a +basic starting point; most types will default to ``Any``. + +.. code-block:: console + + stubgen -p my_great_package + +For more details, see `stubgen docs `__. + +pyright +------- + +pyright contains a tool that generates basic stubs. Like stubgen, these generated +stubs serve more as a starting point. + +.. code-block:: console + + pyright --createstub my_great_package + +For more details, see `pyright docs `__. + +monkeytype +---------- + +monkeytype takes a slightly different approach — you run your code (perhaps via +your tests) and monkeytype collects the types it observes at runtime to generate +stubs. + +.. code-block:: console + + monkeytype run script.py + monkeytype stub my_great_package + +For more details, see `monkeytype docs `__. + +Tools for maintaining stubs +=========================== + +stubtest +-------- + +stubtest is a tool bundled with `mypy `__. + +stubtest finds inconsistencies between stub files and the implementation. It +does this by comparing stub definitions to what it finds from importing your +code and using runtime introspection (via the ``inspect`` module). + +.. code-block:: console + + stubtest my_great_package + +For more details, see `stubtest docs `__. + +flake8-pyi +---------- + +flake8-pyi is a `flake8 `__ plugin that +lints common issues in stub files. + +.. code-block:: console + + flake8 my_great_package + +For more details, see `flake8-pyi docs `__. + +Running a type checker on the stubs +----------------------------------- + +Simply running a type checker on the stubs can catch several issues, from simple +things like detecting missing annotations to more complex things like ensuring +Liskov substitutability or detecting problematic overloads. + +It may be instructive to examine `typeshed `__'s +`setup for testing stubs `__. + +.. + TODO: consider adding examples and configurations for specific type checkers + +Type checking usage of your package +----------------------------------- + +If you have access to a codebase that uses your package — perhaps tests for your +package — running a type checker against it can help you detect issues, +particularly with false positives. + +If your package has some particularly complex aspects, you could even consider +writing dedicated typing tests for tricky definitions. For more details, see +:ref:`testing`. From 7c5e767198f3d39bdc929eee5e342fc5877fbb2a Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 1 May 2022 05:01:52 -0600 Subject: [PATCH 132/539] Fix broken headers (#1171) --- docs/source/libraries.rst | 32 ++++++++++++++++---------------- docs/source/unreachable.rst | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 30049d1a2..201cf4138 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -294,7 +294,7 @@ is obvious from the context: ``__slots__``. Examples of known and unknown types -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code:: python @@ -402,7 +402,7 @@ Best Practices for Inlined Types ================================ Wide vs. Narrow Types -~~~~~~~~~~~~~~~~~~~~~ +--------------------- In type theory, when comparing two types that are related to each other, the “wider” type is the one that is more general, and the “narrower” @@ -433,7 +433,7 @@ parameter typed as ``List[Union[str, int]]`` is much more restrictive and accepts only a ``List[Union[str, int]]``. Overloads -~~~~~~~~~ +--------- If a function or method can return multiple different types and those types can be determined based on the presence or types of certain @@ -443,7 +443,7 @@ are used within a “.py” file, they must appear prior to the function implementation, which should not have an ``@overload`` decorator. Keyword-only Parameters -~~~~~~~~~~~~~~~~~~~~~~~ +----------------------- If a function or method is intended to take parameters that are specified only by name, use the keyword-only separator (``*``). @@ -454,7 +454,7 @@ specified only by name, use the keyword-only separator (``*``). ... Annotating Decorators -~~~~~~~~~~~~~~~~~~~~~ +--------------------- Decorators modify the behavior of a class or a function. Providing annotations for decorators is straightforward if the decorator retains @@ -491,7 +491,7 @@ provide signature assistance. As such, library authors are discouraged from creating decorators that mutate function signatures in this manner. Generic Classes and Functions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------------- Classes and functions that can operate in a generic manner on various types should declare themselves as generic using the mechanisms @@ -501,7 +501,7 @@ should be private to the file that declares it, and should therefore begin with an underscore. Type Aliases -~~~~~~~~~~~~ +------------ Type aliases are symbols that refer to other types. Generic type aliases (those that refer to unspecialized generic classes) are supported by @@ -526,7 +526,7 @@ annotation. StrOrInt: TypeAlias = Union[str, int] Abstract Classes and Methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------- Classes that must be subclassed should derive from ``ABC``, and methods or properties that must be overridden should be decorated with the @@ -553,7 +553,7 @@ exception. raise NotImplementedError() Final Classes and Methods -~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------- Classes that are not intended to be subclassed should be decorated as ``@final`` as described in `PEP @@ -562,7 +562,7 @@ can also be used to specify methods that cannot be overridden by subclasses. Literals -~~~~~~~~ +-------- Type annotations should make use of the Literal type where appropriate, as described in `PEP 586 `__. @@ -570,7 +570,7 @@ Literals allow for more type specificity than their non-literal counterparts. Constants -~~~~~~~~~ +--------- Constant values (those that are read-only) can be specified using the Final annotation as described in `PEP @@ -601,7 +601,7 @@ type annotation would be redundant. LATEST_VERSION: Final[Tuple[int, int]] = (4, 5) Typed Dictionaries, Data Classes, and Named Tuples -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------------------------------- If your library runs only on newer versions of Python, you are encouraged to use some of the new type-friendly classes. @@ -628,7 +628,7 @@ section documents several techniques that can be used to add types while maintaining backward compatibility. Quoted Annotations -~~~~~~~~~~~~~~~~~~ +------------------ Type annotations for variables, parameters, and return types can be placed in quotes. The Python interpreter will then ignore them, whereas @@ -643,7 +643,7 @@ a type checker will interpret them as type annotations. return self._config Type Comment Annotations -~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------ Python 3.0 introduced syntax for parameter and return type annotations, as specified in `PEP 484 `__. @@ -678,7 +678,7 @@ type: . ... typing_extensions -~~~~~~~~~~~~~~~~~ +----------------- New type features that require runtime support are typically included in the stdlib ``typing`` module. Where possible, these new features are @@ -686,7 +686,7 @@ back-ported to a runtime library called ``typing_extensions`` that works with older Python runtimes. TYPE_CHECKING -~~~~~~~~~~~~~ +------------- The ``typing`` module exposes a variable called ``TYPE_CHECKING`` which has a value of False within the Python runtime but a value of True when diff --git a/docs/source/unreachable.rst b/docs/source/unreachable.rst index 16c37d8f4..fa64b656d 100644 --- a/docs/source/unreachable.rst +++ b/docs/source/unreachable.rst @@ -99,7 +99,7 @@ You can also use ``assert_never()`` with a sequence of ``if`` statements: assert_never(op) Marking Code as Unreachable -======================= +=========================== Sometimes a piece of code is unreachable, but the type system is not powerful enough to recognize that. For example, consider a function that From c468c694f4c92cf799892e9cbb6ef283c382a993 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 19 May 2022 07:14:37 -0700 Subject: [PATCH 133/539] Remove typing-extensions (#1189) --- .flake8 | 4 - .flake8-tests | 28 - .github/workflows/ci.yml | 33 - .github/workflows/package.yml | 71 - README.md | 31 +- typing_extensions/CHANGELOG.md | 81 - typing_extensions/README.md | 143 - typing_extensions/README.rst | 2 +- typing_extensions/pyproject.toml | 60 - .../src/test_typing_extensions.py | 2897 ----------------- typing_extensions/src/typing_extensions.py | 1960 ----------- typing_extensions/tox.ini | 7 - 12 files changed, 17 insertions(+), 5300 deletions(-) delete mode 100644 .flake8-tests delete mode 100644 .github/workflows/package.yml delete mode 100644 typing_extensions/CHANGELOG.md delete mode 100644 typing_extensions/README.md delete mode 100644 typing_extensions/pyproject.toml delete mode 100644 typing_extensions/src/test_typing_extensions.py delete mode 100644 typing_extensions/src/typing_extensions.py delete mode 100644 typing_extensions/tox.ini diff --git a/.flake8 b/.flake8 index 53cf55a09..ff64f42c1 100644 --- a/.flake8 +++ b/.flake8 @@ -9,7 +9,3 @@ ignore = E129, # consistency with mypy W504 -exclude = - # tests have more relaxed formatting rules - # and its own specific config in .flake8-tests - typing_extensions/src/test_typing_extensions.py, diff --git a/.flake8-tests b/.flake8-tests deleted file mode 100644 index 5a97fe897..000000000 --- a/.flake8-tests +++ /dev/null @@ -1,28 +0,0 @@ -# This configuration is specific to test_*.py; you need to invoke it -# by specifically naming this config, like this: -# -# $ flake8 --config=.flake8-tests [SOURCES] -# -# This will be possibly merged in the future. - -[flake8] -max-line-length = 100 -ignore = - # temporary ignores until we sort it out - B017, - E302, - E303, - E306, - E501, - E701, - E704, - F722, - F811, - F821, - F841, - W503, - # irrelevant plugins - B3, - DW12, - # consistency with mypy - W504 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 302b2cae6..14cb706e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,36 +8,6 @@ permissions: contents: read jobs: - tests: - name: Run tests - - strategy: - fail-fast: false - matrix: - # We try to test on the earliest available bugfix release of each - # Python version, because typing sometimes changed between bugfix releases. - # For available versions, see: - # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json - python-version: ["3.7", "3.7.1", "3.8", "3.8.0", "3.9", "3.9.0", "3.10", "3.10.0", "3.11-dev"] - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Test typing_extensions - continue-on-error: ${{ matrix.python-version == '3.11-dev' }} - run: | - # Be wary of running `pip install` here, since it becomes easy for us to - # accidentally pick up typing_extensions as installed by a dependency - cd typing_extensions/src - python -m unittest test_typing_extensions.py - linting: name: Lint @@ -59,6 +29,3 @@ jobs: - name: Lint implementation run: flake8 - - - name: Lint tests - run: flake8 --config=.flake8-tests typing_extensions/src/test_typing_extensions.py diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml deleted file mode 100644 index 25f958684..000000000 --- a/.github/workflows/package.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Test packaging - -on: - push: - pull_request: - -permissions: - contents: read - -jobs: - wheel: - name: Test wheel install - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3 - - - name: Install pypa/build - run: | - # Be wary of running `pip install` here, since it becomes easy for us to - # accidentally pick up typing_extensions as installed by a dependency - python -m pip install --upgrade build - python -m pip list - - - name: Build and install wheel - run: | - cd typing_extensions - python -m build . - export path_to_file=$(find dist -type f -name "typing_extensions-*.whl") - echo "::notice::Installing wheel: $path_to_file" - pip install -vvv $path_to_file - python -m pip list - - - name: Attempt to import typing_extensions - run: python -c "import typing_extensions; print(typing_extensions.__all__)" - - sdist: - name: Test sdist install - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3 - - - name: Install pypa/build - run: | - # Be wary of running `pip install` here, since it becomes easy for us to - # accidentally pick up typing_extensions as installed by a dependency - python -m pip install --upgrade build - python -m pip list - - - name: Build and install sdist - run: | - cd typing_extensions - python -m build . - export path_to_file=$(find dist -type f -name "typing_extensions-*.tar.gz") - echo "::notice::Installing sdist: $path_to_file" - pip install -vvv $path_to_file - python -m pip list - - - name: Attempt to import typing_extensions - run: python -c "import typing_extensions; print(typing_extensions.__all__)" diff --git a/README.md b/README.md index 64b91182b..edb902fe5 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ [![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Static Typing for Python -======================== +# Static Typing for Python -Documentation and Support -------------------------- +## Documentation and Support The documentation for Python's static typing can be found at [typing.readthedocs.io](https://typing.readthedocs.io/). You can get @@ -16,26 +14,29 @@ Improvements to the type system should be discussed on the mailing list, although the [issues](https://github.com/python/typing/issues) in this repository contain some historic discussions. -Repository Content ------------------- +## Repository Content This GitHub repository is used for several things: -- The `typing_extensions` module lives in the - [typing\_extensions](./typing_extensions) directory. - - The documentation at [typing.readthedocs.io](https://typing.readthedocs.io/) is maintained in the [docs directory](./docs). - A [discussion forum](https://github.com/python/typing/discussions) for typing-related user help is hosted here. -Historically, this repository hosted a backport of the -[`typing` module](https://docs.python.org/3/library/typing.html) for older -Python versions. The last released version, supporting Python 2.7 and 3.4, -is [available at PyPI](https://pypi.org/project/typing/). +Historically, this repository also hosted: + +- The `typing_extensions` package, which now lives in the + [typing_extensions](https://github.com/python/typing_extensions) repo. + It used to be in the `typing_extensions` directory. + +- A backport of the + [`typing` module](https://docs.python.org/3/library/typing.html) for older + Python versions. It was removed after all Python versions that lack `typing` + in the standard library reached end of life. The last released version, + supporting Python 2.7 and 3.4, + is [available at PyPI](https://pypi.org/project/typing/). -Workflow --------- +## Workflow See [CONTRIBUTING.md](/CONTRIBUTING.md) for more. diff --git a/typing_extensions/CHANGELOG.md b/typing_extensions/CHANGELOG.md deleted file mode 100644 index aa66e55c0..000000000 --- a/typing_extensions/CHANGELOG.md +++ /dev/null @@ -1,81 +0,0 @@ -# Release 4.2.0 (April 17, 2022) - -- Re-export `typing.Unpack` and `typing.TypeVarTuple` on Python 3.11. -- Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. -- Improve "accepts only single type" error messages. -- Improve the distributed package. Patch by Marc Mueller (@cdce8p). -- Update `typing_extensions.dataclass_transform` to rename the - `field_descriptors` parameter to `field_specifiers` and accept - arbitrary keyword arguments. -- Add `typing_extensions.get_overloads` and - `typing_extensions.clear_overloads`, and add registry support to - `typing_extensions.overload`. Backport from python/cpython#89263. -- Add `typing_extensions.assert_type`. Backport from bpo-46480. -- Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). - -# Release 4.1.1 (February 13, 2022) - -- Fix importing `typing_extensions` on Python 3.7.0 and 3.7.1. Original - patch by Nikita Sobolev (@sobolevn). - -# Release 4.1.0 (February 12, 2022) - -- Runtime support for PEP 646, adding `typing_extensions.TypeVarTuple` - and `typing_extensions.Unpack`. -- Add interaction of `Required` and `NotRequired` with `__required_keys__`, - `__optional_keys__` and `get_type_hints()`. Patch by David Cabot (@d-k-bo). -- Runtime support for PEP 675 and `typing_extensions.LiteralString`. -- Add `Never` and `assert_never`. Backport from bpo-46475. -- `ParamSpec` args and kwargs are now equal to themselves. Backport from - bpo-46676. Patch by Gregory Beauregard (@GBeauregard). -- Add `reveal_type`. Backport from bpo-46414. -- Runtime support for PEP 681 and `typing_extensions.dataclass_transform`. -- `Annotated` can now wrap `ClassVar` and `Final`. Backport from - bpo-46491. Patch by Gregory Beauregard (@GBeauregard). -- Add missed `Required` and `NotRequired` to `__all__`. Patch by - Yuri Karabas (@uriyyo). -- The `@final` decorator now sets the `__final__` attribute on the - decorated object to allow runtime introspection. Backport from - bpo-46342. -- Add `is_typeddict`. Patch by Chris Moradi (@chrismoradi) and James - Hilton-Balfe (@Gobot1234). - -# Release 4.0.1 (November 30, 2021) - -- Fix broken sdist in release 4.0.0. Patch by Adam Turner (@AA-Turner). -- Fix equality comparison for `Required` and `NotRequired`. Patch by - Jelle Zijlstra (@jellezijlstra). -- Fix usage of `Self` as a type argument. Patch by Chris Wesseling - (@CharString) and James Hilton-Balfe (@Gobot1234). - -# Release 4.0.0 (November 14, 2021) - -- Starting with version 4.0.0, typing_extensions uses Semantic Versioning. - See the README for more information. -- Dropped support for Python versions 3.5 and older, including Python 2.7. -- Simplified backports for Python 3.6.0 and newer. Patch by Adam Turner (@AA-Turner). - -## Added in version 4.0.0 - -- Runtime support for PEP 673 and `typing_extensions.Self`. Patch by - James Hilton-Balfe (@Gobot1234). -- Runtime support for PEP 655 and `typing_extensions.Required` and `NotRequired`. - Patch by David Foster (@davidfstr). - -## Removed in version 4.0.0 - -The following non-exported but non-private names have been removed as they are -unneeded for supporting Python 3.6 and newer. - -- TypingMeta -- OLD_GENERICS -- SUBS_TREE -- HAVE_ANNOTATED -- HAVE_PROTOCOLS -- V_co -- VT_co - -# Previous releases - -Prior to release 4.0.0 we did not provide a changelog. Please check -the Git history for details. diff --git a/typing_extensions/README.md b/typing_extensions/README.md deleted file mode 100644 index 54b93ddc5..000000000 --- a/typing_extensions/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# Typing Extensions - -[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing) - -## Overview - -The `typing_extensions` module serves two related purposes: - -- Enable use of new type system features on older Python versions. For example, - `typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows - users on Python 3.6 through 3.9 to use it too. -- Enable experimentation with new type system PEPs before they are accepted and - added to the `typing` module. - -New features may be added to `typing_extensions` as soon as they are specified -in a PEP that has been added to the [python/peps](https://github.com/python/peps) -repository. If the PEP is accepted, the feature will then be added to `typing` -for the next CPython release. No typing PEP has been rejected so far, so we -haven't yet figured out how to deal with that possibility. - -Starting with version 4.0.0, `typing_extensions` uses -[Semantic Versioning](https://semver.org/). The -major version is incremented for all backwards-incompatible changes. -Therefore, it's safe to depend -on `typing_extensions` like this: `typing_extensions >=x.y, <(x+1)`, -where `x.y` is the first version that includes all features you need. - -`typing_extensions` supports Python versions 3.7 and higher. In the future, -support for older Python versions will be dropped some time after that version -reaches end of life. - -## Included items - -This module currently contains the following: - -- Experimental features - - - `@dataclass_transform()` (see PEP 681) - -- In `typing` since Python 3.11 - - - `assert_never` - - `assert_type` - - `clear_overloads` - - `get_overloads` - - `LiteralString` (see PEP 675) - - `Never` - - `NotRequired` (see PEP 655) - - `reveal_type` - - `Required` (see PEP 655) - - `Self` (see PEP 673) - - `TypeVarTuple` (see PEP 646) - - `Unpack` (see PEP 646) - -- In `typing` since Python 3.10 - - - `Concatenate` (see PEP 612) - - `ParamSpec` (see PEP 612) - - `ParamSpecArgs` (see PEP 612) - - `ParamSpecKwargs` (see PEP 612) - - `TypeAlias` (see PEP 613) - - `TypeGuard` (see PEP 647) - - `is_typeddict` - -- In `typing` since Python 3.9 - - - `Annotated` (see PEP 593) - -- In `typing` since Python 3.8 - - - `final` (see PEP 591) - - `Final` (see PEP 591) - - `Literal` (see PEP 586) - - `Protocol` (see PEP 544) - - `runtime_checkable` (see PEP 544) - - `TypedDict` (see PEP 589) - - `get_origin` (`typing_extensions` provides this function only in Python 3.7+) - - `get_args` (`typing_extensions` provides this function only in Python 3.7+) - -- In `typing` since Python 3.7 - - - `OrderedDict` - -- In `typing` since Python 3.5 or 3.6 (see [the typing documentation](https://docs.python.org/3.10/library/typing.html) for details) - - - `AsyncContextManager` - - `AsyncGenerator` - - `AsyncIterable` - - `AsyncIterator` - - `Awaitable` - - `ChainMap` - - `ClassVar` (see PEP 526) - - `ContextManager` - - `Coroutine` - - `Counter` - - `DefaultDict` - - `Deque` - - `NewType` - - `NoReturn` - - `overload` - - `Text` - - `Type` - - `TYPE_CHECKING` - - `get_type_hints` - -# Other Notes and Limitations - -Certain objects were changed after they were added to `typing`, and -`typing_extensions` provides a backport even on newer Python versions: - -- `TypedDict` does not store runtime information - about which (if any) keys are non-required in Python 3.8, and does not - honor the `total` keyword with old-style `TypedDict()` in Python - 3.9.0 and 3.9.1. -- `get_origin` and `get_args` lack support for `Annotated` in - Python 3.8 and lack support for `ParamSpecArgs` and `ParamSpecKwargs` - in 3.9. -- `@final` was changed in Python 3.11 to set the `.__final__` attribute. -- `@overload` was changed in Python 3.11 to make function overloads - introspectable at runtime. In order to access overloads with - `typing_extensions.get_overloads()`, you must use - `@typing_extensions.overload`. - -There are a few types whose interface was modified between different -versions of typing. For example, `typing.Sequence` was modified to -subclass `typing.Reversible` as of Python 3.5.3. - -These changes are _not_ backported to prevent subtle compatibility -issues when mixing the differing implementations of modified classes. - -Certain types have incorrect runtime behavior due to limitations of older -versions of the typing module: - -- `ParamSpec` and `Concatenate` will not work with `get_args` and - `get_origin`. Certain PEP 612 special cases in user-defined - `Generic`s are also not available. - -These types are only guaranteed to work for static type checking. - -## Running tests - -To run tests, navigate into the appropriate source directory and run -`test_typing_extensions.py`. diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index d19c86e82..d9a860f16 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -1 +1 @@ -Please see `README.md `_ for the current version of the README file. +Please see `the typing_extensions repo `_ for the current version of the README file. diff --git a/typing_extensions/pyproject.toml b/typing_extensions/pyproject.toml deleted file mode 100644 index e40ad8626..000000000 --- a/typing_extensions/pyproject.toml +++ /dev/null @@ -1,60 +0,0 @@ -# Build system requirements. -[build-system] -requires = ["flit_core >=3.4,<4"] -build-backend = "flit_core.buildapi" - -# Project metadata -[project] -name = "typing_extensions" -version = "4.2.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -readme = "README.md" -requires-python = ">=3.7" -license.file = "LICENSE" -keywords = [ - "annotations", - "backport", - "checker", - "checking", - "function", - "hinting", - "hints", - "type", - "typechecking", - "typehinting", - "typehints", - "typing", -] -# Classifiers list: https://pypi.org/classifiers/ -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: Python Software Foundation License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Topic :: Software Development", -] - -[project.urls] -Home = "https://github.com/python/typing/tree/master/typing_extensions" -Repository = "https://github.com/python/typing" -Changes = "https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG.md" -Documentation = "https://typing.readthedocs.io/" -"Bug Tracker" = "https://github.com/python/typing/issues" -"Q & A" = "https://github.com/python/typing/discussions" - -# Project metadata -- authors. Flit stores this as a list of dicts, so it can't -# be inline above. -[[project.authors]] -name = "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" -email = "levkivskyi@gmail.com" - -[tool.flit.sdist] -include = ["CHANGELOG.md", "README.md", "*/test*.py"] -exclude = [] diff --git a/typing_extensions/src/test_typing_extensions.py b/typing_extensions/src/test_typing_extensions.py deleted file mode 100644 index 7f14f3f9d..000000000 --- a/typing_extensions/src/test_typing_extensions.py +++ /dev/null @@ -1,2897 +0,0 @@ -import sys -import os -import abc -import contextlib -import collections -from collections import defaultdict -import collections.abc -from functools import lru_cache -import inspect -import pickle -import subprocess -import types -from unittest import TestCase, main, skipUnless, skipIf -from unittest.mock import patch -from test import ann_module, ann_module2, ann_module3 -import typing -from typing import TypeVar, Optional, Union, Any, AnyStr -from typing import T, KT, VT # Not in __all__. -from typing import Tuple, List, Dict, Iterable, Iterator, Callable -from typing import Generic, NamedTuple -from typing import no_type_check -import typing_extensions -from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self -from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard -from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired -from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, final, is_typeddict -from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString -from typing_extensions import assert_type, get_type_hints, get_origin, get_args -from typing_extensions import clear_overloads, get_overloads, overload - -# Flags used to mark tests that only apply after a specific -# version of the typing module. -TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0) -TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) - -# 3.11 makes runtime type checks (_type_check) more lenient. -TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) - - -class BaseTestCase(TestCase): - def assertIsSubclass(self, cls, class_or_tuple, msg=None): - if not issubclass(cls, class_or_tuple): - message = f'{cls!r} is not a subclass of {repr(class_or_tuple)}' - if msg is not None: - message += f' : {msg}' - raise self.failureException(message) - - def assertNotIsSubclass(self, cls, class_or_tuple, msg=None): - if issubclass(cls, class_or_tuple): - message = f'{cls!r} is a subclass of {repr(class_or_tuple)}' - if msg is not None: - message += f' : {msg}' - raise self.failureException(message) - - -class Employee: - pass - - -class BottomTypeTestsMixin: - bottom_type: ClassVar[Any] - - def test_equality(self): - self.assertEqual(self.bottom_type, self.bottom_type) - self.assertIs(self.bottom_type, self.bottom_type) - self.assertNotEqual(self.bottom_type, None) - - def test_get_origin(self): - self.assertIs(get_origin(self.bottom_type), None) - - def test_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(42, self.bottom_type) - - def test_subclass_type_error(self): - with self.assertRaises(TypeError): - issubclass(Employee, self.bottom_type) - with self.assertRaises(TypeError): - issubclass(NoReturn, self.bottom_type) - - def test_not_generic(self): - with self.assertRaises(TypeError): - self.bottom_type[int] - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class A(self.bottom_type): - pass - with self.assertRaises(TypeError): - class A(type(self.bottom_type)): - pass - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - self.bottom_type() - with self.assertRaises(TypeError): - type(self.bottom_type)() - - def test_pickle(self): - for proto in range(pickle.HIGHEST_PROTOCOL): - pickled = pickle.dumps(self.bottom_type, protocol=proto) - self.assertIs(self.bottom_type, pickle.loads(pickled)) - - -class NoReturnTests(BottomTypeTestsMixin, BaseTestCase): - bottom_type = NoReturn - - def test_repr(self): - if hasattr(typing, 'NoReturn'): - self.assertEqual(repr(NoReturn), 'typing.NoReturn') - else: - self.assertEqual(repr(NoReturn), 'typing_extensions.NoReturn') - - def test_get_type_hints(self): - def some(arg: NoReturn) -> NoReturn: ... - def some_str(arg: 'NoReturn') -> 'typing.NoReturn': ... - - expected = {'arg': NoReturn, 'return': NoReturn} - targets = [some] - - # On 3.7.0 and 3.7.1, https://github.com/python/cpython/pull/10772 - # wasn't applied yet and NoReturn fails _type_check. - if not ((3, 7, 0) <= sys.version_info < (3, 7, 2)): - targets.append(some_str) - for target in targets: - with self.subTest(target=target): - self.assertEqual(gth(target), expected) - - def test_not_equality(self): - self.assertNotEqual(NoReturn, Never) - self.assertNotEqual(Never, NoReturn) - - -class NeverTests(BottomTypeTestsMixin, BaseTestCase): - bottom_type = Never - - def test_repr(self): - if hasattr(typing, 'Never'): - self.assertEqual(repr(Never), 'typing.Never') - else: - self.assertEqual(repr(Never), 'typing_extensions.Never') - - def test_get_type_hints(self): - def some(arg: Never) -> Never: ... - def some_str(arg: 'Never') -> 'typing_extensions.Never': ... - - expected = {'arg': Never, 'return': Never} - for target in [some, some_str]: - with self.subTest(target=target): - self.assertEqual(gth(target), expected) - - -class AssertNeverTests(BaseTestCase): - def test_exception(self): - with self.assertRaises(AssertionError): - assert_never(None) - - -class ClassVarTests(BaseTestCase): - - def test_basics(self): - if not TYPING_3_11_0: - with self.assertRaises(TypeError): - ClassVar[1] - with self.assertRaises(TypeError): - ClassVar[int, str] - with self.assertRaises(TypeError): - ClassVar[int][str] - - def test_repr(self): - if hasattr(typing, 'ClassVar'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(ClassVar), mod_name + '.ClassVar') - cv = ClassVar[int] - self.assertEqual(repr(cv), mod_name + '.ClassVar[int]') - cv = ClassVar[Employee] - self.assertEqual(repr(cv), mod_name + f'.ClassVar[{__name__}.Employee]') - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(ClassVar)): - pass - with self.assertRaises(TypeError): - class C(type(ClassVar[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - ClassVar() - with self.assertRaises(TypeError): - type(ClassVar)() - with self.assertRaises(TypeError): - type(ClassVar[Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, ClassVar[int]) - with self.assertRaises(TypeError): - issubclass(int, ClassVar) - - -class FinalTests(BaseTestCase): - - def test_basics(self): - if not TYPING_3_11_0: - with self.assertRaises(TypeError): - Final[1] - with self.assertRaises(TypeError): - Final[int, str] - with self.assertRaises(TypeError): - Final[int][str] - - def test_repr(self): - if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(Final), mod_name + '.Final') - cv = Final[int] - self.assertEqual(repr(cv), mod_name + '.Final[int]') - cv = Final[Employee] - self.assertEqual(repr(cv), mod_name + f'.Final[{__name__}.Employee]') - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(Final)): - pass - with self.assertRaises(TypeError): - class C(type(Final[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - Final() - with self.assertRaises(TypeError): - type(Final)() - with self.assertRaises(TypeError): - type(Final[Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, Final[int]) - with self.assertRaises(TypeError): - issubclass(int, Final) - - -class RequiredTests(BaseTestCase): - - def test_basics(self): - if not TYPING_3_11_0: - with self.assertRaises(TypeError): - Required[1] - with self.assertRaises(TypeError): - Required[int, str] - with self.assertRaises(TypeError): - Required[int][str] - - def test_repr(self): - if hasattr(typing, 'Required'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(Required), mod_name + '.Required') - cv = Required[int] - self.assertEqual(repr(cv), mod_name + '.Required[int]') - cv = Required[Employee] - self.assertEqual(repr(cv), mod_name + '.Required[%s.Employee]' % __name__) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(Required)): - pass - with self.assertRaises(TypeError): - class C(type(Required[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - Required() - with self.assertRaises(TypeError): - type(Required)() - with self.assertRaises(TypeError): - type(Required[Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, Required[int]) - with self.assertRaises(TypeError): - issubclass(int, Required) - - -class NotRequiredTests(BaseTestCase): - - def test_basics(self): - if not TYPING_3_11_0: - with self.assertRaises(TypeError): - NotRequired[1] - with self.assertRaises(TypeError): - NotRequired[int, str] - with self.assertRaises(TypeError): - NotRequired[int][str] - - def test_repr(self): - if hasattr(typing, 'NotRequired'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(NotRequired), mod_name + '.NotRequired') - cv = NotRequired[int] - self.assertEqual(repr(cv), mod_name + '.NotRequired[int]') - cv = NotRequired[Employee] - self.assertEqual(repr(cv), mod_name + '.NotRequired[%s.Employee]' % __name__) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(NotRequired)): - pass - with self.assertRaises(TypeError): - class C(type(NotRequired[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - NotRequired() - with self.assertRaises(TypeError): - type(NotRequired)() - with self.assertRaises(TypeError): - type(NotRequired[Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, NotRequired[int]) - with self.assertRaises(TypeError): - issubclass(int, NotRequired) - - -class IntVarTests(BaseTestCase): - def test_valid(self): - T_ints = IntVar("T_ints") # noqa - - def test_invalid(self): - with self.assertRaises(TypeError): - T_ints = IntVar("T_ints", int) - with self.assertRaises(TypeError): - T_ints = IntVar("T_ints", bound=int) - with self.assertRaises(TypeError): - T_ints = IntVar("T_ints", covariant=True) # noqa - - -class LiteralTests(BaseTestCase): - def test_basics(self): - Literal[1] - Literal[1, 2, 3] - Literal["x", "y", "z"] - Literal[None] - - def test_illegal_parameters_do_not_raise_runtime_errors(self): - # Type checkers should reject these types, but we do not - # raise errors at runtime to maintain maximum flexibility - Literal[int] - Literal[Literal[1, 2], Literal[4, 5]] - Literal[3j + 2, ..., ()] - Literal[b"foo", u"bar"] - Literal[{"foo": 3, "bar": 4}] - Literal[T] - - def test_literals_inside_other_types(self): - List[Literal[1, 2, 3]] - List[Literal[("foo", "bar", "baz")]] - - def test_repr(self): - if hasattr(typing, 'Literal'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(Literal[1]), mod_name + ".Literal[1]") - self.assertEqual(repr(Literal[1, True, "foo"]), mod_name + ".Literal[1, True, 'foo']") - self.assertEqual(repr(Literal[int]), mod_name + ".Literal[int]") - self.assertEqual(repr(Literal), mod_name + ".Literal") - self.assertEqual(repr(Literal[None]), mod_name + ".Literal[None]") - - def test_cannot_init(self): - with self.assertRaises(TypeError): - Literal() - with self.assertRaises(TypeError): - Literal[1]() - with self.assertRaises(TypeError): - type(Literal)() - with self.assertRaises(TypeError): - type(Literal[1])() - - def test_no_isinstance_or_issubclass(self): - with self.assertRaises(TypeError): - isinstance(1, Literal[1]) - with self.assertRaises(TypeError): - isinstance(int, Literal[1]) - with self.assertRaises(TypeError): - issubclass(1, Literal[1]) - with self.assertRaises(TypeError): - issubclass(int, Literal[1]) - - def test_no_subclassing(self): - with self.assertRaises(TypeError): - class Foo(Literal[1]): pass - with self.assertRaises(TypeError): - class Bar(Literal): pass - - def test_no_multiple_subscripts(self): - with self.assertRaises(TypeError): - Literal[1][1] - - -class MethodHolder: - @classmethod - def clsmethod(cls): ... - @staticmethod - def stmethod(): ... - def method(self): ... - - -if TYPING_3_11_0: - registry_holder = typing -else: - registry_holder = typing_extensions - - -class OverloadTests(BaseTestCase): - - def test_overload_fails(self): - with self.assertRaises(RuntimeError): - - @overload - def blah(): - pass - - blah() - - def test_overload_succeeds(self): - @overload - def blah(): - pass - - def blah(): - pass - - blah() - - def set_up_overloads(self): - def blah(): - pass - - overload1 = blah - overload(blah) - - def blah(): - pass - - overload2 = blah - overload(blah) - - def blah(): - pass - - return blah, [overload1, overload2] - - # Make sure we don't clear the global overload registry - @patch( - f"{registry_holder.__name__}._overload_registry", - defaultdict(lambda: defaultdict(dict)) - ) - def test_overload_registry(self): - registry = registry_holder._overload_registry - # The registry starts out empty - self.assertEqual(registry, {}) - - impl, overloads = self.set_up_overloads() - self.assertNotEqual(registry, {}) - self.assertEqual(list(get_overloads(impl)), overloads) - - def some_other_func(): pass - overload(some_other_func) - other_overload = some_other_func - def some_other_func(): pass - self.assertEqual(list(get_overloads(some_other_func)), [other_overload]) - - # Make sure that after we clear all overloads, the registry is - # completely empty. - clear_overloads() - self.assertEqual(registry, {}) - self.assertEqual(get_overloads(impl), []) - - # Querying a function with no overloads shouldn't change the registry. - def the_only_one(): pass - self.assertEqual(get_overloads(the_only_one), []) - self.assertEqual(registry, {}) - - def test_overload_registry_repeated(self): - for _ in range(2): - impl, overloads = self.set_up_overloads() - - self.assertEqual(list(get_overloads(impl)), overloads) - - -class AssertTypeTests(BaseTestCase): - - def test_basics(self): - arg = 42 - self.assertIs(assert_type(arg, int), arg) - self.assertIs(assert_type(arg, Union[str, float]), arg) - self.assertIs(assert_type(arg, AnyStr), arg) - self.assertIs(assert_type(arg, None), arg) - - def test_errors(self): - # Bogus calls are not expected to fail. - arg = 42 - self.assertIs(assert_type(arg, 42), arg) - self.assertIs(assert_type(arg, 'hello'), arg) - - -T_a = TypeVar('T_a') - -class AwaitableWrapper(Awaitable[T_a]): - - def __init__(self, value): - self.value = value - - def __await__(self) -> typing.Iterator[T_a]: - yield - return self.value - -class AsyncIteratorWrapper(AsyncIterator[T_a]): - - def __init__(self, value: Iterable[T_a]): - self.value = value - - def __aiter__(self) -> AsyncIterator[T_a]: - return self - - async def __anext__(self) -> T_a: - data = await self.value - if data: - return data - else: - raise StopAsyncIteration - -class ACM: - async def __aenter__(self) -> int: - return 42 - - async def __aexit__(self, etype, eval, tb): - return None - - - -class A: - y: float -class B(A): - x: ClassVar[Optional['B']] = None - y: int - b: int -class CSub(B): - z: ClassVar['CSub'] = B() -class G(Generic[T]): - lst: ClassVar[List[T]] = [] - -class Loop: - attr: Final['Loop'] - -class NoneAndForward: - parent: 'NoneAndForward' - meaning: None - -class XRepr(NamedTuple): - x: int - y: int = 1 - - def __str__(self): - return f'{self.x} -> {self.y}' - - def __add__(self, other): - return 0 - -@runtime -class HasCallProtocol(Protocol): - __call__: typing.Callable - - -async def g_with(am: AsyncContextManager[int]): - x: int - async with am as x: - return x - -try: - g_with(ACM()).send(None) -except StopIteration as e: - assert e.args[0] == 42 - -Label = TypedDict('Label', [('label', str)]) - -class Point2D(TypedDict): - x: int - y: int - -class Point2Dor3D(Point2D, total=False): - z: int - -class LabelPoint2D(Point2D, Label): ... - -class Options(TypedDict, total=False): - log_level: int - log_path: str - -class BaseAnimal(TypedDict): - name: str - -class Animal(BaseAnimal, total=False): - voice: str - tail: bool - -class Cat(Animal): - fur_color: str - -class TotalMovie(TypedDict): - title: str - year: NotRequired[int] - -class NontotalMovie(TypedDict, total=False): - title: Required[str] - year: int - -class AnnotatedMovie(TypedDict): - title: Annotated[Required[str], "foobar"] - year: NotRequired[Annotated[int, 2000]] - - -gth = get_type_hints - - -class GetTypeHintTests(BaseTestCase): - def test_get_type_hints_modules(self): - ann_module_type_hints = {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str} - if (TYPING_3_11_0 - or (TYPING_3_10_0 and sys.version_info.releaselevel in {'candidate', 'final'})): - # More tests were added in 3.10rc1. - ann_module_type_hints['u'] = int | float - self.assertEqual(gth(ann_module), ann_module_type_hints) - self.assertEqual(gth(ann_module2), {}) - self.assertEqual(gth(ann_module3), {}) - - def test_get_type_hints_classes(self): - self.assertEqual(gth(ann_module.C, ann_module.__dict__), - {'y': Optional[ann_module.C]}) - self.assertIsInstance(gth(ann_module.j_class), dict) - self.assertEqual(gth(ann_module.M), {'123': 123, 'o': type}) - self.assertEqual(gth(ann_module.D), - {'j': str, 'k': str, 'y': Optional[ann_module.C]}) - self.assertEqual(gth(ann_module.Y), {'z': int}) - self.assertEqual(gth(ann_module.h_class), - {'y': Optional[ann_module.C]}) - self.assertEqual(gth(ann_module.S), {'x': str, 'y': str}) - self.assertEqual(gth(ann_module.foo), {'x': int}) - self.assertEqual(gth(NoneAndForward, globals()), - {'parent': NoneAndForward, 'meaning': type(None)}) - - def test_respect_no_type_check(self): - @no_type_check - class NoTpCheck: - class Inn: - def __init__(self, x: 'not a type'): ... # noqa - self.assertTrue(NoTpCheck.__no_type_check__) - self.assertTrue(NoTpCheck.Inn.__init__.__no_type_check__) - self.assertEqual(gth(ann_module2.NTC.meth), {}) - class ABase(Generic[T]): - def meth(x: int): ... - @no_type_check - class Der(ABase): ... - self.assertEqual(gth(ABase.meth), {'x': int}) - - def test_get_type_hints_ClassVar(self): - self.assertEqual(gth(ann_module2.CV, ann_module2.__dict__), - {'var': ClassVar[ann_module2.CV]}) - self.assertEqual(gth(B, globals()), - {'y': int, 'x': ClassVar[Optional[B]], 'b': int}) - self.assertEqual(gth(CSub, globals()), - {'z': ClassVar[CSub], 'y': int, 'b': int, - 'x': ClassVar[Optional[B]]}) - self.assertEqual(gth(G), {'lst': ClassVar[List[T]]}) - - def test_final_forward_ref(self): - self.assertEqual(gth(Loop, globals())['attr'], Final[Loop]) - self.assertNotEqual(gth(Loop, globals())['attr'], Final[int]) - self.assertNotEqual(gth(Loop, globals())['attr'], Final) - - -class GetUtilitiesTestCase(TestCase): - def test_get_origin(self): - T = TypeVar('T') - P = ParamSpec('P') - Ts = TypeVarTuple('Ts') - class C(Generic[T]): pass - self.assertIs(get_origin(C[int]), C) - self.assertIs(get_origin(C[T]), C) - self.assertIs(get_origin(int), None) - self.assertIs(get_origin(ClassVar[int]), ClassVar) - self.assertIs(get_origin(Union[int, str]), Union) - self.assertIs(get_origin(Literal[42, 43]), Literal) - self.assertIs(get_origin(Final[List[int]]), Final) - self.assertIs(get_origin(Generic), Generic) - self.assertIs(get_origin(Generic[T]), Generic) - self.assertIs(get_origin(List[Tuple[T, T]][int]), list) - self.assertIs(get_origin(Annotated[T, 'thing']), Annotated) - self.assertIs(get_origin(List), list) - self.assertIs(get_origin(Tuple), tuple) - self.assertIs(get_origin(Callable), collections.abc.Callable) - if sys.version_info >= (3, 9): - self.assertIs(get_origin(list[int]), list) - self.assertIs(get_origin(list), None) - self.assertIs(get_origin(P.args), P) - self.assertIs(get_origin(P.kwargs), P) - self.assertIs(get_origin(Required[int]), Required) - self.assertIs(get_origin(NotRequired[int]), NotRequired) - self.assertIs(get_origin(Unpack[Ts]), Unpack) - self.assertIs(get_origin(Unpack), None) - - def test_get_args(self): - T = TypeVar('T') - Ts = TypeVarTuple('Ts') - class C(Generic[T]): pass - self.assertEqual(get_args(C[int]), (int,)) - self.assertEqual(get_args(C[T]), (T,)) - self.assertEqual(get_args(int), ()) - self.assertEqual(get_args(ClassVar[int]), (int,)) - self.assertEqual(get_args(Union[int, str]), (int, str)) - self.assertEqual(get_args(Literal[42, 43]), (42, 43)) - self.assertEqual(get_args(Final[List[int]]), (List[int],)) - self.assertEqual(get_args(Union[int, Tuple[T, int]][str]), - (int, Tuple[str, int])) - self.assertEqual(get_args(typing.Dict[int, Tuple[T, T]][Optional[int]]), - (int, Tuple[Optional[int], Optional[int]])) - self.assertEqual(get_args(Callable[[], T][int]), ([], int)) - self.assertEqual(get_args(Callable[..., int]), (..., int)) - self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]]), - (int, Callable[[Tuple[T, ...]], str])) - self.assertEqual(get_args(Tuple[int, ...]), (int, ...)) - if TYPING_3_11_0: - self.assertEqual(get_args(Tuple[()]), ()) - else: - self.assertEqual(get_args(Tuple[()]), ((),)) - self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three'])) - self.assertEqual(get_args(List), ()) - self.assertEqual(get_args(Tuple), ()) - self.assertEqual(get_args(Callable), ()) - if sys.version_info >= (3, 9): - self.assertEqual(get_args(list[int]), (int,)) - self.assertEqual(get_args(list), ()) - if sys.version_info >= (3, 9): - # Support Python versions with and without the fix for - # https://bugs.python.org/issue42195 - # The first variant is for 3.9.2+, the second for 3.9.0 and 1 - self.assertIn(get_args(collections.abc.Callable[[int], str]), - (([int], str), ([[int]], str))) - self.assertIn(get_args(collections.abc.Callable[[], str]), - (([], str), ([[]], str))) - self.assertEqual(get_args(collections.abc.Callable[..., str]), (..., str)) - P = ParamSpec('P') - # In 3.9 and lower we use typing_extensions's hacky implementation - # of ParamSpec, which gets incorrectly wrapped in a list - self.assertIn(get_args(Callable[P, int]), [(P, int), ([P], int)]) - self.assertEqual(get_args(Callable[Concatenate[int, P], int]), - (Concatenate[int, P], int)) - self.assertEqual(get_args(Required[int]), (int,)) - self.assertEqual(get_args(NotRequired[int]), (int,)) - self.assertEqual(get_args(Unpack[Ts]), (Ts,)) - self.assertEqual(get_args(Unpack), ()) - - -class CollectionsAbcTests(BaseTestCase): - - def test_isinstance_collections(self): - self.assertNotIsInstance(1, collections.abc.Mapping) - self.assertNotIsInstance(1, collections.abc.Iterable) - self.assertNotIsInstance(1, collections.abc.Container) - self.assertNotIsInstance(1, collections.abc.Sized) - with self.assertRaises(TypeError): - isinstance(collections.deque(), typing_extensions.Deque[int]) - with self.assertRaises(TypeError): - issubclass(collections.Counter, typing_extensions.Counter[str]) - - def test_awaitable(self): - ns = {} - exec( - "async def foo() -> typing_extensions.Awaitable[int]:\n" - " return await AwaitableWrapper(42)\n", - globals(), ns) - foo = ns['foo'] - g = foo() - self.assertIsInstance(g, typing_extensions.Awaitable) - self.assertNotIsInstance(foo, typing_extensions.Awaitable) - g.send(None) # Run foo() till completion, to avoid warning. - - def test_coroutine(self): - ns = {} - exec( - "async def foo():\n" - " return\n", - globals(), ns) - foo = ns['foo'] - g = foo() - self.assertIsInstance(g, typing_extensions.Coroutine) - with self.assertRaises(TypeError): - isinstance(g, typing_extensions.Coroutine[int]) - self.assertNotIsInstance(foo, typing_extensions.Coroutine) - try: - g.send(None) - except StopIteration: - pass - - def test_async_iterable(self): - base_it = range(10) # type: Iterator[int] - it = AsyncIteratorWrapper(base_it) - self.assertIsInstance(it, typing_extensions.AsyncIterable) - self.assertIsInstance(it, typing_extensions.AsyncIterable) - self.assertNotIsInstance(42, typing_extensions.AsyncIterable) - - def test_async_iterator(self): - base_it = range(10) # type: Iterator[int] - it = AsyncIteratorWrapper(base_it) - self.assertIsInstance(it, typing_extensions.AsyncIterator) - self.assertNotIsInstance(42, typing_extensions.AsyncIterator) - - def test_deque(self): - self.assertIsSubclass(collections.deque, typing_extensions.Deque) - class MyDeque(typing_extensions.Deque[int]): ... - self.assertIsInstance(MyDeque(), collections.deque) - - def test_counter(self): - self.assertIsSubclass(collections.Counter, typing_extensions.Counter) - - def test_defaultdict_instantiation(self): - self.assertIs( - type(typing_extensions.DefaultDict()), - collections.defaultdict) - self.assertIs( - type(typing_extensions.DefaultDict[KT, VT]()), - collections.defaultdict) - self.assertIs( - type(typing_extensions.DefaultDict[str, int]()), - collections.defaultdict) - - def test_defaultdict_subclass(self): - - class MyDefDict(typing_extensions.DefaultDict[str, int]): - pass - - dd = MyDefDict() - self.assertIsInstance(dd, MyDefDict) - - self.assertIsSubclass(MyDefDict, collections.defaultdict) - self.assertNotIsSubclass(collections.defaultdict, MyDefDict) - - def test_ordereddict_instantiation(self): - self.assertIs( - type(typing_extensions.OrderedDict()), - collections.OrderedDict) - self.assertIs( - type(typing_extensions.OrderedDict[KT, VT]()), - collections.OrderedDict) - self.assertIs( - type(typing_extensions.OrderedDict[str, int]()), - collections.OrderedDict) - - def test_ordereddict_subclass(self): - - class MyOrdDict(typing_extensions.OrderedDict[str, int]): - pass - - od = MyOrdDict() - self.assertIsInstance(od, MyOrdDict) - - self.assertIsSubclass(MyOrdDict, collections.OrderedDict) - self.assertNotIsSubclass(collections.OrderedDict, MyOrdDict) - - def test_chainmap_instantiation(self): - self.assertIs(type(typing_extensions.ChainMap()), collections.ChainMap) - self.assertIs(type(typing_extensions.ChainMap[KT, VT]()), collections.ChainMap) - self.assertIs(type(typing_extensions.ChainMap[str, int]()), collections.ChainMap) - class CM(typing_extensions.ChainMap[KT, VT]): ... - self.assertIs(type(CM[int, str]()), CM) - - def test_chainmap_subclass(self): - - class MyChainMap(typing_extensions.ChainMap[str, int]): - pass - - cm = MyChainMap() - self.assertIsInstance(cm, MyChainMap) - - self.assertIsSubclass(MyChainMap, collections.ChainMap) - self.assertNotIsSubclass(collections.ChainMap, MyChainMap) - - def test_deque_instantiation(self): - self.assertIs(type(typing_extensions.Deque()), collections.deque) - self.assertIs(type(typing_extensions.Deque[T]()), collections.deque) - self.assertIs(type(typing_extensions.Deque[int]()), collections.deque) - class D(typing_extensions.Deque[T]): ... - self.assertIs(type(D[int]()), D) - - def test_counter_instantiation(self): - self.assertIs(type(typing_extensions.Counter()), collections.Counter) - self.assertIs(type(typing_extensions.Counter[T]()), collections.Counter) - self.assertIs(type(typing_extensions.Counter[int]()), collections.Counter) - class C(typing_extensions.Counter[T]): ... - self.assertIs(type(C[int]()), C) - self.assertEqual(C.__bases__, (collections.Counter, typing.Generic)) - - def test_counter_subclass_instantiation(self): - - class MyCounter(typing_extensions.Counter[int]): - pass - - d = MyCounter() - self.assertIsInstance(d, MyCounter) - self.assertIsInstance(d, collections.Counter) - self.assertIsInstance(d, typing_extensions.Counter) - - def test_async_generator(self): - ns = {} - exec("async def f():\n" - " yield 42\n", globals(), ns) - g = ns['f']() - self.assertIsSubclass(type(g), typing_extensions.AsyncGenerator) - - def test_no_async_generator_instantiation(self): - with self.assertRaises(TypeError): - typing_extensions.AsyncGenerator() - with self.assertRaises(TypeError): - typing_extensions.AsyncGenerator[T, T]() - with self.assertRaises(TypeError): - typing_extensions.AsyncGenerator[int, int]() - - def test_subclassing_async_generator(self): - class G(typing_extensions.AsyncGenerator[int, int]): - def asend(self, value): - pass - def athrow(self, typ, val=None, tb=None): - pass - - ns = {} - exec('async def g(): yield 0', globals(), ns) - g = ns['g'] - self.assertIsSubclass(G, typing_extensions.AsyncGenerator) - self.assertIsSubclass(G, typing_extensions.AsyncIterable) - self.assertIsSubclass(G, collections.abc.AsyncGenerator) - self.assertIsSubclass(G, collections.abc.AsyncIterable) - self.assertNotIsSubclass(type(g), G) - - instance = G() - self.assertIsInstance(instance, typing_extensions.AsyncGenerator) - self.assertIsInstance(instance, typing_extensions.AsyncIterable) - self.assertIsInstance(instance, collections.abc.AsyncGenerator) - self.assertIsInstance(instance, collections.abc.AsyncIterable) - self.assertNotIsInstance(type(g), G) - self.assertNotIsInstance(g, G) - - -class OtherABCTests(BaseTestCase): - - def test_contextmanager(self): - @contextlib.contextmanager - def manager(): - yield 42 - - cm = manager() - self.assertIsInstance(cm, typing_extensions.ContextManager) - self.assertNotIsInstance(42, typing_extensions.ContextManager) - - def test_async_contextmanager(self): - class NotACM: - pass - self.assertIsInstance(ACM(), typing_extensions.AsyncContextManager) - self.assertNotIsInstance(NotACM(), typing_extensions.AsyncContextManager) - @contextlib.contextmanager - def manager(): - yield 42 - - cm = manager() - self.assertNotIsInstance(cm, typing_extensions.AsyncContextManager) - self.assertEqual(typing_extensions.AsyncContextManager[int].__args__, (int,)) - with self.assertRaises(TypeError): - isinstance(42, typing_extensions.AsyncContextManager[int]) - with self.assertRaises(TypeError): - typing_extensions.AsyncContextManager[int, str] - - -class TypeTests(BaseTestCase): - - def test_type_basic(self): - - class User: pass - class BasicUser(User): pass - class ProUser(User): pass - - def new_user(user_class: Type[User]) -> User: - return user_class() - - new_user(BasicUser) - - def test_type_typevar(self): - - class User: pass - class BasicUser(User): pass - class ProUser(User): pass - - U = TypeVar('U', bound=User) - - def new_user(user_class: Type[U]) -> U: - return user_class() - - new_user(BasicUser) - - def test_type_optional(self): - A = Optional[Type[BaseException]] - - def foo(a: A) -> Optional[BaseException]: - if a is None: - return None - else: - return a() - - assert isinstance(foo(KeyboardInterrupt), KeyboardInterrupt) - assert foo(None) is None - - -class NewTypeTests(BaseTestCase): - - def test_basic(self): - UserId = NewType('UserId', int) - UserName = NewType('UserName', str) - self.assertIsInstance(UserId(5), int) - self.assertIsInstance(UserName('Joe'), str) - self.assertEqual(UserId(5) + 1, 6) - - def test_errors(self): - UserId = NewType('UserId', int) - UserName = NewType('UserName', str) - with self.assertRaises(TypeError): - issubclass(UserId, int) - with self.assertRaises(TypeError): - class D(UserName): - pass - - -class Coordinate(Protocol): - x: int - y: int - -@runtime -class Point(Coordinate, Protocol): - label: str - -class MyPoint: - x: int - y: int - label: str - -class XAxis(Protocol): - x: int - -class YAxis(Protocol): - y: int - -@runtime -class Position(XAxis, YAxis, Protocol): - pass - -@runtime -class Proto(Protocol): - attr: int - - def meth(self, arg: str) -> int: - ... - -class Concrete(Proto): - pass - -class Other: - attr: int = 1 - - def meth(self, arg: str) -> int: - if arg == 'this': - return 1 - return 0 - -class NT(NamedTuple): - x: int - y: int - - -class ProtocolTests(BaseTestCase): - - def test_basic_protocol(self): - @runtime - class P(Protocol): - def meth(self): - pass - class C: pass - class D: - def meth(self): - pass - def f(): - pass - self.assertIsSubclass(D, P) - self.assertIsInstance(D(), P) - self.assertNotIsSubclass(C, P) - self.assertNotIsInstance(C(), P) - self.assertNotIsSubclass(types.FunctionType, P) - self.assertNotIsInstance(f, P) - - def test_everything_implements_empty_protocol(self): - @runtime - class Empty(Protocol): pass - class C: pass - def f(): - pass - for thing in (object, type, tuple, C, types.FunctionType): - self.assertIsSubclass(thing, Empty) - for thing in (object(), 1, (), typing, f): - self.assertIsInstance(thing, Empty) - - def test_function_implements_protocol(self): - def f(): - pass - self.assertIsInstance(f, HasCallProtocol) - - def test_no_inheritance_from_nominal(self): - class C: pass - class BP(Protocol): pass - with self.assertRaises(TypeError): - class P(C, Protocol): - pass - with self.assertRaises(TypeError): - class P(Protocol, C): - pass - with self.assertRaises(TypeError): - class P(BP, C, Protocol): - pass - class D(BP, C): pass - class E(C, BP): pass - self.assertNotIsInstance(D(), E) - self.assertNotIsInstance(E(), D) - - def test_no_instantiation(self): - class P(Protocol): pass - with self.assertRaises(TypeError): - P() - class C(P): pass - self.assertIsInstance(C(), C) - T = TypeVar('T') - class PG(Protocol[T]): pass - with self.assertRaises(TypeError): - PG() - with self.assertRaises(TypeError): - PG[int]() - with self.assertRaises(TypeError): - PG[T]() - class CG(PG[T]): pass - self.assertIsInstance(CG[int](), CG) - - def test_cannot_instantiate_abstract(self): - @runtime - class P(Protocol): - @abc.abstractmethod - def ameth(self) -> int: - raise NotImplementedError - class B(P): - pass - class C(B): - def ameth(self) -> int: - return 26 - with self.assertRaises(TypeError): - B() - self.assertIsInstance(C(), P) - - def test_subprotocols_extending(self): - class P1(Protocol): - def meth1(self): - pass - @runtime - class P2(P1, Protocol): - def meth2(self): - pass - class C: - def meth1(self): - pass - def meth2(self): - pass - class C1: - def meth1(self): - pass - class C2: - def meth2(self): - pass - self.assertNotIsInstance(C1(), P2) - self.assertNotIsInstance(C2(), P2) - self.assertNotIsSubclass(C1, P2) - self.assertNotIsSubclass(C2, P2) - self.assertIsInstance(C(), P2) - self.assertIsSubclass(C, P2) - - def test_subprotocols_merging(self): - class P1(Protocol): - def meth1(self): - pass - class P2(Protocol): - def meth2(self): - pass - @runtime - class P(P1, P2, Protocol): - pass - class C: - def meth1(self): - pass - def meth2(self): - pass - class C1: - def meth1(self): - pass - class C2: - def meth2(self): - pass - self.assertNotIsInstance(C1(), P) - self.assertNotIsInstance(C2(), P) - self.assertNotIsSubclass(C1, P) - self.assertNotIsSubclass(C2, P) - self.assertIsInstance(C(), P) - self.assertIsSubclass(C, P) - - def test_protocols_issubclass(self): - T = TypeVar('T') - @runtime - class P(Protocol): - def x(self): ... - @runtime - class PG(Protocol[T]): - def x(self): ... - class BadP(Protocol): - def x(self): ... - class BadPG(Protocol[T]): - def x(self): ... - class C: - def x(self): ... - self.assertIsSubclass(C, P) - self.assertIsSubclass(C, PG) - self.assertIsSubclass(BadP, PG) - with self.assertRaises(TypeError): - issubclass(C, PG[T]) - with self.assertRaises(TypeError): - issubclass(C, PG[C]) - with self.assertRaises(TypeError): - issubclass(C, BadP) - with self.assertRaises(TypeError): - issubclass(C, BadPG) - with self.assertRaises(TypeError): - issubclass(P, PG[T]) - with self.assertRaises(TypeError): - issubclass(PG, PG[int]) - - def test_protocols_issubclass_non_callable(self): - class C: - x = 1 - @runtime - class PNonCall(Protocol): - x = 1 - with self.assertRaises(TypeError): - issubclass(C, PNonCall) - self.assertIsInstance(C(), PNonCall) - PNonCall.register(C) - with self.assertRaises(TypeError): - issubclass(C, PNonCall) - self.assertIsInstance(C(), PNonCall) - # check that non-protocol subclasses are not affected - class D(PNonCall): ... - self.assertNotIsSubclass(C, D) - self.assertNotIsInstance(C(), D) - D.register(C) - self.assertIsSubclass(C, D) - self.assertIsInstance(C(), D) - with self.assertRaises(TypeError): - issubclass(D, PNonCall) - - def test_protocols_isinstance(self): - T = TypeVar('T') - @runtime - class P(Protocol): - def meth(x): ... - @runtime - class PG(Protocol[T]): - def meth(x): ... - class BadP(Protocol): - def meth(x): ... - class BadPG(Protocol[T]): - def meth(x): ... - class C: - def meth(x): ... - self.assertIsInstance(C(), P) - self.assertIsInstance(C(), PG) - with self.assertRaises(TypeError): - isinstance(C(), PG[T]) - with self.assertRaises(TypeError): - isinstance(C(), PG[C]) - with self.assertRaises(TypeError): - isinstance(C(), BadP) - with self.assertRaises(TypeError): - isinstance(C(), BadPG) - - def test_protocols_isinstance_py36(self): - class APoint: - def __init__(self, x, y, label): - self.x = x - self.y = y - self.label = label - class BPoint: - label = 'B' - def __init__(self, x, y): - self.x = x - self.y = y - class C: - def __init__(self, attr): - self.attr = attr - def meth(self, arg): - return 0 - class Bad: pass - self.assertIsInstance(APoint(1, 2, 'A'), Point) - self.assertIsInstance(BPoint(1, 2), Point) - self.assertNotIsInstance(MyPoint(), Point) - self.assertIsInstance(BPoint(1, 2), Position) - self.assertIsInstance(Other(), Proto) - self.assertIsInstance(Concrete(), Proto) - self.assertIsInstance(C(42), Proto) - self.assertNotIsInstance(Bad(), Proto) - self.assertNotIsInstance(Bad(), Point) - self.assertNotIsInstance(Bad(), Position) - self.assertNotIsInstance(Bad(), Concrete) - self.assertNotIsInstance(Other(), Concrete) - self.assertIsInstance(NT(1, 2), Position) - - def test_protocols_isinstance_init(self): - T = TypeVar('T') - @runtime - class P(Protocol): - x = 1 - @runtime - class PG(Protocol[T]): - x = 1 - class C: - def __init__(self, x): - self.x = x - self.assertIsInstance(C(1), P) - self.assertIsInstance(C(1), PG) - - def test_protocols_support_register(self): - @runtime - class P(Protocol): - x = 1 - class PM(Protocol): - def meth(self): pass - class D(PM): pass - class C: pass - D.register(C) - P.register(C) - self.assertIsInstance(C(), P) - self.assertIsInstance(C(), D) - - def test_none_on_non_callable_doesnt_block_implementation(self): - @runtime - class P(Protocol): - x = 1 - class A: - x = 1 - class B(A): - x = None - class C: - def __init__(self): - self.x = None - self.assertIsInstance(B(), P) - self.assertIsInstance(C(), P) - - def test_none_on_callable_blocks_implementation(self): - @runtime - class P(Protocol): - def x(self): ... - class A: - def x(self): ... - class B(A): - x = None - class C: - def __init__(self): - self.x = None - self.assertNotIsInstance(B(), P) - self.assertNotIsInstance(C(), P) - - def test_non_protocol_subclasses(self): - class P(Protocol): - x = 1 - @runtime - class PR(Protocol): - def meth(self): pass - class NonP(P): - x = 1 - class NonPR(PR): pass - class C: - x = 1 - class D: - def meth(self): pass - self.assertNotIsInstance(C(), NonP) - self.assertNotIsInstance(D(), NonPR) - self.assertNotIsSubclass(C, NonP) - self.assertNotIsSubclass(D, NonPR) - self.assertIsInstance(NonPR(), PR) - self.assertIsSubclass(NonPR, PR) - - def test_custom_subclasshook(self): - class P(Protocol): - x = 1 - class OKClass: pass - class BadClass: - x = 1 - class C(P): - @classmethod - def __subclasshook__(cls, other): - return other.__name__.startswith("OK") - self.assertIsInstance(OKClass(), C) - self.assertNotIsInstance(BadClass(), C) - self.assertIsSubclass(OKClass, C) - self.assertNotIsSubclass(BadClass, C) - - def test_issubclass_fails_correctly(self): - @runtime - class P(Protocol): - x = 1 - class C: pass - with self.assertRaises(TypeError): - issubclass(C(), P) - - def test_defining_generic_protocols(self): - T = TypeVar('T') - S = TypeVar('S') - @runtime - class PR(Protocol[T, S]): - def meth(self): pass - class P(PR[int, T], Protocol[T]): - y = 1 - with self.assertRaises(TypeError): - issubclass(PR[int, T], PR) - with self.assertRaises(TypeError): - issubclass(P[str], PR) - with self.assertRaises(TypeError): - PR[int] - with self.assertRaises(TypeError): - P[int, str] - if not TYPING_3_10_0: - with self.assertRaises(TypeError): - PR[int, 1] - with self.assertRaises(TypeError): - PR[int, ClassVar] - class C(PR[int, T]): pass - self.assertIsInstance(C[str](), C) - - def test_defining_generic_protocols_old_style(self): - T = TypeVar('T') - S = TypeVar('S') - @runtime - class PR(Protocol, Generic[T, S]): - def meth(self): pass - class P(PR[int, str], Protocol): - y = 1 - with self.assertRaises(TypeError): - self.assertIsSubclass(PR[int, str], PR) - self.assertIsSubclass(P, PR) - with self.assertRaises(TypeError): - PR[int] - if not TYPING_3_10_0: - with self.assertRaises(TypeError): - PR[int, 1] - class P1(Protocol, Generic[T]): - def bar(self, x: T) -> str: ... - class P2(Generic[T], Protocol): - def bar(self, x: T) -> str: ... - @runtime - class PSub(P1[str], Protocol): - x = 1 - class Test: - x = 1 - def bar(self, x: str) -> str: - return x - self.assertIsInstance(Test(), PSub) - if not TYPING_3_10_0: - with self.assertRaises(TypeError): - PR[int, ClassVar] - - def test_init_called(self): - T = TypeVar('T') - class P(Protocol[T]): pass - class C(P[T]): - def __init__(self): - self.test = 'OK' - self.assertEqual(C[int]().test, 'OK') - - def test_protocols_bad_subscripts(self): - T = TypeVar('T') - S = TypeVar('S') - with self.assertRaises(TypeError): - class P(Protocol[T, T]): pass - with self.assertRaises(TypeError): - class P(Protocol[int]): pass - with self.assertRaises(TypeError): - class P(Protocol[T], Protocol[S]): pass - with self.assertRaises(TypeError): - class P(typing.Mapping[T, S], Protocol[T]): pass - - def test_generic_protocols_repr(self): - T = TypeVar('T') - S = TypeVar('S') - class P(Protocol[T, S]): pass - self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) - self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) - - def test_generic_protocols_eq(self): - T = TypeVar('T') - S = TypeVar('S') - class P(Protocol[T, S]): pass - self.assertEqual(P, P) - self.assertEqual(P[int, T], P[int, T]) - self.assertEqual(P[T, T][Tuple[T, S]][int, str], - P[Tuple[int, str], Tuple[int, str]]) - - def test_generic_protocols_special_from_generic(self): - T = TypeVar('T') - class P(Protocol[T]): pass - self.assertEqual(P.__parameters__, (T,)) - self.assertEqual(P[int].__parameters__, ()) - self.assertEqual(P[int].__args__, (int,)) - self.assertIs(P[int].__origin__, P) - - def test_generic_protocols_special_from_protocol(self): - @runtime - class PR(Protocol): - x = 1 - class P(Protocol): - def meth(self): - pass - T = TypeVar('T') - class PG(Protocol[T]): - x = 1 - def meth(self): - pass - self.assertTrue(P._is_protocol) - self.assertTrue(PR._is_protocol) - self.assertTrue(PG._is_protocol) - if hasattr(typing, 'Protocol'): - self.assertFalse(P._is_runtime_protocol) - else: - with self.assertRaises(AttributeError): - self.assertFalse(P._is_runtime_protocol) - self.assertTrue(PR._is_runtime_protocol) - self.assertTrue(PG[int]._is_protocol) - self.assertEqual(typing_extensions._get_protocol_attrs(P), {'meth'}) - self.assertEqual(typing_extensions._get_protocol_attrs(PR), {'x'}) - self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG)), - frozenset({'x', 'meth'})) - - def test_no_runtime_deco_on_nominal(self): - with self.assertRaises(TypeError): - @runtime - class C: pass - class Proto(Protocol): - x = 1 - with self.assertRaises(TypeError): - @runtime - class Concrete(Proto): - pass - - def test_none_treated_correctly(self): - @runtime - class P(Protocol): - x = None # type: int - class B(object): pass - self.assertNotIsInstance(B(), P) - class C: - x = 1 - class D: - x = None - self.assertIsInstance(C(), P) - self.assertIsInstance(D(), P) - class CI: - def __init__(self): - self.x = 1 - class DI: - def __init__(self): - self.x = None - self.assertIsInstance(C(), P) - self.assertIsInstance(D(), P) - - def test_protocols_in_unions(self): - class P(Protocol): - x = None # type: int - Alias = typing.Union[typing.Iterable, P] - Alias2 = typing.Union[P, typing.Iterable] - self.assertEqual(Alias, Alias2) - - def test_protocols_pickleable(self): - global P, CP # pickle wants to reference the class by name - T = TypeVar('T') - - @runtime - class P(Protocol[T]): - x = 1 - class CP(P[int]): - pass - - c = CP() - c.foo = 42 - c.bar = 'abc' - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(c, proto) - x = pickle.loads(z) - self.assertEqual(x.foo, 42) - self.assertEqual(x.bar, 'abc') - self.assertEqual(x.x, 1) - self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) - s = pickle.dumps(P) - D = pickle.loads(s) - class E: - x = 1 - self.assertIsInstance(E(), D) - - def test_collections_protocols_allowed(self): - @runtime_checkable - class Custom(collections.abc.Iterable, Protocol): - def close(self): pass - - class A: ... - class B: - def __iter__(self): - return [] - def close(self): - return 0 - - self.assertIsSubclass(B, Custom) - self.assertNotIsSubclass(A, Custom) - - def test_no_init_same_for_different_protocol_implementations(self): - class CustomProtocolWithoutInitA(Protocol): - pass - - class CustomProtocolWithoutInitB(Protocol): - pass - - self.assertEqual(CustomProtocolWithoutInitA.__init__, CustomProtocolWithoutInitB.__init__) - - -class TypedDictTests(BaseTestCase): - - def test_basics_iterable_syntax(self): - Emp = TypedDict('Emp', {'name': str, 'id': int}) - self.assertIsSubclass(Emp, dict) - self.assertIsSubclass(Emp, typing.MutableMapping) - self.assertNotIsSubclass(Emp, collections.abc.Sequence) - jim = Emp(name='Jim', id=1) - self.assertIs(type(jim), dict) - self.assertEqual(jim['name'], 'Jim') - self.assertEqual(jim['id'], 1) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp.__module__, __name__) - self.assertEqual(Emp.__bases__, (dict,)) - self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) - self.assertEqual(Emp.__total__, True) - - def test_basics_keywords_syntax(self): - Emp = TypedDict('Emp', name=str, id=int) - self.assertIsSubclass(Emp, dict) - self.assertIsSubclass(Emp, typing.MutableMapping) - self.assertNotIsSubclass(Emp, collections.abc.Sequence) - jim = Emp(name='Jim', id=1) - self.assertIs(type(jim), dict) - self.assertEqual(jim['name'], 'Jim') - self.assertEqual(jim['id'], 1) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp.__module__, __name__) - self.assertEqual(Emp.__bases__, (dict,)) - self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) - self.assertEqual(Emp.__total__, True) - - def test_typeddict_special_keyword_names(self): - TD = TypedDict("TD", cls=type, self=object, typename=str, _typename=int, - fields=list, _fields=dict) - self.assertEqual(TD.__name__, 'TD') - self.assertEqual(TD.__annotations__, {'cls': type, 'self': object, 'typename': str, - '_typename': int, 'fields': list, '_fields': dict}) - a = TD(cls=str, self=42, typename='foo', _typename=53, - fields=[('bar', tuple)], _fields={'baz', set}) - self.assertEqual(a['cls'], str) - self.assertEqual(a['self'], 42) - self.assertEqual(a['typename'], 'foo') - self.assertEqual(a['_typename'], 53) - self.assertEqual(a['fields'], [('bar', tuple)]) - self.assertEqual(a['_fields'], {'baz', set}) - - @skipIf(hasattr(typing, 'TypedDict'), "Should be tested by upstream") - def test_typeddict_create_errors(self): - with self.assertRaises(TypeError): - TypedDict.__new__() - with self.assertRaises(TypeError): - TypedDict() - with self.assertRaises(TypeError): - TypedDict('Emp', [('name', str)], None) - - with self.assertWarns(DeprecationWarning): - Emp = TypedDict(_typename='Emp', name=str, id=int) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) - - with self.assertWarns(DeprecationWarning): - Emp = TypedDict('Emp', _fields={'name': str, 'id': int}) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) - - def test_typeddict_errors(self): - Emp = TypedDict('Emp', {'name': str, 'id': int}) - if hasattr(typing, "Required"): - self.assertEqual(TypedDict.__module__, 'typing') - else: - self.assertEqual(TypedDict.__module__, 'typing_extensions') - jim = Emp(name='Jim', id=1) - with self.assertRaises(TypeError): - isinstance({}, Emp) - with self.assertRaises(TypeError): - isinstance(jim, Emp) - with self.assertRaises(TypeError): - issubclass(dict, Emp) - - if not TYPING_3_11_0: - with self.assertRaises(TypeError): - TypedDict('Hi', x=1) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int), ('y', 1)]) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int)], y=int) - - def test_py36_class_syntax_usage(self): - self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D') - self.assertEqual(LabelPoint2D.__module__, __name__) - self.assertEqual(get_type_hints(LabelPoint2D), {'x': int, 'y': int, 'label': str}) - self.assertEqual(LabelPoint2D.__bases__, (dict,)) - self.assertEqual(LabelPoint2D.__total__, True) - self.assertNotIsSubclass(LabelPoint2D, typing.Sequence) - not_origin = Point2D(x=0, y=1) - self.assertEqual(not_origin['x'], 0) - self.assertEqual(not_origin['y'], 1) - other = LabelPoint2D(x=0, y=1, label='hi') - self.assertEqual(other['label'], 'hi') - - def test_pickle(self): - global EmpD # pickle wants to reference the class by name - EmpD = TypedDict('EmpD', name=str, id=int) - jane = EmpD({'name': 'jane', 'id': 37}) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(jane, proto) - jane2 = pickle.loads(z) - self.assertEqual(jane2, jane) - self.assertEqual(jane2, {'name': 'jane', 'id': 37}) - ZZ = pickle.dumps(EmpD, proto) - EmpDnew = pickle.loads(ZZ) - self.assertEqual(EmpDnew({'name': 'jane', 'id': 37}), jane) - - def test_optional(self): - EmpD = TypedDict('EmpD', name=str, id=int) - - self.assertEqual(typing.Optional[EmpD], typing.Union[None, EmpD]) - self.assertNotEqual(typing.List[EmpD], typing.Tuple[EmpD]) - - def test_total(self): - D = TypedDict('D', {'x': int}, total=False) - self.assertEqual(D(), {}) - self.assertEqual(D(x=1), {'x': 1}) - self.assertEqual(D.__total__, False) - self.assertEqual(D.__required_keys__, frozenset()) - self.assertEqual(D.__optional_keys__, {'x'}) - - self.assertEqual(Options(), {}) - self.assertEqual(Options(log_level=2), {'log_level': 2}) - self.assertEqual(Options.__total__, False) - self.assertEqual(Options.__required_keys__, frozenset()) - self.assertEqual(Options.__optional_keys__, {'log_level', 'log_path'}) - - def test_optional_keys(self): - assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y']) - assert Point2Dor3D.__optional_keys__ == frozenset(['z']) - - def test_required_notrequired_keys(self): - assert NontotalMovie.__required_keys__ == frozenset({'title'}) - assert NontotalMovie.__optional_keys__ == frozenset({'year'}) - - assert TotalMovie.__required_keys__ == frozenset({'title'}) - assert TotalMovie.__optional_keys__ == frozenset({'year'}) - - - def test_keys_inheritance(self): - assert BaseAnimal.__required_keys__ == frozenset(['name']) - assert BaseAnimal.__optional_keys__ == frozenset([]) - assert get_type_hints(BaseAnimal) == {'name': str} - - assert Animal.__required_keys__ == frozenset(['name']) - assert Animal.__optional_keys__ == frozenset(['tail', 'voice']) - assert get_type_hints(Animal) == { - 'name': str, - 'tail': bool, - 'voice': str, - } - - assert Cat.__required_keys__ == frozenset(['name', 'fur_color']) - assert Cat.__optional_keys__ == frozenset(['tail', 'voice']) - assert get_type_hints(Cat) == { - 'fur_color': str, - 'name': str, - 'tail': bool, - 'voice': str, - } - - def test_is_typeddict(self): - assert is_typeddict(Point2D) is True - assert is_typeddict(Point2Dor3D) is True - assert is_typeddict(Union[str, int]) is False - # classes, not instances - assert is_typeddict(Point2D()) is False - - @skipUnless(TYPING_3_8_0, "Python 3.8+ required") - def test_is_typeddict_against_typeddict_from_typing(self): - Point = typing.TypedDict('Point', {'x': int, 'y': int}) - - class PointDict2D(typing.TypedDict): - x: int - y: int - - class PointDict3D(PointDict2D, total=False): - z: int - - assert is_typeddict(Point) is True - assert is_typeddict(PointDict2D) is True - assert is_typeddict(PointDict3D) is True - - -class AnnotatedTests(BaseTestCase): - - def test_repr(self): - if hasattr(typing, 'Annotated'): - mod_name = 'typing' - else: - mod_name = "typing_extensions" - self.assertEqual( - repr(Annotated[int, 4, 5]), - mod_name + ".Annotated[int, 4, 5]" - ) - self.assertEqual( - repr(Annotated[List[int], 4, 5]), - mod_name + ".Annotated[typing.List[int], 4, 5]" - ) - - def test_flatten(self): - A = Annotated[Annotated[int, 4], 5] - self.assertEqual(A, Annotated[int, 4, 5]) - self.assertEqual(A.__metadata__, (4, 5)) - self.assertEqual(A.__origin__, int) - - def test_specialize(self): - L = Annotated[List[T], "my decoration"] - LI = Annotated[List[int], "my decoration"] - self.assertEqual(L[int], Annotated[List[int], "my decoration"]) - self.assertEqual(L[int].__metadata__, ("my decoration",)) - self.assertEqual(L[int].__origin__, List[int]) - with self.assertRaises(TypeError): - LI[int] - with self.assertRaises(TypeError): - L[int, float] - - def test_hash_eq(self): - self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5]) - self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4]) - self.assertEqual( - {Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]}, - {Annotated[int, 4, 5], Annotated[T, 4, 5]} - ) - - def test_instantiate(self): - class C: - classvar = 4 - - def __init__(self, x): - self.x = x - - def __eq__(self, other): - if not isinstance(other, C): - return NotImplemented - return other.x == self.x - - A = Annotated[C, "a decoration"] - a = A(5) - c = C(5) - self.assertEqual(a, c) - self.assertEqual(a.x, c.x) - self.assertEqual(a.classvar, c.classvar) - - def test_instantiate_generic(self): - MyCount = Annotated[typing_extensions.Counter[T], "my decoration"] - self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1}) - self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1}) - - def test_cannot_instantiate_forward(self): - A = Annotated["int", (5, 6)] - with self.assertRaises(TypeError): - A(5) - - def test_cannot_instantiate_type_var(self): - A = Annotated[T, (5, 6)] - with self.assertRaises(TypeError): - A(5) - - def test_cannot_getattr_typevar(self): - with self.assertRaises(AttributeError): - Annotated[T, (5, 7)].x - - def test_attr_passthrough(self): - class C: - classvar = 4 - - A = Annotated[C, "a decoration"] - self.assertEqual(A.classvar, 4) - A.x = 5 - self.assertEqual(C.x, 5) - - @skipIf(sys.version_info[:2] in ((3, 9), (3, 10)), "Waiting for bpo-46491 bugfix.") - def test_special_form_containment(self): - class C: - classvar: Annotated[ClassVar[int], "a decoration"] = 4 - const: Annotated[Final[int], "Const"] = 4 - - if sys.version_info[:2] >= (3, 7): - self.assertEqual(get_type_hints(C, globals())["classvar"], ClassVar[int]) - self.assertEqual(get_type_hints(C, globals())["const"], Final[int]) - else: - self.assertEqual( - get_type_hints(C, globals())["classvar"], - Annotated[ClassVar[int], "a decoration"] - ) - self.assertEqual( - get_type_hints(C, globals())["const"], Annotated[Final[int], "Const"] - ) - - def test_hash_eq(self): - self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5]) - self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4]) - self.assertEqual( - {Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]}, - {Annotated[int, 4, 5], Annotated[T, 4, 5]} - ) - - def test_cannot_subclass(self): - with self.assertRaisesRegex(TypeError, "Cannot subclass .*Annotated"): - class C(Annotated): - pass - - def test_cannot_check_instance(self): - with self.assertRaises(TypeError): - isinstance(5, Annotated[int, "positive"]) - - def test_cannot_check_subclass(self): - with self.assertRaises(TypeError): - issubclass(int, Annotated[int, "positive"]) - - def test_pickle(self): - samples = [typing.Any, typing.Union[int, str], - typing.Optional[str], Tuple[int, ...], - typing.Callable[[str], bytes], - Self, LiteralString, Never] - - for t in samples: - x = Annotated[t, "a"] - - for prot in range(pickle.HIGHEST_PROTOCOL + 1): - with self.subTest(protocol=prot, type=t): - pickled = pickle.dumps(x, prot) - restored = pickle.loads(pickled) - self.assertEqual(x, restored) - - global _Annotated_test_G - - class _Annotated_test_G(Generic[T]): - x = 1 - - G = Annotated[_Annotated_test_G[int], "A decoration"] - G.foo = 42 - G.bar = 'abc' - - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(G, proto) - x = pickle.loads(z) - self.assertEqual(x.foo, 42) - self.assertEqual(x.bar, 'abc') - self.assertEqual(x.x, 1) - - def test_subst(self): - dec = "a decoration" - dec2 = "another decoration" - - S = Annotated[T, dec2] - self.assertEqual(S[int], Annotated[int, dec2]) - - self.assertEqual(S[Annotated[int, dec]], Annotated[int, dec, dec2]) - L = Annotated[List[T], dec] - - self.assertEqual(L[int], Annotated[List[int], dec]) - with self.assertRaises(TypeError): - L[int, int] - - self.assertEqual(S[L[int]], Annotated[List[int], dec, dec2]) - - D = Annotated[Dict[KT, VT], dec] - self.assertEqual(D[str, int], Annotated[Dict[str, int], dec]) - with self.assertRaises(TypeError): - D[int] - - It = Annotated[int, dec] - with self.assertRaises(TypeError): - It[None] - - LI = L[int] - with self.assertRaises(TypeError): - LI[None] - - def test_annotated_in_other_types(self): - X = List[Annotated[T, 5]] - self.assertEqual(X[int], List[Annotated[int, 5]]) - - -class GetTypeHintsTests(BaseTestCase): - def test_get_type_hints(self): - def foobar(x: List['X']): ... - X = Annotated[int, (1, 10)] - self.assertEqual( - get_type_hints(foobar, globals(), locals()), - {'x': List[int]} - ) - self.assertEqual( - get_type_hints(foobar, globals(), locals(), include_extras=True), - {'x': List[Annotated[int, (1, 10)]]} - ) - BA = Tuple[Annotated[T, (1, 0)], ...] - def barfoo(x: BA): ... - self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...]) - self.assertIs( - get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'], - BA - ) - def barfoo2(x: typing.Callable[..., Annotated[List[T], "const"]], - y: typing.Union[int, Annotated[T, "mutable"]]): ... - self.assertEqual( - get_type_hints(barfoo2, globals(), locals()), - {'x': typing.Callable[..., List[T]], 'y': typing.Union[int, T]} - ) - BA2 = typing.Callable[..., List[T]] - def barfoo3(x: BA2): ... - self.assertIs( - get_type_hints(barfoo3, globals(), locals(), include_extras=True)["x"], - BA2 - ) - - def test_get_type_hints_refs(self): - - Const = Annotated[T, "Const"] - - class MySet(Generic[T]): - - def __ior__(self, other: "Const[MySet[T]]") -> "MySet[T]": - ... - - def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]": - ... - - self.assertEqual( - get_type_hints(MySet.__iand__, globals(), locals()), - {'other': MySet[T], 'return': MySet[T]} - ) - - self.assertEqual( - get_type_hints(MySet.__iand__, globals(), locals(), include_extras=True), - {'other': Const[MySet[T]], 'return': MySet[T]} - ) - - self.assertEqual( - get_type_hints(MySet.__ior__, globals(), locals()), - {'other': MySet[T], 'return': MySet[T]} - ) - - def test_get_type_hints_typeddict(self): - assert get_type_hints(TotalMovie) == {'title': str, 'year': int} - assert get_type_hints(TotalMovie, include_extras=True) == { - 'title': str, - 'year': NotRequired[int], - } - - assert get_type_hints(AnnotatedMovie) == {'title': str, 'year': int} - assert get_type_hints(AnnotatedMovie, include_extras=True) == { - 'title': Annotated[Required[str], "foobar"], - 'year': NotRequired[Annotated[int, 2000]], - } - - -class TypeAliasTests(BaseTestCase): - def test_canonical_usage_with_variable_annotation(self): - ns = {} - exec('Alias: TypeAlias = Employee', globals(), ns) - - def test_canonical_usage_with_type_comment(self): - Alias = Employee # type: TypeAlias - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - TypeAlias() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(42, TypeAlias) - - def test_no_issubclass(self): - with self.assertRaises(TypeError): - issubclass(Employee, TypeAlias) - - with self.assertRaises(TypeError): - issubclass(TypeAlias, Employee) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(TypeAlias): - pass - - with self.assertRaises(TypeError): - class C(type(TypeAlias)): - pass - - def test_repr(self): - if hasattr(typing, 'TypeAlias'): - self.assertEqual(repr(TypeAlias), 'typing.TypeAlias') - else: - self.assertEqual(repr(TypeAlias), 'typing_extensions.TypeAlias') - - def test_cannot_subscript(self): - with self.assertRaises(TypeError): - TypeAlias[int] - -class ParamSpecTests(BaseTestCase): - - def test_basic_plain(self): - P = ParamSpec('P') - self.assertEqual(P, P) - self.assertIsInstance(P, ParamSpec) - # Should be hashable - hash(P) - - def test_repr(self): - P = ParamSpec('P') - P_co = ParamSpec('P_co', covariant=True) - P_contra = ParamSpec('P_contra', contravariant=True) - P_2 = ParamSpec('P_2') - self.assertEqual(repr(P), '~P') - self.assertEqual(repr(P_2), '~P_2') - - # Note: PEP 612 doesn't require these to be repr-ed correctly, but - # just follow CPython. - self.assertEqual(repr(P_co), '+P_co') - self.assertEqual(repr(P_contra), '-P_contra') - - def test_valid_uses(self): - P = ParamSpec('P') - T = TypeVar('T') - C1 = typing.Callable[P, int] - self.assertEqual(C1.__args__, (P, int)) - self.assertEqual(C1.__parameters__, (P,)) - C2 = typing.Callable[P, T] - self.assertEqual(C2.__args__, (P, T)) - self.assertEqual(C2.__parameters__, (P, T)) - - - # Test collections.abc.Callable too. - if sys.version_info[:2] >= (3, 9): - # Note: no tests for Callable.__parameters__ here - # because types.GenericAlias Callable is hardcoded to search - # for tp_name "TypeVar" in C. This was changed in 3.10. - C3 = collections.abc.Callable[P, int] - self.assertEqual(C3.__args__, (P, int)) - C4 = collections.abc.Callable[P, T] - self.assertEqual(C4.__args__, (P, T)) - - # ParamSpec instances should also have args and kwargs attributes. - # Note: not in dir(P) because of __class__ hacks - self.assertTrue(hasattr(P, 'args')) - self.assertTrue(hasattr(P, 'kwargs')) - - @skipIf((3, 10, 0) <= sys.version_info[:3] <= (3, 10, 2), "Needs bpo-46676.") - def test_args_kwargs(self): - P = ParamSpec('P') - P_2 = ParamSpec('P_2') - # Note: not in dir(P) because of __class__ hacks - self.assertTrue(hasattr(P, 'args')) - self.assertTrue(hasattr(P, 'kwargs')) - self.assertIsInstance(P.args, ParamSpecArgs) - self.assertIsInstance(P.kwargs, ParamSpecKwargs) - self.assertIs(P.args.__origin__, P) - self.assertIs(P.kwargs.__origin__, P) - self.assertEqual(P.args, P.args) - self.assertEqual(P.kwargs, P.kwargs) - self.assertNotEqual(P.args, P_2.args) - self.assertNotEqual(P.kwargs, P_2.kwargs) - self.assertNotEqual(P.args, P.kwargs) - self.assertNotEqual(P.kwargs, P.args) - self.assertNotEqual(P.args, P_2.kwargs) - self.assertEqual(repr(P.args), "P.args") - self.assertEqual(repr(P.kwargs), "P.kwargs") - - def test_user_generics(self): - T = TypeVar("T") - P = ParamSpec("P") - P_2 = ParamSpec("P_2") - - class X(Generic[T, P]): - pass - - G1 = X[int, P_2] - self.assertEqual(G1.__args__, (int, P_2)) - self.assertEqual(G1.__parameters__, (P_2,)) - - G2 = X[int, Concatenate[int, P_2]] - self.assertEqual(G2.__args__, (int, Concatenate[int, P_2])) - self.assertEqual(G2.__parameters__, (P_2,)) - - # The following are some valid uses cases in PEP 612 that don't work: - # These do not work in 3.9, _type_check blocks the list and ellipsis. - # G3 = X[int, [int, bool]] - # G4 = X[int, ...] - # G5 = Z[[int, str, bool]] - # Not working because this is special-cased in 3.10. - # G6 = Z[int, str, bool] - - class Z(Generic[P]): - pass - - def test_pickle(self): - global P, P_co, P_contra - P = ParamSpec('P') - P_co = ParamSpec('P_co', covariant=True) - P_contra = ParamSpec('P_contra', contravariant=True) - for proto in range(pickle.HIGHEST_PROTOCOL): - with self.subTest(f'Pickle protocol {proto}'): - for paramspec in (P, P_co, P_contra): - z = pickle.loads(pickle.dumps(paramspec, proto)) - self.assertEqual(z.__name__, paramspec.__name__) - self.assertEqual(z.__covariant__, paramspec.__covariant__) - self.assertEqual(z.__contravariant__, paramspec.__contravariant__) - self.assertEqual(z.__bound__, paramspec.__bound__) - - def test_eq(self): - P = ParamSpec('P') - self.assertEqual(P, P) - self.assertEqual(hash(P), hash(P)) - # ParamSpec should compare by id similar to TypeVar in CPython - self.assertNotEqual(ParamSpec('P'), P) - self.assertIsNot(ParamSpec('P'), P) - # Note: normally you don't test this as it breaks when there's - # a hash collision. However, ParamSpec *must* guarantee that - # as long as two objects don't have the same ID, their hashes - # won't be the same. - self.assertNotEqual(hash(ParamSpec('P')), hash(P)) - - -class ConcatenateTests(BaseTestCase): - def test_basics(self): - P = ParamSpec('P') - - class MyClass: ... - - c = Concatenate[MyClass, P] - self.assertNotEqual(c, Concatenate) - - def test_valid_uses(self): - P = ParamSpec('P') - T = TypeVar('T') - - C1 = Callable[Concatenate[int, P], int] - C2 = Callable[Concatenate[int, T, P], T] - - # Test collections.abc.Callable too. - if sys.version_info[:2] >= (3, 9): - C3 = collections.abc.Callable[Concatenate[int, P], int] - C4 = collections.abc.Callable[Concatenate[int, T, P], T] - - def test_invalid_uses(self): - P = ParamSpec('P') - T = TypeVar('T') - - with self.assertRaisesRegex( - TypeError, - 'Cannot take a Concatenate of no types', - ): - Concatenate[()] - - with self.assertRaisesRegex( - TypeError, - 'The last parameter to Concatenate should be a ParamSpec variable', - ): - Concatenate[P, T] - - if not TYPING_3_11_0: - with self.assertRaisesRegex( - TypeError, - 'each arg must be a type', - ): - Concatenate[1, P] - - def test_basic_introspection(self): - P = ParamSpec('P') - C1 = Concatenate[int, P] - C2 = Concatenate[int, T, P] - self.assertEqual(C1.__origin__, Concatenate) - self.assertEqual(C1.__args__, (int, P)) - self.assertEqual(C2.__origin__, Concatenate) - self.assertEqual(C2.__args__, (int, T, P)) - - def test_eq(self): - P = ParamSpec('P') - C1 = Concatenate[int, P] - C2 = Concatenate[int, P] - C3 = Concatenate[int, T, P] - self.assertEqual(C1, C2) - self.assertEqual(hash(C1), hash(C2)) - self.assertNotEqual(C1, C3) - - -class TypeGuardTests(BaseTestCase): - def test_basics(self): - TypeGuard[int] # OK - self.assertEqual(TypeGuard[int], TypeGuard[int]) - - def foo(arg) -> TypeGuard[int]: ... - self.assertEqual(gth(foo), {'return': TypeGuard[int]}) - - def test_repr(self): - if hasattr(typing, 'TypeGuard'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(TypeGuard), f'{mod_name}.TypeGuard') - cv = TypeGuard[int] - self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[int]') - cv = TypeGuard[Employee] - self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[{__name__}.Employee]') - cv = TypeGuard[Tuple[int]] - self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[typing.Tuple[int]]') - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(TypeGuard)): - pass - with self.assertRaises(TypeError): - class C(type(TypeGuard[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - TypeGuard() - with self.assertRaises(TypeError): - type(TypeGuard)() - with self.assertRaises(TypeError): - type(TypeGuard[Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, TypeGuard[int]) - with self.assertRaises(TypeError): - issubclass(int, TypeGuard) - - -class LiteralStringTests(BaseTestCase): - def test_basics(self): - class Foo: - def bar(self) -> LiteralString: ... - def baz(self) -> "LiteralString": ... - - self.assertEqual(gth(Foo.bar), {'return': LiteralString}) - self.assertEqual(gth(Foo.baz), {'return': LiteralString}) - - def test_get_origin(self): - self.assertIsNone(get_origin(LiteralString)) - - def test_repr(self): - if hasattr(typing, 'LiteralString'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(LiteralString), '{}.LiteralString'.format(mod_name)) - - def test_cannot_subscript(self): - with self.assertRaises(TypeError): - LiteralString[int] - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(LiteralString)): - pass - with self.assertRaises(TypeError): - class C(LiteralString): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - LiteralString() - with self.assertRaises(TypeError): - type(LiteralString)() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, LiteralString) - with self.assertRaises(TypeError): - issubclass(int, LiteralString) - - def test_alias(self): - StringTuple = Tuple[LiteralString, LiteralString] - class Alias: - def return_tuple(self) -> StringTuple: - return ("foo", "pep" + "675") - - def test_typevar(self): - StrT = TypeVar("StrT", bound=LiteralString) - self.assertIs(StrT.__bound__, LiteralString) - - def test_pickle(self): - for proto in range(pickle.HIGHEST_PROTOCOL): - pickled = pickle.dumps(LiteralString, protocol=proto) - self.assertIs(LiteralString, pickle.loads(pickled)) - - -class SelfTests(BaseTestCase): - def test_basics(self): - class Foo: - def bar(self) -> Self: ... - - self.assertEqual(gth(Foo.bar), {'return': Self}) - - def test_repr(self): - if hasattr(typing, 'Self'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(Self), '{}.Self'.format(mod_name)) - - def test_cannot_subscript(self): - with self.assertRaises(TypeError): - Self[int] - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(Self)): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - Self() - with self.assertRaises(TypeError): - type(Self)() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, Self) - with self.assertRaises(TypeError): - issubclass(int, Self) - - def test_alias(self): - TupleSelf = Tuple[Self, Self] - class Alias: - def return_tuple(self) -> TupleSelf: - return (self, self) - - def test_pickle(self): - for proto in range(pickle.HIGHEST_PROTOCOL): - pickled = pickle.dumps(Self, protocol=proto) - self.assertIs(Self, pickle.loads(pickled)) - - -class UnpackTests(BaseTestCase): - def test_basic_plain(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(Unpack[Ts], Unpack[Ts]) - with self.assertRaises(TypeError): - Unpack() - - def test_repr(self): - Ts = TypeVarTuple('Ts') - if TYPING_3_11_0: - self.assertEqual(repr(Unpack[Ts]), '*Ts') - else: - self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]') - - def test_cannot_subclass_vars(self): - with self.assertRaises(TypeError): - class V(Unpack[TypeVarTuple('Ts')]): - pass - - def test_tuple(self): - Ts = TypeVarTuple('Ts') - Tuple[Unpack[Ts]] - - def test_union(self): - Xs = TypeVarTuple('Xs') - Ys = TypeVarTuple('Ys') - self.assertEqual( - Union[Unpack[Xs]], - Unpack[Xs] - ) - self.assertNotEqual( - Union[Unpack[Xs]], - Union[Unpack[Xs], Unpack[Ys]] - ) - self.assertEqual( - Union[Unpack[Xs], Unpack[Xs]], - Unpack[Xs] - ) - self.assertNotEqual( - Union[Unpack[Xs], int], - Union[Unpack[Xs]] - ) - self.assertNotEqual( - Union[Unpack[Xs], int], - Union[int] - ) - self.assertEqual( - Union[Unpack[Xs], int].__args__, - (Unpack[Xs], int) - ) - self.assertEqual( - Union[Unpack[Xs], int].__parameters__, - (Xs,) - ) - self.assertIs( - Union[Unpack[Xs], int].__origin__, - Union - ) - - def test_concatenation(self): - Xs = TypeVarTuple('Xs') - self.assertEqual(Tuple[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) - self.assertEqual(Tuple[Unpack[Xs], int].__args__, (Unpack[Xs], int)) - self.assertEqual(Tuple[int, Unpack[Xs], str].__args__, - (int, Unpack[Xs], str)) - class C(Generic[Unpack[Xs]]): pass - self.assertEqual(C[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) - self.assertEqual(C[Unpack[Xs], int].__args__, (Unpack[Xs], int)) - self.assertEqual(C[int, Unpack[Xs], str].__args__, - (int, Unpack[Xs], str)) - - def test_class(self): - Ts = TypeVarTuple('Ts') - - class C(Generic[Unpack[Ts]]): pass - self.assertEqual(C[int].__args__, (int,)) - self.assertEqual(C[int, str].__args__, (int, str)) - - with self.assertRaises(TypeError): - class C(Generic[Unpack[Ts], int]): pass - - T1 = TypeVar('T') - T2 = TypeVar('T') - class C(Generic[T1, T2, Unpack[Ts]]): pass - self.assertEqual(C[int, str].__args__, (int, str)) - self.assertEqual(C[int, str, float].__args__, (int, str, float)) - self.assertEqual(C[int, str, float, bool].__args__, (int, str, float, bool)) - # TODO This should probably also fail on 3.11, pending changes to CPython. - if not TYPING_3_11_0: - with self.assertRaises(TypeError): - C[int] - - -class TypeVarTupleTests(BaseTestCase): - - def test_basic_plain(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(Ts, Ts) - self.assertIsInstance(Ts, TypeVarTuple) - Xs = TypeVarTuple('Xs') - Ys = TypeVarTuple('Ys') - self.assertNotEqual(Xs, Ys) - - def test_repr(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(repr(Ts), 'Ts') - - def test_no_redefinition(self): - self.assertNotEqual(TypeVarTuple('Ts'), TypeVarTuple('Ts')) - - def test_cannot_subclass_vars(self): - with self.assertRaises(TypeError): - class V(TypeVarTuple('Ts')): - pass - - def test_cannot_subclass_var_itself(self): - with self.assertRaises(TypeError): - class V(TypeVarTuple): - pass - - def test_cannot_instantiate_vars(self): - Ts = TypeVarTuple('Ts') - with self.assertRaises(TypeError): - Ts() - - def test_tuple(self): - Ts = TypeVarTuple('Ts') - # Not legal at type checking time but we can't really check against it. - Tuple[Ts] - - def test_args_and_parameters(self): - Ts = TypeVarTuple('Ts') - - t = Tuple[tuple(Ts)] - self.assertEqual(t.__args__, (Unpack[Ts],)) - self.assertEqual(t.__parameters__, (Ts,)) - - -class FinalDecoratorTests(BaseTestCase): - def test_final_unmodified(self): - def func(x): ... - self.assertIs(func, final(func)) - - def test_dunder_final(self): - @final - def func(): ... - @final - class Cls: ... - self.assertIs(True, func.__final__) - self.assertIs(True, Cls.__final__) - - class Wrapper: - __slots__ = ("func",) - def __init__(self, func): - self.func = func - def __call__(self, *args, **kwargs): - return self.func(*args, **kwargs) - - # Check that no error is thrown if the attribute - # is not writable. - @final - @Wrapper - def wrapped(): ... - self.assertIsInstance(wrapped, Wrapper) - self.assertIs(False, hasattr(wrapped, "__final__")) - - class Meta(type): - @property - def __final__(self): return "can't set me" - @final - class WithMeta(metaclass=Meta): ... - self.assertEqual(WithMeta.__final__, "can't set me") - - # Builtin classes throw TypeError if you try to set an - # attribute. - final(int) - self.assertIs(False, hasattr(int, "__final__")) - - # Make sure it works with common builtin decorators - class Methods: - @final - @classmethod - def clsmethod(cls): ... - - @final - @staticmethod - def stmethod(): ... - - # The other order doesn't work because property objects - # don't allow attribute assignment. - @property - @final - def prop(self): ... - - @final - @lru_cache() # noqa: B019 - def cached(self): ... - - # Use getattr_static because the descriptor returns the - # underlying function, which doesn't have __final__. - self.assertIs( - True, - inspect.getattr_static(Methods, "clsmethod").__final__ - ) - self.assertIs( - True, - inspect.getattr_static(Methods, "stmethod").__final__ - ) - self.assertIs(True, Methods.prop.fget.__final__) - self.assertIs(True, Methods.cached.__final__) - - -class RevealTypeTests(BaseTestCase): - def test_reveal_type(self): - obj = object() - self.assertIs(obj, reveal_type(obj)) - - -class DataclassTransformTests(BaseTestCase): - def test_decorator(self): - def create_model(*, frozen: bool = False, kw_only: bool = True): - return lambda cls: cls - - decorated = dataclass_transform(kw_only_default=True, order_default=False)(create_model) - - class CustomerModel: - id: int - - self.assertIs(decorated, create_model) - self.assertEqual( - decorated.__dataclass_transform__, - { - "eq_default": True, - "order_default": False, - "kw_only_default": True, - "field_specifiers": (), - "kwargs": {}, - } - ) - self.assertIs( - decorated(frozen=True, kw_only=False)(CustomerModel), - CustomerModel - ) - - def test_base_class(self): - class ModelBase: - def __init_subclass__(cls, *, frozen: bool = False): ... - - Decorated = dataclass_transform( - eq_default=True, - order_default=True, - # Arbitrary unrecognized kwargs are accepted at runtime. - make_everything_awesome=True, - )(ModelBase) - - class CustomerModel(Decorated, frozen=True): - id: int - - self.assertIs(Decorated, ModelBase) - self.assertEqual( - Decorated.__dataclass_transform__, - { - "eq_default": True, - "order_default": True, - "kw_only_default": False, - "field_specifiers": (), - "kwargs": {"make_everything_awesome": True}, - } - ) - self.assertIsSubclass(CustomerModel, Decorated) - - def test_metaclass(self): - class Field: ... - - class ModelMeta(type): - def __new__( - cls, name, bases, namespace, *, init: bool = True, - ): - return super().__new__(cls, name, bases, namespace) - - Decorated = dataclass_transform( - order_default=True, field_specifiers=(Field,) - )(ModelMeta) - - class ModelBase(metaclass=Decorated): ... - - class CustomerModel(ModelBase, init=False): - id: int - - self.assertIs(Decorated, ModelMeta) - self.assertEqual( - Decorated.__dataclass_transform__, - { - "eq_default": True, - "order_default": True, - "kw_only_default": False, - "field_specifiers": (Field,), - "kwargs": {}, - } - ) - self.assertIsInstance(CustomerModel, Decorated) - - -class AllTests(BaseTestCase): - - def test_typing_extensions_includes_standard(self): - a = typing_extensions.__all__ - self.assertIn('ClassVar', a) - self.assertIn('Type', a) - self.assertIn('ChainMap', a) - self.assertIn('ContextManager', a) - self.assertIn('Counter', a) - self.assertIn('DefaultDict', a) - self.assertIn('Deque', a) - self.assertIn('NewType', a) - self.assertIn('overload', a) - self.assertIn('Text', a) - self.assertIn('TYPE_CHECKING', a) - self.assertIn('TypeAlias', a) - self.assertIn('ParamSpec', a) - self.assertIn("Concatenate", a) - - self.assertIn('Annotated', a) - self.assertIn('get_type_hints', a) - - self.assertIn('Awaitable', a) - self.assertIn('AsyncIterator', a) - self.assertIn('AsyncIterable', a) - self.assertIn('Coroutine', a) - self.assertIn('AsyncContextManager', a) - - self.assertIn('AsyncGenerator', a) - - self.assertIn('Protocol', a) - self.assertIn('runtime', a) - - # Check that all objects in `__all__` are present in the module - for name in a: - self.assertTrue(hasattr(typing_extensions, name)) - - def test_all_names_in___all__(self): - exclude = { - 'GenericMeta', - 'KT', - 'PEP_560', - 'T', - 'T_co', - 'T_contra', - 'VT', - } - actual_names = { - name for name in dir(typing_extensions) - if not name.startswith("_") - and not isinstance(getattr(typing_extensions, name), types.ModuleType) - } - # Make sure all public names are in __all__ - self.assertEqual({*exclude, *typing_extensions.__all__}, - actual_names) - # Make sure all excluded names actually exist - self.assertLessEqual(exclude, actual_names) - - def test_typing_extensions_defers_when_possible(self): - exclude = { - 'overload', - 'Text', - 'TypedDict', - 'TYPE_CHECKING', - 'Final', - 'get_type_hints', - 'is_typeddict', - } - if sys.version_info < (3, 10): - exclude |= {'get_args', 'get_origin'} - if sys.version_info < (3, 11): - exclude.add('final') - for item in typing_extensions.__all__: - if item not in exclude and hasattr(typing, item): - self.assertIs( - getattr(typing_extensions, item), - getattr(typing, item)) - - def test_typing_extensions_compiles_with_opt(self): - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'typing_extensions.py') - try: - subprocess.check_output(f'{sys.executable} -OO {file_path}', - stderr=subprocess.STDOUT, - shell=True) - except subprocess.CalledProcessError: - self.fail('Module does not compile with optimize=2 (-OO flag).') - - - -if __name__ == '__main__': - main() diff --git a/typing_extensions/src/typing_extensions.py b/typing_extensions/src/typing_extensions.py deleted file mode 100644 index dc0388196..000000000 --- a/typing_extensions/src/typing_extensions.py +++ /dev/null @@ -1,1960 +0,0 @@ -import abc -import collections -import collections.abc -import functools -import operator -import sys -import types as _types -import typing - - -# Please keep __all__ alphabetized within each category. -__all__ = [ - # Super-special typing primitives. - 'ClassVar', - 'Concatenate', - 'Final', - 'LiteralString', - 'ParamSpec', - 'ParamSpecArgs', - 'ParamSpecKwargs', - 'Self', - 'Type', - 'TypeVarTuple', - 'Unpack', - - # ABCs (from collections.abc). - 'Awaitable', - 'AsyncIterator', - 'AsyncIterable', - 'Coroutine', - 'AsyncGenerator', - 'AsyncContextManager', - 'ChainMap', - - # Concrete collection types. - 'ContextManager', - 'Counter', - 'Deque', - 'DefaultDict', - 'OrderedDict', - 'TypedDict', - - # Structural checks, a.k.a. protocols. - 'SupportsIndex', - - # One-off things. - 'Annotated', - 'assert_never', - 'assert_type', - 'clear_overloads', - 'dataclass_transform', - 'get_overloads', - 'final', - 'get_args', - 'get_origin', - 'get_type_hints', - 'IntVar', - 'is_typeddict', - 'Literal', - 'NewType', - 'overload', - 'Protocol', - 'reveal_type', - 'runtime', - 'runtime_checkable', - 'Text', - 'TypeAlias', - 'TypeGuard', - 'TYPE_CHECKING', - 'Never', - 'NoReturn', - 'Required', - 'NotRequired', -] - -# for backward compatibility -PEP_560 = True -GenericMeta = type - -# The functions below are modified copies of typing internal helpers. -# They are needed by _ProtocolMeta and they provide support for PEP 646. - -_marker = object() - - -def _check_generic(cls, parameters, elen=_marker): - """Check correct count for parameters of a generic cls (internal helper). - This gives a nice error message in case of count mismatch. - """ - if not elen: - raise TypeError(f"{cls} is not a generic class") - if elen is _marker: - if not hasattr(cls, "__parameters__") or not cls.__parameters__: - raise TypeError(f"{cls} is not a generic class") - elen = len(cls.__parameters__) - alen = len(parameters) - if alen != elen: - if hasattr(cls, "__parameters__"): - parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] - num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters) - if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples): - return - raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};" - f" actual {alen}, expected {elen}") - - -if sys.version_info >= (3, 10): - def _should_collect_from_parameters(t): - return isinstance( - t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType) - ) -elif sys.version_info >= (3, 9): - def _should_collect_from_parameters(t): - return isinstance(t, (typing._GenericAlias, _types.GenericAlias)) -else: - def _should_collect_from_parameters(t): - return isinstance(t, typing._GenericAlias) and not t._special - - -def _collect_type_vars(types, typevar_types=None): - """Collect all type variable contained in types in order of - first appearance (lexicographic order). For example:: - - _collect_type_vars((T, List[S, T])) == (T, S) - """ - if typevar_types is None: - typevar_types = typing.TypeVar - tvars = [] - for t in types: - if ( - isinstance(t, typevar_types) and - t not in tvars and - not _is_unpack(t) - ): - tvars.append(t) - if _should_collect_from_parameters(t): - tvars.extend([t for t in t.__parameters__ if t not in tvars]) - return tuple(tvars) - - -NoReturn = typing.NoReturn - -# Some unconstrained type variables. These are used by the container types. -# (These are not for export.) -T = typing.TypeVar('T') # Any type. -KT = typing.TypeVar('KT') # Key type. -VT = typing.TypeVar('VT') # Value type. -T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers. -T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant. - -ClassVar = typing.ClassVar - -# On older versions of typing there is an internal class named "Final". -# 3.8+ -if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): - Final = typing.Final -# 3.7 -else: - class _FinalForm(typing._SpecialForm, _root=True): - - def __repr__(self): - return 'typing_extensions.' + self._name - - def __getitem__(self, parameters): - item = typing._type_check(parameters, - f'{self._name} accepts only a single type.') - return typing._GenericAlias(self, (item,)) - - Final = _FinalForm('Final', - doc="""A special typing construct to indicate that a name - cannot be re-assigned or overridden in a subclass. - For example: - - MAX_SIZE: Final = 9000 - MAX_SIZE += 1 # Error reported by type checker - - class Connection: - TIMEOUT: Final[int] = 10 - class FastConnector(Connection): - TIMEOUT = 1 # Error reported by type checker - - There is no runtime checking of these properties.""") - -if sys.version_info >= (3, 11): - final = typing.final -else: - # @final exists in 3.8+, but we backport it for all versions - # before 3.11 to keep support for the __final__ attribute. - # See https://bugs.python.org/issue46342 - def final(f): - """This decorator can be used to indicate to type checkers that - the decorated method cannot be overridden, and decorated class - cannot be subclassed. For example: - - class Base: - @final - def done(self) -> None: - ... - class Sub(Base): - def done(self) -> None: # Error reported by type checker - ... - @final - class Leaf: - ... - class Other(Leaf): # Error reported by type checker - ... - - There is no runtime checking of these properties. The decorator - sets the ``__final__`` attribute to ``True`` on the decorated object - to allow runtime introspection. - """ - try: - f.__final__ = True - except (AttributeError, TypeError): - # Skip the attribute silently if it is not writable. - # AttributeError happens if the object has __slots__ or a - # read-only property, TypeError if it's a builtin class. - pass - return f - - -def IntVar(name): - return typing.TypeVar(name) - - -# 3.8+: -if hasattr(typing, 'Literal'): - Literal = typing.Literal -# 3.7: -else: - class _LiteralForm(typing._SpecialForm, _root=True): - - def __repr__(self): - return 'typing_extensions.' + self._name - - def __getitem__(self, parameters): - return typing._GenericAlias(self, parameters) - - Literal = _LiteralForm('Literal', - doc="""A type that can be used to indicate to type checkers - that the corresponding value has a value literally equivalent - to the provided parameter. For example: - - var: Literal[4] = 4 - - The type checker understands that 'var' is literally equal to - the value 4 and no other value. - - Literal[...] cannot be subclassed. There is no runtime - checking verifying that the parameter is actually a value - instead of a type.""") - - -_overload_dummy = typing._overload_dummy # noqa - - -if hasattr(typing, "get_overloads"): # 3.11+ - overload = typing.overload - get_overloads = typing.get_overloads - clear_overloads = typing.clear_overloads -else: - # {module: {qualname: {firstlineno: func}}} - _overload_registry = collections.defaultdict( - functools.partial(collections.defaultdict, dict) - ) - - def overload(func): - """Decorator for overloaded functions/methods. - - In a stub file, place two or more stub definitions for the same - function in a row, each decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - - In a non-stub file (i.e. a regular .py file), do the same but - follow it with an implementation. The implementation should *not* - be decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - def utf8(value): - # implementation goes here - - The overloads for a function can be retrieved at runtime using the - get_overloads() function. - """ - # classmethod and staticmethod - f = getattr(func, "__func__", func) - try: - _overload_registry[f.__module__][f.__qualname__][ - f.__code__.co_firstlineno - ] = func - except AttributeError: - # Not a normal function; ignore. - pass - return _overload_dummy - - def get_overloads(func): - """Return all defined overloads for *func* as a sequence.""" - # classmethod and staticmethod - f = getattr(func, "__func__", func) - if f.__module__ not in _overload_registry: - return [] - mod_dict = _overload_registry[f.__module__] - if f.__qualname__ not in mod_dict: - return [] - return list(mod_dict[f.__qualname__].values()) - - def clear_overloads(): - """Clear all overloads in the registry.""" - _overload_registry.clear() - - -# This is not a real generic class. Don't use outside annotations. -Type = typing.Type - -# Various ABCs mimicking those in collections.abc. -# A few are simply re-exported for completeness. - - -Awaitable = typing.Awaitable -Coroutine = typing.Coroutine -AsyncIterable = typing.AsyncIterable -AsyncIterator = typing.AsyncIterator -Deque = typing.Deque -ContextManager = typing.ContextManager -AsyncContextManager = typing.AsyncContextManager -DefaultDict = typing.DefaultDict - -# 3.7.2+ -if hasattr(typing, 'OrderedDict'): - OrderedDict = typing.OrderedDict -# 3.7.0-3.7.2 -else: - OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) - -Counter = typing.Counter -ChainMap = typing.ChainMap -AsyncGenerator = typing.AsyncGenerator -NewType = typing.NewType -Text = typing.Text -TYPE_CHECKING = typing.TYPE_CHECKING - - -_PROTO_WHITELIST = ['Callable', 'Awaitable', - 'Iterable', 'Iterator', 'AsyncIterable', 'AsyncIterator', - 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', - 'ContextManager', 'AsyncContextManager'] - - -def _get_protocol_attrs(cls): - attrs = set() - for base in cls.__mro__[:-1]: # without object - if base.__name__ in ('Protocol', 'Generic'): - continue - annotations = getattr(base, '__annotations__', {}) - for attr in list(base.__dict__.keys()) + list(annotations.keys()): - if (not attr.startswith('_abc_') and attr not in ( - '__abstractmethods__', '__annotations__', '__weakref__', - '_is_protocol', '_is_runtime_protocol', '__dict__', - '__args__', '__slots__', - '__next_in_mro__', '__parameters__', '__origin__', - '__orig_bases__', '__extra__', '__tree_hash__', - '__doc__', '__subclasshook__', '__init__', '__new__', - '__module__', '_MutableMapping__marker', '_gorg')): - attrs.add(attr) - return attrs - - -def _is_callable_members_only(cls): - return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls)) - - -# 3.8+ -if hasattr(typing, 'Protocol'): - Protocol = typing.Protocol -# 3.7 -else: - - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') - - class _ProtocolMeta(abc.ABCMeta): - # This metaclass is a bit unfortunate and exists only because of the lack - # of __instancehook__. - def __instancecheck__(cls, instance): - # We need this method for situations where attributes are - # assigned in __init__. - if ((not getattr(cls, '_is_protocol', False) or - _is_callable_members_only(cls)) and - issubclass(instance.__class__, cls)): - return True - if cls._is_protocol: - if all(hasattr(instance, attr) and - (not callable(getattr(cls, attr, None)) or - getattr(instance, attr) is not None) - for attr in _get_protocol_attrs(cls)): - return True - return super().__instancecheck__(instance) - - class Protocol(metaclass=_ProtocolMeta): - # There is quite a lot of overlapping code with typing.Generic. - # Unfortunately it is hard to avoid this while these live in two different - # modules. The duplicated code will be removed when Protocol is moved to typing. - """Base class for protocol classes. Protocol classes are defined as:: - - class Proto(Protocol): - def meth(self) -> int: - ... - - Such classes are primarily used with static type checkers that recognize - structural subtyping (static duck-typing), for example:: - - class C: - def meth(self) -> int: - return 0 - - def func(x: Proto) -> int: - return x.meth() - - func(C()) # Passes static type check - - See PEP 544 for details. Protocol classes decorated with - @typing_extensions.runtime act as simple-minded runtime protocol that checks - only the presence of given attributes, ignoring their type signatures. - - Protocol classes can be generic, they are defined as:: - - class GenProto(Protocol[T]): - def meth(self) -> T: - ... - """ - __slots__ = () - _is_protocol = True - - def __new__(cls, *args, **kwds): - if cls is Protocol: - raise TypeError("Type Protocol cannot be instantiated; " - "it can only be used as a base class") - return super().__new__(cls) - - @typing._tp_cache - def __class_getitem__(cls, params): - if not isinstance(params, tuple): - params = (params,) - if not params and cls is not typing.Tuple: - raise TypeError( - f"Parameter list to {cls.__qualname__}[...] cannot be empty") - msg = "Parameters to generic types must be types." - params = tuple(typing._type_check(p, msg) for p in params) # noqa - if cls is Protocol: - # Generic can only be subscripted with unique type variables. - if not all(isinstance(p, typing.TypeVar) for p in params): - i = 0 - while isinstance(params[i], typing.TypeVar): - i += 1 - raise TypeError( - "Parameters to Protocol[...] must all be type variables." - f" Parameter {i + 1} is {params[i]}") - if len(set(params)) != len(params): - raise TypeError( - "Parameters to Protocol[...] must all be unique") - else: - # Subscripting a regular Generic subclass. - _check_generic(cls, params, len(cls.__parameters__)) - return typing._GenericAlias(cls, params) - - def __init_subclass__(cls, *args, **kwargs): - tvars = [] - if '__orig_bases__' in cls.__dict__: - error = typing.Generic in cls.__orig_bases__ - else: - error = typing.Generic in cls.__bases__ - if error: - raise TypeError("Cannot inherit from plain Generic") - if '__orig_bases__' in cls.__dict__: - tvars = typing._collect_type_vars(cls.__orig_bases__) - # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn]. - # If found, tvars must be a subset of it. - # If not found, tvars is it. - # Also check for and reject plain Generic, - # and reject multiple Generic[...] and/or Protocol[...]. - gvars = None - for base in cls.__orig_bases__: - if (isinstance(base, typing._GenericAlias) and - base.__origin__ in (typing.Generic, Protocol)): - # for error messages - the_base = base.__origin__.__name__ - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...]" - " and/or Protocol[...] multiple types.") - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - s_vars = ', '.join(str(t) for t in tvars if t not in gvarset) - s_args = ', '.join(str(g) for g in gvars) - raise TypeError(f"Some type variables ({s_vars}) are" - f" not listed in {the_base}[{s_args}]") - tvars = gvars - cls.__parameters__ = tuple(tvars) - - # Determine if this is a protocol or a concrete subclass. - if not cls.__dict__.get('_is_protocol', None): - cls._is_protocol = any(b is Protocol for b in cls.__bases__) - - # Set (or override) the protocol subclass hook. - def _proto_hook(other): - if not cls.__dict__.get('_is_protocol', None): - return NotImplemented - if not getattr(cls, '_is_runtime_protocol', False): - if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: - return NotImplemented - raise TypeError("Instance and class checks can only be used with" - " @runtime protocols") - if not _is_callable_members_only(cls): - if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: - return NotImplemented - raise TypeError("Protocols with non-method members" - " don't support issubclass()") - if not isinstance(other, type): - # Same error as for issubclass(1, int) - raise TypeError('issubclass() arg 1 must be a class') - for attr in _get_protocol_attrs(cls): - for base in other.__mro__: - if attr in base.__dict__: - if base.__dict__[attr] is None: - return NotImplemented - break - annotations = getattr(base, '__annotations__', {}) - if (isinstance(annotations, typing.Mapping) and - attr in annotations and - isinstance(other, _ProtocolMeta) and - other._is_protocol): - break - else: - return NotImplemented - return True - if '__subclasshook__' not in cls.__dict__: - cls.__subclasshook__ = _proto_hook - - # We have nothing more to do for non-protocols. - if not cls._is_protocol: - return - - # Check consistency of bases. - for base in cls.__bases__: - if not (base in (object, typing.Generic) or - base.__module__ == 'collections.abc' and - base.__name__ in _PROTO_WHITELIST or - isinstance(base, _ProtocolMeta) and base._is_protocol): - raise TypeError('Protocols can only inherit from other' - f' protocols, got {repr(base)}') - cls.__init__ = _no_init - - -# 3.8+ -if hasattr(typing, 'runtime_checkable'): - runtime_checkable = typing.runtime_checkable -# 3.7 -else: - def runtime_checkable(cls): - """Mark a protocol class as a runtime protocol, so that it - can be used with isinstance() and issubclass(). Raise TypeError - if applied to a non-protocol class. - - This allows a simple-minded structural check very similar to the - one-offs in collections.abc such as Hashable. - """ - if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol: - raise TypeError('@runtime_checkable can be only applied to protocol classes,' - f' got {cls!r}') - cls._is_runtime_protocol = True - return cls - - -# Exists for backwards compatibility. -runtime = runtime_checkable - - -# 3.8+ -if hasattr(typing, 'SupportsIndex'): - SupportsIndex = typing.SupportsIndex -# 3.7 -else: - @runtime_checkable - class SupportsIndex(Protocol): - __slots__ = () - - @abc.abstractmethod - def __index__(self) -> int: - pass - - -if hasattr(typing, "Required"): - # The standard library TypedDict in Python 3.8 does not store runtime information - # about which (if any) keys are optional. See https://bugs.python.org/issue38834 - # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" - # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 - # The standard library TypedDict below Python 3.11 does not store runtime - # information about optional and required keys when using Required or NotRequired. - TypedDict = typing.TypedDict - _TypedDictMeta = typing._TypedDictMeta - is_typeddict = typing.is_typeddict -else: - def _check_fails(cls, other): - try: - if sys._getframe(1).f_globals['__name__'] not in ['abc', - 'functools', - 'typing']: - # Typed dicts are only for static structural subtyping. - raise TypeError('TypedDict does not support instance and class checks') - except (AttributeError, ValueError): - pass - return False - - def _dict_new(*args, **kwargs): - if not args: - raise TypeError('TypedDict.__new__(): not enough arguments') - _, args = args[0], args[1:] # allow the "cls" keyword be passed - return dict(*args, **kwargs) - - _dict_new.__text_signature__ = '($cls, _typename, _fields=None, /, **kwargs)' - - def _typeddict_new(*args, total=True, **kwargs): - if not args: - raise TypeError('TypedDict.__new__(): not enough arguments') - _, args = args[0], args[1:] # allow the "cls" keyword be passed - if args: - typename, args = args[0], args[1:] # allow the "_typename" keyword be passed - elif '_typename' in kwargs: - typename = kwargs.pop('_typename') - import warnings - warnings.warn("Passing '_typename' as keyword argument is deprecated", - DeprecationWarning, stacklevel=2) - else: - raise TypeError("TypedDict.__new__() missing 1 required positional " - "argument: '_typename'") - if args: - try: - fields, = args # allow the "_fields" keyword be passed - except ValueError: - raise TypeError('TypedDict.__new__() takes from 2 to 3 ' - f'positional arguments but {len(args) + 2} ' - 'were given') - elif '_fields' in kwargs and len(kwargs) == 1: - fields = kwargs.pop('_fields') - import warnings - warnings.warn("Passing '_fields' as keyword argument is deprecated", - DeprecationWarning, stacklevel=2) - else: - fields = None - - if fields is None: - fields = kwargs - elif kwargs: - raise TypeError("TypedDict takes either a dict or keyword arguments," - " but not both") - - ns = {'__annotations__': dict(fields)} - try: - # Setting correct module is necessary to make typed dict classes pickleable. - ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - - return _TypedDictMeta(typename, (), ns, total=total) - - _typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,' - ' /, *, total=True, **kwargs)') - - class _TypedDictMeta(type): - def __init__(cls, name, bases, ns, total=True): - super().__init__(name, bases, ns) - - def __new__(cls, name, bases, ns, total=True): - # Create new typed dict class object. - # This method is called directly when TypedDict is subclassed, - # or via _typeddict_new when TypedDict is instantiated. This way - # TypedDict supports all three syntaxes described in its docstring. - # Subclasses and instances of TypedDict return actual dictionaries - # via _dict_new. - ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new - tp_dict = super().__new__(cls, name, (dict,), ns) - - annotations = {} - own_annotations = ns.get('__annotations__', {}) - msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" - own_annotations = { - n: typing._type_check(tp, msg) for n, tp in own_annotations.items() - } - required_keys = set() - optional_keys = set() - - for base in bases: - annotations.update(base.__dict__.get('__annotations__', {})) - required_keys.update(base.__dict__.get('__required_keys__', ())) - optional_keys.update(base.__dict__.get('__optional_keys__', ())) - - annotations.update(own_annotations) - for annotation_key, annotation_type in own_annotations.items(): - annotation_origin = get_origin(annotation_type) - if annotation_origin is Annotated: - annotation_args = get_args(annotation_type) - if annotation_args: - annotation_type = annotation_args[0] - annotation_origin = get_origin(annotation_type) - - if annotation_origin is Required: - required_keys.add(annotation_key) - elif annotation_origin is NotRequired: - optional_keys.add(annotation_key) - elif total: - required_keys.add(annotation_key) - else: - optional_keys.add(annotation_key) - - tp_dict.__annotations__ = annotations - tp_dict.__required_keys__ = frozenset(required_keys) - tp_dict.__optional_keys__ = frozenset(optional_keys) - if not hasattr(tp_dict, '__total__'): - tp_dict.__total__ = total - return tp_dict - - __instancecheck__ = __subclasscheck__ = _check_fails - - TypedDict = _TypedDictMeta('TypedDict', (dict,), {}) - TypedDict.__module__ = __name__ - TypedDict.__doc__ = \ - """A simple typed name space. At runtime it is equivalent to a plain dict. - - TypedDict creates a dictionary type that expects all of its - instances to have a certain set of keys, with each key - associated with a value of a consistent type. This expectation - is not checked at runtime but is only enforced by type checkers. - Usage:: - - class Point2D(TypedDict): - x: int - y: int - label: str - - a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK - b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check - - assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') - - The type info can be accessed via the Point2D.__annotations__ dict, and - the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets. - TypedDict supports two additional equivalent forms:: - - Point2D = TypedDict('Point2D', x=int, y=int, label=str) - Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) - - The class syntax is only supported in Python 3.6+, while two other - syntax forms work for Python 2.7 and 3.2+ - """ - - if hasattr(typing, "_TypedDictMeta"): - _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta) - else: - _TYPEDDICT_TYPES = (_TypedDictMeta,) - - def is_typeddict(tp): - """Check if an annotation is a TypedDict class - - For example:: - class Film(TypedDict): - title: str - year: int - - is_typeddict(Film) # => True - is_typeddict(Union[list, str]) # => False - """ - return isinstance(tp, tuple(_TYPEDDICT_TYPES)) - - -if hasattr(typing, "assert_type"): - assert_type = typing.assert_type - -else: - def assert_type(__val, __typ): - """Assert (to the type checker) that the value is of the given type. - - When the type checker encounters a call to assert_type(), it - emits an error if the value is not of the specified type:: - - def greet(name: str) -> None: - assert_type(name, str) # ok - assert_type(name, int) # type checker error - - At runtime this returns the first argument unchanged and otherwise - does nothing. - """ - return __val - - -if hasattr(typing, "Required"): - get_type_hints = typing.get_type_hints -else: - import functools - import types - - # replaces _strip_annotations() - def _strip_extras(t): - """Strips Annotated, Required and NotRequired from a given type.""" - if isinstance(t, _AnnotatedAlias): - return _strip_extras(t.__origin__) - if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired): - return _strip_extras(t.__args__[0]) - if isinstance(t, typing._GenericAlias): - stripped_args = tuple(_strip_extras(a) for a in t.__args__) - if stripped_args == t.__args__: - return t - return t.copy_with(stripped_args) - if hasattr(types, "GenericAlias") and isinstance(t, types.GenericAlias): - stripped_args = tuple(_strip_extras(a) for a in t.__args__) - if stripped_args == t.__args__: - return t - return types.GenericAlias(t.__origin__, stripped_args) - if hasattr(types, "UnionType") and isinstance(t, types.UnionType): - stripped_args = tuple(_strip_extras(a) for a in t.__args__) - if stripped_args == t.__args__: - return t - return functools.reduce(operator.or_, stripped_args) - - return t - - def get_type_hints(obj, globalns=None, localns=None, include_extras=False): - """Return type hints for an object. - - This is often the same as obj.__annotations__, but it handles - forward references encoded as string literals, adds Optional[t] if a - default value equal to None is set and recursively replaces all - 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T' - (unless 'include_extras=True'). - - The argument may be a module, class, method, or function. The annotations - are returned as a dictionary. For classes, annotations include also - inherited members. - - TypeError is raised if the argument is not of a type that can contain - annotations, and an empty dictionary is returned if no annotations are - present. - - BEWARE -- the behavior of globalns and localns is counterintuitive - (unless you are familiar with how eval() and exec() work). The - search order is locals first, then globals. - - - If no dict arguments are passed, an attempt is made to use the - globals from obj (or the respective module's globals for classes), - and these are also used as the locals. If the object does not appear - to have globals, an empty dictionary is used. - - - If one dict argument is passed, it is used for both globals and - locals. - - - If two dict arguments are passed, they specify globals and - locals, respectively. - """ - if hasattr(typing, "Annotated"): - hint = typing.get_type_hints( - obj, globalns=globalns, localns=localns, include_extras=True - ) - else: - hint = typing.get_type_hints(obj, globalns=globalns, localns=localns) - if include_extras: - return hint - return {k: _strip_extras(t) for k, t in hint.items()} - - -# Python 3.9+ has PEP 593 (Annotated) -if hasattr(typing, 'Annotated'): - Annotated = typing.Annotated - # Not exported and not a public API, but needed for get_origin() and get_args() - # to work. - _AnnotatedAlias = typing._AnnotatedAlias -# 3.7-3.8 -else: - class _AnnotatedAlias(typing._GenericAlias, _root=True): - """Runtime representation of an annotated type. - - At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't' - with extra annotations. The alias behaves like a normal typing alias, - instantiating is the same as instantiating the underlying type, binding - it to types is also the same. - """ - def __init__(self, origin, metadata): - if isinstance(origin, _AnnotatedAlias): - metadata = origin.__metadata__ + metadata - origin = origin.__origin__ - super().__init__(origin, origin) - self.__metadata__ = metadata - - def copy_with(self, params): - assert len(params) == 1 - new_type = params[0] - return _AnnotatedAlias(new_type, self.__metadata__) - - def __repr__(self): - return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, " - f"{', '.join(repr(a) for a in self.__metadata__)}]") - - def __reduce__(self): - return operator.getitem, ( - Annotated, (self.__origin__,) + self.__metadata__ - ) - - def __eq__(self, other): - if not isinstance(other, _AnnotatedAlias): - return NotImplemented - if self.__origin__ != other.__origin__: - return False - return self.__metadata__ == other.__metadata__ - - def __hash__(self): - return hash((self.__origin__, self.__metadata__)) - - class Annotated: - """Add context specific metadata to a type. - - Example: Annotated[int, runtime_check.Unsigned] indicates to the - hypothetical runtime_check module that this type is an unsigned int. - Every other consumer of this type can ignore this metadata and treat - this type as int. - - The first argument to Annotated must be a valid type (and will be in - the __origin__ field), the remaining arguments are kept as a tuple in - the __extra__ field. - - Details: - - - It's an error to call `Annotated` with less than two arguments. - - Nested Annotated are flattened:: - - Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] - - - Instantiating an annotated type is equivalent to instantiating the - underlying type:: - - Annotated[C, Ann1](5) == C(5) - - - Annotated can be used as a generic type alias:: - - Optimized = Annotated[T, runtime.Optimize()] - Optimized[int] == Annotated[int, runtime.Optimize()] - - OptimizedList = Annotated[List[T], runtime.Optimize()] - OptimizedList[int] == Annotated[List[int], runtime.Optimize()] - """ - - __slots__ = () - - def __new__(cls, *args, **kwargs): - raise TypeError("Type Annotated cannot be instantiated.") - - @typing._tp_cache - def __class_getitem__(cls, params): - if not isinstance(params, tuple) or len(params) < 2: - raise TypeError("Annotated[...] should be used " - "with at least two arguments (a type and an " - "annotation).") - allowed_special_forms = (ClassVar, Final) - if get_origin(params[0]) in allowed_special_forms: - origin = params[0] - else: - msg = "Annotated[t, ...]: t must be a type." - origin = typing._type_check(params[0], msg) - metadata = tuple(params[1:]) - return _AnnotatedAlias(origin, metadata) - - def __init_subclass__(cls, *args, **kwargs): - raise TypeError( - f"Cannot subclass {cls.__module__}.Annotated" - ) - -# Python 3.8 has get_origin() and get_args() but those implementations aren't -# Annotated-aware, so we can't use those. Python 3.9's versions don't support -# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. -if sys.version_info[:2] >= (3, 10): - get_origin = typing.get_origin - get_args = typing.get_args -# 3.7-3.9 -else: - try: - # 3.9+ - from typing import _BaseGenericAlias - except ImportError: - _BaseGenericAlias = typing._GenericAlias - try: - # 3.9+ - from typing import GenericAlias as _typing_GenericAlias - except ImportError: - _typing_GenericAlias = typing._GenericAlias - - def get_origin(tp): - """Get the unsubscripted version of a type. - - This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar - and Annotated. Return None for unsupported types. Examples:: - - get_origin(Literal[42]) is Literal - get_origin(int) is None - get_origin(ClassVar[int]) is ClassVar - get_origin(Generic) is Generic - get_origin(Generic[T]) is Generic - get_origin(Union[T, int]) is Union - get_origin(List[Tuple[T, T]][int]) == list - get_origin(P.args) is P - """ - if isinstance(tp, _AnnotatedAlias): - return Annotated - if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias, _BaseGenericAlias, - ParamSpecArgs, ParamSpecKwargs)): - return tp.__origin__ - if tp is typing.Generic: - return typing.Generic - return None - - def get_args(tp): - """Get type arguments with all substitutions performed. - - For unions, basic simplifications used by Union constructor are performed. - Examples:: - get_args(Dict[str, int]) == (str, int) - get_args(int) == () - get_args(Union[int, Union[T, int], str][int]) == (int, str) - get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) - get_args(Callable[[], T][int]) == ([], int) - """ - if isinstance(tp, _AnnotatedAlias): - return (tp.__origin__,) + tp.__metadata__ - if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)): - if getattr(tp, "_special", False): - return () - res = tp.__args__ - if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: - res = (list(res[:-1]), res[-1]) - return res - return () - - -# 3.10+ -if hasattr(typing, 'TypeAlias'): - TypeAlias = typing.TypeAlias -# 3.9 -elif sys.version_info[:2] >= (3, 9): - class _TypeAliasForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name - - @_TypeAliasForm - def TypeAlias(self, parameters): - """Special marker indicating that an assignment should - be recognized as a proper type alias definition by type - checkers. - - For example:: - - Predicate: TypeAlias = Callable[..., bool] - - It's invalid when used anywhere except as in the example above. - """ - raise TypeError(f"{self} is not subscriptable") -# 3.7-3.8 -else: - class _TypeAliasForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name - - TypeAlias = _TypeAliasForm('TypeAlias', - doc="""Special marker indicating that an assignment should - be recognized as a proper type alias definition by type - checkers. - - For example:: - - Predicate: TypeAlias = Callable[..., bool] - - It's invalid when used anywhere except as in the example - above.""") - - -# Python 3.10+ has PEP 612 -if hasattr(typing, 'ParamSpecArgs'): - ParamSpecArgs = typing.ParamSpecArgs - ParamSpecKwargs = typing.ParamSpecKwargs -# 3.7-3.9 -else: - class _Immutable: - """Mixin to indicate that object should not be copied.""" - __slots__ = () - - def __copy__(self): - return self - - def __deepcopy__(self, memo): - return self - - class ParamSpecArgs(_Immutable): - """The args for a ParamSpec object. - - Given a ParamSpec object P, P.args is an instance of ParamSpecArgs. - - ParamSpecArgs objects have a reference back to their ParamSpec: - - P.args.__origin__ is P - - This type is meant for runtime introspection and has no special meaning to - static type checkers. - """ - def __init__(self, origin): - self.__origin__ = origin - - def __repr__(self): - return f"{self.__origin__.__name__}.args" - - def __eq__(self, other): - if not isinstance(other, ParamSpecArgs): - return NotImplemented - return self.__origin__ == other.__origin__ - - class ParamSpecKwargs(_Immutable): - """The kwargs for a ParamSpec object. - - Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs. - - ParamSpecKwargs objects have a reference back to their ParamSpec: - - P.kwargs.__origin__ is P - - This type is meant for runtime introspection and has no special meaning to - static type checkers. - """ - def __init__(self, origin): - self.__origin__ = origin - - def __repr__(self): - return f"{self.__origin__.__name__}.kwargs" - - def __eq__(self, other): - if not isinstance(other, ParamSpecKwargs): - return NotImplemented - return self.__origin__ == other.__origin__ - -# 3.10+ -if hasattr(typing, 'ParamSpec'): - ParamSpec = typing.ParamSpec -# 3.7-3.9 -else: - - # Inherits from list as a workaround for Callable checks in Python < 3.9.2. - class ParamSpec(list): - """Parameter specification variable. - - Usage:: - - P = ParamSpec('P') - - Parameter specification variables exist primarily for the benefit of static - type checkers. They are used to forward the parameter types of one - callable to another callable, a pattern commonly found in higher order - functions and decorators. They are only valid when used in ``Concatenate``, - or s the first argument to ``Callable``. In Python 3.10 and higher, - they are also supported in user-defined Generics at runtime. - See class Generic for more information on generic types. An - example for annotating a decorator:: - - T = TypeVar('T') - P = ParamSpec('P') - - def add_logging(f: Callable[P, T]) -> Callable[P, T]: - '''A type-safe decorator to add logging to a function.''' - def inner(*args: P.args, **kwargs: P.kwargs) -> T: - logging.info(f'{f.__name__} was called') - return f(*args, **kwargs) - return inner - - @add_logging - def add_two(x: float, y: float) -> float: - '''Add two numbers together.''' - return x + y - - Parameter specification variables defined with covariant=True or - contravariant=True can be used to declare covariant or contravariant - generic types. These keyword arguments are valid, but their actual semantics - are yet to be decided. See PEP 612 for details. - - Parameter specification variables can be introspected. e.g.: - - P.__name__ == 'T' - P.__bound__ == None - P.__covariant__ == False - P.__contravariant__ == False - - Note that only parameter specification variables defined in global scope can - be pickled. - """ - - # Trick Generic __parameters__. - __class__ = typing.TypeVar - - @property - def args(self): - return ParamSpecArgs(self) - - @property - def kwargs(self): - return ParamSpecKwargs(self) - - def __init__(self, name, *, bound=None, covariant=False, contravariant=False): - super().__init__([self]) - self.__name__ = name - self.__covariant__ = bool(covariant) - self.__contravariant__ = bool(contravariant) - if bound: - self.__bound__ = typing._type_check(bound, 'Bound must be a type.') - else: - self.__bound__ = None - - # for pickling: - try: - def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - def_mod = None - if def_mod != 'typing_extensions': - self.__module__ = def_mod - - def __repr__(self): - if self.__covariant__: - prefix = '+' - elif self.__contravariant__: - prefix = '-' - else: - prefix = '~' - return prefix + self.__name__ - - def __hash__(self): - return object.__hash__(self) - - def __eq__(self, other): - return self is other - - def __reduce__(self): - return self.__name__ - - # Hack to get typing._type_check to pass. - def __call__(self, *args, **kwargs): - pass - - -# 3.7-3.9 -if not hasattr(typing, 'Concatenate'): - # Inherits from list as a workaround for Callable checks in Python < 3.9.2. - class _ConcatenateGenericAlias(list): - - # Trick Generic into looking into this for __parameters__. - __class__ = typing._GenericAlias - - # Flag in 3.8. - _special = False - - def __init__(self, origin, args): - super().__init__(args) - self.__origin__ = origin - self.__args__ = args - - def __repr__(self): - _type_repr = typing._type_repr - return (f'{_type_repr(self.__origin__)}' - f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]') - - def __hash__(self): - return hash((self.__origin__, self.__args__)) - - # Hack to get typing._type_check to pass in Generic. - def __call__(self, *args, **kwargs): - pass - - @property - def __parameters__(self): - return tuple( - tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) - ) - - -# 3.7-3.9 -@typing._tp_cache -def _concatenate_getitem(self, parameters): - if parameters == (): - raise TypeError("Cannot take a Concatenate of no types.") - if not isinstance(parameters, tuple): - parameters = (parameters,) - if not isinstance(parameters[-1], ParamSpec): - raise TypeError("The last parameter to Concatenate should be a " - "ParamSpec variable.") - msg = "Concatenate[arg, ...]: each arg must be a type." - parameters = tuple(typing._type_check(p, msg) for p in parameters) - return _ConcatenateGenericAlias(self, parameters) - - -# 3.10+ -if hasattr(typing, 'Concatenate'): - Concatenate = typing.Concatenate - _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa -# 3.9 -elif sys.version_info[:2] >= (3, 9): - @_TypeAliasForm - def Concatenate(self, parameters): - """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a - higher order function which adds, removes or transforms parameters of a - callable. - - For example:: - - Callable[Concatenate[int, P], int] - - See PEP 612 for detailed information. - """ - return _concatenate_getitem(self, parameters) -# 3.7-8 -else: - class _ConcatenateForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name - - def __getitem__(self, parameters): - return _concatenate_getitem(self, parameters) - - Concatenate = _ConcatenateForm( - 'Concatenate', - doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a - higher order function which adds, removes or transforms parameters of a - callable. - - For example:: - - Callable[Concatenate[int, P], int] - - See PEP 612 for detailed information. - """) - -# 3.10+ -if hasattr(typing, 'TypeGuard'): - TypeGuard = typing.TypeGuard -# 3.9 -elif sys.version_info[:2] >= (3, 9): - class _TypeGuardForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name - - @_TypeGuardForm - def TypeGuard(self, parameters): - """Special typing form used to annotate the return type of a user-defined - type guard function. ``TypeGuard`` only accepts a single type argument. - At runtime, functions marked this way should return a boolean. - - ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static - type checkers to determine a more precise type of an expression within a - program's code flow. Usually type narrowing is done by analyzing - conditional code flow and applying the narrowing to a block of code. The - conditional expression here is sometimes referred to as a "type guard". - - Sometimes it would be convenient to use a user-defined boolean function - as a type guard. Such a function should use ``TypeGuard[...]`` as its - return type to alert static type checkers to this intention. - - Using ``-> TypeGuard`` tells the static type checker that for a given - function: - - 1. The return value is a boolean. - 2. If the return value is ``True``, the type of its argument - is the type inside ``TypeGuard``. - - For example:: - - def is_str(val: Union[str, float]): - # "isinstance" type guard - if isinstance(val, str): - # Type of ``val`` is narrowed to ``str`` - ... - else: - # Else, type of ``val`` is narrowed to ``float``. - ... - - Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower - form of ``TypeA`` (it can even be a wider form) and this may lead to - type-unsafe results. The main reason is to allow for things like - narrowing ``List[object]`` to ``List[str]`` even though the latter is not - a subtype of the former, since ``List`` is invariant. The responsibility of - writing type-safe type guards is left to the user. - - ``TypeGuard`` also works with type variables. For more information, see - PEP 647 (User-Defined Type Guards). - """ - item = typing._type_check(parameters, f'{self} accepts only a single type.') - return typing._GenericAlias(self, (item,)) -# 3.7-3.8 -else: - class _TypeGuardForm(typing._SpecialForm, _root=True): - - def __repr__(self): - return 'typing_extensions.' + self._name - - def __getitem__(self, parameters): - item = typing._type_check(parameters, - f'{self._name} accepts only a single type') - return typing._GenericAlias(self, (item,)) - - TypeGuard = _TypeGuardForm( - 'TypeGuard', - doc="""Special typing form used to annotate the return type of a user-defined - type guard function. ``TypeGuard`` only accepts a single type argument. - At runtime, functions marked this way should return a boolean. - - ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static - type checkers to determine a more precise type of an expression within a - program's code flow. Usually type narrowing is done by analyzing - conditional code flow and applying the narrowing to a block of code. The - conditional expression here is sometimes referred to as a "type guard". - - Sometimes it would be convenient to use a user-defined boolean function - as a type guard. Such a function should use ``TypeGuard[...]`` as its - return type to alert static type checkers to this intention. - - Using ``-> TypeGuard`` tells the static type checker that for a given - function: - - 1. The return value is a boolean. - 2. If the return value is ``True``, the type of its argument - is the type inside ``TypeGuard``. - - For example:: - - def is_str(val: Union[str, float]): - # "isinstance" type guard - if isinstance(val, str): - # Type of ``val`` is narrowed to ``str`` - ... - else: - # Else, type of ``val`` is narrowed to ``float``. - ... - - Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower - form of ``TypeA`` (it can even be a wider form) and this may lead to - type-unsafe results. The main reason is to allow for things like - narrowing ``List[object]`` to ``List[str]`` even though the latter is not - a subtype of the former, since ``List`` is invariant. The responsibility of - writing type-safe type guards is left to the user. - - ``TypeGuard`` also works with type variables. For more information, see - PEP 647 (User-Defined Type Guards). - """) - - -# Vendored from cpython typing._SpecialFrom -class _SpecialForm(typing._Final, _root=True): - __slots__ = ('_name', '__doc__', '_getitem') - - def __init__(self, getitem): - self._getitem = getitem - self._name = getitem.__name__ - self.__doc__ = getitem.__doc__ - - def __getattr__(self, item): - if item in {'__name__', '__qualname__'}: - return self._name - - raise AttributeError(item) - - def __mro_entries__(self, bases): - raise TypeError(f"Cannot subclass {self!r}") - - def __repr__(self): - return f'typing_extensions.{self._name}' - - def __reduce__(self): - return self._name - - def __call__(self, *args, **kwds): - raise TypeError(f"Cannot instantiate {self!r}") - - def __or__(self, other): - return typing.Union[self, other] - - def __ror__(self, other): - return typing.Union[other, self] - - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance()") - - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass()") - - @typing._tp_cache - def __getitem__(self, parameters): - return self._getitem(self, parameters) - - -if hasattr(typing, "LiteralString"): - LiteralString = typing.LiteralString -else: - @_SpecialForm - def LiteralString(self, params): - """Represents an arbitrary literal string. - - Example:: - - from typing_extensions import LiteralString - - def query(sql: LiteralString) -> ...: - ... - - query("SELECT * FROM table") # ok - query(f"SELECT * FROM {input()}") # not ok - - See PEP 675 for details. - - """ - raise TypeError(f"{self} is not subscriptable") - - -if hasattr(typing, "Self"): - Self = typing.Self -else: - @_SpecialForm - def Self(self, params): - """Used to spell the type of "self" in classes. - - Example:: - - from typing import Self - - class ReturnsSelf: - def parse(self, data: bytes) -> Self: - ... - return self - - """ - - raise TypeError(f"{self} is not subscriptable") - - -if hasattr(typing, "Never"): - Never = typing.Never -else: - @_SpecialForm - def Never(self, params): - """The bottom type, a type that has no members. - - This can be used to define a function that should never be - called, or a function that never returns:: - - from typing_extensions import Never - - def never_call_me(arg: Never) -> None: - pass - - def int_or_str(arg: int | str) -> None: - never_call_me(arg) # type checker error - match arg: - case int(): - print("It's an int") - case str(): - print("It's a str") - case _: - never_call_me(arg) # ok, arg is of type Never - - """ - - raise TypeError(f"{self} is not subscriptable") - - -if hasattr(typing, 'Required'): - Required = typing.Required - NotRequired = typing.NotRequired -elif sys.version_info[:2] >= (3, 9): - class _ExtensionsSpecialForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name - - @_ExtensionsSpecialForm - def Required(self, parameters): - """A special typing construct to mark a key of a total=False TypedDict - as required. For example: - - class Movie(TypedDict, total=False): - title: Required[str] - year: int - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - - There is no runtime checking that a required key is actually provided - when instantiating a related TypedDict. - """ - item = typing._type_check(parameters, f'{self._name} accepts only a single type.') - return typing._GenericAlias(self, (item,)) - - @_ExtensionsSpecialForm - def NotRequired(self, parameters): - """A special typing construct to mark a key of a TypedDict as - potentially missing. For example: - - class Movie(TypedDict): - title: str - year: NotRequired[int] - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - """ - item = typing._type_check(parameters, f'{self._name} accepts only a single type.') - return typing._GenericAlias(self, (item,)) - -else: - class _RequiredForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name - - def __getitem__(self, parameters): - item = typing._type_check(parameters, - f'{self._name} accepts only a single type.') - return typing._GenericAlias(self, (item,)) - - Required = _RequiredForm( - 'Required', - doc="""A special typing construct to mark a key of a total=False TypedDict - as required. For example: - - class Movie(TypedDict, total=False): - title: Required[str] - year: int - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - - There is no runtime checking that a required key is actually provided - when instantiating a related TypedDict. - """) - NotRequired = _RequiredForm( - 'NotRequired', - doc="""A special typing construct to mark a key of a TypedDict as - potentially missing. For example: - - class Movie(TypedDict): - title: str - year: NotRequired[int] - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - """) - - -if hasattr(typing, "Unpack"): # 3.11+ - Unpack = typing.Unpack -elif sys.version_info[:2] >= (3, 9): - class _UnpackSpecialForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name - - class _UnpackAlias(typing._GenericAlias, _root=True): - __class__ = typing.TypeVar - - @_UnpackSpecialForm - def Unpack(self, parameters): - """A special typing construct to unpack a variadic type. For example: - - Shape = TypeVarTuple('Shape') - Batch = NewType('Batch', int) - - def add_batch_axis( - x: Array[Unpack[Shape]] - ) -> Array[Batch, Unpack[Shape]]: ... - - """ - item = typing._type_check(parameters, f'{self._name} accepts only a single type.') - return _UnpackAlias(self, (item,)) - - def _is_unpack(obj): - return isinstance(obj, _UnpackAlias) - -else: - class _UnpackAlias(typing._GenericAlias, _root=True): - __class__ = typing.TypeVar - - class _UnpackForm(typing._SpecialForm, _root=True): - def __repr__(self): - return 'typing_extensions.' + self._name - - def __getitem__(self, parameters): - item = typing._type_check(parameters, - f'{self._name} accepts only a single type.') - return _UnpackAlias(self, (item,)) - - Unpack = _UnpackForm( - 'Unpack', - doc="""A special typing construct to unpack a variadic type. For example: - - Shape = TypeVarTuple('Shape') - Batch = NewType('Batch', int) - - def add_batch_axis( - x: Array[Unpack[Shape]] - ) -> Array[Batch, Unpack[Shape]]: ... - - """) - - def _is_unpack(obj): - return isinstance(obj, _UnpackAlias) - - -if hasattr(typing, "TypeVarTuple"): # 3.11+ - TypeVarTuple = typing.TypeVarTuple -else: - class TypeVarTuple: - """Type variable tuple. - - Usage:: - - Ts = TypeVarTuple('Ts') - - In the same way that a normal type variable is a stand-in for a single - type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* - type such as ``Tuple[int, str]``. - - Type variable tuples can be used in ``Generic`` declarations. - Consider the following example:: - - class Array(Generic[*Ts]): ... - - The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, - where ``T1`` and ``T2`` are type variables. To use these type variables - as type parameters of ``Array``, we must *unpack* the type variable tuple using - the star operator: ``*Ts``. The signature of ``Array`` then behaves - as if we had simply written ``class Array(Generic[T1, T2]): ...``. - In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows - us to parameterise the class with an *arbitrary* number of type parameters. - - Type variable tuples can be used anywhere a normal ``TypeVar`` can. - This includes class definitions, as shown above, as well as function - signatures and variable annotations:: - - class Array(Generic[*Ts]): - - def __init__(self, shape: Tuple[*Ts]): - self._shape: Tuple[*Ts] = shape - - def get_shape(self) -> Tuple[*Ts]: - return self._shape - - shape = (Height(480), Width(640)) - x: Array[Height, Width] = Array(shape) - y = abs(x) # Inferred type is Array[Height, Width] - z = x + x # ... is Array[Height, Width] - x.get_shape() # ... is tuple[Height, Width] - - """ - - # Trick Generic __parameters__. - __class__ = typing.TypeVar - - def __iter__(self): - yield self.__unpacked__ - - def __init__(self, name): - self.__name__ = name - - # for pickling: - try: - def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - def_mod = None - if def_mod != 'typing_extensions': - self.__module__ = def_mod - - self.__unpacked__ = Unpack[self] - - def __repr__(self): - return self.__name__ - - def __hash__(self): - return object.__hash__(self) - - def __eq__(self, other): - return self is other - - def __reduce__(self): - return self.__name__ - - def __init_subclass__(self, *args, **kwds): - if '_root' not in kwds: - raise TypeError("Cannot subclass special typing classes") - - -if hasattr(typing, "reveal_type"): - reveal_type = typing.reveal_type -else: - def reveal_type(__obj: T) -> T: - """Reveal the inferred type of a variable. - - When a static type checker encounters a call to ``reveal_type()``, - it will emit the inferred type of the argument:: - - x: int = 1 - reveal_type(x) - - Running a static type checker (e.g., ``mypy``) on this example - will produce output similar to 'Revealed type is "builtins.int"'. - - At runtime, the function prints the runtime type of the - argument and returns it unchanged. - - """ - print(f"Runtime type is {type(__obj).__name__!r}", file=sys.stderr) - return __obj - - -if hasattr(typing, "assert_never"): - assert_never = typing.assert_never -else: - def assert_never(__arg: Never) -> Never: - """Assert to the type checker that a line of code is unreachable. - - Example:: - - def int_or_str(arg: int | str) -> None: - match arg: - case int(): - print("It's an int") - case str(): - print("It's a str") - case _: - assert_never(arg) - - If a type checker finds that a call to assert_never() is - reachable, it will emit an error. - - At runtime, this throws an exception when called. - - """ - raise AssertionError("Expected code to be unreachable") - - -if hasattr(typing, 'dataclass_transform'): - dataclass_transform = typing.dataclass_transform -else: - def dataclass_transform( - *, - eq_default: bool = True, - order_default: bool = False, - kw_only_default: bool = False, - field_specifiers: typing.Tuple[ - typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]], - ... - ] = (), - **kwargs: typing.Any, - ) -> typing.Callable[[T], T]: - """Decorator that marks a function, class, or metaclass as providing - dataclass-like behavior. - - Example: - - from typing_extensions import dataclass_transform - - _T = TypeVar("_T") - - # Used on a decorator function - @dataclass_transform() - def create_model(cls: type[_T]) -> type[_T]: - ... - return cls - - @create_model - class CustomerModel: - id: int - name: str - - # Used on a base class - @dataclass_transform() - class ModelBase: ... - - class CustomerModel(ModelBase): - id: int - name: str - - # Used on a metaclass - @dataclass_transform() - class ModelMeta(type): ... - - class ModelBase(metaclass=ModelMeta): ... - - class CustomerModel(ModelBase): - id: int - name: str - - Each of the ``CustomerModel`` classes defined in this example will now - behave similarly to a dataclass created with the ``@dataclasses.dataclass`` - decorator. For example, the type checker will synthesize an ``__init__`` - method. - - The arguments to this decorator can be used to customize this behavior: - - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be - True or False if it is omitted by the caller. - - ``order_default`` indicates whether the ``order`` parameter is - assumed to be True or False if it is omitted by the caller. - - ``kw_only_default`` indicates whether the ``kw_only`` parameter is - assumed to be True or False if it is omitted by the caller. - - ``field_specifiers`` specifies a static list of supported classes - or functions that describe fields, similar to ``dataclasses.field()``. - - At runtime, this decorator records its arguments in the - ``__dataclass_transform__`` attribute on the decorated object. - - See PEP 681 for details. - - """ - def decorator(cls_or_fn): - cls_or_fn.__dataclass_transform__ = { - "eq_default": eq_default, - "order_default": order_default, - "kw_only_default": kw_only_default, - "field_specifiers": field_specifiers, - "kwargs": kwargs, - } - return cls_or_fn - return decorator - - -# We have to do some monkey patching to deal with the dual nature of -# Unpack/TypeVarTuple: -# - We want Unpack to be a kind of TypeVar so it gets accepted in -# Generic[Unpack[Ts]] -# - We want it to *not* be treated as a TypeVar for the purposes of -# counting generic parameters, so that when we subscript a generic, -# the runtime doesn't try to substitute the Unpack with the subscripted type. -if not hasattr(typing, "TypeVarTuple"): - typing._collect_type_vars = _collect_type_vars - typing._check_generic = _check_generic diff --git a/typing_extensions/tox.ini b/typing_extensions/tox.ini deleted file mode 100644 index 08869364b..000000000 --- a/typing_extensions/tox.ini +++ /dev/null @@ -1,7 +0,0 @@ -[tox] -isolated_build = True -envlist = py36, py37, py38, py39, py310 - -[testenv] -changedir = src -commands = python -m unittest discover From 61af76ad4c13397f09978dfbb9c2bbe8756a95d0 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 19 May 2022 16:27:56 +0200 Subject: [PATCH 134/539] Remove the typing-extensions issue template (#1190) --- .github/ISSUE_TEMPLATE/typing-extensions-issue.md | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/typing-extensions-issue.md diff --git a/.github/ISSUE_TEMPLATE/typing-extensions-issue.md b/.github/ISSUE_TEMPLATE/typing-extensions-issue.md deleted file mode 100644 index 226796e69..000000000 --- a/.github/ISSUE_TEMPLATE/typing-extensions-issue.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: typing-extensions issue -about: Report a problem or suggest changes for the typing-extensions library -title: '' -labels: 'topic: typing-extensions' -assignees: '' - ---- - - From bc18c21ff601fc01a8f433b243be3f0408bbaf33 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 21 May 2022 15:14:11 +0100 Subject: [PATCH 135/539] Delete `CONTRIBUTING.md` (#1192) --- CONTRIBUTING.md | 55 ------------------------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index e2b8d413f..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,55 +0,0 @@ -Code in this repository should follow CPython's style guidelines and -contributors need to sign the PSF Contributor Agreement. - -# typing\_extensions - -The `typing_extensions` module provides a way to access new features from the standard -library `typing` module in older versions of Python. For example, Python 3.10 adds -`typing.TypeGuard`, but users of older versions of Python can use `typing_extensions` to -use `TypeGuard` in their code even if they are unable to upgrade to Python 3.10. - -If you contribute the runtime implementation of a new `typing` feature to CPython, you -are encouraged to also implement the feature in `typing_extensions`. Because the runtime -implementation of much of the infrastructure in the `typing` module has changed over -time, this may require different code for some older Python versions. - -`typing_extensions` may also include experimental features that are not yet part of the -standard library, so that users can experiment with them before they are added to the -standard library. Such features should ideally already be specified in a PEP or draft -PEP. - -`typing_extensions` supports Python versions 3.7 and up. - -# Versioning scheme - -Starting with version 4.0.0, `typing_extensions` uses -[Semantic Versioning](https://semver.org/). The major version is incremented for all -backwards-incompatible changes. - -# Workflow for PyPI releases - -- Ensure that GitHub Actions reports no errors. - -- Update the version number in `typing_extensions/pyproject.toml` and in - `typing_extensions/CHANGELOG.md`. - -- Make sure your environment is up to date - - - `git checkout master` - - `git pull` - - `python -m pip install --upgrade build twine` - -- Build the source and wheel distributions: - - - `cd typing_extensions` - - `rm -rf dist/` - - `python -m build .` - -- Install the built distributions locally and test (if you were using `tox`, you already - tested the source distribution). - -- Run `twine upload dist/*`. - -- Tag the release. The tag should be just the version number, e.g. `4.1.1`. - -- `git push --tags` From 831742fe3b19c12850e58f6545b4b3cb09965955 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Sat, 21 May 2022 21:34:21 +0200 Subject: [PATCH 136/539] Split Best Practics document from Stubs document (#1193) Move all "style guide" items over that apply to both stubs as well as implementation. The items are reordered and the "Types" section was split, but the text itself is unchanged. Part of #851 --- docs/index.rst | 2 + docs/source/best_practices.rst | 130 +++++++++++++++++++++++++++++++++ docs/source/reference.rst | 1 + docs/source/stubs.rst | 97 +----------------------- 4 files changed, 136 insertions(+), 94 deletions(-) create mode 100644 docs/source/best_practices.rst diff --git a/docs/index.rst b/docs/index.rst index b5fe26888..d1bcea2cf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,6 +32,8 @@ Indices and tables * :ref:`genindex` * :ref:`search` +.. _contact: + Discussions and Support ======================= diff --git a/docs/source/best_practices.rst b/docs/source/best_practices.rst new file mode 100644 index 000000000..f40c447cf --- /dev/null +++ b/docs/source/best_practices.rst @@ -0,0 +1,130 @@ +.. _best-practices: + +********************* +Typing Best Practices +********************* + +Introduction +============ + +Over time, some best practices have proven themselves as useful when working +with type hints in Python. Not all practices are applicable in all situations +and some practices come down to personal style and preference, but they +are a good default set of recommendations to fall back to, unless there is +a specific reason to deviate. + +These best practices are constantly evolving, especially as the typing +capabilities and ecosystem grow. So expect new best practices to be added +and existing best practices to be modified or even removed as better practices +evolve. That is why we would love to hear from your experiences with typing. +Please see :ref:`contact` on how to join the discussion. + +Typing Features +=============== + +Type Aliases +------------ + +Use ``TypeAlias`` for type aliases (but not for regular aliases). + +Yes:: + + _IntList: TypeAlias = list[int] + g = os.stat + Path = pathlib.Path + ERROR = errno.EEXIST + +No:: + + _IntList = list[int] + g: TypeAlias = os.stat + Path: TypeAlias = pathlib.Path + ERROR: TypeAlias = errno.EEXIST + +Ergonomic Practices +=================== + +Using `Any` +----------- + +Generally, use ``Any`` when a type cannot be expressed appropriately +with the current type system or using the correct type is unergonomic. + +Arguments and Return Types +-------------------------- + +For arguments, prefer protocols and abstract types (``Mapping``, +``Sequence``, ``Iterable``, etc.). If an argument accepts literally any value, +use ``object`` instead of ``Any``. + +For return values, prefer concrete types (``list``, ``dict``, etc.) for +concrete implementations. The return values of protocols +and abstract base classes must be judged on a case-by-case basis. + +Yes:: + + def map_it(input: Iterable[str]) -> list[int]: ... + def create_map() -> dict[str, int]: ... + def to_string(o: object) -> str: ... # accepts any object + +No:: + + def map_it(input: list[str]) -> list[int]: ... + def create_map() -> MutableMapping[str, int]: ... + def to_string(o: Any) -> str: ... + +Maybe:: + + class MyProto(Protocol): + def foo(self) -> list[int]: ... + def bar(self) -> Mapping[str]: ... + +Avoid union return types, since they require ``isinstance()`` checks. +Use ``Any`` or ``X | Any`` if necessary. + +Stylistic Practices +=================== + +Shorthand Syntax +---------------- + +Where possible, use shorthand syntax for unions instead of +``Union`` or ``Optional``. ``None`` should be the last +element of an union. + +Yes:: + + def foo(x: str | int) -> None: ... + def bar(x: str | None) -> int | None: ... + +No:: + + def foo(x: Union[str, int]) -> None: ... + def bar(x: Optional[str]) -> Optional[int]: ... + def baz(x: None | str) -> None: ... + +Types +----- + +Use ``float`` instead of ``int | float``. +Use ``None`` instead of ``Literal[None]``. + +Built-in Generics +----------------- + +Use built-in generics instead of the aliases from ``typing``, +where possible. + +Yes:: + + from collections.abc import Iterable + + def foo(x: type[MyClass]) -> list[str]: ... + def bar(x: Iterable[str]) -> None: ... + +No:: + + from typing import Iterable, List, Type + + def foo(x: Type[MyClass]) -> List[str]: ... + def bar(x: Iterable[str]) -> None: ... diff --git a/docs/source/reference.rst b/docs/source/reference.rst index c7fc57ee6..814dfe1c7 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -7,6 +7,7 @@ Type System Reference :caption: Contents: stubs + best_practices quality typing Module Documentation diff --git a/docs/source/stubs.rst b/docs/source/stubs.rst index 215c1a311..b0db61093 100644 --- a/docs/source/stubs.rst +++ b/docs/source/stubs.rst @@ -766,7 +766,7 @@ should not reject stubs that do not follow these recommendations, but linters can warn about them. Stub files should generally follow the Style Guide for Python Code (PEP 8) -[#pep8]_. There are a few exceptions, outlined below, that take the +[#pep8]_ and the :ref:`best-practices`. There are a few exceptions, outlined below, that take the different structure of stub files into account and are aimed to create more concise files. @@ -810,24 +810,6 @@ No:: x: int class MyError(Exception): ... # leave an empty line between the classes -Shorthand Syntax ----------------- - -Where possible, use shorthand syntax for unions instead of -``Union`` or ``Optional``. ``None`` should be the last -element of an union. - -Yes:: - - def foo(x: str | int) -> None: ... - def bar(x: str | None) -> int | None: ... - -No:: - - def foo(x: Union[str, int]) -> None: ... - def bar(x: Optional[str]) -> Optional[int]: ... - def baz(x: None | str) -> None: ... - Module Level Attributes ----------------------- @@ -846,24 +828,7 @@ No:: z = 0 # type: int a = ... # type: int -Type Aliases ------------- - -Use ``TypeAlias`` for type aliases (but not for regular aliases). - -Yes:: - - _IntList: TypeAlias = list[int] - g = os.stat - Path = pathlib.Path - ERROR = errno.EEXIST - -No:: - - _IntList = list[int] - g: TypeAlias = os.stat - Path: TypeAlias = pathlib.Path - ERROR: TypeAlias = errno.EEXIST +.. _stub-style-classes: Classes ------- @@ -998,67 +963,11 @@ No:: forward_reference: 'OtherClass' class OtherClass: ... -Types ------ - -Generally, use ``Any`` when a type cannot be expressed appropriately -with the current type system or using the correct type is unergonomic. - -Use ``float`` instead of ``int | float``. -Use ``None`` instead of ``Literal[None]``. - -For arguments, prefer protocols and abstract types (``Mapping``, -``Sequence``, ``Iterable``, etc.). If an argument accepts literally any value, -use ``object`` instead of ``Any``. - -For return values, prefer concrete types (``list``, ``dict``, etc.) for -concrete implementations. The return values of protocols -and abstract base classes must be judged on a case-by-case basis. - -Yes:: - - def map_it(input: Iterable[str]) -> list[int]: ... - def create_map() -> dict[str, int]: ... - def to_string(o: object) -> str: ... # accepts any object - -No:: - - def map_it(input: list[str]) -> list[int]: ... - def create_map() -> MutableMapping[str, int]: ... - def to_string(o: Any) -> str: ... - -Maybe:: - - class MyProto(Protocol): - def foo(self) -> list[int]: ... - def bar(self) -> Mapping[str]: ... - -Avoid union return types, since they require ``isinstance()`` checks. -Use ``Any`` or ``X | Any`` if necessary. - -Use built-in generics instead of the aliases from ``typing``, -where possible. See the section `Built-in Generics`_ for cases, -where it's not possible to use them. - -Yes:: - - from collections.abc import Iterable - - def foo(x: type[MyClass]) -> list[str]: ... - def bar(x: Iterable[str]) -> None: ... - -No:: - - from typing import Iterable, List, Type - - def foo(x: Type[MyClass]) -> List[str]: ... - def bar(x: Iterable[str]) -> None: ... - NamedTuple and TypedDict ------------------------ Use the class-based syntax for ``typing.NamedTuple`` and -``typing.TypedDict``, following the Classes section of this style guide. +``typing.TypedDict``, following the :ref:`stub-style-classes` section of this style guide. Yes:: From 4bdf0db49dda83a00dfb5f7e45c96e89bc0e0c8a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 24 May 2022 13:06:29 +0100 Subject: [PATCH 137/539] Delete "Workflow" section in the README (#1196) Fixes #1195 --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index edb902fe5..98d9cc91b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,3 @@ Historically, this repository also hosted: in the standard library reached end of life. The last released version, supporting Python 2.7 and 3.4, is [available at PyPI](https://pypi.org/project/typing/). - -## Workflow - -See [CONTRIBUTING.md](/CONTRIBUTING.md) for more. From 45dee4f52a9251d79500155453cf953c4184a173 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 2 Jun 2022 22:15:34 +0200 Subject: [PATCH 138/539] Best Practices: Add note about object/Any (#1198) Part of #851 Co-authored-by: Jelle Zijlstra --- docs/source/best_practices.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/source/best_practices.rst b/docs/source/best_practices.rst index f40c447cf..2e0cff6b1 100644 --- a/docs/source/best_practices.rst +++ b/docs/source/best_practices.rst @@ -44,12 +44,21 @@ No:: Ergonomic Practices =================== -Using `Any` ------------ +Using ``Any`` and ``object`` +---------------------------- Generally, use ``Any`` when a type cannot be expressed appropriately with the current type system or using the correct type is unergonomic. +If a function accepts every possible object as an argument, for example +because it's only passed to ``str()``, use ``object`` instead of ``Any`` as +type annotation. Similarly, if the return value of a callback is ignored, +annotate it with ``object``:: + + def call_cb_if_int(cb: Callable[[int], object], o: object) -> None: + if isinstance(o, int): + cb(o) + Arguments and Return Types -------------------------- From c7b0a54db788a7c4fa124f654b6f0ea536939089 Mon Sep 17 00:00:00 2001 From: Ilya Konstantinov Date: Thu, 7 Jul 2022 08:33:31 -0400 Subject: [PATCH 139/539] Document use of assert_type (#1220) --- docs/source/quality.rst | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/docs/source/quality.rst b/docs/source/quality.rst index 4b3bd35b3..8ec08aed8 100644 --- a/docs/source/quality.rst +++ b/docs/source/quality.rst @@ -20,21 +20,39 @@ a few of them. For simplicity, we will assume that type-checking is done with ``mypy``. Many of these strategies can be applied to other type-checkers as well. -Testing Using ``mypy --warn-unused-ignores`` --------------------------------------------- +Testing Using ``assert_type`` and ``--warn-unused-ignores`` +----------------------------------------------------------- -Clever use of ``--warn-unused-ignores`` can be used to check that certain -expressions are or are not well-typed. +The idea is to write normal Python files, set aside in a dedicated directory like ``typing_tests/``, which assert certain properties +of the type annotations. -The idea is to write normal python files which contain valid expressions along +``assert_type`` (``mypy`` 0.950 and above) can ensure that the type annotation produces the expected type. + +If the following file is under test: + +.. code-block:: python + + # foo.py + def bar(x: int) -> str: + return str(x) + +then the following file tests ``foo.py``: + +.. code-block:: python + + from typing_extensions import assert_type + + assert_type(bar(42), str) + +Clever use of ``mypy --warn-unused-ignores`` can be used to check that certain +expressions are or are not well-typed. The idea is to have valid expressions along with invalid expressions annotated with ``type: ignore`` comments. When ``mypy --warn-unused-ignores`` is run on these files, it should pass. -A directory of test files, ``typing_tests/``, can be maintained. This strategy does not offer strong guarantees about the types under test, but it requires no additional tooling. -If the following file is under test +If the following file is under test: .. code-block:: python @@ -42,7 +60,7 @@ If the following file is under test def bar(x: int) -> str: return str(x) -Then the following file tests ``foo.py``: +then the following file tests ``foo.py``: .. code-block:: python From 5442a37e62df638af50b58ab6ddb8adfc4570e4d Mon Sep 17 00:00:00 2001 From: Kevin Kirsche Date: Mon, 22 Aug 2022 11:45:54 -0400 Subject: [PATCH 140/539] Add type-hint and stub integration section (#1244) Partially addresses #1242 --- docs/index.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index d1bcea2cf..c00a278ea 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -74,6 +74,13 @@ Linters and Formatters * `flake8-pyi `_, a plugin for the `flake8 `_ linter that adds support for type stubs. + +Type-Hint and Stub Integration +---------------------- + +* `autotyping `_, a tool which infers simple types from their context and inserts them as inline type-hints. +* `merge_pyi `_, integrates .pyi signatures as inline type-hints in Python source code. +* `retype `_, Re-applies type annotations from .pyi stubs to your codebase. Typing PEPs =========== From 92ccf0f33cc2f5946e9e6fa5fdb1609bcd5f8024 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 10 Oct 2022 15:34:43 +0200 Subject: [PATCH 141/539] Update LICENSE file (#1233) * Update the copyright years. * Update some wording in the history introduction. * Change capitalization of the word "internet". --- LICENSE | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/LICENSE b/LICENSE index 583f9f6e6..1df6b3b8d 100644 --- a/LICENSE +++ b/LICENSE @@ -13,12 +13,11 @@ software. In May 2000, Guido and the Python core development team moved to BeOpen.com to form the BeOpen PythonLabs team. In October of the same -year, the PythonLabs team moved to Digital Creations (now Zope -Corporation, see http://www.zope.com). In 2001, the Python Software -Foundation (PSF, see http://www.python.org/psf/) was formed, a -non-profit organization created specifically to own Python-related -Intellectual Property. Zope Corporation is a sponsoring member of -the PSF. +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. All Python releases are Open Source (see http://www.opensource.org for the Open Source Definition). Historically, most, but not all, Python @@ -74,8 +73,9 @@ analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are -retained in Python alone or in any derivative version prepared by Licensee. +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make @@ -180,9 +180,9 @@ version prepared by Licensee. Alternately, in lieu of CNRI's License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6.1 is made available subject to the terms and conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the Internet using the following +Python 1.6.1 may be located on the internet using the following unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the Internet +Agreement may also be obtained from a proxy server on the internet using the following URL: http://hdl.handle.net/1895.22/1013". 3. In the event Licensee prepares a derivative work that is based on From dc8d235fc90786183f0f12be4b9072ad9b4516dd Mon Sep 17 00:00:00 2001 From: Sergei Vorobev Date: Sun, 29 Jan 2023 12:33:16 -0800 Subject: [PATCH 142/539] Add information about Python discord (#1338) --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 98d9cc91b..27e82502e 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,20 @@ -[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - # Static Typing for Python ## Documentation and Support The documentation for Python's static typing can be found at [typing.readthedocs.io](https://typing.readthedocs.io/). You can get -help either in our [support forum](https://github.com/python/typing/discussions) or -chat with us on [Gitter](https://gitter.im/python/typing). +help in our [support forum](https://github.com/python/typing/discussions). Improvements to the type system should be discussed on the [typing-sig](https://mail.python.org/mailman3/lists/typing-sig.python.org/) mailing list, although the [issues](https://github.com/python/typing/issues) in this repository contain some historic discussions. +For conversations that are more suitable to a chat platform, you can use one of the following: +- [gitter](https://gitter.im/python/typing) +- [discord](https://discord.com/channels/267624335836053506/891788761371906108) `#type-hinting` channel + ## Repository Content This GitHub repository is used for several things: From 188471412419a21051b26a8c25738ad8d6e434eb Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Fri, 3 Feb 2023 18:06:34 -0800 Subject: [PATCH 143/539] Update list of PEPs (#1341) --- docs/index.rst | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index c00a278ea..686aad301 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -74,7 +74,7 @@ Linters and Formatters * `flake8-pyi `_, a plugin for the `flake8 `_ linter that adds support for type stubs. - + Type-Hint and Stub Integration ---------------------- @@ -85,6 +85,9 @@ Type-Hint and Stub Integration Typing PEPs =========== +https://peps.python.org/topic/typing + +* `PEP 482 `_, literature overview on type hints * `PEP 483 `_, background on type hints * `PEP 484 `_, type hints * `PEP 526 `_, variable annotations and ``ClassVar`` @@ -101,8 +104,15 @@ Typing PEPs * `PEP 613 `_, ``TypeAlias`` * `PEP 646 `_, variadic generics and ``TypeVarTuple`` * `PEP 647 `_, ``TypeGuard`` +* `PEP 649 `_ (draft), ``from __future__ import co_annotations`` * `PEP 655 `_, ``Required`` and ``NotRequired`` * `PEP 673 `_, ``Self`` * `PEP 675 `_, ``LiteralString`` -* `PEP 677 `_ (rejected), callable type syntax -* `PEP 681 `_ (draft), ``@dataclass_transform()`` +* `PEP 677 `_ (rejected), ``(int, str) -> bool`` callable type syntax +* `PEP 681 `_ ``@dataclass_transform()`` +* `PEP 688 `_ (draft), ``Buffer`` +* `PEP 692 `_ ``Unpack[TypedDict]`` for ``**kwargs`` +* `PEP 695 `_ (draft), ``class Class[T]:`` type parameter syntax +* `PEP 696 `_ (draft), defaults for type variables +* `PEP 698 `_ ``@override`` +* `PEP 702 `_ (draft), ``@deprecated()`` From 7ff5991c3b807b1ee13c87df29b1cb6c33773c52 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 19 Feb 2023 16:07:45 -0800 Subject: [PATCH 144/539] Update docs for tools that inline pyi (#1352) retype is no longer maintained. Since it's based on lib2to3, its corpse is not going to be useful for too long. merge_pyi now just wraps libCST, so mention that. If someone seeing this is looking for things to do, I think libCST's support here could be improved. E.g., I filed https://github.com/Instagram/LibCST/pull/868 But maybe that should convert to typing.Union. I'm sure there are other things that could be improved! --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 686aad301..2fef96e08 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -80,7 +80,7 @@ Type-Hint and Stub Integration * `autotyping `_, a tool which infers simple types from their context and inserts them as inline type-hints. * `merge_pyi `_, integrates .pyi signatures as inline type-hints in Python source code. -* `retype `_, Re-applies type annotations from .pyi stubs to your codebase. + This is a thin wrapper around ``ApplyTypeAnnotationsVisitor`` from `libCST `_. Typing PEPs =========== From 604ed086d2d6bbcb915ea314c54b0638dbcdf8ca Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Tue, 28 Mar 2023 16:52:30 -0700 Subject: [PATCH 145/539] Update PEP 688 status, drop mentions of Python 2 (#1379) --- docs/index.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 2fef96e08..84c4e5417 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,13 +48,13 @@ Type Checkers ------------- * `mypy `_, the reference implementation for type - checkers. Supports Python 2 and 3. + checkers. * `pyre `_, written in OCaml and optimized for - performance. Supports Python 3 only. + performance. * `pyright `_, a type checker that - emphasizes speed. Supports Python 3 only. + emphasizes speed. * `pytype `_, checks and infers types for - unannotated code. Supports Python 2 and 3. + unannotated code. Development Environments ------------------------ @@ -110,7 +110,7 @@ https://peps.python.org/topic/typing * `PEP 675 `_, ``LiteralString`` * `PEP 677 `_ (rejected), ``(int, str) -> bool`` callable type syntax * `PEP 681 `_ ``@dataclass_transform()`` -* `PEP 688 `_ (draft), ``Buffer`` +* `PEP 688 `_ ``Buffer`` * `PEP 692 `_ ``Unpack[TypedDict]`` for ``**kwargs`` * `PEP 695 `_ (draft), ``class Class[T]:`` type parameter syntax * `PEP 696 `_ (draft), defaults for type variables From e38affe8ff6daf0bca1e4442b5d31bea543bb79d Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Wed, 29 Mar 2023 00:38:18 -0700 Subject: [PATCH 146/539] Link to mypy docs (#1380) --- docs/index.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 84c4e5417..708444b8a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,6 +26,13 @@ Reference source/reference +.. seealso:: + + The documentation at https://mypy.readthedocs.io/ is relatively accessible + and complete. Particularly refer to the "Type System Reference" section of + the docs -- since the Python typing system is standardised via PEPs, this + information should apply to most Python type checkers. + Indices and tables ================== From 3811d8cadc81a687d8adc4ab81d42b3a91ea1259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr?= Date: Sun, 18 Jun 2023 14:43:57 +0300 Subject: [PATCH 147/539] Docs: fix `TypeError` in code example (#1413) Fix TypeError of 'class MyProto(Protocol)' TypeError: Too few arguments for typing.Mapping; actual 1, expected 2 --- docs/source/best_practices.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/best_practices.rst b/docs/source/best_practices.rst index 2e0cff6b1..24a6ff880 100644 --- a/docs/source/best_practices.rst +++ b/docs/source/best_practices.rst @@ -86,7 +86,7 @@ Maybe:: class MyProto(Protocol): def foo(self) -> list[int]: ... - def bar(self) -> Mapping[str]: ... + def bar(self) -> Mapping[str, str]: ... Avoid union return types, since they require ``isinstance()`` checks. Use ``Any`` or ``X | Any`` if necessary. From a477b761be76a9d3fbc512e9d57319b06fdd5d78 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 18 Jun 2023 14:11:20 -0700 Subject: [PATCH 148/539] Add link to PEP 705, update 695 status (#1414) --- docs/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 708444b8a..c61d13b6c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -119,7 +119,8 @@ https://peps.python.org/topic/typing * `PEP 681 `_ ``@dataclass_transform()`` * `PEP 688 `_ ``Buffer`` * `PEP 692 `_ ``Unpack[TypedDict]`` for ``**kwargs`` -* `PEP 695 `_ (draft), ``class Class[T]:`` type parameter syntax +* `PEP 695 `_ ``class Class[T]:`` type parameter syntax * `PEP 696 `_ (draft), defaults for type variables * `PEP 698 `_ ``@override`` * `PEP 702 `_ (draft), ``@deprecated()`` +* `PEP 705 `_ (draft), ``TypedMapping`` From f2df4b4d939f7ebc8cbd0e36589fb71ad0dae7b0 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 18 Jun 2023 16:57:34 -0700 Subject: [PATCH 149/539] Import the protocols documentation from mypy (#1415) Co-authored-by: Alex Waygood Co-authored-by: Jelle Zijlstra --- docs/source/protocols.rst | 577 ++++++++++++++++++++++++++++++++++++++ docs/source/reference.rst | 1 + 2 files changed, 578 insertions(+) create mode 100644 docs/source/protocols.rst diff --git a/docs/source/protocols.rst b/docs/source/protocols.rst new file mode 100644 index 000000000..39d3bfbfb --- /dev/null +++ b/docs/source/protocols.rst @@ -0,0 +1,577 @@ +.. _protocol-types: + +Protocols and structural subtyping +================================== + +The Python type system supports two ways of deciding whether two objects are +compatible as types: nominal subtyping and structural subtyping. + +*Nominal* subtyping is strictly based on the class hierarchy. If class ``Dog`` +inherits class ``Animal``, it's a subtype of ``Animal``. Instances of ``Dog`` +can be used when ``Animal`` instances are expected. This form of subtyping +is what Python's type system predominantly uses: it's easy to +understand and produces clear and concise error messages, and matches how the +native :py:func:`isinstance ` check works -- based on class +hierarchy. + +*Structural* subtyping is based on the operations that can be performed with an +object. Class ``Dog`` is a structural subtype of class ``Animal`` if the former +has all attributes and methods of the latter, and with compatible types. + +Structural subtyping can be seen as a static equivalent of duck typing, which is +well known to Python programmers. See :pep:`544` for the detailed specification +of protocols and structural subtyping in Python. + +.. _predefined_protocols: + +Predefined protocols +******************** + +The :py:mod:`typing` module defines various protocol classes that correspond to +places where duck typing is commonly used in Python, such as +:py:class:`Iterable[T] `. + +If a class defines a suitable :py:meth:`__iter__ ` method, type +checkers understand that it implements the :py:term:`iterable` protocol and is compatible +with :py:class:`Iterable[T] `. For example, ``IntList`` below +is iterable, over ``int`` values: + +.. code-block:: python + + from typing import Iterator, Iterable, Optional + + class IntList: + def __init__(self, value: int, next_node: Optional['IntList']) -> None: + self.value = value + self.next_node = next_node + + def __iter__(self) -> Iterator[int]: + current = self + while current: + yield current.value + current = current.next_node + + def print_numbered(items: Iterable[int]) -> None: + for n, x in enumerate(items): + print(n + 1, x) + + x = IntList(3, IntList(5, None)) + print_numbered(x) # OK + print_numbered([4, 5]) # Also OK + +:ref:`predefined_protocols_reference` lists all protocols defined in +:py:mod:`typing` and the signatures of the corresponding methods you need to define +to implement each protocol. + +Simple user-defined protocols +***************************** + +You can define your own protocol class by inheriting from the special +``Protocol`` class: + +.. code-block:: python + + from typing import Iterable + from typing_extensions import Protocol + + class SupportsClose(Protocol): + # Empty method body (explicit '...') + def close(self) -> None: ... + + class Resource: # No SupportsClose base class! + + def close(self) -> None: + self.resource.release() + + # ... other methods ... + + def close_all(items: Iterable[SupportsClose]) -> None: + for item in items: + item.close() + + close_all([Resource(), open('some/file')]) # OK + +``Resource`` is a subtype of the ``SupportsClose`` protocol since it defines +a compatible ``close`` method. Regular file objects returned by :py:func:`open` are +similarly compatible with the protocol, as they support ``close()``. + +Defining subprotocols and subclassing protocols +*********************************************** + +You can also define subprotocols. Existing protocols can be extended +and merged using multiple inheritance. Example: + +.. code-block:: python + + # ... continuing from the previous example + + class SupportsRead(Protocol): + def read(self, amount: int) -> bytes: ... + + class TaggedReadableResource(SupportsClose, SupportsRead, Protocol): + label: str + + class AdvancedResource(Resource): + def __init__(self, label: str) -> None: + self.label = label + + def read(self, amount: int) -> bytes: + # some implementation + ... + + resource: TaggedReadableResource + resource = AdvancedResource('handle with care') # OK + +Note that inheriting from an existing protocol does not automatically +turn the subclass into a protocol -- it just creates a regular +(non-protocol) class or ABC that implements the given protocol (or +protocols). The ``Protocol`` base class must always be explicitly +present if you are defining a protocol: + +.. code-block:: python + + class NotAProtocol(SupportsClose): # This is NOT a protocol + new_attr: int + + class Concrete: + new_attr: int = 0 + + def close(self) -> None: + ... + + # Error: nominal subtyping used by default + x: NotAProtocol = Concrete() # Error! + +You can also include default implementations of methods in +protocols. If you explicitly subclass these protocols, you inherit +these default implementations. + +Explicitly including a protocol as a +base class is also a way of documenting that your class implements a +particular protocol, and it forces the type checker to verify that your class +implementation is actually compatible with the protocol. In particular, +omitting a value for an attribute or a method body will make it implicitly +abstract: + +.. code-block:: python + + class SomeProto(Protocol): + attr: int # Note, no right hand side + # If the body is literally just "...", explicit subclasses will + # be treated as abstract classes if they don't implement the method. + def method(self) -> str: ... + + class ExplicitSubclass(SomeProto): + pass + + ExplicitSubclass() # error: Cannot instantiate abstract class 'ExplicitSubclass' + # with abstract attributes 'attr' and 'method' + +Similarly, explicitly assigning to a protocol instance can be a way to ask the +type checker to verify that your class implements a protocol: + +.. code-block:: python + + _proto: SomeProto = cast(ExplicitSubclass, None) + +Invariance of protocol attributes +********************************* + +A common issue with protocols is that protocol attributes are invariant. +For example: + +.. code-block:: python + + class Box(Protocol): + content: object + + class IntBox: + content: int + + def takes_box(box: Box) -> None: ... + + takes_box(IntBox()) # error: Argument 1 to "takes_box" has incompatible type "IntBox"; expected "Box" + # note: Following member(s) of "IntBox" have conflicts: + # note: content: expected "object", got "int" + +This is because ``Box`` defines ``content`` as a mutable attribute. +Here's why this is problematic: + +.. code-block:: python + + def takes_box_evil(box: Box) -> None: + box.content = "asdf" # This is bad, since box.content is supposed to be an object + + my_int_box = IntBox() + takes_box_evil(my_int_box) + my_int_box.content + 1 # Oops, TypeError! + +This can be fixed by declaring ``content`` to be read-only in the ``Box`` +protocol using ``@property``: + +.. code-block:: python + + class Box(Protocol): + @property + def content(self) -> object: ... + + class IntBox: + content: int + + def takes_box(box: Box) -> None: ... + + takes_box(IntBox(42)) # OK + +Recursive protocols +******************* + +Protocols can be recursive (self-referential) and mutually +recursive. This is useful for declaring abstract recursive collections +such as trees and linked lists: + +.. code-block:: python + + from typing import TypeVar, Optional + from typing_extensions import Protocol + + class TreeLike(Protocol): + value: int + + @property + def left(self) -> Optional['TreeLike']: ... + + @property + def right(self) -> Optional['TreeLike']: ... + + class SimpleTree: + def __init__(self, value: int) -> None: + self.value = value + self.left: Optional['SimpleTree'] = None + self.right: Optional['SimpleTree'] = None + + root: TreeLike = SimpleTree(0) # OK + +Using isinstance() with protocols +********************************* + +You can use a protocol class with :py:func:`isinstance` if you decorate it +with the ``@runtime_checkable`` class decorator. The decorator adds +rudimentary support for runtime structural checks: + +.. code-block:: python + + from typing_extensions import Protocol, runtime_checkable + + @runtime_checkable + class Portable(Protocol): + handles: int + + class Mug: + def __init__(self) -> None: + self.handles = 1 + + def use(handles: int) -> None: ... + + mug = Mug() + if isinstance(mug, Portable): # Works at runtime! + use(mug.handles) + +:py:func:`isinstance` also works with the :ref:`predefined protocols ` +in :py:mod:`typing` such as :py:class:`~typing.Iterable`. + +.. warning:: + :py:func:`isinstance` with protocols is not completely safe at runtime. + For example, signatures of methods are not checked. The runtime + implementation only checks that all protocol members exist, + not that they have the correct type. :py:func:`issubclass` with protocols + will only check for the existence of methods. + +.. note:: + :py:func:`isinstance` with protocols can also be surprisingly slow. + In many cases, you're better served by using :py:func:`hasattr` to + check for the presence of attributes. + +.. _callback_protocols: + +Callback protocols +****************** + +Protocols can be used to define flexible callback types that are hard +(or even impossible) to express using the :py:data:`Callable[...] ` syntax, such as variadic, +overloaded, and complex generic callbacks. They are defined with a special :py:meth:`__call__ ` +member: + +.. code-block:: python + + from typing import Optional, Iterable + from typing_extensions import Protocol + + class Combiner(Protocol): + def __call__(self, *vals: bytes, maxlen: Optional[int] = None) -> list[bytes]: ... + + def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes: + for item in data: + ... + + def good_cb(*vals: bytes, maxlen: Optional[int] = None) -> list[bytes]: + ... + def bad_cb(*vals: bytes, maxitems: Optional[int]) -> list[bytes]: + ... + + batch_proc([], good_cb) # OK + batch_proc([], bad_cb) # Error! Argument 2 has incompatible type because of + # different name and kind in the callback + +Callback protocols and :py:data:`~typing.Callable` types can be used mostly interchangeably. +Argument names in :py:meth:`__call__ ` methods must be identical, unless +a double underscore prefix is used. For example: + +.. code-block:: python + + from typing import Callable, TypeVar + from typing_extensions import Protocol + + T = TypeVar('T') + + class Copy(Protocol): + def __call__(self, __origin: T) -> T: ... + + copy_a: Callable[[T], T] + copy_b: Copy + + copy_a = copy_b # OK + copy_b = copy_a # Also OK + +.. _predefined_protocols_reference: + +Predefined protocol reference +***************************** + +Iteration protocols +................... + +The iteration protocols allow you to type things that can be iterated +over in ``for`` loops or things that can be passed to :py:func:`next`. + +Iterable[T] +----------- + +The :ref:`example above ` has a simple implementation of an +:py:meth:`__iter__ ` method. + +.. code-block:: python + + def __iter__(self) -> Iterator[T] + +See also :py:class:`~typing.Iterable`. + +Iterator[T] +----------- + +.. code-block:: python + + def __next__(self) -> T + def __iter__(self) -> Iterator[T] + +See also :py:class:`~typing.Iterator`. + +Collection protocols +.................... + +Many of these are implemented by built-in container types such as +:py:class:`list` and :py:class:`dict`, and these are also useful for user-defined +collection objects. + +Sized +----- + +This is a type for objects that support :py:func:`len(x) `. + +.. code-block:: python + + def __len__(self) -> int + +See also :py:class:`~typing.Sized`. + +Container[T] +------------ + +This is a type for objects that support the ``in`` operator. + +.. code-block:: python + + def __contains__(self, x: object) -> bool + +See also :py:class:`~typing.Container`. + +Collection[T] +------------- + +.. code-block:: python + + def __len__(self) -> int + def __iter__(self) -> Iterator[T] + def __contains__(self, x: object) -> bool + +See also :py:class:`~typing.Collection`. + +One-off protocols +................. + +These protocols are typically only useful with a single standard +library function or class. + +Reversible[T] +------------- + +This is a type for objects that support :py:func:`reversed(x) `. + +.. code-block:: python + + def __reversed__(self) -> Iterator[T] + +See also :py:class:`~typing.Reversible`. + +SupportsAbs[T] +-------------- + +This is a type for objects that support :py:func:`abs(x) `. ``T`` is the type of +value returned by :py:func:`abs(x) `. + +.. code-block:: python + + def __abs__(self) -> T + +See also :py:class:`~typing.SupportsAbs`. + +SupportsBytes +------------- + +This is a type for objects that support :py:class:`bytes(x) `. + +.. code-block:: python + + def __bytes__(self) -> bytes + +See also :py:class:`~typing.SupportsBytes`. + +.. _supports-int-etc: + +SupportsComplex +--------------- + +This is a type for objects that support :py:class:`complex(x) `. Note that no arithmetic operations +are supported. + +.. code-block:: python + + def __complex__(self) -> complex + +See also :py:class:`~typing.SupportsComplex`. + +SupportsFloat +------------- + +This is a type for objects that support :py:class:`float(x) `. Note that no arithmetic operations +are supported. + +.. code-block:: python + + def __float__(self) -> float + +See also :py:class:`~typing.SupportsFloat`. + +SupportsInt +----------- + +This is a type for objects that support :py:class:`int(x) `. Note that no arithmetic operations +are supported. + +.. code-block:: python + + def __int__(self) -> int + +See also :py:class:`~typing.SupportsInt`. + +SupportsRound[T] +---------------- + +This is a type for objects that support :py:func:`round(x) `. + +.. code-block:: python + + def __round__(self) -> T + +See also :py:class:`~typing.SupportsRound`. + +Async protocols +............... + +These protocols can be useful in async code. See :ref:`async-and-await` +for more information. + +Awaitable[T] +------------ + +.. code-block:: python + + def __await__(self) -> Generator[Any, None, T] + +See also :py:class:`~typing.Awaitable`. + +AsyncIterable[T] +---------------- + +.. code-block:: python + + def __aiter__(self) -> AsyncIterator[T] + +See also :py:class:`~typing.AsyncIterable`. + +AsyncIterator[T] +---------------- + +.. code-block:: python + + def __anext__(self) -> Awaitable[T] + def __aiter__(self) -> AsyncIterator[T] + +See also :py:class:`~typing.AsyncIterator`. + +Context manager protocols +......................... + +There are two protocols for context managers -- one for regular context +managers and one for async ones. These allow defining objects that can +be used in ``with`` and ``async with`` statements. + +ContextManager[T] +----------------- + +.. code-block:: python + + def __enter__(self) -> T + def __exit__(self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType]) -> Optional[bool] + +See also :py:class:`~typing.ContextManager`. + +AsyncContextManager[T] +---------------------- + +.. code-block:: python + + def __aenter__(self) -> Awaitable[T] + def __aexit__(self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType]) -> Awaitable[Optional[bool]] + +See also :py:class:`~typing.AsyncContextManager`. + +Credits +******* + +This document is based on the `mypy documentation `_ diff --git a/docs/source/reference.rst b/docs/source/reference.rst index 814dfe1c7..27ce0d045 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -6,6 +6,7 @@ Type System Reference :maxdepth: 2 :caption: Contents: + protocols stubs best_practices quality From 7d72ce0d628dd811bc8c6037caee0f5f3f9c6fdb Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 25 Jun 2023 16:33:53 -0700 Subject: [PATCH 150/539] Import the generics documentation from mypy (#1416) --- docs/source/generics.rst | 986 ++++++++++++++++++++++++++++++++++++++ docs/source/reference.rst | 1 + 2 files changed, 987 insertions(+) create mode 100644 docs/source/generics.rst diff --git a/docs/source/generics.rst b/docs/source/generics.rst new file mode 100644 index 000000000..cb82af508 --- /dev/null +++ b/docs/source/generics.rst @@ -0,0 +1,986 @@ +Generics +======== + +You may have seen type hints like ``list[str]`` or ``dict[str, int]`` in Python +code. These types are interesting in that they are parametrised by other types! +A ``list[str]`` isn't just a list, it's a list of strings. Types with type +parameters like this are called *generic types*. + +You can define your own generic classes that take type parameters, similar to +built-in types such as ``list[X]``. Note that such user-defined generics are a +moderately advanced feature and you can get far without ever using them. + +.. _generic-classes: + +Defining generic classes +************************ + +Here is a very simple generic class that represents a stack: + +.. code-block:: python + + from typing import TypeVar, Generic + + T = TypeVar('T') + + class Stack(Generic[T]): + def __init__(self) -> None: + # Create an empty list with items of type T + self.items: list[T] = [] + + def push(self, item: T) -> None: + self.items.append(item) + + def pop(self) -> T: + return self.items.pop() + + def empty(self) -> bool: + return not self.items + +The ``Stack`` class can be used to represent a stack of any type: +``Stack[int]``, ``Stack[tuple[int, str]]``, etc. + +Using ``Stack`` is similar to built-in container types, like ``list``: + +.. code-block:: python + + # Construct an empty Stack[int] instance + stack = Stack[int]() + stack.push(2) + stack.pop() + 1 + stack.push('x') # error: Argument 1 to "push" of "Stack" has incompatible type "str"; expected "int" + +When creating instances of generic classes, the type argument can usually be +inferred. In cases where you explicitly specify the type argument, the +construction of the instance will be type checked correspondingly. + +.. code-block:: python + + class Box(Generic[T]): + def __init__(self, content: T) -> None: + self.content = content + + Box(1) # OK, inferred type is Box[int] + Box[int](1) # Also OK + Box[int]('some string') # error: Argument 1 to "Box" has incompatible type "str"; expected "int" + +.. _generic-subclasses: + +Defining subclasses of generic classes +************************************** + +User-defined generic classes and generic classes defined in :py:mod:`typing` +can be used as a base class for another class (generic or non-generic). For example: + +.. code-block:: python + + from typing import Generic, TypeVar, Mapping, Iterator + + KT = TypeVar('KT') + VT = TypeVar('VT') + + # This is a generic subclass of Mapping + class MyMap(Mapping[KT, VT]): + def __getitem__(self, k: KT) -> VT: ... + def __iter__(self) -> Iterator[KT]: ... + def __len__(self) -> int: ... + + items: MyMap[str, int] # OK + + # This is a non-generic subclass of dict + class StrDict(dict[str, str]): + def __str__(self) -> str: + return f'StrDict({super().__str__()})' + + + data: StrDict[int, int] # error: "StrDict" expects no type arguments, but 2 given + data2: StrDict # OK + + # This is a user-defined generic class + class Receiver(Generic[T]): + def accept(self, value: T) -> None: ... + + # This is a generic subclass of Receiver + class AdvancedReceiver(Receiver[T]): ... + +.. note:: + + Note that you have to explicitly inherit from :py:class:`~typing.Mapping` + and :py:class:`~typing.Sequence` for your class to be considered a mapping + or sequence. This is because these classes are nominally typed, unlike + protocols like :py:class:`~typing.Iterable`, which use + :ref:`structural subtyping `. + +:py:class:`Generic ` can be omitted from bases if there are +other base classes that include type variables, such as ``Mapping[KT, VT]`` +in the above example. If you include ``Generic[...]`` in bases, then +it should list all type variables present in other bases (or more, +if needed). The order of type variables is defined by the following +rules: + +* If ``Generic[...]`` is present, then the order of variables is + always determined by their order in ``Generic[...]``. +* If there are no ``Generic[...]`` in bases, then all type variables + are collected in the lexicographic order (i.e. by first appearance). + +For example: + +.. code-block:: python + + from typing import Generic, TypeVar, Any + + T = TypeVar('T') + S = TypeVar('S') + U = TypeVar('U') + + class One(Generic[T]): ... + class Another(Generic[T]): ... + + class First(One[T], Another[S]): ... + class Second(One[T], Another[S], Generic[S, U, T]): ... + + x: First[int, str] # Here T is bound to int, S is bound to str + y: Second[int, str, Any] # Here T is Any, S is int, and U is str + +.. _generic-functions: + +Generic functions +***************** + +Type variables can be used to define generic functions. These are functions +where the types of the arguments or return value have some relationship: + +.. code-block:: python + + from typing import TypeVar, Sequence + + T = TypeVar('T') + + # A generic function! + def first(seq: Sequence[T]) -> T: + return seq[0] + +As with generic classes, the type variable can be replaced with any +type. That means ``first`` can be used with any sequence type, and the +return type is derived from the sequence item type. For example: + +.. code-block:: python + + reveal_type(first([1, 2, 3])) # Revealed type is "builtins.int" + reveal_type(first(['a', 'b'])) # Revealed type is "builtins.str" + +Since type variables are about describing the relationship between +two or more types, it's usually not useful to have a type variable +only appear once in a function signature. + +Note that for convenience, a single type variable symbol (such as ``T`` above) +can be used in multiple generic functions or classes, even though the logical +scope is different in each generic function or class. In the following example +we reuse the same type variable symbol in two generic functions; these two +functions do not share any typing relationship to each other: + +.. code-block:: python + + from typing import TypeVar, Sequence + + T = TypeVar('T') + + def first(seq: Sequence[T]) -> T: + return seq[0] + + def last(seq: Sequence[T]) -> T: + return seq[-1] + +Variables should not have a type variable in their type unless the type variable +is bound by a containing generic class, generic function or generic alias. + +.. _generic-methods-and-generic-self: + +Generic methods and generic self +******************************** + +You can also define generic methods — just use a type variable in the +method signature that is different from the type variable(s) bound in +the class definition. + +.. code-block:: python + + # T is the type variable bound by this class + class PairedBox(Generic[T]): + def __init__(self, content: T) -> None: + self.content = content + + # S is a type variable bound only in this method + def first(self, x: list[S]) -> S: + return x[0] + + def pair_with_first(self, x: list[S]) -> tuple[S, T]: + return (x[0], self.content) + + box = PairedBox("asdf") + reveal_type(box.first([1, 2, 3])) # Revealed type is "builtins.int" + reveal_type(box.pair_with_first([1, 2, 3])) # Revealed type is "tuple[builtins.int, builtins.str]" + +In particular, the ``self`` argument may also be generic, allowing a +method to return the most precise type known at the point of access. +In this way, for example, you can type check a chain of setter +methods: + +.. code-block:: python + + from typing import TypeVar + + T = TypeVar('T', bound='Shape') + + class Shape: + def set_scale(self: T, scale: float) -> T: + self.scale = scale + return self + + class Circle(Shape): + def set_radius(self, r: float) -> 'Circle': + self.radius = r + return self + + class Square(Shape): + def set_width(self, w: float) -> 'Square': + self.width = w + return self + + circle: Circle = Circle().set_scale(0.5).set_radius(2.7) + square: Square = Square().set_scale(0.5).set_width(3.2) + +Without using generic ``self``, the last two lines could not be type +checked properly, since the return type of ``set_scale`` would be +``Shape``, which doesn't define ``set_radius`` or ``set_width``. + +Other uses are factory methods, such as copy and deserialization. +For class methods, you can also define generic ``cls``, using :py:class:`type`: + +.. code-block:: python + + from typing import TypeVar, Type + + T = TypeVar('T', bound='Friend') + + class Friend: + other: "Friend" = None + + @classmethod + def make_pair(cls: Type[T]) -> tuple[T, T]: + a, b = cls(), cls() + a.other = b + b.other = a + return a, b + + class SuperFriend(Friend): + pass + + a, b = SuperFriend.make_pair() + +Note that when overriding a method with generic ``self``, you must either +return a generic ``self`` too, or return an instance of the current class. +In the latter case, you must implement this method in all future subclasses. + +Note also that the type checker may not always verify that the implementation of a copy +or a deserialization method returns the actual type of self. Therefore +you may need to silence the type checker inside these methods (but not at the call site), +possibly by making use of the ``Any`` type or a ``# type: ignore`` comment. + +For some advanced uses of self types, see :ref:`additional examples `. + +Automatic self types using typing.Self +************************************** + +Since the patterns described above are quite common, a simpler syntax +was introduced in :pep:`673`. + +Instead of defining a type variable and using an explicit annotation +for ``self``, you can use the special type ``typing.Self``. This is +automatically transformed into a type variable with the current class +as the upper bound, and you don't need an annotation for ``self`` (or +``cls`` in class methods). + +Here's what the example from the previous section looks like +when using ``typing.Self``: + +.. code-block:: python + + from typing import Self + + class Friend: + other: Self | None = None + + @classmethod + def make_pair(cls) -> tuple[Self, Self]: + a, b = cls(), cls() + a.other = b + b.other = a + return a, b + + class SuperFriend(Friend): + pass + + a, b = SuperFriend.make_pair() + +This is more compact than using explicit type variables. Also, you can +use ``Self`` in attribute annotations in addition to methods. + +.. note:: + + To use this feature on Python versions earlier than 3.11, you will need to + import ``Self`` from ``typing_extensions`` (version 4.0 or newer). + +.. _variance-of-generics: + +Variance of generic types +************************* + +There are three main kinds of generic types with respect to subtype +relations between them: invariant, covariant, and contravariant. +Assuming that we have a pair of types ``Animal`` and ``Bear``, and +``Bear`` is a subtype of ``Animal``, these are defined as follows: + +* A generic class ``MyCovGen[T]`` is called covariant in type parameter + ``T`` if ``MyCovGen[Bear]`` is a subtype of ``MyCovGen[Animal]``. + This is the most intuitive form of variance. +* A generic class ``MyContraGen[T]`` is called contravariant in type + parameter ``T`` if ``MyContraGen[Animal]`` is a subtype of + ``MyContraGen[Bear]``. +* A generic class ``MyInvGen[T]`` is called invariant in ``T`` if neither + of the above is true. + +Let us illustrate this by few simple examples: + +.. code-block:: python + + # We'll use these classes in the examples below + class Shape: ... + class Triangle(Shape): ... + class Square(Shape): ... + +* Most immutable containers, such as :py:class:`~typing.Sequence` and + :py:class:`~typing.FrozenSet` are covariant. :py:data:`~typing.Union` is + also covariant in all variables: ``Union[Triangle, int]`` is + a subtype of ``Union[Shape, int]``. + + .. code-block:: python + + def count_lines(shapes: Sequence[Shape]) -> int: + return sum(shape.num_sides for shape in shapes) + + triangles: Sequence[Triangle] + count_lines(triangles) # OK + + def foo(triangle: Triangle, num: int): + shape_or_number: Union[Shape, int] + # a Triangle is a Shape, and a Shape is a valid Union[Shape, int] + shape_or_number = triangle + + Covariance should feel relatively intuitive, but contravariance and invariance + can be harder to reason about. + +* :py:data:`~typing.Callable` is an example of type that behaves contravariantly + in types of arguments. That is, ``Callable[[Shape], int]`` is a subtype of + ``Callable[[Triangle], int]``, despite ``Shape`` being a supertype of + ``Triangle``. To understand this, consider: + + .. code-block:: python + + def cost_of_paint_required( + triangle: Triangle, + area_calculator: Callable[[Triangle], float] + ) -> float: + return area_calculator(triangle) * DOLLAR_PER_SQ_FT + + # This straightforwardly works + def area_of_triangle(triangle: Triangle) -> float: ... + cost_of_paint_required(triangle, area_of_triangle) # OK + + # But this works as well! + def area_of_any_shape(shape: Shape) -> float: ... + cost_of_paint_required(triangle, area_of_any_shape) # OK + + ``cost_of_paint_required`` needs a callable that can calculate the area of a + triangle. If we give it a callable that can calculate the area of an + arbitrary shape (not just triangles), everything still works. + +* :py:class:`~typing.List` is an invariant generic type. Naively, one would think + that it is covariant, like :py:class:`~typing.Sequence` above, but consider this code: + + .. code-block:: python + + class Circle(Shape): + # The rotate method is only defined on Circle, not on Shape + def rotate(self): ... + + def add_one(things: list[Shape]) -> None: + things.append(Shape()) + + my_circles: list[Circle] = [] + add_one(my_circles) # This may appear safe, but... + my_circles[-1].rotate() # ...this will fail, since my_circles[0] is now a Shape, not a Circle + + Another example of an invariant type is :py:class:`~typing.Dict`. Most mutable containers + are invariant. + +By default, all user-defined generics are invariant. +To declare a given generic class as covariant or contravariant use +type variables defined with special keyword arguments ``covariant`` or +``contravariant``. For example: + +.. code-block:: python + + from typing import Generic, TypeVar + + T_co = TypeVar('T_co', covariant=True) + + class Box(Generic[T_co]): # this type is declared covariant + def __init__(self, content: T_co) -> None: + self._content = content + + def get_content(self) -> T_co: + return self._content + + def look_into(box: Box[Animal]): ... + + my_box = Box(Cat()) + look_into(my_box) # OK, but would be an error if Box was invariant in T + +.. _type-variable-upper-bound: + +Type variables with upper bounds +******************************** + +By default, a type variable can be replaced with any type. This means that +you can't do very much with an object of type ``T`` safely -- you don't +know anything about it! + +It's therefore often useful to be able to limit the types that a type +variable can take on, for instance, by restricting it to values that are +subtypes of a specific type. + +Such a type is called the upper bound of the type variable, and is specified +with the ``bound=...`` keyword argument to :py:class:`~typing.TypeVar`. + +.. code-block:: python + + from typing import TypeVar, SupportsAbs + + T = TypeVar('T', bound=SupportsAbs[float]) + +In the definition of a generic function that uses such a type variable +``T``, the type represented by ``T`` is assumed to be a subtype of +its upper bound, so the function can use methods of the upper bound on +values of type ``T``. + +.. code-block:: python + + def largest_in_absolute_value(*xs: T) -> T: + return max(xs, key=abs) # Okay, because T is a subtype of SupportsAbs[float]. + +In a call to such a function, the type ``T`` must be replaced by a +type that is a subtype of its upper bound. Continuing the example +above: + +.. code-block:: python + + largest_in_absolute_value(-3.5, 2) # OK, has type float + largest_in_absolute_value(5+6j, 7) # OK, has type complex + largest_in_absolute_value('a', 'b') # error: error: Value of type variable "T" of "largest_in_absolute_value" cannot be "str" + +Type parameters of generic classes may also have upper bounds, which +restrict the valid values for the type parameter in the same way. + +.. _type-variable-value-restriction: + +Type variables with constraints +******************************* + +In some cases, it can be useful to restrict the values that a type variable can take to +exactly a specific set of types. This feature is a little complex and should +be avoided if an upper bound can be made to work instead, as above. + +An example is a type variable that can only have values ``str`` and ``bytes``: + +.. code-block:: python + + from typing import TypeVar + + AnyStr = TypeVar('AnyStr', str, bytes) + +This is actually such a common type variable that :py:data:`~typing.AnyStr` is +defined in :py:mod:`typing`. + +We can use :py:data:`~typing.AnyStr` to define a function that can concatenate +two strings or bytes objects, but it can't be called with other +argument types: + +.. code-block:: python + + from typing import AnyStr + + def concat(x: AnyStr, y: AnyStr) -> AnyStr: + return x + y + + concat('a', 'b') # Okay + concat(b'a', b'b') # Okay + concat(1, 2) # Error! + +Importantly, this is different from a union type, since combinations +of ``str`` and ``bytes`` are not accepted: + +.. code-block:: python + + concat('string', b'bytes') # Error! + +In this case, this is exactly what we want, since it's not possible +to concatenate a string and a bytes object! If we tried to use +``Union``, the type checker would complain about this possibility: + +.. code-block:: python + + def union_concat(x: Union[str, bytes], y: Union[str, bytes]) -> Union[str, bytes]: + return x + y # Error: can't concatenate str and bytes + +Another interesting special case is calling ``concat()`` with a +subtype of ``str``: + +.. code-block:: python + + class S(str): pass + + ss = concat(S('foo'), S('bar')) + reveal_type(ss) # Revealed type is "builtins.str" + +You may expect that the type of ``ss`` is ``S``, but the type is +actually ``str``: a subtype gets promoted to one of the valid values +for the type variable, which in this case is ``str``. + +This is thus subtly different from *bounded quantification* in languages such as +Java, where the return type would be ``S``. The way type checkers implement this +actually does exactly what we want for ``concat``, since ``concat`` returns an +instance of exactly ``str`` in the above example: + +.. code-block:: python + + >>> print(type(ss)) + + +You can also use a :py:class:`~typing.TypeVar` with a restricted set of possible +values when defining a generic class. For example, you can use the type +:py:class:`Pattern[AnyStr] ` for the return value of :py:func:`re.compile`, +since regular expressions can be based on a string or a bytes pattern. + +A type variable may not have both a value restriction (see +:ref:`type-variable-upper-bound`) and an upper bound. + +.. _declaring-decorators: + +Declaring decorators +******************** + +Decorators are typically functions that take a function as an argument and +return another function. Describing this behaviour in terms of types can +be a little tricky; we'll show how you can use ``TypeVar`` and a special +kind of type variable called a *parameter specification* to do so. + +Suppose we have the following decorator, not type annotated yet, +that preserves the original function's signature and merely prints the decorated function's name: + +.. code-block:: python + + def printing_decorator(func): + def wrapper(*args, **kwds): + print("Calling", func) + return func(*args, **kwds) + return wrapper + +and we use it to decorate function ``add_forty_two``: + +.. code-block:: python + + # A decorated function. + @printing_decorator + def add_forty_two(value: int) -> int: + return value + 42 + + a = add_forty_two(3) + +Since ``printing_decorator`` is not type-annotated, the following won't get type checked: + +.. code-block:: python + + reveal_type(a) # Revealed type is "Any" + add_forty_two('foo') # No type checker error :( + +This is a sorry state of affairs! + +Here's how one could annotate the decorator: + +.. code-block:: python + + from typing import Any, Callable, TypeVar, cast + + F = TypeVar('F', bound=Callable[..., Any]) + + # A decorator that preserves the signature. + def printing_decorator(func: F) -> F: + def wrapper(*args, **kwds): + print("Calling", func) + return func(*args, **kwds) + return cast(F, wrapper) + + @printing_decorator + def add_forty_two(value: int) -> int: + return value + 42 + + a = add_forty_two(3) + reveal_type(a) # Revealed type is "builtins.int" + add_forty_two('x') # Argument 1 to "add_forty_two" has incompatible type "str"; expected "int" + +This still has some shortcomings. First, we need to use the unsafe +:py:func:`~typing.cast` to convince type checkers that ``wrapper()`` has the same +signature as ``func``. See :ref:`casts `. + +Second, the ``wrapper()`` function is not tightly type checked, although +wrapper functions are typically small enough that this is not a big +problem. This is also the reason for the :py:func:`~typing.cast` call in the +``return`` statement in ``printing_decorator()``. + +However, we can use a parameter specification (:py:class:`~typing.ParamSpec`), +for a more faithful type annotation: + +.. code-block:: python + + from typing import Callable, TypeVar + from typing_extensions import ParamSpec + + P = ParamSpec('P') + T = TypeVar('T') + + def printing_decorator(func: Callable[P, T]) -> Callable[P, T]: + def wrapper(*args: P.args, **kwds: P.kwargs) -> T: + print("Calling", func) + return func(*args, **kwds) + return wrapper + +Parameter specifications also allow you to describe decorators that +alter the signature of the input function: + +.. code-block:: python + + from typing import Callable, TypeVar + from typing_extensions import ParamSpec + + P = ParamSpec('P') + T = TypeVar('T') + + # We reuse 'P' in the return type, but replace 'T' with 'str' + def stringify(func: Callable[P, T]) -> Callable[P, str]: + def wrapper(*args: P.args, **kwds: P.kwargs) -> str: + return str(func(*args, **kwds)) + return wrapper + + @stringify + def add_forty_two(value: int) -> int: + return value + 42 + + a = add_forty_two(3) + reveal_type(a) # Revealed type is "builtins.str" + add_forty_two('x') # error: Argument 1 to "add_forty_two" has incompatible type "str"; expected "int" + +Or insert an argument: + +.. code-block:: python + + from typing import Callable, TypeVar + from typing_extensions import Concatenate, ParamSpec + + P = ParamSpec('P') + T = TypeVar('T') + + def printing_decorator(func: Callable[P, T]) -> Callable[Concatenate[str, P], T]: + def wrapper(msg: str, /, *args: P.args, **kwds: P.kwargs) -> T: + print("Calling", func, "with", msg) + return func(*args, **kwds) + return wrapper + + @printing_decorator + def add_forty_two(value: int) -> int: + return value + 42 + + a = add_forty_two('three', 3) + +.. _decorator-factories: + +Decorator factories +------------------- + +Functions that take arguments and return a decorator (also called second-order decorators), are +similarly supported via generics: + +.. code-block:: python + + from typing import Any, Callable, TypeVar + + F = TypeVar('F', bound=Callable[..., Any]) + + def route(url: str) -> Callable[[F], F]: + ... + + @route(url='/') + def index(request: Any) -> str: + return 'Hello world' + +Sometimes the same decorator supports both bare calls and calls with arguments. This can be +achieved by combining with :py:func:`@overload `: + +.. code-block:: python + + from typing import Any, Callable, Optional, TypeVar, overload + + F = TypeVar('F', bound=Callable[..., Any]) + + # Bare decorator usage + @overload + def atomic(__func: F) -> F: ... + # Decorator with arguments + @overload + def atomic(*, savepoint: bool = True) -> Callable[[F], F]: ... + + # Implementation + def atomic(__func: Optional[Callable[..., Any]] = None, *, savepoint: bool = True): + def decorator(func: Callable[..., Any]): + ... # Code goes here + if __func is not None: + return decorator(__func) + else: + return decorator + + # Usage + @atomic + def func1() -> None: ... + + @atomic(savepoint=False) + def func2() -> None: ... + +Generic protocols +***************** + +Protocols can also be generic (see also :ref:`protocol-types`). Several +:ref:`predefined protocols ` are generic, such as +:py:class:`Iterable[T] `, and you can define additional generic +protocols. Generic protocols mostly follow the normal rules for generic classes. +Example: + +.. code-block:: python + + from typing import TypeVar + from typing_extensions import Protocol + + T = TypeVar('T') + + class Box(Protocol[T]): + content: T + + def do_stuff(one: Box[str], other: Box[bytes]) -> None: + ... + + class StringWrapper: + def __init__(self, content: str) -> None: + self.content = content + + class BytesWrapper: + def __init__(self, content: bytes) -> None: + self.content = content + + do_stuff(StringWrapper('one'), BytesWrapper(b'other')) # OK + + x: Box[float] = ... + y: Box[int] = ... + x = y # Error -- Box is invariant + +Note that ``class ClassName(Protocol[T])`` is allowed as a shorthand for +``class ClassName(Protocol, Generic[T])``, as per :pep:`PEP 544: Generic protocols <544#generic-protocols>`, + +The main difference between generic protocols and ordinary generic classes is +that the declared variances of generic type variables in a protocol are checked +against how they are used in the protocol definition. The protocol in this +example is rejected, since the type variable ``T`` is used covariantly as a +return type, but the type variable is invariant: + +.. code-block:: python + + from typing import Protocol, TypeVar + + T = TypeVar('T') + + class ReadOnlyBox(Protocol[T]): # error: Invariant type variable "T" used in protocol where covariant one is expected + def content(self) -> T: ... + +This example correctly uses a covariant type variable: + +.. code-block:: python + + from typing import Protocol, TypeVar + + T_co = TypeVar('T_co', covariant=True) + + class ReadOnlyBox(Protocol[T_co]): # OK + def content(self) -> T_co: ... + + ax: ReadOnlyBox[float] = ... + ay: ReadOnlyBox[int] = ... + ax = ay # OK -- ReadOnlyBox is covariant + +See :ref:`variance-of-generics` for more about variance. + +Generic protocols can also be recursive. Example: + +.. code-block:: python + + T = TypeVar('T') + + class Linked(Protocol[T]): + val: T + def next(self) -> 'Linked[T]': ... + + class L: + val: int + def next(self) -> 'L': ... + + def last(seq: Linked[T]) -> T: ... + + result = last(L()) + reveal_type(result) # Revealed type is "builtins.int" + +.. _generic-type-aliases: + +Generic type aliases +******************** + +Type aliases can be generic. In this case they can be used in two ways: +Subscripted aliases are equivalent to original types with substituted type +variables, so the number of type arguments must match the number of free type variables +in the generic type alias. Unsubscripted aliases are treated as original types with free +variables replaced with ``Any``. Examples (following :pep:`PEP 484: Type aliases +<484#type-aliases>`): + +.. code-block:: python + + from typing import TypeVar, Iterable, Union, Callable + + S = TypeVar('S') + + TInt = tuple[int, S] + UInt = Union[S, int] + CBack = Callable[..., S] + + def response(query: str) -> UInt[str]: # Same as Union[str, int] + ... + def activate(cb: CBack[S]) -> S: # Same as Callable[..., S] + ... + table_entry: TInt # Same as tuple[int, Any] + + T = TypeVar('T', int, float, complex) + + Vec = Iterable[tuple[T, T]] + + def inproduct(v: Vec[T]) -> T: + return sum(x*y for x, y in v) + + def dilate(v: Vec[T], scale: T) -> Vec[T]: + return ((x * scale, y * scale) for x, y in v) + + v1: Vec[int] = [] # Same as Iterable[tuple[int, int]] + v2: Vec = [] # Same as Iterable[tuple[Any, Any]] + v3: Vec[int, int] = [] # Error: Invalid alias, too many type arguments! + +Type aliases can be imported from modules just like other names. An +alias can also target another alias, although building complex chains +of aliases is not recommended -- this impedes code readability, thus +defeating the purpose of using aliases. Example: + +.. code-block:: python + + from typing import TypeVar, Generic, Optional + from example1 import AliasType + from example2 import Vec + + # AliasType and Vec are type aliases (Vec as defined above) + + def fun() -> AliasType: + ... + + T = TypeVar('T') + + class NewVec(Vec[T]): + ... + + for i, j in NewVec[int](): + ... + + OIntVec = Optional[Vec[int]] + +Using type variable bounds or values in generic aliases has the same effect +as in generic classes/functions. + + +Generic class internals +*********************** + +You may wonder what happens at runtime when you index a generic class. +Indexing returns a *generic alias* to the original class that returns instances +of the original class on instantiation: + +.. code-block:: python + + >>> from typing import TypeVar, Generic + >>> T = TypeVar('T') + >>> class Stack(Generic[T]): ... + >>> Stack + __main__.Stack + >>> Stack[int] + __main__.Stack[int] + >>> instance = Stack[int]() + >>> instance.__class__ + __main__.Stack + +Generic aliases can be instantiated or subclassed, similar to real +classes, but the above examples illustrate that type variables are +erased at runtime. Generic ``Stack`` instances are just ordinary +Python objects, and they have no extra runtime overhead or magic due +to being generic, other than overloading the indexing operation. + +Note that in Python 3.8 and lower, the built-in types +:py:class:`list`, :py:class:`dict` and others do not support indexing. +This is why we have the aliases :py:class:`~typing.List`, +:py:class:`~typing.Dict` and so on in the :py:mod:`typing` +module. Indexing these aliases gives you a generic alias that +resembles generic aliases constructed by directly indexing the target +class in more recent versions of Python: + +.. code-block:: python + + >>> # Only relevant for Python 3.8 and below + >>> # For Python 3.9 onwards, prefer `list[int]` syntax + >>> from typing import List + >>> List[int] + typing.List[int] + +Note that the generic aliases in ``typing`` don't support constructing +instances: + +.. code-block:: python + + >>> from typing import List + >>> List[int]() + Traceback (most recent call last): + ... + TypeError: Type List cannot be instantiated; use list() instead + +Credits +******* + +This document is based on the `mypy documentation `_ diff --git a/docs/source/reference.rst b/docs/source/reference.rst index 27ce0d045..54afcc7b1 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -6,6 +6,7 @@ Type System Reference :maxdepth: 2 :caption: Contents: + generics protocols stubs best_practices From 054aa5387aaed68eea59a3d7dabeaad1089a1c13 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Fri, 30 Jun 2023 11:55:43 +0200 Subject: [PATCH 151/539] Remove typing_extensions directory (#1423) --- typing_extensions/LICENSE | 254 ----------------------------------- typing_extensions/README.rst | 1 - 2 files changed, 255 deletions(-) delete mode 100644 typing_extensions/LICENSE delete mode 100644 typing_extensions/README.rst diff --git a/typing_extensions/LICENSE b/typing_extensions/LICENSE deleted file mode 100644 index 583f9f6e6..000000000 --- a/typing_extensions/LICENSE +++ /dev/null @@ -1,254 +0,0 @@ -A. HISTORY OF THE SOFTWARE -========================== - -Python was created in the early 1990s by Guido van Rossum at Stichting -Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands -as a successor of a language called ABC. Guido remains Python's -principal author, although it includes many contributions from others. - -In 1995, Guido continued his work on Python at the Corporation for -National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) -in Reston, Virginia where he released several versions of the -software. - -In May 2000, Guido and the Python core development team moved to -BeOpen.com to form the BeOpen PythonLabs team. In October of the same -year, the PythonLabs team moved to Digital Creations (now Zope -Corporation, see http://www.zope.com). In 2001, the Python Software -Foundation (PSF, see http://www.python.org/psf/) was formed, a -non-profit organization created specifically to own Python-related -Intellectual Property. Zope Corporation is a sponsoring member of -the PSF. - -All Python releases are Open Source (see http://www.opensource.org for -the Open Source Definition). Historically, most, but not all, Python -releases have also been GPL-compatible; the table below summarizes -the various releases. - - Release Derived Year Owner GPL- - from compatible? (1) - - 0.9.0 thru 1.2 1991-1995 CWI yes - 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes - 1.6 1.5.2 2000 CNRI no - 2.0 1.6 2000 BeOpen.com no - 1.6.1 1.6 2001 CNRI yes (2) - 2.1 2.0+1.6.1 2001 PSF no - 2.0.1 2.0+1.6.1 2001 PSF yes - 2.1.1 2.1+2.0.1 2001 PSF yes - 2.1.2 2.1.1 2002 PSF yes - 2.1.3 2.1.2 2002 PSF yes - 2.2 and above 2.1.1 2001-now PSF yes - -Footnotes: - -(1) GPL-compatible doesn't mean that we're distributing Python under - the GPL. All Python licenses, unlike the GPL, let you distribute - a modified version without making your changes open source. The - GPL-compatible licenses make it possible to combine Python with - other software that is released under the GPL; the others don't. - -(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, - because its license has a choice of law clause. According to - CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 - is "not incompatible" with the GPL. - -Thanks to the many outside volunteers who have worked under Guido's -direction to make these releases possible. - - -B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON -=============================================================== - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are -retained in Python alone or in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 -------------------------------------------- - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - -1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an -office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the -Individual or Organization ("Licensee") accessing and otherwise using -this software in source or binary form and its associated -documentation ("the Software"). - -2. Subject to the terms and conditions of this BeOpen Python License -Agreement, BeOpen hereby grants Licensee a non-exclusive, -royalty-free, world-wide license to reproduce, analyze, test, perform -and/or display publicly, prepare derivative works, distribute, and -otherwise use the Software alone or in any derivative version, -provided, however, that the BeOpen Python License is retained in the -Software, alone or in any derivative version prepared by Licensee. - -3. BeOpen is making the Software available to Licensee on an "AS IS" -basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS -AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -5. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -6. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of California, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed to -create any relationship of agency, partnership, or joint venture -between BeOpen and Licensee. This License Agreement does not grant -permission to use BeOpen trademarks or trade names in a trademark -sense to endorse or promote products or services of Licensee, or any -third party. As an exception, the "BeOpen Python" logos available at -http://www.pythonlabs.com/logos.html may be used according to the -permissions granted on that web page. - -7. By copying, installing or otherwise using the software, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 ---------------------------------------- - -1. This LICENSE AGREEMENT is between the Corporation for National -Research Initiatives, having an office at 1895 Preston White Drive, -Reston, VA 20191 ("CNRI"), and the Individual or Organization -("Licensee") accessing and otherwise using Python 1.6.1 software in -source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, CNRI -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python 1.6.1 -alone or in any derivative version, provided, however, that CNRI's -License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) -1995-2001 Corporation for National Research Initiatives; All Rights -Reserved" are retained in Python 1.6.1 alone or in any derivative -version prepared by Licensee. Alternately, in lieu of CNRI's License -Agreement, Licensee may substitute the following text (omitting the -quotes): "Python 1.6.1 is made available subject to the terms and -conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the Internet using the following -unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the Internet -using the following URL: http://hdl.handle.net/1895.22/1013". - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python 1.6.1 or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python 1.6.1. - -4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" -basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. This License Agreement shall be governed by the federal -intellectual property law of the United States, including without -limitation the federal copyright law, and, to the extent such -U.S. federal law does not apply, by the law of the Commonwealth of -Virginia, excluding Virginia's conflict of law provisions. -Notwithstanding the foregoing, with regard to derivative works based -on Python 1.6.1 that incorporate non-separable material that was -previously distributed under the GNU General Public License (GPL), the -law of the Commonwealth of Virginia shall govern this License -Agreement only as to issues arising under or with respect to -Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this -License Agreement shall be deemed to create any relationship of -agency, partnership, or joint venture between CNRI and Licensee. This -License Agreement does not grant permission to use CNRI trademarks or -trade name in a trademark sense to endorse or promote products or -services of Licensee, or any third party. - -8. By clicking on the "ACCEPT" button where indicated, or by copying, -installing or otherwise using Python 1.6.1, Licensee agrees to be -bound by the terms and conditions of this License Agreement. - - ACCEPT - - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 --------------------------------------------------- - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, -The Netherlands. All rights reserved. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE -FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst deleted file mode 100644 index d9a860f16..000000000 --- a/typing_extensions/README.rst +++ /dev/null @@ -1 +0,0 @@ -Please see `the typing_extensions repo `_ for the current version of the README file. From d8eeb1d924ae4da6da642aa5ed0b1ad43d713a06 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Tue, 26 Sep 2023 16:54:08 -0700 Subject: [PATCH 152/539] Add an anti-pitch for typing (#1477) --- docs/source/guides.rst | 1 + docs/source/typing_anti_pitch.rst | 92 +++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 docs/source/typing_anti_pitch.rst diff --git a/docs/source/guides.rst b/docs/source/guides.rst index e85c5eb02..578abd619 100644 --- a/docs/source/guides.rst +++ b/docs/source/guides.rst @@ -9,3 +9,4 @@ Type System Guides libraries writing_stubs unreachable + typing_anti_pitch diff --git a/docs/source/typing_anti_pitch.rst b/docs/source/typing_anti_pitch.rst new file mode 100644 index 000000000..1d2796e21 --- /dev/null +++ b/docs/source/typing_anti_pitch.rst @@ -0,0 +1,92 @@ +.. _typing-anti-pitch: + +Reasons to avoid static type checking +===================================== + +In the words of :pep:`484`:: + + It should also be emphasized that Python will remain a dynamically typed language, and the + authors have no desire to ever make type hints mandatory, even by convention. + +The idea that dynamism in Python is a strength of the language is reflected in the fact that +Python's type system is gradual. See :pep:`483` for details, but the long and short of this is +that you can add static types to your codebase only to the extent that you want to, and static +type checkers and other tools should be able to put up with this. + +It's also worth noting that "static type checking" encompasses a spectrum of possible degrees of +strictness. On the one hand, you can set yourself up so that your type checker does almost nothing. +On the other -- well, I love type checking, but I would quit Python if I had to enable all +possible strictness checks that type checkers offer. + +Anyway, with all that said, here's a list of possible reasons to not use static type checking +in Python:: + +* You simply don't want to. Python is a tool that is meant to serve you. Python is a big tent, + multi-paradigm language that generally allows you to do things in the way that best suits your + needs, as best determined by you. + +* Type annotations can both help and hurt readability. While type annotations can serve both + humans and machines, particularly complex annotations or changes to idioms serve machines more + than they do humans. Readability counts. + +* The cost-benefit ratio isn't good enough. Pleasing static type checkers requires a non-zero amount + of busy work. If this isn't worth the extra confidence you get, you shouldn't add static type + checking. + +* Your codebase fits in your developers' heads. Opinions vary, but people tend to agree that at + some number of developers and lines of code, static type checking confers significantly more + benefit. You don't feel like you're there yet. + +* If you maintain high test coverage, that might provide sufficient quality assurance for your + needs (acknowledging that static type checking and tests enforce different things; static type + checking usually cannot validate logic, tests can often not prove invariants of your code to + hold). + +* Your codebase is old, large and has been working fine without static type checking for years. + While Python's type system is designed to + `allow gradual adoption of static type checking `_, + the total cost of adding type annotations to a large extant codebase can be prohibitive. + +* Your application uses a particularly dynamic framework or your library does enough dynamic things + that type checking would be unlikely to help your developers and users. Migrating application + frameworks could be costly. Either a) redesigning your library in ways that static type checkers + could better understand or b) figuring out clever type annotations to twist the arms of type + checkers would take a lot of effort. + +* Your codebase has suffered at the hands of `Hyrum's Law `_ + and all possible observable behaviour is depended on. In order to avoid false positives for your + users, all your types end up being either a) complicated ``Protocol``\s that are hard to maintain, + or b) ``Any`` in which case there's not much point. (On the other hand, static type checking could + be a good solution for communicating to users what behaviour they should be allowed to rely on) + +* You're not opposed to type checking in theory, but you dislike Python type checkers in practice. + Maybe they don't understand enough of the idioms you use, maybe you'd like them to infer more + instead of relying on explicit annotations, maybe they're too slow, maybe they don't integrate + well with your editor, maybe they're too hard to configure. Whatever the reason -- it just doesn't + work for your project. + +* Type checking in Python isn't actually strict enough, powerful enough or expressive enough for + you. Python type checkers end up making various decisions out of pragmatism, or due to limited + resources, and these decisions might not be the ones for you. This might mean that typed Python + simply isn't the right language for you, or you need to find other methods to enforce the + properties you desire. + +Advice for maintainers of untyped libraries +******************************************* + +You've made the decision that adding static types isn't the right choice for your library. But +perhaps you'd still like to help your users who do use static type checking -- and maybe you have +some enthusiastic would-be contributors willing to help with this. + +One option is encourage such contributors to publish a :pep:`561` stub-only package that is +maintained separately from your main project. They could also contribute these stubs to the +`typeshed `_ project. + +Note that if you're willing to maintain the stubs, but you don't wish to have them inline and don't +want to statically type check your code, you can accomplish this by distributing type stubs inside +your package. See :ref:`libraries` for more information. See :ref:`writing_stubs` for advice on +how to help maintain type stubs. + +If more users pester you about adding static types, feel free to link them to this document. And if +you ever change your mind, make sure to check out some of the other guides in this documentation, +and ask any questions you have over at `Python's typing discussions `_. From 13ecfc7fd692e6fff696566eb0391d00ed431652 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 3 Oct 2023 19:34:11 -0700 Subject: [PATCH 153/539] Add .readthedocs.yaml (#1482) --- .readthedocs.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..ad1334293 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/requirements.txt From 3f139608cb139c04ae4330b134e2556b2cf25fcd Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 26 Nov 2023 17:21:46 -0800 Subject: [PATCH 154/539] Fix docs warnings (#1514) --- docs/index.rst | 2 +- docs/source/generics.rst | 4 +--- docs/source/protocols.rst | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index c61d13b6c..77b6ea7a2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -83,7 +83,7 @@ Linters and Formatters stubs. Type-Hint and Stub Integration ----------------------- +------------------------------ * `autotyping `_, a tool which infers simple types from their context and inserts them as inline type-hints. * `merge_pyi `_, integrates .pyi signatures as inline type-hints in Python source code. diff --git a/docs/source/generics.rst b/docs/source/generics.rst index cb82af508..5cada315d 100644 --- a/docs/source/generics.rst +++ b/docs/source/generics.rst @@ -287,8 +287,6 @@ or a deserialization method returns the actual type of self. Therefore you may need to silence the type checker inside these methods (but not at the call site), possibly by making use of the ``Any`` type or a ``# type: ignore`` comment. -For some advanced uses of self types, see :ref:`additional examples `. - Automatic self types using typing.Self ************************************** @@ -641,7 +639,7 @@ Here's how one could annotate the decorator: This still has some shortcomings. First, we need to use the unsafe :py:func:`~typing.cast` to convince type checkers that ``wrapper()`` has the same -signature as ``func``. See :ref:`casts `. +signature as ``func``. Second, the ``wrapper()`` function is not tightly type checked, although wrapper functions are typically small enough that this is not a big diff --git a/docs/source/protocols.rst b/docs/source/protocols.rst index 39d3bfbfb..13b2e9ca8 100644 --- a/docs/source/protocols.rst +++ b/docs/source/protocols.rst @@ -507,8 +507,7 @@ See also :py:class:`~typing.SupportsRound`. Async protocols ............... -These protocols can be useful in async code. See :ref:`async-and-await` -for more information. +These protocols can be useful in async code. Awaitable[T] ------------ From 0c89ed564069639d3a62ebd9790acdc182719c2f Mon Sep 17 00:00:00 2001 From: MegaIng Date: Mon, 27 Nov 2023 02:26:04 +0100 Subject: [PATCH 155/539] Fix formatting in typing_anti_pitch.rst (#1512) Co-authored-by: Jelle Zijlstra --- docs/source/typing_anti_pitch.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/typing_anti_pitch.rst b/docs/source/typing_anti_pitch.rst index 1d2796e21..fbd56767c 100644 --- a/docs/source/typing_anti_pitch.rst +++ b/docs/source/typing_anti_pitch.rst @@ -3,10 +3,10 @@ Reasons to avoid static type checking ===================================== -In the words of :pep:`484`:: +In the words of :pep:`484`: - It should also be emphasized that Python will remain a dynamically typed language, and the - authors have no desire to ever make type hints mandatory, even by convention. + It should also be emphasized that Python will remain a dynamically typed language, and + the authors have no desire to ever make type hints mandatory, even by convention. The idea that dynamism in Python is a strength of the language is reflected in the fact that Python's type system is gradual. See :pep:`483` for details, but the long and short of this is @@ -19,7 +19,7 @@ On the other -- well, I love type checking, but I would quit Python if I had to possible strictness checks that type checkers offer. Anyway, with all that said, here's a list of possible reasons to not use static type checking -in Python:: +in Python: * You simply don't want to. Python is a tool that is meant to serve you. Python is a big tent, multi-paradigm language that generally allows you to do things in the way that best suits your From 7117775f5465c6705bb753bd639e6255af380386 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 10 Dec 2023 20:27:54 -0800 Subject: [PATCH 156/539] Add the typing spec (#1517) Copied over from https://github.com/JelleZijlstra/typing-spec with some further improvements The text largely derives from the typing PEPs, though I added some material. The organization is based on an outline by @erictraut. @rchen152 and @daverball made contributions to the text. --- docs/index.rst | 8 + docs/spec/aliases.rst | 189 +++ docs/spec/annotations.rst | 245 ++++ docs/spec/callables.rst | 398 ++++++ docs/spec/class-compat.rst | 130 ++ docs/spec/concepts.rst | 95 ++ docs/spec/dataclasses.rst | 522 ++++++++ docs/spec/directives.rst | 264 ++++ docs/spec/distributing.rst | 254 ++++ docs/spec/generics.rst | 2505 +++++++++++++++++++++++++++++++++++ docs/spec/historical.rst | 291 ++++ docs/spec/index.rst | 25 + docs/spec/literal.rst | 818 ++++++++++++ docs/spec/narrowing.rst | 106 ++ docs/spec/overload.rst | 104 ++ docs/spec/protocol.rst | 647 +++++++++ docs/spec/qualifiers.rst | 278 ++++ docs/spec/requirements.txt | 2 + docs/spec/special-types.rst | 223 ++++ docs/spec/type-system.rst | 67 + docs/spec/typeddict.rst | 634 +++++++++ 21 files changed, 7805 insertions(+) create mode 100644 docs/spec/aliases.rst create mode 100644 docs/spec/annotations.rst create mode 100644 docs/spec/callables.rst create mode 100644 docs/spec/class-compat.rst create mode 100644 docs/spec/concepts.rst create mode 100644 docs/spec/dataclasses.rst create mode 100644 docs/spec/directives.rst create mode 100644 docs/spec/distributing.rst create mode 100644 docs/spec/generics.rst create mode 100644 docs/spec/historical.rst create mode 100644 docs/spec/index.rst create mode 100644 docs/spec/literal.rst create mode 100644 docs/spec/narrowing.rst create mode 100644 docs/spec/overload.rst create mode 100644 docs/spec/protocol.rst create mode 100644 docs/spec/qualifiers.rst create mode 100644 docs/spec/requirements.txt create mode 100644 docs/spec/special-types.rst create mode 100644 docs/spec/type-system.rst create mode 100644 docs/spec/typeddict.rst diff --git a/docs/index.rst b/docs/index.rst index 77b6ea7a2..7918148da 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,6 +33,14 @@ Reference the docs -- since the Python typing system is standardised via PEPs, this information should apply to most Python type checkers. +Specification +============= + +.. toctree:: + :maxdepth: 2 + + spec/index + Indices and tables ================== diff --git a/docs/spec/aliases.rst b/docs/spec/aliases.rst new file mode 100644 index 000000000..b60ed1775 --- /dev/null +++ b/docs/spec/aliases.rst @@ -0,0 +1,189 @@ +Type aliases +============ + +(See :pep:`613` for the introduction of ``TypeAlias``, and +:pep:`695` for the ``type`` statement.) + +Type aliases may be defined by simple variable assignments:: + + Url = str + + def retry(url: Url, retry_count: int) -> None: ... + +Or by using ``typing.TypeAlias``:: + + from typing import TypeAlias + + Url: TypeAlias = str + + def retry(url: Url, retry_count: int) -> None: ... + +Or by using the ``type`` statement (Python 3.12 and higher):: + + type Url = str + + def retry(url: Url, retry_count: int) -> None: ... + +Note that we recommend capitalizing alias names, since they represent +user-defined types, which (like user-defined classes) are typically +spelled that way. + +Type aliases may be as complex as type hints in annotations -- +anything that is acceptable as a type hint is acceptable in a type +alias:: + + from typing import TypeVar + from collections.abc import Iterable + + T = TypeVar('T', bound=float) + Vector = Iterable[tuple[T, T]] + + def inproduct(v: Vector[T]) -> T: + return sum(x*y for x, y in v) + def dilate(v: Vector[T], scale: T) -> Vector[T]: + return ((x * scale, y * scale) for x, y in v) + vec: Vector[float] = [] + + +This is equivalent to:: + + from typing import TypeVar + from collections.abc import Iterable + + T = TypeVar('T', bound=float) + + def inproduct(v: Iterable[tuple[T, T]]) -> T: + return sum(x*y for x, y in v) + def dilate(v: Iterable[tuple[T, T]], scale: T) -> Iterable[tuple[T, T]]: + return ((x * scale, y * scale) for x, y in v) + vec: Iterable[tuple[float, float]] = [] + +``TypeAlias`` +------------- + +The explicit alias declaration syntax with ``TypeAlias`` clearly differentiates between the three +possible kinds of assignments: typed global expressions, untyped global +expressions, and type aliases. This avoids the existence of assignments that +break type checking when an annotation is added, and avoids classifying the +nature of the assignment based on the type of the value. + +Implicit syntax (pre-existing): + +:: + + x = 1 # untyped global expression + x: int = 1 # typed global expression + + x = int # type alias + x: type[int] = int # typed global expression + + +Explicit syntax: + +:: + + x = 1 # untyped global expression + x: int = 1 # typed global expression + + x = int # untyped global expression (see note below) + x: type[int] = int # typed global expression + + x: TypeAlias = int # type alias + x: TypeAlias = "MyClass" # type alias + + +Note: The examples above illustrate implicit and explicit alias declarations in +isolation. For the sake of backwards compatibility, type checkers should support +both simultaneously, meaning an untyped global expression ``x = int`` will +still be considered a valid type alias. + +``type`` statement +------------------ + +Type aliases may also be defined using the ``type`` statement (Python 3.12 and +higher). + +The ``type`` statement allows the creation of explicitly generic +type aliases:: + + type ListOrSet[T] = list[T] | set[T] + +Type parameters declared as part of a generic type alias are valid only +when evaluating the right-hand side of the type alias. + +As with ``typing.TypeAlias``, type checkers should restrict the right-hand +expression to expression forms that are allowed within type annotations. +The use of more complex expression forms (call expressions, ternary operators, +arithmetic operators, comparison operators, etc.) should be flagged as an +error. + +Type alias expressions are not allowed to use traditional type variables (i.e. +those allocated with an explicit ``TypeVar`` constructor call). Type checkers +should generate an error in this case. + +:: + + T = TypeVar("T") + type MyList = list[T] # Type checker error: traditional type variable usage + +``NewType`` +----------- + +There are also situations where a programmer might want to avoid logical +errors by creating simple classes. For example:: + + class UserId(int): + pass + + def get_by_user_id(user_id: UserId): + ... + +However, this approach introduces a runtime overhead. To avoid this, +``typing.py`` provides a helper function ``NewType`` that creates +simple unique types with almost zero runtime overhead. For a static type +checker ``Derived = NewType('Derived', Base)`` is roughly equivalent +to a definition:: + + class Derived(Base): + def __init__(self, _x: Base) -> None: + ... + +While at runtime, ``NewType('Derived', Base)`` returns a dummy function +that simply returns its argument. Type checkers require explicit casts +from ``int`` where ``UserId`` is expected, while implicitly casting +from ``UserId`` where ``int`` is expected. Examples:: + + UserId = NewType('UserId', int) + + def name_by_id(user_id: UserId) -> str: + ... + + UserId('user') # Fails type check + + name_by_id(42) # Fails type check + name_by_id(UserId(42)) # OK + + num = UserId(5) + 1 # type: int + +``NewType`` accepts exactly two arguments: a name for the new unique type, +and a base class. The latter should be a proper class (i.e., +not a type construct like ``Union``, etc.), or another unique type created +by calling ``NewType``. The function returned by ``NewType`` +accepts only one argument; this is equivalent to supporting only one +constructor accepting an instance of the base class (see above). Example:: + + class PacketId: + def __init__(self, major: int, minor: int) -> None: + self._major = major + self._minor = minor + + TcpPacketId = NewType('TcpPacketId', PacketId) + + packet = PacketId(100, 100) + tcp_packet = TcpPacketId(packet) # OK + + tcp_packet = TcpPacketId(127, 0) # Fails in type checker and at runtime + +Both ``isinstance`` and ``issubclass``, as well as subclassing will fail +for ``NewType('Derived', Base)`` since function objects don't support +these operations. diff --git a/docs/spec/annotations.rst b/docs/spec/annotations.rst new file mode 100644 index 000000000..17eae5b9f --- /dev/null +++ b/docs/spec/annotations.rst @@ -0,0 +1,245 @@ +Type annotations +================ + +The meaning of annotations +-------------------------- + +The type system leverages :pep:`3107`-style annotations with a number of +extensions described in sections below. In its basic form, type +hinting is used by filling function annotation slots with classes:: + + def greeting(name: str) -> str: + return 'Hello ' + name + +This states that the expected type of the ``name`` argument is +``str``. Analogically, the expected return type is ``str``. + +Expressions whose type is a subtype of a specific argument type are +also accepted for that argument. + +Any function without annotations should be treated as having the most +general type possible, or ignored, by any type checker. + +It is recommended but not required that checked functions have +annotations for all arguments and the return type. For a checked +function, the default annotation for arguments and for the return type +is ``Any``. An exception is the first argument of instance and +class methods. If it is not annotated, then it is assumed to have the +type of the containing class for instance methods, and a type object +type corresponding to the containing class object for class methods. +For example, in class ``A`` the first argument of an instance method +has the implicit type ``A``. In a class method, the precise type of +the first argument cannot be represented using the available type +notation. + +(Note that the return type of ``__init__`` ought to be annotated with +``-> None``. The reason for this is subtle. If ``__init__`` assumed +a return annotation of ``-> None``, would that mean that an +argument-less, un-annotated ``__init__`` method should still be +type-checked? Rather than leaving this ambiguous or introducing an +exception to the exception, we simply say that ``__init__`` ought to +have a return annotation; the default behavior is thus the same as for +other methods.) + +A type checker is expected to check the body of a checked function for +consistency with the given annotations. The annotations may also be +used to check correctness of calls appearing in other checked functions. + +Type checkers are expected to attempt to infer as much information as +necessary. The minimum requirement is to handle the builtin +decorators ``@property``, ``@staticmethod`` and ``@classmethod``. + +.. _valid-types: + +Valid type expression forms +--------------------------- + +Type hints may be built-in classes (including those defined in +standard library or third-party extension modules), abstract base +classes, types available in the ``types`` module, and user-defined +classes (including those defined in the standard library or +third-party modules). + +While annotations are normally the best format for type hints, +there are times when it is more appropriate to represent them +by a special comment, or in a separately distributed stub +file. (See below for examples.) + +Annotations must be valid expressions that evaluate without raising +exceptions at the time the function is defined (but see below for +forward references). + +Annotations should be kept simple or static analysis tools may not be +able to interpret the values. For example, dynamically computed types +are unlikely to be understood. (This is an +intentionally somewhat vague requirement; specific inclusions and +exclusions may be added in the future as warranted by the discussion.) + +In addition to the above, the following special constructs defined +below may be used: ``None``, ``Any``, ``Union``, ``Tuple``, +``Callable``, all ABCs and stand-ins for concrete classes exported +from ``typing`` (e.g. ``Sequence`` and ``Dict``), type variables, and +type aliases. + +Forward references +------------------ + +When a type hint contains names that have not been defined yet, that +definition may be expressed as a string literal, to be resolved later. + +A situation where this occurs commonly is the definition of a +container class, where the class being defined occurs in the signature +of some of the methods. For example, the following code (the start of +a simple binary tree implementation) does not work:: + + class Tree: + def __init__(self, left: Tree, right: Tree): + self.left = left + self.right = right + +To address this, we write:: + + class Tree: + def __init__(self, left: 'Tree', right: 'Tree'): + self.left = left + self.right = right + +The string literal should contain a valid Python expression (i.e., +``compile(lit, '', 'eval')`` should be a valid code object) and it +should evaluate without errors once the module has been fully loaded. +The local and global namespace in which it is evaluated should be the +same namespaces in which default arguments to the same function would +be evaluated. + +Moreover, the expression should be parseable as a valid type hint, i.e., +it is constrained by the rules from the section on :ref:`valid-types`. + +It is allowable to use string literals as *part* of a type hint, for +example:: + + class Tree: + ... + def leaves(self) -> list['Tree']: + ... + +A common use for forward references is when e.g. Django models are +needed in the signatures. Typically, each model is in a separate +file, and has methods taking arguments whose type involves other models. +Because of the way circular imports work in Python, it is often not +possible to import all the needed models directly:: + + # File models/a.py + from models.b import B + class A(Model): + def foo(self, b: B): ... + + # File models/b.py + from models.a import A + class B(Model): + def bar(self, a: A): ... + + # File main.py + from models.a import A + from models.b import B + +Assuming main is imported first, this will fail with an ImportError at +the line ``from models.a import A`` in models/b.py, which is being +imported from models/a.py before a has defined class A. The solution +is to switch to module-only imports and reference the models by their +_module_._class_ name:: + + # File models/a.py + from models import b + class A(Model): + def foo(self, b: 'b.B'): ... + + # File models/b.py + from models import a + class B(Model): + def bar(self, a: 'a.A'): ... + + # File main.py + from models.a import A + from models.b import B + +Annotating generator functions and coroutines +--------------------------------------------- + +The return type of generator functions can be annotated by +the generic type ``Generator[yield_type, send_type, +return_type]`` provided by ``typing.py`` module:: + + def echo_round() -> Generator[int, float, str]: + res = yield + while res: + res = yield round(res) + return 'OK' + +Coroutines introduced in :pep:`492` are annotated with the same syntax as +ordinary functions. However, the return type annotation corresponds to the +type of ``await`` expression, not to the coroutine type:: + + async def spam(ignored: int) -> str: + return 'spam' + + async def foo() -> None: + bar = await spam(42) # type is str + +The generic ABC ``collections.abc.Coroutine`` can be used +to specify awaitables that also support +``send()`` and ``throw()`` methods. The variance and order of type variables +correspond to those of ``Generator``, namely ``Coroutine[T_co, T_contra, V_co]``, +for example:: + + from collections.abc import Coroutine + c: Coroutine[list[str], str, int] + ... + x = c.send('hi') # type is list[str] + async def bar() -> None: + x = await c # type is int + +The generic ABCs ``Awaitable``, +``AsyncIterable``, and ``AsyncIterator`` can be used for situations where more precise +types cannot be specified:: + + def op() -> collections.abc.Awaitable[str]: + if cond: + return spam(42) + else: + return asyncio.Future(...) + +Annotating instance and class methods +------------------------------------- + +In most cases the first argument of class and instance methods +does not need to be annotated, and it is assumed to have the +type of the containing class for instance methods, and a type object +type corresponding to the containing class object for class methods. +In addition, the first argument in an instance method can be annotated +with a type variable. In this case the return type may use the same +type variable, thus making that method a generic function. For example:: + + T = TypeVar('T', bound='Copyable') + class Copyable: + def copy(self: T) -> T: + # return a copy of self + + class C(Copyable): ... + c = C() + c2 = c.copy() # type here should be C + +The same applies to class methods using ``type[]`` in an annotation +of the first argument:: + + T = TypeVar('T', bound='C') + class C: + @classmethod + def factory(cls: type[T]) -> T: + # make a new instance of cls + + class D(C): ... + d = D.factory() # type here should be D + +Note that some type checkers may apply restrictions on this use, such as +requiring an appropriate upper bound for the type variable used +(see examples). diff --git a/docs/spec/callables.rst b/docs/spec/callables.rst new file mode 100644 index 000000000..072c4a0fc --- /dev/null +++ b/docs/spec/callables.rst @@ -0,0 +1,398 @@ +Callables +========= + +Argument defaults +----------------- + +It may be useful to declare an argument as having a default +without specifying the actual default value. For example:: + + def foo(x: AnyStr, y: AnyStr = ...) -> AnyStr: ... + +What should the default value look like? Any of the options ``""``, +``b""`` or ``None`` fails to satisfy the type constraint. + +In such cases the default value may be specified as a literal +ellipsis, i.e. the above example is literally what you would write. + +Annotating ``*args`` and ``**kwargs`` +------------------------------------- + +Type annotation on variadic positional arguments +(``*args``) and keyword arguments (``**kwargs``) refer to +the types of individual arguments, not to the type of the +entire collection (except if ``Unpack`` is used). + +Therefore, the definition:: + + def foo(*args: str, **kwds: int): ... + +is acceptable and it means that the function accepts an +arbitrary number of positional arguments of type ``str`` +and an arbitrary number of keyword arguments of type ``int``. +For example, all of the following +represent function calls with valid types of arguments:: + + foo('a', 'b', 'c') + foo(x=1, y=2) + foo('', z=0) + +In the body of function ``foo``, the type of variable ``args`` is +deduced as ``tuple[str, ...]`` and the type of variable ``kwds`` +is ``dict[str, int]``. + +.. _unpack-kwargs: + +``Unpack`` for keyword arguments +-------------------------------- + +``typing.Unpack`` has two use cases in the type system: + +* As introduced by :pep:`646`, a backward-compatible form for certain operations + involving variadic generics. See the section on ``TypeVarTuple`` for details. +* As introduced by :pep:`692`, a way to annotate the ``**kwargs`` of a function. + +This second usage is described in this section. The following example:: + + from typing import TypedDict, Unpack + + class Movie(TypedDict): + name: str + year: int + + def foo(**kwargs: Unpack[Movie]) -> None: ... + +means that the ``**kwargs`` comprise two keyword arguments specified by +``Movie`` (i.e. a ``name`` keyword of type ``str`` and a ``year`` keyword of +type ``int``). This indicates that the function should be called as follows:: + + kwargs: Movie = {"name": "Life of Brian", "year": 1979} + + foo(**kwargs) # OK! + foo(name="The Meaning of Life", year=1983) # OK! + +When ``Unpack`` is used, type checkers treat ``kwargs`` inside the +function body as a ``TypedDict``:: + + def foo(**kwargs: Unpack[Movie]) -> None: + assert_type(kwargs, Movie) # OK! + + +Using the new annotation will not have any runtime effect - it should only be +taken into account by type checkers. Any mention of errors in the following +sections relates to type checker errors. + +Function calls with standard dictionaries +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Passing a dictionary of type ``dict[str, object]`` as a ``**kwargs`` argument +to a function that has ``**kwargs`` annotated with ``Unpack`` must generate a +type checker error. On the other hand, the behaviour for functions using +standard, untyped dictionaries can depend on the type checker. For example:: + + def foo(**kwargs: Unpack[Movie]) -> None: ... + + movie: dict[str, object] = {"name": "Life of Brian", "year": 1979} + foo(**movie) # WRONG! Movie is of type dict[str, object] + + typed_movie: Movie = {"name": "The Meaning of Life", "year": 1983} + foo(**typed_movie) # OK! + + another_movie = {"name": "Life of Brian", "year": 1979} + foo(**another_movie) # Depends on the type checker. + +Keyword collisions +^^^^^^^^^^^^^^^^^^ + +A ``TypedDict`` that is used to type ``**kwargs`` could potentially contain +keys that are already defined in the function's signature. If the duplicate +name is a standard parameter, an error should be reported by type checkers. +If the duplicate name is a positional-only parameter, no errors should be +generated. For example:: + + def foo(name, **kwargs: Unpack[Movie]) -> None: ... # WRONG! "name" will + # always bind to the + # first parameter. + + def foo(name, /, **kwargs: Unpack[Movie]) -> None: ... # OK! "name" is a + # positional-only parameter, + # so **kwargs can contain + # a "name" keyword. + +Required and non-required keys +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default all keys in a ``TypedDict`` are required. This behaviour can be +overridden by setting the dictionary's ``total`` parameter as ``False``. +Moreover, :pep:`655` introduced new type qualifiers - ``typing.Required`` and +``typing.NotRequired`` - that enable specifying whether a particular key is +required or not:: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + +When using a ``TypedDict`` to type ``**kwargs`` all of the required and +non-required keys should correspond to required and non-required function +keyword parameters. Therefore, if a required key is not supported by the +caller, then an error must be reported by type checkers. + +Assignment +^^^^^^^^^^ + +Assignments of a function typed with ``**kwargs: Unpack[Movie]`` and +another callable type should pass type checking only if they are compatible. +This can happen for the scenarios described below. + +Source and destination contain ``**kwargs`` +""""""""""""""""""""""""""""""""""""""""""" + +Both destination and source functions have a ``**kwargs: Unpack[TypedDict]`` +parameter and the destination function's ``TypedDict`` is assignable to the +source function's ``TypedDict`` and the rest of the parameters are +compatible:: + + class Animal(TypedDict): + name: str + + class Dog(Animal): + breed: str + + def accept_animal(**kwargs: Unpack[Animal]): ... + def accept_dog(**kwargs: Unpack[Dog]): ... + + accept_dog = accept_animal # OK! Expression of type Dog can be + # assigned to a variable of type Animal. + + accept_animal = accept_dog # WRONG! Expression of type Animal + # cannot be assigned to a variable of type Dog. + +.. _PEP 692 assignment dest no kwargs: + +Source contains ``**kwargs`` and destination doesn't +"""""""""""""""""""""""""""""""""""""""""""""""""""" + +The destination callable doesn't contain ``**kwargs``, the source callable +contains ``**kwargs: Unpack[TypedDict]`` and the destination function's keyword +arguments are assignable to the corresponding keys in source function's +``TypedDict``. Moreover, not required keys should correspond to optional +function arguments, whereas required keys should correspond to required +function arguments. Again, the rest of the parameters have to be compatible. +Continuing the previous example:: + + class Example(TypedDict): + animal: Animal + string: str + number: NotRequired[int] + + def src(**kwargs: Unpack[Example]): ... + def dest(*, animal: Dog, string: str, number: int = ...): ... + + dest = src # OK! + +It is worth pointing out that the destination function's parameters that are to +be compatible with the keys and values from the ``TypedDict`` must be keyword +only:: + + def dest(dog: Dog, string: str, number: int = ...): ... + + dog: Dog = {"name": "Daisy", "breed": "labrador"} + + dest(dog, "some string") # OK! + + dest = src # Type checker error! + dest(dog, "some string") # The same call fails at + # runtime now because 'src' expects + # keyword arguments. + +The reverse situation where the destination callable contains +``**kwargs: Unpack[TypedDict]`` and the source callable doesn't contain +``**kwargs`` should be disallowed. This is because, we cannot be sure that +additional keyword arguments are not being passed in when an instance of a +subclass had been assigned to a variable with a base class type and then +unpacked in the destination callable invocation:: + + def dest(**kwargs: Unpack[Animal]): ... + def src(name: str): ... + + dog: Dog = {"name": "Daisy", "breed": "Labrador"} + animal: Animal = dog + + dest = src # WRONG! + dest(**animal) # Fails at runtime. + +Similar situation can happen even without inheritance as compatibility +between ``TypedDict``\s is based on structural subtyping. + +Source contains untyped ``**kwargs`` +"""""""""""""""""""""""""""""""""""" + +The destination callable contains ``**kwargs: Unpack[TypedDict]`` and the +source callable contains untyped ``**kwargs``:: + + def src(**kwargs): ... + def dest(**kwargs: Unpack[Movie]): ... + + dest = src # OK! + +Source contains traditionally typed ``**kwargs: T`` +""""""""""""""""""""""""""""""""""""""""""""""""""" + +The destination callable contains ``**kwargs: Unpack[TypedDict]``, the source +callable contains traditionally typed ``**kwargs: T`` and each of the +destination function ``TypedDict``'s fields is assignable to a variable of +type ``T``:: + + class Vehicle: + ... + + class Car(Vehicle): + ... + + class Motorcycle(Vehicle): + ... + + class Vehicles(TypedDict): + car: Car + moto: Motorcycle + + def dest(**kwargs: Unpack[Vehicles]): ... + def src(**kwargs: Vehicle): ... + + dest = src # OK! + +On the other hand, if the destination callable contains either untyped or +traditionally typed ``**kwargs: T`` and the source callable is typed using +``**kwargs: Unpack[TypedDict]`` then an error should be generated, because +traditionally typed ``**kwargs`` aren't checked for keyword names. + +To summarize, function parameters should behave contravariantly and function +return types should behave covariantly. + +Passing kwargs inside a function to another function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +`A previous point `_ +mentions the problem of possibly passing additional keyword arguments by +assigning a subclass instance to a variable that has a base class type. Let's +consider the following example:: + + class Animal(TypedDict): + name: str + + class Dog(Animal): + breed: str + + def takes_name(name: str): ... + + dog: Dog = {"name": "Daisy", "breed": "Labrador"} + animal: Animal = dog + + def foo(**kwargs: Unpack[Animal]): + print(kwargs["name"].capitalize()) + + def bar(**kwargs: Unpack[Animal]): + takes_name(**kwargs) + + def baz(animal: Animal): + takes_name(**animal) + + def spam(**kwargs: Unpack[Animal]): + baz(kwargs) + + foo(**animal) # OK! foo only expects and uses keywords of 'Animal'. + + bar(**animal) # WRONG! This will fail at runtime because 'breed' keyword + # will be passed to 'takes_name' as well. + + spam(**animal) # WRONG! Again, 'breed' keyword will be eventually passed + # to 'takes_name'. + +In the example above, the call to ``foo`` will not cause any issues at +runtime. Even though ``foo`` expects ``kwargs`` of type ``Animal`` it doesn't +matter if it receives additional arguments because it only reads and uses what +it needs completely ignoring any additional values. + +The calls to ``bar`` and ``spam`` will fail because an unexpected keyword +argument will be passed to the ``takes_name`` function. + +Therefore, ``kwargs`` hinted with an unpacked ``TypedDict`` can only be passed +to another function if the function to which unpacked kwargs are being passed +to has ``**kwargs`` in its signature as well, because then additional keywords +would not cause errors at runtime during function invocation. Otherwise, the +type checker should generate an error. + +In cases similar to the ``bar`` function above the problem could be worked +around by explicitly dereferencing desired fields and using them as arguments +to perform the function call:: + + def bar(**kwargs: Unpack[Animal]): + name = kwargs["name"] + takes_name(name) + +Using ``Unpack`` with types other than ``TypedDict`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``TypedDict`` is the only permitted heterogeneous type for typing ``**kwargs``. +Therefore, in the context of typing ``**kwargs``, using ``Unpack`` with types +other than ``TypedDict`` should not be allowed and type checkers should +generate errors in such cases. + +Callable +-------- + +Frameworks expecting callback functions of specific signatures might be +type hinted using ``Callable[[Arg1Type, Arg2Type], ReturnType]``. +Examples:: + + from collections.abc import Callable + + def feeder(get_next_item: Callable[[], str]) -> None: + # Body + + def async_query(on_success: Callable[[int], None], + on_error: Callable[[int, Exception], None]) -> None: + # Body + +It is possible to declare the return type of a callable without +specifying the call signature by substituting a literal ellipsis +(three dots) for the list of arguments:: + + def partial(func: Callable[..., str], *args) -> Callable[..., str]: + # Body + +Note that there are no square brackets around the ellipsis. The +arguments of the callback are completely unconstrained in this case +(and keyword arguments are acceptable). + +Since using callbacks with keyword arguments is not perceived as a +common use case, there is currently no support for specifying keyword +arguments with ``Callable``. Similarly, ``Callable`` does not support +specifying callback signatures with a variable number of arguments of a +specific type. For these use cases, see the section on +`Callback protocols`_. + +Callback protocols +------------------ + +Protocols can be used to define flexible callback types that are hard +(or even impossible) to express using the ``Callable[...]`` syntax +specified by :pep:`484`, such as variadic, overloaded, and complex generic +callbacks. They can be defined as protocols with a ``__call__`` member:: + + from typing import Protocol + + class Combiner(Protocol): + def __call__(self, *vals: bytes, + maxlen: int | None = None) -> list[bytes]: ... + + def good_cb(*vals: bytes, maxlen: int | None = None) -> list[bytes]: + ... + def bad_cb(*vals: bytes, maxitems: int | None) -> list[bytes]: + ... + + comb: Combiner = good_cb # OK + comb = bad_cb # Error! Argument 2 has incompatible type because of + # different name and kind in the callback + +Callback protocols and ``Callable[...]`` types can be used interchangeably. diff --git a/docs/spec/class-compat.rst b/docs/spec/class-compat.rst new file mode 100644 index 000000000..6dcf4765e --- /dev/null +++ b/docs/spec/class-compat.rst @@ -0,0 +1,130 @@ +Class type compatibility +======================== + +``ClassVar`` +------------ + +(Originally specified in :pep:`526`.) + +A covariant type ``ClassVar[T_co]`` exists in the ``typing`` +module. It accepts only a single argument that should be a valid type, +and is used to annotate class variables that should not be set on class +instances. This restriction is ensured by static checkers, +but not at runtime. + +Type annotations can be used to annotate class and instance variables +in class bodies and methods. In particular, the value-less notation ``a: int`` +allows one to annotate instance variables that should be initialized +in ``__init__`` or ``__new__``. The syntax is as follows:: + + class BasicStarship: + captain: str = 'Picard' # instance variable with default + damage: int # instance variable without default + stats: ClassVar[dict[str, int]] = {} # class variable + +Here ``ClassVar`` is a special class defined by the typing module that +indicates to the static type checker that this variable should not be +set on instances. + +Note that a ``ClassVar`` parameter cannot include any type variables, regardless +of the level of nesting: ``ClassVar[T]`` and ``ClassVar[list[set[T]]]`` are +both invalid if ``T`` is a type variable. + +This could be illustrated with a more detailed example. In this class:: + + class Starship: + captain = 'Picard' + stats = {} + + def __init__(self, damage, captain=None): + self.damage = damage + if captain: + self.captain = captain # Else keep the default + + def hit(self): + Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1 + +``stats`` is intended to be a class variable (keeping track of many different +per-game statistics), while ``captain`` is an instance variable with a default +value set in the class. This difference might not be seen by a type +checker: both get initialized in the class, but ``captain`` serves only +as a convenient default value for the instance variable, while ``stats`` +is truly a class variable -- it is intended to be shared by all instances. + +Since both variables happen to be initialized at the class level, it is +useful to distinguish them by marking class variables as annotated with +types wrapped in ``ClassVar[...]``. In this way a type checker may flag +accidental assignments to attributes with the same name on instances. + +For example, annotating the discussed class:: + + class Starship: + captain: str = 'Picard' + damage: int + stats: ClassVar[dict[str, int]] = {} + + def __init__(self, damage: int, captain: str = None): + self.damage = damage + if captain: + self.captain = captain # Else keep the default + + def hit(self): + Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1 + + enterprise_d = Starship(3000) + enterprise_d.stats = {} # Flagged as error by a type checker + Starship.stats = {} # This is OK + +As a matter of convenience (and convention), instance variables can be +annotated in ``__init__`` or other methods, rather than in the class:: + + from typing import Generic, TypeVar + T = TypeVar('T') + + class Box(Generic[T]): + def __init__(self, content): + self.content: T = content + +``@override`` +------------- + +(Originally specified by :pep:`698`.) + +When type checkers encounter a method decorated with ``@typing.override`` they +should treat it as a type error unless that method is overriding a compatible +method or attribute in some ancestor class. + + +.. code-block:: python + + from typing import override + + class Parent: + def foo(self) -> int: + return 1 + + def bar(self, x: str) -> str: + return x + + class Child(Parent): + @override + def foo(self) -> int: + return 2 + + @override + def baz() -> int: # Type check error: no matching signature in ancestor + return 1 + + +The ``@override`` decorator should be permitted anywhere a type checker +considers a method to be a valid override, which typically includes not only +normal methods but also ``@property``, ``@staticmethod``, and ``@classmethod``. + + +Strict Enforcement Per-Project +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We believe that ``@override`` is most useful if checkers also allow developers +to opt into a strict mode where methods that override a parent class are +required to use the decorator. Strict enforcement should be opt-in for backward +compatibility. diff --git a/docs/spec/concepts.rst b/docs/spec/concepts.rst new file mode 100644 index 000000000..832c8c319 --- /dev/null +++ b/docs/spec/concepts.rst @@ -0,0 +1,95 @@ +Type system concepts +==================== + +Union types +----------- + +Since accepting a small, limited set of expected types for a single +argument is common, the type system supports union types, created with the +``|`` operator. +Example:: + + def handle_employees(e: Employee | Sequence[Employee]) -> None: + if isinstance(e, Employee): + e = [e] + ... + +A type factored by ``T1 | T2 | ...`` is a supertype +of all types ``T1``, ``T2``, etc., so that a value that +is a member of one of these types is acceptable for an argument +annotated by ``T1 | T2 | ...``. + +One common case of union types are *optional* types. By default, +``None`` is an invalid value for any type, unless a default value of +``None`` has been provided in the function definition. Examples:: + + def handle_employee(e: Employee | None) -> None: ... + +A past version of this specification allowed type checkers to assume an optional +type when the default value is ``None``, as in this code:: + + def handle_employee(e: Employee = None): ... + +This would have been treated as equivalent to:: + + def handle_employee(e: Employee | None = None) -> None: ... + +This is no longer the recommended behavior. Type checkers should move +towards requiring the optional type to be made explicit. + +Support for singleton types in unions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A singleton instance is frequently used to mark some special condition, +in particular in situations where ``None`` is also a valid value +for a variable. Example:: + + _empty = object() + + def func(x=_empty): + if x is _empty: # default argument value + return 0 + elif x is None: # argument was provided and it's None + return 1 + else: + return x * 2 + +To allow precise typing in such situations, the user should use +a union type in conjunction with the ``enum.Enum`` class provided +by the standard library, so that type errors can be caught statically:: + + from enum import Enum + + class Empty(Enum): + token = 0 + _empty = Empty.token + + def func(x: int | None | Empty = _empty) -> int: + + boom = x * 42 # This fails type check + + if x is _empty: + return 0 + elif x is None: + return 1 + else: # At this point typechecker knows that x can only have type int + return x * 2 + +Since the subclasses of ``Enum`` cannot be further subclassed, +the type of variable ``x`` can be statically inferred in all branches +of the above example. The same approach is applicable if more than one +singleton object is needed: one can use an enumeration that has more than +one value:: + + class Reason(Enum): + timeout = 1 + error = 2 + + def process(response: str | Reason = '') -> str: + if response is Reason.timeout: + return 'TIMEOUT' + elif response is Reason.error: + return 'ERROR' + else: + # response can be only str, all other possible values exhausted + return 'PROCESSED: ' + response diff --git a/docs/spec/dataclasses.rst b/docs/spec/dataclasses.rst new file mode 100644 index 000000000..b1fc2a4c6 --- /dev/null +++ b/docs/spec/dataclasses.rst @@ -0,0 +1,522 @@ +Dataclasses +=========== + +Type checkers should support dataclasses created through +the :py:mod:`dataclasses` module. In addition, the type system +contains a mechanism to make third-party classes behave like +standard dataclasses. + +The ``dataclass_transform`` decorator +------------------------------------- + +(Originally specified in :pep:`681`.) + +Specification +^^^^^^^^^^^^^ + +This specification describes a decorator function in +the ``typing`` module named ``dataclass_transform``. This decorator +can be applied to either a function that is itself a decorator, +a class, or a metaclass. The presence of +``dataclass_transform`` tells a static type checker that the decorated +function, class, or metaclass performs runtime "magic" that transforms +a class, endowing it with dataclass-like behaviors. + +If ``dataclass_transform`` is applied to a function, using the decorated +function as a decorator is assumed to apply dataclass-like semantics. +If the function has overloads, the ``dataclass_transform`` decorator can +be applied to the implementation of the function or any one, but not more +than one, of the overloads. When applied to an overload, the +``dataclass_transform`` decorator still impacts all usage of the +function. + +If ``dataclass_transform`` is applied to a class, dataclass-like +semantics will be assumed for any class that directly or indirectly +derives from the decorated class or uses the decorated class as a +metaclass. Attributes on the decorated class and its base classes +are not considered to be fields. + +Examples of each approach are shown in the following sections. Each +example creates a ``CustomerModel`` class with dataclass-like semantics. +The implementation of the decorated objects is omitted for brevity, +but we assume that they modify classes in the following ways: + +* They synthesize an ``__init__`` method using data fields declared + within the class and its parent classes. +* They synthesize ``__eq__`` and ``__ne__`` methods. + +Type checkers will recognize that the +``CustomerModel`` class can be instantiated using the synthesized +``__init__`` method: + +.. code-block:: python + + # Using positional arguments + c1 = CustomerModel(327, "John Smith") + + # Using keyword arguments + c2 = CustomerModel(id=327, name="John Smith") + + # These calls will generate runtime errors and should be flagged as + # errors by a static type checker. + c3 = CustomerModel() + c4 = CustomerModel(327, first_name="John") + c5 = CustomerModel(327, "John Smith", 0) + +Decorator function example +"""""""""""""""""""""""""" + +.. code-block:: python + + _T = TypeVar("_T") + + # The ``create_model`` decorator is defined by a library. + # This could be in a type stub or inline. + @typing.dataclass_transform() + def create_model(cls: Type[_T]) -> Type[_T]: + cls.__init__ = ... + cls.__eq__ = ... + cls.__ne__ = ... + return cls + + # The ``create_model`` decorator can now be used to create new model + # classes, like this: + @create_model + class CustomerModel: + id: int + name: str + +Class example +""""""""""""" + +.. code-block:: python + + # The ``ModelBase`` class is defined by a library. This could be in + # a type stub or inline. + @typing.dataclass_transform() + class ModelBase: ... + + # The ``ModelBase`` class can now be used to create new model + # subclasses, like this: + class CustomerModel(ModelBase): + id: int + name: str + +Metaclass example +""""""""""""""""" + +.. code-block:: python + + # The ``ModelMeta`` metaclass and ``ModelBase`` class are defined by + # a library. This could be in a type stub or inline. + @typing.dataclass_transform() + class ModelMeta(type): ... + + class ModelBase(metaclass=ModelMeta): ... + + # The ``ModelBase`` class can now be used to create new model + # subclasses, like this: + class CustomerModel(ModelBase): + id: int + name: str + +Decorator function and class/metaclass parameters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A decorator function, class, or metaclass that provides dataclass-like +functionality may accept parameters that modify certain behaviors. +This specification defines the following parameters that static type +checkers must honor if they are used by a dataclass transform. Each of +these parameters accepts a bool argument, and it must be possible for +the bool value (``True`` or ``False``) to be statically evaluated. + +* ``eq``, ``order``, ``frozen``, ``init`` and ``unsafe_hash`` are parameters + supported in the stdlib dataclass, with meanings defined in + :pep:`PEP 557 <557#id7>`. +* ``kw_only``, ``match_args`` and ``slots`` are parameters supported + in the stdlib dataclass, first introduced in Python 3.10. + +``dataclass_transform`` parameters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Parameters to ``dataclass_transform`` allow for some basic +customization of default behaviors: + +.. code-block:: python + + _T = TypeVar("_T") + + def dataclass_transform( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + frozen_default: bool = False, + field_specifiers: tuple[type | Callable[..., Any], ...] = (), + **kwargs: Any, + ) -> Callable[[_T], _T]: ... + +* ``eq_default`` indicates whether the ``eq`` parameter is assumed to + be True or False if it is omitted by the caller. If not specified, + ``eq_default`` will default to True (the default assumption for + dataclass). +* ``order_default`` indicates whether the ``order`` parameter is + assumed to be True or False if it is omitted by the caller. If not + specified, ``order_default`` will default to False (the default + assumption for dataclass). +* ``kw_only_default`` indicates whether the ``kw_only`` parameter is + assumed to be True or False if it is omitted by the caller. If not + specified, ``kw_only_default`` will default to False (the default + assumption for dataclass). +* ``frozen_default`` indicates whether the ``frozen`` parameter is + assumed to be True or False if it is omitted by the caller. If not + specified, ``frozen_default`` will default to False (the default + assumption for dataclass). +* ``field_specifiers`` specifies a static list of supported classes + that describe fields. Some libraries also supply functions to + allocate instances of field specifiers, and those functions may + also be specified in this tuple. If not specified, + ``field_specifiers`` will default to an empty tuple (no field + specifiers supported). The standard dataclass behavior supports + only one type of field specifier called ``Field`` plus a helper + function (``field``) that instantiates this class, so if we were + describing the stdlib dataclass behavior, we would provide the + tuple argument ``(dataclasses.Field, dataclasses.field)``. +* ``kwargs`` allows arbitrary additional keyword args to be passed to + ``dataclass_transform``. This gives type checkers the freedom to + support experimental parameters without needing to wait for changes + in ``typing.py``. Type checkers should report errors for any + unrecognized parameters. + +In the future, we may add additional parameters to +``dataclass_transform`` as needed to support common behaviors in user +code. These additions will be made after reaching consensus on +typing-sig rather than via additional PEPs. + +The following sections provide additional examples showing how these +parameters are used. + +Decorator function example +"""""""""""""""""""""""""" + +.. code-block:: python + + # Indicate that the ``create_model`` function assumes keyword-only + # parameters for the synthesized ``__init__`` method unless it is + # invoked with ``kw_only=False``. It always synthesizes order-related + # methods and provides no way to override this behavior. + @typing.dataclass_transform(kw_only_default=True, order_default=True) + def create_model( + *, + frozen: bool = False, + kw_only: bool = True, + ) -> Callable[[Type[_T]], Type[_T]]: ... + + # Example of how this decorator would be used by code that imports + # from this library: + @create_model(frozen=True, kw_only=False) + class CustomerModel: + id: int + name: str + +Class example +""""""""""""" + +.. code-block:: python + + # Indicate that classes that derive from this class default to + # synthesizing comparison methods. + @typing.dataclass_transform(eq_default=True, order_default=True) + class ModelBase: + def __init_subclass__( + cls, + *, + init: bool = True, + frozen: bool = False, + eq: bool = True, + order: bool = True, + ): + ... + + # Example of how this class would be used by code that imports + # from this library: + class CustomerModel( + ModelBase, + init=False, + frozen=True, + eq=False, + order=False, + ): + id: int + name: str + +Metaclass example +""""""""""""""""" + +.. code-block:: python + + # Indicate that classes that use this metaclass default to + # synthesizing comparison methods. + @typing.dataclass_transform(eq_default=True, order_default=True) + class ModelMeta(type): + def __new__( + cls, + name, + bases, + namespace, + *, + init: bool = True, + frozen: bool = False, + eq: bool = True, + order: bool = True, + ): + ... + + class ModelBase(metaclass=ModelMeta): + ... + + # Example of how this class would be used by code that imports + # from this library: + class CustomerModel( + ModelBase, + init=False, + frozen=True, + eq=False, + order=False, + ): + id: int + name: str + + +Field specifiers +^^^^^^^^^^^^^^^^^ + +Most libraries that support dataclass-like semantics provide one or +more "field specifier" types that allow a class definition to provide +additional metadata about each field in the class. This metadata can +describe, for example, default values, or indicate whether the field +should be included in the synthesized ``__init__`` method. + +Field specifiers can be omitted in cases where additional metadata is +not required: + +.. code-block:: python + + @dataclass + class Employee: + # Field with no specifier + name: str + + # Field that uses field specifier class instance + age: Optional[int] = field(default=None, init=False) + + # Field with type annotation and simple initializer to + # describe default value + is_paid_hourly: bool = True + + # Not a field (but rather a class variable) because type + # annotation is not provided. + office_number = "unassigned" + + +Field specifier parameters +"""""""""""""""""""""""""" + +Libraries that support dataclass-like semantics and support field +specifier classes typically use common parameter names to construct +these field specifiers. This specification formalizes the names and +meanings of the parameters that must be understood for static type +checkers. These standardized parameters must be keyword-only. + +These parameters are a superset of those supported by +``dataclasses.field``, excluding those that do not have an impact on +type checking such as ``compare`` and ``hash``. + +Field specifier classes are allowed to use other +parameters in their constructors, and those parameters can be +positional and may use other names. + +* ``init`` is an optional bool parameter that indicates whether the + field should be included in the synthesized ``__init__`` method. If + unspecified, ``init`` defaults to True. Field specifier functions + can use overloads that implicitly specify the value of ``init`` + using a literal bool value type + (``Literal[False]`` or ``Literal[True]``). +* ``default`` is an optional parameter that provides the default value + for the field. +* ``default_factory`` is an optional parameter that provides a runtime + callback that returns the default value for the field. If neither + ``default`` nor ``default_factory`` are specified, the field is + assumed to have no default value and must be provided a value when + the class is instantiated. +* ``factory`` is an alias for ``default_factory``. Stdlib dataclasses + use the name ``default_factory``, but attrs uses the name ``factory`` + in many scenarios, so this alias is necessary for supporting attrs. +* ``kw_only`` is an optional bool parameter that indicates whether the + field should be marked as keyword-only. If true, the field will be + keyword-only. If false, it will not be keyword-only. If unspecified, + the value of the ``kw_only`` parameter on the object decorated with + ``dataclass_transform`` will be used, or if that is unspecified, the + value of ``kw_only_default`` on ``dataclass_transform`` will be used. +* ``alias`` is an optional str parameter that provides an alternative + name for the field. This alternative name is used in the synthesized + ``__init__`` method. + +It is an error to specify more than one of ``default``, +``default_factory`` and ``factory``. + +This example demonstrates the above: + +.. code-block:: python + + # Library code (within type stub or inline) + # In this library, passing a resolver means that init must be False, + # and the overload with Literal[False] enforces that. + @overload + def model_field( + *, + default: Optional[Any] = ..., + resolver: Callable[[], Any], + init: Literal[False] = False, + ) -> Any: ... + + @overload + def model_field( + *, + default: Optional[Any] = ..., + resolver: None = None, + init: bool = True, + ) -> Any: ... + + @typing.dataclass_transform( + kw_only_default=True, + field_specifiers=(model_field, )) + def create_model( + *, + init: bool = True, + ) -> Callable[[Type[_T]], Type[_T]]: ... + + # Code that imports this library: + @create_model(init=False) + class CustomerModel: + id: int = model_field(resolver=lambda : 0) + name: str + + +Runtime behavior +^^^^^^^^^^^^^^^^ + +At runtime, the ``dataclass_transform`` decorator's only effect is to +set an attribute named ``__dataclass_transform__`` on the decorated +function or class to support introspection. The value of the attribute +should be a dict mapping the names of the ``dataclass_transform`` +parameters to their values. + +For example: + +.. code-block:: python + + { + "eq_default": True, + "order_default": False, + "kw_only_default": False, + "field_specifiers": (), + "kwargs": {} + } + + +Dataclass semantics +^^^^^^^^^^^^^^^^^^^ + +Except where stated otherwise, classes impacted by +``dataclass_transform``, either by inheriting from a class that is +decorated with ``dataclass_transform`` or by being decorated with +a function decorated with ``dataclass_transform``, are assumed to +behave like stdlib ``dataclass``. + +This includes, but is not limited to, the following semantics: + +* Frozen dataclasses cannot inherit from non-frozen dataclasses. A + class that has been decorated with ``dataclass_transform`` is + considered neither frozen nor non-frozen, thus allowing frozen + classes to inherit from it. Similarly, a class that directly + specifies a metaclass that is decorated with ``dataclass_transform`` + is considered neither frozen nor non-frozen. + + Consider these class examples: + + .. code-block:: python + + # ModelBase is not considered either "frozen" or "non-frozen" + # because it is decorated with ``dataclass_transform`` + @typing.dataclass_transform() + class ModelBase(): ... + + # Vehicle is considered non-frozen because it does not specify + # "frozen=True". + class Vehicle(ModelBase): + name: str + + # Car is a frozen class that derives from Vehicle, which is a + # non-frozen class. This is an error. + class Car(Vehicle, frozen=True): + wheel_count: int + + And these similar metaclass examples: + + .. code-block:: python + + @typing.dataclass_transform() + class ModelMeta(type): ... + + # ModelBase is not considered either "frozen" or "non-frozen" + # because it directly specifies ModelMeta as its metaclass. + class ModelBase(metaclass=ModelMeta): ... + + # Vehicle is considered non-frozen because it does not specify + # "frozen=True". + class Vehicle(ModelBase): + name: str + + # Car is a frozen class that derives from Vehicle, which is a + # non-frozen class. This is an error. + class Car(Vehicle, frozen=True): + wheel_count: int + +* Field ordering and inheritance is assumed to follow the rules + specified in :pep:`557 <557#inheritance>`. This includes the effects of + overrides (redefining a field in a child class that has already been + defined in a parent class). + +* :pep:`PEP 557 indicates <557#post-init-parameters>` that + all fields without default values must appear before + fields with default values. Although not explicitly + stated in PEP 557, this rule is ignored when ``init=False``, and + this specification likewise ignores this requirement in that + situation. Likewise, there is no need to enforce this ordering when + keyword-only parameters are used for ``__init__``, so the rule is + not enforced if ``kw_only`` semantics are in effect. + +* As with ``dataclass``, method synthesis is skipped if it would + overwrite a method that is explicitly declared within the class. + Method declarations on base classes do not cause method synthesis to + be skipped. + + For example, if a class declares an ``__init__`` method explicitly, + an ``__init__`` method will not be synthesized for that class. + +* KW_ONLY sentinel values are supported as described in `the Python + docs `_ + and `bpo-43532 `_. + +* ClassVar attributes are not considered dataclass fields and are + `ignored by dataclass mechanisms `_. + + +Undefined behavior +^^^^^^^^^^^^^^^^^^ + +If multiple ``dataclass_transform`` decorators are found, either on a +single function (including its overloads), a single class, or within a +class hierarchy, the resulting behavior is undefined. Library authors +should avoid these scenarios. diff --git a/docs/spec/directives.rst b/docs/spec/directives.rst new file mode 100644 index 000000000..57f3c4b6e --- /dev/null +++ b/docs/spec/directives.rst @@ -0,0 +1,264 @@ +.. _directives: + +Type checker directives +======================= + +``assert_type()`` +----------------- + +The function ``typing.assert_type(val, typ)`` allows users to +ask a static type checker to confirm that *val* has an inferred type of *typ*. + +When a type checker encounters a call to ``assert_type()``, it +should emit an error if the value is not of the specified type:: + + def greet(name: str) -> None: + assert_type(name, str) # OK, inferred type of `name` is `str` + assert_type(name, int) # type checker error + +``reveal_type()`` +----------------- + +The function ``reveal_type(obj)`` makes type checkers +reveal the inferred static type of an expression. + +When a static type checker encounters a call to this function, +it should emit a diagnostic with the type of the argument. For example:: + + x: int = 1 + reveal_type(x) # Revealed type is "builtins.int" + +``# type: ignore`` comments +--------------------------- + +The special comment ``# type: ignore`` is used to silence type checker +errors. + +The ``# type: ignore`` comment should be put on the line that the +error refers to:: + + import http.client + errors = { + 'not_found': http.client.NOT_FOUND # type: ignore + } + +A ``# type: ignore`` comment on a line by itself at the top of a file, +before any docstrings, imports, or other executable code, silences all +errors in the file. Blank lines and other comments, such as shebang +lines and coding cookies, may precede the ``# type: ignore`` comment. + +In some cases, linting tools or other comments may be needed on the same +line as a type comment. In these cases, the type comment should be before +other comments and linting markers: + + # type: ignore # + +``cast()`` +---------- + +Occasionally the type checker may need a different kind of hint: the +programmer may know that an expression is of a more constrained type +than a type checker may be able to infer. For example:: + + from typing import cast + + def find_first_str(a: list[object]) -> str: + index = next(i for i, x in enumerate(a) if isinstance(x, str)) + # We only get here if there's at least one string in a + return cast(str, a[index]) + +Some type checkers may not be able to infer that the type of +``a[index]`` is ``str`` and only infer ``object`` or ``Any``, but we +know that (if the code gets to that point) it must be a string. The +``cast(t, x)`` call tells the type checker that we are confident that +the type of ``x`` is ``t``. At runtime a cast always returns the +expression unchanged -- it does not check the type, and it does not +convert or coerce the value. + +Casts differ from type comments (see the previous section). When using +a type comment, the type checker should still verify that the inferred +type is consistent with the stated type. When using a cast, the type +checker should blindly believe the programmer. Also, casts can be used +in expressions, while type comments only apply to assignments. + +``TYPE_CHECKING`` +----------------- + +Sometimes there's code that must be seen by a type checker (or other +static analysis tools) but should not be executed. For such +situations the ``typing`` module defines a constant, +``TYPE_CHECKING``, that is considered ``True`` during type checking +(or other static analysis) but ``False`` at runtime. Example:: + + import typing + + if typing.TYPE_CHECKING: + import expensive_mod + + def a_func(arg: 'expensive_mod.SomeClass') -> None: + a_var: expensive_mod.SomeClass = arg + ... + +(Note that the type annotation must be enclosed in quotes, making it a +"forward reference", to hide the ``expensive_mod`` reference from the +interpreter runtime. In the variable annotation no quotes are needed.) + +This approach may also be useful to handle import cycles. + +``@no_type_check`` +------------------ + +To mark portions of the program that should not be covered by type +hinting, you can use the ``@typing.no_type_check`` decorator on a class or function. +Functions with this decorator should be treated as having +no annotations. + +Version and platform checking +----------------------------- + +Type checkers are expected to understand simple version and platform +checks, e.g.:: + + import sys + + if sys.version_info >= (3, 12): + # Python 3.12+ + else: + # Python 3.11 and lower + + if sys.platform == 'win32': + # Windows specific definitions + else: + # Posix specific definitions + +Don't expect a checker to understand obfuscations like +``"".join(reversed(sys.platform)) == "xunil"``. + +``@deprecated`` +--------------- + +(Originally specified in :pep:`702`.) + +The :py:func:`warnings.deprecated` +decorator can be used on a class, function or method to mark it as deprecated. +This includes :class:`typing.TypedDict` and :class:`typing.NamedTuple` definitions. +With overloaded functions, the decorator may be applied to individual overloads, +indicating that the particular overload is deprecated. The decorator may also be +applied to the overload implementation function, indicating that the entire function +is deprecated. + +The decorator takes the following arguments: + +* A required positional-only argument representing the deprecation message. +* Two keyword-only arguments, ``category`` and ``stacklevel``, controlling + runtime behavior (see under "Runtime behavior" below). + +The positional-only argument is of type ``str`` and contains a message that should +be shown by the type checker when it encounters a usage of the decorated object. +Tools may clean up the deprecation message for display, for example +by using :func:`inspect.cleandoc` or equivalent logic. +The message must be a string literal. +The content of deprecation messages is up to the user, but it may include the version +in which the deprecated object is to be removed, and information about suggested +replacement APIs. + +Type checkers should produce a diagnostic whenever they encounter a usage of an +object marked as deprecated. For deprecated overloads, this includes all calls +that resolve to the deprecated overload. +For deprecated classes and functions, this includes: + +* References through module, class, or instance attributes (``module.deprecated_object``, + ``module.SomeClass.deprecated_method``, ``module.SomeClass().deprecated_method``) +* Any usage of deprecated objects in their defining module + (``x = deprecated_object()`` in ``module.py``) +* If ``import *`` is used, usage of deprecated objects from the + module (``from module import *; x = deprecated_object()``) +* ``from`` imports (``from module import deprecated_object``) +* Any syntax that indirectly triggers a call to the function. For example, + if the ``__add__`` method of a class ``C`` is deprecated, then + the code ``C() + C()`` should trigger a diagnostic. Similarly, if the + setter of a property is marked deprecated, attempts to set the property + should trigger a diagnostic. + +If a method is marked with the :func:`typing.override` decorator from :pep:`698` +and the base class method it overrides is deprecated, the type checker should +produce a diagnostic. + +There are additional scenarios where deprecations could come into play. +For example, an object may implement a :class:`typing.Protocol`, but one +of the methods required for protocol compliance is deprecated. +As scenarios such as this one appear complex and relatively unlikely to come up in practice, +this PEP does not mandate that type checkers detect them. + +Example +^^^^^^^ + +As an example, consider this library stub named ``library.pyi``: + +.. code-block:: python + + from warnings import deprecated + + @deprecated("Use Spam instead") + class Ham: ... + + @deprecated("It is pining for the fiords") + def norwegian_blue(x: int) -> int: ... + + @overload + @deprecated("Only str will be allowed") + def foo(x: int) -> str: ... + @overload + def foo(x: str) -> str: ... + + class Spam: + @deprecated("There is enough spam in the world") + def __add__(self, other: object) -> object: ... + + @property + @deprecated("All spam will be equally greasy") + def greasy(self) -> float: ... + + @property + def shape(self) -> str: ... + @shape.setter + @deprecated("Shapes are becoming immutable") + def shape(self, value: str) -> None: ... + +Here is how type checkers should handle usage of this library: + +.. code-block:: python + + from library import Ham # error: Use of deprecated class Ham. Use Spam instead. + + import library + + library.norwegian_blue(1) # error: Use of deprecated function norwegian_blue. It is pining for the fiords. + map(library.norwegian_blue, [1, 2, 3]) # error: Use of deprecated function norwegian_blue. It is pining for the fiords. + + library.foo(1) # error: Use of deprecated overload for foo. Only str will be allowed. + library.foo("x") # no error + + ham = Ham() # no error (already reported above) + + spam = library.Spam() + spam + 1 # error: Use of deprecated method Spam.__add__. There is enough spam in the world. + spam.greasy # error: Use of deprecated property Spam.greasy. All spam will be equally greasy. + spam.shape # no error + spam.shape = "cube" # error: Use of deprecated property setter Spam.shape. Shapes are becoming immutable. + +The exact wording of the diagnostics is up to the type checker and is not part +of the specification. + +Type checker behavior +^^^^^^^^^^^^^^^^^^^^^ + +It is unspecified exactly how type checkers should present deprecation +diagnostics to their users. However, some users (e.g., application developers +targeting only a specific version of Python) may not care about deprecations, +while others (e.g., library developers who want their library to remain +compatible with future versions of Python) would want to catch any use of +deprecated functionality in their CI pipeline. Therefore, it is recommended +that type checkers provide configuration options that cover both use cases. +As with any other type checker error, it is also possible to ignore deprecations +using ``# type: ignore`` comments. diff --git a/docs/spec/distributing.rst b/docs/spec/distributing.rst new file mode 100644 index 000000000..d6f602065 --- /dev/null +++ b/docs/spec/distributing.rst @@ -0,0 +1,254 @@ +.. _distributing-type: + +Distributing type information +============================= + +Stub files +---------- + +Stub files are files containing type hints that are only for use by +the type checker, not at runtime. There are several use cases for +stub files: + +* Extension modules + +* Third-party modules whose authors have not yet added type hints + +* Standard library modules for which type hints have not yet been + written + +* Modules that must be compatible with Python 2 and 3 + +* Modules that use annotations for other purposes + +Stub files have the same syntax as regular Python modules. There is one +feature of the ``typing`` module that is different in stub files: +the ``@overload`` decorator described below. + +The type checker should only check function signatures in stub files; +It is recommended that function bodies in stub files just be a single +ellipsis (``...``). + +The type checker should have a configurable search path for stub files. +If a stub file is found the type checker should not read the +corresponding "real" module. + +While stub files are syntactically valid Python modules, they use the +``.pyi`` extension to make it possible to maintain stub files in the +same directory as the corresponding real module. This also reinforces +the notion that no runtime behavior should be expected of stub files. + +Additional notes on stub files: + +* Modules and variables imported into the stub are not considered + exported from the stub unless the import uses the ``import ... as + ...`` form or the equivalent ``from ... import ... as ...`` form. + (*UPDATE:* To clarify, the intention here is that only names + imported using the form ``X as X`` will be exported, i.e. the name + before and after ``as`` must be the same.) + +* However, as an exception to the previous bullet, all objects + imported into a stub using ``from ... import *`` are considered + exported. (This makes it easier to re-export all objects from a + given module that may vary by Python version.) + +* Just like in `normal Python files `_, submodules + automatically become exported attributes of their parent module + when imported. For example, if the ``spam`` package has the + following directory structure:: + + spam/ + __init__.pyi + ham.pyi + + where ``__init__.pyi`` contains a line such as ``from . import ham`` + or ``from .ham import Ham``, then ``ham`` is an exported attribute + of ``spam``. + +* Stub files may be incomplete. To make type checkers aware of this, the file + can contain the following code:: + + def __getattr__(name) -> Any: ... + + Any identifier not defined in the stub is therefore assumed to be of type + ``Any``. + +The Typeshed Repo +^^^^^^^^^^^^^^^^^ + +There is a `shared repository `_ where useful stubs are being +collected. Policies regarding the stubs collected here are +decided separately and reported in the repo's documentation. + + +Type information in libraries +----------------------------- + +There are several motivations and methods of supporting typing in a package. +This specification recognizes three types of packages that users of typing wish to +create: + +1. The package maintainer would like to add type information inline. + +2. The package maintainer would like to add type information via stubs. + +3. A third party or package maintainer would like to share stub files for + a package, but the maintainer does not want to include them in the source + of the package. + +This specification aims to support all three scenarios and make them simple to add to +packaging and deployment. + +The two major parts of this specification are the packaging specifications +and the resolution order for resolving module type information. + + +Packaging Type Information +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In order to make packaging and distributing type information as simple and +easy as possible, packaging and distribution is done through existing +frameworks. + +Package maintainers who wish to support type checking of their code MUST add +a marker file named ``py.typed`` to their package supporting typing. This marker applies +recursively: if a top-level package includes it, all its sub-packages MUST support +type checking as well. To have this file installed with the package, +maintainers can use existing packaging options such as ``package_data`` in +distutils, shown below. + +Distutils option example:: + + setup( + ..., + package_data = { + 'foopkg': ['py.typed'], + }, + ..., + ) + +For namespace packages (see :pep:`420`), the ``py.typed`` file should be in the +submodules of the namespace, to avoid conflicts and for clarity. + +This specification does not support distributing typing information as part of +module-only distributions or single-file modules within namespace packages. + +The single-file module should be refactored into a package +and indicate that the package supports typing as described +above. + +Stub-only Packages +"""""""""""""""""" + +For package maintainers wishing to ship stub files containing all of their +type information, it is preferred that the ``*.pyi`` stubs are alongside the +corresponding ``*.py`` files. However, the stubs can also be put in a separate +package and distributed separately. Third parties can also find this method +useful if they wish to distribute stub files. The name of the stub package +MUST follow the scheme ``foopkg-stubs`` for type stubs for the package named +``foopkg``. Note that for stub-only packages adding a ``py.typed`` marker is not +needed since the name ``*-stubs`` is enough to indicate it is a source of typing +information. + +Third parties seeking to distribute stub files are encouraged to contact the +maintainer of the package about distribution alongside the package. If the +maintainer does not wish to maintain or package stub files or type information +inline, then a third party stub-only package can be created. + +In addition, stub-only distributions SHOULD indicate which version(s) +of the runtime package are supported by indicating the runtime distribution's +version(s) through normal dependency data. For example, the +stub package ``flyingcircus-stubs`` can indicate the versions of the +runtime ``flyingcircus`` distribution it supports through ``install_requires`` +in distutils-based tools, or the equivalent in other packaging tools. Note that +in pip 9.0, if you update ``flyingcircus-stubs``, it will update +``flyingcircus``. In pip 9.0, you can use the +``--upgrade-strategy=only-if-needed`` flag. In pip 10.0 this is the default +behavior. + +For namespace packages (see :pep:`420`), stub-only packages should +use the ``-stubs`` suffix on only the root namespace package. +All stub-only namespace packages should omit ``__init__.pyi`` files. ``py.typed`` +marker files are not necessary for stub-only packages, but similarly +to packages with inline types, if used, they should be in submodules of the namespace to +avoid conflicts and for clarity. + +For example, if the ``pentagon`` and ``hexagon`` are separate distributions +installing within the namespace package ``shapes.polygons`` +The corresponding types-only distributions should produce packages +laid out as follows:: + + shapes-stubs + └── polygons + └── pentagon + └── __init__.pyi + + shapes-stubs + └── polygons + └── hexagon +    └── __init__.pyi + +Partial Stub Packages +""""""""""""""""""""" + +Many stub packages will only have part of the type interface for libraries +completed, especially initially. For the benefit of type checking and code +editors, packages can be "partial". This means modules not found in the stub +package SHOULD be searched for in parts four and five of the module resolution +order above, namely inline packages and typeshed. + +Type checkers should merge the stub package and runtime package or typeshed +directories. This can be thought of as the functional equivalent of copying the +stub package into the same directory as the corresponding runtime package or +typeshed folder and type checking the combined directory structure. Thus type +checkers MUST maintain the normal resolution order of checking ``*.pyi`` before +``*.py`` files. + +If a stub package distribution is partial it MUST include ``partial\n`` in a +``py.typed`` file. For stub-packages distributing within a namespace +package (see :pep:`420`), the ``py.typed`` file should be in the +submodules of the namespace. + +Type checkers should treat namespace packages within stub-packages as +incomplete since multiple distributions may populate them. +Regular packages within namespace packages in stub-package distributions +are considered complete unless a ``py.typed`` with ``partial\n`` is included. + +.. _mro: + +Import resolutiong ordering +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following is the order in which type checkers supporting this specification SHOULD +resolve modules containing type information: + + +1. Stubs or Python source manually put in the beginning of the path. Type + checkers SHOULD provide this to allow the user complete control of which + stubs to use, and to patch broken stubs/inline types from packages. + In mypy the ``$MYPYPATH`` environment variable can be used for this. + +2. User code - the files the type checker is running on. + +3. Stub packages - these packages SHOULD supersede any installed inline + package. They can be found at ``foopkg-stubs`` for package ``foopkg``. + +4. Packages with a ``py.typed`` marker file - if there is nothing overriding + the installed package, *and* it opts into type checking, the types + bundled with the package SHOULD be used (be they in ``.pyi`` type + stub files or inline in ``.py`` files). + +5. Typeshed (if used) - Provides the stdlib types and several third party + libraries. + +If typecheckers identify a stub-only namespace package without the desired module +in step 3, they should continue to step 4/5. Typecheckers should identify namespace packages +by the absence of ``__init__.pyi``. This allows different subpackages to +independently opt for inline vs stub-only. + +Type checkers that check a different Python version than the version they run +on MUST find the type information in the ``site-packages``/``dist-packages`` +of that Python version. This can be queried e.g. +``pythonX.Y -c 'import site; print(site.getsitepackages())'``. It is also recommended +that the type checker allow for the user to point to a particular Python +binary, in case it is not in the path. diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst new file mode 100644 index 000000000..0878663e7 --- /dev/null +++ b/docs/spec/generics.rst @@ -0,0 +1,2505 @@ +Generics +======== + +Introduction +------------ + +Since type information about objects kept in containers cannot be +statically inferred in a generic way, abstract base classes have been +extended to support subscription to denote expected types for container +elements. Example:: + + from collections.abc import Mapping + + def notify_by_email(employees: set[Employee], overrides: Mapping[str, str]) -> None: ... + +Generics can be parameterized by using a factory available in +``typing`` called ``TypeVar``. Example:: + + from collections.abc import Sequence + from typing import TypeVar + + T = TypeVar('T') # Declare type variable + + def first(l: Sequence[T]) -> T: # Generic function + return l[0] + +Or, since Python 3.12 (:pep:`695`), by using the new syntax for +generic functions:: + + from collections.abc import Sequence + + def first[T](l: Sequence[T]) -> T: # Generic function + return l[0] + +The two syntaxes are equivalent. +In either case the contract is that the returned value is consistent with +the elements held by the collection. + +A ``TypeVar()`` expression must always directly be assigned to a +variable (it should not be used as part of a larger expression). The +argument to ``TypeVar()`` must be a string equal to the variable name +to which it is assigned. Type variables must not be redefined. + +``TypeVar`` supports constraining parametric types to a fixed set of possible +types (note: those types cannot be parameterized by type variables). For +example, we can define a type variable that ranges over just ``str`` and +``bytes``. By default, a type variable ranges over all possible types. +Example of constraining a type variable:: + + from typing import TypeVar + + AnyStr = TypeVar('AnyStr', str, bytes) + + def concat(x: AnyStr, y: AnyStr) -> AnyStr: + return x + y + +Or using the built-in syntax (3.12 and higher):: + + def concat[AnyStr: (str, bytes)](x: AnyStr, y: AnyStr) -> AnyStr: + return x + y + +The function ``concat`` can be called with either two ``str`` arguments +or two ``bytes`` arguments, but not with a mix of ``str`` and ``bytes`` +arguments. + +There should be at least two constraints, if any; specifying a single +constraint is disallowed. + +Subtypes of types constrained by a type variable should be treated +as their respective explicitly listed base types in the context of the +type variable. Consider this example:: + + class MyStr(str): ... + + x = concat(MyStr('apple'), MyStr('pie')) + +The call is valid but the type variable ``AnyStr`` will be set to +``str`` and not ``MyStr``. In effect, the inferred type of the return +value assigned to ``x`` will also be ``str``. + +Additionally, ``Any`` is a valid value for every type variable. +Consider the following:: + + def count_truthy(elements: list[Any]) -> int: + return sum(1 for elem in elements if elem) + +This is equivalent to omitting the generic notation and just saying +``elements: list``. + + +User-defined generic types +-------------------------- + +You can include a ``Generic`` base class to define a user-defined class +as generic. Example:: + + from typing import TypeVar, Generic + from logging import Logger + + T = TypeVar('T') + + class LoggedVar(Generic[T]): + def __init__(self, value: T, name: str, logger: Logger) -> None: + self.name = name + self.logger = logger + self.value = value + + def set(self, new: T) -> None: + self.log('Set ' + repr(self.value)) + self.value = new + + def get(self) -> T: + self.log('Get ' + repr(self.value)) + return self.value + + def log(self, message: str) -> None: + self.logger.info('{}: {}'.format(self.name, message)) + +Or, in Python 3.12 and higher, by using the new syntax for generic +classes:: + + class LoggedVar[T]: + # methods as in previous example + +This implicitly adds ``Generic[T]`` as a base class and type checkers +should treat the two largely equivalently (except for variance, see below). + +``Generic[T]`` as a base class defines that the class ``LoggedVar`` +takes a single type parameter ``T``. This also makes ``T`` valid as +a type within the class body. + +The ``Generic`` base class uses a metaclass that defines ``__getitem__`` +so that ``LoggedVar[t]`` is valid as a type:: + + from collections.abc import Iterable + + def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None: + for var in vars: + var.set(0) + +A generic type can have any number of type variables, and type variables +may be constrained. This is valid:: + + from typing import TypeVar, Generic + ... + + T = TypeVar('T') + S = TypeVar('S') + + class Pair(Generic[T, S]): + ... + +Each type variable argument to ``Generic`` must be distinct. This is +thus invalid:: + + from typing import TypeVar, Generic + ... + + T = TypeVar('T') + + class Pair(Generic[T, T]): # INVALID + ... + +The ``Generic[T]`` base class is redundant in simple cases where you +subclass some other generic class and specify type variables for its +parameters:: + + from typing import TypeVar + from collections.abc import Iterator + + T = TypeVar('T') + + class MyIter(Iterator[T]): + ... + +That class definition is equivalent to:: + + class MyIter(Iterator[T], Generic[T]): + ... + +You can use multiple inheritance with ``Generic``:: + + from typing import TypeVar, Generic + from collections.abc import Sized, Iterable, Container + + T = TypeVar('T') + + class LinkedList(Sized, Generic[T]): + ... + + K = TypeVar('K') + V = TypeVar('V') + + class MyMapping(Iterable[tuple[K, V]], + Container[tuple[K, V]], + Generic[K, V]): + ... + +Subclassing a generic class without specifying type parameters assumes +``Any`` for each position. In the following example, ``MyIterable`` +is not generic but implicitly inherits from ``Iterable[Any]``:: + + from collections.abc import Iterable + + class MyIterable(Iterable): # Same as Iterable[Any] + ... + +Generic metaclasses are not supported. + + +Scoping rules for type variables +-------------------------------- + +Type variables follow normal name resolution rules. +However, there are some special cases in the static typechecking context: + +* A type variable used in a generic function could be inferred to represent + different types in the same code block. Example:: + + from typing import TypeVar, Generic + + T = TypeVar('T') + + def fun_1(x: T) -> T: ... # T here + def fun_2(x: T) -> T: ... # and here could be different + + fun_1(1) # This is OK, T is inferred to be int + fun_2('a') # This is also OK, now T is str + +* A type variable used in a method of a generic class that coincides + with one of the variables that parameterize this class is always bound + to that variable. Example:: + + from typing import TypeVar, Generic + + T = TypeVar('T') + + class MyClass(Generic[T]): + def meth_1(self, x: T) -> T: ... # T here + def meth_2(self, x: T) -> T: ... # and here are always the same + + a: MyClass[int] = MyClass() + a.meth_1(1) # OK + a.meth_2('a') # This is an error! + +* A type variable used in a method that does not match any of the variables + that parameterize the class makes this method a generic function in that + variable:: + + T = TypeVar('T') + S = TypeVar('S') + class Foo(Generic[T]): + def method(self, x: T, y: S) -> S: + ... + + x: Foo[int] = Foo() + y = x.method(0, "abc") # inferred type of y is str + +* Unbound type variables should not appear in the bodies of generic functions, + or in the class bodies apart from method definitions:: + + T = TypeVar('T') + S = TypeVar('S') + + def a_fun(x: T) -> None: + # this is OK + y: list[T] = [] + # but below is an error! + y: list[S] = [] + + class Bar(Generic[T]): + # this is also an error + an_attr: list[S] = [] + + def do_something(x: S) -> S: # this is OK though + ... + +* A generic class definition that appears inside a generic function + should not use type variables that parameterize the generic function:: + + def a_fun(x: T) -> None: + + # This is OK + a_list: list[T] = [] + ... + + # This is however illegal + class MyGeneric(Generic[T]): + ... + +* A generic class nested in another generic class cannot use the same type + variables. The scope of the type variables of the outer class + doesn't cover the inner one:: + + T = TypeVar('T') + S = TypeVar('S') + + class Outer(Generic[T]): + class Bad(Iterable[T]): # Error + ... + class AlsoBad: + x: list[T] # Also an error + + class Inner(Iterable[S]): # OK + ... + attr: Inner[T] # Also OK + + +Instantiating generic classes and type erasure +---------------------------------------------- + +User-defined generic classes can be instantiated. Suppose we write +a ``Node`` class inheriting from ``Generic[T]``:: + + from typing import TypeVar, Generic + + T = TypeVar('T') + + class Node(Generic[T]): + ... + +To create ``Node`` instances you call ``Node()`` just as for a regular +class. At runtime the type (class) of the instance will be ``Node``. +But what type does it have to the type checker? The answer depends on +how much information is available in the call. If the constructor +(``__init__`` or ``__new__``) uses ``T`` in its signature, and a +corresponding argument value is passed, the type of the corresponding +argument(s) is substituted. Otherwise, ``Any`` is assumed. Example:: + + from typing import TypeVar, Generic + + T = TypeVar('T') + + class Node(Generic[T]): + x: T # Instance attribute (see below) + def __init__(self, label: T = None) -> None: + ... + + x = Node('') # Inferred type is Node[str] + y = Node(0) # Inferred type is Node[int] + z = Node() # Inferred type is Node[Any] + +In case the inferred type uses ``[Any]`` but the intended type is more +specific, you can use a type comment (see below) to force the type of +the variable, e.g.:: + + # (continued from previous example) + a: Node[int] = Node() + b: Node[str] = Node() + +Alternatively, you can instantiate a specific concrete type, e.g.:: + + # (continued from previous example) + p = Node[int]() + q = Node[str]() + r = Node[int]('') # Error + s = Node[str](0) # Error + +Note that the runtime type (class) of ``p`` and ``q`` is still just ``Node`` +-- ``Node[int]`` and ``Node[str]`` are distinguishable class objects, but +the runtime class of the objects created by instantiating them doesn't +record the distinction. This behavior is called "type erasure"; it is +common practice in languages with generics (e.g. Java, TypeScript). + +Using generic classes (parameterized or not) to access attributes will result +in type check failure. Outside the class definition body, a class attribute +cannot be assigned, and can only be looked up by accessing it through a +class instance that does not have an instance attribute with the same name:: + + # (continued from previous example) + Node[int].x = 1 # Error + Node[int].x # Error + Node.x = 1 # Error + Node.x # Error + type(p).x # Error + p.x # Ok (evaluates to None) + Node[int]().x # Ok (evaluates to None) + p.x = 1 # Ok, but assigning to instance attribute + +Generic versions of abstract collections like ``Mapping`` or ``Sequence`` +and generic versions of built-in classes -- ``List``, ``Dict``, ``Set``, +and ``FrozenSet`` -- cannot be instantiated. However, concrete user-defined +subclasses thereof and generic versions of concrete collections can be +instantiated:: + + data = DefaultDict[int, bytes]() + +Note that one should not confuse static types and runtime classes. +The type is still erased in this case and the above expression is +just a shorthand for:: + + data: DefaultDict[int, bytes] = collections.defaultdict() + +It is not recommended to use the subscripted class (e.g. ``Node[int]``) +directly in an expression -- using a type alias (e.g. ``IntNode = Node[int]``) +instead is preferred. (First, creating the subscripted class, +e.g. ``Node[int]``, has a runtime cost. Second, using a type alias +is more readable.) + + +Arbitrary generic types as base classes +--------------------------------------- + +``Generic[T]`` is only valid as a base class -- it's not a proper type. +However, user-defined generic types such as ``LinkedList[T]`` from the +above example and built-in generic types and ABCs such as ``list[T]`` +and ``Iterable[T]`` are valid both as types and as base classes. For +example, we can define a subclass of ``dict`` that specializes type +arguments:: + + class Node: + ... + + class SymbolTable(dict[str, list[Node]]): + def push(self, name: str, node: Node) -> None: + self.setdefault(name, []).append(node) + + def pop(self, name: str) -> Node: + return self[name].pop() + + def lookup(self, name: str) -> Node | None: + nodes = self.get(name) + if nodes: + return nodes[-1] + return None + +``SymbolTable`` is a subclass of ``dict`` and a subtype of ``dict[str, +list[Node]]``. + +If a generic base class has a type variable as a type argument, this +makes the defined class generic. For example, we can define a generic +``LinkedList`` class that is iterable and a container:: + + from typing import TypeVar + from collections.abc import Iterable, Container + + T = TypeVar('T') + + class LinkedList(Iterable[T], Container[T]): + ... + +Now ``LinkedList[int]`` is a valid type. Note that we can use ``T`` +multiple times in the base class list, as long as we don't use the +same type variable ``T`` multiple times within ``Generic[...]``. + +Also consider the following example:: + + from typing import TypeVar + from collections.abc import Mapping + + T = TypeVar('T') + + class MyDict(Mapping[str, T]): + ... + +In this case MyDict has a single parameter, T. + + +Abstract generic types +---------------------- + +The metaclass used by ``Generic`` is a subclass of ``abc.ABCMeta``. +A generic class can be an ABC by including abstract methods +or properties, and generic classes can also have ABCs as base +classes without a metaclass conflict. + + +Type variables with an upper bound +---------------------------------- + +A type variable may specify an upper bound using ``bound=`` (note: + itself cannot be parameterized by type variables). This means that an +actual type substituted (explicitly or implicitly) for the type variable must +be a subtype of the boundary type. Example:: + + from typing import TypeVar + from collections.abc import Sized + + ST = TypeVar('ST', bound=Sized) + + def longer(x: ST, y: ST) -> ST: + if len(x) > len(y): + return x + else: + return y + + longer([1], [1, 2]) # ok, return type list[int] + longer({1}, {1, 2}) # ok, return type set[int] + longer([1], {1, 2}) # ok, return type Collection[int] + +An upper bound cannot be combined with type constraints (as used in +``AnyStr``, see the example earlier); type constraints cause the +inferred type to be _exactly_ one of the constraint types, while an +upper bound just requires that the actual type is a subtype of the +boundary type. + + +Variance +-------- + +Consider a class ``Employee`` with a subclass ``Manager``. Now +suppose we have a function with an argument annotated with +``list[Employee]``. Should we be allowed to call this function with a +variable of type ``list[Manager]`` as its argument? Many people would +answer "yes, of course" without even considering the consequences. +But unless we know more about the function, a type checker should +reject such a call: the function might append an ``Employee`` instance +to the list, which would violate the variable's type in the caller. + +It turns out such an argument acts *contravariantly*, whereas the +intuitive answer (which is correct in case the function doesn't mutate +its argument!) requires the argument to act *covariantly*. A longer +introduction to these concepts can be found on `Wikipedia +`_ and in :pep:`483`; here we just show how to control +a type checker's behavior. + +By default generic types declared using the old ``TypeVar`` syntax +are considered *invariant* in all type variables, +which means that values for variables annotated with types like +``list[Employee]`` must exactly match the type annotation -- no subclasses or +superclasses of the type parameter (in this example ``Employee``) are +allowed. See below for the behavior when using the built-in generic syntax +in Python 3.12 and higher. + +To facilitate the declaration of container types where covariant or +contravariant type checking is acceptable, type variables accept keyword +arguments ``covariant=True`` or ``contravariant=True``. At most one of these +may be passed. Generic types defined with such variables are considered +covariant or contravariant in the corresponding variable. By convention, +it is recommended to use names ending in ``_co`` for type variables +defined with ``covariant=True`` and names ending in ``_contra`` for that +defined with ``contravariant=True``. + +A typical example involves defining an immutable (or read-only) +container class:: + + from typing import TypeVar, Generic + from collections.abc import Iterable, Iterator + + T_co = TypeVar('T_co', covariant=True) + + class ImmutableList(Generic[T_co]): + def __init__(self, items: Iterable[T_co]) -> None: ... + def __iter__(self) -> Iterator[T_co]: ... + ... + + class Employee: ... + + class Manager(Employee): ... + + def dump_employees(emps: ImmutableList[Employee]) -> None: + for emp in emps: + ... + + mgrs: ImmutableList[Manager] = ImmutableList([Manager()]) + dump_employees(mgrs) # OK + +The read-only collection classes in ``typing`` are all declared +covariant in their type variable (e.g. ``Mapping`` and ``Sequence``). The +mutable collection classes (e.g. ``MutableMapping`` and +``MutableSequence``) are declared invariant. The one example of +a contravariant type is the ``Generator`` type, which is contravariant +in the ``send()`` argument type (see below). + +Note: Covariance or contravariance is *not* a property of a type variable, +but a property of a generic class defined using this variable. +Variance is only applicable to generic types; generic functions +do not have this property. The latter should be defined using only +type variables without ``covariant`` or ``contravariant`` keyword arguments. +For example, the following example is +fine:: + + from typing import TypeVar + + class Employee: ... + + class Manager(Employee): ... + + E = TypeVar('E', bound=Employee) + + def dump_employee(e: E) -> None: ... + + dump_employee(Manager()) # OK + +while the following is prohibited:: + + B_co = TypeVar('B_co', covariant=True) + + def bad_func(x: B_co) -> B_co: # Flagged as error by a type checker + ... + +ParamSpec +--------- + +(Originally specified by :pep:`612`.) + +``ParamSpec`` Variables +^^^^^^^^^^^^^^^^^^^^^^^ + +Declaration +"""""""""""" + +A parameter specification variable is defined in a similar manner to how a +normal type variable is defined with ``typing.TypeVar``. + +.. code-block:: + + from typing import ParamSpec + P = ParamSpec("P") # Accepted + P = ParamSpec("WrongName") # Rejected because P =/= WrongName + +The runtime should accept ``bound``\ s and ``covariant`` and ``contravariant`` +arguments in the declaration just as ``typing.TypeVar`` does, but for now we +will defer the standardization of the semantics of those options to a later PEP. + +Valid use locations +""""""""""""""""""" + +Previously only a list of parameter arguments (``[A, B, C]``) or an ellipsis +(signifying "undefined parameters") were acceptable as the first "argument" to +``typing.Callable`` . We now augment that with two new options: a parameter +specification variable (``Callable[P, int]``\ ) or a concatenation on a +parameter specification variable (``Callable[Concatenate[int, P], int]``\ ). + +.. code-block:: + + callable ::= Callable "[" parameters_expression, type_expression "]" + + parameters_expression ::= + | "..." + | "[" [ type_expression ("," type_expression)* ] "]" + | parameter_specification_variable + | concatenate "[" + type_expression ("," type_expression)* "," + parameter_specification_variable + "]" + +where ``parameter_specification_variable`` is a ``typing.ParamSpec`` variable, +declared in the manner as defined above, and ``concatenate`` is +``typing.Concatenate``. + +As before, ``parameters_expression``\ s by themselves are not acceptable in +places where a type is expected + +.. code-block:: + + def foo(x: P) -> P: ... # Rejected + def foo(x: Concatenate[int, P]) -> int: ... # Rejected + def foo(x: list[P]) -> None: ... # Rejected + def foo(x: Callable[[int, str], P]) -> None: ... # Rejected + + +User-Defined Generic Classes +"""""""""""""""""""""""""""" + +Just as defining a class as inheriting from ``Generic[T]`` makes a class generic +for a single parameter (when ``T`` is a ``TypeVar``\ ), defining a class as +inheriting from ``Generic[P]`` makes a class generic on +``parameters_expression``\ s (when ``P`` is a ``ParamSpec``). + +.. code-block:: + + T = TypeVar("T") + P_2 = ParamSpec("P_2") + + class X(Generic[T, P]): + f: Callable[P, int] + x: T + + def f(x: X[int, P_2]) -> str: ... # Accepted + def f(x: X[int, Concatenate[int, P_2]]) -> str: ... # Accepted + def f(x: X[int, [int, bool]]) -> str: ... # Accepted + def f(x: X[int, ...]) -> str: ... # Accepted + def f(x: X[int, int]) -> str: ... # Rejected + +Or, equivalently, using the built-in syntax for generics in Python 3.12 +and higher:: + + class X[T, **P]: + f: Callable[P, int] + x: T + +By the rules defined above, spelling a concrete instance of a class generic +with respect to only a single ``ParamSpec`` would require unsightly double +brackets. For aesthetic purposes we allow these to be omitted. + +.. code-block:: + + class Z(Generic[P]): + f: Callable[P, int] + + def f(x: Z[[int, str, bool]]) -> str: ... # Accepted + def f(x: Z[int, str, bool]) -> str: ... # Equivalent + + # Both Z[[int, str, bool]] and Z[int, str, bool] express this: + class Z_instantiated: + f: Callable[[int, str, bool], int] + +Semantics +""""""""" + +The inference rules for the return type of a function invocation whose signature +contains a ``ParamSpec`` variable are analogous to those around +evaluating ones with ``TypeVar``\ s. + +.. code-block:: + + def changes_return_type_to_str(x: Callable[P, int]) -> Callable[P, str]: ... + + def returns_int(a: str, b: bool) -> int: ... + + f = changes_return_type_to_str(returns_int) # f should have the type: + # (a: str, b: bool) -> str + + f("A", True) # Accepted + f(a="A", b=True) # Accepted + f("A", "A") # Rejected + + expects_str(f("A", True)) # Accepted + expects_int(f("A", True)) # Rejected + +Just as with traditional ``TypeVars``\ , a user may include the same +``ParamSpec`` multiple times in the arguments of the same function, +to indicate a dependency between multiple arguments. In these cases a type +checker may choose to solve to a common behavioral supertype (i.e. a set of +parameters for which all of the valid calls are valid in both of the subtypes), +but is not obligated to do so. + +.. code-block:: + + P = ParamSpec("P") + + def foo(x: Callable[P, int], y: Callable[P, int]) -> Callable[P, bool]: ... + + def x_y(x: int, y: str) -> int: ... + def y_x(y: int, x: str) -> int: ... + + foo(x_y, x_y) # Should return (x: int, y: str) -> bool + # (a callable with two positional-or-keyword parameters) + + foo(x_y, y_x) # Could return (a: int, b: str, /) -> bool + # (a callable with two positional-only parameters) + # This works because both callables have types that are + # behavioral subtypes of Callable[[int, str], int] + + + def keyword_only_x(*, x: int) -> int: ... + def keyword_only_y(*, y: int) -> int: ... + foo(keyword_only_x, keyword_only_y) # Rejected + +The constructors of user-defined classes generic on ``ParamSpec``\ s should be +evaluated in the same way. + +.. code-block:: + + U = TypeVar("U") + + class Y(Generic[U, P]): + f: Callable[P, str] + prop: U + + def __init__(self, f: Callable[P, str], prop: U) -> None: + self.f = f + self.prop = prop + + def a(q: int) -> str: ... + + Y(a, 1) # Should resolve to Y[(q: int), int] + Y(a, 1).f # Should resolve to (q: int) -> str + +The semantics of ``Concatenate[X, Y, P]`` are that it represents the parameters +represented by ``P`` with two positional-only parameters prepended. This means +that we can use it to represent higher order functions that add, remove or +transform a finite number of parameters of a callable. + +.. code-block:: + + def bar(x: int, *args: bool) -> int: ... + + def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ... + + add(bar) # Should return (a: str, /, x: int, *args: bool) -> bool + + def remove(x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ... + + remove(bar) # Should return (*args: bool) -> bool + + def transform( + x: Callable[Concatenate[int, P], int] + ) -> Callable[Concatenate[str, P], bool]: ... + + transform(bar) # Should return (a: str, /, *args: bool) -> bool + +This also means that while any function that returns an ``R`` can satisfy +``typing.Callable[P, R]``, only functions that can be called positionally in +their first position with a ``X`` can satisfy +``typing.Callable[Concatenate[X, P], R]``. + +.. code-block:: + + def expects_int_first(x: Callable[Concatenate[int, P], int]) -> None: ... + + @expects_int_first # Rejected + def one(x: str) -> int: ... + + @expects_int_first # Rejected + def two(*, x: int) -> int: ... + + @expects_int_first # Rejected + def three(**kwargs: int) -> int: ... + + @expects_int_first # Accepted + def four(*args: int) -> int: ... + +There are still some classes of decorators still not supported with these +features: + +* those that add/remove/change a **variable** number of parameters (for + example, ``functools.partial`` remains untypable even using ``ParamSpec``) +* those that add/remove/change keyword-only parameters. + +The components of a ``ParamSpec`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A ``ParamSpec`` captures both positional and keyword accessible +parameters, but there unfortunately is no object in the runtime that captures +both of these together. Instead, we are forced to separate them into ``*args`` +and ``**kwargs``\ , respectively. This means we need to be able to split apart +a single ``ParamSpec`` into these two components, and then bring +them back together into a call. To do this, we introduce ``P.args`` to +represent the tuple of positional arguments in a given call and +``P.kwargs`` to represent the corresponding ``Mapping`` of keywords to +values. + +Valid use locations +""""""""""""""""""" + +These "properties" can only be used as the annotated types for +``*args`` and ``**kwargs``\ , accessed from a ParamSpec already in scope. + +.. code-block:: + + def puts_p_into_scope(f: Callable[P, int]) -> None: + + def inner(*args: P.args, **kwargs: P.kwargs) -> None: # Accepted + pass + + def mixed_up(*args: P.kwargs, **kwargs: P.args) -> None: # Rejected + pass + + def misplaced(x: P.args) -> None: # Rejected + pass + + def out_of_scope(*args: P.args, **kwargs: P.kwargs) -> None: # Rejected + pass + + +Furthermore, because the default kind of parameter in Python (\ ``(x: int)``\ ) +may be addressed both positionally and through its name, two valid invocations +of a ``(*args: P.args, **kwargs: P.kwargs)`` function may give different +partitions of the same set of parameters. Therefore, we need to make sure that +these special types are only brought into the world together, and are used +together, so that our usage is valid for all possible partitions. + +.. code-block:: + + def puts_p_into_scope(f: Callable[P, int]) -> None: + + stored_args: P.args # Rejected + + stored_kwargs: P.kwargs # Rejected + + def just_args(*args: P.args) -> None: # Rejected + pass + + def just_kwargs(**kwargs: P.kwargs) -> None: # Rejected + pass + + +Semantics +""""""""" + +With those requirements met, we can now take advantage of the unique properties +afforded to us by this set up: + + +* Inside the function, ``args`` has the type ``P.args``\ , not + ``tuple[P.args, ...]`` as would be with a normal annotation + (and likewise with the ``**kwargs``\ ) + + * This special case is necessary to encapsulate the heterogeneous contents + of the ``args``/``kwargs`` of a given call, which cannot be expressed + by an indefinite tuple/dictionary type. + +* A function of type ``Callable[P, R]`` can be called with ``(*args, **kwargs)`` + if and only if ``args`` has the type ``P.args`` and ``kwargs`` has the type + ``P.kwargs``\ , and that those types both originated from the same function + declaration. +* A function declared as ``def inner(*args: P.args, **kwargs: P.kwargs) -> X`` + has type ``Callable[P, X]``. + +With these three properties, we now have the ability to fully type check +parameter preserving decorators. + +.. code-block:: + + def decorator(f: Callable[P, int]) -> Callable[P, None]: + + def foo(*args: P.args, **kwargs: P.kwargs) -> None: + + f(*args, **kwargs) # Accepted, should resolve to int + + f(*kwargs, **args) # Rejected + + f(1, *args, **kwargs) # Rejected + + return foo # Accepted + +To extend this to include ``Concatenate``, we declare the following properties: + +* A function of type ``Callable[Concatenate[A, B, P], R]`` can only be + called with ``(a, b, *args, **kwargs)`` when ``args`` and ``kwargs`` are the + respective components of ``P``, ``a`` is of type ``A`` and ``b`` is of + type ``B``. +* A function declared as + ``def inner(a: A, b: B, *args: P.args, **kwargs: P.kwargs) -> R`` + has type ``Callable[Concatenate[A, B, P], R]``. Placing keyword-only + parameters between the ``*args`` and ``**kwargs`` is forbidden. + +.. code-block:: + + def add(f: Callable[P, int]) -> Callable[Concatenate[str, P], None]: + + def foo(s: str, *args: P.args, **kwargs: P.kwargs) -> None: # Accepted + pass + + def bar(*args: P.args, s: str, **kwargs: P.kwargs) -> None: # Rejected + pass + + return foo # Accepted + + + def remove(f: Callable[Concatenate[int, P], int]) -> Callable[P, None]: + + def foo(*args: P.args, **kwargs: P.kwargs) -> None: + f(1, *args, **kwargs) # Accepted + + f(*args, 1, **kwargs) # Rejected + + f(*args, **kwargs) # Rejected + + return foo + +Note that the names of the parameters preceding the ``ParamSpec`` +components are not mentioned in the resulting ``Concatenate``. This means that +these parameters can not be addressed via a named argument: + +.. code-block:: + + def outer(f: Callable[P, None]) -> Callable[P, None]: + def foo(x: int, *args: P.args, **kwargs: P.kwargs) -> None: + f(*args, **kwargs) + + def bar(*args: P.args, **kwargs: P.kwargs) -> None: + foo(1, *args, **kwargs) # Accepted + foo(x=1, *args, **kwargs) # Rejected + + return bar + +.. _above: + +This is not an implementation convenience, but a soundness requirement. If we +were to allow that second calling style, then the following snippet would be +problematic. + +.. code-block:: + + @outer + def problem(*, x: object) -> None: + pass + + problem(x="uh-oh") + +Inside of ``bar``, we would get +``TypeError: foo() got multiple values for argument 'x'``. Requiring these +concatenated arguments to be addressed positionally avoids this kind of problem, +and simplifies the syntax for spelling these types. Note that this also why we +have to reject signatures of the form +``(*args: P.args, s: str, **kwargs: P.kwargs)``. + +If one of these prepended positional parameters contains a free ``ParamSpec``\ , +we consider that variable in scope for the purposes of extracting the components +of that ``ParamSpec``. That allows us to spell things like this: + +.. code-block:: + + def twice(f: Callable[P, int], *args: P.args, **kwargs: P.kwargs) -> int: + return f(*args, **kwargs) + f(*args, **kwargs) + +The type of ``twice`` in the above example is +``Callable[Concatenate[Callable[P, int], P], int]``, where ``P`` is bound by the +outer ``Callable``. This has the following semantics: + +.. code-block:: + + def a_int_b_str(a: int, b: str) -> int: + pass + + twice(a_int_b_str, 1, "A") # Accepted + + twice(a_int_b_str, b="A", a=1) # Accepted + + twice(a_int_b_str, "A", 1) # Rejected + +TypeVarTuple +------------ + +(Originally specified in :pep:`646`.) + +A ``TypeVarTuple`` serves as a placeholder not for a single type +but for a *tuple* of types. + +In addition, we introduce a new use for the star operator: to 'unpack' +``TypeVarTuple`` instances and tuple types such as ``tuple[int, +str]``. Unpacking a ``TypeVarTuple`` or tuple type is the typing +equivalent of unpacking a variable or a tuple of values. + +Type Variable Tuples +^^^^^^^^^^^^^^^^^^^^ + +In the same way that a normal type variable is a stand-in for a single +type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* type such as +``tuple[int, str]``. + +Type variable tuples are created and used with: + +:: + + from typing import TypeVarTuple + + Ts = TypeVarTuple('Ts') + + class Array(Generic[*Ts]): + ... + + def foo(*args: *Ts): + ... + +Or when using the built-in syntax for generics in Python 3.12 and higher:: + + class Array[*Ts]: + ... + + def foo[*Ts](*args: *Ts): + ... + +Using Type Variable Tuples in Generic Classes +""""""""""""""""""""""""""""""""""""""""""""" + +Type variable tuples behave like a number of individual type variables packed in a +``tuple``. To understand this, consider the following example: + +:: + + Shape = TypeVarTuple('Shape') + + class Array(Generic[*Shape]): ... + + Height = NewType('Height', int) + Width = NewType('Width', int) + x: Array[Height, Width] = Array() + +The ``Shape`` type variable tuple here behaves like ``tuple[T1, T2]``, +where ``T1`` and ``T2`` are type variables. To use these type variables +as type parameters of ``Array``, we must *unpack* the type variable tuple using +the star operator: ``*Shape``. The signature of ``Array`` then behaves +as if we had simply written ``class Array(Generic[T1, T2]): ...``. + +In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows +us to parameterise the class with an *arbitrary* number of type parameters. +That is, in addition to being able to define rank-2 arrays such as +``Array[Height, Width]``, we could also define rank-3 arrays, rank-4 arrays, +and so on: + +:: + + Time = NewType('Time', int) + Batch = NewType('Batch', int) + y: Array[Batch, Height, Width] = Array() + z: Array[Time, Batch, Height, Width] = Array() + +Using Type Variable Tuples in Functions +""""""""""""""""""""""""""""""""""""""" + +Type variable tuples can be used anywhere a normal ``TypeVar`` can. +This includes class definitions, as shown above, as well as function +signatures and variable annotations: + +:: + + class Array(Generic[*Shape]): + + def __init__(self, shape: tuple[*Shape]): + self._shape: tuple[*Shape] = shape + + def get_shape(self) -> tuple[*Shape]: + return self._shape + + shape = (Height(480), Width(640)) + x: Array[Height, Width] = Array(shape) + y = abs(x) # Inferred type is Array[Height, Width] + z = x + x # ... is Array[Height, Width] + +Type Variable Tuples Must Always be Unpacked +"""""""""""""""""""""""""""""""""""""""""""" + +Note that in the previous example, the ``shape`` argument to ``__init__`` +was annotated as ``tuple[*Shape]``. Why is this necessary - if ``Shape`` +behaves like ``tuple[T1, T2, ...]``, couldn't we have annotated the ``shape`` +argument as ``Shape`` directly? + +This is, in fact, deliberately not possible: type variable tuples must +*always* be used unpacked (that is, prefixed by the star operator). This is +for two reasons: + +* To avoid potential confusion about whether to use a type variable tuple + in a packed or unpacked form ("Hmm, should I write '``-> Shape``', + or '``-> tuple[Shape]``', or '``-> tuple[*Shape]``'...?") +* To improve readability: the star also functions as an explicit visual + indicator that the type variable tuple is not a normal type variable. + +Variance, Type Constraints and Type Bounds: Not (Yet) Supported +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +``TypeVarTuple`` does not yet support specification of: + +* Variance (e.g. ``TypeVar('T', covariant=True)``) +* Type constraints (``TypeVar('T', int, float)``) +* Type bounds (``TypeVar('T', bound=ParentClass)``) + +We leave the decision of how these arguments should behave to a future PEP, when variadic generics have been tested in the field. As of PEP 646, type variable tuples are +invariant. + +Type Variable Tuple Equality +"""""""""""""""""""""""""""" + +If the same ``TypeVarTuple`` instance is used in multiple places in a signature +or class, a valid type inference might be to bind the ``TypeVarTuple`` to +a ``tuple`` of a union of types: + +:: + + def foo(arg1: tuple[*Ts], arg2: tuple[*Ts]): ... + + a = (0,) + b = ('0',) + foo(a, b) # Can Ts be bound to tuple[int | str]? + +We do *not* allow this; type unions may *not* appear within the ``tuple``. +If a type variable tuple appears in multiple places in a signature, +the types must match exactly (the list of type parameters must be the same +length, and the type parameters themselves must be identical): + +:: + + def pointwise_multiply( + x: Array[*Shape], + y: Array[*Shape] + ) -> Array[*Shape]: ... + + x: Array[Height] + y: Array[Width] + z: Array[Height, Width] + pointwise_multiply(x, x) # Valid + pointwise_multiply(x, y) # Error + pointwise_multiply(x, z) # Error + +Multiple Type Variable Tuples: Not Allowed +"""""""""""""""""""""""""""""""""""""""""" + +Only a single type variable tuple may appear in a type parameter list: + +:: + + class Array(Generic[*Ts1, *Ts2]): ... # Error + +The reason is that multiple type variable tuples make it ambiguous +which parameters get bound to which type variable tuple: :: + + x: Array[int, str, bool] # Ts1 = ???, Ts2 = ??? + +Type Concatenation +^^^^^^^^^^^^^^^^^^ + +Type variable tuples don't have to be alone; normal types can be +prefixed and/or suffixed: + +:: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + Channels = NewType('Channels', int) + + def add_batch_axis(x: Array[*Shape]) -> Array[Batch, *Shape]: ... + def del_batch_axis(x: Array[Batch, *Shape]) -> Array[*Shape]: ... + def add_batch_channels( + x: Array[*Shape] + ) -> Array[Batch, *Shape, Channels]: ... + + a: Array[Height, Width] + b = add_batch_axis(a) # Inferred type is Array[Batch, Height, Width] + c = del_batch_axis(b) # Array[Height, Width] + d = add_batch_channels(a) # Array[Batch, Height, Width, Channels] + + +Normal ``TypeVar`` instances can also be prefixed and/or suffixed: + +:: + + T = TypeVar('T') + Ts = TypeVarTuple('Ts') + + def prefix_tuple( + x: T, + y: tuple[*Ts] + ) -> tuple[T, *Ts]: ... + + z = prefix_tuple(x=0, y=(True, 'a')) + # Inferred type of z is tuple[int, bool, str] + +Unpacking Tuple Types +^^^^^^^^^^^^^^^^^^^^^ + +We mentioned that a ``TypeVarTuple`` stands for a tuple of types. +Since we can unpack a ``TypeVarTuple``, for consistency, we also +allow unpacking a tuple type. As we shall see, this also enables a +number of interesting features. + + +Unpacking Concrete Tuple Types +"""""""""""""""""""""""""""""" + +Unpacking a concrete tuple type is analogous to unpacking a tuple of +values at runtime. ``tuple[int, *tuple[bool, bool], str]`` is +equivalent to ``tuple[int, bool, bool, str]``. + +Unpacking Unbounded Tuple Types +""""""""""""""""""""""""""""""" + +Unpacking an unbounded tuple preserves the unbounded tuple as it is. +That is, ``*tuple[int, ...]`` remains ``*tuple[int, ...]``; there's no +simpler form. This enables us to specify types such as ``tuple[int, +*tuple[str, ...], str]`` - a tuple type where the first element is +guaranteed to be of type ``int``, the last element is guaranteed to be +of type ``str``, and the elements in the middle are zero or more +elements of type ``str``. Note that ``tuple[*tuple[int, ...]]`` is +equivalent to ``tuple[int, ...]``. + +Unpacking unbounded tuples is also useful in function signatures where +we don't care about the exact elements and don't want to define an +unnecessary ``TypeVarTuple``: + +:: + + def process_batch_channels( + x: Array[Batch, *tuple[Any, ...], Channels] + ) -> None: + ... + + + x: Array[Batch, Height, Width, Channels] + process_batch_channels(x) # OK + y: Array[Batch, Channels] + process_batch_channels(y) # OK + z: Array[Batch] + process_batch_channels(z) # Error: Expected Channels. + + +We can also pass a ``*tuple[int, ...]`` wherever a ``*Ts`` is +expected. This is useful when we have particularly dynamic code and +cannot state the precise number of dimensions or the precise types for +each of the dimensions. In those cases, we can smoothly fall back to +an unbounded tuple: + +:: + + y: Array[*tuple[Any, ...]] = read_from_file() + + def expect_variadic_array( + x: Array[Batch, *Shape] + ) -> None: ... + + expect_variadic_array(y) # OK + + def expect_precise_array( + x: Array[Batch, Height, Width, Channels] + ) -> None: ... + + expect_precise_array(y) # OK + +``Array[*tuple[Any, ...]]`` stands for an array with an arbitrary +number of dimensions of type ``Any``. This means that, in the call to +``expect_variadic_array``, ``Batch`` is bound to ``Any`` and ``Shape`` +is bound to ``tuple[Any, ...]``. In the call to +``expect_precise_array``, the variables ``Batch``, ``Height``, +``Width``, and ``Channels`` are all bound to ``Any``. + +This allows users to handle dynamic code gracefully while still +explicitly marking the code as unsafe (by using ``y: Array[*tuple[Any, +...]]``). Otherwise, users would face noisy errors from the type +checker every time they tried to use the variable ``y``, which would +hinder them when migrating a legacy code base to use ``TypeVarTuple``. + +Multiple Unpackings in a Tuple: Not Allowed +""""""""""""""""""""""""""""""""""""""""""" + +As with ``TypeVarTuples``, `only one `_ unpacking may appear in a tuple: + + +:: + + x: tuple[int, *Ts, str, *Ts2] # Error + y: tuple[int, *tuple[int, ...], str, *tuple[str, ...]] # Error + + +``*args`` as a Type Variable Tuple +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:pep:`484` states that when a type annotation is provided for ``*args``, every argument +must be of the type annotated. That is, if we specify ``*args`` to be type ``int``, +then *all* arguments must be of type ``int``. This limits our ability to specify +the type signatures of functions that take heterogeneous argument types. + +If ``*args`` is annotated as a type variable tuple, however, the types of the +individual arguments become the types in the type variable tuple: + +:: + + Ts = TypeVarTuple('Ts') + + def args_to_tuple(*args: *Ts) -> tuple[*Ts]: ... + + args_to_tuple(1, 'a') # Inferred type is tuple[int, str] + +In the above example, ``Ts`` is bound to ``tuple[int, str]``. If no +arguments are passed, the type variable tuple behaves like an empty +tuple, ``tuple[()]``. + +As usual, we can unpack any tuple types. For example, by using a type +variable tuple inside a tuple of other types, we can refer to prefixes +or suffixes of the variadic argument list. For example: + +:: + + # os.execle takes arguments 'path, arg0, arg1, ..., env' + def execle(path: str, *args: *tuple[*Ts, Env]) -> None: ... + +Note that this is different to + +:: + + def execle(path: str, *args: *Ts, env: Env) -> None: ... + +as this would make ``env`` a keyword-only argument. + +Using an unpacked unbounded tuple is equivalent to the +:pep:`484#arbitrary-argument-lists-and-default-argument-values` +behavior of ``*args: int``, which accepts zero or +more values of type ``int``: + +:: + + def foo(*args: *tuple[int, ...]) -> None: ... + + # equivalent to: + def foo(*args: int) -> None: ... + +Unpacking tuple types also allows more precise types for heterogeneous +``*args``. The following function expects an ``int`` at the beginning, +zero or more ``str`` values, and a ``str`` at the end: + +:: + + def foo(*args: *tuple[int, *tuple[str, ...], str]) -> None: ... + +For completeness, we mention that unpacking a concrete tuple allows us +to specify ``*args`` of a fixed number of heterogeneous types: + +:: + + def foo(*args: *tuple[int, str]) -> None: ... + + foo(1, "hello") # OK + +Note that, in keeping with the rule that type variable tuples must always +be used unpacked, annotating ``*args`` as being a plain type variable tuple +instance is *not* allowed: + +:: + + def foo(*args: Ts): ... # NOT valid + +``*args`` is the only case where an argument can be annotated as ``*Ts`` directly; +other arguments should use ``*Ts`` to parameterise something else, e.g. ``tuple[*Ts]``. +If ``*args`` itself is annotated as ``tuple[*Ts]``, the old behaviour still applies: +all arguments must be a ``tuple`` parameterised with the same types. + +:: + + def foo(*args: tuple[*Ts]): ... + + foo((0,), (1,)) # Valid + foo((0,), (1, 2)) # Error + foo((0,), ('1',)) # Error + +Finally, note that a type variable tuple may *not* be used as the type of +``**kwargs``. (We do not yet know of a use case for this feature, so we prefer +to leave the ground fresh for a potential future PEP.) + +:: + + # NOT valid + def foo(**kwargs: *Ts): ... + +Type Variable Tuples with ``Callable`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Type variable tuples can also be used in the arguments section of a +``Callable``: + +:: + + class Process: + def __init__( + self, + target: Callable[[*Ts], None], + args: tuple[*Ts], + ) -> None: ... + + def func(arg1: int, arg2: str) -> None: ... + + Process(target=func, args=(0, 'foo')) # Valid + Process(target=func, args=('foo', 0)) # Error + +Other types and normal type variables can also be prefixed/suffixed +to the type variable tuple: + +:: + + T = TypeVar('T') + + def foo(f: Callable[[int, *Ts, T], tuple[T, *Ts]]): ... + +The behavior of a Callable containing an unpacked item, whether the +item is a ``TypeVarTuple`` or a tuple type, is to treat the elements +as if they were the type for ``*args``. So, ``Callable[[*Ts], None]`` +is treated as the type of the function: + +:: + + def foo(*args: *Ts) -> None: ... + +``Callable[[int, *Ts, T], tuple[T, *Ts]]`` is treated as the type of +the function: + +:: + + def foo(*args: *tuple[int, *Ts, T]) -> tuple[T, *Ts]: ... + +Behaviour when Type Parameters are not Specified +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When a generic class parameterised by a type variable tuple is used without +any type parameters, it behaves as if the type variable tuple was +substituted with ``tuple[Any, ...]``: + +:: + + def takes_any_array(arr: Array): ... + + # equivalent to: + def takes_any_array(arr: Array[*tuple[Any, ...]]): ... + + x: Array[Height, Width] + takes_any_array(x) # Valid + y: Array[Time, Height, Width] + takes_any_array(y) # Also valid + +This enables gradual typing: existing functions accepting, for example, +a plain TensorFlow ``Tensor`` will still be valid even if ``Tensor`` is made +generic and calling code passes a ``Tensor[Height, Width]``. + +This also works in the opposite direction: + +:: + + def takes_specific_array(arr: Array[Height, Width]): ... + + z: Array + # equivalent to Array[*tuple[Any, ...]] + + takes_specific_array(z) + +(For details, see the section on `Unpacking Unbounded Tuple Types`_.) + +This way, even if libraries are updated to use types like ``Array[Height, Width]``, +users of those libraries won't be forced to also apply type annotations to +all of their code; users still have a choice about what parts of their code +to type and which parts to not. + +Aliases +^^^^^^^ + +Generic aliases can be created using a type variable tuple in +a similar way to regular type variables: + +:: + + IntTuple = tuple[int, *Ts] + NamedArray = tuple[str, Array[*Ts]] + + IntTuple[float, bool] # Equivalent to tuple[int, float, bool] + NamedArray[Height] # Equivalent to tuple[str, Array[Height]] + +As this example shows, all type parameters passed to the alias are +bound to the type variable tuple. + +This allows us to define convenience aliases for arrays of a fixed shape +or datatype: + +:: + + Shape = TypeVarTuple('Shape') + DType = TypeVar('DType') + class Array(Generic[DType, *Shape]): + + # E.g. Float32Array[Height, Width, Channels] + Float32Array = Array[np.float32, *Shape] + + # E.g. Array1D[np.uint8] + Array1D = Array[DType, Any] + +If an explicitly empty type parameter list is given, the type variable +tuple in the alias is set empty: + +:: + + IntTuple[()] # Equivalent to tuple[int] + NamedArray[()] # Equivalent to tuple[str, Array[()]] + +If the type parameter list is omitted entirely, the unspecified type +variable tuples are treated as ``tuple[Any, ...]`` (similar to +`Behaviour when Type Parameters are not Specified`_): + +:: + + def takes_float_array_of_any_shape(x: Float32Array): ... + x: Float32Array[Height, Width] = Array() + takes_float_array_of_any_shape(x) # Valid + + def takes_float_array_with_specific_shape( + y: Float32Array[Height, Width] + ): ... + y: Float32Array = Array() + takes_float_array_with_specific_shape(y) # Valid + +Normal ``TypeVar`` instances can also be used in such aliases: + +:: + + T = TypeVar('T') + Foo = tuple[T, *Ts] + + # T bound to str, Ts to tuple[int] + Foo[str, int] + # T bound to float, Ts to tuple[()] + Foo[float] + # T bound to Any, Ts to an tuple[Any, ...] + Foo + + +Substitution in Aliases +^^^^^^^^^^^^^^^^^^^^^^^ + +In the previous section, we only discussed simple usage of generic aliases +in which the type arguments were just simple types. However, a number of +more exotic constructions are also possible. + + +Type Arguments can be Variadic +"""""""""""""""""""""""""""""" + +First, type arguments to generic aliases can be variadic. For example, a +``TypeVarTuple`` can be used as a type argument: + +:: + + Ts1 = TypeVar('Ts1') + Ts2 = TypeVar('Ts2') + + IntTuple = tuple[int, *Ts1] + IntFloatTuple = IntTuple[float, *Ts2] # Valid + +Here, ``*Ts1`` in the ``IntTuple`` alias is bound to ``tuple[float, *Ts2]``, +resulting in an alias ``IntFloatTuple`` equivalent to +``tuple[int, float, *Ts2]``. + +Unpacked arbitrary-length tuples can also be used as type arguments, with +similar effects: + +:: + + IntFloatsTuple = IntTuple[*tuple[float, ...]] # Valid + +Here, ``*Ts1`` is bound to ``*tuple[float, ...]``, resulting in +``IntFloatsTuple`` being equivalent to ``tuple[int, *tuple[float, ...]]``: a tuple +consisting of an ``int`` then zero or more ``float``\s. + + +Variadic Arguments Require Variadic Aliases +""""""""""""""""""""""""""""""""""""""""""" + +Variadic type arguments can only be used with generic aliases that are +themselves variadic. For example: + +:: + + T = TypeVar('T') + + IntTuple = tuple[int, T] + + IntTuple[str] # Valid + IntTuple[*Ts] # NOT valid + IntTuple[*tuple[float, ...]] # NOT valid + +Here, ``IntTuple`` is a *non*-variadic generic alias that takes exactly one +type argument. Hence, it cannot accept ``*Ts`` or ``*tuple[float, ...]`` as type +arguments, because they represent an arbitrary number of types. + + +Aliases with Both TypeVars and TypeVarTuples +"""""""""""""""""""""""""""""""""""""""""""" + +In `Aliases`_, we briefly mentioned that aliases can be generic in both +``TypeVar``\s and ``TypeVarTuple``\s: + +:: + + T = TypeVar('T') + Foo = tuple[T, *Ts] + + Foo[str, int] # T bound to str, Ts to tuple[int] + Foo[str, int, float] # T bound to str, Ts to tuple[int, float] + +In accordance with `Multiple Type Variable Tuples: Not Allowed`_, at most one +``TypeVarTuple`` may appear in the type parameters to an alias. However, a +``TypeVarTuple`` can be combined with an arbitrary number of ``TypeVar``\s, +both before and after: + +:: + + T1 = TypeVar('T1') + T2 = TypeVar('T2') + T3 = TypeVar('T3') + + tuple[*Ts, T1, T2] # Valid + tuple[T1, T2, *Ts] # Valid + tuple[T1, *Ts, T2, T3] # Valid + +In order to substitute these type variables with supplied type arguments, +any type variables at the beginning or end of the type parameter list first +consume type arguments, and then any remaining type arguments are bound +to the ``TypeVarTuple``: + +:: + + Shrubbery = tuple[*Ts, T1, T2] + + Shrubbery[str, bool] # T2=bool, T1=str, Ts=tuple[()] + Shrubbery[str, bool, float] # T2=float, T1=bool, Ts=tuple[str] + Shrubbery[str, bool, float, int] # T2=int, T1=float, Ts=tuple[str, bool] + + Ptang = tuple[T1, *Ts, T2, T3] + + Ptang[str, bool, float] # T1=str, T3=float, T2=bool, Ts=tuple[()] + Ptang[str, bool, float, int] # T1=str, T3=int, T2=float, Ts=tuple[bool] + +Note that the minimum number of type arguments in such cases is set by +the number of ``TypeVar``\s: + +:: + + Shrubbery[int] # Not valid; Shrubbery needs at least two type arguments + + +Splitting Arbitrary-Length Tuples +""""""""""""""""""""""""""""""""" + +A final complication occurs when an unpacked arbitrary-length tuple is used +as a type argument to an alias consisting of both ``TypeVar``\s and a +``TypeVarTuple``: + +:: + + Elderberries = tuple[*Ts, T1] + Hamster = Elderberries[*tuple[int, ...]] # valid + +In such cases, the arbitrary-length tuple is split between the ``TypeVar``\s +and the ``TypeVarTuple``. We assume the arbitrary-length tuple contains +at least as many items as there are ``TypeVar``\s, such that individual +instances of the inner type - here ``int`` - are bound to any ``TypeVar``\s +present. The 'rest' of the arbitrary-length tuple - here ``*tuple[int, ...]``, +since a tuple of arbitrary length minus two items is still arbitrary-length - +is bound to the ``TypeVarTuple``. + +Here, therefore, ``Hamster`` is equivalent to ``tuple[*tuple[int, ...], int]``: +a tuple consisting of zero or more ``int``\s, then a final ``int``. + +Of course, such splitting only occurs if necessary. For example, if we instead +did: + +:: + + Elderberries[*tuple[int, ...], str] + +Then splitting would not occur; ``T1`` would be bound to ``str``, and +``Ts`` to ``*tuple[int, ...]``. + +In particularly awkward cases, a ``TypeVarTuple`` may consume both a type +*and* a part of an arbitrary-length tuple type: + +:: + + Elderberries[str, *tuple[int, ...]] + +Here, ``T1`` is bound to ``int``, and ``Ts`` is bound to +``tuple[str, *tuple[int, ...]]``. This expression is therefore equivalent to +``tuple[str, *tuple[int, ...], int]``: a tuple consisting of a ``str``, then +zero or more ``int``\s, ending with an ``int``. + + +TypeVarTuples Cannot be Split +""""""""""""""""""""""""""""" + +Finally, although any arbitrary-length tuples in the type argument list can be +split between the type variables and the type variable tuple, the same is not +true of ``TypeVarTuple``\s in the argument list: + +:: + + Ts1 = TypeVarTuple('Ts1') + Ts2 = TypeVarTuple('Ts2') + + Camelot = tuple[T, *Ts1] + Camelot[*Ts2] # NOT valid + +This is not possible because, unlike in the case of an unpacked arbitrary-length +tuple, there is no way to 'peer inside' the ``TypeVarTuple`` to see what its +individual types are. + + +Overloads for Accessing Individual Types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For situations where we require access to each individual type in the type variable tuple, +overloads can be used with individual ``TypeVar`` instances in place of the type variable tuple: + +:: + + Shape = TypeVarTuple('Shape') + Axis1 = TypeVar('Axis1') + Axis2 = TypeVar('Axis2') + Axis3 = TypeVar('Axis3') + + class Array(Generic[*Shape]): + + @overload + def transpose( + self: Array[Axis1, Axis2] + ) -> Array[Axis2, Axis1]: ... + + @overload + def transpose( + self: Array[Axis1, Axis2, Axis3] + ) -> Array[Axis3, Axis2, Axis1]: ... + +(For array shape operations in particular, having to specify +overloads for each possible rank is, of course, a rather cumbersome +solution. However, it's the best we can do without additional type +manipulation mechanisms.) + +``Self`` +-------- + +(Originally specified in :pep:`673`.) + +Use in Method Signatures +^^^^^^^^^^^^^^^^^^^^^^^^ + +``Self`` used in the signature of a method is treated as if it were a +``TypeVar`` bound to the class. + +:: + + from typing import Self + + class Shape: + def set_scale(self, scale: float) -> Self: + self.scale = scale + return self + +is treated equivalently to: + +:: + + from typing import TypeVar + + SelfShape = TypeVar("SelfShape", bound="Shape") + + class Shape: + def set_scale(self: SelfShape, scale: float) -> SelfShape: + self.scale = scale + return self + +This works the same for a subclass too: + +:: + + class Circle(Shape): + def set_radius(self, radius: float) -> Self: + self.radius = radius + return self + +which is treated equivalently to: + +:: + + SelfCircle = TypeVar("SelfCircle", bound="Circle") + + class Circle(Shape): + def set_radius(self: SelfCircle, radius: float) -> SelfCircle: + self.radius = radius + return self + +One implementation strategy is to simply desugar the former to the latter in a +preprocessing step. If a method uses ``Self`` in its signature, the type of +``self`` within a method will be ``Self``. In other cases, the type of +``self`` will remain the enclosing class. + + +Use in Classmethod Signatures +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``Self`` type annotation is also useful for classmethods that return +an instance of the class that they operate on. For example, ``from_config`` in +the following snippet builds a ``Shape`` object from a given ``config``. + +:: + + class Shape: + def __init__(self, scale: float) -> None: ... + + @classmethod + def from_config(cls, config: dict[str, float]) -> Shape: + return cls(config["scale"]) + + +However, this means that ``Circle.from_config(...)`` is inferred to return a +value of type ``Shape``, when in fact it should be ``Circle``: + +:: + + class Circle(Shape): + def circumference(self) -> float: ... + + shape = Shape.from_config({"scale": 7.0}) + # => Shape + + circle = Circle.from_config({"scale": 7.0}) + # => *Shape*, not Circle + + circle.circumference() + # Error: `Shape` has no attribute `circumference` + + +The current workaround for this is unintuitive and error-prone: + +:: + + Self = TypeVar("Self", bound="Shape") + + class Shape: + @classmethod + def from_config( + cls: type[Self], config: dict[str, float] + ) -> Self: + return cls(config["scale"]) + +Instead, ``Self`` can be used directly: + +:: + + from typing import Self + + class Shape: + @classmethod + def from_config(cls, config: dict[str, float]) -> Self: + return cls(config["scale"]) + +This avoids the complicated ``cls: type[Self]`` annotation and the ``TypeVar`` +declaration with a ``bound``. Once again, the latter code behaves equivalently +to the former code. + +Use in Parameter Types +^^^^^^^^^^^^^^^^^^^^^^ + +Another use for ``Self`` is to annotate parameters that expect instances of +the current class: + +:: + + Self = TypeVar("Self", bound="Shape") + + class Shape: + def difference(self: Self, other: Self) -> float: ... + + def apply(self: Self, f: Callable[[Self], None]) -> None: ... + +``Self`` can be used directly to achieve the same behavior: + +:: + + from typing import Self + + class Shape: + def difference(self, other: Self) -> float: ... + + def apply(self, f: Callable[[Self], None]) -> None: ... + +Note that specifying ``self: Self`` is harmless, so some users may find it +more readable to write the above as: + +:: + + class Shape: + def difference(self: Self, other: Self) -> float: ... + +Use in Attribute Annotations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Another use for ``Self`` is to annotate attributes. One example is where we +have a ``LinkedList`` whose elements must be subclasses of the current class. + +:: + + from dataclasses import dataclass + from typing import Generic, TypeVar + + T = TypeVar("T") + + @dataclass + class LinkedList(Generic[T]): + value: T + next: LinkedList[T] | None = None + + # OK + LinkedList[int](value=1, next=LinkedList[int](value=2)) + # Not OK + LinkedList[int](value=1, next=LinkedList[str](value="hello")) + + +However, annotating the ``next`` attribute as ``LinkedList[T]`` allows invalid +constructions with subclasses: + +:: + + @dataclass + class OrdinalLinkedList(LinkedList[int]): + def ordinal_value(self) -> str: + return as_ordinal(self.value) + + # Should not be OK because LinkedList[int] is not a subclass of + # OrdinalLinkedList, # but the type checker allows it. + xs = OrdinalLinkedList(value=1, next=LinkedList[int](value=2)) + + if xs.next: + print(xs.next.ordinal_value()) # Runtime Error. + + +This constraint can be expressed using ``next: Self | None``: + +:: + + from typing import Self + + @dataclass + class LinkedList(Generic[T]): + value: T + next: Self | None = None + + @dataclass + class OrdinalLinkedList(LinkedList[int]): + def ordinal_value(self) -> str: + return as_ordinal(self.value) + + xs = OrdinalLinkedList(value=1, next=LinkedList[int](value=2)) + # Type error: Expected OrdinalLinkedList, got LinkedList[int]. + + if xs.next is not None: + xs.next = OrdinalLinkedList(value=3, next=None) # OK + xs.next = LinkedList[int](value=3, next=None) # Not OK + + + +The code above is semantically equivalent to treating each attribute +containing a ``Self`` type as a ``property`` that returns that type: + +:: + + from dataclasses import dataclass + from typing import Any, Generic, TypeVar + + T = TypeVar("T") + Self = TypeVar("Self", bound="LinkedList") + + + class LinkedList(Generic[T]): + value: T + + @property + def next(self: Self) -> Self | None: + return self._next + + @next.setter + def next(self: Self, next: Self | None) -> None: + self._next = next + + class OrdinalLinkedList(LinkedList[int]): + def ordinal_value(self) -> str: + return str(self.value) + +Use in Generic Classes +^^^^^^^^^^^^^^^^^^^^^^ + +``Self`` can also be used in generic class methods: + +:: + + class Container(Generic[T]): + value: T + def set_value(self, value: T) -> Self: ... + + +This is equivalent to writing: + +:: + + Self = TypeVar("Self", bound="Container[Any]") + + class Container(Generic[T]): + value: T + def set_value(self: Self, value: T) -> Self: ... + + +The behavior is to preserve the type argument of the object on which the +method was called. When called on an object with concrete type +``Container[int]``, ``Self`` is bound to ``Container[int]``. When called with +an object of generic type ``Container[T]``, ``Self`` is bound to +``Container[T]``: + +:: + + def object_with_concrete_type() -> None: + int_container: Container[int] + str_container: Container[str] + reveal_type(int_container.set_value(42)) # => Container[int] + reveal_type(str_container.set_value("hello")) # => Container[str] + + def object_with_generic_type( + container: Container[T], value: T, + ) -> Container[T]: + return container.set_value(value) # => Container[T] + + +The PEP doesn’t specify the exact type of ``self.value`` within the method +``set_value``. Some type checkers may choose to implement ``Self`` types using +class-local type variables with ``Self = TypeVar(“Self”, +bound=Container[T])``, which will infer a precise type ``T``. However, given +that class-local type variables are not a standardized type system feature, it +is also acceptable to infer ``Any`` for ``self.value``. We leave this up to +the type checker. + +Note that we reject using ``Self`` with type arguments, such as ``Self[int]``. +This is because it creates ambiguity about the type of the ``self`` parameter +and introduces unnecessary complexity: + +:: + + class Container(Generic[T]): + def foo( + self, other: Self[int], other2: Self, + ) -> Self[str]: # Rejected + ... + +In such cases, we recommend using an explicit type for ``self``: + +:: + + class Container(Generic[T]): + def foo( + self: Container[T], + other: Container[int], + other2: Container[T] + ) -> Container[str]: ... + + +Use in Protocols +^^^^^^^^^^^^^^^^ + +``Self`` is valid within Protocols, similar to its use in classes: + +:: + + from typing import Protocol, Self + + class ShapeProtocol(Protocol): + scale: float + + def set_scale(self, scale: float) -> Self: + self.scale = scale + return self + +is treated equivalently to: + +:: + + from typing import TypeVar + + SelfShape = TypeVar("SelfShape", bound="ShapeProtocol") + + class ShapeProtocol(Protocol): + scale: float + + def set_scale(self: SelfShape, scale: float) -> SelfShape: + self.scale = scale + return self + + +See :pep:`PEP 544 +<544#self-types-in-protocols>` for +details on the behavior of TypeVars bound to protocols. + +Checking a class for compatibility with a protocol: If a protocol uses +``Self`` in methods or attribute annotations, then a class ``Foo`` is +considered compatible with the protocol if its corresponding methods and +attribute annotations use either ``Self`` or ``Foo`` or any of ``Foo``’s +subclasses. See the examples below: + +:: + + from typing import Protocol + + class ShapeProtocol(Protocol): + def set_scale(self, scale: float) -> Self: ... + + class ReturnSelf: + scale: float = 1.0 + + def set_scale(self, scale: float) -> Self: + self.scale = scale + return self + + class ReturnConcreteShape: + scale: float = 1.0 + + def set_scale(self, scale: float) -> ReturnConcreteShape: + self.scale = scale + return self + + class BadReturnType: + scale: float = 1.0 + + def set_scale(self, scale: float) -> int: + self.scale = scale + return 42 + + class ReturnDifferentClass: + scale: float = 1.0 + + def set_scale(self, scale: float) -> ReturnConcreteShape: + return ReturnConcreteShape(...) + + def accepts_shape(shape: ShapeProtocol) -> None: + y = shape.set_scale(0.5) + reveal_type(y) + + def main() -> None: + return_self_shape: ReturnSelf + return_concrete_shape: ReturnConcreteShape + bad_return_type: BadReturnType + return_different_class: ReturnDifferentClass + + accepts_shape(return_self_shape) # OK + accepts_shape(return_concrete_shape) # OK + accepts_shape(bad_return_type) # Not OK + # Not OK because it returns a non-subclass. + accepts_shape(return_different_class) + + +Valid Locations for ``Self`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A ``Self`` annotation is only valid in class contexts, and will always refer +to the encapsulating class. In contexts involving nested classes, ``Self`` +will always refer to the innermost class. + +The following uses of ``Self`` are accepted: + +:: + + class ReturnsSelf: + def foo(self) -> Self: ... # Accepted + + @classmethod + def bar(cls) -> Self: # Accepted + return cls() + + def __new__(cls, value: int) -> Self: ... # Accepted + + def explicitly_use_self(self: Self) -> Self: ... # Accepted + + # Accepted (Self can be nested within other types) + def returns_list(self) -> list[Self]: ... + + # Accepted (Self can be nested within other types) + @classmethod + def return_cls(cls) -> type[Self]: + return cls + + class Child(ReturnsSelf): + # Accepted (we can override a method that uses Self annotations) + def foo(self) -> Self: ... + + class TakesSelf: + def foo(self, other: Self) -> bool: ... # Accepted + + class Recursive: + # Accepted (treated as an @property returning ``Self | None``) + next: Self | None + + class CallableAttribute: + def foo(self) -> int: ... + + # Accepted (treated as an @property returning the Callable type) + bar: Callable[[Self], int] = foo + + class HasNestedFunction: + x: int = 42 + + def foo(self) -> None: + + # Accepted (Self is bound to HasNestedFunction). + def nested(z: int, inner_self: Self) -> Self: + print(z) + print(inner_self.x) + return inner_self + + nested(42, self) # OK + + + class Outer: + class Inner: + def foo(self) -> Self: ... # Accepted (Self is bound to Inner) + + +The following uses of ``Self`` are rejected. + +:: + + def foo(bar: Self) -> Self: ... # Rejected (not within a class) + + bar: Self # Rejected (not within a class) + + class Foo: + # Rejected (Self is treated as unknown). + def has_existing_self_annotation(self: T) -> Self: ... + + class Foo: + def return_concrete_type(self) -> Self: + return Foo() # Rejected (see FooChild below for rationale) + + class FooChild(Foo): + child_value: int = 42 + + def child_method(self) -> None: + # At runtime, this would be Foo, not FooChild. + y = self.return_concrete_type() + + y.child_value + # Runtime error: Foo has no attribute child_value + + class Bar(Generic[T]): + def bar(self) -> T: ... + + class Baz(Bar[Self]): ... # Rejected + +We reject type aliases containing ``Self``. Supporting ``Self`` +outside class definitions can require a lot of special-handling in +type checkers. Given that it also goes against the rest of the PEP to +use ``Self`` outside a class definition, we believe the added +convenience of aliases is not worth it: + +:: + + TupleSelf = Tuple[Self, Self] # Rejected + + class Alias: + def return_tuple(self) -> TupleSelf: # Rejected + return (self, self) + +Note that we reject ``Self`` in staticmethods. ``Self`` does not add much +value since there is no ``self`` or ``cls`` to return. The only possible use +cases would be to return a parameter itself or some element from a container +passed in as a parameter. These don’t seem worth the additional complexity. + +:: + + class Base: + @staticmethod + def make() -> Self: # Rejected + ... + + @staticmethod + def return_parameter(foo: Self) -> Self: # Rejected + ... + +Likewise, we reject ``Self`` in metaclasses. ``Self`` consistently refers to the +same type (that of ``self``). But in metaclasses, it would have to refer to +different types in different method signatures. For example, in ``__mul__``, +``Self`` in the return type would refer to the implementing class +``Foo``, not the enclosing class ``MyMetaclass``. But, in ``__new__``, ``Self`` +in the return type would refer to the enclosing class ``MyMetaclass``. To +avoid confusion, we reject this edge case. + +:: + + class MyMetaclass(type): + def __new__(cls, *args: Any) -> Self: # Rejected + return super().__new__(cls, *args) + + def __mul__(cls, count: int) -> list[Self]: # Rejected + return [cls()] * count + + class Foo(metaclass=MyMetaclass): ... + +Variance Inference +------------------ + +(Originally specified by :pep:`695`.) + +The introduction of explicit syntax for generic classes in Python 3.12 +eliminates the need for variance to be specified for type +parameters. Instead, type checkers will infer the variance of type parameters +based on their usage within a class. Type parameters are inferred to be +invariant, covariant, or contravariant depending on how they are used. + +Python type checkers already include the ability to determine the variance of +type parameters for the purpose of validating variance within a generic +protocol class. This capability can be used for all classes (whether or not +they are protocols) to calculate the variance of each type parameter. + +The algorithm for computing the variance of a type parameter is as follows. + +For each type parameter in a generic class: + +1. If the type parameter is variadic (``TypeVarTuple``) or a parameter +specification (``ParamSpec``), it is always considered invariant. No further +inference is needed. + +2. If the type parameter comes from a traditional ``TypeVar`` declaration and +is not specified as ``infer_variance`` (see below), its variance is specified +by the ``TypeVar`` constructor call. No further inference is needed. + +3. Create two specialized versions of the class. We'll refer to these as +``upper`` and ``lower`` specializations. In both of these specializations, +replace all type parameters other than the one being inferred by a dummy type +instance (a concrete anonymous class that is type compatible with itself and +assumed to meet the bounds or constraints of the type parameter). In +the ``upper`` specialized class, specialize the target type parameter with +an ``object`` instance. This specialization ignores the type parameter's +upper bound or constraints. In the ``lower`` specialized class, specialize +the target type parameter with itself (i.e. the corresponding type argument +is the type parameter itself). + +4. Determine whether ``lower`` can be assigned to ``upper`` using normal type +compatibility rules. If so, the target type parameter is covariant. If not, +determine whether ``upper`` can be assigned to ``lower``. If so, the target +type parameter is contravariant. If neither of these combinations are +assignable, the target type parameter is invariant. + +Here is an example. + +:: + + class ClassA[T1, T2, T3](list[T1]): + def method1(self, a: T2) -> None: + ... + + def method2(self) -> T3: + ... + +To determine the variance of ``T1``, we specialize ``ClassA`` as follows: + +:: + + upper = ClassA[object, Dummy, Dummy] + lower = ClassA[T1, Dummy, Dummy] + +We find that ``upper`` is not assignable to ``lower`` using normal type +compatibility rules defined in :pep:`484`. Likewise, ``lower`` is not assignable +to ``upper``, so we conclude that ``T1`` is invariant. + +To determine the variance of ``T2``, we specialize ``ClassA`` as follows: + +:: + + upper = ClassA[Dummy, object, Dummy] + lower = ClassA[Dummy, T2, Dummy] + +Since ``upper`` is assignable to ``lower``, ``T2`` is contravariant. + +To determine the variance of ``T3``, we specialize ``ClassA`` as follows: + +:: + + upper = ClassA[Dummy, Dummy, object] + lower = ClassA[Dummy, Dummy, T3] + +Since ``lower`` is assignable to ``upper``, ``T3`` is covariant. + + +Auto Variance For TypeVar +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The existing ``TypeVar`` class constructor accepts keyword parameters named +``covariant`` and ``contravariant``. If both of these are ``False``, the +type variable is assumed to be invariant. PEP 695 adds another keyword +parameter named ``infer_variance`` indicating that a type checker should use +inference to determine whether the type variable is invariant, covariant or +contravariant. A corresponding instance variable ``__infer_variance__`` can be +accessed at runtime to determine whether the variance is inferred. Type +variables that are implicitly allocated using the new syntax will always +have ``__infer_variance__`` set to ``True``. + +A generic class that uses the traditional syntax may include combinations of +type variables with explicit and inferred variance. + +:: + + T1 = TypeVar("T1", infer_variance=True) # Inferred variance + T2 = TypeVar("T2") # Invariant + T3 = TypeVar("T3", covariant=True) # Covariant + + # A type checker should infer the variance for T1 but use the + # specified variance for T2 and T3. + class ClassA(Generic[T1, T2, T3]): ... + + +Compatibility with Traditional TypeVars +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The existing mechanism for allocating ``TypeVar``, ``TypeVarTuple``, and +``ParamSpec`` is retained for backward compatibility. However, these +"traditional" type variables should not be combined with type parameters +allocated using the new syntax. Such a combination should be flagged as +an error by type checkers. This is necessary because the type parameter +order is ambiguous. + +It is OK to combine traditional type variables with new-style type parameters +if the class, function, or type alias does not use the new syntax. The +new-style type parameters must come from an outer scope in this case. + +:: + + K = TypeVar("K") + + class ClassA[V](dict[K, V]): ... # Type checker error + + class ClassB[K, V](dict[K, V]): ... # OK + + class ClassC[V]: + # The use of K and V for "method1" is OK because it uses the + # "traditional" generic function mechanism where type parameters + # are implicit. In this case V comes from an outer scope (ClassC) + # and K is introduced implicitly as a type parameter for "method1". + def method1(self, a: V, b: K) -> V | K: ... + + # The use of M and K are not allowed for "method2". A type checker + # should generate an error in this case because this method uses the + # new syntax for type parameters, and all type parameters associated + # with the method must be explicitly declared. In this case, ``K`` + # is not declared by "method2", nor is it supplied by a new-style + # type parameter defined in an outer scope. + def method2[M](self, a: M, b: K) -> M | K: ... diff --git a/docs/spec/historical.rst b/docs/spec/historical.rst new file mode 100644 index 000000000..b505fc327 --- /dev/null +++ b/docs/spec/historical.rst @@ -0,0 +1,291 @@ +.. _historical: + +Historical and deprecated features +================================== + +Over the course of the development of the Python type system, several +changes were made to the Python grammar and standard library to make it +easier to use the type system. This specification aims to use the more +modern syntax in all examples, but type checkers should generally support +the older alternatives and treat them as equivalent. + +This section lists all of these cases. + +Type comments +------------- + +No first-class syntax support for explicitly marking variables as being +of a specific type existed when the type system was first designed. +To help with type inference in +complex cases, a comment of the following format may be used:: + + x = [] # type: list[Employee] + x, y, z = [], [], [] # type: list[int], list[int], list[str] + x, y, z = [], [], [] # type: (list[int], list[int], list[str]) + a, b, *c = range(5) # type: float, float, list[float] + x = [1, 2] # type: list[int] + +Type comments should be put on the last line of the statement that +contains the variable definition. + +These should be treated as equivalent to annotating the variables +using :pep:`526` variable annotations:: + + x: list[Employee] = [] + x: list[int] + y: list[int] + z: list[str] + x, y, z = [], [], [] + a: float + b: float + c: list[float] + a, b, *c = range(5) + x: list[int] = [1, 2] + +Type comments can also be placed on +``with`` statements and ``for`` statements, right after the colon. + +Examples of type comments on ``with`` and ``for`` statements:: + + with frobnicate() as foo: # type: int + # Here foo is an int + ... + + for x, y in points: # type: float, float + # Here x and y are floats + ... + +In stubs it may be useful to declare the existence of a variable +without giving it an initial value. This can be done using :pep:`526` +variable annotation syntax:: + + from typing import IO + + stream: IO[str] + +The above syntax is acceptable in stubs for all versions of Python. +However, in non-stub code for versions of Python 3.5 and earlier +there is a special case:: + + from typing import IO + + stream = None # type: IO[str] + +Type checkers should not complain about this (despite the value +``None`` not matching the given type), nor should they change the +inferred type to ``... | None``. The +assumption here is that other code will ensure that the variable is +given a value of the proper type, and all uses can assume that the +variable has the given type. + +Type comments on function definitions +------------------------------------- + +Some tools may want to support type annotations in code that must be +compatible with Python 2.7. For this purpose function annotations can be placed in +a ``# type:`` comment. Such a comment must be placed immediately +following the function header (before the docstring). An example: the +following Python 3 code:: + + def embezzle(self, account: str, funds: int = 1000000, *fake_receipts: str) -> None: + """Embezzle funds from account using fake receipts.""" + + +is equivalent to the following:: + + def embezzle(self, account, funds=1000000, *fake_receipts): + # type: (str, int, *str) -> None + """Embezzle funds from account using fake receipts.""" + + +Note that for methods, no type is needed for ``self``. + +For an argument-less method it would look like this:: + + def load_cache(self): + # type: () -> bool + + +Sometimes you want to specify the return type for a function or method +without (yet) specifying the argument types. To support this +explicitly, the argument list may be replaced with an ellipsis. +Example:: + + def send_email(address, sender, cc, bcc, subject, body): + # type: (...) -> bool + """Send an email message. Return True if successful.""" + + +Sometimes you have a long list of parameters and specifying their +types in a single ``# type:`` comment would be awkward. To this end +you may list the arguments one per line and add a ``# type:`` comment +per line after an argument's associated comma, if any. +To specify the return type use the ellipsis syntax. Specifying the return +type is not mandatory and not every argument needs to be given a type. +A line with a ``# type:`` comment should contain exactly one argument. +The type comment for the last argument (if any) should precede the close +parenthesis. Example:: + + def send_email(address, # type: Union[str, List[str]] + sender, # type: str + cc, # type: Optional[List[str]] + bcc, # type: Optional[List[str]] + subject='', + body=None # type: List[str] + ): + # type: (...) -> bool + """Send an email message. Return True if successful.""" + + +Notes: + +- Tools that support this syntax should support it regardless of the + Python version being checked. This is necessary in order to support + code that straddles Python 2 and Python 3. + +- It is not allowed for an argument or return value to have both + a type annotation and a type comment. + +- When using the short form (e.g. ``# type: (str, int) -> None``) + every argument must be accounted for, except the first argument of + instance and class methods (those are usually omitted, but it's + allowed to include them). + +- The return type is mandatory for the short form. If in Python 3 you + would omit some argument or the return type, the Python 2 notation + should use ``Any``. + +- When using the short form, for ``*args`` and ``**kwds``, put 1 or 2 + stars in front of the corresponding type annotation. (As with + Python 3 annotations, the annotation here denotes the type of the + individual argument values, not of the tuple/dict that you receive + as the special argument value ``args`` or ``kwds``.) + +- Like other type comments, any names used in the annotations must be + imported or defined by the module containing the annotation. + +- When using the short form, the entire annotation must be one line. + +- The short form may also occur on the same line as the close + parenthesis, e.g.:: + + def add(a, b): # type: (int, int) -> int + return a + b + +- Misplaced type comments will be flagged as errors by a type checker. + If necessary, such comments could be commented twice. For example:: + + def f(): + '''Docstring''' + # type: () -> None # Error! + + def g(): + '''Docstring''' + # # type: () -> None # This is OK + +When checking Python 2.7 code, type checkers should treat the ``int`` and +``long`` types as equivalent. For parameters typed as ``Text``, arguments of +type ``str`` as well as ``unicode`` should be acceptable. + + +Positional-only arguments +------------------------- + +Some functions are designed to take their arguments only positionally, +and expect their callers never to use the argument's name to provide +that argument by keyword. Before Python 3.8 (:pep:`570`), Python did +not provide a way to declare positional-only arguments. + +To support positional-only arguments on older Python versions, type +checkers support the following special case: +all arguments with names beginning with +``__`` are assumed to be positional-only, except if their names also +end with ``__``:: + + def quux(__x: int, __y__: int = 0) -> None: ... + + quux(3, __y__=1) # This call is fine. + + quux(__x=3) # This call is an error. + + +Generics in standard collections +-------------------------------- + +Before Python 3.9 (:pep:`585`), standard library generic types like +``list`` could not be parameterized at runtime (i.e., ``list[int]`` +would throw an error). Therefore, the ``typing`` module provided +generic aliases for major builtin and standard library types (e.g., +``typing.List[int]``). + +In each of these cases, type checkers should treat the library type +as equivalent to the alias in the ``typing`` module. This includes: + +* ``list`` and ``typing.List`` +* ``dict`` and ``typing.Dict`` +* ``set`` and ``typing.Set`` +* ``frozenset`` and ``typing.FrozenSet`` +* ``tuple`` and ``typing.Tuple`` +* ``type`` and ``typing.Type`` +* ``collections.deque`` and ``typing.Deque`` +* ``collections.defaultdict`` and ``typing.DefaultDict`` +* ``collections.OrderedDict`` and ``typing.OrderedDict`` +* ``collections.Counter`` and ``typing.Counter`` +* ``collections.ChainMap`` and ``typing.ChainMap`` +* ``collections.abc.Awaitable`` and ``typing.Awaitable`` +* ``collections.abc.Coroutine`` and ``typing.Coroutine`` +* ``collections.abc.AsyncIterable`` and ``typing.AsyncIterable`` +* ``collections.abc.AsyncIterator`` and ``typing.AsyncIterator`` +* ``collections.abc.AsyncGenerator`` and ``typing.AsyncGenerator`` +* ``collections.abc.Iterable`` and ``typing.Iterable`` +* ``collections.abc.Iterator`` and ``typing.Iterator`` +* ``collections.abc.Generator`` and ``typing.Generator`` +* ``collections.abc.Reversible`` and ``typing.Reversible`` +* ``collections.abc.Container`` and ``typing.Container`` +* ``collections.abc.Collection`` and ``typing.Collection`` +* ``collections.abc.Callable`` and ``typing.Callable`` +* ``collections.abc.Set`` and ``typing.AbstractSet`` (note the change in name) +* ``collections.abc.MutableSet`` and ``typing.MutableSet`` +* ``collections.abc.Mapping`` and ``typing.Mapping`` +* ``collections.abc.MutableMapping`` and ``typing.MutableMapping`` +* ``collections.abc.Sequence`` and ``typing.Sequence`` +* ``collections.abc.MutableSequence`` and ``typing.MutableSequence`` +* ``collections.abc.ByteString`` and ``typing.ByteString`` +* ``collections.abc.MappingView`` and ``typing.MappingView`` +* ``collections.abc.KeysView`` and ``typing.KeysView`` +* ``collections.abc.ItemsView`` and ``typing.ItemsView`` +* ``collections.abc.ValuesView`` and ``typing.ValuesView`` +* ``contextlib.AbstractContextManager`` and ``typing.ContextManager`` (note the change in name) +* ``contextlib.AbstractAsyncContextManager`` and ``typing.AsyncContextManager`` (note the change in name) +* ``re.Pattern`` and ``typing.Pattern`` +* ``re.Match`` and ``typing.Match`` + +The generic aliases in the ``typing`` module are considered deprecated +and type checkers may warn if they are used. + +``Union`` and ``Optional`` +-------------------------- + +Before Python 3.10 (:pep:`604`), Python did not support the ``|`` operator +for creating unions of types. Therefore, the ``typing.Union`` special form can also +be used to create union types. Type checkers should treat the two forms as equivalent. + +In addition, the ``Optional`` special form provides a shortcut for a union with ``None``. + +Examples: + +* ``int | str`` is the same as ``Union[int, str]`` +* ``int | str | range`` is the same as ``Union[int, str, range]`` +* ``int | None`` is the same as ``Optional[int]`` and ``Union[int, None]`` + +``Unpack`` +---------- + +:pep:`646`, which introduced ``TypeVarTuple`` into Python 3.11, also made two grammar +changes to support use of variadic generics, allowing use of the ``*`` operator in +index operations and in ``*args`` annotations. The ``Unpack[]`` operator was added to +support equivalent semantics on older Python versions. It should be treated as equivalent +to the ``*`` syntax. In particular, the following are equivalent: + +* ``A[*Ts]`` is the same as ``A[Unpack[Ts]]`` +* ``def f(*args: *Ts): ...`` is the same as ``def f(*args: Unpack[Ts]): ...`` diff --git a/docs/spec/index.rst b/docs/spec/index.rst new file mode 100644 index 000000000..028e0fcbb --- /dev/null +++ b/docs/spec/index.rst @@ -0,0 +1,25 @@ +Specification for the Python type system +======================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + type-system + concepts + annotations + special-types + generics + qualifiers + class-compat + aliases + literal + protocol + callables + overload + dataclasses + typeddict + narrowing + directives + distributing + historical diff --git a/docs/spec/literal.rst b/docs/spec/literal.rst new file mode 100644 index 000000000..9f800b704 --- /dev/null +++ b/docs/spec/literal.rst @@ -0,0 +1,818 @@ +Literals +======== + +``Literal`` +----------- + +(Originally specified in :pep:`586`.) + + +Core Semantics +^^^^^^^^^^^^^^ + +This section outlines the baseline behavior of literal types. + +Core behavior +""""""""""""" + +Literal types indicate that a variable has a specific and +concrete value. For example, if we define some variable ``foo`` to have +type ``Literal[3]``, we are declaring that ``foo`` must be exactly equal +to ``3`` and no other value. + +Given some value ``v`` that is a member of type ``T``, the type +``Literal[v]`` shall be treated as a subtype of ``T``. For example, +``Literal[3]`` is a subtype of ``int``. + +All methods from the parent type will be directly inherited by the +literal type. So, if we have some variable ``foo`` of type ``Literal[3]`` +it’s safe to do things like ``foo + 5`` since ``foo`` inherits ``int``’s +``__add__`` method. The resulting type of ``foo + 5`` is ``int``. + +This "inheriting" behavior is identical to how we +:pep:`handle NewTypes <484#newtype-helper-function>`. + +Equivalence of two Literals +""""""""""""""""""""""""""" + +Two types ``Literal[v1]`` and ``Literal[v2]`` are equivalent when +both of the following conditions are true: + +1. ``type(v1) == type(v2)`` +2. ``v1 == v2`` + +For example, ``Literal[20]`` and ``Literal[0x14]`` are equivalent. +However, ``Literal[0]`` and ``Literal[False]`` are *not* equivalent +despite that ``0 == False`` evaluates to 'true' at runtime: ``0`` +has type ``int`` and ``False`` has type ``bool``. + +Shortening unions of literals +""""""""""""""""""""""""""""" + +Literals are parameterized with one or more values. When a Literal is +parameterized with more than one value, it's treated as exactly equivalent +to the union of those types. That is, ``Literal[v1, v2, v3]`` is equivalent +to ``Literal[v1] | Literal[v2] | Literal[v3]``. + +This shortcut helps make writing signatures for functions that accept +many different literals more ergonomic — for example, functions like +``open(...)``:: + + # Note: this is a simplification of the true type signature. + _PathType = str | bytes | int + + @overload + def open(path: _PathType, + mode: Literal["r", "w", "a", "x", "r+", "w+", "a+", "x+"], + ) -> IO[str]: ... + @overload + def open(path: _PathType, + mode: Literal["rb", "wb", "ab", "xb", "r+b", "w+b", "a+b", "x+b"], + ) -> IO[bytes]: ... + + # Fallback overload for when the user isn't using literal types + @overload + def open(path: _PathType, mode: str) -> IO[Any]: ... + +The provided values do not all have to be members of the same type. +For example, ``Literal[42, "foo", True]`` is a legal type. + +However, Literal **must** be parameterized with at least one type. +Types like ``Literal[]`` or ``Literal`` are illegal. + + +Legal and illegal parameterizations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This section describes what exactly constitutes a legal ``Literal[...]`` type: +what values may and may not be used as parameters. + +In short, a ``Literal[...]`` type may be parameterized by one or more literal +expressions, and nothing else. + + +Legal parameters for ``Literal`` at type check time +""""""""""""""""""""""""""""""""""""""""""""""""""" + +``Literal`` may be parameterized with literal ints, byte and unicode strings, +bools, Enum values and ``None``. So for example, all of +the following would be legal:: + + Literal[26] + Literal[0x1A] # Exactly equivalent to Literal[26] + Literal[-4] + Literal["hello world"] + Literal[b"hello world"] + Literal[u"hello world"] + Literal[True] + Literal[Color.RED] # Assuming Color is some enum + Literal[None] + +**Note:** Since the type ``None`` is inhabited by just a single +value, the types ``None`` and ``Literal[None]`` are exactly equivalent. +Type checkers may simplify ``Literal[None]`` into just ``None``. + +``Literal`` may also be parameterized by other literal types, or type aliases +to other literal types. For example, the following is legal:: + + ReadOnlyMode = Literal["r", "r+"] + WriteAndTruncateMode = Literal["w", "w+", "wt", "w+t"] + WriteNoTruncateMode = Literal["r+", "r+t"] + AppendMode = Literal["a", "a+", "at", "a+t"] + + AllModes = Literal[ReadOnlyMode, WriteAndTruncateMode, + WriteNoTruncateMode, AppendMode] + +This feature is again intended to help make using and reusing literal types +more ergonomic. + +**Note:** As a consequence of the above rules, type checkers are also expected +to support types that look like the following:: + + Literal[Literal[Literal[1, 2, 3], "foo"], 5, None] + +This should be exactly equivalent to the following type:: + + Literal[1, 2, 3, "foo", 5, None] + +...and also to the following type:: + + Literal[1, 2, 3, "foo", 5] | None + +**Note:** String literal types like ``Literal["foo"]`` should subtype either +bytes or unicode in the same way regular string literals do at runtime. + +For example, in Python 3, the type ``Literal["foo"]`` is equivalent to +``Literal[u"foo"]``, since ``"foo"`` is equivalent to ``u"foo"`` in Python 3. + +Similarly, in Python 2, the type ``Literal["foo"]`` is equivalent to +``Literal[b"foo"]`` -- unless the file includes a +``from __future__ import unicode_literals`` import, in which case it would be +equivalent to ``Literal[u"foo"]``. + +Illegal parameters for ``Literal`` at type check time +""""""""""""""""""""""""""""""""""""""""""""""""""""" + +The following parameters are intentionally disallowed by design: + +- Arbitrary expressions like ``Literal[3 + 4]`` or + ``Literal["foo".replace("o", "b")]``. + + - Rationale: Literal types are meant to be a + minimal extension to the :pep:`484` typing ecosystem and requiring type + checkers to interpret potentially expressions inside types adds too + much complexity. + + - As a consequence, complex numbers like ``Literal[4 + 3j]`` and + ``Literal[-4 + 2j]`` are also prohibited. For consistency, literals like + ``Literal[4j]`` that contain just a single complex number are also + prohibited. + + - The only exception to this rule is the unary ``-`` (minus) for ints: types + like ``Literal[-5]`` are *accepted*. + +- Tuples containing valid literal types like ``Literal[(1, "foo", "bar")]``. + The user could always express this type as + ``tuple[Literal[1], Literal["foo"], Literal["bar"]]`` instead. Also, + tuples are likely to be confused with the ``Literal[1, 2, 3]`` + shortcut. + +- Mutable literal data structures like dict literals, list literals, or + set literals: literals are always implicitly final and immutable. So, + ``Literal[{"a": "b", "c": "d"}]`` is illegal. + +- Any other types: for example, ``Literal[Path]``, or + ``Literal[some_object_instance]`` are illegal. This includes typevars: if + ``T`` is a typevar, ``Literal[T]`` is not allowed. Typevars can vary over + only types, never over values. + +The following are provisionally disallowed for simplicity. We can consider +allowing them in the future. + +- Floats: e.g. ``Literal[3.14]``. Representing Literals of infinity or NaN + in a clean way is tricky; real-world APIs are unlikely to vary their + behavior based on a float parameter. + +- Any: e.g. ``Literal[Any]``. ``Any`` is a type, and ``Literal[...]`` is + meant to contain values only. It is also unclear what ``Literal[Any]`` + would actually semantically mean. + +Parameters at runtime +""""""""""""""""""""" + +Although the set of parameters ``Literal[...]`` may contain at type check time +is very small, the actual implementation of ``typing.Literal`` will not perform +any checks at runtime. For example:: + + def my_function(x: Literal[1 + 2]) -> int: + return x * 3 + + x: Literal = 3 + y: Literal[my_function] = my_function + +The type checker should reject this program: all three uses of +``Literal`` are *invalid* according to this spec. However, Python itself +should execute this program with no errors. + +This is partly to help us preserve flexibility in case we want to expand the +scope of what ``Literal`` can be used for in the future, and partly because +it is not possible to detect all illegal parameters at runtime to begin with. +For example, it is impossible to distinguish between ``Literal[1 + 2]`` and +``Literal[3]`` at runtime. + +Literals, enums, and forward references +""""""""""""""""""""""""""""""""""""""" + +One potential ambiguity is between literal strings and forward +references to literal enum members. For example, suppose we have the +type ``Literal["Color.RED"]``. Does this literal type +contain a string literal or a forward reference to some ``Color.RED`` +enum member? + +In cases like these, we always assume the user meant to construct a +literal string. If the user wants a forward reference, they must wrap +the entire literal type in a string -- e.g. ``"Literal[Color.RED]"``. + +Type inference +^^^^^^^^^^^^^^ + +This section describes a few rules regarding type inference and +literals, along with some examples. + +Backwards compatibility +""""""""""""""""""""""" + +When type checkers add support for Literal, it's important they do so +in a way that maximizes backwards-compatibility. Type checkers should +ensure that code that used to type check continues to do so after support +for Literal is added on a best-effort basis. + +This is particularly important when performing type inference. For +example, given the statement ``x = "blue"``, should the inferred +type of ``x`` be ``str`` or ``Literal["blue"]``? + +One naive strategy would be to always assume expressions are intended +to be Literal types. So, ``x`` would always have an inferred type of +``Literal["blue"]`` in the example above. This naive strategy is almost +certainly too disruptive -- it would cause programs like the following +to start failing when they previously did not:: + + # If a type checker infers 'var' has type Literal[3] + # and my_list has type List[Literal[3]]... + var = 3 + my_list = [var] + + # ...this call would be a type-error. + my_list.append(4) + +Another example of when this strategy would fail is when setting fields +in objects:: + + class MyObject: + def __init__(self) -> None: + # If a type checker infers MyObject.field has type Literal[3]... + self.field = 3 + + m = MyObject() + + # ...this assignment would no longer type check + m.field = 4 + +An alternative strategy that *does* maintain compatibility in every case would +be to always assume expressions are *not* Literal types unless they are +explicitly annotated otherwise. A type checker using this strategy would +always infer that ``x`` is of type ``str`` in the first example above. + +This is not the only viable strategy: type checkers should feel free to experiment +with more sophisticated inference techniques. No particular strategy is +mandated, but type checkers should keep in mind the importance of backwards +compatibility. + +Using non-Literals in Literal contexts +"""""""""""""""""""""""""""""""""""""" + +Literal types follow the existing rules regarding subtyping with no additional +special-casing. For example, programs like the following are type safe:: + + def expects_str(x: str) -> None: ... + var: Literal["foo"] = "foo" + + # Legal: Literal["foo"] is a subtype of str + expects_str(var) + +This also means non-Literal expressions in general should not automatically +be cast to Literal. For example:: + + def expects_literal(x: Literal["foo"]) -> None: ... + + def runner(my_str: str) -> None: + # ILLEGAL: str is not a subclass of Literal["foo"] + expects_literal(my_str) + +**Note:** If the user wants their API to support accepting both literals +*and* the original type -- perhaps for legacy purposes -- they should +implement a fallback overload. See :ref:`literalstring-overloads`. + +Interactions with other types and features +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This section discusses how Literal types interact with other existing types. + +Intelligent indexing of structured data +""""""""""""""""""""""""""""""""""""""" + +Literals can be used to "intelligently index" into structured types like +tuples, NamedTuple, and classes. (Note: this is not an exhaustive list). + +For example, type checkers should infer the correct value type when +indexing into a tuple using an int key that corresponds a valid index:: + + a: Literal[0] = 0 + b: Literal[5] = 5 + + some_tuple: tuple[int, str, List[bool]] = (3, "abc", [True, False]) + reveal_type(some_tuple[a]) # Revealed type is 'int' + some_tuple[b] # Error: 5 is not a valid index into the tuple + +We expect similar behavior when using functions like getattr:: + + class Test: + def __init__(self, param: int) -> None: + self.myfield = param + + def mymethod(self, val: int) -> str: ... + + a: Literal["myfield"] = "myfield" + b: Literal["mymethod"] = "mymethod" + c: Literal["blah"] = "blah" + + t = Test() + reveal_type(getattr(t, a)) # Revealed type is 'int' + reveal_type(getattr(t, b)) # Revealed type is 'Callable[[int], str]' + getattr(t, c) # Error: No attribute named 'blah' in Test + +**Note:** See `Interactions with Final`_ for how we can +express the variable declarations above in a more compact manner. + +Interactions with overloads +""""""""""""""""""""""""""" + +Literal types and overloads do not need to interact in a special +way: the existing rules work fine. + +However, one important use case type checkers must take care to +support is the ability to use a *fallback* when the user is not using literal +types. For example, consider ``open``:: + + _PathType = str | bytes | int + + @overload + def open(path: _PathType, + mode: Literal["r", "w", "a", "x", "r+", "w+", "a+", "x+"], + ) -> IO[str]: ... + @overload + def open(path: _PathType, + mode: Literal["rb", "wb", "ab", "xb", "r+b", "w+b", "a+b", "x+b"], + ) -> IO[bytes]: ... + + # Fallback overload for when the user isn't using literal types + @overload + def open(path: _PathType, mode: str) -> IO[Any]: ... + +If we were to change the signature of ``open`` to use just the first two overloads, +we would break any code that does not pass in a literal string expression. +For example, code like this would be broken:: + + mode: str = pick_file_mode(...) + with open(path, mode) as f: + # f should continue to be of type IO[Any] here + +A little more broadly: we mandate that whenever we add literal types to +some existing API in typeshed, we also always include a fallback overload to +maintain backwards-compatibility. + +Interactions with generics +"""""""""""""""""""""""""" + +Types like ``Literal[3]`` are meant to be just plain old subclasses of +``int``. This means you can use types like ``Literal[3]`` anywhere +you could use normal types, such as with generics. + +This means that it is legal to parameterize generic functions or +classes using Literal types:: + + A = TypeVar('A', bound=int) + B = TypeVar('B', bound=int) + C = TypeVar('C', bound=int) + + # A simplified definition for Matrix[row, column] + class Matrix(Generic[A, B]): + def __add__(self, other: Matrix[A, B]) -> Matrix[A, B]: ... + def __matmul__(self, other: Matrix[B, C]) -> Matrix[A, C]: ... + def transpose(self) -> Matrix[B, A]: ... + + foo: Matrix[Literal[2], Literal[3]] = Matrix(...) + bar: Matrix[Literal[3], Literal[7]] = Matrix(...) + + baz = foo @ bar + reveal_type(baz) # Revealed type is 'Matrix[Literal[2], Literal[7]]' + +Similarly, it is legal to construct TypeVars with value restrictions +or bounds involving Literal types:: + + T = TypeVar('T', Literal["a"], Literal["b"], Literal["c"]) + S = TypeVar('S', bound=Literal["foo"]) + +...although it is unclear when it would ever be useful to construct a +TypeVar with a Literal upper bound. For example, the ``S`` TypeVar in +the above example is essentially pointless: we can get equivalent behavior +by using ``S = Literal["foo"]`` instead. + +**Note:** Literal types and generics deliberately interact in only very +basic and limited ways. In particular, libraries that want to type check +code containing a heavy amount of numeric or numpy-style manipulation will +almost certainly likely find Literal types as described here to be +insufficient for their needs. + +Interactions with enums and exhaustiveness checks +""""""""""""""""""""""""""""""""""""""""""""""""" + +Type checkers should be capable of performing exhaustiveness checks when +working Literal types that have a closed number of variants, such as +enums. For example, the type checker should be capable of inferring that +the final ``else`` statement must be of type ``str``, since all three +values of the ``Status`` enum have already been exhausted:: + + class Status(Enum): + SUCCESS = 0 + INVALID_DATA = 1 + FATAL_ERROR = 2 + + def parse_status(s: str | Status) -> None: + if s is Status.SUCCESS: + print("Success!") + elif s is Status.INVALID_DATA: + print("The given data is invalid because...") + elif s is Status.FATAL_ERROR: + print("Unexpected fatal error...") + else: + # 's' must be of type 'str' since all other options are exhausted + print("Got custom status: " + s) + +The interaction described above is not new: it's already +:pep:`codified within PEP 484 <484#support-for-singleton-types-in-unions>`. +However, many type +checkers (such as mypy) do not yet implement this due to the expected +complexity of the implementation work. + +Some of this complexity will be alleviated once Literal types are introduced: +rather than entirely special-casing enums, we can instead treat them as being +approximately equivalent to the union of their values and take advantage of any +existing logic regarding unions, exhaustibility, type narrowing, reachability, +and so forth the type checker might have already implemented. + +So here, the ``Status`` enum could be treated as being approximately equivalent +to ``Literal[Status.SUCCESS, Status.INVALID_DATA, Status.FATAL_ERROR]`` +and the type of ``s`` narrowed accordingly. + +Interactions with narrowing +""""""""""""""""""""""""""" + +Type checkers may optionally perform additional analysis for both enum and +non-enum Literal types beyond what is described in the section above. + +For example, it may be useful to perform narrowing based on things like +containment or equality checks:: + + def parse_status(status: str) -> None: + if status in ("MALFORMED", "ABORTED"): + # Type checker could narrow 'status' to type + # Literal["MALFORMED", "ABORTED"] here. + return expects_bad_status(status) + + # Similarly, type checker could narrow 'status' to Literal["PENDING"] + if status == "PENDING": + expects_pending_status(status) + +It may also be useful to perform narrowing taking into account expressions +involving Literal bools. For example, we can combine ``Literal[True]``, +``Literal[False]``, and overloads to construct "custom type guards":: + + @overload + def is_int_like(x: int | list[int]) -> Literal[True]: ... + @overload + def is_int_like(x: object) -> bool: ... + def is_int_like(x): ... + + vector: list[int] = [1, 2, 3] + if is_int_like(vector): + vector.append(3) + else: + vector.append("bad") # This branch is inferred to be unreachable + + scalar: int | str + if is_int_like(scalar): + scalar += 3 # Type checks: type of 'scalar' is narrowed to 'int' + else: + scalar += "foo" # Type checks: type of 'scalar' is narrowed to 'str' + +Interactions with Final +""""""""""""""""""""""" + +The ``Final`` qualifier can be used to declare that some variable or +attribute cannot be reassigned:: + + foo: Final = 3 + foo = 4 # Error: 'foo' is declared to be Final + +Note that in the example above, we know that ``foo`` will always be equal to +exactly ``3``. A type checker can use this information to deduce that ``foo`` +is valid to use in any context that expects a ``Literal[3]``:: + + def expects_three(x: Literal[3]) -> None: ... + + expects_three(foo) # Type checks, since 'foo' is Final and equal to 3 + +The ``Final`` qualifier serves as a shorthand for declaring that a variable +is *effectively Literal*. + +Type checkers are expected to +support this shortcut. Specifically, given a variable or attribute assignment +of the form ``var: Final = value`` where ``value`` is a valid parameter for +``Literal[...]``, type checkers should understand that ``var`` may be used in +any context that expects a ``Literal[value]``. + +Type checkers are not obligated to understand any other uses of Final. For +example, whether or not the following program type checks is left unspecified:: + + # Note: The assignment does not exactly match the form 'var: Final = value'. + bar1: Final[int] = 3 + expects_three(bar1) # May or may not be accepted by type checkers + + # Note: "Literal[1 + 2]" is not a legal type. + bar2: Final = 1 + 2 + expects_three(bar2) # May or may not be accepted by type checkers + +``LiteralString`` +----------------- + +(Originally specified in :pep:`675`.) + +Valid locations for ``LiteralString`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``LiteralString`` can be used where any other type can be used: + +:: + + variable_annotation: LiteralString + + def my_function(literal_string: LiteralString) -> LiteralString: ... + + class Foo: + my_attribute: LiteralString + + type_argument: List[LiteralString] + + T = TypeVar("T", bound=LiteralString) + +It cannot be nested within unions of ``Literal`` types: + +:: + + bad_union: Literal["hello", LiteralString] # Not OK + bad_nesting: Literal[LiteralString] # Not OK + + +Type inference +^^^^^^^^^^^^^^ + +Inferring ``LiteralString`` +""""""""""""""""""""""""""" + +Any literal string type is compatible with ``LiteralString``. For +example, ``x: LiteralString = "foo"`` is valid because ``"foo"`` is +inferred to be of type ``Literal["foo"]``. + +We also infer ``LiteralString`` in the +following cases: + ++ Addition: ``x + y`` is of type ``LiteralString`` if both ``x`` and + ``y`` are compatible with ``LiteralString``. + ++ Joining: ``sep.join(xs)`` is of type ``LiteralString`` if ``sep``'s + type is compatible with ``LiteralString`` and ``xs``'s type is + compatible with ``Iterable[LiteralString]``. + ++ In-place addition: If ``s`` has type ``LiteralString`` and ``x`` has + type compatible with ``LiteralString``, then ``s += x`` preserves + ``s``'s type as ``LiteralString``. + ++ String formatting: An f-string has type ``LiteralString`` if and only + if its constituent expressions are literal strings. ``s.format(...)`` + has type ``LiteralString`` if and only if ``s`` and the arguments have + types compatible with ``LiteralString``. + +In all other cases, if one or more of the composed values has a +non-literal type ``str``, the composition of types will have type +``str``. For example, if ``s`` has type ``str``, then ``"hello" + s`` +has type ``str``. This matches the pre-existing behavior of type +checkers. + +``LiteralString`` is compatible with the type ``str``. It inherits all +methods from ``str``. So, if we have a variable ``s`` of type +``LiteralString``, it is safe to write ``s.startswith("hello")``. + +Some type checkers refine the type of a string when doing an equality +check: + +:: + + def foo(s: str) -> None: + if s == "bar": + reveal_type(s) # => Literal["bar"] + +Such a refined type in the if-block is also compatible with +``LiteralString`` because its type is ``Literal["bar"]``. + + +Examples +"""""""" + +See the examples below to help clarify the above rules: + +:: + + + literal_string: LiteralString + s: str = literal_string # OK + + literal_string: LiteralString = s # Error: Expected LiteralString, got str. + literal_string: LiteralString = "hello" # OK + +Addition of literal strings: + +:: + + def expect_literal_string(s: LiteralString) -> None: ... + + expect_literal_string("foo" + "bar") # OK + expect_literal_string(literal_string + "bar") # OK + + literal_string2: LiteralString + expect_literal_string(literal_string + literal_string2) # OK + + plain_string: str + expect_literal_string(literal_string + plain_string) # Not OK. + +Join using literal strings: + +:: + + expect_literal_string(",".join(["foo", "bar"])) # OK + expect_literal_string(literal_string.join(["foo", "bar"])) # OK + expect_literal_string(literal_string.join([literal_string, literal_string2])) # OK + + xs: List[LiteralString] + expect_literal_string(literal_string.join(xs)) # OK + expect_literal_string(plain_string.join([literal_string, literal_string2])) + # Not OK because the separator has type 'str'. + +In-place addition using literal strings: + +:: + + literal_string += "foo" # OK + literal_string += literal_string2 # OK + literal_string += plain_string # Not OK + +Format strings using literal strings: + +:: + + literal_name: LiteralString + expect_literal_string(f"hello {literal_name}") + # OK because it is composed from literal strings. + + expect_literal_string("hello {}".format(literal_name)) # OK + + expect_literal_string(f"hello") # OK + + username: str + expect_literal_string(f"hello {username}") + # NOT OK. The format-string is constructed from 'username', + # which has type 'str'. + + expect_literal_string("hello {}".format(username)) # Not OK + +Other literal types, such as literal integers, are not compatible with ``LiteralString``: + +:: + + some_int: int + expect_literal_string(some_int) # Error: Expected LiteralString, got int. + + literal_one: Literal[1] = 1 + expect_literal_string(literal_one) # Error: Expected LiteralString, got Literal[1]. + + +We can call functions on literal strings: + +:: + + def add_limit(query: LiteralString) -> LiteralString: + return query + " LIMIT = 1" + + def my_query(query: LiteralString, user_id: str) -> None: + sql_connection().execute(add_limit(query), (user_id,)) # OK + +Conditional statements and expressions work as expected: + +:: + + def return_literal_string() -> LiteralString: + return "foo" if condition1() else "bar" # OK + + def return_literal_str2(literal_string: LiteralString) -> LiteralString: + return "foo" if condition1() else literal_string # OK + + def return_literal_str3() -> LiteralString: + if condition1(): + result: Literal["foo"] = "foo" + else: + result: LiteralString = "bar" + + return result # OK + + +Interaction with TypeVars and Generics +"""""""""""""""""""""""""""""""""""""" + +TypeVars can be bound to ``LiteralString``: + +:: + + from typing import Literal, LiteralString, TypeVar + + TLiteral = TypeVar("TLiteral", bound=LiteralString) + + def literal_identity(s: TLiteral) -> TLiteral: + return s + + hello: Literal["hello"] = "hello" + y = literal_identity(hello) + reveal_type(y) # => Literal["hello"] + + s: LiteralString + y2 = literal_identity(s) + reveal_type(y2) # => LiteralString + + s_error: str + literal_identity(s_error) + # Error: Expected TLiteral (bound to LiteralString), got str. + + +``LiteralString`` can be used as a type argument for generic classes: + +:: + + class Container(Generic[T]): + def __init__(self, value: T) -> None: + self.value = value + + literal_string: LiteralString = "hello" + x: Container[LiteralString] = Container(literal_string) # OK + + s: str + x_error: Container[LiteralString] = Container(s) # Not OK + +Standard containers like ``List`` work as expected: + +:: + + xs: List[LiteralString] = ["foo", "bar", "baz"] + + +.. _literalstring-overloads: + +Interactions with Overloads +""""""""""""""""""""""""""" + +Literal strings and overloads do not need to interact in a special +way: the existing rules work fine. ``LiteralString`` can be used as a +fallback overload where a specific ``Literal["foo"]`` type does not +match: + +:: + + @overload + def foo(x: Literal["foo"]) -> int: ... + @overload + def foo(x: LiteralString) -> bool: ... + @overload + def foo(x: str) -> str: ... + + x1: int = foo("foo") # First overload. + x2: bool = foo("bar") # Second overload. + s: str + x3: str = foo(s) # Third overload. diff --git a/docs/spec/narrowing.rst b/docs/spec/narrowing.rst new file mode 100644 index 000000000..91b9e00bb --- /dev/null +++ b/docs/spec/narrowing.rst @@ -0,0 +1,106 @@ +Type narrowing +============== + +Type checkers should narrow the types of expressions in +certain contexts. This behavior is currently largely unspecified. + +TypeGuard +--------- + +(Originally specified in :pep:`647`.) + +The symbol ``TypeGuard``, exported from the ``typing`` module, is a special form +that accepts a single type argument. It is used to annotate the return type of a +user-defined type guard function. Return statements within a type guard function +should return bool values, and type checkers should verify that all return paths +return a bool. + +In all other respects, ``TypeGuard`` is a distinct type from bool. It is not a +subtype of bool. Therefore, ``Callable[..., TypeGuard[int]]`` is not assignable +to ``Callable[..., bool]``. + +When ``TypeGuard`` is used to annotate the return type of a function or +method that accepts at least one parameter, that function or method is +treated by type checkers as a user-defined type guard. The type argument +provided for ``TypeGuard`` indicates the type that has been validated by +the function. + +User-defined type guards can be generic functions, as shown in this example: + +:: + + _T = TypeVar("_T") + + def is_two_element_tuple(val: Tuple[_T, ...]) -> TypeGuard[tuple[_T, _T]]: + return len(val) == 2 + + def func(names: tuple[str, ...]): + if is_two_element_tuple(names): + reveal_type(names) # tuple[str, str] + else: + reveal_type(names) # tuple[str, ...] + + +Type checkers should assume that type narrowing should be applied to the +expression that is passed as the first positional argument to a user-defined +type guard. If the type guard function accepts more than one argument, no +type narrowing is applied to those additional argument expressions. + +If a type guard function is implemented as an instance method or class method, +the first positional argument maps to the second parameter (after "self" or +"cls"). + +Here are some examples of user-defined type guard functions that accept more +than one argument: + +:: + + def is_str_list(val: list[object], allow_empty: bool) -> TypeGuard[list[str]]: + if len(val) == 0: + return allow_empty + return all(isinstance(x, str) for x in val) + + _T = TypeVar("_T") + + def is_set_of(val: set[Any], type: type[_T]) -> TypeGuard[Set[_T]]: + return all(isinstance(x, type) for x in val) + + +The return type of a user-defined type guard function will normally refer to +a type that is strictly "narrower" than the type of the first argument (that +is, it's a more specific type that can be assigned to the more general type). +However, it is not required that the return type be strictly narrower. This +allows for cases like the example above where ``list[str]`` is not assignable +to ``list[object]``. + +When a conditional statement includes a call to a user-defined type guard +function, and that function returns true, the expression passed as the first +positional argument to the type guard function should be assumed by a static +type checker to take on the type specified in the TypeGuard return type, +unless and until it is further narrowed within the conditional code block. + +Some built-in type guards provide narrowing for both positive and negative +tests (in both the ``if`` and ``else`` clauses). For example, consider the +type guard for an expression of the form ``x is None``. If ``x`` has a type that +is a union of None and some other type, it will be narrowed to ``None`` in the +positive case and the other type in the negative case. User-defined type +guards apply narrowing only in the positive case (the ``if`` clause). The type +is not narrowed in the negative case. + +:: + + OneOrTwoStrs = tuple[str] | tuple[str, str] + def func(val: OneOrTwoStrs): + if is_two_element_tuple(val): + reveal_type(val) # tuple[str, str] + ... + else: + reveal_type(val) # OneOrTwoStrs + ... + + if not is_two_element_tuple(val): + reveal_type(val) # OneOrTwoStrs + ... + else: + reveal_type(val) # tuple[str, str] + ... diff --git a/docs/spec/overload.rst b/docs/spec/overload.rst new file mode 100644 index 000000000..d457a0a80 --- /dev/null +++ b/docs/spec/overload.rst @@ -0,0 +1,104 @@ +``@overload`` +============= + +The ``@overload`` decorator allows describing functions and methods +that support multiple different combinations of argument types. This +pattern is used frequently in builtin modules and types. For example, +the ``__getitem__()`` method of the ``bytes`` type can be described as +follows:: + + from typing import overload + + class bytes: + ... + @overload + def __getitem__(self, i: int) -> int: ... + @overload + def __getitem__(self, s: slice) -> bytes: ... + +This description is more precise than would be possible using unions +(which cannot express the relationship between the argument and return +types):: + + class bytes: + ... + def __getitem__(self, a: int | slice) -> int | bytes: ... + +Another example where ``@overload`` comes in handy is the type of the +builtin ``map()`` function, which takes a different number of +arguments depending on the type of the callable:: + + from typing import TypeVar, overload + from collections.abc import Callable, Iterable, Iterator + + T1 = TypeVar('T1') + T2 = TypeVar('T2') + S = TypeVar('S') + + @overload + def map(func: Callable[[T1], S], iter1: Iterable[T1]) -> Iterator[S]: ... + @overload + def map(func: Callable[[T1, T2], S], + iter1: Iterable[T1], iter2: Iterable[T2]) -> Iterator[S]: ... + # ... and we could add more items to support more than two iterables + +Note that we could also easily add items to support ``map(None, ...)``:: + + @overload + def map(func: None, iter1: Iterable[T1]) -> Iterable[T1]: ... + @overload + def map(func: None, + iter1: Iterable[T1], + iter2: Iterable[T2]) -> Iterable[tuple[T1, T2]]: ... + +Uses of the ``@overload`` decorator as shown above are suitable for +stub files. In regular modules, a series of ``@overload``-decorated +definitions must be followed by exactly one +non-``@overload``-decorated definition (for the same function/method). +The ``@overload``-decorated definitions are for the benefit of the +type checker only, since they will be overwritten by the +non-``@overload``-decorated definition, while the latter is used at +runtime but should be ignored by a type checker. At runtime, calling +a ``@overload``-decorated function directly will raise +``NotImplementedError``. Here's an example of a non-stub overload +that can't easily be expressed using a union or a type variable:: + + @overload + def utf8(value: None) -> None: + pass + @overload + def utf8(value: bytes) -> bytes: + pass + @overload + def utf8(value: unicode) -> bytes: + pass + def utf8(value): + + +A constrained ``TypeVar`` type can often be used instead of using the +``@overload`` decorator. For example, the definitions of ``concat1`` +and ``concat2`` in this stub file are equivalent:: + + from typing import TypeVar + + AnyStr = TypeVar('AnyStr', str, bytes) + + def concat1(x: AnyStr, y: AnyStr) -> AnyStr: ... + + @overload + def concat2(x: str, y: str) -> str: ... + @overload + def concat2(x: bytes, y: bytes) -> bytes: ... + +Some functions, such as ``map`` or ``bytes.__getitem__`` above, can't +be represented precisely using type variables. We +recommend that ``@overload`` is only used in cases where a type +variable is not sufficient. + +Another important difference between type variables such as ``AnyStr`` +and using ``@overload`` is that the prior can also be used to define +constraints for generic class type parameters. For example, the type +parameter of the generic class ``typing.IO`` is constrained (only +``IO[str]``, ``IO[bytes]`` and ``IO[Any]`` are valid):: + + class IO(Generic[AnyStr]): ... diff --git a/docs/spec/protocol.rst b/docs/spec/protocol.rst new file mode 100644 index 000000000..cab85678f --- /dev/null +++ b/docs/spec/protocol.rst @@ -0,0 +1,647 @@ +Protocols +--------- + +(Originally specified in :pep:`544`.) + +Terminology +^^^^^^^^^^^ + +The term *protocols* is used for types supporting structural +subtyping. The reason is that the term *iterator protocol*, +for example, is widely understood in the community, and coming up with +a new term for this concept in a statically typed context would just create +confusion. + +This has the drawback that the term *protocol* becomes overloaded with +two subtly different meanings: the first is the traditional, well-known but +slightly fuzzy concept of protocols such as iterator; the second is the more +explicitly defined concept of protocols in statically typed code. +The distinction is not important most of the time, and in other +cases we can just add a qualifier such as *protocol classes* +when referring to the static type concept. + +If a class includes a protocol in its MRO, the class is called +an *explicit* subclass of the protocol. If a class is a structural subtype +of a protocol, it is said to implement the protocol and to be compatible +with a protocol. If a class is compatible with a protocol but the protocol +is not included in the MRO, the class is an *implicit* subtype +of the protocol. (Note that one can explicitly subclass a protocol and +still not implement it if a protocol attribute is set to ``None`` +in the subclass, see Python `data model `_ +for details.) + +The attributes (variables and methods) of a protocol that are mandatory +for another class in order to be considered a structural subtype are called +protocol members. + + +.. _definition: + +Defining a protocol +^^^^^^^^^^^^^^^^^^^ + +Protocols are defined by including a special new class ``typing.Protocol`` +(an instance of ``abc.ABCMeta``) in the base classes list, typically +at the end of the list. Here is a simple example:: + + from typing import Protocol + + class SupportsClose(Protocol): + def close(self) -> None: + ... + +Now if one defines a class ``Resource`` with a ``close()`` method that has +a compatible signature, it would implicitly be a subtype of +``SupportsClose``, since the structural subtyping is used for +protocol types:: + + class Resource: + ... + def close(self) -> None: + self.file.close() + self.lock.release() + +Apart from a few restrictions explicitly mentioned below, protocol types can +be used in every context where normal types can:: + + def close_all(things: Iterable[SupportsClose]) -> None: + for t in things: + t.close() + + f = open('foo.txt') + r = Resource() + close_all([f, r]) # OK! + close_all([1]) # Error: 'int' has no 'close' method + +Note that both the user-defined class ``Resource`` and the built-in +``IO`` type (the return type of ``open()``) are considered subtypes of +``SupportsClose``, because they provide a ``close()`` method with +a compatible type signature. + + +Protocol members +^^^^^^^^^^^^^^^^ + +All methods defined in the protocol class body are protocol members, both +normal and decorated with ``@abstractmethod``. If any parameters of a +protocol method are not annotated, then their types are assumed to be ``Any`` +(see :pep:`484`). Bodies of protocol methods are type checked. +An abstract method that should not be called via ``super()`` ought to raise +``NotImplementedError``. Example:: + + from typing import Protocol + from abc import abstractmethod + + class Example(Protocol): + def first(self) -> int: # This is a protocol member + return 42 + + @abstractmethod + def second(self) -> int: # Method without a default implementation + raise NotImplementedError + +Static methods, class methods, and properties are equally allowed +in protocols. + +To define a protocol variable, one can use :pep:`526` variable +annotations in the class body. Additional attributes *only* defined in +the body of a method by assignment via ``self`` are not allowed. The rationale +for this is that the protocol class implementation is often not shared by +subtypes, so the interface should not depend on the default implementation. +Examples:: + + from typing import Protocol + + class Template(Protocol): + name: str # This is a protocol member + value: int = 0 # This one too (with default) + + def method(self) -> None: + self.temp: list[int] = [] # Error in type checker + + class Concrete: + def __init__(self, name: str, value: int) -> None: + self.name = name + self.value = value + + def method(self) -> None: + return + + var: Template = Concrete('value', 42) # OK + +To distinguish between protocol class variables and protocol instance +variables, the special ``ClassVar`` annotation should be used as specified +by :pep:`526`. By default, protocol variables as defined above are considered +readable and writable. To define a read-only protocol variable, one can use +an (abstract) property. + + +Explicitly declaring implementation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To explicitly declare that a certain class implements a given protocol, +it can be used as a regular base class. In this case a class could use +default implementations of protocol members. Static analysis tools are +expected to automatically detect that a class implements a given protocol. +So while it's possible to subclass a protocol explicitly, it's *not necessary* +to do so for the sake of type-checking. + +The default implementations cannot be used if +the subtype relationship is implicit and only via structural +subtyping -- the semantics of inheritance is not changed. Examples:: + + class PColor(Protocol): + @abstractmethod + def draw(self) -> str: + ... + def complex_method(self) -> int: + # some complex code here + + class NiceColor(PColor): + def draw(self) -> str: + return "deep blue" + + class BadColor(PColor): + def draw(self) -> str: + return super().draw() # Error, no default implementation + + class ImplicitColor: # Note no 'PColor' base here + def draw(self) -> str: + return "probably gray" + def complex_method(self) -> int: + # class needs to implement this + + nice: NiceColor + another: ImplicitColor + + def represent(c: PColor) -> None: + print(c.draw(), c.complex_method()) + + represent(nice) # OK + represent(another) # Also OK + +Note that there is little difference between explicit and implicit +subtypes; the main benefit of explicit subclassing is to get some protocol +methods "for free". In addition, type checkers can statically verify that +the class actually implements the protocol correctly:: + + class RGB(Protocol): + rgb: tuple[int, int, int] + + @abstractmethod + def intensity(self) -> int: + return 0 + + class Point(RGB): + def __init__(self, red: int, green: int, blue: str) -> None: + self.rgb = red, green, blue # Error, 'blue' must be 'int' + + # Type checker might warn that 'intensity' is not defined + +A class can explicitly inherit from multiple protocols and also from normal +classes. In this case methods are resolved using normal MRO and a type checker +verifies that all subtyping are correct. The semantics of ``@abstractmethod`` +is not changed; all of them must be implemented by an explicit subclass +before it can be instantiated. + + +Merging and extending protocols +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The general philosophy is that protocols are mostly like regular ABCs, +but a static type checker will handle them specially. Subclassing a protocol +class would not turn the subclass into a protocol unless it also has +``typing.Protocol`` as an explicit base class. Without this base, the class +is "downgraded" to a regular ABC that cannot be used with structural +subtyping. The rationale for this rule is that we don't want to accidentally +have some class act as a protocol just because one of its base classes +happens to be one. We still slightly prefer nominal subtyping over structural +subtyping in the static typing world. + +A subprotocol can be defined by having *both* one or more protocols as +immediate base classes and also having ``typing.Protocol`` as an immediate +base class:: + + from typing import Protocol + from collections.abc import Sized + + class SizedAndClosable(Sized, Protocol): + def close(self) -> None: + ... + +Now the protocol ``SizedAndClosable`` is a protocol with two methods, +``__len__`` and ``close``. If one omits ``Protocol`` in the base class list, +this would be a regular (non-protocol) class that must implement ``Sized``. +Alternatively, one can implement ``SizedAndClosable`` protocol by merging +the ``SupportsClose`` protocol from the example in the `definition`_ section +with ``typing.Sized``:: + + from collections.abc import Sized + + class SupportsClose(Protocol): + def close(self) -> None: + ... + + class SizedAndClosable(Sized, SupportsClose, Protocol): + pass + +The two definitions of ``SizedAndClosable`` are equivalent. +Subclass relationships between protocols are not meaningful when +considering subtyping, since structural compatibility is +the criterion, not the MRO. + +If ``Protocol`` is included in the base class list, all the other base classes +must be protocols. A protocol can't extend a regular class. +Note that rules around explicit subclassing are different +from regular ABCs, where abstractness is simply defined by having at least one +abstract method being unimplemented. Protocol classes must be marked +*explicitly*. + + +Generic protocols +^^^^^^^^^^^^^^^^^ + +Generic protocols are important. For example, ``SupportsAbs``, ``Iterable`` +and ``Iterator`` are generic protocols. They are defined similar to normal +non-protocol generic types:: + + class Iterable(Protocol[T]): + @abstractmethod + def __iter__(self) -> Iterator[T]: + ... + +``Protocol[T, S, ...]`` is allowed as a shorthand for +``Protocol, Generic[T, S, ...]``. + +User-defined generic protocols support explicitly declared variance. +Type checkers will warn if the inferred variance is different from +the declared variance. Examples:: + + T = TypeVar('T') + T_co = TypeVar('T_co', covariant=True) + T_contra = TypeVar('T_contra', contravariant=True) + + class Box(Protocol[T_co]): + def content(self) -> T_co: + ... + + box: Box[float] + second_box: Box[int] + box = second_box # This is OK due to the covariance of 'Box'. + + class Sender(Protocol[T_contra]): + def send(self, data: T_contra) -> int: + ... + + sender: Sender[float] + new_sender: Sender[int] + new_sender = sender # OK, 'Sender' is contravariant. + + class Proto(Protocol[T]): + attr: T # this class is invariant, since it has a mutable attribute + + var: Proto[float] + another_var: Proto[int] + var = another_var # Error! 'Proto[float]' is incompatible with 'Proto[int]'. + +Note that unlike nominal classes, de facto covariant protocols cannot be +declared as invariant, since this can break transitivity of subtyping. +For example:: + + T = TypeVar('T') + + class AnotherBox(Protocol[T]): # Error, this protocol is covariant in T, + def content(self) -> T: # not invariant. + ... + + +Recursive protocols +^^^^^^^^^^^^^^^^^^^ + +Recursive protocols are also supported. Forward references to the protocol +class names can be given as strings as specified by :pep:`484`. Recursive +protocols are useful for representing self-referential data structures +like trees in an abstract fashion:: + + class Traversable(Protocol): + def leaves(self) -> Iterable['Traversable']: + ... + +Note that for recursive protocols, a class is considered a subtype of +the protocol in situations where the decision depends on itself. +Continuing the previous example:: + + class SimpleTree: + def leaves(self) -> list['SimpleTree']: + ... + + root: Traversable = SimpleTree() # OK + + class Tree(Generic[T]): + def leaves(self) -> list['Tree[T]']: + ... + + def walk(graph: Traversable) -> None: + ... + tree: Tree[float] = Tree() + walk(tree) # OK, 'Tree[float]' is a subtype of 'Traversable' + + +Self-types in protocols +^^^^^^^^^^^^^^^^^^^^^^^ + +The self-types in protocols follow the +:pep:`corresponding specification <484#annotating-instance-and-class-methods>` +of :pep:`484`. For example:: + + C = TypeVar('C', bound='Copyable') + class Copyable(Protocol): + def copy(self: C) -> C: + + class One: + def copy(self) -> 'One': + ... + + T = TypeVar('T', bound='Other') + class Other: + def copy(self: T) -> T: + ... + + c: Copyable + c = One() # OK + c = Other() # Also OK + +Subtyping relationships with other types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Protocols cannot be instantiated, so there are no values whose +runtime type is a protocol. For variables and parameters with protocol types, +subtyping relationships are subject to the following rules: + +* A protocol is never a subtype of a concrete type. +* A concrete type ``X`` is a subtype of protocol ``P`` + if and only if ``X`` implements all protocol members of ``P`` with + compatible types. In other words, subtyping with respect to a protocol is + always structural. +* A protocol ``P1`` is a subtype of another protocol ``P2`` if ``P1`` defines + all protocol members of ``P2`` with compatible types. + +Generic protocol types follow the same rules of variance as non-protocol +types. Protocol types can be used in all contexts where any other types +can be used, such as in unions, ``ClassVar``, type variables bounds, etc. +Generic protocols follow the rules for generic abstract classes, except for +using structural compatibility instead of compatibility defined by +inheritance relationships. + +Static type checkers will recognize protocol implementations, even if the +corresponding protocols are *not imported*:: + + # file lib.py + from collections.abc import Sized + + T = TypeVar('T', contravariant=True) + class ListLike(Sized, Protocol[T]): + def append(self, x: T) -> None: + pass + + def populate(lst: ListLike[int]) -> None: + ... + + # file main.py + from lib import populate # Note that ListLike is NOT imported + + class MockStack: + def __len__(self) -> int: + return 42 + def append(self, x: int) -> None: + print(x) + + populate([1, 2, 3]) # Passes type check + populate(MockStack()) # Also OK + + +Unions and intersections of protocols +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Unions of protocol classes behaves the same way as for non-protocol +classes. For example:: + + from typing importt Protocol + + class Exitable(Protocol): + def exit(self) -> int: + ... + class Quittable(Protocol): + def quit(self) -> int | None: + ... + + def finish(task: Exitable | Quittable) -> int: + ... + class DefaultJob: + ... + def quit(self) -> int: + return 0 + finish(DefaultJob()) # OK + +One can use multiple inheritance to define an intersection of protocols. +Example:: + + from collections.abc import Iterable, Hashable + + class HashableFloats(Iterable[float], Hashable, Protocol): + pass + + def cached_func(args: HashableFloats) -> float: + ... + cached_func((1, 2, 3)) # OK, tuple is both hashable and iterable + + +``Type[]`` and class objects vs protocols +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Variables and parameters annotated with ``Type[Proto]`` accept only concrete +(non-protocol) subtypes of ``Proto``. The main reason for this is to allow +instantiation of parameters with such types. For example:: + + class Proto(Protocol): + @abstractmethod + def meth(self) -> int: + ... + class Concrete: + def meth(self) -> int: + return 42 + + def fun(cls: Type[Proto]) -> int: + return cls().meth() # OK + fun(Proto) # Error + fun(Concrete) # OK + +The same rule applies to variables:: + + var: Type[Proto] + var = Proto # Error + var = Concrete # OK + var().meth() # OK + +Assigning an ABC or a protocol class to a variable is allowed if it is +not explicitly typed, and such assignment creates a type alias. +For normal (non-abstract) classes, the behavior of ``Type[]`` is +not changed. + +A class object is considered an implementation of a protocol if accessing +all members on it results in types compatible with the protocol members. +For example:: + + from typing import Any, Protocol + + class ProtoA(Protocol): + def meth(self, x: int) -> int: ... + class ProtoB(Protocol): + def meth(self, obj: Any, x: int) -> int: ... + + class C: + def meth(self, x: int) -> int: ... + + a: ProtoA = C # Type check error, signatures don't match! + b: ProtoB = C # OK + + +``NewType()`` and type aliases +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Protocols are essentially anonymous. To emphasize this point, static type +checkers might refuse protocol classes inside ``NewType()`` to avoid an +illusion that a distinct type is provided:: + + from typing import NewType, Protocol + from collections.abc import Iterator + + class Id(Protocol): + code: int + secrets: Iterator[bytes] + + UserId = NewType('UserId', Id) # Error, can't provide distinct type + +In contrast, type aliases are fully supported, including generic type +aliases:: + + from typing import TypeVar + from collections.abc import Reversible, Iterable, Sized + + T = TypeVar('T') + class SizedIterable(Iterable[T], Sized, Protocol): + pass + CompatReversible = Reversible[T] | SizedIterable[T] + + +Modules as implementations of protocols +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A module object is accepted where a protocol is expected if the public +interface of the given module is compatible with the expected protocol. +For example:: + + # file default_config.py + timeout = 100 + one_flag = True + other_flag = False + + # file main.py + import default_config + from typing import Protocol + + class Options(Protocol): + timeout: int + one_flag: bool + other_flag: bool + + def setup(options: Options) -> None: + ... + + setup(default_config) # OK + +To determine compatibility of module level functions, the ``self`` argument +of the corresponding protocol methods is dropped. For example:: + + # callbacks.py + def on_error(x: int) -> None: + ... + def on_success() -> None: + ... + + # main.py + import callbacks + from typing import Protocol + + class Reporter(Protocol): + def on_error(self, x: int) -> None: + ... + def on_success(self) -> None: + ... + + rp: Reporter = callbacks # Passes type check + +``@runtime_checkable`` decorator and narrowing types by ``isinstance()`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The default semantics is that ``isinstance()`` and ``issubclass()`` fail +for protocol types. This is in the spirit of duck typing -- protocols +basically would be used to model duck typing statically, not explicitly +at runtime. + +However, it should be possible for protocol types to implement custom +instance and class checks when this makes sense, similar to how ``Iterable`` +and other ABCs in ``collections.abc`` and ``typing`` already do it, +but this is limited to non-generic and unsubscripted generic protocols +(``Iterable`` is statically equivalent to ``Iterable[Any]``). +The ``typing`` module will define a special ``@runtime_checkable`` class decorator +that provides the same semantics for class and instance checks as for +``collections.abc`` classes, essentially making them "runtime protocols":: + + from typing import runtime_checkable, Protocol + + @runtime_checkable + class SupportsClose(Protocol): + def close(self): + ... + + assert isinstance(open('some/file'), SupportsClose) + +Note that instance checks are not 100% reliable statically, which is why +this behavior is opt-in. +The most type checkers can do is to treat ``isinstance(obj, Iterator)`` +roughly as a simpler way to write +``hasattr(x, '__iter__') and hasattr(x, '__next__')``. To minimize +the risks for this feature, the following rules are applied. + +**Definitions**: + +* *Data and non-data protocols*: A protocol is called a non-data protocol + if it only contains methods as members (for example ``Sized``, + ``Iterator``, etc). A protocol that contains at least one non-method member + (like ``x: int``) is called a data protocol. +* *Unsafe overlap*: A type ``X`` is called unsafely overlapping with + a protocol ``P``, if ``X`` is not a subtype of ``P``, but it is a subtype + of the type erased version of ``P`` where all members have type ``Any``. + In addition, if at least one element of a union unsafely overlaps with + a protocol ``P``, then the whole union is unsafely overlapping with ``P``. + +**Specification**: + +* A protocol can be used as a second argument in ``isinstance()`` and + ``issubclass()`` only if it is explicitly opt-in by ``@runtime_checkable`` + decorator. This requirement exists because protocol checks are not type safe + in case of dynamically set attributes, and because type checkers can only prove + that an ``isinstance()`` check is safe only for a given class, not for all its + subclasses. +* ``isinstance()`` can be used with both data and non-data protocols, while + ``issubclass()`` can be used only with non-data protocols. This restriction + exists because some data attributes can be set on an instance in constructor + and this information is not always available on the class object. +* Type checkers should reject an ``isinstance()`` or ``issubclass()`` call, if + there is an unsafe overlap between the type of the first argument and + the protocol. +* Type checkers should be able to select a correct element from a union after + a safe ``isinstance()`` or ``issubclass()`` call. For narrowing from non-union + types, type checkers can use their best judgement (this is intentionally + unspecified, since a precise specification would require intersection types). diff --git a/docs/spec/qualifiers.rst b/docs/spec/qualifiers.rst new file mode 100644 index 000000000..2a0395466 --- /dev/null +++ b/docs/spec/qualifiers.rst @@ -0,0 +1,278 @@ +Type qualifiers +=============== + +``@final`` +---------- + +(Originally specified in :pep:`591`.) + +The ``typing.final`` decorator is used to restrict the use of +inheritance and overriding. + +A type checker should prohibit any class decorated with ``@final`` +from being subclassed and any method decorated with ``@final`` from +being overridden in a subclass. The method decorator version may be +used with all of instance methods, class methods, static methods, and properties. + +For example:: + + from typing import final + + @final + class Base: + ... + + class Derived(Base): # Error: Cannot inherit from final class "Base" + ... + +and:: + + from typing import final + + class Base: + @final + def foo(self) -> None: + ... + + class Derived(Base): + def foo(self) -> None: # Error: Cannot override final attribute "foo" + # (previously declared in base class "Base") + ... + + +For overloaded methods, ``@final`` should be placed on the +implementation (or on the first overload, for stubs):: + + from typing import Any, overload + + class Base: + @overload + def method(self) -> None: ... + @overload + def method(self, arg: int) -> int: ... + @final + def method(self, x=None): + ... + +It is an error to use ``@final`` on a non-method function. + +``Final`` +--------- + +(Originally specified in :pep:`591`.) + +The ``typing.Final`` type qualifier is used to indicate that a +variable or attribute should not be reassigned, redefined, or overridden. + +Syntax +^^^^^^ + +``Final`` may be used in one of several forms: + +* With an explicit type, using the syntax ``Final[]``. Example:: + + ID: Final[float] = 1 + +* With no type annotation. Example:: + + ID: Final = 1 + + The typechecker should apply its usual type inference mechanisms to + determine the type of ``ID`` (here, likely, ``int``). Note that unlike for + generic classes this is *not* the same as ``Final[Any]``. + +* In class bodies and stub files you can omit the right hand side and just write + ``ID: Final[float]``. If the right hand side is omitted, there must + be an explicit type argument to ``Final``. + +* Finally, as ``self.id: Final = 1`` (also optionally with a type in + square brackets). This is allowed *only* in ``__init__`` methods, so + that the final instance attribute is assigned only once when an + instance is created. + + +Semantics and examples +^^^^^^^^^^^^^^^^^^^^^^ + +The two main rules for defining a final name are: + +* There can be *at most one* final declaration per module or class for + a given attribute. There can't be separate class-level and instance-level + constants with the same name. + +* There must be *exactly one* assignment to a final name. + +This means a type checker should prevent further assignments to final +names in type-checked code:: + + from typing import Final + + RATE: Final = 3000 + + class Base: + DEFAULT_ID: Final = 0 + + RATE = 300 # Error: can't assign to final attribute + Base.DEFAULT_ID = 1 # Error: can't override a final attribute + +Note that a type checker need not allow ``Final`` declarations inside loops +since the runtime will see multiple assignments to the same variable in +subsequent iterations. + +Additionally, a type checker should prevent final attributes from +being overridden in a subclass:: + + from typing import Final + + class Window: + BORDER_WIDTH: Final = 2.5 + ... + + class ListView(Window): + BORDER_WIDTH = 3 # Error: can't override a final attribute + +A final attribute declared in a class body without an initializer must +be initialized in the ``__init__`` method (except in stub files):: + + class ImmutablePoint: + x: Final[int] + y: Final[int] # Error: final attribute without an initializer + + def __init__(self) -> None: + self.x = 1 # Good + +Type checkers should infer a final attribute that is initialized in +a class body as being a class variable. Variables should not be annotated +with both ``ClassVar`` and ``Final``. + +``Final`` may only be used as the outermost type in assignments or variable +annotations. Using it in any other position is an error. In particular, +``Final`` can't be used in annotations for function arguments:: + + x: list[Final[int]] = [] # Error! + + def fun(x: Final[List[int]]) -> None: # Error! + ... + +Note that declaring a name as final only guarantees that the name will +not be re-bound to another value, but does not make the value +immutable. Immutable ABCs and containers may be used in combination +with ``Final`` to prevent mutating such values:: + + x: Final = ['a', 'b'] + x.append('c') # OK + + y: Final[Sequence[str]] = ['a', 'b'] + y.append('x') # Error: "Sequence[str]" has no attribute "append" + z: Final = ('a', 'b') # Also works + + +Type checkers should treat uses of a final name that was initialized +with a literal as if it was replaced by the literal. For example, the +following should be allowed:: + + from typing import NamedTuple, Final + + X: Final = "x" + Y: Final = "y" + N = NamedTuple("N", [(X, int), (Y, int)]) + +``Annotated`` +------------- + +(Originally specified by :pep:`593`.) + +Syntax +^^^^^^ + +``Annotated`` is parameterized with a type and an arbitrary list of +Python values that represent the annotations. Here are the specific +details of the syntax: + +* The first argument to ``Annotated`` must be a valid type + +* Multiple type annotations are supported (``Annotated`` supports variadic + arguments):: + + Annotated[int, ValueRange(3, 10), ctype("char")] + +* ``Annotated`` must be called with at least two arguments ( + ``Annotated[int]`` is not valid) + +* The order of the annotations is preserved and matters for equality + checks:: + + Annotated[int, ValueRange(3, 10), ctype("char")] != Annotated[ + int, ctype("char"), ValueRange(3, 10) + ] + +* Nested ``Annotated`` types are flattened, with metadata ordered + starting with the innermost annotation:: + + Annotated[Annotated[int, ValueRange(3, 10)], ctype("char")] == Annotated[ + int, ValueRange(3, 10), ctype("char") + ] + +* Duplicated annotations are not removed:: + + Annotated[int, ValueRange(3, 10)] != Annotated[ + int, ValueRange(3, 10), ValueRange(3, 10) + ] + +* ``Annotated`` can be used with nested and generic aliases:: + + T = TypeVar("T") + Vec = Annotated[list[tuple[T, T]], MaxLen(10)] + V = Vec[int] + + V == Annotated[list[tuple[int, int]], MaxLen(10)] + +Consuming annotations +^^^^^^^^^^^^^^^^^^^^^ + +Ultimately, the responsibility of how to interpret the annotations (if +at all) is the responsibility of the tool or library encountering the +``Annotated`` type. A tool or library encountering an ``Annotated`` type +can scan through the annotations to determine if they are of interest +(e.g., using ``isinstance()``). + +**Unknown annotations:** When a tool or a library does not support +annotations or encounters an unknown annotation it should just ignore it +and treat annotated type as the underlying type. For example, when encountering +an annotation that is not an instance of ``struct2.ctype`` to the annotations +for name (e.g., ``Annotated[str, 'foo', struct2.ctype("<10s")]``), the unpack +method should ignore it. + +**Namespacing annotations:** Namespaces are not needed for annotations since +the class used by the annotations acts as a namespace. + +**Multiple annotations:** It's up to the tool consuming the annotations +to decide whether the client is allowed to have several annotations on +one type and how to merge those annotations. + +Since the ``Annotated`` type allows you to put several annotations of +the same (or different) type(s) on any node, the tools or libraries +consuming those annotations are in charge of dealing with potential +duplicates. For example, if you are doing value range analysis you might +allow this:: + + T1 = Annotated[int, ValueRange(-10, 5)] + T2 = Annotated[T1, ValueRange(-20, 3)] + +Flattening nested annotations, this translates to:: + + T2 = Annotated[int, ValueRange(-10, 5), ValueRange(-20, 3)] + +Aliases & Concerns over verbosity +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Writing ``typing.Annotated`` everywhere can be quite verbose; +fortunately, the ability to alias annotations means that in practice we +don't expect clients to have to write lots of boilerplate code:: + + T = TypeVar('T') + Const = Annotated[T, my_annotations.CONST] + + class C: + def const_method(self: Const[List[int]]) -> int: + ... diff --git a/docs/spec/requirements.txt b/docs/spec/requirements.txt new file mode 100644 index 000000000..3ef203c86 --- /dev/null +++ b/docs/spec/requirements.txt @@ -0,0 +1,2 @@ +sphinx +furo diff --git a/docs/spec/special-types.rst b/docs/spec/special-types.rst new file mode 100644 index 000000000..698d20559 --- /dev/null +++ b/docs/spec/special-types.rst @@ -0,0 +1,223 @@ +Special types in annotations +============================ + +``Any`` +------- + +A special kind of type is ``Any``. Every type is consistent with +``Any``. It can be considered a type that has all values and all methods. +Note that ``Any`` and builtin type ``object`` are completely different. + +When the type of a value is ``object``, the type checker will reject +almost all operations on it, and assigning it to a variable (or using +it as a return value) of a more specialized type is a type error. On +the other hand, when a value has type ``Any``, the type checker will +allow all operations on it, and a value of type ``Any`` can be assigned +to a variable (or used as a return value) of a more constrained type. + +A function parameter without an annotation is assumed to be annotated with +``Any``. If a generic type is used without specifying type parameters, +they are assumed to be ``Any``:: + + from collections.abc import Mapping + + def use_map(m: Mapping) -> None: # Same as Mapping[Any, Any] + ... + +This rule also applies to ``tuple``, in annotation context it is equivalent +to ``tuple[Any, ...]``. As well, a bare +``Callable`` in an annotation is equivalent to ``Callable[..., Any]``:: + + from collections.abc import Callable + + def check_args(args: tuple) -> bool: + ... + + check_args(()) # OK + check_args((42, 'abc')) # Also OK + check_args(3.14) # Flagged as error by a type checker + + # A list of arbitrary callables is accepted by this function + def apply_callbacks(cbs: list[Callable]) -> None: + ... + +``Any`` can also be used as a base class. This can be useful for +avoiding type checker errors with classes that can duck type anywhere or +are highly dynamic. + +``None`` +-------- + +When used in a type hint, the expression ``None`` is considered +equivalent to ``type(None)``. + +``NoReturn`` +------------ + +The ``typing`` module provides a special type ``NoReturn`` to annotate functions +that never return normally. For example, a function that unconditionally +raises an exception:: + + from typing import NoReturn + + def stop() -> NoReturn: + raise RuntimeError('no way') + +The ``NoReturn`` annotation is used for functions such as ``sys.exit``. +Static type checkers will ensure that functions annotated as returning +``NoReturn`` truly never return, either implicitly or explicitly:: + + import sys + from typing import NoReturn + + def f(x: int) -> NoReturn: # Error, f(0) implicitly returns None + if x != 0: + sys.exit(1) + +The checkers will also recognize that the code after calls to such functions +is unreachable and will behave accordingly:: + + # continue from first example + def g(x: int) -> int: + if x > 0: + return x + stop() + return 'whatever works' # Error might be not reported by some checkers + # that ignore errors in unreachable blocks + +The ``NoReturn`` type is only valid as a return annotation of functions, +and considered an error if it appears in other positions:: + + from typing import NoReturn + + # All of the following are errors + def bad1(x: NoReturn) -> int: + ... + bad2: NoReturn = None + def bad3() -> list[NoReturn]: + ... + +``Never`` +--------- + +Since Python 3.11, the ``typing`` module has a primitive ``Never``. This +represents the bottom type, a type that has no members. Type checkers are +expected to treat this type as equivalent to ``NoReturn``, but it is explicitly +also allowed in argument positions. + +Tuples +------ + +The type of a tuple can be expressed by listing the element +types: ``tuple[int, int, str]`` is a tuple containing an int, +another int, and a str. The empty tuple can be typed as +``tuple[()]``. Arbitrary-length homogeneous tuples can be +expressed using one type and ellipsis, for example ``tuple[int, ...]``. + +Special cases for ``float`` and ``complex`` +------------------------------------------- + +Python's numeric types ``complex``, ``float`` and ``int`` are not +subtypes of each other, but to support common use cases, the type +system contains a straightforward shortcut: +when an argument is annotated as having +type ``float``, an argument of type ``int`` is acceptable; similar, +for an argument annotated as having type ``complex``, arguments of +type ``float`` or ``int`` are acceptable. + +``type[]`` +---------- + +Sometimes you want to talk about class objects, in particular class +objects that inherit from a given class. This can be spelled as +``type[C]`` where ``C`` is a class. To clarify: while ``C`` (when +used as an annotation) refers to instances of class ``C``, ``type[C]`` +refers to *subclasses* of ``C``. (This is a similar distinction as +between ``object`` and ``type``.) + +For example, suppose we have the following classes:: + + class User: ... # Abstract base for User classes + class BasicUser(User): ... + class ProUser(User): ... + class TeamUser(User): ... + +And suppose we have a function that creates an instance of one of +these classes if you pass it a class object:: + + def new_user(user_class): + user = user_class() + # (Here we could write the user object to a database) + return user + +Without subscripting ``type[]`` the best we could do to annotate ``new_user()`` +would be:: + + def new_user(user_class: type) -> User: + ... + +However using ``type[]`` and a type variable with an upper bound we +can do much better:: + + U = TypeVar('U', bound=User) + def new_user(user_class: type[U]) -> U: + ... + +Now when we call ``new_user()`` with a specific subclass of ``User`` a +type checker will infer the correct type of the result:: + + joe = new_user(BasicUser) # Inferred type is BasicUser + +The value corresponding to ``type[C]`` must be an actual class object +that's a subtype of ``C``, not a special form. In other words, in the +above example calling e.g. ``new_user(BasicUser | ProUser)`` is +rejected by the type checker (in addition to failing at runtime +because you can't instantiate a union). + +Note that it is legal to use a union of classes as the parameter for +``type[]``, as in:: + + def new_non_team_user(user_class: type[BasicUser | ProUser]): + user = new_user(user_class) + ... + +However the actual argument passed in at runtime must still be a +concrete class object, e.g. in the above example:: + + new_non_team_user(ProUser) # OK + new_non_team_user(TeamUser) # Disallowed by type checker + +``type[Any]`` is also supported (see below for its meaning). + +``type[T]`` where ``T`` is a type variable is allowed when annotating the +first argument of a class method (see the relevant section). + +Any other special constructs like ``tuple`` or ``Callable`` are not allowed +as an argument to ``type``. + +There are some concerns with this feature: for example when +``new_user()`` calls ``user_class()`` this implies that all subclasses +of ``User`` must support this in their constructor signature. However +this is not unique to ``type[]``: class methods have similar concerns. +A type checker ought to flag violations of such assumptions, but by +default constructor calls that match the constructor signature in the +indicated base class (``User`` in the example above) should be +allowed. A program containing a complex or extensible class hierarchy +might also handle this by using a factory class method. + +When ``type`` is parameterized it requires exactly one parameter. +Plain ``type`` without brackets, the root of Python's metaclass +hierarchy, is equivalent to ``type[Any]``. + +Regarding the behavior of ``type[Any]`` (or ``type``), +accessing attributes of a variable with this type only provides +attributes and methods defined by ``type`` (for example, +``__repr__()`` and ``__mro__``). Such a variable can be called with +arbitrary arguments, and the return type is ``Any``. + +``type`` is covariant in its parameter, because ``type[Derived]`` is a +subtype of ``type[Base]``:: + + def new_pro_user(pro_user_class: type[ProUser]): + user = new_user(pro_user_class) # OK + ... diff --git a/docs/spec/type-system.rst b/docs/spec/type-system.rst new file mode 100644 index 000000000..5a8f6dbb9 --- /dev/null +++ b/docs/spec/type-system.rst @@ -0,0 +1,67 @@ +The Python Type System +====================== + +This document describes a specification for the Python type system. + +The type system aims to provide a standard syntax for type annotations, +opening up Python code to easier static analysis and refactoring, +potential runtime type checking, and (perhaps, in some contexts) +code generation utilizing type information. + +Of these goals, static analysis is the most important. This includes +support for off-line type checkers such as mypy, as well as providing +a standard notation that can be used by IDEs for code completion and +refactoring. + +Purpose +------- + +This specification aims to provide a full description of the Python +type system. For type checker authors, it provides a complete +description of expected semantics. For library authors, it provides +guarantees to rely on when working with multiple type checkers. + +The type system was originally specified in a series of PEPs, starting +with :pep:`484`. This document is intended to replace those PEPs, and +was initially created by merging the specification sections of the +various PEPs. However, the PEPs are uneven in depth and do not fully +cover all aspects of the type system. Addressing these issues is an +ongoing project. + +Non-goals +--------- + +While the typing module contains some building blocks for +runtime type checking -- in particular the ``get_type_hints()`` +function -- third party packages would have to be developed to +implement specific runtime type checking functionality, for example +using decorators or metaclasses. Using type hints for performance +optimizations is left as an exercise for the reader. + +It should also be emphasized that **Python will remain a dynamically +typed language, and there is no desire to ever make type hints +mandatory, even by convention.** + +Definition of terms +------------------- + +This section defines a few terms that may be used elsewhere in the specification. + +The definition of "MAY", "MUST", and "SHOULD", and "SHOULD NOT" are +to be interpreted as described in :rfc:`2119`. + +"inline" - the types are part of the runtime code using :pep:`526` and +:pep:`3107` syntax (the filename ends in ``.py``). + +"stubs" - files containing only type information, empty of runtime code +(the filename ends in ``.pyi``). + +"Distributions" are the packaged files which are used to publish and distribute +a release. (:pep:`426`) + +"Module" a file containing Python runtime code or stubbed type information. + +"Package" a directory or directories that namespace Python modules. +(Note the distinction between packages and distributions. While most +distributions are named after the one package they install, some +distributions install multiple packages.) diff --git a/docs/spec/typeddict.rst b/docs/spec/typeddict.rst new file mode 100644 index 000000000..43a345e51 --- /dev/null +++ b/docs/spec/typeddict.rst @@ -0,0 +1,634 @@ +Typed dictionaries +================== + +TypedDict +--------- + +(Originally specified in :pep:`589`.) + +A TypedDict type represents dictionary objects with a specific set of +string keys, and with specific value types for each valid key. Each +string key can be either required (it must be present) or +non-required (it doesn't need to exist). + +There are two ways of defining TypedDict types. The first uses +a class-based syntax. The second is an alternative +assignment-based syntax that is provided for backwards compatibility, +to allow the feature to be backported to older Python versions. The +rationale is similar to why :pep:`484` supports a comment-based +annotation syntax for Python 2.7: type hinting is particularly useful +for large existing codebases, and these often need to run on older +Python versions. The two syntax options parallel the syntax variants +supported by ``typing.NamedTuple``. Other features include +TypedDict inheritance and totality (specifying whether keys are +required or not). + +This section also provides a sketch of how a type checker is expected +to support type checking operations involving TypedDict objects. +Similar to :pep:`484`, this discussion is left somewhat vague on purpose, +to allow experimentation with a wide variety of different type +checking approaches. In particular, type compatibility should be +based on structural compatibility: a more specific TypedDict type can +be compatible with a smaller (more general) TypedDict type. + + +Class-based Syntax +^^^^^^^^^^^^^^^^^^ + +A TypedDict type can be defined using the class definition syntax with +``typing.TypedDict`` as the sole base class:: + + from typing import TypedDict + + class Movie(TypedDict): + name: str + year: int + +``Movie`` is a TypedDict type with two items: ``'name'`` (with type +``str``) and ``'year'`` (with type ``int``). + +A type checker should validate that the body of a class-based +TypedDict definition conforms to the following rules: + +* The class body should only contain lines with item definitions of the + form ``key: value_type``, optionally preceded by a docstring. The + syntax for item definitions is identical to attribute annotations, + but there must be no initializer, and the key name actually refers + to the string value of the key instead of an attribute name. + +* Type comments cannot be used with the class-based syntax, for + consistency with the class-based ``NamedTuple`` syntax. Instead, + `Alternative Syntax`_ provides an + alternative, assignment-based syntax for backwards compatibility. + +* String literal forward references are valid in the value types. + +* Methods are not allowed, since the runtime type of a TypedDict + object will always be just ``dict`` (it is never a subclass of + ``dict``). + +* Specifying a metaclass is not allowed. + +* TypedDicts may be made generic by adding ``Generic[T]`` among the + bases (or, in Python 3.12 and higher, by using the new + syntax for generic classes). + +An empty TypedDict can be created by only including ``pass`` in the +body (if there is a docstring, ``pass`` can be omitted):: + + class EmptyDict(TypedDict): + pass + + +Using TypedDict Types +^^^^^^^^^^^^^^^^^^^^^ + +Here is an example of how the type ``Movie`` can be used:: + + movie: Movie = {'name': 'Blade Runner', + 'year': 1982} + +An explicit ``Movie`` type annotation is generally needed, as +otherwise an ordinary dictionary type could be assumed by a type +checker, for backwards compatibility. When a type checker can infer +that a constructed dictionary object should be a TypedDict, an +explicit annotation can be omitted. A typical example is a dictionary +object as a function argument. In this example, a type checker is +expected to infer that the dictionary argument should be understood as +a TypedDict:: + + def record_movie(movie: Movie) -> None: ... + + record_movie({'name': 'Blade Runner', 'year': 1982}) + +Another example where a type checker should treat a dictionary display +as a TypedDict is in an assignment to a variable with a previously +declared TypedDict type:: + + movie: Movie + ... + movie = {'name': 'Blade Runner', 'year': 1982} + +Operations on ``movie`` can be checked by a static type checker:: + + movie['director'] = 'Ridley Scott' # Error: invalid key 'director' + movie['year'] = '1982' # Error: invalid value type ("int" expected) + +The code below should be rejected, since ``'title'`` is not a valid +key, and the ``'name'`` key is missing:: + + movie2: Movie = {'title': 'Blade Runner', + 'year': 1982} + +The created TypedDict type object is not a real class object. Here +are the only uses of the type a type checker is expected to allow: + +* It can be used in type annotations and in any context where an + arbitrary type hint is valid, such as in type aliases and as the + target type of a cast. + +* It can be used as a callable object with keyword arguments + corresponding to the TypedDict items. Non-keyword arguments are not + allowed. Example:: + + m = Movie(name='Blade Runner', year=1982) + + When called, the TypedDict type object returns an ordinary + dictionary object at runtime:: + + print(type(m)) # + +* It can be used as a base class, but only when defining a derived + TypedDict. This is discussed in more detail below. + +In particular, TypedDict type objects cannot be used in +``isinstance()`` tests such as ``isinstance(d, Movie)``. The reason is +that there is no existing support for checking types of dictionary +item values, since ``isinstance()`` does not work with many :pep:`484` +types, including common ones like ``List[str]``. This would be needed +for cases like this:: + + class Strings(TypedDict): + items: List[str] + + print(isinstance({'items': [1]}, Strings)) # Should be False + print(isinstance({'items': ['x']}, Strings)) # Should be True + +The above use case is not supported. This is consistent with how +``isinstance()`` is not supported for ``List[str]``. + + +Inheritance +^^^^^^^^^^^ + +It is possible for a TypedDict type to inherit from one or more +TypedDict types using the class-based syntax. In this case the +``TypedDict`` base class should not be included. Example:: + + class BookBasedMovie(Movie): + based_on: str + +Now ``BookBasedMovie`` has keys ``name``, ``year``, and ``based_on``. +It is equivalent to this definition, since TypedDict types use +structural compatibility:: + + class BookBasedMovie(TypedDict): + name: str + year: int + based_on: str + +Here is an example of multiple inheritance:: + + class X(TypedDict): + x: int + + class Y(TypedDict): + y: str + + class XYZ(X, Y): + z: bool + +The TypedDict ``XYZ`` has three items: ``x`` (type ``int``), ``y`` +(type ``str``), and ``z`` (type ``bool``). + +A TypedDict cannot inherit from both a TypedDict type and a +non-TypedDict base class other than ``Generic``. + +Additional notes on TypedDict class inheritance: + +* Changing a field type of a parent TypedDict class in a subclass is not allowed. + Example:: + + class X(TypedDict): + x: str + + class Y(X): + x: int # Type check error: cannot overwrite TypedDict field "x" + + In the example outlined above TypedDict class annotations returns + type ``str`` for key ``x``:: + + print(Y.__annotations__) # {'x': } + + +* Multiple inheritance does not allow conflict types for the same name field:: + + class X(TypedDict): + x: int + + class Y(TypedDict): + x: str + + class XYZ(X, Y): # Type check error: cannot overwrite TypedDict field "x" while merging + xyz: bool + + +Totality +^^^^^^^^ + +By default, all keys must be present in a TypedDict. It is possible +to override this by specifying *totality*. Here is how to do this +using the class-based syntax:: + + class Movie(TypedDict, total=False): + name: str + year: int + +This means that a ``Movie`` TypedDict can have any of the keys omitted. Thus +these are valid:: + + m: Movie = {} + m2: Movie = {'year': 2015} + +A type checker is only expected to support a literal ``False`` or +``True`` as the value of the ``total`` argument. ``True`` is the +default, and makes all items defined in the class body be required. + +The totality flag only applies to items defined in the body of the +TypedDict definition. Inherited items won't be affected, and instead +use totality of the TypedDict type where they were defined. This makes +it possible to have a combination of required and non-required keys in +a single TypedDict type. Alternatively, ``Required`` and ``NotRequired`` +(see below) can be used to mark individual items as required or non-required. + + +Alternative Syntax +^^^^^^^^^^^^^^^^^^ + +This section provides an alternative syntax that can be backported to +older Python versions such as 3.5 and 2.7 that don't support the +variable definition syntax introduced in :pep:`526`. It +resembles the traditional syntax for defining named tuples:: + + Movie = TypedDict('Movie', {'name': str, 'year': int}) + +It is also possible to specify totality using the alternative syntax:: + + Movie = TypedDict('Movie', + {'name': str, 'year': int}, + total=False) + +The semantics are equivalent to the class-based syntax. This syntax +doesn't support inheritance, however. The +motivation for this is keeping the backwards compatible syntax as +simple as possible while covering the most common use cases. + +A type checker is only expected to accept a dictionary display expression +as the second argument to ``TypedDict``. In particular, a variable that +refers to a dictionary object does not need to be supported, to simplify +implementation. + + +Type Consistency +^^^^^^^^^^^^^^^^ + +Informally speaking, *type consistency* is a generalization of the +is-subtype-of relation to support the ``Any`` type. It is defined +more formally in :pep:`483`. This section introduces the +new, non-trivial rules needed to support type consistency for +TypedDict types. + +First, any TypedDict type is consistent with ``Mapping[str, object]``. +Second, a TypedDict type ``A`` is consistent with TypedDict ``B`` if +``A`` is structurally compatible with ``B``. This is true if and only +if both of these conditions are satisfied: + +* For each key in ``B``, ``A`` has the corresponding key and the + corresponding value type in ``A`` is consistent with the value type + in ``B``. For each key in ``B``, the value type in ``B`` is also + consistent with the corresponding value type in ``A``. + +* For each required key in ``B``, the corresponding key is required + in ``A``. For each non-required key in ``B``, the corresponding key + is not required in ``A``. + +Discussion: + +* Value types behave invariantly, since TypedDict objects are mutable. + This is similar to mutable container types such as ``List`` and + ``Dict``. Example where this is relevant:: + + class A(TypedDict): + x: int | None + + class B(TypedDict): + x: int + + def f(a: A) -> None: + a['x'] = None + + b: B = {'x': 0} + f(b) # Type check error: 'B' not compatible with 'A' + b['x'] + 1 # Runtime error: None + 1 + +* A TypedDict type with a required key is not consistent with a + TypedDict type where the same key is a non-required key, since the + latter allows keys to be deleted. Example where this is relevant:: + + class A(TypedDict, total=False): + x: int + + class B(TypedDict): + x: int + + def f(a: A) -> None: + del a['x'] + + b: B = {'x': 0} + f(b) # Type check error: 'B' not compatible with 'A' + b['x'] + 1 # Runtime KeyError: 'x' + +* A TypedDict type ``A`` with no key ``'x'`` is not consistent with a + TypedDict type with a non-required key ``'x'``, since at runtime + the key ``'x'`` could be present and have an incompatible type + (which may not be visible through ``A`` due to structural subtyping). + Example:: + + class A(TypedDict, total=False): + x: int + y: int + + class B(TypedDict, total=False): + x: int + + class C(TypedDict, total=False): + x: int + y: str + + def f(a: A) -> None: + a['y'] = 1 + + def g(b: B) -> None: + f(b) # Type check error: 'B' incompatible with 'A' + + c: C = {'x': 0, 'y': 'foo'} + g(c) + c['y'] + 'bar' # Runtime error: int + str + +* A TypedDict isn't consistent with any ``Dict[...]`` type, since + dictionary types allow destructive operations, including + ``clear()``. They also allow arbitrary keys to be set, which + would compromise type safety. Example:: + + class A(TypedDict): + x: int + + class B(A): + y: str + + def f(d: Dict[str, int]) -> None: + d['y'] = 0 + + def g(a: A) -> None: + f(a) # Type check error: 'A' incompatible with Dict[str, int] + + b: B = {'x': 0, 'y': 'foo'} + g(b) + b['y'] + 'bar' # Runtime error: int + str + +* A TypedDict with all ``int`` values is not consistent with + ``Mapping[str, int]``, since there may be additional non-``int`` + values not visible through the type, due to structural subtyping. + These can be accessed using the ``values()`` and ``items()`` + methods in ``Mapping``, for example. Example:: + + class A(TypedDict): + x: int + + class B(TypedDict): + x: int + y: str + + def sum_values(m: Mapping[str, int]) -> int: + n = 0 + for v in m.values(): + n += v # Runtime error + return n + + def f(a: A) -> None: + sum_values(a) # Error: 'A' incompatible with Mapping[str, int] + + b: B = {'x': 0, 'y': 'foo'} + f(b) + + +Supported and Unsupported Operations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Type checkers should support restricted forms of most ``dict`` +operations on TypedDict objects. The guiding principle is that +operations not involving ``Any`` types should be rejected by type +checkers if they may violate runtime type safety. Here are some of +the most important type safety violations to prevent: + +1. A required key is missing. + +2. A value has an invalid type. + +3. A key that is not defined in the TypedDict type is added. + +A key that is not a literal should generally be rejected, since its +value is unknown during type checking, and thus can cause some of the +above violations. (`Use of Final Values and Literal Types`_ +generalizes this to cover final names and literal types.) + +The use of a key that is not known to exist should be reported as an +error, even if this wouldn't necessarily generate a runtime type +error. These are often mistakes, and these may insert values with an +invalid type if structural subtyping hides the types of certain items. +For example, ``d['x'] = 1`` should generate a type check error if +``'x'`` is not a valid key for ``d`` (which is assumed to be a +TypedDict type). + +Extra keys included in TypedDict object construction should also be +caught. In this example, the ``director`` key is not defined in +``Movie`` and is expected to generate an error from a type checker:: + + m: Movie = dict( + name='Alien', + year=1979, + director='Ridley Scott') # error: Unexpected key 'director' + +Type checkers should reject the following operations on TypedDict +objects as unsafe, even though they are valid for normal dictionaries: + +* Operations with arbitrary ``str`` keys (instead of string literals + or other expressions with known string values) should generally be + rejected. This involves both destructive operations such as setting + an item and read-only operations such as subscription expressions. + As an exception to the above rule, ``d.get(e)`` and ``e in d`` + should be allowed for TypedDict objects, for an arbitrary expression + ``e`` with type ``str``. The motivation is that these are safe and + can be useful for introspecting TypedDict objects. The static type + of ``d.get(e)`` should be ``object`` if the string value of ``e`` + cannot be determined statically. + +* ``clear()`` is not safe since it could remove required keys, some of + which may not be directly visible because of structural + subtyping. ``popitem()`` is similarly unsafe, even if all known + keys are not required (``total=False``). + +* ``del obj['key']`` should be rejected unless ``'key'`` is a + non-required key. + +Type checkers may allow reading an item using ``d['x']`` even if +the key ``'x'`` is not required, instead of requiring the use of +``d.get('x')`` or an explicit ``'x' in d`` check. The rationale is +that tracking the existence of keys is difficult to implement in full +generality, and that disallowing this could require many changes to +existing code. + +The exact type checking rules are up to each type checker to decide. +In some cases potentially unsafe operations may be accepted if the +alternative is to generate false positive errors for idiomatic code. + + +Use of Final Values and Literal Types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Type checkers should allow final names (:pep:`591`) with +string values to be used instead of string literals in operations on +TypedDict objects. For example, this is valid:: + + YEAR: Final = 'year' + + m: Movie = {'name': 'Alien', 'year': 1979} + years_since_epoch = m[YEAR] - 1970 + +Similarly, an expression with a suitable literal type +(:pep:`586`) can be used instead of a literal value:: + + def get_value(movie: Movie, + key: Literal['year', 'name']) -> int | str: + return movie[key] + +Type checkers are only expected to support actual string literals, not +final names or literal types, for specifying keys in a TypedDict type +definition. Also, only a boolean literal can be used to specify +totality in a TypedDict definition. The motivation for this is to +make type declarations self-contained, and to simplify the +implementation of type checkers. + + +Backwards Compatibility +^^^^^^^^^^^^^^^^^^^^^^^ + +To retain backwards compatibility, type checkers should not infer a +TypedDict type unless it is sufficiently clear that this is desired by +the programmer. When unsure, an ordinary dictionary type should be +inferred. Otherwise existing code that type checks without errors may +start generating errors once TypedDict support is added to the type +checker, since TypedDict types are more restrictive than dictionary +types. In particular, they aren't subtypes of dictionary types. + + +``Required`` and ``NotRequired`` +-------------------------------- + +(Originally specified in :pep:`655`.) + +The ``typing.Required`` type qualifier is used to indicate that a +variable declared in a TypedDict definition is a required key: + +:: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + +Additionally the ``typing.NotRequired`` type qualifier is used to +indicate that a variable declared in a TypedDict definition is a +potentially-missing key: + +:: + + class Movie(TypedDict): # implicitly total=True + title: str + year: NotRequired[int] + +It is an error to use ``Required[]`` or ``NotRequired[]`` in any +location that is not an item of a TypedDict. +Type checkers must enforce this restriction. + +It is valid to use ``Required[]`` and ``NotRequired[]`` even for +items where it is redundant, to enable additional explicitness if desired: + +:: + + class Movie(TypedDict): + title: Required[str] # redundant + year: NotRequired[int] + +It is an error to use both ``Required[]`` and ``NotRequired[]`` at the +same time: + +:: + + class Movie(TypedDict): + title: str + year: NotRequired[Required[int]] # ERROR + +Type checkers must enforce this restriction. +The runtime implementations of ``Required[]`` and ``NotRequired[]`` +may also enforce this restriction. + +The :pep:`alternative functional syntax <589#alternative-syntax>` +for TypedDict also supports +``Required[]`` and ``NotRequired[]``: + +:: + + Movie = TypedDict('Movie', {'name': str, 'year': NotRequired[int]}) + + +Interaction with ``total=False`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Any :pep:`589`-style TypedDict declared with ``total=False`` is equivalent +to a TypedDict with an implicit ``total=True`` definition with all of its +keys marked as ``NotRequired[]``. + +Therefore: + +:: + + class _MovieBase(TypedDict): # implicitly total=True + title: str + + class Movie(_MovieBase, total=False): + year: int + + +is equivalent to: + +:: + + class _MovieBase(TypedDict): + title: str + + class Movie(_MovieBase): + year: NotRequired[int] + + +Interaction with ``Annotated[]`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``Required[]`` and ``NotRequired[]`` can be used with ``Annotated[]``, +in any nesting order: + +:: + + class Movie(TypedDict): + title: str + year: NotRequired[Annotated[int, ValueRange(-9999, 9999)]] # ok + +:: + + class Movie(TypedDict): + title: str + year: Annotated[NotRequired[int], ValueRange(-9999, 9999)] # ok + +In particular allowing ``Annotated[]`` to be the outermost annotation +for an item allows better interoperability with non-typing uses of +annotations, which may always want ``Annotated[]`` as the outermost annotation +(`discussion `__). From 9f35263d2e78d7e904abbd7ad9d190720d7b9ad2 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 10 Dec 2023 21:19:33 -0800 Subject: [PATCH 157/539] Fix trailing spaces (#1537) --- docs/spec/annotations.rst | 2 +- docs/spec/callables.rst | 22 +++++++++++----------- docs/spec/generics.rst | 6 +++--- docs/spec/historical.rst | 2 +- docs/spec/literal.rst | 4 ++-- docs/spec/narrowing.rst | 8 ++++---- docs/spec/requirements.txt | 2 -- 7 files changed, 22 insertions(+), 24 deletions(-) delete mode 100644 docs/spec/requirements.txt diff --git a/docs/spec/annotations.rst b/docs/spec/annotations.rst index 17eae5b9f..a3b8ea7df 100644 --- a/docs/spec/annotations.rst +++ b/docs/spec/annotations.rst @@ -42,7 +42,7 @@ have a return annotation; the default behavior is thus the same as for other methods.) A type checker is expected to check the body of a checked function for -consistency with the given annotations. The annotations may also be +consistency with the given annotations. The annotations may also be used to check correctness of calls appearing in other checked functions. Type checkers are expected to attempt to infer as much information as diff --git a/docs/spec/callables.rst b/docs/spec/callables.rst index 072c4a0fc..382c9ded3 100644 --- a/docs/spec/callables.rst +++ b/docs/spec/callables.rst @@ -154,7 +154,7 @@ compatible:: class Animal(TypedDict): name: str - + class Dog(Animal): breed: str @@ -181,10 +181,10 @@ function arguments. Again, the rest of the parameters have to be compatible. Continuing the previous example:: class Example(TypedDict): - animal: Animal + animal: Animal string: str number: NotRequired[int] - + def src(**kwargs: Unpack[Example]): ... def dest(*, animal: Dog, string: str, number: int = ...): ... @@ -245,7 +245,7 @@ type ``T``:: class Vehicle: ... - + class Car(Vehicle): ... @@ -255,7 +255,7 @@ type ``T``:: class Vehicles(TypedDict): car: Car moto: Motorcycle - + def dest(**kwargs: Unpack[Vehicles]): ... def src(**kwargs: Vehicle): ... @@ -279,7 +279,7 @@ consider the following example:: class Animal(TypedDict): name: str - + class Dog(Animal): breed: str @@ -290,21 +290,21 @@ consider the following example:: def foo(**kwargs: Unpack[Animal]): print(kwargs["name"].capitalize()) - + def bar(**kwargs: Unpack[Animal]): takes_name(**kwargs) - + def baz(animal: Animal): takes_name(**animal) - + def spam(**kwargs: Unpack[Animal]): baz(kwargs) - + foo(**animal) # OK! foo only expects and uses keywords of 'Animal'. bar(**animal) # WRONG! This will fail at runtime because 'breed' keyword # will be passed to 'takes_name' as well. - + spam(**animal) # WRONG! Again, 'breed' keyword will be eventually passed # to 'takes_name'. diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst index 0878663e7..fa9947520 100644 --- a/docs/spec/generics.rst +++ b/docs/spec/generics.rst @@ -1050,7 +1050,7 @@ Or when using the built-in syntax for generics in Python 3.12 and higher:: class Array[*Ts]: ... - + def foo[*Ts](*args: *Ts): ... @@ -1685,7 +1685,7 @@ to the ``TypeVarTuple``: Ptang[str, bool, float] # T1=str, T3=float, T2=bool, Ts=tuple[()] Ptang[str, bool, float, int] # T1=str, T3=int, T2=float, Ts=tuple[bool] - + Note that the minimum number of type arguments in such cases is set by the number of ``TypeVar``\s: @@ -2406,7 +2406,7 @@ Here is an example. class ClassA[T1, T2, T3](list[T1]): def method1(self, a: T2) -> None: ... - + def method2(self) -> T3: ... diff --git a/docs/spec/historical.rst b/docs/spec/historical.rst index b505fc327..e586c7987 100644 --- a/docs/spec/historical.rst +++ b/docs/spec/historical.rst @@ -26,7 +26,7 @@ complex cases, a comment of the following format may be used:: x = [1, 2] # type: list[int] Type comments should be put on the last line of the statement that -contains the variable definition. +contains the variable definition. These should be treated as equivalent to annotating the variables using :pep:`526` variable annotations:: diff --git a/docs/spec/literal.rst b/docs/spec/literal.rst index 9f800b704..39d214f38 100644 --- a/docs/spec/literal.rst +++ b/docs/spec/literal.rst @@ -192,7 +192,7 @@ allowing them in the future. - Floats: e.g. ``Literal[3.14]``. Representing Literals of infinity or NaN in a clean way is tricky; real-world APIs are unlikely to vary their behavior based on a float parameter. - + - Any: e.g. ``Literal[Any]``. ``Any`` is a type, and ``Literal[...]`` is meant to contain values only. It is also unclear what ``Literal[Any]`` would actually semantically mean. @@ -515,7 +515,7 @@ involving Literal bools. For example, we can combine ``Literal[True]``, scalar += 3 # Type checks: type of 'scalar' is narrowed to 'int' else: scalar += "foo" # Type checks: type of 'scalar' is narrowed to 'str' - + Interactions with Final """"""""""""""""""""""" diff --git a/docs/spec/narrowing.rst b/docs/spec/narrowing.rst index 91b9e00bb..a30b91b13 100644 --- a/docs/spec/narrowing.rst +++ b/docs/spec/narrowing.rst @@ -74,9 +74,9 @@ allows for cases like the example above where ``list[str]`` is not assignable to ``list[object]``. When a conditional statement includes a call to a user-defined type guard -function, and that function returns true, the expression passed as the first -positional argument to the type guard function should be assumed by a static -type checker to take on the type specified in the TypeGuard return type, +function, and that function returns true, the expression passed as the first +positional argument to the type guard function should be assumed by a static +type checker to take on the type specified in the TypeGuard return type, unless and until it is further narrowed within the conditional code block. Some built-in type guards provide narrowing for both positive and negative @@ -97,7 +97,7 @@ is not narrowed in the negative case. else: reveal_type(val) # OneOrTwoStrs ... - + if not is_two_element_tuple(val): reveal_type(val) # OneOrTwoStrs ... diff --git a/docs/spec/requirements.txt b/docs/spec/requirements.txt deleted file mode 100644 index 3ef203c86..000000000 --- a/docs/spec/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -sphinx -furo From 007e5e6985801ecb8b70e4f6078a786dba330b00 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 11 Dec 2023 00:32:24 -0800 Subject: [PATCH 158/539] Typing Spec: Don't prescribe using joins (#1538) --- docs/spec/generics.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/spec/generics.rst b/docs/spec/generics.rst index fa9947520..4e502603e 100644 --- a/docs/spec/generics.rst +++ b/docs/spec/generics.rst @@ -486,7 +486,7 @@ be a subtype of the boundary type. Example:: longer([1], [1, 2]) # ok, return type list[int] longer({1}, {1, 2}) # ok, return type set[int] - longer([1], {1, 2}) # ok, return type Collection[int] + longer([1], {1, 2}) # ok, return type a supertype of list[int] and set[int] An upper bound cannot be combined with type constraints (as used in ``AnyStr``, see the example earlier); type constraints cause the From 42f39e740fc8e8ad2a86717b88ea6c628d2fba51 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 12 Dec 2023 06:06:04 +0100 Subject: [PATCH 159/539] Discussions moved to discourse (#1540) Also clarify that the purpose of the issues --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 27e82502e..9809cd697 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,13 @@ The documentation for Python's static typing can be found at [typing.readthedocs.io](https://typing.readthedocs.io/). You can get help in our [support forum](https://github.com/python/typing/discussions). -Improvements to the type system should be discussed on the -[typing-sig](https://mail.python.org/mailman3/lists/typing-sig.python.org/) -mailing list, although the [issues](https://github.com/python/typing/issues) in this -repository contain some historic discussions. +Improvements to the type system should be discussed on +[Python's Discourse](https://discuss.python.org/c/typing/32), and are +tracked in the [issues](https://github.com/python/typing/issues) in this +repository. For conversations that are more suitable to a chat platform, you can use one of the following: + - [gitter](https://gitter.im/python/typing) - [discord](https://discord.com/channels/267624335836053506/891788761371906108) `#type-hinting` channel From ec4903b8072e50847c51110af411e6646960522a Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 12 Dec 2023 08:49:57 +0100 Subject: [PATCH 160/539] Update an URL (#1542) --- docs/source/writing_stubs.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/writing_stubs.rst b/docs/source/writing_stubs.rst index 381bd73a3..d463a48b7 100644 --- a/docs/source/writing_stubs.rst +++ b/docs/source/writing_stubs.rst @@ -91,7 +91,7 @@ things like detecting missing annotations to more complex things like ensuring Liskov substitutability or detecting problematic overloads. It may be instructive to examine `typeshed `__'s -`setup for testing stubs `__. +`setup for testing stubs `__. .. TODO: consider adding examples and configurations for specific type checkers From 8aa8f8b6825ac3a28cb2a06d83c20174a94439da Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Thu, 28 Dec 2023 12:24:24 -0700 Subject: [PATCH 161/539] Initial check-in of type specification conformance suite. (#1552) --- .flake8 | 1 + conformance/.gitignore | 47 ++ conformance/README.md | 71 +++ conformance/requirements.txt | 7 + .../results/mypy/aliases_explicit.toml | 28 ++ .../results/mypy/aliases_implicit.toml | 40 ++ conformance/results/mypy/aliases_newtype.toml | 18 + .../results/mypy/aliases_recursive.toml | 14 + .../results/mypy/aliases_type_statement.toml | 76 +++ .../results/mypy/aliases_typealiastype.toml | 57 +++ .../results/mypy/aliases_variance.toml | 7 + .../results/mypy/annotations_typeexpr.toml | 20 + .../results/mypy/callables_annotation.toml | 16 + .../results/mypy/callables_kwargs.toml | 23 + .../results/mypy/callables_protocol.toml | 34 ++ .../results/mypy/dataclasses_descriptors.toml | 8 + .../results/mypy/dataclasses_frozen.toml | 7 + .../results/mypy/dataclasses_hash.toml | 6 + .../results/mypy/dataclasses_inheritance.toml | 5 + .../results/mypy/dataclasses_kwonly.toml | 18 + .../results/mypy/dataclasses_order.toml | 4 + .../results/mypy/dataclasses_postinit.toml | 11 + .../results/mypy/dataclasses_slots.toml | 9 + .../mypy/dataclasses_transform_class.toml | 9 + .../mypy/dataclasses_transform_field.toml | 10 + .../mypy/dataclasses_transform_func.toml | 13 + .../mypy/dataclasses_transform_meta.toml | 9 + .../results/mypy/dataclasses_usage.toml | 16 + .../results/mypy/generics_self_advanced.toml | 15 + .../mypy/generics_self_attributes.toml | 5 + .../results/mypy/generics_self_basic.toml | 10 + .../results/mypy/generics_self_protocols.toml | 15 + .../results/mypy/generics_self_usage.toml | 19 + .../results/mypy/literals_interactions.toml | 12 + .../results/mypy/literals_literalstring.toml | 15 + .../mypy/literals_parameterizations.toml | 22 + .../results/mypy/literals_semantics.toml | 7 + .../results/mypy/narrowing_typeguard.toml | 5 + .../results/mypy/typeddicts_alt_syntax.toml | 13 + .../results/mypy/typeddicts_class_syntax.toml | 8 + .../results/mypy/typeddicts_final.toml | 3 + .../results/mypy/typeddicts_inheritance.toml | 6 + .../results/mypy/typeddicts_operations.toml | 14 + .../results/mypy/typeddicts_required.toml | 12 + .../mypy/typeddicts_type_consistency.toml | 14 + .../results/mypy/typeddicts_usage.toml | 10 + conformance/results/mypy/version.toml | 2 + .../results/pyre/aliases_explicit.toml | 38 ++ .../results/pyre/aliases_implicit.toml | 34 ++ conformance/results/pyre/aliases_newtype.toml | 20 + .../results/pyre/aliases_recursive.toml | 21 + .../results/pyre/aliases_type_statement.toml | 7 + .../results/pyre/aliases_typealiastype.toml | 45 ++ .../results/pyre/aliases_variance.toml | 7 + .../results/pyre/annotations_typeexpr.toml | 19 + .../results/pyre/callables_annotation.toml | 17 + .../results/pyre/callables_kwargs.toml | 12 + .../results/pyre/callables_protocol.toml | 26 ++ .../results/pyre/dataclasses_descriptors.toml | 7 + .../results/pyre/dataclasses_frozen.toml | 9 + .../results/pyre/dataclasses_hash.toml | 6 + .../results/pyre/dataclasses_inheritance.toml | 7 + .../results/pyre/dataclasses_kwonly.toml | 6 + .../results/pyre/dataclasses_order.toml | 6 + .../results/pyre/dataclasses_postinit.toml | 8 + .../results/pyre/dataclasses_slots.toml | 9 + .../pyre/dataclasses_transform_class.toml | 22 + .../pyre/dataclasses_transform_field.toml | 11 + .../pyre/dataclasses_transform_func.toml | 26 ++ .../pyre/dataclasses_transform_meta.toml | 19 + .../results/pyre/dataclasses_usage.toml | 20 + .../results/pyre/generics_self_advanced.toml | 15 + .../pyre/generics_self_attributes.toml | 10 + .../results/pyre/generics_self_basic.toml | 15 + .../results/pyre/generics_self_protocols.toml | 7 + .../results/pyre/generics_self_usage.toml | 10 + .../results/pyre/literals_interactions.toml | 11 + .../results/pyre/literals_literalstring.toml | 13 + .../pyre/literals_parameterizations.toml | 30 ++ .../results/pyre/literals_semantics.toml | 9 + .../results/pyre/narrowing_typeguard.toml | 9 + .../results/pyre/typeddicts_alt_syntax.toml | 10 + .../results/pyre/typeddicts_class_syntax.toml | 11 + .../results/pyre/typeddicts_final.toml | 7 + .../results/pyre/typeddicts_inheritance.toml | 8 + .../results/pyre/typeddicts_operations.toml | 14 + .../results/pyre/typeddicts_required.toml | 11 + .../pyre/typeddicts_type_consistency.toml | 19 + .../results/pyre/typeddicts_usage.toml | 10 + conformance/results/pyre/version.toml | 2 + .../results/pyright/aliases_explicit.toml | 51 +++ .../results/pyright/aliases_implicit.toml | 31 ++ .../results/pyright/aliases_newtype.toml | 21 + .../results/pyright/aliases_recursive.toml | 66 +++ .../pyright/aliases_type_statement.toml | 48 ++ .../pyright/aliases_typealiastype.toml | 37 ++ .../results/pyright/aliases_variance.toml | 11 + .../results/pyright/annotations_typeexpr.toml | 29 ++ .../results/pyright/callables_annotation.toml | 16 + .../results/pyright/callables_kwargs.toml | 30 ++ .../results/pyright/callables_protocol.toml | 59 +++ .../pyright/dataclasses_descriptors.toml | 3 + .../results/pyright/dataclasses_frozen.toml | 11 + .../results/pyright/dataclasses_hash.toml | 11 + .../pyright/dataclasses_inheritance.toml | 5 + .../results/pyright/dataclasses_kwonly.toml | 6 + .../results/pyright/dataclasses_order.toml | 4 + .../results/pyright/dataclasses_postinit.toml | 15 + .../results/pyright/dataclasses_slots.toml | 10 + .../pyright/dataclasses_transform_class.toml | 13 + .../pyright/dataclasses_transform_field.toml | 5 + .../pyright/dataclasses_transform_func.toml | 12 + .../pyright/dataclasses_transform_meta.toml | 13 + .../results/pyright/dataclasses_usage.toml | 17 + .../pyright/generics_self_advanced.toml | 3 + .../pyright/generics_self_attributes.toml | 14 + .../results/pyright/generics_self_basic.toml | 8 + .../pyright/generics_self_protocols.toml | 15 + .../results/pyright/generics_self_usage.toml | 19 + .../pyright/literals_interactions.toml | 7 + .../pyright/literals_literalstring.toml | 25 + .../pyright/literals_parameterizations.toml | 21 + .../results/pyright/literals_semantics.toml | 10 + .../results/pyright/narrowing_typeguard.toml | 5 + .../pyright/typeddicts_alt_syntax.toml | 9 + .../pyright/typeddicts_class_syntax.toml | 8 + .../results/pyright/typeddicts_final.toml | 3 + .../pyright/typeddicts_inheritance.toml | 9 + .../pyright/typeddicts_operations.toml | 24 + .../results/pyright/typeddicts_required.toml | 7 + .../pyright/typeddicts_type_consistency.toml | 25 + .../results/pyright/typeddicts_usage.toml | 12 + conformance/results/pyright/version.toml | 2 + .../results/pytype/aliases_explicit.toml | 48 ++ .../results/pytype/aliases_implicit.toml | 46 ++ .../results/pytype/aliases_newtype.toml | 24 + .../results/pytype/aliases_recursive.toml | 59 +++ .../pytype/aliases_type_statement.toml | 7 + .../results/pytype/aliases_typealiastype.toml | 18 + .../results/pytype/aliases_variance.toml | 8 + .../results/pytype/annotations_typeexpr.toml | 26 ++ .../results/pytype/callables_annotation.toml | 32 ++ .../results/pytype/callables_kwargs.toml | 48 ++ .../results/pytype/callables_protocol.toml | 27 ++ .../pytype/dataclasses_descriptors.toml | 40 ++ .../results/pytype/dataclasses_frozen.toml | 8 + .../results/pytype/dataclasses_hash.toml | 6 + .../pytype/dataclasses_inheritance.toml | 7 + .../results/pytype/dataclasses_kwonly.toml | 23 + .../results/pytype/dataclasses_order.toml | 6 + .../results/pytype/dataclasses_postinit.toml | 13 + .../results/pytype/dataclasses_slots.toml | 12 + .../pytype/dataclasses_transform_class.toml | 15 + .../pytype/dataclasses_transform_field.toml | 11 + .../pytype/dataclasses_transform_func.toml | 36 ++ .../pytype/dataclasses_transform_meta.toml | 11 + .../results/pytype/dataclasses_usage.toml | 38 ++ .../pytype/generics_self_advanced.toml | 51 +++ .../pytype/generics_self_attributes.toml | 9 + .../results/pytype/generics_self_basic.toml | 52 +++ .../pytype/generics_self_protocols.toml | 6 + .../results/pytype/generics_self_usage.toml | 31 ++ .../results/pytype/literals_interactions.toml | 48 ++ .../pytype/literals_literalstring.toml | 6 + .../pytype/literals_parameterizations.toml | 41 ++ .../results/pytype/literals_semantics.toml | 25 + .../results/pytype/narrowing_typeguard.toml | 6 + .../results/pytype/typeddicts_alt_syntax.toml | 14 + .../pytype/typeddicts_class_syntax.toml | 8 + .../results/pytype/typeddicts_final.toml | 3 + .../pytype/typeddicts_inheritance.toml | 9 + .../results/pytype/typeddicts_operations.toml | 24 + .../results/pytype/typeddicts_required.toml | 8 + .../pytype/typeddicts_type_consistency.toml | 20 + .../results/pytype/typeddicts_usage.toml | 14 + conformance/results/pytype/version.toml | 2 + conformance/results/results.html | 431 ++++++++++++++++++ conformance/src/__init__.py | 0 conformance/src/main.py | 129 ++++++ conformance/src/reporting.py | 104 +++++ conformance/src/results_template.html | 147 ++++++ conformance/src/test_groups.py | 46 ++ conformance/src/test_groups.toml | 68 +++ conformance/src/type_checker.py | 244 ++++++++++ conformance/tests/aliases_explicit.py | 102 +++++ conformance/tests/aliases_implicit.py | 136 ++++++ conformance/tests/aliases_newtype.py | 62 +++ conformance/tests/aliases_recursive.py | 82 ++++ conformance/tests/aliases_type_statement.py | 92 ++++ conformance/tests/aliases_typealiastype.py | 70 +++ conformance/tests/aliases_variance.py | 45 ++ conformance/tests/annotations_typeexpr.py | 99 ++++ conformance/tests/callables_annotation.py | 45 ++ conformance/tests/callables_kwargs.py | 123 +++++ conformance/tests/callables_protocol.py | 313 +++++++++++++ conformance/tests/dataclasses_descriptors.py | 68 +++ conformance/tests/dataclasses_frozen.py | 42 ++ conformance/tests/dataclasses_hash.py | 70 +++ conformance/tests/dataclasses_inheritance.py | 64 +++ conformance/tests/dataclasses_kwonly.py | 62 +++ conformance/tests/dataclasses_order.py | 55 +++ conformance/tests/dataclasses_postinit.py | 55 +++ conformance/tests/dataclasses_slots.py | 70 +++ .../tests/dataclasses_transform_class.py | 119 +++++ .../tests/dataclasses_transform_field.py | 77 ++++ .../tests/dataclasses_transform_func.py | 97 ++++ .../tests/dataclasses_transform_meta.py | 100 ++++ conformance/tests/dataclasses_usage.py | 230 ++++++++++ conformance/tests/generics_self_advanced.py | 46 ++ conformance/tests/generics_self_attributes.py | 34 ++ conformance/tests/generics_self_basic.py | 82 ++++ conformance/tests/generics_self_protocols.py | 64 +++ conformance/tests/generics_self_usage.py | 126 +++++ conformance/tests/literals_interactions.py | 116 +++++ conformance/tests/literals_literalstring.py | 170 +++++++ .../tests/literals_parameterizations.py | 67 +++ conformance/tests/literals_semantics.py | 42 ++ conformance/tests/narrowing_typeguard.py | 108 +++++ conformance/tests/typeddicts_alt_syntax.py | 45 ++ conformance/tests/typeddicts_class_syntax.py | 79 ++++ conformance/tests/typeddicts_final.py | 26 ++ conformance/tests/typeddicts_inheritance.py | 66 +++ conformance/tests/typeddicts_operations.py | 65 +++ conformance/tests/typeddicts_required.py | 76 +++ .../tests/typeddicts_type_consistency.py | 150 ++++++ conformance/tests/typeddicts_usage.py | 42 ++ 226 files changed, 7961 insertions(+) create mode 100644 conformance/.gitignore create mode 100644 conformance/README.md create mode 100644 conformance/requirements.txt create mode 100644 conformance/results/mypy/aliases_explicit.toml create mode 100644 conformance/results/mypy/aliases_implicit.toml create mode 100644 conformance/results/mypy/aliases_newtype.toml create mode 100644 conformance/results/mypy/aliases_recursive.toml create mode 100644 conformance/results/mypy/aliases_type_statement.toml create mode 100644 conformance/results/mypy/aliases_typealiastype.toml create mode 100644 conformance/results/mypy/aliases_variance.toml create mode 100644 conformance/results/mypy/annotations_typeexpr.toml create mode 100644 conformance/results/mypy/callables_annotation.toml create mode 100644 conformance/results/mypy/callables_kwargs.toml create mode 100644 conformance/results/mypy/callables_protocol.toml create mode 100644 conformance/results/mypy/dataclasses_descriptors.toml create mode 100644 conformance/results/mypy/dataclasses_frozen.toml create mode 100644 conformance/results/mypy/dataclasses_hash.toml create mode 100644 conformance/results/mypy/dataclasses_inheritance.toml create mode 100644 conformance/results/mypy/dataclasses_kwonly.toml create mode 100644 conformance/results/mypy/dataclasses_order.toml create mode 100644 conformance/results/mypy/dataclasses_postinit.toml create mode 100644 conformance/results/mypy/dataclasses_slots.toml create mode 100644 conformance/results/mypy/dataclasses_transform_class.toml create mode 100644 conformance/results/mypy/dataclasses_transform_field.toml create mode 100644 conformance/results/mypy/dataclasses_transform_func.toml create mode 100644 conformance/results/mypy/dataclasses_transform_meta.toml create mode 100644 conformance/results/mypy/dataclasses_usage.toml create mode 100644 conformance/results/mypy/generics_self_advanced.toml create mode 100644 conformance/results/mypy/generics_self_attributes.toml create mode 100644 conformance/results/mypy/generics_self_basic.toml create mode 100644 conformance/results/mypy/generics_self_protocols.toml create mode 100644 conformance/results/mypy/generics_self_usage.toml create mode 100644 conformance/results/mypy/literals_interactions.toml create mode 100644 conformance/results/mypy/literals_literalstring.toml create mode 100644 conformance/results/mypy/literals_parameterizations.toml create mode 100644 conformance/results/mypy/literals_semantics.toml create mode 100644 conformance/results/mypy/narrowing_typeguard.toml create mode 100644 conformance/results/mypy/typeddicts_alt_syntax.toml create mode 100644 conformance/results/mypy/typeddicts_class_syntax.toml create mode 100644 conformance/results/mypy/typeddicts_final.toml create mode 100644 conformance/results/mypy/typeddicts_inheritance.toml create mode 100644 conformance/results/mypy/typeddicts_operations.toml create mode 100644 conformance/results/mypy/typeddicts_required.toml create mode 100644 conformance/results/mypy/typeddicts_type_consistency.toml create mode 100644 conformance/results/mypy/typeddicts_usage.toml create mode 100644 conformance/results/mypy/version.toml create mode 100644 conformance/results/pyre/aliases_explicit.toml create mode 100644 conformance/results/pyre/aliases_implicit.toml create mode 100644 conformance/results/pyre/aliases_newtype.toml create mode 100644 conformance/results/pyre/aliases_recursive.toml create mode 100644 conformance/results/pyre/aliases_type_statement.toml create mode 100644 conformance/results/pyre/aliases_typealiastype.toml create mode 100644 conformance/results/pyre/aliases_variance.toml create mode 100644 conformance/results/pyre/annotations_typeexpr.toml create mode 100644 conformance/results/pyre/callables_annotation.toml create mode 100644 conformance/results/pyre/callables_kwargs.toml create mode 100644 conformance/results/pyre/callables_protocol.toml create mode 100644 conformance/results/pyre/dataclasses_descriptors.toml create mode 100644 conformance/results/pyre/dataclasses_frozen.toml create mode 100644 conformance/results/pyre/dataclasses_hash.toml create mode 100644 conformance/results/pyre/dataclasses_inheritance.toml create mode 100644 conformance/results/pyre/dataclasses_kwonly.toml create mode 100644 conformance/results/pyre/dataclasses_order.toml create mode 100644 conformance/results/pyre/dataclasses_postinit.toml create mode 100644 conformance/results/pyre/dataclasses_slots.toml create mode 100644 conformance/results/pyre/dataclasses_transform_class.toml create mode 100644 conformance/results/pyre/dataclasses_transform_field.toml create mode 100644 conformance/results/pyre/dataclasses_transform_func.toml create mode 100644 conformance/results/pyre/dataclasses_transform_meta.toml create mode 100644 conformance/results/pyre/dataclasses_usage.toml create mode 100644 conformance/results/pyre/generics_self_advanced.toml create mode 100644 conformance/results/pyre/generics_self_attributes.toml create mode 100644 conformance/results/pyre/generics_self_basic.toml create mode 100644 conformance/results/pyre/generics_self_protocols.toml create mode 100644 conformance/results/pyre/generics_self_usage.toml create mode 100644 conformance/results/pyre/literals_interactions.toml create mode 100644 conformance/results/pyre/literals_literalstring.toml create mode 100644 conformance/results/pyre/literals_parameterizations.toml create mode 100644 conformance/results/pyre/literals_semantics.toml create mode 100644 conformance/results/pyre/narrowing_typeguard.toml create mode 100644 conformance/results/pyre/typeddicts_alt_syntax.toml create mode 100644 conformance/results/pyre/typeddicts_class_syntax.toml create mode 100644 conformance/results/pyre/typeddicts_final.toml create mode 100644 conformance/results/pyre/typeddicts_inheritance.toml create mode 100644 conformance/results/pyre/typeddicts_operations.toml create mode 100644 conformance/results/pyre/typeddicts_required.toml create mode 100644 conformance/results/pyre/typeddicts_type_consistency.toml create mode 100644 conformance/results/pyre/typeddicts_usage.toml create mode 100644 conformance/results/pyre/version.toml create mode 100644 conformance/results/pyright/aliases_explicit.toml create mode 100644 conformance/results/pyright/aliases_implicit.toml create mode 100644 conformance/results/pyright/aliases_newtype.toml create mode 100644 conformance/results/pyright/aliases_recursive.toml create mode 100644 conformance/results/pyright/aliases_type_statement.toml create mode 100644 conformance/results/pyright/aliases_typealiastype.toml create mode 100644 conformance/results/pyright/aliases_variance.toml create mode 100644 conformance/results/pyright/annotations_typeexpr.toml create mode 100644 conformance/results/pyright/callables_annotation.toml create mode 100644 conformance/results/pyright/callables_kwargs.toml create mode 100644 conformance/results/pyright/callables_protocol.toml create mode 100644 conformance/results/pyright/dataclasses_descriptors.toml create mode 100644 conformance/results/pyright/dataclasses_frozen.toml create mode 100644 conformance/results/pyright/dataclasses_hash.toml create mode 100644 conformance/results/pyright/dataclasses_inheritance.toml create mode 100644 conformance/results/pyright/dataclasses_kwonly.toml create mode 100644 conformance/results/pyright/dataclasses_order.toml create mode 100644 conformance/results/pyright/dataclasses_postinit.toml create mode 100644 conformance/results/pyright/dataclasses_slots.toml create mode 100644 conformance/results/pyright/dataclasses_transform_class.toml create mode 100644 conformance/results/pyright/dataclasses_transform_field.toml create mode 100644 conformance/results/pyright/dataclasses_transform_func.toml create mode 100644 conformance/results/pyright/dataclasses_transform_meta.toml create mode 100644 conformance/results/pyright/dataclasses_usage.toml create mode 100644 conformance/results/pyright/generics_self_advanced.toml create mode 100644 conformance/results/pyright/generics_self_attributes.toml create mode 100644 conformance/results/pyright/generics_self_basic.toml create mode 100644 conformance/results/pyright/generics_self_protocols.toml create mode 100644 conformance/results/pyright/generics_self_usage.toml create mode 100644 conformance/results/pyright/literals_interactions.toml create mode 100644 conformance/results/pyright/literals_literalstring.toml create mode 100644 conformance/results/pyright/literals_parameterizations.toml create mode 100644 conformance/results/pyright/literals_semantics.toml create mode 100644 conformance/results/pyright/narrowing_typeguard.toml create mode 100644 conformance/results/pyright/typeddicts_alt_syntax.toml create mode 100644 conformance/results/pyright/typeddicts_class_syntax.toml create mode 100644 conformance/results/pyright/typeddicts_final.toml create mode 100644 conformance/results/pyright/typeddicts_inheritance.toml create mode 100644 conformance/results/pyright/typeddicts_operations.toml create mode 100644 conformance/results/pyright/typeddicts_required.toml create mode 100644 conformance/results/pyright/typeddicts_type_consistency.toml create mode 100644 conformance/results/pyright/typeddicts_usage.toml create mode 100644 conformance/results/pyright/version.toml create mode 100644 conformance/results/pytype/aliases_explicit.toml create mode 100644 conformance/results/pytype/aliases_implicit.toml create mode 100644 conformance/results/pytype/aliases_newtype.toml create mode 100644 conformance/results/pytype/aliases_recursive.toml create mode 100644 conformance/results/pytype/aliases_type_statement.toml create mode 100644 conformance/results/pytype/aliases_typealiastype.toml create mode 100644 conformance/results/pytype/aliases_variance.toml create mode 100644 conformance/results/pytype/annotations_typeexpr.toml create mode 100644 conformance/results/pytype/callables_annotation.toml create mode 100644 conformance/results/pytype/callables_kwargs.toml create mode 100644 conformance/results/pytype/callables_protocol.toml create mode 100644 conformance/results/pytype/dataclasses_descriptors.toml create mode 100644 conformance/results/pytype/dataclasses_frozen.toml create mode 100644 conformance/results/pytype/dataclasses_hash.toml create mode 100644 conformance/results/pytype/dataclasses_inheritance.toml create mode 100644 conformance/results/pytype/dataclasses_kwonly.toml create mode 100644 conformance/results/pytype/dataclasses_order.toml create mode 100644 conformance/results/pytype/dataclasses_postinit.toml create mode 100644 conformance/results/pytype/dataclasses_slots.toml create mode 100644 conformance/results/pytype/dataclasses_transform_class.toml create mode 100644 conformance/results/pytype/dataclasses_transform_field.toml create mode 100644 conformance/results/pytype/dataclasses_transform_func.toml create mode 100644 conformance/results/pytype/dataclasses_transform_meta.toml create mode 100644 conformance/results/pytype/dataclasses_usage.toml create mode 100644 conformance/results/pytype/generics_self_advanced.toml create mode 100644 conformance/results/pytype/generics_self_attributes.toml create mode 100644 conformance/results/pytype/generics_self_basic.toml create mode 100644 conformance/results/pytype/generics_self_protocols.toml create mode 100644 conformance/results/pytype/generics_self_usage.toml create mode 100644 conformance/results/pytype/literals_interactions.toml create mode 100644 conformance/results/pytype/literals_literalstring.toml create mode 100644 conformance/results/pytype/literals_parameterizations.toml create mode 100644 conformance/results/pytype/literals_semantics.toml create mode 100644 conformance/results/pytype/narrowing_typeguard.toml create mode 100644 conformance/results/pytype/typeddicts_alt_syntax.toml create mode 100644 conformance/results/pytype/typeddicts_class_syntax.toml create mode 100644 conformance/results/pytype/typeddicts_final.toml create mode 100644 conformance/results/pytype/typeddicts_inheritance.toml create mode 100644 conformance/results/pytype/typeddicts_operations.toml create mode 100644 conformance/results/pytype/typeddicts_required.toml create mode 100644 conformance/results/pytype/typeddicts_type_consistency.toml create mode 100644 conformance/results/pytype/typeddicts_usage.toml create mode 100644 conformance/results/pytype/version.toml create mode 100644 conformance/results/results.html create mode 100644 conformance/src/__init__.py create mode 100644 conformance/src/main.py create mode 100644 conformance/src/reporting.py create mode 100644 conformance/src/results_template.html create mode 100644 conformance/src/test_groups.py create mode 100644 conformance/src/test_groups.toml create mode 100644 conformance/src/type_checker.py create mode 100644 conformance/tests/aliases_explicit.py create mode 100644 conformance/tests/aliases_implicit.py create mode 100644 conformance/tests/aliases_newtype.py create mode 100644 conformance/tests/aliases_recursive.py create mode 100644 conformance/tests/aliases_type_statement.py create mode 100644 conformance/tests/aliases_typealiastype.py create mode 100644 conformance/tests/aliases_variance.py create mode 100644 conformance/tests/annotations_typeexpr.py create mode 100644 conformance/tests/callables_annotation.py create mode 100644 conformance/tests/callables_kwargs.py create mode 100644 conformance/tests/callables_protocol.py create mode 100644 conformance/tests/dataclasses_descriptors.py create mode 100644 conformance/tests/dataclasses_frozen.py create mode 100644 conformance/tests/dataclasses_hash.py create mode 100644 conformance/tests/dataclasses_inheritance.py create mode 100644 conformance/tests/dataclasses_kwonly.py create mode 100644 conformance/tests/dataclasses_order.py create mode 100644 conformance/tests/dataclasses_postinit.py create mode 100644 conformance/tests/dataclasses_slots.py create mode 100644 conformance/tests/dataclasses_transform_class.py create mode 100644 conformance/tests/dataclasses_transform_field.py create mode 100644 conformance/tests/dataclasses_transform_func.py create mode 100644 conformance/tests/dataclasses_transform_meta.py create mode 100644 conformance/tests/dataclasses_usage.py create mode 100644 conformance/tests/generics_self_advanced.py create mode 100644 conformance/tests/generics_self_attributes.py create mode 100644 conformance/tests/generics_self_basic.py create mode 100644 conformance/tests/generics_self_protocols.py create mode 100644 conformance/tests/generics_self_usage.py create mode 100644 conformance/tests/literals_interactions.py create mode 100644 conformance/tests/literals_literalstring.py create mode 100644 conformance/tests/literals_parameterizations.py create mode 100644 conformance/tests/literals_semantics.py create mode 100644 conformance/tests/narrowing_typeguard.py create mode 100644 conformance/tests/typeddicts_alt_syntax.py create mode 100644 conformance/tests/typeddicts_class_syntax.py create mode 100644 conformance/tests/typeddicts_final.py create mode 100644 conformance/tests/typeddicts_inheritance.py create mode 100644 conformance/tests/typeddicts_operations.py create mode 100644 conformance/tests/typeddicts_required.py create mode 100644 conformance/tests/typeddicts_type_consistency.py create mode 100644 conformance/tests/typeddicts_usage.py diff --git a/.flake8 b/.flake8 index ff64f42c1..13a85deb9 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,7 @@ [flake8] max-line-length = 90 +exclude = conformance ignore = # irrelevant plugins B3, diff --git a/conformance/.gitignore b/conformance/.gitignore new file mode 100644 index 000000000..5eae7a458 --- /dev/null +++ b/conformance/.gitignore @@ -0,0 +1,47 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Environments +.env +.venv + +# Tools +.mypy_cache +.pyre_configuration +.pyre +.coverage +htmlcov + +# General +.DS_Store + +# Editor temp files +.*.swp + +# Workspace configurations +.vscode diff --git a/conformance/README.md b/conformance/README.md new file mode 100644 index 000000000..ebc39986c --- /dev/null +++ b/conformance/README.md @@ -0,0 +1,71 @@ +# Type System Conformance + +## Motivation + +[PEP 729](https://peps.python.org/pep-0729/) provides a structured and documented way to specify and evolve the Python type system. In support of this effort, an official [Python typing spec](https://github.com/python/typing/tree/main/docs/spec) has been drafted. This spec consolidates details from various historical typing-related PEPs. The spec will be modified over time to clarify unspecified and under-specified parts of the type system. It will also be extended to cover new features of the type system. + +Accompanying the typing specification is this conformance test suite which validates the behavior of static type checkers against the specification. + +## Structure & Name + +This project contains test cases for behaviors defined in the Python typing spec. Tests are structured and grouped in accordance with the specification's chapter headings. + +* [concepts](https://typing.readthedocs.io/en/latest/spec/concepts.html) +* [annotations](https://typing.readthedocs.io/en/latest/spec/annotations.html) +* [specialtypes](https://typing.readthedocs.io/en/latest/spec/special-types.html) +* [generics](https://typing.readthedocs.io/en/latest/spec/generics.html) +* [qualifiers](https://typing.readthedocs.io/en/latest/spec/qualifiers.html) +* [classes](https://typing.readthedocs.io/en/latest/spec/class-compat.html) +* [aliases](https://typing.readthedocs.io/en/latest/spec/aliases.html) +* [literals](https://typing.readthedocs.io/en/latest/spec/literal.html) +* [protocols](https://typing.readthedocs.io/en/latest/spec/protocol.html) +* [callables](https://typing.readthedocs.io/en/latest/spec/callables.html) +* [overloads](https://typing.readthedocs.io/en/latest/spec/overload.html) +* [dataclasses](https://typing.readthedocs.io/en/latest/spec/dataclasses.html) +* [typeddicts](https://typing.readthedocs.io/en/latest/spec/typeddict.html) +* [narrowing](https://typing.readthedocs.io/en/latest/spec/narrowing.html) +* [directives](https://typing.readthedocs.io/en/latest/spec/directives.html) +* [distribution](https://typing.readthedocs.io/en/latest/spec/distributing.html) +* [historical](https://typing.readthedocs.io/en/latest/spec/historical.html) + +A test file is a ".py" file. The file name should start with one of the above names followed by a description of the test (with words separated by underscores). For example, `generics_paramspec_basic_usage.py` would contain the basic usage tests for `ParamSpec`. Each test file can contain multiple individual unit tests, but these tests should be related to each other. If the number of unit tests in a single test file exceeds ten, it may be desirable to split it into separate test files. This will help maintain a consistent level of granularity across tests. + +## Notes About Tests + +Tests are designed to run on all current and future static type checkers. They must therefore be type-checker agnostic and should not rely on functionality or behaviors that are specific to one type checker or another. + +Test cases are meant to be human readable. They should include comments that help explain their purpose (what is being tested, whether an error should be generated, etc.). They should also contain links to the typing spec, discussions, and issue trackers. + +The test suite focuses on static type checking not general Python semantics. Tests should therefore focus on static analysis behaviors, not runtime behaviors. + +## Running the Conformance Test Tool + +To run the conformance test suite: +* Clone the https://github.com/python/typing repo. +* Create and activate a Python 3.12 virtual environment. +* Switch to the `conformance` subdirectory and install all dependencies (`pip install -r requirements.txt`). +* Switch to the `src` subdirectory and run `python main.py`. + +Note that some type checkers may not run on some platforms. For example, pytype cannot be installed on Windows. If a type checker fails to install, tests will be skipped for that type checker. + +## Reporting Conformance Results + +Different type checkers report errors in different ways (with different wording in error messages and different line numbers or character ranges for errors). This variation makes it difficult to fully automate test validation given that tests will want to check for both false positive and false negative type errors. Some level of manual inspection will therefore be needed to determine whether a type checker is fully conformant with all tests in any given test file. This "scoring" process is required only when the output of a test changes — e.g. when a new version of that type checker is released and the tests are rerun. We assume that the output of a type checker will be the same from one run to the next unless/until a new version is released that fixes or introduces a bug. In this case, the output will need to be manually inspected and the conformance results re-scored for those tests whose output has changed. + +Conformance results are reported and summarized for each supported type checker. Initially, results will be reported for mypy and pyright. It is the goal and desire to add additional type checkers over time. + +## Adding a New Test Case + +To add a new test, create a new ".py" file in the `tests` directory. Its name must begin with one of the above test group names followed by an underscore. Write the contents of the test including a module docstring describing the purpose of the test. Next, run the conformance test tool. This will generate a new `.toml` file in the `results` subdirectory corresponding each type checker. Manually review the output from each type checker and determine whether it conforms to the specification. If so, add `conformant = "Pass"` to the `.toml` file. If it does not fully comply, add `conformant = "Partial"` and a `notes` section detailing where it is not compliant. If the type checker doesn't support the feature in the test add `conformant = "Unsupported"`. Once the conformance status has been updated, rerun the conformance test tool to regenerate the summary report. + +## Updating a Test Case + +If a test is updated (augmented or fixed), the process is similar to when adding a new test. Run the conformance test tool to generate new results and manually examine the output of each supported type checker. Then update the conformance status accordingly. Once the conformance status has been updated, rerun the conformance test tool to regenerate the summary report. + +## Updating a Type Checker + +If a new version of a type checker is released, re-run the test tool with the new version. If the type checker output has changed for any test cases, the tool will supply the old and new outputs. Examine these to determine whether the conformance status has changed. Once the conformance status has been updated, re-run the test tool again to regenerate the summary report. + +## Contributing + +Contributions are welcome! diff --git a/conformance/requirements.txt b/conformance/requirements.txt new file mode 100644 index 000000000..3f3979428 --- /dev/null +++ b/conformance/requirements.txt @@ -0,0 +1,7 @@ +tomli +tomlkit +pyright +mypy +pyre-check +pytype; platform_system != "Windows" + diff --git a/conformance/results/mypy/aliases_explicit.toml b/conformance/results/mypy/aliases_explicit.toml new file mode 100644 index 000000000..fafe98232 --- /dev/null +++ b/conformance/results/mypy/aliases_explicit.toml @@ -0,0 +1,28 @@ +conformant = "Partial" +notes = """ +Does not reject specialization of type alias that has already been implicitly specialized. +""" +output = """ +aliases_explicit.py:67: error: Bad number of arguments for type alias, expected: 0, given: 1 [type-arg] +aliases_explicit.py:68: error: Bad number of arguments for type alias, expected: 0, given: 1 [type-arg] +aliases_explicit.py:69: error: Bad number of arguments for type alias, expected: 1, given: 2 [type-arg] +aliases_explicit.py:70: error: Bad number of arguments for type alias, expected: 1, given: 2 [type-arg] +aliases_explicit.py:71: error: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "int" [misc] +aliases_explicit.py:79: error: Invalid type alias: expression is not a valid type [valid-type] +aliases_explicit.py:80: error: Bracketed expression "[...]" is not valid as a type [valid-type] +aliases_explicit.py:81: error: Invalid type alias: expression is not a valid type [valid-type] +aliases_explicit.py:82: error: Invalid type alias: expression is not a valid type [valid-type] +aliases_explicit.py:83: error: Invalid type alias: expression is not a valid type [valid-type] +aliases_explicit.py:84: error: Invalid type alias: expression is not a valid type [valid-type] +aliases_explicit.py:85: error: Invalid type alias: expression is not a valid type [valid-type] +aliases_explicit.py:86: error: Invalid type alias: expression is not a valid type [valid-type] +aliases_explicit.py:87: error: Variable "aliases_explicit.var1" is not valid as a type [valid-type] +aliases_explicit.py:87: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_explicit.py:88: error: Invalid type: try using Literal[True] instead? [valid-type] +aliases_explicit.py:89: error: Invalid type: try using Literal[1] instead? [valid-type] +aliases_explicit.py:90: error: Invalid type alias: expression is not a valid type [valid-type] +aliases_explicit.py:90: error: Function "list" could always be true in boolean context [truthy-function] +aliases_explicit.py:91: error: Invalid type alias: expression is not a valid type [valid-type] +aliases_explicit.py:101: error: "" not callable [operator] +aliases_explicit.py:102: error: Bad number of arguments for type alias, expected: 0, given: 1 [type-arg] +""" diff --git a/conformance/results/mypy/aliases_implicit.toml b/conformance/results/mypy/aliases_implicit.toml new file mode 100644 index 000000000..732bcdf95 --- /dev/null +++ b/conformance/results/mypy/aliases_implicit.toml @@ -0,0 +1,40 @@ +conformant = "Pass" +output = """ +aliases_implicit.py:76: error: Bad number of arguments for type alias, expected: 0, given: 1 [type-arg] +aliases_implicit.py:77: error: Bad number of arguments for type alias, expected: 0, given: 1 [type-arg] +aliases_implicit.py:78: error: Bad number of arguments for type alias, expected: 1, given: 2 [type-arg] +aliases_implicit.py:79: error: Bad number of arguments for type alias, expected: 1, given: 2 [type-arg] +aliases_implicit.py:80: error: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "int" [misc] +aliases_implicit.py:81: error: Type argument "str" of "GoodTypeAlias12" must be a subtype of "float" [type-var] +aliases_implicit.py:100: error: Function "list" could always be true in boolean context [truthy-function] +aliases_implicit.py:106: error: Variable "aliases_implicit.BadTypeAlias1" is not valid as a type [valid-type] +aliases_implicit.py:106: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:107: error: Variable "aliases_implicit.BadTypeAlias2" is not valid as a type [valid-type] +aliases_implicit.py:107: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:108: error: Variable "aliases_implicit.BadTypeAlias3" is not valid as a type [valid-type] +aliases_implicit.py:108: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:109: error: Variable "aliases_implicit.BadTypeAlias4" is not valid as a type [valid-type] +aliases_implicit.py:109: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:110: error: Variable "aliases_implicit.BadTypeAlias5" is not valid as a type [valid-type] +aliases_implicit.py:110: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:111: error: Variable "aliases_implicit.BadTypeAlias6" is not valid as a type [valid-type] +aliases_implicit.py:111: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:112: error: Variable "aliases_implicit.BadTypeAlias7" is not valid as a type [valid-type] +aliases_implicit.py:112: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:113: error: Variable "aliases_implicit.BadTypeAlias8" is not valid as a type [valid-type] +aliases_implicit.py:113: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:114: error: Variable "aliases_implicit.BadTypeAlias9" is not valid as a type [valid-type] +aliases_implicit.py:114: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:115: error: Variable "aliases_implicit.BadTypeAlias10" is not valid as a type [valid-type] +aliases_implicit.py:115: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:116: error: Variable "aliases_implicit.BadTypeAlias11" is not valid as a type [valid-type] +aliases_implicit.py:116: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:117: error: Variable "aliases_implicit.BadTypeAlias12" is not valid as a type [valid-type] +aliases_implicit.py:117: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:118: error: Variable "aliases_implicit.BadTypeAlias13" is not valid as a type [valid-type] +aliases_implicit.py:118: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:119: error: Variable "aliases_implicit.BadTypeAlias14" is not valid as a type [valid-type] +aliases_implicit.py:119: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_implicit.py:133: error: "" not callable [operator] +aliases_implicit.py:135: error: Bad number of arguments for type alias, expected: 0, given: 1 [type-arg] +""" diff --git a/conformance/results/mypy/aliases_newtype.toml b/conformance/results/mypy/aliases_newtype.toml new file mode 100644 index 000000000..b25a4de40 --- /dev/null +++ b/conformance/results/mypy/aliases_newtype.toml @@ -0,0 +1,18 @@ +conformant = "Pass" +output = """ +aliases_newtype.py:11: error: Argument 1 to "UserId" has incompatible type "str"; expected "int" [arg-type] +aliases_newtype.py:12: error: Incompatible types in assignment (expression has type "int", variable has type "UserId") [assignment] +aliases_newtype.py:20: error: Cannot use isinstance() with NewType type [misc] +aliases_newtype.py:23: error: Cannot subclass "NewType" [misc] +aliases_newtype.py:32: error: String argument 1 "BadName" to NewType(...) does not match variable name "GoodName" [misc] +aliases_newtype.py:36: error: "GoodNewType1" expects no type arguments, but 1 given [type-arg] +aliases_newtype.py:42: error: Argument 2 to NewType(...) must be subclassable (got "int | str") [valid-newtype] +aliases_newtype.py:45: error: Type variable "aliases_newtype.T" is unbound [valid-type] +aliases_newtype.py:45: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class) +aliases_newtype.py:45: note: (Hint: Use "T" in function signature to bind "T" inside a function) +aliases_newtype.py:47: error: NewType cannot be used with protocol classes [misc] +aliases_newtype.py:49: error: Argument 2 to NewType(...) must be subclassable (got "Literal[7]") [valid-newtype] +aliases_newtype.py:56: error: Argument 2 to NewType(...) must be subclassable (got "TD1") [valid-newtype] +aliases_newtype.py:60: error: NewType(...) expects exactly two positional arguments [misc] +aliases_newtype.py:62: error: Argument 2 to NewType(...) must be subclassable (got "Any") [valid-newtype] +""" diff --git a/conformance/results/mypy/aliases_recursive.toml b/conformance/results/mypy/aliases_recursive.toml new file mode 100644 index 000000000..6d8137a2c --- /dev/null +++ b/conformance/results/mypy/aliases_recursive.toml @@ -0,0 +1,14 @@ +conformant = "Pass" +output = """ +aliases_recursive.py:19: error: Dict entry 1 has incompatible type "str": "complex"; expected "str": "int | str | float | list[Json] | dict[str, Json] | None" [dict-item] +aliases_recursive.py:20: error: List item 1 has incompatible type "complex"; expected "int | str | float | list[Json] | dict[str, Json] | None" [list-item] +aliases_recursive.py:38: error: Incompatible types in assignment (expression has type "tuple[int, tuple[str, int], tuple[int, tuple[int, list[int]]]]", variable has type "RecursiveTuple") [assignment] +aliases_recursive.py:39: error: Name "t6" already defined on line 38 [no-redef] +aliases_recursive.py:50: error: Dict entry 0 has incompatible type "str": "list[int]"; expected "str": "str | int | Mapping[str, RecursiveMapping]" [dict-item] +aliases_recursive.py:51: error: Dict entry 2 has incompatible type "str": "list[int]"; expected "str": "str | int | Mapping[str, RecursiveMapping]" [dict-item] +aliases_recursive.py:55: error: Dict entry 2 has incompatible type "str": "list[int]"; expected "str": "str | int | Mapping[str, RecursiveMapping]" [dict-item] +aliases_recursive.py:67: error: List item 0 has incompatible type "float"; expected "GenericTypeAlias1[str] | str" [list-item] +aliases_recursive.py:73: error: List item 0 has incompatible type "float"; expected "GenericTypeAlias2[str, int] | str | int" [list-item] +aliases_recursive.py:76: error: Invalid recursive alias: a union item of itself [misc] +aliases_recursive.py:81: error: Invalid recursive alias: a union item of itself [misc] +""" diff --git a/conformance/results/mypy/aliases_type_statement.toml b/conformance/results/mypy/aliases_type_statement.toml new file mode 100644 index 000000000..3774e7bf1 --- /dev/null +++ b/conformance/results/mypy/aliases_type_statement.toml @@ -0,0 +1,76 @@ +conformant = "Unsupported" +notes = """ +Does not support `type` statement. +""" +output = """ +aliases_type_statement.py: error: Cannot assign multiple types to name "BadTypeAlias14" without an explicit "Type[...]" annotation [misc] +aliases_type_statement.py:8: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:9: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:9: error: Name "S3" is not defined [name-defined] +aliases_type_statement.py:9: error: Name "S1" is not defined [name-defined] +aliases_type_statement.py:9: error: Name "S2" is not defined [name-defined] +aliases_type_statement.py:10: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:10: error: Value of type "UnionType" is not indexable [index] +aliases_type_statement.py:14: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:21: error: "type[int]" has no attribute "__value__" [attr-defined] +aliases_type_statement.py:23: error: "type[int]" has no attribute "other_attrib" [attr-defined] +aliases_type_statement.py:37: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:38: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:39: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:40: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:41: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:42: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:43: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:44: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:45: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:46: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:47: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:48: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:48: error: Function "list" could always be true in boolean context [truthy-function] +aliases_type_statement.py:49: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:52: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:54: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:58: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:64: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:64: error: Name "K" is not defined [name-defined] +aliases_type_statement.py:69: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:72: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:72: error: Name "T" is not defined [name-defined] +aliases_type_statement.py:72: error: Variable "aliases_type_statement.RecursiveTypeAlias1" is not valid as a type [valid-type] +aliases_type_statement.py:72: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_type_statement.py:74: error: Variable "aliases_type_statement.RecursiveTypeAlias1" is not valid as a type [valid-type] +aliases_type_statement.py:74: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_type_statement.py:75: error: Variable "aliases_type_statement.RecursiveTypeAlias1" is not valid as a type [valid-type] +aliases_type_statement.py:75: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_type_statement.py:77: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:77: error: Name "P" is not defined [name-defined] +aliases_type_statement.py:77: error: Name "T" is not defined [name-defined] +aliases_type_statement.py:77: error: Name "S" is not defined [name-defined] +aliases_type_statement.py:77: error: Variable "aliases_type_statement.RecursiveTypeAlias2" is not valid as a type [valid-type] +aliases_type_statement.py:77: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_type_statement.py:79: error: Unexpected "..." [misc] +aliases_type_statement.py:79: error: Variable "aliases_type_statement.RecursiveTypeAlias2" is not valid as a type [valid-type] +aliases_type_statement.py:79: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_type_statement.py:80: error: Unexpected "..." [misc] +aliases_type_statement.py:80: error: Variable "aliases_type_statement.RecursiveTypeAlias2" is not valid as a type [valid-type] +aliases_type_statement.py:80: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_type_statement.py:81: error: Unexpected "..." [misc] +aliases_type_statement.py:81: error: Variable "aliases_type_statement.RecursiveTypeAlias2" is not valid as a type [valid-type] +aliases_type_statement.py:81: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_type_statement.py:82: error: Bracketed expression "[...]" is not valid as a type [valid-type] +aliases_type_statement.py:82: error: Variable "aliases_type_statement.RecursiveTypeAlias2" is not valid as a type [valid-type] +aliases_type_statement.py:82: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_type_statement.py:84: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:84: error: Name "RecursiveTypeAlias3" is not defined [name-defined] +aliases_type_statement.py:86: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:86: error: Name "T" is not defined [name-defined] +aliases_type_statement.py:86: error: Cannot determine type of "RecursiveTypeAlias4" [has-type] +aliases_type_statement.py:88: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:88: error: Name "T" is not defined [name-defined] +aliases_type_statement.py:88: error: Variable "aliases_type_statement.RecursiveTypeAlias5" is not valid as a type [valid-type] +aliases_type_statement.py:88: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_type_statement.py:90: error: PEP 695 type aliases are not yet supported [valid-type] +aliases_type_statement.py:90: error: Cannot resolve name "RecursiveTypeAlias7" (possible cyclic definition) [misc] +aliases_type_statement.py:90: error: Name "RecursiveTypeAlias7" is used before definition [used-before-def] +aliases_type_statement.py:91: error: PEP 695 type aliases are not yet supported [valid-type] +""" diff --git a/conformance/results/mypy/aliases_typealiastype.toml b/conformance/results/mypy/aliases_typealiastype.toml new file mode 100644 index 000000000..4ed5f7931 --- /dev/null +++ b/conformance/results/mypy/aliases_typealiastype.toml @@ -0,0 +1,57 @@ +conformant = "Unsupported" +notes = """ +Support for TypeAliasType is not implemented. +""" +output = """ +aliases_typealiastype.py:17: error: Type variable "aliases_typealiastype.T" is unbound [valid-type] +aliases_typealiastype.py:17: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class) +aliases_typealiastype.py:17: note: (Hint: Use "T" in function signature to bind "T" inside a function) +aliases_typealiastype.py:17: error: Argument "type_params" to "TypeAliasType" has incompatible type "tuple[object]"; expected "tuple[TypeVar | ParamSpec | TypeVarTuple, ...]" [arg-type] +aliases_typealiastype.py:18: error: Type variable "aliases_typealiastype.T" is unbound [valid-type] +aliases_typealiastype.py:18: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class) +aliases_typealiastype.py:18: note: (Hint: Use "T" in function signature to bind "T" inside a function) +aliases_typealiastype.py:18: error: Type variable "aliases_typealiastype.S" is unbound [valid-type] +aliases_typealiastype.py:18: note: (Hint: Use "Generic[S]" or "Protocol[S]" base class to bind "S" inside a class) +aliases_typealiastype.py:18: note: (Hint: Use "S" in function signature to bind "S" inside a function) +aliases_typealiastype.py:18: error: Argument "type_params" to "TypeAliasType" has incompatible type "tuple[object, object]"; expected "tuple[TypeVar | ParamSpec | TypeVarTuple, ...]" [arg-type] +aliases_typealiastype.py:19: error: Cannot resolve name "GoodAlias4" (possible cyclic definition) [misc] +aliases_typealiastype.py:19: error: Argument "type_params" to "TypeAliasType" has incompatible type "tuple[object]"; expected "tuple[TypeVar | ParamSpec | TypeVarTuple, ...]" [arg-type] +aliases_typealiastype.py:22: error: Type variable "aliases_typealiastype.S" is unbound [valid-type] +aliases_typealiastype.py:22: note: (Hint: Use "Generic[S]" or "Protocol[S]" base class to bind "S" inside a class) +aliases_typealiastype.py:22: note: (Hint: Use "S" in function signature to bind "S" inside a function) +aliases_typealiastype.py:22: error: Cannot resolve name "GoodAlias5" (possible cyclic definition) [misc] +aliases_typealiastype.py:22: error: TypeVarTuple "Ts" is unbound [valid-type] +aliases_typealiastype.py:23: error: Argument "type_params" to "TypeAliasType" has incompatible type "tuple[object, object, object, object]"; expected "tuple[TypeVar | ParamSpec | TypeVarTuple, ...]" [arg-type] +aliases_typealiastype.py:32: error: "TypeAliasType" has no attribute "other_attrib" [attr-defined] +aliases_typealiastype.py:35: error: Variable "aliases_typealiastype.GoodAlias4" is not valid as a type [valid-type] +aliases_typealiastype.py:35: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_typealiastype.py:36: error: Variable "aliases_typealiastype.GoodAlias4" is not valid as a type [valid-type] +aliases_typealiastype.py:36: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_typealiastype.py:37: error: Unexpected "..." [misc] +aliases_typealiastype.py:37: error: Variable "aliases_typealiastype.GoodAlias5" is not valid as a type [valid-type] +aliases_typealiastype.py:37: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_typealiastype.py:38: error: Unexpected "..." [misc] +aliases_typealiastype.py:38: error: Variable "aliases_typealiastype.GoodAlias5" is not valid as a type [valid-type] +aliases_typealiastype.py:38: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_typealiastype.py:39: error: Bracketed expression "[...]" is not valid as a type [valid-type] +aliases_typealiastype.py:39: error: Variable "aliases_typealiastype.GoodAlias5" is not valid as a type [valid-type] +aliases_typealiastype.py:39: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_typealiastype.py:39: error: Unpack is only valid in a variadic position [valid-type] +aliases_typealiastype.py:40: error: Unexpected "..." [misc] +aliases_typealiastype.py:40: error: Variable "aliases_typealiastype.GoodAlias5" is not valid as a type [valid-type] +aliases_typealiastype.py:40: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +aliases_typealiastype.py:44: error: Type variable "aliases_typealiastype.S" is unbound [valid-type] +aliases_typealiastype.py:44: note: (Hint: Use "Generic[S]" or "Protocol[S]" base class to bind "S" inside a class) +aliases_typealiastype.py:44: note: (Hint: Use "S" in function signature to bind "S" inside a function) +aliases_typealiastype.py:44: error: Argument "type_params" to "TypeAliasType" has incompatible type "tuple[object]"; expected "tuple[TypeVar | ParamSpec | TypeVarTuple, ...]" [arg-type] +aliases_typealiastype.py:46: error: Type variable "aliases_typealiastype.S" is unbound [valid-type] +aliases_typealiastype.py:46: note: (Hint: Use "Generic[S]" or "Protocol[S]" base class to bind "S" inside a class) +aliases_typealiastype.py:46: note: (Hint: Use "S" in function signature to bind "S" inside a function) +aliases_typealiastype.py:48: error: Argument "type_params" to "TypeAliasType" has incompatible type "tuple[object, object]"; expected "tuple[TypeVar | ParamSpec | TypeVarTuple, ...]" [arg-type] +aliases_typealiastype.py:50: error: Cannot determine type of "BadAlias4" [has-type] +aliases_typealiastype.py:52: error: Cannot determine type of "BadAlias5" [has-type] +aliases_typealiastype.py:52: error: Argument "type_params" to "TypeAliasType" has incompatible type "tuple[object]"; expected "tuple[TypeVar | ParamSpec | TypeVarTuple, ...]" [arg-type] +aliases_typealiastype.py:54: error: Cannot determine type of "BadAlias7" [has-type] +aliases_typealiastype.py:54: error: Name "BadAlias7" is used before definition [used-before-def] +aliases_typealiastype.py:69: error: Function "list" could always be true in boolean context [truthy-function] +""" diff --git a/conformance/results/mypy/aliases_variance.toml b/conformance/results/mypy/aliases_variance.toml new file mode 100644 index 000000000..f20fc53d6 --- /dev/null +++ b/conformance/results/mypy/aliases_variance.toml @@ -0,0 +1,7 @@ +conformant = "Pass" +output = """ +aliases_variance.py:24: error: Variance of TypeVar "T_co" incompatible with variance in parent type [type-var] +aliases_variance.py:28: error: Variance of TypeVar "T_co" incompatible with variance in parent type [type-var] +aliases_variance.py:32: error: Variance of TypeVar "T_co" incompatible with variance in parent type [type-var] +aliases_variance.py:44: error: Variance of TypeVar "T_contra" incompatible with variance in parent type [type-var] +""" diff --git a/conformance/results/mypy/annotations_typeexpr.toml b/conformance/results/mypy/annotations_typeexpr.toml new file mode 100644 index 000000000..b5caec86b --- /dev/null +++ b/conformance/results/mypy/annotations_typeexpr.toml @@ -0,0 +1,20 @@ +conformant = "Pass" +output = """ +annotations_typeexpr.py:77: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:77: note: Suggestion: use eval[...] instead of eval(...) +annotations_typeexpr.py:78: error: Bracketed expression "[...]" is not valid as a type [valid-type] +annotations_typeexpr.py:79: error: Syntax error in type annotation [syntax] +annotations_typeexpr.py:79: note: Suggestion: Use Tuple[T1, ..., Tn] instead of (T1, ..., Tn) +annotations_typeexpr.py:80: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:81: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:82: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:83: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:84: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:85: error: Variable "annotations_typeexpr.var1" is not valid as a type [valid-type] +annotations_typeexpr.py:85: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +annotations_typeexpr.py:86: error: Invalid type: try using Literal[True] instead? [valid-type] +annotations_typeexpr.py:87: error: Invalid type: try using Literal[1] instead? [valid-type] +annotations_typeexpr.py:88: error: Invalid type: try using Literal[-1] instead? [valid-type] +annotations_typeexpr.py:89: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:90: error: Invalid type comment or annotation [valid-type] +""" diff --git a/conformance/results/mypy/callables_annotation.toml b/conformance/results/mypy/callables_annotation.toml new file mode 100644 index 000000000..a333e0fab --- /dev/null +++ b/conformance/results/mypy/callables_annotation.toml @@ -0,0 +1,16 @@ +conformant = "Pass" +output = """ +callables_annotation.py:13: error: Too few arguments [call-arg] +callables_annotation.py:14: error: Argument 2 has incompatible type "int"; expected "str" [arg-type] +callables_annotation.py:15: error: Too many arguments [call-arg] +callables_annotation.py:16: error: Unexpected keyword argument "a" [call-arg] +callables_annotation.py:16: error: Unexpected keyword argument "b" [call-arg] +callables_annotation.py:22: error: Too many arguments [call-arg] +callables_annotation.py:39: error: Please use "Callable[[], ]" or "Callable" [misc] +callables_annotation.py:40: error: The first argument to Callable must be a list of types, parameter specification, or "..." [valid-type] +callables_annotation.py:40: note: See https://mypy.readthedocs.io/en/stable/kinds_of_types.html#callable-types-and-lambdas +callables_annotation.py:41: error: Bracketed expression "[...]" is not valid as a type [valid-type] +callables_annotation.py:41: note: Did you mean "List[...]"? +callables_annotation.py:42: error: Please use "Callable[[], ]" or "Callable" [misc] +callables_annotation.py:43: error: Unexpected "..." [misc] +""" diff --git a/conformance/results/mypy/callables_kwargs.toml b/conformance/results/mypy/callables_kwargs.toml new file mode 100644 index 000000000..2e22ca6fe --- /dev/null +++ b/conformance/results/mypy/callables_kwargs.toml @@ -0,0 +1,23 @@ +conformant = "Pass" +output = """ +callables_kwargs.py:22: note: "func1" defined here +callables_kwargs.py:43: error: Missing named argument "v1" for "func1" [call-arg] +callables_kwargs.py:43: error: Missing named argument "v3" for "func1" [call-arg] +callables_kwargs.py:48: error: Unexpected keyword argument "v4" for "func1" [call-arg] +callables_kwargs.py:49: error: Too many positional arguments for "func1" [misc] +callables_kwargs.py:55: error: Argument 1 to "func1" has incompatible type "**dict[str, str]"; expected "int" [arg-type] +callables_kwargs.py:58: error: Argument 1 to "func1" has incompatible type "**dict[str, object]"; expected "int" [arg-type] +callables_kwargs.py:58: error: Argument 1 to "func1" has incompatible type "**dict[str, object]"; expected "str" [arg-type] +callables_kwargs.py:60: error: "func1" gets multiple values for keyword argument "v1" [misc] +callables_kwargs.py:61: error: "func2" gets multiple values for keyword argument "v3" [misc] +callables_kwargs.py:61: error: Argument 1 to "func2" has incompatible type "int"; expected "str" [arg-type] +callables_kwargs.py:62: error: "func2" gets multiple values for keyword argument "v1" [misc] +callables_kwargs.py:98: error: Incompatible types in assignment (expression has type "Callable[[KwArg(TD2)], None]", variable has type "TDProtocol3") [assignment] +callables_kwargs.py:98: note: "TDProtocol3.__call__" has type "Callable[[NamedArg(int, 'v1'), NamedArg(int, 'v2'), NamedArg(str, 'v3')], None]" +callables_kwargs.py:99: error: Incompatible types in assignment (expression has type "Callable[[KwArg(TD2)], None]", variable has type "TDProtocol4") [assignment] +callables_kwargs.py:99: note: "TDProtocol4.__call__" has type "Callable[[NamedArg(int, 'v1')], None]" +callables_kwargs.py:100: error: Incompatible types in assignment (expression has type "Callable[[KwArg(TD2)], None]", variable has type "TDProtocol5") [assignment] +callables_kwargs.py:100: note: "TDProtocol5.__call__" has type "Callable[[Arg(int, 'v1'), Arg(str, 'v3')], None]" +callables_kwargs.py:109: error: Overlap between argument names and ** TypedDict items: "v1" [misc] +callables_kwargs.py:121: error: Unpack item in ** argument must be a TypedDict [misc] +""" diff --git a/conformance/results/mypy/callables_protocol.toml b/conformance/results/mypy/callables_protocol.toml new file mode 100644 index 000000000..fdffbe3bf --- /dev/null +++ b/conformance/results/mypy/callables_protocol.toml @@ -0,0 +1,34 @@ +conformant = "Pass" +output = """ +callables_protocol.py:35: error: Incompatible types in assignment (expression has type "Callable[[VarArg(bytes), NamedArg(int | None, 'max_items')], list[bytes]]", variable has type "Proto1") [assignment] +callables_protocol.py:35: note: "Proto1.__call__" has type "Callable[[VarArg(bytes), DefaultNamedArg(int | None, 'max_len')], list[bytes]]" +callables_protocol.py:36: error: Incompatible types in assignment (expression has type "Callable[[VarArg(bytes)], list[bytes]]", variable has type "Proto1") [assignment] +callables_protocol.py:36: note: "Proto1.__call__" has type "Callable[[VarArg(bytes), DefaultNamedArg(int | None, 'max_len')], list[bytes]]" +callables_protocol.py:37: error: Incompatible types in assignment (expression has type "Callable[[VarArg(bytes), NamedArg(str | None, 'max_len')], list[bytes]]", variable has type "Proto1") [assignment] +callables_protocol.py:37: note: "Proto1.__call__" has type "Callable[[VarArg(bytes), DefaultNamedArg(int | None, 'max_len')], list[bytes]]" +callables_protocol.py:67: error: Incompatible types in assignment (expression has type "Callable[[VarArg(bytes)], Any]", variable has type "Proto2") [assignment] +callables_protocol.py:67: note: "Proto2.__call__" has type "Callable[[VarArg(bytes), KwArg(str)], None]" +callables_protocol.py:68: error: Incompatible types in assignment (expression has type "Callable[[VarArg(str), KwArg(str)], Any]", variable has type "Proto2") [assignment] +callables_protocol.py:68: note: "Proto2.__call__" has type "Callable[[VarArg(bytes), KwArg(str)], None]" +callables_protocol.py:69: error: Incompatible types in assignment (expression has type "Callable[[VarArg(bytes), KwArg(bytes)], Any]", variable has type "Proto2") [assignment] +callables_protocol.py:69: note: "Proto2.__call__" has type "Callable[[VarArg(bytes), KwArg(str)], None]" +callables_protocol.py:70: error: Incompatible types in assignment (expression has type "Callable[[KwArg(str)], Any]", variable has type "Proto2") [assignment] +callables_protocol.py:70: note: "Proto2.__call__" has type "Callable[[VarArg(bytes), KwArg(str)], None]" +callables_protocol.py:97: error: Incompatible types in assignment (expression has type "Callable[[int], None]", variable has type "Proto4") [assignment] +callables_protocol.py:97: note: "function" is missing following "Proto4" protocol member: +callables_protocol.py:97: note: other_attribute +callables_protocol.py:121: error: Incompatible types in assignment (expression has type "Callable[[VarArg(bytes), DefaultNamedArg(int | None, 'max_len')], list[bytes]]", variable has type "NotProto6") [assignment] +callables_protocol.py:169: error: Incompatible types in assignment (expression has type "Callable[[int], Any]", variable has type "Proto8") [assignment] +callables_protocol.py:169: note: "Proto8.__call__" has type overloaded function +callables_protocol.py:186: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +callables_protocol.py:187: error: "Proto9[P, R]" has no attribute "xxx" [attr-defined] +callables_protocol.py:197: error: "Proto9[[int], str]" has no attribute "other_attribute2"; maybe "other_attribute"? [attr-defined] +callables_protocol.py:238: error: Incompatible types in assignment (expression has type "Callable[[int, str], Any]", variable has type "Proto11") [assignment] +callables_protocol.py:238: note: "Proto11.__call__" has type "Callable[[int, Arg(str, 'y')], Any]" +callables_protocol.py:260: error: Incompatible types in assignment (expression has type "Callable[[VarArg(Any), NamedArg(Any, 'kwarg0')], None]", variable has type "Proto12") [assignment] +callables_protocol.py:260: note: "Proto12.__call__" has type "Callable[[VarArg(Any), NamedArg(Any, 'kwarg0'), NamedArg(Any, 'kwarg1')], None]" +callables_protocol.py:284: error: Incompatible types in assignment (expression has type "Callable[[str], str]", variable has type "Proto13_Default") [assignment] +callables_protocol.py:284: note: "Proto13_Default.__call__" has type "Callable[[DefaultArg(str, 'path')], str]" +callables_protocol.py:311: error: Incompatible types in assignment (expression has type "Callable[[NamedArg(str, 'path')], str]", variable has type "Proto14_Default") [assignment] +callables_protocol.py:311: note: "Proto14_Default.__call__" has type "Callable[[DefaultNamedArg(str, 'path')], str]" +""" diff --git a/conformance/results/mypy/dataclasses_descriptors.toml b/conformance/results/mypy/dataclasses_descriptors.toml new file mode 100644 index 000000000..b3ba809c1 --- /dev/null +++ b/conformance/results/mypy/dataclasses_descriptors.toml @@ -0,0 +1,8 @@ +conformant = "Partial" +notes = """ +Does not correctly evaluate type of descriptor access. +""" +output = """ +dataclasses_descriptors.py:66: error: Expression is of type "Desc2[int]", not "int" [assert-type] +dataclasses_descriptors.py:67: error: Expression is of type "Desc2[str]", not "str" [assert-type] +""" diff --git a/conformance/results/mypy/dataclasses_frozen.toml b/conformance/results/mypy/dataclasses_frozen.toml new file mode 100644 index 000000000..a4d73884c --- /dev/null +++ b/conformance/results/mypy/dataclasses_frozen.toml @@ -0,0 +1,7 @@ +conformant = "Pass" +output = """ +dataclasses_frozen.py:16: error: Property "a" defined in "DC1" is read-only [misc] +dataclasses_frozen.py:17: error: Property "b" defined in "DC1" is read-only [misc] +dataclasses_frozen.py:23: error: Cannot inherit non-frozen dataclass from a frozen one [misc] +dataclasses_frozen.py:33: error: Cannot inherit frozen dataclass from a non-frozen one [misc] +""" diff --git a/conformance/results/mypy/dataclasses_hash.toml b/conformance/results/mypy/dataclasses_hash.toml new file mode 100644 index 000000000..a71f51ca0 --- /dev/null +++ b/conformance/results/mypy/dataclasses_hash.toml @@ -0,0 +1,6 @@ +conformant = "Partial" +notes = """ +Does not report when dataclass is not compatible with Hashable protocol. +""" +output = """ +""" diff --git a/conformance/results/mypy/dataclasses_inheritance.toml b/conformance/results/mypy/dataclasses_inheritance.toml new file mode 100644 index 000000000..72c6b22f9 --- /dev/null +++ b/conformance/results/mypy/dataclasses_inheritance.toml @@ -0,0 +1,5 @@ +conformant = "Pass" +output = """ +dataclasses_inheritance.py:60: error: Cannot override instance variable (previously declared on base class "DC6") with class variable [misc] +dataclasses_inheritance.py:64: error: Cannot override class variable (previously declared on base class "DC6") with instance variable [misc] +""" diff --git a/conformance/results/mypy/dataclasses_kwonly.toml b/conformance/results/mypy/dataclasses_kwonly.toml new file mode 100644 index 000000000..d693c57fe --- /dev/null +++ b/conformance/results/mypy/dataclasses_kwonly.toml @@ -0,0 +1,18 @@ +conformant = "Partial" +notes = """ +Incorrectly rejects kw_only field with default before positional field. +""" +output = """ +dataclasses_kwonly.py:23: error: Too many positional arguments for "DC1" [misc] +dataclasses_kwonly.py:29: error: Attributes without a default cannot follow attributes with one [misc] +dataclasses_kwonly.py:32: error: Too many positional arguments for "DC2" [misc] +dataclasses_kwonly.py:32: error: Too few arguments for "DC2" [call-arg] +dataclasses_kwonly.py:32: error: Argument 1 to "DC2" has incompatible type "str"; expected "int" [arg-type] +dataclasses_kwonly.py:35: error: "DC2" gets multiple values for keyword argument "b" [misc] +dataclasses_kwonly.py:35: error: Too few arguments for "DC2" [call-arg] +dataclasses_kwonly.py:35: error: Argument 1 to "DC2" has incompatible type "str"; expected "int" [arg-type] +dataclasses_kwonly.py:38: error: Too many positional arguments for "DC2" [misc] +dataclasses_kwonly.py:38: error: Argument 1 to "DC2" has incompatible type "str"; expected "int" [arg-type] +dataclasses_kwonly.py:38: error: Argument 2 to "DC2" has incompatible type "int"; expected "str" [arg-type] +dataclasses_kwonly.py:53: error: Too many positional arguments for "DC3" [misc] +""" diff --git a/conformance/results/mypy/dataclasses_order.toml b/conformance/results/mypy/dataclasses_order.toml new file mode 100644 index 000000000..f42a06aab --- /dev/null +++ b/conformance/results/mypy/dataclasses_order.toml @@ -0,0 +1,4 @@ +conformant = "Pass" +output = """ +dataclasses_order.py:50: error: Unsupported operand types for < ("DC1" and "DC2") [operator] +""" diff --git a/conformance/results/mypy/dataclasses_postinit.toml b/conformance/results/mypy/dataclasses_postinit.toml new file mode 100644 index 000000000..a94485d91 --- /dev/null +++ b/conformance/results/mypy/dataclasses_postinit.toml @@ -0,0 +1,11 @@ +conformant = "Pass" +output = """ +dataclasses_postinit.py:19: error: Argument 3 of "__post_init__" is incompatible with supertype "dataclass"; supertype defines the argument type as "str" [override] +dataclasses_postinit.py:28: error: "DC1" has no attribute "x" [attr-defined] +dataclasses_postinit.py:29: error: "DC1" has no attribute "y" [attr-defined] +dataclasses_postinit.py:36: error: Signature of "__post_init__" incompatible with supertype "dataclass" [override] +dataclasses_postinit.py:36: note: Superclass: +dataclasses_postinit.py:36: note: def __post_init__(self: DC2, x: int, y: str) -> None +dataclasses_postinit.py:36: note: Subclass: +dataclasses_postinit.py:36: note: def __post_init__(self: DC2, x: int) -> None +""" diff --git a/conformance/results/mypy/dataclasses_slots.toml b/conformance/results/mypy/dataclasses_slots.toml new file mode 100644 index 000000000..8c06b49e4 --- /dev/null +++ b/conformance/results/mypy/dataclasses_slots.toml @@ -0,0 +1,9 @@ +conformant = "Partial" +notes = """ +Does not reject write to instance variable that is not defined in __slots__. +""" +output = """ +dataclasses_slots.py:12: error: "DC1" both defines "__slots__" and is used with "slots=True" [misc] +dataclasses_slots.py:67: error: "type[DC6]" has no attribute "__slots__" [attr-defined] +dataclasses_slots.py:70: error: "DC6" has no attribute "__slots__" [attr-defined] +""" diff --git a/conformance/results/mypy/dataclasses_transform_class.toml b/conformance/results/mypy/dataclasses_transform_class.toml new file mode 100644 index 000000000..e3b63d2a1 --- /dev/null +++ b/conformance/results/mypy/dataclasses_transform_class.toml @@ -0,0 +1,9 @@ +conformant = "Pass" +output = """ +dataclasses_transform_class.py:48: error: Cannot inherit non-frozen dataclass from a frozen one [misc] +dataclasses_transform_class.py:60: error: Property "id" defined in "Customer1" is read-only [misc] +dataclasses_transform_class.py:63: error: Too many positional arguments for "Customer1" [misc] +dataclasses_transform_class.py:69: error: Unsupported left operand type for < ("Customer1") [operator] +dataclasses_transform_class.py:79: error: Too many positional arguments for "Customer2" [misc] +dataclasses_transform_class.py:119: error: Property "id" defined in "Customer3" is read-only [misc] +""" diff --git a/conformance/results/mypy/dataclasses_transform_field.toml b/conformance/results/mypy/dataclasses_transform_field.toml new file mode 100644 index 000000000..83d454d45 --- /dev/null +++ b/conformance/results/mypy/dataclasses_transform_field.toml @@ -0,0 +1,10 @@ +conformant = "Partial" +notes = """ +Does not properly handle field constructor that has default value for `kw_only` or `init` parameter. +""" +output = """ +dataclasses_transform_field.py:64: error: Unexpected keyword argument "id" for "CustomerModel1" [call-arg] +dataclasses_transform_field.py:75: error: Too many positional arguments for "CustomerModel2" [misc] +dataclasses_transform_field.py:75: error: Missing named argument "name" for "CustomerModel2" [call-arg] +dataclasses_transform_field.py:77: error: Missing named argument "id" for "CustomerModel2" [call-arg] +""" diff --git a/conformance/results/mypy/dataclasses_transform_func.toml b/conformance/results/mypy/dataclasses_transform_func.toml new file mode 100644 index 000000000..fdba05f46 --- /dev/null +++ b/conformance/results/mypy/dataclasses_transform_func.toml @@ -0,0 +1,13 @@ +conformant = "Partial" +notes = """ +Does not handle `kw_only=False` override when `kw_only_default=True`. +Does not report error when `order=False` and comparison operators are used. +""" +output = """ +dataclasses_transform_func.py:53: error: Too many positional arguments for "Customer1" [misc] +dataclasses_transform_func.py:57: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment] +dataclasses_transform_func.py:65: error: Unexpected keyword argument "salary" for "Customer1" [call-arg] +dataclasses_transform_func.py:71: error: Too many positional arguments for "Customer2" [misc] +dataclasses_transform_func.py:90: error: Cannot inherit non-frozen dataclass from a frozen one [misc] +dataclasses_transform_func.py:97: error: Property "id" defined in "Customer3" is read-only [misc] +""" diff --git a/conformance/results/mypy/dataclasses_transform_meta.toml b/conformance/results/mypy/dataclasses_transform_meta.toml new file mode 100644 index 000000000..ca6df8752 --- /dev/null +++ b/conformance/results/mypy/dataclasses_transform_meta.toml @@ -0,0 +1,9 @@ +conformant = "Pass" +output = """ +dataclasses_transform_meta.py:48: error: Cannot inherit non-frozen dataclass from a frozen one [misc] +dataclasses_transform_meta.py:60: error: Property "id" defined in "Customer1" is read-only [misc] +dataclasses_transform_meta.py:63: error: Too many positional arguments for "Customer1" [misc] +dataclasses_transform_meta.py:70: error: Unsupported left operand type for < ("Customer1") [operator] +dataclasses_transform_meta.py:80: error: Too many positional arguments for "Customer2" [misc] +dataclasses_transform_meta.py:100: error: Property "id" defined in "Customer3" is read-only [misc] +""" diff --git a/conformance/results/mypy/dataclasses_usage.toml b/conformance/results/mypy/dataclasses_usage.toml new file mode 100644 index 000000000..7e66442d9 --- /dev/null +++ b/conformance/results/mypy/dataclasses_usage.toml @@ -0,0 +1,16 @@ +conformant = "Pass" +output = """ +dataclasses_usage.py:36: error: Accessing "__init__" on an instance is unsound, since instance.__init__ could be from an incompatible subclass [misc] +dataclasses_usage.py:51: error: Missing positional argument "unit_price" in call to "InventoryItem" [call-arg] +dataclasses_usage.py:52: error: Argument 2 to "InventoryItem" has incompatible type "str"; expected "float" [arg-type] +dataclasses_usage.py:53: error: Too many arguments for "InventoryItem" [call-arg] +dataclasses_usage.py:62: error: Attributes without a default cannot follow attributes with one [misc] +dataclasses_usage.py:68: error: Attributes without a default cannot follow attributes with one [misc] +dataclasses_usage.py:74: error: Attributes without a default cannot follow attributes with one [misc] +dataclasses_usage.py:84: error: Too many arguments for "DC4" [call-arg] +dataclasses_usage.py:89: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +dataclasses_usage.py:127: error: Too many arguments for "DC7" [call-arg] +dataclasses_usage.py:130: error: Missing positional argument "y" in call to "DC8" [call-arg] +dataclasses_usage.py:179: error: Too many arguments for "DC13" [call-arg] +dataclasses_usage.py:207: error: Name "v1" already defined on line 25 [no-redef] +""" diff --git a/conformance/results/mypy/generics_self_advanced.toml b/conformance/results/mypy/generics_self_advanced.toml new file mode 100644 index 000000000..bfee9ad9e --- /dev/null +++ b/conformance/results/mypy/generics_self_advanced.toml @@ -0,0 +1,15 @@ +conformant = "Partial" +notes = """ +Does not infer the type of an unannotated `self` parameter to be type `Self`. +Does not retain `Self` when calling method that returns `Self`. +Does not infer the type of an unannotated `cls` parameter to be type `type[Self]`. +Does not retain `Self` when accessing attribute through `type[Self]`. +""" +output = """ +generics_self_advanced.py:35: error: Expression is of type "ChildB", not "Self" [assert-type] +generics_self_advanced.py:38: error: Expression is of type "ChildB", not "Self" [assert-type] +generics_self_advanced.py:42: error: Expression is of type "type[ChildB]", not "type[Self]" [assert-type] +generics_self_advanced.py:43: error: Expression is of type "list[ChildB]", not "list[Self]" [assert-type] +generics_self_advanced.py:44: error: Expression is of type "ChildB", not "Self" [assert-type] +generics_self_advanced.py:45: error: Expression is of type "ChildB", not "Self" [assert-type] +""" diff --git a/conformance/results/mypy/generics_self_attributes.toml b/conformance/results/mypy/generics_self_attributes.toml new file mode 100644 index 000000000..ccd57fe29 --- /dev/null +++ b/conformance/results/mypy/generics_self_attributes.toml @@ -0,0 +1,5 @@ +conformant = "Pass" +output = """ +generics_self_attributes.py:26: error: Argument "next" to "OrdinalLinkedList" has incompatible type "LinkedList[int]"; expected "OrdinalLinkedList | None" [arg-type] +generics_self_attributes.py:32: error: Incompatible types in assignment (expression has type "LinkedList[int]", variable has type "OrdinalLinkedList | None") [assignment] +""" diff --git a/conformance/results/mypy/generics_self_basic.toml b/conformance/results/mypy/generics_self_basic.toml new file mode 100644 index 000000000..317f9ac13 --- /dev/null +++ b/conformance/results/mypy/generics_self_basic.toml @@ -0,0 +1,10 @@ +conformant = "Partial" +notes = """ +Does not properly handle constructor call through `cls` parameter. +""" +output = """ +generics_self_basic.py:19: error: Incompatible return value type (got "Shape", expected "Self") [return-value] +generics_self_basic.py:27: error: Too many arguments for "Shape" [call-arg] +generics_self_basic.py:32: error: Incompatible return value type (got "Shape", expected "Self") [return-value] +generics_self_basic.py:64: error: Self type cannot have type arguments [misc] +""" diff --git a/conformance/results/mypy/generics_self_protocols.toml b/conformance/results/mypy/generics_self_protocols.toml new file mode 100644 index 000000000..d3ef4c71b --- /dev/null +++ b/conformance/results/mypy/generics_self_protocols.toml @@ -0,0 +1,15 @@ +conformant = "Pass" +output = """ +generics_self_protocols.py:61: error: Argument 1 to "accepts_shape" has incompatible type "BadReturnType"; expected "ShapeProtocol" [arg-type] +generics_self_protocols.py:61: note: Following member(s) of "BadReturnType" have conflicts: +generics_self_protocols.py:61: note: Expected: +generics_self_protocols.py:61: note: def set_scale(self, scale: float) -> BadReturnType +generics_self_protocols.py:61: note: Got: +generics_self_protocols.py:61: note: def set_scale(self, scale: float) -> int +generics_self_protocols.py:64: error: Argument 1 to "accepts_shape" has incompatible type "ReturnDifferentClass"; expected "ShapeProtocol" [arg-type] +generics_self_protocols.py:64: note: Following member(s) of "ReturnDifferentClass" have conflicts: +generics_self_protocols.py:64: note: Expected: +generics_self_protocols.py:64: note: def set_scale(self, scale: float) -> ReturnDifferentClass +generics_self_protocols.py:64: note: Got: +generics_self_protocols.py:64: note: def set_scale(self, scale: float) -> ReturnConcreteShape +""" diff --git a/conformance/results/mypy/generics_self_usage.toml b/conformance/results/mypy/generics_self_usage.toml new file mode 100644 index 000000000..4bff8bf4f --- /dev/null +++ b/conformance/results/mypy/generics_self_usage.toml @@ -0,0 +1,19 @@ +conformant = "Pass" +output = """ +generics_self_usage.py:73: error: Self type is only allowed in annotations within class definition [misc] +generics_self_usage.py:76: error: Self type is only allowed in annotations within class definition [misc] +generics_self_usage.py:82: error: Method cannot have explicit self annotation and Self type [misc] +generics_self_usage.py:82: error: A function returning TypeVar should receive at least one argument containing the same TypeVar [type-var] +generics_self_usage.py:82: note: Consider using the upper bound "Foo2" instead +generics_self_usage.py:86: error: Incompatible return value type (got "Foo3", expected "Self") [return-value] +generics_self_usage.py:101: error: Self type is only allowed in annotations within class definition [misc] +generics_self_usage.py:103: error: Self type is only allowed in annotations within class definition [misc] +generics_self_usage.py:106: error: Self type is only allowed in annotations within class definition [misc] +generics_self_usage.py:106: error: Self type cannot be used in type alias target [misc] +generics_self_usage.py:111: error: Static methods cannot use Self type [misc] +generics_self_usage.py:111: error: A function returning TypeVar should receive at least one argument containing the same TypeVar [type-var] +generics_self_usage.py:111: note: Consider using the upper bound "Base" instead +generics_self_usage.py:116: error: Static methods cannot use Self type [misc] +generics_self_usage.py:121: error: Self type cannot be used in a metaclass [misc] +generics_self_usage.py:125: error: Self type cannot be used in a metaclass [misc] +""" diff --git a/conformance/results/mypy/literals_interactions.toml b/conformance/results/mypy/literals_interactions.toml new file mode 100644 index 000000000..b2b4ddbd0 --- /dev/null +++ b/conformance/results/mypy/literals_interactions.toml @@ -0,0 +1,12 @@ +conformant = "Partial" +notes = """ +Does not narrow type of `x` with `x in Literal` type guard pattern. +""" +output = """ +literals_interactions.py:15: error: Tuple index out of range [misc] +literals_interactions.py:16: error: Tuple index out of range [misc] +literals_interactions.py:17: error: Tuple index out of range [misc] +literals_interactions.py:18: error: Tuple index out of range [misc] +literals_interactions.py:106: error: Argument 1 to "expects_bad_status" has incompatible type "str"; expected "Literal['MALFORMED', 'ABORTED']" [arg-type] +literals_interactions.py:109: error: Argument 1 to "expects_pending_status" has incompatible type "str"; expected "Literal['PENDING']" [arg-type] +""" diff --git a/conformance/results/mypy/literals_literalstring.toml b/conformance/results/mypy/literals_literalstring.toml new file mode 100644 index 000000000..0f3409329 --- /dev/null +++ b/conformance/results/mypy/literals_literalstring.toml @@ -0,0 +1,15 @@ +conformant = "Unsupported" +notes = """ +Support for `LiteralString` is not implemented. +""" +output = """ +literals_literalstring.py:36: error: Parameter 2 of Literal[...] is invalid [valid-type] +literals_literalstring.py:37: error: Parameter 1 of Literal[...] is invalid [valid-type] +literals_literalstring.py:43: error: Incompatible types in assignment (expression has type "Literal['two']", variable has type "Literal['']") [assignment] +literals_literalstring.py:74: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment] +literals_literalstring.py:75: error: Incompatible types in assignment (expression has type "bytes", variable has type "str") [assignment] +literals_literalstring.py:142: error: Overloaded function signatures 1 and 2 overlap with incompatible return types [overload-overlap] +literals_literalstring.py:142: error: Overloaded function signatures 1 and 3 overlap with incompatible return types [overload-overlap] +literals_literalstring.py:152: error: Overloaded function signature 3 will never be matched: signature 2's parameter type(s) are the same or broader [misc] +literals_literalstring.py:162: error: Expression is of type "bool", not "str" [assert-type] +""" diff --git a/conformance/results/mypy/literals_parameterizations.toml b/conformance/results/mypy/literals_parameterizations.toml new file mode 100644 index 000000000..2b099f547 --- /dev/null +++ b/conformance/results/mypy/literals_parameterizations.toml @@ -0,0 +1,22 @@ +conformant = "Partial" +notes = """ +Does not reject tuple within Literal. +""" +output = """ +literals_parameterizations.py:40: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type] +literals_parameterizations.py:41: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type] +literals_parameterizations.py:42: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type] +literals_parameterizations.py:43: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type] +literals_parameterizations.py:44: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type] +literals_parameterizations.py:46: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type] +literals_parameterizations.py:47: error: Parameter 1 of Literal[...] is invalid [valid-type] +literals_parameterizations.py:48: error: Parameter 1 of Literal[...] is invalid [valid-type] +literals_parameterizations.py:49: error: Parameter 1 of Literal[...] is invalid [valid-type] +literals_parameterizations.py:50: error: Parameter 1 of Literal[...] cannot be of type "float" [valid-type] +literals_parameterizations.py:51: error: Parameter 1 of Literal[...] cannot be of type "Any" [valid-type] +literals_parameterizations.py:52: error: Parameter 1 of Literal[...] is invalid [valid-type] +literals_parameterizations.py:55: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type] +literals_parameterizations.py:58: error: Literal[...] must have at least one parameter [valid-type] +literals_parameterizations.py:59: error: Parameter 1 of Literal[...] is invalid [valid-type] +literals_parameterizations.py:63: error: Incompatible types in assignment (expression has type "Literal[Color.RED]", variable has type "Literal['Color.RED']") [assignment] +""" diff --git a/conformance/results/mypy/literals_semantics.toml b/conformance/results/mypy/literals_semantics.toml new file mode 100644 index 000000000..589c39314 --- /dev/null +++ b/conformance/results/mypy/literals_semantics.toml @@ -0,0 +1,7 @@ +conformant = "Pass" +output = """ +literals_semantics.py:10: error: Incompatible types in assignment (expression has type "Literal[4]", variable has type "Literal[3]") [assignment] +literals_semantics.py:24: error: Incompatible types in assignment (expression has type "Literal[0]", variable has type "Literal[False]") [assignment] +literals_semantics.py:25: error: Incompatible types in assignment (expression has type "Literal[False]", variable has type "Literal[0]") [assignment] +literals_semantics.py:33: error: Incompatible types in assignment (expression has type "int", variable has type "Literal[3, 4, 5]") [assignment] +""" diff --git a/conformance/results/mypy/narrowing_typeguard.toml b/conformance/results/mypy/narrowing_typeguard.toml new file mode 100644 index 000000000..d8568da2a --- /dev/null +++ b/conformance/results/mypy/narrowing_typeguard.toml @@ -0,0 +1,5 @@ +conformant = "Pass" +output = """ +narrowing_typeguard.py:102: error: TypeGuard functions must have a positional argument [valid-type] +narrowing_typeguard.py:107: error: TypeGuard functions must have a positional argument [valid-type] +""" diff --git a/conformance/results/mypy/typeddicts_alt_syntax.toml b/conformance/results/mypy/typeddicts_alt_syntax.toml new file mode 100644 index 000000000..bb1da440f --- /dev/null +++ b/conformance/results/mypy/typeddicts_alt_syntax.toml @@ -0,0 +1,13 @@ +conformant = "Partial" +notes = """ +Does not support keyword-argument form of alternative syntax (deprecated in 3.11). +""" +output = """ +typeddicts_alt_syntax.py:23: error: TypedDict() expects a dictionary literal as the second argument [misc] +typeddicts_alt_syntax.py:27: error: Invalid TypedDict() field name [misc] +typeddicts_alt_syntax.py:31: error: First argument "WrongName" to TypedDict() does not match variable name "BadTypedDict3" [name-match] +typeddicts_alt_syntax.py:35: error: Too many arguments for TypedDict() [misc] +typeddicts_alt_syntax.py:41: error: Unexpected arguments to TypedDict() [misc] +typeddicts_alt_syntax.py:44: error: Extra keys ("name", "year") for TypedDict "Movie2" [typeddict-unknown-key] +typeddicts_alt_syntax.py:45: error: Extra keys ("name", "year") for TypedDict "Movie2" [typeddict-unknown-key] +""" diff --git a/conformance/results/mypy/typeddicts_class_syntax.toml b/conformance/results/mypy/typeddicts_class_syntax.toml new file mode 100644 index 000000000..3b229c3dd --- /dev/null +++ b/conformance/results/mypy/typeddicts_class_syntax.toml @@ -0,0 +1,8 @@ +conformant = "Pass" +output = """ +typeddicts_class_syntax.py:29: error: Invalid statement in TypedDict definition; expected "field_name: field_type" [misc] +typeddicts_class_syntax.py:33: error: Invalid statement in TypedDict definition; expected "field_name: field_type" [misc] +typeddicts_class_syntax.py:38: error: Invalid statement in TypedDict definition; expected "field_name: field_type" [misc] +typeddicts_class_syntax.py:44: error: Unexpected keyword argument "metaclass" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_class_syntax.py:49: error: Unexpected keyword argument "other" for "__init_subclass__" of "TypedDict" [call-arg] +""" diff --git a/conformance/results/mypy/typeddicts_final.toml b/conformance/results/mypy/typeddicts_final.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/mypy/typeddicts_final.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/mypy/typeddicts_inheritance.toml b/conformance/results/mypy/typeddicts_inheritance.toml new file mode 100644 index 000000000..231cc311e --- /dev/null +++ b/conformance/results/mypy/typeddicts_inheritance.toml @@ -0,0 +1,6 @@ +conformant = "Pass" +output = """ +typeddicts_inheritance.py:44: error: All bases of a new TypedDict must be TypedDict types [misc] +typeddicts_inheritance.py:55: error: Overwriting TypedDict field "x" while extending [misc] +typeddicts_inheritance.py:65: error: Overwriting TypedDict field "x" while merging [misc] +""" diff --git a/conformance/results/mypy/typeddicts_operations.toml b/conformance/results/mypy/typeddicts_operations.toml new file mode 100644 index 000000000..43718b8f1 --- /dev/null +++ b/conformance/results/mypy/typeddicts_operations.toml @@ -0,0 +1,14 @@ +conformant = "Pass" +output = """ +typeddicts_operations.py:22: error: Value of "name" has incompatible type "int"; expected "str" [typeddict-item] +typeddicts_operations.py:23: error: Value of "year" has incompatible type "str"; expected "int" [typeddict-item] +typeddicts_operations.py:24: error: TypedDict "Movie" has no key "other" [typeddict-unknown-key] +typeddicts_operations.py:26: error: TypedDict "Movie" has no key "other" [typeddict-item] +typeddicts_operations.py:28: error: Missing key "year" for TypedDict "Movie" [typeddict-item] +typeddicts_operations.py:29: error: Incompatible types (expression has type "float", TypedDict item "year" has type "int") [typeddict-item] +typeddicts_operations.py:32: error: Extra key "other" for TypedDict "Movie" [typeddict-unknown-key] +typeddicts_operations.py:37: error: Expected TypedDict key to be string literal [misc] +typeddicts_operations.py:47: error: "Movie" has no attribute "clear" [attr-defined] +typeddicts_operations.py:49: error: Key "name" of TypedDict "Movie" cannot be deleted [misc] +typeddicts_operations.py:62: error: "MovieOptional" has no attribute "clear" [attr-defined] +""" diff --git a/conformance/results/mypy/typeddicts_required.toml b/conformance/results/mypy/typeddicts_required.toml new file mode 100644 index 000000000..4e11a4ad6 --- /dev/null +++ b/conformance/results/mypy/typeddicts_required.toml @@ -0,0 +1,12 @@ +conformant = "Partial" +notes = """ +Does not support nesting of `Annotated` and `Required` or `NotRequired`. +""" +output = """ +typeddicts_required.py:12: error: Required[] can be only used in a TypedDict definition [valid-type] +typeddicts_required.py:16: error: NotRequired[] can be only used in a TypedDict definition [valid-type] +typeddicts_required.py:59: error: Required[] can be only used in a TypedDict definition [valid-type] +typeddicts_required.py:60: error: NotRequired[] can be only used in a TypedDict definition [valid-type] +typeddicts_required.py:65: error: Required[] can be only used in a TypedDict definition [valid-type] +typeddicts_required.py:67: error: Required[] can be only used in a TypedDict definition [valid-type] +""" diff --git a/conformance/results/mypy/typeddicts_type_consistency.toml b/conformance/results/mypy/typeddicts_type_consistency.toml new file mode 100644 index 000000000..a9c45d463 --- /dev/null +++ b/conformance/results/mypy/typeddicts_type_consistency.toml @@ -0,0 +1,14 @@ +conformant = "Pass" +output = """ +typeddicts_type_consistency.py:21: error: Incompatible types in assignment (expression has type "B1", variable has type "A1") [assignment] +typeddicts_type_consistency.py:38: error: Incompatible types in assignment (expression has type "B2", variable has type "A2") [assignment] +typeddicts_type_consistency.py:65: error: Incompatible types in assignment (expression has type "A3", variable has type "B3") [assignment] +typeddicts_type_consistency.py:69: error: Extra key "y" for TypedDict "A3" [typeddict-unknown-key] +typeddicts_type_consistency.py:76: error: Incompatible types in assignment (expression has type "B3", variable has type "dict[str, int]") [assignment] +typeddicts_type_consistency.py:77: error: Incompatible types in assignment (expression has type "B3", variable has type "dict[str, object]") [assignment] +typeddicts_type_consistency.py:78: error: Incompatible types in assignment (expression has type "B3", variable has type "dict[Any, Any]") [assignment] +typeddicts_type_consistency.py:82: error: Incompatible types in assignment (expression has type "B3", variable has type "Mapping[str, int]") [assignment] +typeddicts_type_consistency.py:99: error: Incompatible types in assignment (expression has type "str | None", variable has type "str") [assignment] +typeddicts_type_consistency.py:105: error: Incompatible types in assignment (expression has type "int | str", variable has type "int") [assignment] +typeddicts_type_consistency.py:124: error: Incompatible types (expression has type "int", TypedDict item "inner_key" has type "str") [typeddict-item] +""" diff --git a/conformance/results/mypy/typeddicts_usage.toml b/conformance/results/mypy/typeddicts_usage.toml new file mode 100644 index 000000000..d57132453 --- /dev/null +++ b/conformance/results/mypy/typeddicts_usage.toml @@ -0,0 +1,10 @@ +conformant = "Pass" +output = """ +typeddicts_usage.py:23: error: TypedDict "Movie" has no key "director" [typeddict-unknown-key] +typeddicts_usage.py:24: error: Value of "year" has incompatible type "str"; expected "int" [typeddict-item] +typeddicts_usage.py:28: error: Missing key "name" for TypedDict "Movie" [typeddict-item] +typeddicts_usage.py:28: error: Extra key "title" for TypedDict "Movie" [typeddict-unknown-key] +typeddicts_usage.py:35: error: Cannot use isinstance() with TypedDict type [misc] +typeddicts_usage.py:40: error: Variable "typing.TypedDict" is not valid as a type [valid-type] +typeddicts_usage.py:40: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +""" diff --git a/conformance/results/mypy/version.toml b/conformance/results/mypy/version.toml new file mode 100644 index 000000000..63c2f4d79 --- /dev/null +++ b/conformance/results/mypy/version.toml @@ -0,0 +1,2 @@ +version = "mypy 1.8.0" +test_duration = 0.3814089298248291 diff --git a/conformance/results/pyre/aliases_explicit.toml b/conformance/results/pyre/aliases_explicit.toml new file mode 100644 index 000000000..3137647ea --- /dev/null +++ b/conformance/results/pyre/aliases_explicit.toml @@ -0,0 +1,38 @@ +conformant = "Partial" +notes = """ +Incorrectly reports error for type alias defined with ParamSpec. +Incorrectly rejects some valid type aliases when used in annotations. +Incorrectly evaluates generic type alias with ParamSpec with missing type argument. +Does not report some illegal annotation forms as invalid type aliases. +Does not report invalid specialization of generic type aliases. +Incorrectly rejects import alias of `TypeAlias` when used to define type alias. +Does not report invalid specialization of already-specialized generic type alias. +""" +output = """ +aliases_explicit.py:23:0 Incompatible variable type [9]: GoodTypeAlias9 is declared to have type `TA` but is used as type `Type[typing.Callable[..., Variable[$synthetic_attribute_resolution_variable]]]`. +aliases_explicit.py:23:30 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[typing.Any, Type[Variable[$synthetic_attribute_resolution_variable]]]` but got `Tuple[object, TypeVar]`. +aliases_explicit.py:26:0 Incompatible variable type [9]: GoodTypeAlias12 is declared to have type `TA` but is used as type `Type[typing.Callable[..., Variable[$synthetic_attribute_resolution_variable]]]`. +aliases_explicit.py:26:31 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[typing.Any, Type[Variable[$synthetic_attribute_resolution_variable]]]` but got `Tuple[ParamSpec, None]`. +aliases_explicit.py:41:8 Undefined or invalid type [11]: Annotation `GoodTypeAlias9` is not defined as a type. +aliases_explicit.py:44:9 Undefined or invalid type [11]: Annotation `GoodTypeAlias12` is not defined as a type. +aliases_explicit.py:51:25 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `UnionType`. +aliases_explicit.py:53:26 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[str], typing.Any]`. +aliases_explicit.py:54:26 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[int], Type[int], Type[int], Type[str]]`. +aliases_explicit.py:57:29 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[typing.Any, Type[Variable[$synthetic_attribute_resolution_variable]]]` but got `Tuple[List[Type[Union[int, str]]], None]`. +aliases_explicit.py:60:30 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[typing.Any, Type[Variable[$synthetic_attribute_resolution_variable]]]` but got `Tuple[typing.Any, None]`. +aliases_explicit.py:62:26 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `UnionType`. +aliases_explicit.py:80:0 Incompatible variable type [9]: BadTypeAlias2 is declared to have type `TA` but is used as type `List[Type[Union[int, str]]]`. +aliases_explicit.py:81:0 Incompatible variable type [9]: BadTypeAlias3 is declared to have type `TA` but is used as type `Tuple[Tuple[Type[int], Type[str]]]`. +aliases_explicit.py:82:0 Incompatible variable type [9]: BadTypeAlias4 is declared to have type `TA` but is used as type `List[Type[int]]`. +aliases_explicit.py:83:0 Incompatible variable type [9]: BadTypeAlias5 is declared to have type `TA` but is used as type `Dict[str, str]`. +aliases_explicit.py:84:0 Incompatible variable type [9]: BadTypeAlias6 is declared to have type `TA` but is used as type `Type[int]`. +aliases_explicit.py:85:0 Incompatible variable type [9]: BadTypeAlias7 is declared to have type `TA` but is used as type `Type[int]`. +aliases_explicit.py:86:0 Incompatible variable type [9]: BadTypeAlias8 is declared to have type `TA` but is used as type `Type[Union[int, str]]`. +aliases_explicit.py:87:0 Incompatible variable type [9]: BadTypeAlias9 is declared to have type `TA` but is used as type `int`. +aliases_explicit.py:88:0 Incompatible variable type [9]: BadTypeAlias10 is declared to have type `TA` but is used as type `bool`. +aliases_explicit.py:89:0 Incompatible variable type [9]: BadTypeAlias11 is declared to have type `TA` but is used as type `int`. +aliases_explicit.py:90:0 Incompatible variable type [9]: BadTypeAlias12 is declared to have type `TA` but is used as type `Type[Union[list, set]]`. +aliases_explicit.py:91:0 Incompatible variable type [9]: BadTypeAlias13 is declared to have type `TA` but is used as type `str`. +aliases_explicit.py:97:16 Call error [29]: `TA` is not a function. +aliases_explicit.py:101:5 Call error [29]: `TA` is not a function. +""" diff --git a/conformance/results/pyre/aliases_implicit.toml b/conformance/results/pyre/aliases_implicit.toml new file mode 100644 index 000000000..8b34288a5 --- /dev/null +++ b/conformance/results/pyre/aliases_implicit.toml @@ -0,0 +1,34 @@ +conformant = "Partial" +notes = """ +Incorrectly reports error for type alias defined with ParamSpec. +Incorrectly rejects some valid type aliases when used in annotations. +Incorrectly evaluates generic type alias with ParamSpec with missing type argument. +Does not report invalid specialization of generic type aliases. +Does not report error for attempt to instantiate union type alias. +Does not report invalid specialization of already-specialized generic type alias. +""" +output = """ +aliases_implicit.py:38:26 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[typing.Any, Type[Variable[$synthetic_attribute_resolution_variable]]]` but got `Tuple[object, TypeVar]`. +aliases_implicit.py:42:27 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[typing.Any, Type[Variable[$synthetic_attribute_resolution_variable]]]` but got `Tuple[ParamSpec, None]`. +aliases_implicit.py:54:8 Undefined or invalid type [11]: Annotation `GoodTypeAlias9` is not defined as a type. +aliases_implicit.py:58:9 Undefined or invalid type [11]: Annotation `GoodTypeAlias13` is not defined as a type. +aliases_implicit.py:62:25 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `UnionType`. +aliases_implicit.py:64:26 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[str], typing.Any]`. +aliases_implicit.py:65:26 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[int], Type[int], Type[int], Type[str]]`. +aliases_implicit.py:68:29 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[typing.Any, Type[Variable[$synthetic_attribute_resolution_variable]]]` but got `Tuple[List[Type[Union[int, str]]], None]`. +aliases_implicit.py:72:30 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[typing.Any, Type[Variable[$synthetic_attribute_resolution_variable]]]` but got `Tuple[typing.Any, None]`. +aliases_implicit.py:106:8 Undefined or invalid type [11]: Annotation `BadTypeAlias1` is not defined as a type. +aliases_implicit.py:107:8 Undefined or invalid type [11]: Annotation `BadTypeAlias2` is not defined as a type. +aliases_implicit.py:108:8 Undefined or invalid type [11]: Annotation `BadTypeAlias3` is not defined as a type. +aliases_implicit.py:109:8 Undefined or invalid type [11]: Annotation `BadTypeAlias4` is not defined as a type. +aliases_implicit.py:110:8 Undefined or invalid type [11]: Annotation `BadTypeAlias5` is not defined as a type. +aliases_implicit.py:111:8 Undefined or invalid type [11]: Annotation `BadTypeAlias6` is not defined as a type. +aliases_implicit.py:112:8 Undefined or invalid type [11]: Annotation `BadTypeAlias7` is not defined as a type. +aliases_implicit.py:113:8 Undefined or invalid type [11]: Annotation `BadTypeAlias8` is not defined as a type. +aliases_implicit.py:114:8 Undefined or invalid type [11]: Annotation `BadTypeAlias9` is not defined as a type. +aliases_implicit.py:115:9 Undefined or invalid type [11]: Annotation `BadTypeAlias10` is not defined as a type. +aliases_implicit.py:116:9 Undefined or invalid type [11]: Annotation `BadTypeAlias11` is not defined as a type. +aliases_implicit.py:117:9 Undefined or invalid type [11]: Annotation `BadTypeAlias12` is not defined as a type. +aliases_implicit.py:118:9 Undefined or invalid type [11]: Annotation `BadTypeAlias13` is not defined as a type. +aliases_implicit.py:119:9 Undefined or invalid type [11]: Annotation `BadTypeAlias14` is not defined as a type. +""" diff --git a/conformance/results/pyre/aliases_newtype.toml b/conformance/results/pyre/aliases_newtype.toml new file mode 100644 index 000000000..c7094e2e6 --- /dev/null +++ b/conformance/results/pyre/aliases_newtype.toml @@ -0,0 +1,20 @@ +conformant = "Partial" +notes = """ +Does not reject use of NewType in `isinstance` call. +Does not reject use of NewType in class definition statement. +Does not report inconsistency between name of NewType and assigned identifier name. +Does not reject use of NewType with generic class with TypeVar. +Does not reject use of NewType with protocol class. +Does not reject use of NewType with TypedDict class. +Does not reject use of NewType with another NewType. +Does not reject use of NewType with Any. +""" +output = """ +aliases_newtype.py:11:7 Incompatible parameter type [6]: In call `UserId.__init__`, for 1st positional argument, expected `int` but got `str`. +aliases_newtype.py:12:0 Incompatible variable type [9]: u1 is declared to have type `UserId` but is used as type `int`. +aliases_newtype.py:36:5 Invalid type parameters [24]: Non-generic type `GoodNewType1` cannot take parameters. +aliases_newtype.py:42:37 Invalid inheritance [39]: `typing.Union[int, str]` is not a valid parent class. +aliases_newtype.py:49:37 Invalid inheritance [39]: `typing_extensions.Literal[7]` is not a valid parent class. +aliases_newtype.py:60:14 Too many arguments [19]: Call `NewType.__init__` expects 2 positional arguments, 3 were provided. +aliases_newtype.py:62:37 Invalid inheritance [39]: `typing.Any` is not a valid parent class. +""" diff --git a/conformance/results/pyre/aliases_recursive.toml b/conformance/results/pyre/aliases_recursive.toml new file mode 100644 index 000000000..b57ee1075 --- /dev/null +++ b/conformance/results/pyre/aliases_recursive.toml @@ -0,0 +1,21 @@ +conformant = "Partial" +notes = """ +Does not properly handle some recursive type aliases. +Does not properly handle specialization of generic recursive type aliases. +""" +output = """ +aliases_recursive.py:19:0 Incompatible variable type [9]: j4 is declared to have type `aliases_recursive.Json (resolves to Union[None, Dict[str, Json], List[Json], float, int, str])` but is used as type `Dict[str, complex]`. +aliases_recursive.py:20:0 Incompatible variable type [9]: j5 is declared to have type `aliases_recursive.Json (resolves to Union[None, Dict[str, Json], List[Json], float, int, str])` but is used as type `List[complex]`. +aliases_recursive.py:30:35 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[str, typing.Any]`. +aliases_recursive.py:33:4 Undefined or invalid type [11]: Annotation `RecursiveTuple` is not defined as a type. +aliases_recursive.py:42:39 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[Type[Variable[_KT]], Type[Variable[_VT_co](covariant)]]` but got `Tuple[Type[str], str]`. +aliases_recursive.py:44:4 Undefined or invalid type [11]: Annotation `RecursiveMapping` is not defined as a type. +aliases_recursive.py:62:25 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `_SpecialForm`. +aliases_recursive.py:65:4 Undefined or invalid type [11]: Annotation `SpecializedTypeAlias1` is not defined as a type. +aliases_recursive.py:66:4 Undefined or invalid type [11]: Annotation `GenericTypeAlias1` is not defined as a type. +aliases_recursive.py:69:25 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `_SpecialForm`. +aliases_recursive.py:71:4 Undefined or invalid type [11]: Annotation `GenericTypeAlias2` is not defined as a type. +aliases_recursive.py:76:0 Incompatible variable type [9]: RecursiveUnion is declared to have type `TypeAlias` but is used as type `Type[typing.Any]`. +aliases_recursive.py:78:0 Incompatible variable type [9]: MutualReference1 is declared to have type `TypeAlias` but is used as type `Type[typing.Any]`. +aliases_recursive.py:81:0 Incompatible variable type [9]: MutualReference2 is declared to have type `TypeAlias` but is used as type `Type[typing.Any]`. +""" diff --git a/conformance/results/pyre/aliases_type_statement.toml b/conformance/results/pyre/aliases_type_statement.toml new file mode 100644 index 000000000..7b33dcff1 --- /dev/null +++ b/conformance/results/pyre/aliases_type_statement.toml @@ -0,0 +1,7 @@ +conformant = "Unsupported" +notes = """ +Does not support `type` statement. +""" +output = """ +aliases_type_statement.py:8:6 Parsing failure [404]: invalid syntax +""" diff --git a/conformance/results/pyre/aliases_typealiastype.toml b/conformance/results/pyre/aliases_typealiastype.toml new file mode 100644 index 000000000..25117d5d8 --- /dev/null +++ b/conformance/results/pyre/aliases_typealiastype.toml @@ -0,0 +1,45 @@ +conformant = "Unsupported" +notes = """ +Support for TypeAliasType is not implemented. +""" +output = """ +aliases_typealiastype.py:5:0 Undefined import [21]: Could not find a name `TypeAliasType` defined in module `typing`. +aliases_typealiastype.py:16:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:17:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:17:46 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `TypeVar`. +aliases_typealiastype.py:18:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:18:46 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `TypeVar`. +aliases_typealiastype.py:18:56 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `TypeVar`. +aliases_typealiastype.py:19:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:20:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:22:13 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[typing.Any, Type[Variable[$synthetic_attribute_resolution_variable]]]` but got `Tuple[ParamSpec, TypeVar]`. +aliases_typealiastype.py:22:29 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `TypeVar`. +aliases_typealiastype.py:22:71 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `typing.Tuple[typing.Any, ...]`. +aliases_typealiastype.py:27:17 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:27:50 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `TypeVar`. +aliases_typealiastype.py:35:4 Undefined or invalid type [11]: Annotation `GoodAlias4` is not defined as a type. +aliases_typealiastype.py:37:4 Undefined or invalid type [11]: Annotation `GoodAlias5` is not defined as a type. +aliases_typealiastype.py:39:4 Invalid type [31]: Expression `$local_aliases_typealiastype$GoodAlias5[(int, str, [int, str], *tuple[(int, str, int)])]` is not a valid type. +aliases_typealiastype.py:43:12 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:44:22 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `TypeVar`. +aliases_typealiastype.py:46:12 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:46:44 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `TypeVar`. +aliases_typealiastype.py:47:12 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:50:12 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:51:12 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:54:12 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:55:12 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:58:12 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:59:12 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:60:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:61:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:62:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:63:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:64:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:65:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:66:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:67:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:68:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:69:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +aliases_typealiastype.py:70:13 Undefined attribute [16]: Module `typing` has no attribute `TypeAliasType`. +""" diff --git a/conformance/results/pyre/aliases_variance.toml b/conformance/results/pyre/aliases_variance.toml new file mode 100644 index 000000000..029de6d60 --- /dev/null +++ b/conformance/results/pyre/aliases_variance.toml @@ -0,0 +1,7 @@ +conformant = "Pass" +output = """ +aliases_variance.py:24:0 Invalid type variance [46]: The type variable `Variable[T_co](covariant)` is incompatible with parent class type variable `Variable[T]` because subclasses cannot use more permissive type variables than their superclasses. +aliases_variance.py:28:0 Invalid type variance [46]: The type variable `Variable[T_co](covariant)` is incompatible with parent class type variable `Variable[T]` because subclasses cannot use more permissive type variables than their superclasses. +aliases_variance.py:32:0 Invalid type variance [46]: The type variable `Variable[T_co](covariant)` is incompatible with parent class type variable `Variable[T]` because subclasses cannot use more permissive type variables than their superclasses. +aliases_variance.py:44:0 Invalid type variance [46]: The type variable `Variable[T_contra](contravariant)` is incompatible with parent class type variable `Variable[T]` because subclasses cannot use more permissive type variables than their superclasses. +""" diff --git a/conformance/results/pyre/annotations_typeexpr.toml b/conformance/results/pyre/annotations_typeexpr.toml new file mode 100644 index 000000000..86bc19108 --- /dev/null +++ b/conformance/results/pyre/annotations_typeexpr.toml @@ -0,0 +1,19 @@ +conformant = "Pass" +output = """ +annotations_typeexpr.py:65:26 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `object`. +annotations_typeexpr.py:67:27 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[object, typing.Any]`. +annotations_typeexpr.py:77:8 Invalid type [31]: Expression `eval("".join(map(chr, [105, 110, 116])))` is not a valid type. +annotations_typeexpr.py:78:8 Invalid type [31]: Expression `[int, str]` is not a valid type. +annotations_typeexpr.py:79:8 Invalid type [31]: Expression `(int, str)` is not a valid type. +annotations_typeexpr.py:80:8 Invalid type [31]: Expression `comprehension(int for generators(generator($target$i in range(1) if )))` is not a valid type. +annotations_typeexpr.py:81:8 Invalid type [31]: Expression `{ }` is not a valid type. +annotations_typeexpr.py:82:8 Invalid type [31]: Expression `lambda () (int)()` is not a valid type. +annotations_typeexpr.py:83:8 Invalid type [31]: Expression `[int][0]` is not a valid type. +annotations_typeexpr.py:84:8 Invalid type [31]: Expression `int if 1 < 3 else str` is not a valid type. +annotations_typeexpr.py:85:8 Undefined or invalid type [11]: Annotation `var1` is not defined as a type. +annotations_typeexpr.py:86:9 Invalid type [31]: Expression `True` is not a valid type. +annotations_typeexpr.py:87:9 Invalid type [31]: Expression `1` is not a valid type. +annotations_typeexpr.py:88:9 Invalid type [31]: Expression `-1` is not a valid type. +annotations_typeexpr.py:89:9 Invalid type [31]: Expression `int or str` is not a valid type. +annotations_typeexpr.py:90:9 Invalid type [31]: Expression `"int"` is not a valid type. +""" diff --git a/conformance/results/pyre/callables_annotation.toml b/conformance/results/pyre/callables_annotation.toml new file mode 100644 index 000000000..b7967a987 --- /dev/null +++ b/conformance/results/pyre/callables_annotation.toml @@ -0,0 +1,17 @@ +conformant = "Partial" +notes = """ +Does not evaluate correct type for `*args: int` parameter. +Does not reject illegal form `Callable[[...], int]`. +""" +output = """ +callables_annotation.py:13:4 Missing argument [20]: PositionalOnly call expects argument in position 1. +callables_annotation.py:14:10 Incompatible parameter type [6]: In anonymous call, for 2nd positional argument, expected `str` but got `int`. +callables_annotation.py:15:4 Too many arguments [19]: PositionalOnly call expects 2 positional arguments, 3 were provided. +callables_annotation.py:16:4 Unexpected keyword [28]: Unexpected keyword argument `a` to anonymous call. +callables_annotation.py:22:4 Too many arguments [19]: PositionalOnly call expects 0 positional arguments, 1 was provided. +callables_annotation.py:35:28 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[int], typing.Any]`. +callables_annotation.py:39:4 Invalid type [31]: Expression `typing.Callable[int]` is not a valid type. +callables_annotation.py:40:4 Invalid type [31]: Expression `typing.Callable[(int, int)]` is not a valid type. +callables_annotation.py:41:4 Invalid type [31]: Expression `typing.Callable[([], [int])]` is not a valid type. +callables_annotation.py:42:4 Invalid type [31]: Expression `typing.Callable[(int, int, int)]` is not a valid type. +""" diff --git a/conformance/results/pyre/callables_kwargs.toml b/conformance/results/pyre/callables_kwargs.toml new file mode 100644 index 000000000..013f9786a --- /dev/null +++ b/conformance/results/pyre/callables_kwargs.toml @@ -0,0 +1,12 @@ +conformant = "Unsupported" +notes = """ +Does not understand Unpack in the context of **kwargs annotation. +""" +output = """ +callables_kwargs.py:22:20 Undefined or invalid type [11]: Annotation `Unpack` is not defined as a type. +callables_kwargs.py:49:4 Too many arguments [19]: Call `func1` expects 1 positional argument, 4 were provided. +callables_kwargs.py:59:12 Incompatible parameter type [6]: In call `func2`, for 1st positional argument, expected `str` but got `object`. +callables_kwargs.py:61:10 Incompatible parameter type [6]: In call `func2`, for 1st positional argument, expected `str` but got `int`. +callables_kwargs.py:62:18 Incompatible parameter type [6]: In call `func2`, for 2nd positional argument, expected `str` but got `object`. +callables_kwargs.py:121:20 Invalid type variable [34]: The type variable `Variable[T (bound to callables_kwargs.TD2)]` isn't present in the function's parameters. +""" diff --git a/conformance/results/pyre/callables_protocol.toml b/conformance/results/pyre/callables_protocol.toml new file mode 100644 index 000000000..e48969c9b --- /dev/null +++ b/conformance/results/pyre/callables_protocol.toml @@ -0,0 +1,26 @@ +conformant = "Partial" +notes = """ +Does not correctly handle callback protocol that declares attributes in all functions. +Does not report type incompatibility for callback protocol with positional-only parameters. +Incorrectly reports type compatibility error with callback that has *args and **kwargs. +Does not report type incompatibility for callback missing a default argument for positional parameter. +Does not report type incompatibility for callback missing a default argument for keyword parameter. +""" +output = """ +callables_protocol.py:35:0 Incompatible variable type [9]: cb1 is declared to have type `Proto1` but is used as type `typing.Callable(cb1_bad1)[[Variable(bytes), KeywordOnly(max_items, Optional[int])], List[bytes]]`. +callables_protocol.py:36:0 Incompatible variable type [9]: cb1 is declared to have type `Proto1` but is used as type `typing.Callable(cb1_bad2)[[Variable(bytes)], List[bytes]]`. +callables_protocol.py:37:0 Incompatible variable type [9]: cb1 is declared to have type `Proto1` but is used as type `typing.Callable(cb1_bad3)[[Variable(bytes), KeywordOnly(max_len, Optional[str])], List[bytes]]`. +callables_protocol.py:67:0 Incompatible variable type [9]: cb2 is declared to have type `Proto2` but is used as type `typing.Callable(cb2_bad1)[[Variable(bytes)], typing.Any]`. +callables_protocol.py:68:0 Incompatible variable type [9]: cb2 is declared to have type `Proto2` but is used as type `typing.Callable(cb2_bad2)[[Variable(str), Keywords(str)], typing.Any]`. +callables_protocol.py:69:0 Incompatible variable type [9]: cb2 is declared to have type `Proto2` but is used as type `typing.Callable(cb2_bad3)[[Variable(bytes), Keywords(bytes)], typing.Any]`. +callables_protocol.py:70:0 Incompatible variable type [9]: cb2 is declared to have type `Proto2` but is used as type `typing.Callable(cb2_bad4)[[Keywords(str)], typing.Any]`. +callables_protocol.py:97:0 Incompatible variable type [9]: var4 is declared to have type `Proto4` but is used as type `typing.Callable(cb4_bad1)[[Named(x, int)], None]`. +callables_protocol.py:121:0 Incompatible variable type [9]: cb6 is declared to have type `NotProto6` but is used as type `typing.Callable(cb6_bad1)[[Variable(bytes), KeywordOnly(max_len, Optional[int], default)], List[bytes]]`. +callables_protocol.py:169:0 Incompatible variable type [9]: cb8 is declared to have type `Proto8` but is used as type `typing.Callable(cb8_bad1)[[Named(x, int)], typing.Any]`. +callables_protocol.py:186:4 Incompatible attribute type [8]: Attribute `other_attribute` declared in class `Proto9` has type `int` but is used as type `str`. +callables_protocol.py:187:4 Undefined attribute [16]: `Proto9` has no attribute `xxx`. +callables_protocol.py:197:6 Undefined attribute [16]: `Proto9` has no attribute `other_attribute2`. +callables_protocol.py:216:0 Incompatible variable type [9]: cb10 is declared to have type `Proto10` but is used as type `typing.Callable(cb10_good)[[], None]`. +callables_protocol.py:259:0 Incompatible variable type [9]: cb12 is declared to have type `Proto12` but is used as type `typing.Callable(cb12_good2)[[Variable(typing.Any), Keywords(typing.Any)], None]`. +callables_protocol.py:260:0 Incompatible variable type [9]: cb12 is declared to have type `Proto12` but is used as type `typing.Callable(cb12_bad1)[[Variable(typing.Any), KeywordOnly(kwarg0, typing.Any)], None]`. +""" diff --git a/conformance/results/pyre/dataclasses_descriptors.toml b/conformance/results/pyre/dataclasses_descriptors.toml new file mode 100644 index 000000000..1d031cd07 --- /dev/null +++ b/conformance/results/pyre/dataclasses_descriptors.toml @@ -0,0 +1,7 @@ +conformant = "Partial" +notes = """ +Incorrectly generates error when calling constructor of dataclass with descriptor. +""" +output = """ +dataclasses_descriptors.py:35:10 Incompatible parameter type [6]: In call `DC1.__init__`, for 1st positional argument, expected `Desc1` but got `int`. +""" diff --git a/conformance/results/pyre/dataclasses_frozen.toml b/conformance/results/pyre/dataclasses_frozen.toml new file mode 100644 index 000000000..152ccfc50 --- /dev/null +++ b/conformance/results/pyre/dataclasses_frozen.toml @@ -0,0 +1,9 @@ +conformant = "Partial" +notes = """ +Does not reject frozen dataclass inherited from non-frozen dataclass. +Does not reject non-frozen dataclass inherited from frozen dataclass. +""" +output = """ +dataclasses_frozen.py:16:0 Invalid assignment [41]: Cannot reassign final attribute `dc1.a`. +dataclasses_frozen.py:17:0 Invalid assignment [41]: Cannot reassign final attribute `dc1.b`. +""" diff --git a/conformance/results/pyre/dataclasses_hash.toml b/conformance/results/pyre/dataclasses_hash.toml new file mode 100644 index 000000000..a71f51ca0 --- /dev/null +++ b/conformance/results/pyre/dataclasses_hash.toml @@ -0,0 +1,6 @@ +conformant = "Partial" +notes = """ +Does not report when dataclass is not compatible with Hashable protocol. +""" +output = """ +""" diff --git a/conformance/results/pyre/dataclasses_inheritance.toml b/conformance/results/pyre/dataclasses_inheritance.toml new file mode 100644 index 000000000..5000e40e5 --- /dev/null +++ b/conformance/results/pyre/dataclasses_inheritance.toml @@ -0,0 +1,7 @@ +conformant = "Partial" +notes = """ +Does not reject ClassVar that is overridden by instance variable. +Does not reject instance variable that is overridden by ClassVar. +""" +output = """ +""" diff --git a/conformance/results/pyre/dataclasses_kwonly.toml b/conformance/results/pyre/dataclasses_kwonly.toml new file mode 100644 index 000000000..2ed61b5ae --- /dev/null +++ b/conformance/results/pyre/dataclasses_kwonly.toml @@ -0,0 +1,6 @@ +conformant = "Pass" +output = """ +dataclasses_kwonly.py:23:0 Too many arguments [19]: Call `DC1.__init__` expects 1 positional argument, 2 were provided. +dataclasses_kwonly.py:38:0 Too many arguments [19]: Call `DC2.__init__` expects 1 positional argument, 2 were provided. +dataclasses_kwonly.py:53:0 Too many arguments [19]: Call `DC3.__init__` expects 1 positional argument, 2 were provided. +""" diff --git a/conformance/results/pyre/dataclasses_order.toml b/conformance/results/pyre/dataclasses_order.toml new file mode 100644 index 000000000..4f4d92b02 --- /dev/null +++ b/conformance/results/pyre/dataclasses_order.toml @@ -0,0 +1,6 @@ +conformant = "Partial" +notes = """ +Does not report type incompatibility with comparison operator. +""" +output = """ +""" diff --git a/conformance/results/pyre/dataclasses_postinit.toml b/conformance/results/pyre/dataclasses_postinit.toml new file mode 100644 index 000000000..dd01d5656 --- /dev/null +++ b/conformance/results/pyre/dataclasses_postinit.toml @@ -0,0 +1,8 @@ +conformant = "Unsupported" +notes = """ +Does not perform validation of `__post_init__` method. +Does not reject access of `InitVar` from object. +""" +output = """ +dataclasses_postinit.py:54:4 Inconsistent override [14]: `dataclasses_postinit.DC4.__post_init__` overrides method defined in `DC3` inconsistently. Could not find parameter `_age` in overridden signature. +""" diff --git a/conformance/results/pyre/dataclasses_slots.toml b/conformance/results/pyre/dataclasses_slots.toml new file mode 100644 index 000000000..f23f3133f --- /dev/null +++ b/conformance/results/pyre/dataclasses_slots.toml @@ -0,0 +1,9 @@ +conformant = "Partial" +notes = """ +Does not report error when `slots=True` is used with `__slots__` definition. +Does not reject write to instance variable that is not defined in __slots__. +Does not reject access to `__slots__` from dataclass instance when `slots=False`. +""" +output = """ +dataclasses_slots.py:67:0 Undefined attribute [16]: `DC6` has no attribute `__slots__`. +""" diff --git a/conformance/results/pyre/dataclasses_transform_class.toml b/conformance/results/pyre/dataclasses_transform_class.toml new file mode 100644 index 000000000..1ce4be7d7 --- /dev/null +++ b/conformance/results/pyre/dataclasses_transform_class.toml @@ -0,0 +1,22 @@ +conformant = "Unsupported" +notes = """ +Does not understand @dataclass_transform. +""" +output = """ +dataclasses_transform_class.py:27:0 Uninitialized attribute [13]: Attribute `not_a_field` is declared in class `ModelBase` to have type `str` but is never initialized. +dataclasses_transform_class.py:52:0 Uninitialized attribute [13]: Attribute `id` is declared in class `Customer2` to have type `int` but is never initialized. +dataclasses_transform_class.py:57:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_class.py:63:7 Too many arguments [19]: Call `object.__init__` expects 0 positional arguments, 2 were provided. +dataclasses_transform_class.py:65:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_class.py:69:5 Unsupported operand [58]: `<` is not supported for operand types `Customer1` and `Customer1`. +dataclasses_transform_class.py:71:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_class.py:73:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_class.py:75:5 Unsupported operand [58]: `<` is not supported for operand types `Customer2` and `Customer2`. +dataclasses_transform_class.py:79:7 Too many arguments [19]: Call `object.__init__` expects 0 positional arguments, 2 were provided. +dataclasses_transform_class.py:86:0 Uninitialized attribute [13]: Attribute `not_a_field` is declared in class `GenericModelBase` to have type `Variable[T]` but is never initialized. +dataclasses_transform_class.py:103:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_class.py:107:0 Uninitialized attribute [13]: Attribute `not_a_field` is declared in class `ModelBaseFrozen` to have type `str` but is never initialized. +dataclasses_transform_class.py:111:0 Uninitialized attribute [13]: Attribute `id` is declared in class `Customer3` to have type `int` but is never initialized. +dataclasses_transform_class.py:111:0 Uninitialized attribute [13]: Attribute `name` is declared in class `Customer3` to have type `str` but is never initialized. +dataclasses_transform_class.py:116:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +""" diff --git a/conformance/results/pyre/dataclasses_transform_field.toml b/conformance/results/pyre/dataclasses_transform_field.toml new file mode 100644 index 000000000..cbecc0d24 --- /dev/null +++ b/conformance/results/pyre/dataclasses_transform_field.toml @@ -0,0 +1,11 @@ +conformant = "Unsupported" +notes = """ +Does not understand @dataclass_transform. +""" +output = """ +dataclasses_transform_field.py:49:42 Invalid type variable [34]: The type variable `Variable[T]` isn't present in the function's parameters. +dataclasses_transform_field.py:60:0 Unexpected keyword [28]: Unexpected keyword argument `name` to call `object.__init__`. +dataclasses_transform_field.py:64:0 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_field.py:75:0 Too many arguments [19]: Call `object.__init__` expects 0 positional arguments, 1 was provided. +dataclasses_transform_field.py:77:0 Unexpected keyword [28]: Unexpected keyword argument `name` to call `object.__init__`. +""" diff --git a/conformance/results/pyre/dataclasses_transform_func.toml b/conformance/results/pyre/dataclasses_transform_func.toml new file mode 100644 index 000000000..5c010ed56 --- /dev/null +++ b/conformance/results/pyre/dataclasses_transform_func.toml @@ -0,0 +1,26 @@ +conformant = "Unsupported" +notes = """ +Does not understand @dataclass_transform. +""" +output = """ +dataclasses_transform_func.py:20:0 Incompatible overload [43]: The implementation of `create_model` does not accept all possible arguments of overload defined on line `20`. +dataclasses_transform_func.py:25:5 Invalid type variable [34]: The type variable `Variable[T]` isn't present in the function's parameters. +dataclasses_transform_func.py:29:0 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). +dataclasses_transform_func.py:34:0 Uninitialized attribute [13]: Attribute `id` is declared in class `Customer1` to have type `int` but is never initialized. +dataclasses_transform_func.py:34:0 Uninitialized attribute [13]: Attribute `name` is declared in class `Customer1` to have type `str` but is never initialized. +dataclasses_transform_func.py:40:0 Uninitialized attribute [13]: Attribute `id` is declared in class `Customer2` to have type `int` but is never initialized. +dataclasses_transform_func.py:40:0 Uninitialized attribute [13]: Attribute `name` is declared in class `Customer2` to have type `str` but is never initialized. +dataclasses_transform_func.py:46:0 Uninitialized attribute [13]: Attribute `salary` is declared in class `Customer2Subclass` to have type `float` but is never initialized. +dataclasses_transform_func.py:50:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_func.py:53:7 Too many arguments [19]: Call `object.__init__` expects 0 positional arguments, 2 were provided. +dataclasses_transform_func.py:57:0 Incompatible attribute type [8]: Attribute `name` declared in class `Customer1` has type `str` but is used as type `int`. +dataclasses_transform_func.py:61:5 Unsupported operand [58]: `<` is not supported for operand types `Customer1` and `Customer1`. +dataclasses_transform_func.py:65:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_func.py:67:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_func.py:71:7 Too many arguments [19]: Call `object.__init__` expects 0 positional arguments, 2 were provided. +dataclasses_transform_func.py:73:5 Unsupported operand [58]: `<` is not supported for operand types `Customer2` and `Customer2`. +dataclasses_transform_func.py:82:0 Uninitialized attribute [13]: Attribute `id` is declared in class `Customer3` to have type `int` but is never initialized. +dataclasses_transform_func.py:82:0 Uninitialized attribute [13]: Attribute `name` is declared in class `Customer3` to have type `str` but is never initialized. +dataclasses_transform_func.py:90:0 Uninitialized attribute [13]: Attribute `age` is declared in class `Customer3Subclass` to have type `int` but is never initialized. +dataclasses_transform_func.py:94:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +""" diff --git a/conformance/results/pyre/dataclasses_transform_meta.toml b/conformance/results/pyre/dataclasses_transform_meta.toml new file mode 100644 index 000000000..130917ca1 --- /dev/null +++ b/conformance/results/pyre/dataclasses_transform_meta.toml @@ -0,0 +1,19 @@ +conformant = "Unsupported" +notes = """ +Does not understand @dataclass_transform. +""" +output = """ +dataclasses_transform_meta.py:25:0 Uninitialized attribute [13]: Attribute `not_a_field` is declared in class `ModelMeta` to have type `str` but is never initialized. +dataclasses_transform_meta.py:52:0 Uninitialized attribute [13]: Attribute `id` is declared in class `Customer2` to have type `int` but is never initialized. +dataclasses_transform_meta.py:57:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_meta.py:63:7 Too many arguments [19]: Call `object.__init__` expects 0 positional arguments, 2 were provided. +dataclasses_transform_meta.py:66:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_meta.py:70:5 Unsupported operand [58]: `<` is not supported for operand types `Customer1` and `Customer1`. +dataclasses_transform_meta.py:72:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_meta.py:74:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +dataclasses_transform_meta.py:76:5 Unsupported operand [58]: `<` is not supported for operand types `Customer2` and `Customer2`. +dataclasses_transform_meta.py:80:7 Too many arguments [19]: Call `object.__init__` expects 0 positional arguments, 2 were provided. +dataclasses_transform_meta.py:92:0 Uninitialized attribute [13]: Attribute `id` is declared in class `Customer3` to have type `int` but is never initialized. +dataclasses_transform_meta.py:92:0 Uninitialized attribute [13]: Attribute `name` is declared in class `Customer3` to have type `str` but is never initialized. +dataclasses_transform_meta.py:97:7 Unexpected keyword [28]: Unexpected keyword argument `id` to call `object.__init__`. +""" diff --git a/conformance/results/pyre/dataclasses_usage.toml b/conformance/results/pyre/dataclasses_usage.toml new file mode 100644 index 000000000..1f111055b --- /dev/null +++ b/conformance/results/pyre/dataclasses_usage.toml @@ -0,0 +1,20 @@ +conformant = "Partial" +notes = """ +Does not report error when field with no default follows field with default. +Incorrectly reports error with InitVar that has default value. +""" +output = """ +dataclasses_usage.py:51:5 Missing argument [20]: Call `InventoryItem.__init__` expects argument `unit_price`. +dataclasses_usage.py:52:27 Incompatible parameter type [6]: In call `InventoryItem.__init__`, for 2nd positional argument, expected `float` but got `str`. +dataclasses_usage.py:53:5 Too many arguments [19]: Call `InventoryItem.__init__` expects 3 positional arguments, 4 were provided. +dataclasses_usage.py:73:4 Incompatible attribute type [8]: Attribute `a` declared in class `DC3` has type `InitVar[int]` but is used as type `int`. +dataclasses_usage.py:84:5 Too many arguments [19]: Call `DC4.__init__` expects 1 positional argument, 2 were provided. +dataclasses_usage.py:89:4 Incompatible attribute type [8]: Attribute `a` declared in class `DC5` has type `int` but is used as type `str`. +dataclasses_usage.py:116:0 Uninitialized attribute [13]: Attribute `y` is declared in class `DC8` to have type `int` but is never initialized. +dataclasses_usage.py:127:0 Too many arguments [19]: Call `DC7.__init__` expects 1 positional argument, 2 were provided. +dataclasses_usage.py:130:0 Missing argument [20]: Call `DC8.__init__` expects argument `y`. +dataclasses_usage.py:172:0 Uninitialized attribute [13]: Attribute `x` is declared in class `DC13` to have type `int` but is never initialized. +dataclasses_usage.py:172:0 Uninitialized attribute [13]: Attribute `x_squared` is declared in class `DC13` to have type `int` but is never initialized. +dataclasses_usage.py:179:0 Too many arguments [19]: Call `object.__init__` expects 0 positional arguments, 1 was provided. +dataclasses_usage.py:207:0 Incompatible variable type [9]: v1 is declared to have type `DataclassProto` but is used as type `DC15`. +""" diff --git a/conformance/results/pyre/generics_self_advanced.toml b/conformance/results/pyre/generics_self_advanced.toml new file mode 100644 index 000000000..2930afeb0 --- /dev/null +++ b/conformance/results/pyre/generics_self_advanced.toml @@ -0,0 +1,15 @@ +conformant = "Unsupported" +notes = """ +Does not understand `Self` type. +""" +output = """ +generics_self_advanced.py:25:7 Undefined or invalid type [11]: Annotation `Self` is not defined as a type. +generics_self_advanced.py:35:26 Undefined attribute [16]: Module `typing` has no attribute `Self`. +generics_self_advanced.py:36:33 Undefined attribute [16]: Module `typing` has no attribute `Self`. +generics_self_advanced.py:37:31 Undefined attribute [16]: Module `typing` has no attribute `Self`. +generics_self_advanced.py:38:36 Undefined attribute [16]: Module `typing` has no attribute `Self`. +generics_self_advanced.py:42:30 Undefined attribute [16]: Module `typing` has no attribute `Self`. +generics_self_advanced.py:43:32 Undefined attribute [16]: Module `typing` has no attribute `Self`. +generics_self_advanced.py:44:30 Undefined attribute [16]: Module `typing` has no attribute `Self`. +generics_self_advanced.py:45:35 Undefined attribute [16]: Module `typing` has no attribute `Self`. +""" diff --git a/conformance/results/pyre/generics_self_attributes.toml b/conformance/results/pyre/generics_self_attributes.toml new file mode 100644 index 000000000..f772cd636 --- /dev/null +++ b/conformance/results/pyre/generics_self_attributes.toml @@ -0,0 +1,10 @@ +conformant = "Unsupported" +notes = """ +Does not understand `Self` type. +""" +output = """ +generics_self_attributes.py:16:10 Undefined or invalid type [11]: Annotation `Self` is not defined as a type. +generics_self_attributes.py:26:5 Unexpected keyword [28]: Unexpected keyword argument `next` to call `OrdinalLinkedList.__init__`. +generics_self_attributes.py:29:14 Unexpected keyword [28]: Unexpected keyword argument `next` to call `OrdinalLinkedList.__init__`. +generics_self_attributes.py:32:14 Unexpected keyword [28]: Unexpected keyword argument `next` to call `LinkedList.__init__`. +""" diff --git a/conformance/results/pyre/generics_self_basic.toml b/conformance/results/pyre/generics_self_basic.toml new file mode 100644 index 000000000..66191b50c --- /dev/null +++ b/conformance/results/pyre/generics_self_basic.toml @@ -0,0 +1,15 @@ +conformant = "Unsupported" +notes = """ +Does not understand `Self` type. +""" +output = """ +generics_self_basic.py:13:26 Undefined attribute [16]: Module `typing` has no attribute `Self`. +generics_self_basic.py:19:8 Incompatible return type [7]: Expected `Variable[_Self_generics_self_basic_Shape__ (bound to Shape)]` but got `Shape`. +generics_self_basic.py:26:30 Undefined attribute [16]: Module `typing` has no attribute `Self`. +generics_self_basic.py:27:15 Too many arguments [19]: Call `object.__init__` expects 0 positional arguments, 1 was provided. +generics_self_basic.py:32:8 Incompatible return type [7]: Expected `Variable[_Self_generics_self_basic_Shape__ (bound to Shape)]` but got `Shape`. +generics_self_basic.py:39:27 Undefined attribute [16]: Module `typing` has no attribute `Self`. +generics_self_basic.py:57:0 Uninitialized attribute [13]: Attribute `value` is declared in class `Container` to have type `Variable[T]` but is never initialized. +generics_self_basic.py:64:25 Undefined or invalid type [11]: Annotation `Self` is not defined as a type. +generics_self_basic.py:80:31 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[T]]` but got `TypeVar`. +""" diff --git a/conformance/results/pyre/generics_self_protocols.toml b/conformance/results/pyre/generics_self_protocols.toml new file mode 100644 index 000000000..3943248a8 --- /dev/null +++ b/conformance/results/pyre/generics_self_protocols.toml @@ -0,0 +1,7 @@ +conformant = "Partial" +notes = """ +Does not reject protocol compatibility due to method `Self` return type. +""" +output = """ +generics_self_protocols.py:61:18 Incompatible parameter type [6]: In call `accepts_shape`, for 1st positional argument, expected `ShapeProtocol` but got `BadReturnType`. +""" diff --git a/conformance/results/pyre/generics_self_usage.toml b/conformance/results/pyre/generics_self_usage.toml new file mode 100644 index 000000000..6349aa8a7 --- /dev/null +++ b/conformance/results/pyre/generics_self_usage.toml @@ -0,0 +1,10 @@ +conformant = "Unsupported" +notes = """ +Does not understand `Self` type. +""" +output = """ +generics_self_usage.py:20:34 Undefined or invalid type [11]: Annotation `Self` is not defined as a type. +generics_self_usage.py:86:8 Incompatible return type [7]: Expected `Variable[_Self_generics_self_usage_Foo3__ (bound to Foo3)]` but got `Foo3`. +generics_self_usage.py:106:0 Incompatible variable type [9]: TupleSelf is declared to have type `TypeAlias` but is used as type `Type[tuple[Variable[_T_co](covariant)]]`. +generics_self_usage.py:106:29 Undefined attribute [16]: Module `typing` has no attribute `Self`. +""" diff --git a/conformance/results/pyre/literals_interactions.toml b/conformance/results/pyre/literals_interactions.toml new file mode 100644 index 000000000..4f88bb35a --- /dev/null +++ b/conformance/results/pyre/literals_interactions.toml @@ -0,0 +1,11 @@ +conformant = "Partial" +notes = """ +Does not detect out-of-bound tuple literal index. +Does not narrow type of `x` with `x in Literal` type guard pattern. +Does not narrow type of `x` with `x == Literal` type guard pattern. +""" +output = """ +literals_interactions.py:51:38 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[AnyStr <: [str, bytes]]]` but got `object`. +literals_interactions.py:106:34 Incompatible parameter type [6]: In call `expects_bad_status`, for 1st positional argument, expected `Union[typing_extensions.Literal['ABORTED'], typing_extensions.Literal['MALFORMED']]` but got `str`. +literals_interactions.py:109:31 Non-literal string [62]: In call `expects_pending_status`, for 1st positional argument, expected `LiteralString` but got `str`. Ensure only a string literal or a `LiteralString` is used. +""" diff --git a/conformance/results/pyre/literals_literalstring.toml b/conformance/results/pyre/literals_literalstring.toml new file mode 100644 index 000000000..c60ee6199 --- /dev/null +++ b/conformance/results/pyre/literals_literalstring.toml @@ -0,0 +1,13 @@ +conformant = "Pass" +output = """ +literals_literalstring.py:36:11 Invalid type [31]: Expression `LiteralString` is not a literal value. +literals_literalstring.py:36:11 Undefined or invalid type [11]: Annotation `typing` is not defined as a type. +literals_literalstring.py:37:13 Invalid type [31]: Expression `LiteralString` is not a literal value. +literals_literalstring.py:43:4 Incompatible variable type [9]: x2 is declared to have type `typing_extensions.Literal['']` but is used as type `typing_extensions.Literal['two']`. +literals_literalstring.py:66:4 Incompatible variable type [9]: x1 is declared to have type `typing_extensions.LiteralString` but is used as type `str`. +literals_literalstring.py:74:4 Incompatible variable type [9]: x3 is declared to have type `typing_extensions.LiteralString` but is used as type `typing_extensions.Literal[3]`. +literals_literalstring.py:75:4 Incompatible variable type [9]: x4 is declared to have type `typing_extensions.LiteralString` but is used as type `typing_extensions.Literal[b'test']`. +literals_literalstring.py:120:21 Incompatible parameter type [6]: In call `literal_identity`, for 1st positional argument, expected `Variable[TLiteral (bound to typing_extensions.LiteralString)]` but got `str`. +literals_literalstring.py:134:50 Incompatible parameter type [6]: In call `Container.__init__`, for 1st positional argument, expected `Variable[T (bound to typing_extensions.LiteralString)]` but got `str`. +literals_literalstring.py:166:4 Incompatible variable type [9]: x1 is declared to have type `List[str]` but is used as type `List[typing_extensions.LiteralString]`. +""" diff --git a/conformance/results/pyre/literals_parameterizations.toml b/conformance/results/pyre/literals_parameterizations.toml new file mode 100644 index 000000000..89d38c177 --- /dev/null +++ b/conformance/results/pyre/literals_parameterizations.toml @@ -0,0 +1,30 @@ +conformant = "Partial" +notes = """ +Does not support type aliases in Literal type expression. +Does not support nested Literal type expression. +Does not reject unary + operator in Literal type expression. +Does not reject tuple in Literal type expression. +Does not reject "bare" Literal in type expression. +""" +output = """ +literals_parameterizations.py:32:0 Invalid type [31]: Expression `AppendMode` is not a literal value. +literals_parameterizations.py:32:0 Invalid type [31]: Expression `ReadOnlyMode` is not a literal value. +literals_parameterizations.py:32:0 Invalid type [31]: Expression `WriteAndTruncateMode` is not a literal value. +literals_parameterizations.py:32:0 Invalid type [31]: Expression `WriteNoTruncateMode` is not a literal value. +literals_parameterizations.py:32:0 Undefined or invalid type [11]: Annotation `` is not defined as a type. +literals_parameterizations.py:34:8 Invalid type [31]: Expression `typing.Literal[(typing.Literal[(typing.Literal[(1, 2, 3)], "foo")], 5, None)]` is not a valid type. +literals_parameterizations.py:40:6 Invalid type [31]: Expression `typing.Literal[3.__add__(4)]` is not a valid type. +literals_parameterizations.py:41:6 Invalid type [31]: Expression `typing.Literal["foo".replace("o", "b")]` is not a valid type. +literals_parameterizations.py:42:6 Invalid type [31]: Expression `typing.Literal[4.__add__(3.000000j)]` is not a valid type. +literals_parameterizations.py:44:6 Invalid type [31]: Expression `typing.Literal[not False]` is not a valid type. +literals_parameterizations.py:46:6 Invalid type [31]: Expression `typing.Literal[{ "a":"b","c":"d" }]` is not a valid type. +literals_parameterizations.py:47:6 Invalid type [31]: Expression `typing.Literal[int]` is not a valid type. +literals_parameterizations.py:48:6 Invalid type [31]: Expression `variable` is not a literal value. +literals_parameterizations.py:49:7 Invalid type [31]: Expression `T` is not a literal value. +literals_parameterizations.py:50:7 Invalid type [31]: Expression `typing.Literal[3.140000]` is not a valid type. +literals_parameterizations.py:51:7 Invalid type [31]: Expression `Any` is not a literal value. +literals_parameterizations.py:52:7 Invalid type [31]: Expression `typing.Literal[...]` is not a valid type. +literals_parameterizations.py:55:19 Invalid type [31]: Expression `typing.Literal[1.__add__(2)]` is not a valid type. +literals_parameterizations.py:59:3 Invalid type [31]: Expression `my_function` is not a literal value. +literals_parameterizations.py:63:4 Incompatible variable type [9]: x1 is declared to have type `typing_extensions.Literal['Color.RED']` but is used as type `typing_extensions.Literal[Color.RED]`. +""" diff --git a/conformance/results/pyre/literals_semantics.toml b/conformance/results/pyre/literals_semantics.toml new file mode 100644 index 000000000..c7f8f8c9f --- /dev/null +++ b/conformance/results/pyre/literals_semantics.toml @@ -0,0 +1,9 @@ +conformant = "Partial" +notes = """ +Does not reject augmented operation that modifies literal value. +""" +output = """ +literals_semantics.py:10:0 Incompatible variable type [9]: v2 is declared to have type `typing_extensions.Literal[3]` but is used as type `typing_extensions.Literal[4]`. +literals_semantics.py:24:4 Incompatible variable type [9]: x1 is declared to have type `typing_extensions.Literal[False]` but is used as type `typing_extensions.Literal[0]`. +literals_semantics.py:25:4 Incompatible variable type [9]: x2 is declared to have type `typing_extensions.Literal[0]` but is used as type `typing_extensions.Literal[False]`. +""" diff --git a/conformance/results/pyre/narrowing_typeguard.toml b/conformance/results/pyre/narrowing_typeguard.toml new file mode 100644 index 000000000..b14ed9e43 --- /dev/null +++ b/conformance/results/pyre/narrowing_typeguard.toml @@ -0,0 +1,9 @@ +conformant = "Partial" +notes = """ +Does not support `tuple` in `assert_type` call. +Does not reject TypeGuard method with too few parameters. +""" +output = """ +narrowing_typeguard.py:17:33 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[str], Type[str]]`. +narrowing_typeguard.py:19:33 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[str], typing.Any]`. +""" diff --git a/conformance/results/pyre/typeddicts_alt_syntax.toml b/conformance/results/pyre/typeddicts_alt_syntax.toml new file mode 100644 index 000000000..1435a730a --- /dev/null +++ b/conformance/results/pyre/typeddicts_alt_syntax.toml @@ -0,0 +1,10 @@ +conformant = "Partial" +notes = """ +Does not report when name of TypedDict doesn't match assigned identifier name. +Does not support keyword-argument form of alternative syntax (deprecated in 3.11). +""" +output = """ +typeddicts_alt_syntax.py:23:16 Call error [29]: `object` is not a function. +typeddicts_alt_syntax.py:41:9 Call error [29]: `object` is not a function. +typeddicts_alt_syntax.py:43:8 Undefined or invalid type [11]: Annotation `Movie2` is not defined as a type. +""" diff --git a/conformance/results/pyre/typeddicts_class_syntax.toml b/conformance/results/pyre/typeddicts_class_syntax.toml new file mode 100644 index 000000000..50ffee139 --- /dev/null +++ b/conformance/results/pyre/typeddicts_class_syntax.toml @@ -0,0 +1,11 @@ +conformant = "Partial" +notes = """ +Does not reject methods within TypedDict class. +Does not report when metaclass is provided. +Does not report when other keyword argument is provided. +Does not support generic TypedDict class. +""" +output = """ +typeddicts_class_syntax.py:57:0 Uninitialized attribute [13]: Attribute `name` is declared in class `GenericTypedDict` to have type `str` but is never initialized. +typeddicts_class_syntax.py:57:0 Uninitialized attribute [13]: Attribute `value` is declared in class `GenericTypedDict` to have type `Variable[T]` but is never initialized. +""" diff --git a/conformance/results/pyre/typeddicts_final.toml b/conformance/results/pyre/typeddicts_final.toml new file mode 100644 index 000000000..c55712229 --- /dev/null +++ b/conformance/results/pyre/typeddicts_final.toml @@ -0,0 +1,7 @@ +conformant = "Partial" +notes = """ +Does not handle value with literal type as index to TypedDict object. +""" +output = """ +typeddicts_final.py:26:17 Incompatible parameter type [6]: In call `TypedDictionary.__getitem__`, for 1st positional argument, expected `typing_extensions.Literal['name']` but got `Union[typing_extensions.Literal['name'], typing_extensions.Literal['year']]`. +""" diff --git a/conformance/results/pyre/typeddicts_inheritance.toml b/conformance/results/pyre/typeddicts_inheritance.toml new file mode 100644 index 000000000..d15915ca3 --- /dev/null +++ b/conformance/results/pyre/typeddicts_inheritance.toml @@ -0,0 +1,8 @@ +conformant = "Partial" +notes = """ +Does not reject TypedDict class that inherits from non-TypedDict class. +""" +output = """ +typeddicts_inheritance.py:54:0 Inconsistent override [15]: `x` overrides attribute defined in `X1` inconsistently. Type `int` is not a subtype of the overridden attribute `str`. +typeddicts_inheritance.py:65:0 Invalid inheritance [39]: Field `x` has type `int` in base class `X2` and type `str` in base class `Y2`. +""" diff --git a/conformance/results/pyre/typeddicts_operations.toml b/conformance/results/pyre/typeddicts_operations.toml new file mode 100644 index 000000000..202a0a974 --- /dev/null +++ b/conformance/results/pyre/typeddicts_operations.toml @@ -0,0 +1,14 @@ +conformant = "Pass" +output = """ +typeddicts_operations.py:22:16 Invalid TypedDict operation [54]: Expected `str` to be assigned to `Movie` field `name` but got `int`. +typeddicts_operations.py:23:16 Invalid TypedDict operation [54]: Expected `int` to be assigned to `Movie` field `year` but got `str`. +typeddicts_operations.py:24:6 TypedDict accessed with a missing key [27]: TypedDict `Movie` has no key `other`. +typeddicts_operations.py:26:12 TypedDict accessed with a missing key [27]: TypedDict `Movie` has no key `other`. +typeddicts_operations.py:28:8 TypedDict initialization error [55]: Missing required field `year` for TypedDict `Movie`. +typeddicts_operations.py:29:8 TypedDict initialization error [55]: Expected type `int` for `Movie` field `year` but got `float`. +typeddicts_operations.py:32:8 TypedDict initialization error [55]: TypedDict `Movie` has no field `other`. +typeddicts_operations.py:37:4 Incompatible variable type [9]: movie is declared to have type `Movie` but is used as type `Dict[str, Union[int, str]]`. +typeddicts_operations.py:44:10 TypedDict accessed with a missing key [27]: TypedDict `Movie` has no key `other`. +typeddicts_operations.py:47:0 Undefined attribute [16]: `Movie` has no attribute `clear`. +typeddicts_operations.py:62:0 Undefined attribute [16]: `MovieOptional` has no attribute `clear`. +""" diff --git a/conformance/results/pyre/typeddicts_required.toml b/conformance/results/pyre/typeddicts_required.toml new file mode 100644 index 000000000..9a654d815 --- /dev/null +++ b/conformance/results/pyre/typeddicts_required.toml @@ -0,0 +1,11 @@ +conformant = "Partial" +notes = """ +Does not reject use of `Required` in function parameter annotation. +Does not reject nested use of `Required` in type annotation. +Does not support recursive TypedDict definitions. +""" +output = """ +typeddicts_required.py:11:0 Uninitialized attribute [13]: Attribute `x` is declared in class `NotTypedDict` to have type `Required[int]` but is never initialized. +typeddicts_required.py:71:62 Undefined or invalid type [11]: Annotation `RecursiveMovie` is not defined as a type. +typeddicts_required.py:74:24 TypedDict initialization error [55]: Expected type `unknown` for `RecursiveMovie` field `predecessor` but got `typing.Dict[str, str]`. +""" diff --git a/conformance/results/pyre/typeddicts_type_consistency.toml b/conformance/results/pyre/typeddicts_type_consistency.toml new file mode 100644 index 000000000..28b9043e4 --- /dev/null +++ b/conformance/results/pyre/typeddicts_type_consistency.toml @@ -0,0 +1,19 @@ +conformant = "Partial" +notes = """ +Does not reject assignment of TypedDict with missing key. +Does not return non-Optional value from `get` method for required key. +Does not properly handle nested TypedDicts. +""" +output = """ +typeddicts_type_consistency.py:21:0 Incompatible variable type [9]: a1 is declared to have type `A1` but is used as type `B1`. +typeddicts_type_consistency.py:38:0 Incompatible variable type [9]: a2 is declared to have type `A2` but is used as type `B2`. +typeddicts_type_consistency.py:69:11 TypedDict initialization error [55]: TypedDict `A3` has no field `y`. +typeddicts_type_consistency.py:76:0 Incompatible variable type [9]: d1 is declared to have type `Dict[str, int]` but is used as type `B3`. +typeddicts_type_consistency.py:77:0 Incompatible variable type [9]: d2 is declared to have type `Dict[str, object]` but is used as type `B3`. +typeddicts_type_consistency.py:78:0 Incompatible variable type [9]: d3 is declared to have type `Dict[typing.Any, typing.Any]` but is used as type `B3`. +typeddicts_type_consistency.py:82:0 Incompatible variable type [9]: m1 is declared to have type `Mapping[str, int]` but is used as type `B3`. +typeddicts_type_consistency.py:99:0 Incompatible variable type [9]: name3 is declared to have type `str` but is used as type `Optional[str]`. +typeddicts_type_consistency.py:105:0 Incompatible variable type [9]: age4 is declared to have type `int` but is used as type `Union[str, int]`. +typeddicts_type_consistency.py:124:41 TypedDict initialization error [55]: Expected type `str` for `Inner1` field `inner_key` but got `int`. +typeddicts_type_consistency.py:150:0 Incompatible variable type [9]: o4 is declared to have type `Outer3` but is used as type `Outer2`. +""" diff --git a/conformance/results/pyre/typeddicts_usage.toml b/conformance/results/pyre/typeddicts_usage.toml new file mode 100644 index 000000000..739dbf331 --- /dev/null +++ b/conformance/results/pyre/typeddicts_usage.toml @@ -0,0 +1,10 @@ +conformant = "Partial" +notes = """ +Does not report errant use of TypedDict in `isinstance` call. +Does not reject use of TypedDict as TypeVar bound. +""" +output = """ +typeddicts_usage.py:23:6 TypedDict accessed with a missing key [27]: TypedDict `Movie` has no key `director`. +typeddicts_usage.py:24:16 Invalid TypedDict operation [54]: Expected `int` to be assigned to `Movie` field `year` but got `str`. +typeddicts_usage.py:28:16 TypedDict initialization error [55]: Missing required field `name` for TypedDict `Movie`. +""" diff --git a/conformance/results/pyre/version.toml b/conformance/results/pyre/version.toml new file mode 100644 index 000000000..91daa0eeb --- /dev/null +++ b/conformance/results/pyre/version.toml @@ -0,0 +1,2 @@ +version = "pyre 0.9.19" +test_duration = 1.5496571063995361 diff --git a/conformance/results/pyright/aliases_explicit.toml b/conformance/results/pyright/aliases_explicit.toml new file mode 100644 index 000000000..a69c20491 --- /dev/null +++ b/conformance/results/pyright/aliases_explicit.toml @@ -0,0 +1,51 @@ +conformant = "Partial" +notes = """ +Incorrectly evaluates type of specialized type alias parameterized with ParamSpec. +Allows some illegal annotation forms to be interpreted as valid type aliases. +""" +output = """ +aliases_explicit.py:57:17 - error: "assert_type" mismatch: expected "(int, str, str) -> None" but received "(int, str, str) -> None" (reportGeneralTypeIssues) +aliases_explicit.py:67:24 - error: Expected no type arguments for class "int" (reportGeneralTypeIssues) +aliases_explicit.py:67:24 - error: Expected no type arguments for class "NoneType" (reportGeneralTypeIssues) +aliases_explicit.py:68:9 - error: Type "list[int | None]" is already specialized (reportGeneralTypeIssues) +aliases_explicit.py:69:29 - error: Too many type arguments provided for "GoodTypeAlias4[T@GoodTypeAlias4]"; expected 1 but received 2 +aliases_explicit.py:70:29 - error: Too many type arguments provided for "GoodTypeAlias8[T@GoodTypeAlias8]"; expected 1 but received 2 +aliases_explicit.py:71:24 - error: Expected ParamSpec, ellipsis, or list of types +aliases_explicit.py:79:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_explicit.py:79:21 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +aliases_explicit.py:80:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_explicit.py:80:21 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +aliases_explicit.py:80:21 - error: Expected type expression but received "list[Unknown]" (reportGeneralTypeIssues) +aliases_explicit.py:81:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_explicit.py:81:21 - error: Tuple expression not allowed in type annotation +  Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +aliases_explicit.py:81:21 - error: Expected type expression but received "tuple[tuple[type[int], type[str]]]" (reportGeneralTypeIssues) +aliases_explicit.py:82:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_explicit.py:82:21 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +aliases_explicit.py:82:21 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +aliases_explicit.py:83:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_explicit.py:83:21 - error: Dictionary expression not allowed in type annotation +  Use Dict[T1, T2] to indicate a dictionary type (reportGeneralTypeIssues) +aliases_explicit.py:83:21 - error: Expected type expression but received "dict[str, str]" (reportGeneralTypeIssues) +aliases_explicit.py:84:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_explicit.py:84:21 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +aliases_explicit.py:85:21 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +aliases_explicit.py:85:21 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +aliases_explicit.py:86:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_explicit.py:86:21 - error: Ternary expression not allowed in type annotation +aliases_explicit.py:87:21 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_explicit.py:88:22 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) +aliases_explicit.py:89:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_explicit.py:89:22 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +aliases_explicit.py:90:22 - error: Binary operator not allowed in type annotation +aliases_explicit.py:91:22 - error: Expected expression +aliases_explicit.py:91:22 - error: Tuple expression not allowed in type annotation +  Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +aliases_explicit.py:100:5 - error: Type "list[Unknown]" is already specialized (reportGeneralTypeIssues) +aliases_explicit.py:101:6 - error: Object of type "type[list[Unknown]] | type[set[Unknown]]" is not callable (reportGeneralTypeIssues) +aliases_explicit.py:102:5 - error: Type "list[Unknown]" is already specialized (reportGeneralTypeIssues) +aliases_explicit.py:102:5 - error: Type "set[Unknown]" is already specialized (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/aliases_implicit.toml b/conformance/results/pyright/aliases_implicit.toml new file mode 100644 index 000000000..be26d5c86 --- /dev/null +++ b/conformance/results/pyright/aliases_implicit.toml @@ -0,0 +1,31 @@ +conformant = "Partial" +notes = """ +Incorrectly evaluates type of specialized type alias parameterized with ParamSpec. +Allows some illegal annotation forms to be interpreted as valid type aliases. +""" +output = """ +aliases_implicit.py:68:17 - error: "assert_type" mismatch: expected "(int, str, str) -> None" but received "(int, str, str) -> None" (reportGeneralTypeIssues) +aliases_implicit.py:76:24 - error: Expected no type arguments for class "int" (reportGeneralTypeIssues) +aliases_implicit.py:76:24 - error: Expected no type arguments for class "NoneType" (reportGeneralTypeIssues) +aliases_implicit.py:77:9 - error: Type "list[int | None]" is already specialized (reportGeneralTypeIssues) +aliases_implicit.py:78:29 - error: Too many type arguments provided for "GoodTypeAlias4[T@GoodTypeAlias4]"; expected 1 but received 2 +aliases_implicit.py:79:29 - error: Too many type arguments provided for "GoodTypeAlias8[T@GoodTypeAlias8]"; expected 1 but received 2 +aliases_implicit.py:80:24 - error: Expected ParamSpec, ellipsis, or list of types +aliases_implicit.py:81:9 - error: Could not specialize type "GoodTypeAlias12[TFloat@GoodTypeAlias12]" +  Type "str" cannot be assigned to type "float" +    "str" is incompatible with "float" +aliases_implicit.py:106:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:107:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:108:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:109:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:110:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:113:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:114:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:115:10 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:116:10 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:118:10 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:119:10 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:133:6 - error: Object of type "type[list[Unknown]] | type[set[Unknown]]" is not callable (reportGeneralTypeIssues) +aliases_implicit.py:135:5 - error: Type "list[Unknown]" is already specialized (reportGeneralTypeIssues) +aliases_implicit.py:135:5 - error: Type "set[Unknown]" is already specialized (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/aliases_newtype.toml b/conformance/results/pyright/aliases_newtype.toml new file mode 100644 index 000000000..5024de109 --- /dev/null +++ b/conformance/results/pyright/aliases_newtype.toml @@ -0,0 +1,21 @@ +conformant = "Partial" +notes = """ +Does not reject use of NewType in `isinstance` call. +Does not report inconsistency between name of NewType and assigned identifier name. +Does not reject use of NewType with TypedDict class. +Does not reject use of NewType with another NewType. +Does not reject use of NewType with Any. +""" +output = """ +aliases_newtype.py:11:8 - error: Argument of type "Literal['user']" cannot be assigned to parameter "_x" of type "int" in function "__init__" +  "Literal['user']" is incompatible with "int" (reportGeneralTypeIssues) +aliases_newtype.py:12:14 - error: Expression of type "Literal[42]" cannot be assigned to declared type "UserId" +  "Literal[42]" is incompatible with "UserId" (reportGeneralTypeIssues) +aliases_newtype.py:23:21 - error: Base class "UserId" is marked final and cannot be subclassed +aliases_newtype.py:36:19 - error: Expected no type arguments for class "GoodNewType1" (reportGeneralTypeIssues) +aliases_newtype.py:42:38 - error: Expected class as second argument to NewType +aliases_newtype.py:45:43 - error: Type variable "T" has no meaning in this context (reportGeneralTypeIssues) +aliases_newtype.py:47:38 - error: NewType cannot be used with protocol class +aliases_newtype.py:49:38 - error: NewType cannot be used with Literal type +aliases_newtype.py:60:15 - error: NewType requires two positional arguments (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/aliases_recursive.toml b/conformance/results/pyright/aliases_recursive.toml new file mode 100644 index 000000000..b4ff9fb57 --- /dev/null +++ b/conformance/results/pyright/aliases_recursive.toml @@ -0,0 +1,66 @@ +conformant = "Pass" +output = """ +aliases_recursive.py:19:12 - error: Expression of type "dict[str, int | complex]" cannot be assigned to declared type "Json" +  Type "dict[str, int | complex]" cannot be assigned to type "Json" +    "dict[str, int | complex]" is incompatible with "None" +    "dict[str, int | complex]" is incompatible with "int" +    "dict[str, int | complex]" is incompatible with "str" +    "dict[str, int | complex]" is incompatible with "float" +    "dict[str, int | complex]" is incompatible with "list[Json]" +    "dict[str, int | complex]" is incompatible with "dict[str, Json]" +      Type parameter "_VT@dict" is invariant, but "int | complex" is not the same as "Json" + ... (reportGeneralTypeIssues) +aliases_recursive.py:20:16 - error: Expression of type "list[int | complex]" cannot be assigned to declared type "Json" +  Type "complex" cannot be assigned to type "Json" +    "complex" is incompatible with "None" +    "complex" is incompatible with "int" +    "complex" is incompatible with "str" +    "complex" is incompatible with "float" +    "complex" is incompatible with "list[Json]" +    "complex" is incompatible with "dict[str, Json]" (reportGeneralTypeIssues) +aliases_recursive.py:38:22 - error: Expression of type "tuple[Literal[1], tuple[Literal['1'], Literal[1]], tuple[Literal[1], tuple[Literal[1], list[int]]]]" cannot be assigned to declared type "RecursiveTuple" +  Type "tuple[Literal[1], tuple[Literal['1'], Literal[1]], tuple[Literal[1], tuple[Literal[1], list[int]]]]" cannot be assigned to type "RecursiveTuple" +    "tuple[Literal[1], tuple[Literal['1'], Literal[1]], tuple[Literal[1], tuple[Literal[1], list[int]]]]" is incompatible with "str" +    "tuple[Literal[1], tuple[Literal['1'], Literal[1]], tuple[Literal[1], tuple[Literal[1], list[int]]]]" is incompatible with "int" +    "tuple[Literal[1], tuple[Literal['1'], Literal[1]], tuple[Literal[1], tuple[Literal[1], list[int]]]]" is incompatible with "tuple[RecursiveTuple, ...]" +      Tuple entry 1 is incorrect type (reportGeneralTypeIssues) +aliases_recursive.py:39:22 - error: Expression of type "tuple[Literal[1], list[int]]" cannot be assigned to declared type "RecursiveTuple" +  Type "tuple[Literal[1], list[int]]" cannot be assigned to type "RecursiveTuple" +    "tuple[Literal[1], list[int]]" is incompatible with "str" +    "tuple[Literal[1], list[int]]" is incompatible with "int" +    "tuple[Literal[1], list[int]]" is incompatible with "tuple[RecursiveTuple, ...]" +      Tuple entry 1 is incorrect type (reportGeneralTypeIssues) +aliases_recursive.py:50:24 - error: Expression of type "dict[str, list[int]]" cannot be assigned to declared type "RecursiveMapping" +  Type "dict[str, list[int]]" cannot be assigned to type "RecursiveMapping" +    "dict[str, list[int]]" is incompatible with "str" +    "dict[str, list[int]]" is incompatible with "int" +    "dict[str, list[int]]" is incompatible with "Mapping[str, RecursiveMapping]" +      Type parameter "_VT_co@Mapping" is covariant, but "list[int]" is not a subtype of "RecursiveMapping" +        Type "list[int]" cannot be assigned to type "RecursiveMapping" +          "list[int]" is incompatible with "str" +          "list[int]" is incompatible with "int" + ... (reportGeneralTypeIssues) +aliases_recursive.py:51:24 - error: Expression of type "dict[str, str | int | list[int]]" cannot be assigned to declared type "RecursiveMapping" +  Type "dict[str, str | int | list[int]]" cannot be assigned to type "RecursiveMapping" +    "dict[str, str | int | list[int]]" is incompatible with "str" +    "dict[str, str | int | list[int]]" is incompatible with "int" +    "dict[str, str | int | list[int]]" is incompatible with "Mapping[str, RecursiveMapping]" +      Type parameter "_VT_co@Mapping" is covariant, but "str | int | list[int]" is not a subtype of "RecursiveMapping" (reportGeneralTypeIssues) +aliases_recursive.py:52:24 - error: Expression of type "dict[str, str | int | dict[str, str | int | list[int]]]" cannot be assigned to declared type "RecursiveMapping" +  Type "dict[str, str | int | dict[str, str | int | list[int]]]" cannot be assigned to type "RecursiveMapping" +    "dict[str, str | int | dict[str, str | int | list[int]]]" is incompatible with "str" +    "dict[str, str | int | dict[str, str | int | list[int]]]" is incompatible with "int" +    "dict[str, str | int | dict[str, str | int | list[int]]]" is incompatible with "Mapping[str, RecursiveMapping]" +      Type parameter "_VT_co@Mapping" is covariant, but "str | int | dict[str, str | int | list[int]]" is not a subtype of "RecursiveMapping" (reportGeneralTypeIssues) +aliases_recursive.py:67:38 - error: Expression of type "list[str | list[float]]" cannot be assigned to declared type "GenericTypeAlias1[str]" +  Type "float" cannot be assigned to type "GenericTypeAlias1[T1@GenericTypeAlias1] | str" +    "float" is incompatible with "GenericTypeAlias1[T1@GenericTypeAlias1]" +    "float" is incompatible with "str" (reportGeneralTypeIssues) +aliases_recursive.py:73:51 - error: Expression of type "list[list[int | list[str | int | list[float]]] | str]" cannot be assigned to declared type "GenericTypeAlias2[str, int]" +  Type "float" cannot be assigned to type "GenericTypeAlias2[T1@GenericTypeAlias2, T2@GenericTypeAlias2] | str | int" +    "float" is incompatible with "GenericTypeAlias2[T1@GenericTypeAlias2, T2@GenericTypeAlias2]" +    "float" is incompatible with "str" +    "float" is incompatible with "int" (reportGeneralTypeIssues) +aliases_recursive.py:76:29 - error: Type alias "RecursiveUnion" cannot use itself in its definition (reportGeneralTypeIssues) +aliases_recursive.py:78:31 - error: Type alias "MutualReference1" cannot use itself in its definition (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/aliases_type_statement.toml b/conformance/results/pyright/aliases_type_statement.toml new file mode 100644 index 000000000..abc0dc775 --- /dev/null +++ b/conformance/results/pyright/aliases_type_statement.toml @@ -0,0 +1,48 @@ +conformant = "Partial" +notes = """ +Does not reject binary expression when used in type alias definition. +""" +output = """ +aliases_type_statement.py:44:26 - error: Statements must be separated by newlines or semicolons +aliases_type_statement.py:44:35 - error: Expected ":" +aliases_type_statement.py:17:12 - error: Cannot access member "bit_count" for type "TypeAliasType" +  Member "bit_count" is unknown (reportGeneralTypeIssues) +aliases_type_statement.py:19:1 - error: Object of type "TypeAliasType" is not callable (reportGeneralTypeIssues) +aliases_type_statement.py:23:18 - error: Cannot access member "other_attrib" for type "TypeAliasType" +  Member "other_attrib" is unknown (reportGeneralTypeIssues) +aliases_type_statement.py:26:18 - error: Expected type expression but received "TypeAliasType" (reportGeneralTypeIssues) +aliases_type_statement.py:31:22 - error: Argument of type "TypeAliasType" cannot be assigned to parameter "__class_or_tuple" of type "_ClassInfo" in function "isinstance" +  Type "TypeAliasType" cannot be assigned to type "_ClassInfo" +    "TypeAliasType" is incompatible with "type" +    "TypeAliasType" is incompatible with "UnionType" +    "TypeAliasType" is incompatible with "tuple[_ClassInfo, ...]" (reportGeneralTypeIssues) +aliases_type_statement.py:37:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:38:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:38:22 - error: Expected type expression but received "list[Unknown]" (reportGeneralTypeIssues) +aliases_type_statement.py:39:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:39:22 - error: Expected type expression but received "tuple[tuple[type[int], type[str]]]" (reportGeneralTypeIssues) +aliases_type_statement.py:40:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:40:22 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +aliases_type_statement.py:41:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:41:22 - error: Expected type expression but received "dict[str, str]" (reportGeneralTypeIssues) +aliases_type_statement.py:42:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:43:22 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +aliases_type_statement.py:45:22 - error: Expected type expression but received "int" (reportGeneralTypeIssues) +aliases_type_statement.py:46:23 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) +aliases_type_statement.py:47:23 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:47:23 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +aliases_type_statement.py:49:23 - error: Expected expression +aliases_type_statement.py:58:10 - error: A type statement can be used only within a module or class scope +aliases_type_statement.py:64:23 - error: Type parameter "V" is not included in the type parameter list for "TA1" (reportGeneralTypeIssues) +aliases_type_statement.py:69:17 - error: Type parameter "T1" is not included in the type parameter list for "TA2" (reportGeneralTypeIssues) +aliases_type_statement.py:79:7 - error: Could not specialize type "RecursiveTypeAlias2[S@RecursiveTypeAlias2, T@RecursiveTypeAlias2, P@RecursiveTypeAlias2]" +  Type "str" cannot be assigned to type "int" +    "str" is incompatible with "int" +aliases_type_statement.py:81:7 - error: Could not specialize type "RecursiveTypeAlias2[S@RecursiveTypeAlias2, T@RecursiveTypeAlias2, P@RecursiveTypeAlias2]" +  Type "int" cannot be assigned to type "str" +    "int" is incompatible with "str" +aliases_type_statement.py:84:28 - error: Type alias "RecursiveTypeAlias3" cannot use itself in its definition (reportGeneralTypeIssues) +aliases_type_statement.py:86:31 - error: Type alias "RecursiveTypeAlias4" cannot use itself in its definition (reportGeneralTypeIssues) +aliases_type_statement.py:90:28 - error: Type alias "RecursiveTypeAlias6" cannot use itself in its definition (reportGeneralTypeIssues) +aliases_type_statement.py:52:10 - error: Type alias declaration "BadTypeAlias14" is obscured by a declaration of the same name (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/aliases_typealiastype.toml b/conformance/results/pyright/aliases_typealiastype.toml new file mode 100644 index 000000000..34329cf9d --- /dev/null +++ b/conformance/results/pyright/aliases_typealiastype.toml @@ -0,0 +1,37 @@ +conformant = "Partial" +notes = """ +Does not reject type alias with TypeVar that is not in scope and not in `type_params`. +Does not allow access to `__value__` attribute of type alias. +Allows some illegal annotation forms to be interpreted as valid type aliases. +""" +output = """ +aliases_typealiastype.py:30:18 - error: Cannot access member "__value__" for type "GoodAlias1" +  Member "__value__" is unknown (reportGeneralTypeIssues) +aliases_typealiastype.py:32:18 - error: Cannot access member "other_attrib" for type "GoodAlias1" +  Member "other_attrib" is unknown (reportGeneralTypeIssues) +aliases_typealiastype.py:40:5 - error: Could not specialize type "GoodAlias5[S@GoodAlias5, TStr@GoodAlias5, P@GoodAlias5, Ts@GoodAlias5]" +  Type "int" cannot be assigned to type "str" +    "int" is incompatible with "str" +aliases_typealiastype.py:44:23 - error: Type variable "S" has no meaning in this context (reportGeneralTypeIssues) +aliases_typealiastype.py:48:35 - error: Type parameter list must be a tuple containing only TypeVar, TypeVarTuple, or ParamSpec +aliases_typealiastype.py:50:40 - error: Type alias "BadAlias4" cannot use itself in its definition (reportGeneralTypeIssues) +aliases_typealiastype.py:52:18 - error: Type alias "BadAlias5" cannot use itself in its definition (reportGeneralTypeIssues) +aliases_typealiastype.py:54:40 - error: Type alias "BadAlias6" cannot use itself in its definition (reportGeneralTypeIssues) +aliases_typealiastype.py:58:40 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_typealiastype.py:59:40 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_typealiastype.py:59:40 - error: Expected type expression but received "list[Unknown]" (reportGeneralTypeIssues) +aliases_typealiastype.py:60:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_typealiastype.py:60:42 - error: Expected type expression but received "tuple[tuple[type[int], type[str]]]" (reportGeneralTypeIssues) +aliases_typealiastype.py:61:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_typealiastype.py:61:42 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +aliases_typealiastype.py:62:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_typealiastype.py:62:42 - error: Expected type expression but received "dict[str, str]" (reportGeneralTypeIssues) +aliases_typealiastype.py:63:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_typealiastype.py:64:42 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +aliases_typealiastype.py:65:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_typealiastype.py:66:42 - error: Expected type expression but received "int" (reportGeneralTypeIssues) +aliases_typealiastype.py:67:42 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) +aliases_typealiastype.py:68:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_typealiastype.py:68:42 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +aliases_typealiastype.py:70:42 - error: Expected expression +""" diff --git a/conformance/results/pyright/aliases_variance.toml b/conformance/results/pyright/aliases_variance.toml new file mode 100644 index 000000000..c933cf7cf --- /dev/null +++ b/conformance/results/pyright/aliases_variance.toml @@ -0,0 +1,11 @@ +conformant = "Pass" +output = """ +aliases_variance.py:24:23 - error: Type "T_co@ClassA_1" cannot be assigned to type variable "T@ClassA" +  Variance of type argument "T_co@ClassA_1" is incompatible with base class "ClassA" (reportGeneralTypeIssues) +aliases_variance.py:28:26 - error: Could not specialize type "A_Alias_1[T_co@A_Alias_1]" +  Variance of type argument "T_co@ClassA_2" is incompatible with "T_co@A_Alias_1" +aliases_variance.py:32:26 - error: Could not specialize type "A_Alias_2[T_co@A_Alias_2]" +  Variance of type argument "T_co@ClassA_3" is incompatible with "T_co@A_Alias_2" +aliases_variance.py:44:26 - error: Could not specialize type "B_Alias_1[T_co@B_Alias_1, T_contra@B_Alias_1]" +  Variance of type argument "T_contra@ClassB_1" is incompatible with "T_co@B_Alias_1" +""" diff --git a/conformance/results/pyright/annotations_typeexpr.toml b/conformance/results/pyright/annotations_typeexpr.toml new file mode 100644 index 000000000..f1bd93080 --- /dev/null +++ b/conformance/results/pyright/annotations_typeexpr.toml @@ -0,0 +1,29 @@ +conformant = "Pass" +output = """ +annotations_typeexpr.py:77:9 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +annotations_typeexpr.py:78:9 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +annotations_typeexpr.py:78:9 - error: Expected type expression but received "list[Unknown]" (reportGeneralTypeIssues) +annotations_typeexpr.py:79:9 - error: Tuple expression not allowed in type annotation +  Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +annotations_typeexpr.py:79:9 - error: Expected type expression but received "tuple[type[int], type[str]]" (reportGeneralTypeIssues) +annotations_typeexpr.py:80:9 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +annotations_typeexpr.py:80:9 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +annotations_typeexpr.py:81:9 - error: Dictionary expression not allowed in type annotation +  Use Dict[T1, T2] to indicate a dictionary type (reportGeneralTypeIssues) +annotations_typeexpr.py:81:9 - error: Expected type expression but received "dict[Unknown, Unknown]" (reportGeneralTypeIssues) +annotations_typeexpr.py:82:9 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +annotations_typeexpr.py:83:9 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +annotations_typeexpr.py:83:9 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +annotations_typeexpr.py:84:9 - error: Ternary expression not allowed in type annotation +annotations_typeexpr.py:85:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +annotations_typeexpr.py:86:10 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) +annotations_typeexpr.py:87:10 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +annotations_typeexpr.py:88:10 - error: Unary operator not allowed in type annotation +annotations_typeexpr.py:89:10 - error: Binary operator not allowed in type annotation +annotations_typeexpr.py:90:10 - error: Expected expression +annotations_typeexpr.py:90:10 - error: Tuple expression not allowed in type annotation +  Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/callables_annotation.toml b/conformance/results/pyright/callables_annotation.toml new file mode 100644 index 000000000..eac02284e --- /dev/null +++ b/conformance/results/pyright/callables_annotation.toml @@ -0,0 +1,16 @@ +conformant = "Pass" +output = """ +callables_annotation.py:13:5 - error: Expected 1 more positional argument (reportGeneralTypeIssues) +callables_annotation.py:14:11 - error: Argument of type "Literal[2]" cannot be assigned to parameter of type "str" +  "Literal[2]" is incompatible with "str" (reportGeneralTypeIssues) +callables_annotation.py:15:15 - error: Expected 2 positional arguments (reportGeneralTypeIssues) +callables_annotation.py:16:10 - error: Expected 2 more positional arguments (reportGeneralTypeIssues) +callables_annotation.py:22:8 - error: Expected 0 positional arguments (reportGeneralTypeIssues) +callables_annotation.py:39:14 - error: Expected parameter type list or "..." +callables_annotation.py:39:5 - error: Expected return type as second type argument for "Callable" (reportGeneralTypeIssues) +callables_annotation.py:40:14 - error: Expected parameter type list or "..." +callables_annotation.py:41:18 - error: List expression not allowed for this type argument +callables_annotation.py:42:14 - error: Expected parameter type list or "..." +callables_annotation.py:42:24 - error: Expected only two type arguments to "Callable" +callables_annotation.py:43:15 - error: "..." is not allowed in this context +""" diff --git a/conformance/results/pyright/callables_kwargs.toml b/conformance/results/pyright/callables_kwargs.toml new file mode 100644 index 000000000..651eee9ea --- /dev/null +++ b/conformance/results/pyright/callables_kwargs.toml @@ -0,0 +1,30 @@ +conformant = "Pass" +output = """ +callables_kwargs.py:26:5 - error: Could not access item in TypedDict +  "v2" is not a required key in "*TD2", so access may result in runtime exception (reportTypedDictNotRequiredAccess) +callables_kwargs.py:43:5 - error: Arguments missing for parameters "v1", "v3" (reportGeneralTypeIssues) +callables_kwargs.py:48:32 - error: No parameter named "v4" (reportGeneralTypeIssues) +callables_kwargs.py:49:11 - error: Expected 0 positional arguments (reportGeneralTypeIssues) +callables_kwargs.py:55:13 - error: Argument of type "str" cannot be assigned to parameter "v1" of type "int" in function "func1" +  "str" is incompatible with "int" (reportGeneralTypeIssues) +callables_kwargs.py:60:19 - error: Unable to match unpacked TypedDict argument to parameters +  Parameter "v1" is already assigned (reportGeneralTypeIssues) +callables_kwargs.py:61:16 - error: Unable to match unpacked TypedDict argument to parameters +  Parameter "v3" is already assigned (reportGeneralTypeIssues) +callables_kwargs.py:62:19 - error: Unable to match unpacked TypedDict argument to parameters +  Parameter "v1" is already assigned (reportGeneralTypeIssues) +callables_kwargs.py:98:19 - error: Expression of type "(**kwargs: **TD2) -> None" cannot be assigned to declared type "TDProtocol3" +  Type "(**kwargs: **TD2) -> None" cannot be assigned to type "(*, v1: int, v2: int, v3: str) -> None" +    Keyword parameter "v2" of type "int" cannot be assigned to type "str" +      "int" is incompatible with "str" (reportGeneralTypeIssues) +callables_kwargs.py:99:19 - error: Expression of type "(**kwargs: **TD2) -> None" cannot be assigned to declared type "TDProtocol4" +  Type "(**kwargs: **TD2) -> None" cannot be assigned to type "(*, v1: int) -> None" +    Keyword parameter "v3" is missing in destination (reportGeneralTypeIssues) +callables_kwargs.py:100:19 - error: Expression of type "(**kwargs: **TD2) -> None" cannot be assigned to declared type "TDProtocol5" +  Type "(**kwargs: **TD2) -> None" cannot be assigned to type "(v1: int, v3: str) -> None" +    Function accepts too many positional parameters; expected 0 but received 2 +      Keyword parameter "v1" is missing in destination +      Keyword parameter "v3" is missing in destination (reportGeneralTypeIssues) +callables_kwargs.py:109:30 - error: Typed dictionary overlaps with keyword parameter: v1 (reportGeneralTypeIssues) +callables_kwargs.py:121:21 - error: Expected TypedDict type argument for Unpack (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/callables_protocol.toml b/conformance/results/pyright/callables_protocol.toml new file mode 100644 index 000000000..7e548a2a7 --- /dev/null +++ b/conformance/results/pyright/callables_protocol.toml @@ -0,0 +1,59 @@ +conformant = "Partial" +notes = """ +Does not report type incompatibility for callback protocol with positional-only parameters. +""" +output = """ +callables_protocol.py:35:7 - error: Expression of type "(*vals: bytes, max_items: int | None) -> list[bytes]" cannot be assigned to declared type "Proto1" +  Type "(*vals: bytes, max_items: int | None) -> list[bytes]" cannot be assigned to type "(*vals: bytes, max_len: int | None = None) -> list[bytes]" +    Keyword parameter "max_items" is missing in destination +    Keyword parameter "max_len" is missing in source (reportGeneralTypeIssues) +callables_protocol.py:36:7 - error: Expression of type "(*vals: bytes) -> list[bytes]" cannot be assigned to declared type "Proto1" +  Type "(*vals: bytes) -> list[bytes]" cannot be assigned to type "(*vals: bytes, max_len: int | None = None) -> list[bytes]" +    Keyword parameter "max_len" is missing in source (reportGeneralTypeIssues) +callables_protocol.py:37:7 - error: Expression of type "(*vals: bytes, max_len: str | None) -> list[bytes]" cannot be assigned to declared type "Proto1" +  Type "(*vals: bytes, max_len: str | None) -> list[bytes]" cannot be assigned to type "(*vals: bytes, max_len: int | None = None) -> list[bytes]" +    Keyword parameter "max_len" of type "int | None" cannot be assigned to type "str | None" +    Parameter "max_len" is missing default argument (reportGeneralTypeIssues) +callables_protocol.py:67:7 - error: Expression of type "(*a: bytes) -> None" cannot be assigned to declared type "Proto2" +  Type "(*a: bytes) -> None" cannot be assigned to type "(*vals: bytes, **kwargs: str) -> None" +    Parameter "**kwargs" has no corresponding parameter (reportGeneralTypeIssues) +callables_protocol.py:68:7 - error: Expression of type "(*a: str, **b: str) -> None" cannot be assigned to declared type "Proto2" +  Type "(*a: str, **b: str) -> None" cannot be assigned to type "(*vals: bytes, **kwargs: str) -> None" +    Parameter 1: type "*tuple[bytes, ...]" cannot be assigned to type "*tuple[str, ...]" +      "*tuple[bytes, ...]" is incompatible with "*tuple[str, ...]" +        Tuple entry 1 is incorrect type +          "bytes" is incompatible with "str" (reportGeneralTypeIssues) +callables_protocol.py:69:7 - error: Expression of type "(*a: bytes, **b: bytes) -> None" cannot be assigned to declared type "Proto2" +  Type "(*a: bytes, **b: bytes) -> None" cannot be assigned to type "(*vals: bytes, **kwargs: str) -> None" +    Parameter 2: type "str" cannot be assigned to type "bytes" +      "str" is incompatible with "bytes" (reportGeneralTypeIssues) +callables_protocol.py:70:7 - error: Expression of type "(**b: str) -> None" cannot be assigned to declared type "Proto2" +  Type "(**b: str) -> None" cannot be assigned to type "(*vals: bytes, **kwargs: str) -> None" +    Parameter "*vals" has no corresponding parameter (reportGeneralTypeIssues) +callables_protocol.py:97:16 - error: Expression of type "(x: int) -> None" cannot be assigned to declared type "Proto4" +  "function" is incompatible with protocol "Proto4" +    "other_attribute" is not present +    "__call__" is not present (reportGeneralTypeIssues) +callables_protocol.py:121:18 - error: Expression of type "(*vals: bytes, max_len: int | None = None) -> list[bytes]" cannot be assigned to declared type "NotProto6" +  "function" is incompatible with "NotProto6" (reportGeneralTypeIssues) +callables_protocol.py:169:7 - error: Expression of type "(x: int) -> Any" cannot be assigned to declared type "Proto8" +  One or more overloads of "__call__" is not assignable +    Type "(x: int) -> Any" cannot be assigned to type "(x: str) -> str" +      Parameter 1: type "str" cannot be assigned to type "int" +        "str" is incompatible with "int" (reportGeneralTypeIssues) +callables_protocol.py:186:33 - error: Cannot assign member "other_attribute" for type "Proto9[P@decorator1, R@decorator1]" +  "Literal['str']" is incompatible with "int" (reportGeneralTypeIssues) +callables_protocol.py:187:15 - error: Cannot assign member "xxx" for type "Proto9[P@decorator1, R@decorator1]" +  Member "xxx" is unknown (reportGeneralTypeIssues) +callables_protocol.py:197:16 - error: Cannot access member "other_attribute2" for type "Proto9[(x: int), str]" +  Member "other_attribute2" is unknown (reportGeneralTypeIssues) +callables_protocol.py:260:8 - error: Expression of type "(*args: Any, kwarg0: Any) -> None" cannot be assigned to declared type "Proto12" +  Type "(*args: Any, kwarg0: Any) -> None" cannot be assigned to type "(*args: Any, kwarg0: Any, kwarg1: Any) -> None" +    Keyword parameter "kwarg1" is missing in source (reportGeneralTypeIssues) +callables_protocol.py:284:27 - error: Expression of type "(path: str) -> str" cannot be assigned to declared type "Proto13_Default" +  Type "(path: str) -> str" cannot be assigned to type "(path: str = ...) -> str" +    Parameter "path" is missing default argument (reportGeneralTypeIssues) +callables_protocol.py:311:27 - error: Expression of type "(*, path: str) -> str" cannot be assigned to declared type "Proto14_Default" +  Type "(*, path: str) -> str" cannot be assigned to type "(*, path: str = ...) -> str" +    Parameter "path" is missing default argument (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_descriptors.toml b/conformance/results/pyright/dataclasses_descriptors.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/dataclasses_descriptors.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/dataclasses_frozen.toml b/conformance/results/pyright/dataclasses_frozen.toml new file mode 100644 index 000000000..5af00da01 --- /dev/null +++ b/conformance/results/pyright/dataclasses_frozen.toml @@ -0,0 +1,11 @@ +conformant = "Pass" +output = """ +dataclasses_frozen.py:16:5 - error: Cannot assign member "a" for type "DC1" +  "DC1" is frozen +    Member "__set__" is unknown (reportGeneralTypeIssues) +dataclasses_frozen.py:17:5 - error: Cannot assign member "b" for type "DC1" +  "DC1" is frozen +    Member "__set__" is unknown (reportGeneralTypeIssues) +dataclasses_frozen.py:22:1 - error: A non-frozen class cannot inherit from a class that is frozen (reportGeneralTypeIssues) +dataclasses_frozen.py:32:12 - error: A frozen class cannot inherit from a class that is not frozen (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_hash.toml b/conformance/results/pyright/dataclasses_hash.toml new file mode 100644 index 000000000..0f8dcdf8d --- /dev/null +++ b/conformance/results/pyright/dataclasses_hash.toml @@ -0,0 +1,11 @@ +conformant = "Pass" +output = """ +dataclasses_hash.py:15:16 - error: Expression of type "DC1" cannot be assigned to declared type "Hashable" +  "DC1" is incompatible with protocol "Hashable" +    "__hash__" is an incompatible type +      Type "None" cannot be assigned to type "() -> int" (reportGeneralTypeIssues) +dataclasses_hash.py:32:16 - error: Expression of type "DC3" cannot be assigned to declared type "Hashable" +  "DC3" is incompatible with protocol "Hashable" +    "__hash__" is an incompatible type +      Type "None" cannot be assigned to type "() -> int" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_inheritance.toml b/conformance/results/pyright/dataclasses_inheritance.toml new file mode 100644 index 000000000..6a1dfbd25 --- /dev/null +++ b/conformance/results/pyright/dataclasses_inheritance.toml @@ -0,0 +1,5 @@ +conformant = "Pass" +output = """ +dataclasses_inheritance.py:60:5 - error: Class variable "x" overrides instance variable of same name in class "DC6" (reportIncompatibleVariableOverride) +dataclasses_inheritance.py:64:5 - error: Instance variable "y" overrides class variable of same name in class "DC6" (reportIncompatibleVariableOverride) +""" diff --git a/conformance/results/pyright/dataclasses_kwonly.toml b/conformance/results/pyright/dataclasses_kwonly.toml new file mode 100644 index 000000000..da7cd69db --- /dev/null +++ b/conformance/results/pyright/dataclasses_kwonly.toml @@ -0,0 +1,6 @@ +conformant = "Pass" +output = """ +dataclasses_kwonly.py:23:11 - error: Expected 1 positional argument (reportGeneralTypeIssues) +dataclasses_kwonly.py:38:11 - error: Expected 1 positional argument (reportGeneralTypeIssues) +dataclasses_kwonly.py:53:11 - error: Expected 1 positional argument (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_order.toml b/conformance/results/pyright/dataclasses_order.toml new file mode 100644 index 000000000..15f99b753 --- /dev/null +++ b/conformance/results/pyright/dataclasses_order.toml @@ -0,0 +1,4 @@ +conformant = "Pass" +output = """ +dataclasses_order.py:50:4 - error: Operator "<" not supported for types "DC1" and "DC2" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_postinit.toml b/conformance/results/pyright/dataclasses_postinit.toml new file mode 100644 index 000000000..3ac995fe2 --- /dev/null +++ b/conformance/results/pyright/dataclasses_postinit.toml @@ -0,0 +1,15 @@ +conformant = "Partial" +notes = """ +Reports incorrect error for incompatible `__post_init__` method override. +""" +output = """ +dataclasses_postinit.py:19:40 - error: Dataclass __post_init__ method parameter type mismatch for field "y" +  "str" is incompatible with "int" (reportGeneralTypeIssues) +dataclasses_postinit.py:28:11 - error: Cannot access member "x" for type "DC1" +  Member "x" is an init-only field (reportGeneralTypeIssues) +dataclasses_postinit.py:29:11 - error: Cannot access member "y" for type "DC1" +  Member "y" is an init-only field (reportGeneralTypeIssues) +dataclasses_postinit.py:36:9 - error: Dataclass __post_init__ incorrect parameter count; number of InitVar fields is 2 (reportGeneralTypeIssues) +dataclasses_postinit.py:54:9 - error: Method "__post_init__" overrides class "DC3" in an incompatible manner +  Positional parameter count mismatch; base method has 2, but override has 3 (reportIncompatibleMethodOverride) +""" diff --git a/conformance/results/pyright/dataclasses_slots.toml b/conformance/results/pyright/dataclasses_slots.toml new file mode 100644 index 000000000..945605048 --- /dev/null +++ b/conformance/results/pyright/dataclasses_slots.toml @@ -0,0 +1,10 @@ +conformant = "Pass" +output = """ +dataclasses_slots.py:11:12 - error: __slots__ is already defined in class (reportGeneralTypeIssues) +dataclasses_slots.py:26:14 - error: "y" is not specified in __slots__ (reportGeneralTypeIssues) +dataclasses_slots.py:39:14 - error: "y" is not specified in __slots__ (reportGeneralTypeIssues) +dataclasses_slots.py:67:5 - error: Cannot access member "__slots__" for type "type[DC6]" +  Member "__slots__" is unknown (reportGeneralTypeIssues) +dataclasses_slots.py:70:8 - error: Cannot access member "__slots__" for type "DC6" +  Member "__slots__" is unknown (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_transform_class.toml b/conformance/results/pyright/dataclasses_transform_class.toml new file mode 100644 index 000000000..dc746b85e --- /dev/null +++ b/conformance/results/pyright/dataclasses_transform_class.toml @@ -0,0 +1,13 @@ +conformant = "Pass" +output = """ +dataclasses_transform_class.py:48:7 - error: A non-frozen class cannot inherit from a class that is frozen (reportGeneralTypeIssues) +dataclasses_transform_class.py:60:6 - error: Cannot assign member "id" for type "Customer1" +  "Customer1" is frozen +    Member "__set__" is unknown (reportGeneralTypeIssues) +dataclasses_transform_class.py:63:18 - error: Expected 0 positional arguments (reportGeneralTypeIssues) +dataclasses_transform_class.py:69:6 - error: Operator "<" not supported for types "Customer1" and "Customer1" (reportGeneralTypeIssues) +dataclasses_transform_class.py:79:18 - error: Expected 0 positional arguments (reportGeneralTypeIssues) +dataclasses_transform_class.py:119:6 - error: Cannot assign member "id" for type "Customer3" +  "Customer3" is frozen +    Member "__set__" is unknown (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_transform_field.toml b/conformance/results/pyright/dataclasses_transform_field.toml new file mode 100644 index 000000000..ac0282c38 --- /dev/null +++ b/conformance/results/pyright/dataclasses_transform_field.toml @@ -0,0 +1,5 @@ +conformant = "Pass" +output = """ +dataclasses_transform_field.py:64:16 - error: No parameter named "id" (reportGeneralTypeIssues) +dataclasses_transform_field.py:75:16 - error: Expected 0 positional arguments (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_transform_func.toml b/conformance/results/pyright/dataclasses_transform_func.toml new file mode 100644 index 000000000..4db9df93a --- /dev/null +++ b/conformance/results/pyright/dataclasses_transform_func.toml @@ -0,0 +1,12 @@ +conformant = "Pass" +output = """ +dataclasses_transform_func.py:57:13 - error: Cannot assign member "name" for type "Customer1" +  "Literal[3]" is incompatible with "str" (reportGeneralTypeIssues) +dataclasses_transform_func.py:61:6 - error: Operator "<" not supported for types "Customer1" and "Customer1" (reportGeneralTypeIssues) +dataclasses_transform_func.py:65:36 - error: No parameter named "salary" (reportGeneralTypeIssues) +dataclasses_transform_func.py:71:18 - error: Expected 0 positional arguments (reportGeneralTypeIssues) +dataclasses_transform_func.py:89:1 - error: A non-frozen class cannot inherit from a class that is frozen (reportGeneralTypeIssues) +dataclasses_transform_func.py:97:6 - error: Cannot assign member "id" for type "Customer3" +  "Customer3" is frozen +    Member "__set__" is unknown (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_transform_meta.toml b/conformance/results/pyright/dataclasses_transform_meta.toml new file mode 100644 index 000000000..a1ef23798 --- /dev/null +++ b/conformance/results/pyright/dataclasses_transform_meta.toml @@ -0,0 +1,13 @@ +conformant = "Pass" +output = """ +dataclasses_transform_meta.py:48:36 - error: A non-frozen class cannot inherit from a class that is frozen (reportGeneralTypeIssues) +dataclasses_transform_meta.py:60:6 - error: Cannot assign member "id" for type "Customer1" +  "Customer1" is frozen +    Member "__set__" is unknown (reportGeneralTypeIssues) +dataclasses_transform_meta.py:63:18 - error: Expected 0 positional arguments (reportGeneralTypeIssues) +dataclasses_transform_meta.py:70:6 - error: Operator "<" not supported for types "Customer1" and "Customer1" (reportGeneralTypeIssues) +dataclasses_transform_meta.py:80:18 - error: Expected 0 positional arguments (reportGeneralTypeIssues) +dataclasses_transform_meta.py:100:6 - error: Cannot assign member "id" for type "Customer3" +  "Customer3" is frozen +    Member "__set__" is unknown (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_usage.toml b/conformance/results/pyright/dataclasses_usage.toml new file mode 100644 index 000000000..35dd7e6f0 --- /dev/null +++ b/conformance/results/pyright/dataclasses_usage.toml @@ -0,0 +1,17 @@ +conformant = "Pass" +output = """ +dataclasses_usage.py:51:6 - error: Argument missing for parameter "unit_price" (reportGeneralTypeIssues) +dataclasses_usage.py:52:28 - error: Argument of type "Literal['price']" cannot be assigned to parameter "unit_price" of type "float" in function "__init__" +  "Literal['price']" is incompatible with "float" (reportGeneralTypeIssues) +dataclasses_usage.py:53:36 - error: Expected 3 positional arguments (reportGeneralTypeIssues) +dataclasses_usage.py:62:5 - error: Fields without default values cannot appear after fields with default values (reportGeneralTypeIssues) +dataclasses_usage.py:68:5 - error: Fields without default values cannot appear after fields with default values (reportGeneralTypeIssues) +dataclasses_usage.py:74:5 - error: Fields without default values cannot appear after fields with default values (reportGeneralTypeIssues) +dataclasses_usage.py:84:13 - error: Expected 1 positional argument (reportGeneralTypeIssues) +dataclasses_usage.py:89:36 - error: Argument of type "type[str]" cannot be assigned to parameter "default_factory" of type "() -> _T@field" in function "field" +  No overloaded function matches type "() -> int" (reportGeneralTypeIssues) +dataclasses_usage.py:127:8 - error: Expected 1 positional argument (reportGeneralTypeIssues) +dataclasses_usage.py:130:1 - error: Argument missing for parameter "y" (reportGeneralTypeIssues) +dataclasses_usage.py:179:1 - error: Expected no arguments to "DC13" constructor (reportGeneralTypeIssues) +dataclasses_usage.py:230:9 - error: Dataclass field without type annotation will cause runtime exception (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/generics_self_advanced.toml b/conformance/results/pyright/generics_self_advanced.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/generics_self_advanced.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/generics_self_attributes.toml b/conformance/results/pyright/generics_self_attributes.toml new file mode 100644 index 000000000..4ef1a5877 --- /dev/null +++ b/conformance/results/pyright/generics_self_attributes.toml @@ -0,0 +1,14 @@ +conformant = "Pass" +output = """ +generics_self_attributes.py:26:38 - error: Argument of type "LinkedList[int]" cannot be assigned to parameter "next" of type "OrdinalLinkedList | None" in function "__init__" +  Type "LinkedList[int]" cannot be assigned to type "OrdinalLinkedList | None" +    "LinkedList[int]" is incompatible with "OrdinalLinkedList" +    "LinkedList[int]" is incompatible with "None" (reportGeneralTypeIssues) +generics_self_attributes.py:32:8 - error: Cannot assign member "next" for type "OrdinalLinkedList" +  Expression of type "LinkedList[int]" cannot be assigned to member "next" of class "OrdinalLinkedList" +    Member "__set__" is unknown +    Member "__set__" is unknown +    Type "LinkedList[int]" cannot be assigned to type "OrdinalLinkedList | None" +      "LinkedList[int]" is incompatible with "OrdinalLinkedList" +      "LinkedList[int]" is incompatible with "None" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/generics_self_basic.toml b/conformance/results/pyright/generics_self_basic.toml new file mode 100644 index 000000000..8cf34c522 --- /dev/null +++ b/conformance/results/pyright/generics_self_basic.toml @@ -0,0 +1,8 @@ +conformant = "Pass" +output = """ +generics_self_basic.py:19:16 - error: Expression of type "Shape" cannot be assigned to return type "Self@Shape" +  Type "Shape" cannot be assigned to type "Self@Shape" (reportGeneralTypeIssues) +generics_self_basic.py:32:16 - error: Expression of type "Shape" cannot be assigned to return type "Self@Shape" +  Type "Shape" cannot be assigned to type "Self@Shape" (reportGeneralTypeIssues) +generics_self_basic.py:64:31 - error: Expected no type arguments for class "Self" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/generics_self_protocols.toml b/conformance/results/pyright/generics_self_protocols.toml new file mode 100644 index 000000000..713d768ff --- /dev/null +++ b/conformance/results/pyright/generics_self_protocols.toml @@ -0,0 +1,15 @@ +conformant = "Pass" +output = """ +generics_self_protocols.py:61:19 - error: Argument of type "BadReturnType" cannot be assigned to parameter "shape" of type "ShapeProtocol" in function "accepts_shape" +  "BadReturnType" is incompatible with protocol "ShapeProtocol" +    "set_scale" is an incompatible type +      Type "(scale: float) -> int" cannot be assigned to type "(scale: float) -> BadReturnType" +        Function return type "int" is incompatible with type "BadReturnType" +          "int" is incompatible with "BadReturnType" (reportGeneralTypeIssues) +generics_self_protocols.py:64:19 - error: Argument of type "ReturnDifferentClass" cannot be assigned to parameter "shape" of type "ShapeProtocol" in function "accepts_shape" +  "ReturnDifferentClass" is incompatible with protocol "ShapeProtocol" +    "set_scale" is an incompatible type +      Type "(scale: float) -> ReturnConcreteShape" cannot be assigned to type "(scale: float) -> ReturnDifferentClass" +        Function return type "ReturnConcreteShape" is incompatible with type "ReturnDifferentClass" +          "ReturnConcreteShape" is incompatible with "ReturnDifferentClass" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/generics_self_usage.toml b/conformance/results/pyright/generics_self_usage.toml new file mode 100644 index 000000000..d20349701 --- /dev/null +++ b/conformance/results/pyright/generics_self_usage.toml @@ -0,0 +1,19 @@ +conformant = "Pass" +output = """ +generics_self_usage.py:73:14 - error: "Self" is not valid in this context (reportGeneralTypeIssues) +generics_self_usage.py:73:23 - error: "Self" is not valid in this context (reportGeneralTypeIssues) +generics_self_usage.py:76:6 - error: "Self" is not valid in this context (reportGeneralTypeIssues) +generics_self_usage.py:82:54 - error: "Self" cannot be used in a function with a `self` or `cls` parameter that has a type annotation other than "Self" (reportGeneralTypeIssues) +generics_self_usage.py:82:44 - warning: TypeVar "TFoo2" appears only once in generic function signature +  Use "Foo2" instead (reportInvalidTypeVarUse) +generics_self_usage.py:86:16 - error: Expression of type "Foo3" cannot be assigned to return type "Self@Foo3" +  Type "Foo3" cannot be assigned to type "Self@Foo3" (reportGeneralTypeIssues) +generics_self_usage.py:101:15 - error: "Self" is not valid in this context (reportGeneralTypeIssues) +generics_self_usage.py:103:12 - error: "Self" is not valid in this context (reportGeneralTypeIssues) +generics_self_usage.py:106:30 - error: "Self" is not valid in this context (reportGeneralTypeIssues) +generics_self_usage.py:111:19 - error: "Self" is not valid in this context (reportGeneralTypeIssues) +generics_self_usage.py:116:31 - error: "Self" is not valid in this context (reportGeneralTypeIssues) +generics_self_usage.py:116:40 - error: "Self" is not valid in this context (reportGeneralTypeIssues) +generics_self_usage.py:121:37 - error: "Self" cannot be used within a metaclass (a subclass of "type") (reportGeneralTypeIssues) +generics_self_usage.py:125:42 - error: "Self" cannot be used within a metaclass (a subclass of "type") (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/literals_interactions.toml b/conformance/results/pyright/literals_interactions.toml new file mode 100644 index 000000000..203a7064d --- /dev/null +++ b/conformance/results/pyright/literals_interactions.toml @@ -0,0 +1,7 @@ +conformant = "Pass" +output = """ +literals_interactions.py:15:5 - error: Index 5 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues) +literals_interactions.py:16:5 - error: Index -5 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues) +literals_interactions.py:17:5 - error: Index 4 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues) +literals_interactions.py:18:5 - error: Index -4 is out of range for type tuple[int, str, list[bool]] (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/literals_literalstring.toml b/conformance/results/pyright/literals_literalstring.toml new file mode 100644 index 000000000..cf5f841ca --- /dev/null +++ b/conformance/results/pyright/literals_literalstring.toml @@ -0,0 +1,25 @@ +conformant = "Pass" +output = """ +literals_literalstring.py:36:29 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_literalstring.py:37:22 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_literalstring.py:43:23 - error: Expression of type "Literal['two']" cannot be assigned to declared type "Literal['']" +  "Literal['two']" cannot be assigned to type "Literal['']" (reportGeneralTypeIssues) +literals_literalstring.py:66:25 - error: Expression of type "str" cannot be assigned to declared type "LiteralString" +  "str" is incompatible with "LiteralString" (reportGeneralTypeIssues) +literals_literalstring.py:74:25 - error: Expression of type "Literal[3]" cannot be assigned to declared type "LiteralString" +  "Literal[3]" is incompatible with "LiteralString" (reportGeneralTypeIssues) +literals_literalstring.py:75:25 - error: Expression of type "Literal[b"test"]" cannot be assigned to declared type "LiteralString" +  "Literal[b"test"]" is incompatible with "LiteralString" (reportGeneralTypeIssues) +literals_literalstring.py:120:22 - error: Argument of type "str" cannot be assigned to parameter "s" of type "TLiteral@literal_identity" in function "literal_identity" +  Type "str" cannot be assigned to type "LiteralString" +    "str" is incompatible with "LiteralString" (reportGeneralTypeIssues) +literals_literalstring.py:134:51 - error: Argument of type "str" cannot be assigned to parameter "value" of type "T@Container" in function "__init__" +  Type "str" cannot be assigned to type "LiteralString" +    "str" is incompatible with "LiteralString" (reportGeneralTypeIssues) +literals_literalstring.py:142:5 - error: Overload 1 for "func8" overlaps overload 2 and returns an incompatible type (reportOverlappingOverload) +literals_literalstring.py:142:5 - error: Overload 1 for "func8" overlaps overload 3 and returns an incompatible type (reportOverlappingOverload) +literals_literalstring.py:166:21 - error: Expression of type "list[LiteralString]" cannot be assigned to declared type "list[str]" +  "list[LiteralString]" is incompatible with "list[str]" +    Type parameter "_T@list" is invariant, but "LiteralString" is not the same as "str" +    Consider switching from "list" to "Sequence" which is covariant (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/literals_parameterizations.toml b/conformance/results/pyright/literals_parameterizations.toml new file mode 100644 index 000000000..3ed2765d2 --- /dev/null +++ b/conformance/results/pyright/literals_parameterizations.toml @@ -0,0 +1,21 @@ +conformant = "Pass" +output = """ +literals_parameterizations.py:40:15 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:41:15 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:42:15 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:43:15 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:44:15 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:45:15 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:46:15 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:47:15 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:48:15 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:49:16 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:50:16 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:51:16 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:52:16 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:55:28 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:58:4 - error: "Literal" cannot be used in this context without a type argument +literals_parameterizations.py:59:12 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +literals_parameterizations.py:63:32 - error: Expression of type "Literal[Color.RED]" cannot be assigned to declared type "Literal['Color.RED']" +  "Literal[Color.RED]" cannot be assigned to type "Literal['Color.RED']" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/literals_semantics.toml b/conformance/results/pyright/literals_semantics.toml new file mode 100644 index 000000000..e9e40a1ee --- /dev/null +++ b/conformance/results/pyright/literals_semantics.toml @@ -0,0 +1,10 @@ +conformant = "Pass" +output = """ +literals_semantics.py:10:18 - error: Expression of type "Literal[4]" cannot be assigned to declared type "Literal[3]" +  "Literal[4]" cannot be assigned to type "Literal[3]" (reportGeneralTypeIssues) +literals_semantics.py:24:26 - error: Expression of type "Literal[0]" cannot be assigned to declared type "Literal[False]" +  "Literal[0]" cannot be assigned to type "Literal[False]" (reportGeneralTypeIssues) +literals_semantics.py:25:22 - error: Expression of type "Literal[False]" cannot be assigned to declared type "Literal[0]" +  "Literal[False]" cannot be assigned to type "Literal[0]" (reportGeneralTypeIssues) +literals_semantics.py:33:10 - error: Expression of type "Literal[6, 7, 8]" cannot be assigned to declared type "Literal[3, 4, 5]" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/narrowing_typeguard.toml b/conformance/results/pyright/narrowing_typeguard.toml new file mode 100644 index 000000000..8ad960539 --- /dev/null +++ b/conformance/results/pyright/narrowing_typeguard.toml @@ -0,0 +1,5 @@ +conformant = "Pass" +output = """ +narrowing_typeguard.py:102:9 - error: User-defined type guard functions and methods must have at least one input parameter (reportGeneralTypeIssues) +narrowing_typeguard.py:107:9 - error: User-defined type guard functions and methods must have at least one input parameter (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/typeddicts_alt_syntax.toml b/conformance/results/pyright/typeddicts_alt_syntax.toml new file mode 100644 index 000000000..12142faae --- /dev/null +++ b/conformance/results/pyright/typeddicts_alt_syntax.toml @@ -0,0 +1,9 @@ +conformant = "Pass" +output = """ +typeddicts_alt_syntax.py:23:17 - error: Expected dict or keyword parameter as second parameter +typeddicts_alt_syntax.py:27:45 - error: Expected string literal for dictionary entry name +typeddicts_alt_syntax.py:31:1 - error: TypedDict must be assigned to a variable named "WrongName" +typeddicts_alt_syntax.py:35:78 - error: Extra TypedDict arguments not supported +typeddicts_alt_syntax.py:45:43 - error: Expression of type "dict[str, str]" cannot be assigned to declared type "Movie2" +  "Literal['']" is incompatible with "int" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/typeddicts_class_syntax.toml b/conformance/results/pyright/typeddicts_class_syntax.toml new file mode 100644 index 000000000..96b9e5518 --- /dev/null +++ b/conformance/results/pyright/typeddicts_class_syntax.toml @@ -0,0 +1,8 @@ +conformant = "Pass" +output = """ +typeddicts_class_syntax.py:29:5 - error: TypedDict classes can contain only type annotations +typeddicts_class_syntax.py:33:5 - error: TypedDict classes can contain only type annotations +typeddicts_class_syntax.py:38:5 - error: TypedDict classes can contain only type annotations +typeddicts_class_syntax.py:44:32 - error: TypedDict does not support __init_subclass__ parameter "metaclass" +typeddicts_class_syntax.py:49:32 - error: TypedDict does not support __init_subclass__ parameter "other" +""" diff --git a/conformance/results/pyright/typeddicts_final.toml b/conformance/results/pyright/typeddicts_final.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/typeddicts_final.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/typeddicts_inheritance.toml b/conformance/results/pyright/typeddicts_inheritance.toml new file mode 100644 index 000000000..d0470b408 --- /dev/null +++ b/conformance/results/pyright/typeddicts_inheritance.toml @@ -0,0 +1,9 @@ +conformant = "Pass" +output = """ +typeddicts_inheritance.py:44:7 - error: All base classes for TypedDict classes must also be TypedDict classes +  Class "NonTypedDict" is not a TypedDict +typeddicts_inheritance.py:55:4 - error: "x" overrides symbol of same name in class "X1" +  Variable is mutable so its type is invariant +    Override type "int" is not the same as base type "str" (reportIncompatibleVariableOverride) +typeddicts_inheritance.py:65:7 - error: Base classes for class "XYZ2" define variable "x" in incompatible way (reportIncompatibleVariableOverride) +""" diff --git a/conformance/results/pyright/typeddicts_operations.toml b/conformance/results/pyright/typeddicts_operations.toml new file mode 100644 index 000000000..cba0f0d79 --- /dev/null +++ b/conformance/results/pyright/typeddicts_operations.toml @@ -0,0 +1,24 @@ +conformant = "Pass" +output = """ +typeddicts_operations.py:22:1 - error: Could not assign item in TypedDict +  "Literal[1982]" is incompatible with "str" (reportGeneralTypeIssues) +typeddicts_operations.py:23:1 - error: Could not assign item in TypedDict +  "Literal['']" is incompatible with "int" (reportGeneralTypeIssues) +typeddicts_operations.py:24:1 - error: Could not assign item in TypedDict +  "other" is not a defined key in "Movie" (reportGeneralTypeIssues) +typeddicts_operations.py:26:7 - error: Could not access item in TypedDict +  "other" is not a defined key in "Movie" (reportGeneralTypeIssues) +typeddicts_operations.py:28:9 - error: Expression of type "dict[str, str]" cannot be assigned to declared type "Movie" +  "year" is required in "Movie" (reportGeneralTypeIssues) +typeddicts_operations.py:29:42 - error: Expression of type "dict[str, str | float]" cannot be assigned to declared type "Movie" +  "float" is incompatible with "int" (reportGeneralTypeIssues) +typeddicts_operations.py:32:36 - error: Expression of type "dict[str, str | int]" cannot be assigned to declared type "Movie" +  "other" is an undefined field in type "Movie" (reportGeneralTypeIssues) +typeddicts_operations.py:37:20 - error: Expression of type "dict[str, str | int]" cannot be assigned to declared type "Movie" (reportGeneralTypeIssues) +typeddicts_operations.py:47:7 - error: Cannot access member "clear" for type "Movie" +  Member "clear" is unknown (reportGeneralTypeIssues) +typeddicts_operations.py:49:5 - error: Could not delete item in TypedDict +  "name" is a required key and cannot be deleted (reportGeneralTypeIssues) +typeddicts_operations.py:62:16 - error: Cannot access member "clear" for type "MovieOptional" +  Member "clear" is unknown (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/typeddicts_required.toml b/conformance/results/pyright/typeddicts_required.toml new file mode 100644 index 000000000..55fc83dad --- /dev/null +++ b/conformance/results/pyright/typeddicts_required.toml @@ -0,0 +1,7 @@ +conformant = "Pass" +output = """ +typeddicts_required.py:12:8 - error: "Required" is not allowed in this context +typeddicts_required.py:16:8 - error: "NotRequired" is not allowed in this context +typeddicts_required.py:59:8 - error: "Required" is not allowed in this context +typeddicts_required.py:60:8 - error: "Required" is not allowed in this context +""" diff --git a/conformance/results/pyright/typeddicts_type_consistency.toml b/conformance/results/pyright/typeddicts_type_consistency.toml new file mode 100644 index 000000000..91b110b02 --- /dev/null +++ b/conformance/results/pyright/typeddicts_type_consistency.toml @@ -0,0 +1,25 @@ +conformant = "Pass" +output = """ +typeddicts_type_consistency.py:21:10 - error: Expression of type "B1" cannot be assigned to declared type "A1" +  "x" is an incompatible type +    Type "int" cannot be assigned to type "int | None" +      "int" is incompatible with "None" (reportGeneralTypeIssues) +typeddicts_type_consistency.py:38:10 - error: Expression of type "B2" cannot be assigned to declared type "A2" +  "x" is not required in "type[A2]" (reportGeneralTypeIssues) +typeddicts_type_consistency.py:65:6 - error: Expression of type "A3" cannot be assigned to declared type "B3" +  "y" is missing from "type[A3]" (reportGeneralTypeIssues) +typeddicts_type_consistency.py:69:21 - error: Expression of type "dict[str, int]" cannot be assigned to declared type "A3" +  "y" is an undefined field in type "A3" (reportGeneralTypeIssues) +typeddicts_type_consistency.py:76:22 - error: Expression of type "B3" cannot be assigned to declared type "dict[str, int]" +  "B3" is incompatible with "dict[str, int]" (reportGeneralTypeIssues) +typeddicts_type_consistency.py:77:25 - error: Expression of type "B3" cannot be assigned to declared type "dict[str, object]" +  "B3" is incompatible with "dict[str, object]" (reportGeneralTypeIssues) +typeddicts_type_consistency.py:78:22 - error: Expression of type "B3" cannot be assigned to declared type "dict[Any, Any]" +  "B3" is incompatible with "dict[Any, Any]" (reportGeneralTypeIssues) +typeddicts_type_consistency.py:82:25 - error: Expression of type "B3" cannot be assigned to declared type "Mapping[str, int]" +  "B3" is incompatible with "Mapping[str, int]" +    Type parameter "_VT_co@Mapping" is covariant, but "object" is not a subtype of "int" +      "object" is incompatible with "int" (reportGeneralTypeIssues) +typeddicts_type_consistency.py:124:56 - error: Expression of type "dict[str, dict[str, dict[str, int]]]" cannot be assigned to declared type "Outer1" +  "Literal[1]" is incompatible with "str" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/typeddicts_usage.toml b/conformance/results/pyright/typeddicts_usage.toml new file mode 100644 index 000000000..d8894115b --- /dev/null +++ b/conformance/results/pyright/typeddicts_usage.toml @@ -0,0 +1,12 @@ +conformant = "Pass" +output = """ +typeddicts_usage.py:23:1 - error: Could not assign item in TypedDict +  "director" is not a defined key in "Movie" (reportGeneralTypeIssues) +typeddicts_usage.py:24:1 - error: Could not assign item in TypedDict +  "Literal['1982']" is incompatible with "int" (reportGeneralTypeIssues) +typeddicts_usage.py:28:18 - error: Expression of type "dict[str, str | int]" cannot be assigned to declared type "Movie" +  "title" is an undefined field in type "Movie" (reportGeneralTypeIssues) +typeddicts_usage.py:35:22 - error: Second argument to "isinstance" must be a class or tuple of classes +  TypedDict class not allowed for instance or class checks (reportGeneralTypeIssues) +typeddicts_usage.py:40:24 - error: "TypedDict" cannot be used in this context +""" diff --git a/conformance/results/pyright/version.toml b/conformance/results/pyright/version.toml new file mode 100644 index 000000000..281a67b40 --- /dev/null +++ b/conformance/results/pyright/version.toml @@ -0,0 +1,2 @@ +version = "pyright 1.1.343" +test_duration = 1.0995278358459473 diff --git a/conformance/results/pytype/aliases_explicit.toml b/conformance/results/pytype/aliases_explicit.toml new file mode 100644 index 000000000..f40717d66 --- /dev/null +++ b/conformance/results/pytype/aliases_explicit.toml @@ -0,0 +1,48 @@ +conformant = "Partial" +notes = """ +Incorrectly reports error for type alias defined with ParamSpec. +Does not report invalid specialization of generic type alias with bound TypeVar. +Incorrectly evaluates generic type alias with ParamSpec with missing type argument. +Does not report some illegal annotation forms as invalid type aliases. +Does not report invalid specialization of already-specialized generic type alias. +""" +output = """ +File "aliases_explicit.py", line 41, in : Invalid type annotation 'Callable[Concatenate[int, P], R][[str, str], None]' [invalid-annotation] + Callable[Concatenate[int, P], R] expected 1 parameter, got 2 +File "aliases_explicit.py", line 41, in : Invalid type annotation '[str, str]' [invalid-annotation] + Not a type +File "aliases_explicit.py", line 57, in good_type_aliases: Callable[Concatenate, Any] [assert-type] + Expected: Callable[[int, str, str], None] + Actual: Callable[Concatenate, Any] +File "aliases_explicit.py", line 60, in good_type_aliases: Callable[[Any], None] [assert-type] + Expected: Callable[..., None] + Actual: Callable[[Any], None] +File "aliases_explicit.py", line 67, in : Invalid type annotation 'Optional[int][int]' [invalid-annotation] + Optional[int] expected 0 parameters, got 1 +File "aliases_explicit.py", line 68, in : Invalid type annotation 'List[Optional[int]][int]' [invalid-annotation] + List[Optional[int]] expected 0 parameters, got 1 +File "aliases_explicit.py", line 69, in : Invalid type annotation 'List[T][int, int]' [invalid-annotation] + List[T] expected 1 parameter, got 2 +File "aliases_explicit.py", line 70, in : Invalid type annotation 'Callable[[int, T], T][int, int]' [invalid-annotation] + Callable[[int, T], T] expected 1 parameter, got 2 +File "aliases_explicit.py", line 71, in : Invalid type annotation 'Callable[Concatenate[int, P], R][int, int]' [invalid-annotation] + Callable[Concatenate[int, P], R] expected 1 parameter, got 2 +File "aliases_explicit.py", line 80, in : Invalid type annotation '[int, str]' for BadTypeAlias2 [invalid-annotation] + Not a type +File "aliases_explicit.py", line 81, in : Invalid type annotation '((int, str),)' for BadTypeAlias3 [invalid-annotation] + Not a type +File "aliases_explicit.py", line 82, in : Invalid type annotation '' for BadTypeAlias4 [invalid-annotation] + Not a type +File "aliases_explicit.py", line 83, in : Invalid type annotation "{'a': 'b'}" for BadTypeAlias5 [invalid-annotation] + Not a type +File "aliases_explicit.py", line 87, in : Invalid type annotation '3' for BadTypeAlias9 [invalid-annotation] + Not a type +File "aliases_explicit.py", line 88, in : Invalid type annotation 'True' for BadTypeAlias10 [invalid-annotation] + Not a type +File "aliases_explicit.py", line 89, in : Invalid type annotation '1' for BadTypeAlias11 [invalid-annotation] + Not a type +File "aliases_explicit.py", line 91, in : Invalid type annotation '' for BadTypeAlias13 [invalid-annotation] + Must be constant +File "aliases_explicit.py", line 102, in : Invalid type annotation 'Union[list, set][int]' [invalid-annotation] + Union[list, set] expected 0 parameters, got 1 +""" diff --git a/conformance/results/pytype/aliases_implicit.toml b/conformance/results/pytype/aliases_implicit.toml new file mode 100644 index 000000000..bf73de15f --- /dev/null +++ b/conformance/results/pytype/aliases_implicit.toml @@ -0,0 +1,46 @@ +conformant = "Partial" +notes = """ +Incorrectly reports error for type alias defined with ParamSpec. +Does not report invalid specialization of generic type alias with bound TypeVar. +Incorrectly evaluates generic type alias with ParamSpec with missing type argument. +Allows some illegal annotation forms to be interpreted as valid type aliases. +Does not report invalid specialization of already-specialized generic type alias. +""" +output = """ +File "aliases_implicit.py", line 54, in : Invalid type annotation 'Callable[Concatenate[int, P], R][[str, str], None]' [invalid-annotation] + Callable[Concatenate[int, P], R] expected 1 parameter, got 2 +File "aliases_implicit.py", line 54, in : Invalid type annotation '[str, str]' [invalid-annotation] + Not a type +File "aliases_implicit.py", line 68, in good_type_aliases: Callable[Concatenate, Any] [assert-type] + Expected: Callable[[int, str, str], None] + Actual: Callable[Concatenate, Any] +File "aliases_implicit.py", line 72, in good_type_aliases: Callable[[Any], None] [assert-type] + Expected: Callable[..., None] + Actual: Callable[[Any], None] +File "aliases_implicit.py", line 76, in : Invalid type annotation 'Optional[int][int]' [invalid-annotation] + Optional[int] expected 0 parameters, got 1 +File "aliases_implicit.py", line 77, in : Invalid type annotation 'List[Optional[int]][int]' [invalid-annotation] + List[Optional[int]] expected 0 parameters, got 1 +File "aliases_implicit.py", line 78, in : Invalid type annotation 'List[T][int, int]' [invalid-annotation] + List[T] expected 1 parameter, got 2 +File "aliases_implicit.py", line 79, in : Invalid type annotation 'Callable[[int, T], T][int, int]' [invalid-annotation] + Callable[[int, T], T] expected 1 parameter, got 2 +File "aliases_implicit.py", line 80, in : Invalid type annotation 'Callable[Concatenate[int, P], R][int, int]' [invalid-annotation] + Callable[Concatenate[int, P], R] expected 1 parameter, got 2 +File "aliases_implicit.py", line 105, in : Invalid type annotation '[int, str]' for p2 [invalid-annotation] + Not a type +File "aliases_implicit.py", line 105, in : Invalid type annotation '((int, str),)' for p3 [invalid-annotation] + Not a type +File "aliases_implicit.py", line 105, in : Invalid type annotation '' for p4 [invalid-annotation] + Not a type +File "aliases_implicit.py", line 105, in : Invalid type annotation "{'a': 'b'}" for p5 [invalid-annotation] + Not a type +File "aliases_implicit.py", line 105, in : Invalid type annotation '3' for p9 [invalid-annotation] + Not a type +File "aliases_implicit.py", line 105, in : Invalid type annotation 'True' for p10 [invalid-annotation] + Not a type +File "aliases_implicit.py", line 105, in : Invalid type annotation '1' for p11 [invalid-annotation] + Not a type +File "aliases_implicit.py", line 135, in : Invalid type annotation 'Union[list, set][int]' [invalid-annotation] + Union[list, set] expected 0 parameters, got 1 +""" diff --git a/conformance/results/pytype/aliases_newtype.toml b/conformance/results/pytype/aliases_newtype.toml new file mode 100644 index 000000000..b93e257df --- /dev/null +++ b/conformance/results/pytype/aliases_newtype.toml @@ -0,0 +1,24 @@ +conformant = "Partial" +notes = """ +Does not reject use of NewType in `isinstance` call. +Does not reject use of NewType in class definition statement. +Does not report inconsistency between name of NewType and assigned identifier name. +Does not reject use of NewType with generic class with TypeVar. +Does not reject use of NewType with protocol class. +Does not reject use of NewType with TypedDict class. +Does not reject use of NewType with another NewType. +Does not reject use of NewType with Any. +""" +output = """ +File "aliases_newtype.py", line 11, in : Function UserId.__init__ was called with the wrong arguments [wrong-arg-types] + Expected: (self, val: int) + Actually passed: (self, val: str) +File "aliases_newtype.py", line 12, in : Type annotation for u1 does not match type of assignment [annotation-type-mismatch] + Annotation: UserId + Assignment: int +File "aliases_newtype.py", line 36, in : class GoodNewType1 is not indexable [not-indexable] + ('GoodNewType1' does not subclass Generic) +File "aliases_newtype.py", line 60, in : Function typing.NewType expects 2 arg(s), got 3 [wrong-arg-count] + Expected: (name, tp) + Actually passed: (name, tp, _) +""" diff --git a/conformance/results/pytype/aliases_recursive.toml b/conformance/results/pytype/aliases_recursive.toml new file mode 100644 index 000000000..6009ddb60 --- /dev/null +++ b/conformance/results/pytype/aliases_recursive.toml @@ -0,0 +1,59 @@ +conformant = "Partial" +notes = """ +Does not detect type violation for some deeply-nested types. +Does not properly handle `|` for unions in some recursive type alias definitions. +Does not detect cyclical references in recursive type alias definition. +""" +output = """ +File "aliases_recursive.py", line 19, in : Type annotation for j4 does not match type of assignment [annotation-type-mismatch] + Annotation: Optional[Union[Dict[str, Json], List[Json], float, int, str]] + Assignment: Dict[str, Union[complex, int]] +File "aliases_recursive.py", line 20, in : Type annotation for j5 does not match type of assignment [annotation-type-mismatch] + Annotation: Optional[Union[Dict[str, Json], List[Json], float, int, str]] + Assignment: List[Union[complex, int]] +File "aliases_recursive.py", line 38, in : Type annotation for t6 does not match type of assignment [annotation-type-mismatch] + Annotation: Union[Tuple[RecursiveTuple, ...], Union[int, str]] + Assignment: Tuple[int, Tuple[str, int], Tuple[int, Tuple[int, List[int]]]] +File "aliases_recursive.py", line 39, in : Type annotation for t6 does not match type of assignment [annotation-type-mismatch] + Annotation: Union[Tuple[RecursiveTuple, ...], Union[int, str]] + Assignment: Tuple[int, List[int]] +File "aliases_recursive.py", line 50, in : Type annotation for m7 does not match type of assignment [annotation-type-mismatch] + Annotation: Union[Mapping[str, RecursiveMapping], Union[int, str]] + Assignment: Dict[str, List[int]] + Attributes of protocol Mapping[str, RecursiveMapping] are not implemented on list: get, items, keys, values +File "aliases_recursive.py", line 51, in : Type annotation for m8 does not match type of assignment [annotation-type-mismatch] + Annotation: Union[Mapping[str, RecursiveMapping], Union[int, str]] + Assignment: Dict[str, Union[List[int], int, str]] + Attributes of protocol Mapping[str, RecursiveMapping] are not implemented on list: get, items, keys, values +File "aliases_recursive.py", line 62, in : unsupported operand type(s) for |: ''GenericTypeAlias1[T1]': str' and 'T1: TypeVar' [unsupported-operands] + No attribute '__or__' on ''GenericTypeAlias1[T1]': str' or '__ror__' on 'T1: TypeVar' +File "aliases_recursive.py", line 63, in : Invalid type annotation 'list[str]' [invalid-annotation] + list expected 0 parameters, got 1 +File "aliases_recursive.py", line 66, in : Invalid type annotation 'GenericTypeAlias1[str]' [invalid-annotation] + Invalid type annotation 'list[str]' + list expected 0 parameters, got 1 +File "aliases_recursive.py", line 66, in : Invalid type annotation 'list[str]' [invalid-annotation] + list expected 0 parameters, got 1 +File "aliases_recursive.py", line 67, in : Invalid type annotation 'GenericTypeAlias1[str]' [invalid-annotation] + Invalid type annotation 'list[str]' + list expected 0 parameters, got 1 +File "aliases_recursive.py", line 67, in : Invalid type annotation 'list[str]' [invalid-annotation] + list expected 0 parameters, got 1 +File "aliases_recursive.py", line 69, in : unsupported operand type(s) for |: ''GenericTypeAlias2[T1, T2]': str' and 'T1: TypeVar' [unsupported-operands] + No attribute '__or__' on ''GenericTypeAlias2[T1, T2]': str' or '__ror__' on 'T1: TypeVar' +File "aliases_recursive.py", line 71, in : Invalid type annotation 'GenericTypeAlias2[str, int]' [invalid-annotation] + Invalid type annotation 'list[str, int]' + list expected 1 parameter, got 2 +File "aliases_recursive.py", line 71, in : Invalid type annotation 'list[str, int]' [invalid-annotation] + list expected 1 parameter, got 2 +File "aliases_recursive.py", line 72, in : Invalid type annotation 'GenericTypeAlias2[str, float]' [invalid-annotation] + Invalid type annotation 'list[str, float]' + list expected 1 parameter, got 2 +File "aliases_recursive.py", line 72, in : Invalid type annotation 'list[str, float]' [invalid-annotation] + list expected 1 parameter, got 2 +File "aliases_recursive.py", line 73, in : Invalid type annotation 'GenericTypeAlias2[str, int]' [invalid-annotation] + Invalid type annotation 'list[str, int]' + list expected 1 parameter, got 2 +File "aliases_recursive.py", line 73, in : Invalid type annotation 'list[str, int]' [invalid-annotation] + list expected 1 parameter, got 2 +""" diff --git a/conformance/results/pytype/aliases_type_statement.toml b/conformance/results/pytype/aliases_type_statement.toml new file mode 100644 index 000000000..49c698731 --- /dev/null +++ b/conformance/results/pytype/aliases_type_statement.toml @@ -0,0 +1,7 @@ +conformant = "Unsupported" +notes = """ +Does not support `type` statement. +""" +output = """ +File "aliases_type_statement.py", line 8: Type statement is only supported in Python 3.12 and greater [python-compiler-error] +""" diff --git a/conformance/results/pytype/aliases_typealiastype.toml b/conformance/results/pytype/aliases_typealiastype.toml new file mode 100644 index 000000000..ca246c9ea --- /dev/null +++ b/conformance/results/pytype/aliases_typealiastype.toml @@ -0,0 +1,18 @@ +conformant = "Unsupported" +notes = """ +Support for TypeAliasType is not implemented. +""" +output = """ +File "aliases_typealiastype.py", line 5, in : Can't find module 'typing.TypeAliasType'. [import-error] +File "aliases_typealiastype.py", line 5, in : typing.TypeVarTuple not supported yet [not-supported-yet] +File "aliases_typealiastype.py", line 11, in : Function TypeVarTuple.__init__ expects 1 arg(s), got 2 [wrong-arg-count] + Expected: (self) + Actually passed: (self, _) +File "aliases_typealiastype.py", line 19, in : Name 'GoodAlias4' is not defined [name-error] +File "aliases_typealiastype.py", line 22, in : Name 'GoodAlias5' is not defined [name-error] +File "aliases_typealiastype.py", line 22, in : Invalid type annotation '' [invalid-annotation] + Not a type +File "aliases_typealiastype.py", line 50, in : Name 'BadAlias4' is not defined [name-error] +File "aliases_typealiastype.py", line 52, in : Name 'BadAlias5' is not defined [name-error] +File "aliases_typealiastype.py", line 54, in : Name 'BadAlias7' is not defined [name-error] +""" diff --git a/conformance/results/pytype/aliases_variance.toml b/conformance/results/pytype/aliases_variance.toml new file mode 100644 index 000000000..4d08ba023 --- /dev/null +++ b/conformance/results/pytype/aliases_variance.toml @@ -0,0 +1,8 @@ +conformant = "Unsupported" +notes = """ +Does not detect variance incompatibility. +""" +output = """ +File "aliases_variance.py", line 9, in : argument "covariant" to TypeVar not supported yet [not-supported-yet] +File "aliases_variance.py", line 10, in : argument "contravariant" to TypeVar not supported yet [not-supported-yet] +""" diff --git a/conformance/results/pytype/annotations_typeexpr.toml b/conformance/results/pytype/annotations_typeexpr.toml new file mode 100644 index 000000000..6b91be0f3 --- /dev/null +++ b/conformance/results/pytype/annotations_typeexpr.toml @@ -0,0 +1,26 @@ +conformant = "Partial" +notes = """ +Does not reject call expressions in type annotation. +Does not reject call lambda expression in type annotation. +Does not reject list expression in type annotation. +Does not reject ternary expression in type annotation. +Does not reject f-string in type annotation. +""" +output = """ +File "annotations_typeexpr.py", line 76, in : Invalid type annotation '[int, str]' for p2 [invalid-annotation] + Not a type +File "annotations_typeexpr.py", line 76, in : Invalid type annotation '(int, str)' for p3 [invalid-annotation] + Not a type +File "annotations_typeexpr.py", line 76, in : Invalid type annotation '' for p4 [invalid-annotation] + Not a type +File "annotations_typeexpr.py", line 76, in : Invalid type annotation '{}' for p5 [invalid-annotation] + Not a type +File "annotations_typeexpr.py", line 76, in : Invalid type annotation '3' for p9 [invalid-annotation] + Not a type +File "annotations_typeexpr.py", line 76, in : Invalid type annotation 'True' for p10 [invalid-annotation] + Not a type +File "annotations_typeexpr.py", line 76, in : Invalid type annotation '1' for p11 [invalid-annotation] + Not a type +File "annotations_typeexpr.py", line 76, in : Invalid type annotation '-1' for p12 [invalid-annotation] + Not a type +""" diff --git a/conformance/results/pytype/callables_annotation.toml b/conformance/results/pytype/callables_annotation.toml new file mode 100644 index 000000000..b83d24273 --- /dev/null +++ b/conformance/results/pytype/callables_annotation.toml @@ -0,0 +1,32 @@ +conformant = "Pass" +output = """ +File "callables_annotation.py", line 13, in func1: Function expects 2 arg(s), got 1 [wrong-arg-count] + Expected: (_, _) + Actually passed: (_) +File "callables_annotation.py", line 14, in func1: Function was called with the wrong arguments [wrong-arg-types] + Expected: (_, _1: str) + Actually passed: (_, _1: int) +File "callables_annotation.py", line 15, in func1: Function expects 2 arg(s), got 3 [wrong-arg-count] + Expected: (_, _) + Actually passed: (_, _, _) +File "callables_annotation.py", line 16, in func1: Invalid keyword arguments (a, b) to function [wrong-keyword-args] + Expected: (_, _) + Actually passed: (a, b) +File "callables_annotation.py", line 22, in func2: Function expects 0 arg(s), got 1 [wrong-arg-count] + Expected: () + Actually passed: (_) +File "callables_annotation.py", line 39, in : Invalid type annotation 'int' [invalid-annotation] + First argument to Callable must be a list of argument types or ellipsis. +File "callables_annotation.py", line 39, in : Invalid type annotation 'Callable[Any]' [invalid-annotation] + Callable[_ARGS, _RET] expected 2 parameters, got 1 +File "callables_annotation.py", line 40, in : Invalid type annotation 'int' [invalid-annotation] + First argument to Callable must be a list of argument types or ellipsis. +File "callables_annotation.py", line 41, in : Invalid type annotation '[int]' [invalid-annotation] + Not a type +File "callables_annotation.py", line 42, in : Invalid type annotation 'int' [invalid-annotation] + First argument to Callable must be a list of argument types or ellipsis. +File "callables_annotation.py", line 42, in : Invalid type annotation 'Callable[Any, int, int]' [invalid-annotation] + Callable[_ARGS, _RET] expected 2 parameters, got 3 +File "callables_annotation.py", line 43, in : Invalid type annotation 'Ellipsis' [invalid-annotation] + Not allowed at index 0 in list +""" diff --git a/conformance/results/pytype/callables_kwargs.toml b/conformance/results/pytype/callables_kwargs.toml new file mode 100644 index 000000000..c7a2086f8 --- /dev/null +++ b/conformance/results/pytype/callables_kwargs.toml @@ -0,0 +1,48 @@ +conformant = "Unsupported" +notes = """ +Does not understand Unpack in the context of **kwargs annotation. +""" +output = """ +File "callables_kwargs.py", line 10, in : typing.NotRequired not supported yet [not-supported-yet] +File "callables_kwargs.py", line 10, in : typing.Required not supported yet [not-supported-yet] +File "callables_kwargs.py", line 10, in : typing.Unpack not supported yet [not-supported-yet] +File "callables_kwargs.py", line 24, in func1: Unpack[TD2] [assert-type] + Expected: int + Actual: Unpack[TD2] +File "callables_kwargs.py", line 30, in func1: Unpack[TD2] [assert-type] + Expected: str + Actual: Unpack[TD2] +File "callables_kwargs.py", line 33, in func1: Unpack[TD2] [assert-type] + Expected: str + Actual: Unpack[TD2] +File "callables_kwargs.py", line 39, in func2: Dict[str, Unpack[TD1]] [assert-type] + Expected: TD1 + Actual: Dict[str, Unpack[TD1]] +File "callables_kwargs.py", line 44, in func3: Function func1 was called with the wrong arguments [wrong-arg-types] + Expected: (v1: Unpack[TD2], ...) + Actually passed: (v1: int, ...) +File "callables_kwargs.py", line 46, in func3: Function TD2.__init__ was called with the wrong arguments [wrong-arg-types] + Expected: (*, v1: Required[int], ...) + Actually passed: (v1: int, ...) +File "callables_kwargs.py", line 48, in func3: Function func1 was called with the wrong arguments [wrong-arg-types] + Expected: (v1: Unpack[TD2], ...) + Actually passed: (v1: int, ...) +File "callables_kwargs.py", line 49, in func3: Function func1 expects 0 arg(s), got 3 [wrong-arg-count] + Expected: (**kwargs) + Actually passed: (_, _, _) +File "callables_kwargs.py", line 55, in func3: Function func1 was called with the wrong arguments [wrong-arg-types] + Expected: (**kwargs: Mapping[str, Unpack[TD2]]) + Actually passed: (kwargs: Dict[str, str]) +File "callables_kwargs.py", line 58, in func3: Function func1 was called with the wrong arguments [wrong-arg-types] + Expected: (v1: Unpack[TD2], ...) + Actually passed: (v1: int, ...) +File "callables_kwargs.py", line 60, in func3: Function func1 was called with the wrong arguments [wrong-arg-types] + Expected: (v1: Unpack[TD2], ...) + Actually passed: (v1: int, ...) +File "callables_kwargs.py", line 61, in func3: Function func2 was called with the wrong arguments [wrong-arg-types] + Expected: (v3: str, ...) + Actually passed: (v3: int, ...) +File "callables_kwargs.py", line 62, in func3: Function func2 was called with the wrong arguments [wrong-arg-types] + Expected: (v3, v1: Unpack[TD1], ...) + Actually passed: (v1: int, ...) +""" diff --git a/conformance/results/pytype/callables_protocol.toml b/conformance/results/pytype/callables_protocol.toml new file mode 100644 index 000000000..2afc6ae41 --- /dev/null +++ b/conformance/results/pytype/callables_protocol.toml @@ -0,0 +1,27 @@ +conformant = "Unsupported" +notes = """ +Does not properly handle type compatibility checks with callback protocols. +""" +output = """ +File "callables_protocol.py", line 9, in : argument "contravariant" to TypeVar not supported yet [not-supported-yet] +File "callables_protocol.py", line 10, in : argument "covariant" to TypeVar not supported yet [not-supported-yet] +File "callables_protocol.py", line 121, in : Type annotation for cb6 does not match type of assignment [annotation-type-mismatch] + Annotation: NotProto6 + Assignment: Callable[..., List[bytes]] +File "callables_protocol.py", line 173, in : argument "covariant" to TypeVar not supported yet [not-supported-yet] +File "callables_protocol.py", line 176, in : Invalid type annotation 'Protocol' [invalid-annotation] + Parameters to Generic[...] must all be type variables +File "callables_protocol.py", line 183, in : Invalid type annotation 'Proto9[P, R]' [invalid-annotation] + Proto9[R] expected 1 parameter, got 2 +File "callables_protocol.py", line 184, in decorator1: Invalid type annotation 'Proto9[P, R]' [invalid-annotation] + Proto9[R] expected 1 parameter, got 2 +File "callables_protocol.py", line 188, in decorator1: bad return type [bad-return-type] + Expected: Proto9 + Actually returned: Proto9 +Called from (traceback): + line 191, in current file +File "callables_protocol.py", line 197, in : No attribute 'other_attribute2' on Proto9 [attribute-error] +File "callables_protocol.py", line 199, in : Function Proto9.__call__ was called with the wrong arguments [wrong-arg-types] + Expected: (self, x: P.kwargs, ...) + Actually passed: (self, x: int) +""" diff --git a/conformance/results/pytype/dataclasses_descriptors.toml b/conformance/results/pytype/dataclasses_descriptors.toml new file mode 100644 index 000000000..e48da430c --- /dev/null +++ b/conformance/results/pytype/dataclasses_descriptors.toml @@ -0,0 +1,40 @@ +conformant = "Unsupported" +notes = """ +Does not understand descriptor objects in dataclass. +""" +output = """ +File "dataclasses_descriptors.py", line 24, in __get__: bad return type [bad-return-type] + Expected: int + Actually returned: None +Called from (traceback): + line 37, in current file +File "dataclasses_descriptors.py", line 24, in __get__: bad return type [bad-return-type] + Expected: Desc1 + Actually returned: None +Called from (traceback): + line 38, in current file +File "dataclasses_descriptors.py", line 35, in : Function DC1.__init__ was called with the wrong arguments [wrong-arg-types] + Expected: (self, y: Desc1 = ...) + Actually passed: (self, y: int) +File "dataclasses_descriptors.py", line 51, in __get__: bad return type [bad-return-type] + Expected: list + Actually returned: None +File "dataclasses_descriptors.py", line 61, in : list [assert-type] + Expected: List[int] + Actual: list +File "dataclasses_descriptors.py", line 62, in : list [assert-type] + Expected: List[str] + Actual: list +File "dataclasses_descriptors.py", line 63, in : list [assert-type] + Expected: List[str] + Actual: list +File "dataclasses_descriptors.py", line 66, in : Any [assert-type] + Expected: int + Actual: Any +File "dataclasses_descriptors.py", line 67, in : Any [assert-type] + Expected: str + Actual: Any +File "dataclasses_descriptors.py", line 68, in : Any [assert-type] + Expected: str + Actual: Any +""" diff --git a/conformance/results/pytype/dataclasses_frozen.toml b/conformance/results/pytype/dataclasses_frozen.toml new file mode 100644 index 000000000..4f832c2bf --- /dev/null +++ b/conformance/results/pytype/dataclasses_frozen.toml @@ -0,0 +1,8 @@ +conformant = "Unsupported" +notes = """ +Does not report assignment to field within frozen dataclass instance. +Does not reject frozen dataclass inherited from non-frozen dataclass. +Does not reject non-frozen dataclass inherited from frozen dataclass. +""" +output = """ +""" diff --git a/conformance/results/pytype/dataclasses_hash.toml b/conformance/results/pytype/dataclasses_hash.toml new file mode 100644 index 000000000..a71f51ca0 --- /dev/null +++ b/conformance/results/pytype/dataclasses_hash.toml @@ -0,0 +1,6 @@ +conformant = "Partial" +notes = """ +Does not report when dataclass is not compatible with Hashable protocol. +""" +output = """ +""" diff --git a/conformance/results/pytype/dataclasses_inheritance.toml b/conformance/results/pytype/dataclasses_inheritance.toml new file mode 100644 index 000000000..5000e40e5 --- /dev/null +++ b/conformance/results/pytype/dataclasses_inheritance.toml @@ -0,0 +1,7 @@ +conformant = "Partial" +notes = """ +Does not reject ClassVar that is overridden by instance variable. +Does not reject instance variable that is overridden by ClassVar. +""" +output = """ +""" diff --git a/conformance/results/pytype/dataclasses_kwonly.toml b/conformance/results/pytype/dataclasses_kwonly.toml new file mode 100644 index 000000000..153cca7a0 --- /dev/null +++ b/conformance/results/pytype/dataclasses_kwonly.toml @@ -0,0 +1,23 @@ +conformant = "Partial" +notes = """ +Incorrectly reports error when kw_only field has default value. +Incorrectly rejects kw_only field with default before positional field. +""" +output = """ +File "dataclasses_kwonly.py", line 38, in : Function DC2.__init__ expects 2 arg(s), got 3 [wrong-arg-count] + Expected: (self, a, *, b) + Actually passed: (self, a, _) +File "dataclasses_kwonly.py", line 47, in : Missing parameter 'a' in call to function DC3.__init__ [missing-parameter] + Expected: (self, *, a, b) + Actually passed: (self, _) +File "dataclasses_kwonly.py", line 50, in : Missing parameter 'a' in call to function DC3.__init__ [missing-parameter] + Expected: (self, *, a, b) + Actually passed: (self, b, _) +File "dataclasses_kwonly.py", line 53, in : Missing parameter 'a' in call to function DC3.__init__ [missing-parameter] + Expected: (self, *, a, b) + Actually passed: (self, _, _) +File "dataclasses_kwonly.py", line 57, in : In method __init__, non-default argument c follows default argument [invalid-function-definition] +File "dataclasses_kwonly.py", line 61, in : function DC4.__init__ got multiple values for keyword argument 'b' [duplicate-keyword-argument] + Expected: (self, a, b, c) + Actually passed: (self, a, b, b) +""" diff --git a/conformance/results/pytype/dataclasses_order.toml b/conformance/results/pytype/dataclasses_order.toml new file mode 100644 index 000000000..4f4d92b02 --- /dev/null +++ b/conformance/results/pytype/dataclasses_order.toml @@ -0,0 +1,6 @@ +conformant = "Partial" +notes = """ +Does not report type incompatibility with comparison operator. +""" +output = """ +""" diff --git a/conformance/results/pytype/dataclasses_postinit.toml b/conformance/results/pytype/dataclasses_postinit.toml new file mode 100644 index 000000000..58c6be024 --- /dev/null +++ b/conformance/results/pytype/dataclasses_postinit.toml @@ -0,0 +1,13 @@ +conformant = "Partial" +notes = """ +Does not validate `__post_init__` method. +Reports incorrect error for incompatible `__post_init__` method override. +""" +output = """ +File "dataclasses_postinit.py", line 28, in : No attribute 'x' on DC1 [attribute-error] +File "dataclasses_postinit.py", line 29, in : No attribute 'y' on DC1 [attribute-error] +File "dataclasses_postinit.py", line 54, in DC4: Overriding method signature mismatch [signature-mismatch] + Base signature: 'def DC3.__post_init__(self, _name: str) -> Any'. + Subclass signature: 'def DC4.__post_init__(self, _name: str, _age: int) -> Any'. + Parameter '_age' must have a default value. +""" diff --git a/conformance/results/pytype/dataclasses_slots.toml b/conformance/results/pytype/dataclasses_slots.toml new file mode 100644 index 000000000..f891c51ee --- /dev/null +++ b/conformance/results/pytype/dataclasses_slots.toml @@ -0,0 +1,12 @@ +conformant = "Partial" +notes = """ +Does not report error when `slots=True` is used with `__slots__` definition. +Does not reject write to instance variable that is not defined in __slots__. +Incorrectly reports error when accessing `__slots__` when `slots=True`. +""" +output = """ +File "dataclasses_slots.py", line 57, in : No attribute '__slots__' on Type[DC5] [attribute-error] +File "dataclasses_slots.py", line 58, in : No attribute '__slots__' on DC5 [attribute-error] +File "dataclasses_slots.py", line 67, in : No attribute '__slots__' on Type[DC6] [attribute-error] +File "dataclasses_slots.py", line 70, in : No attribute '__slots__' on DC6 [attribute-error] +""" diff --git a/conformance/results/pytype/dataclasses_transform_class.toml b/conformance/results/pytype/dataclasses_transform_class.toml new file mode 100644 index 000000000..b2f967610 --- /dev/null +++ b/conformance/results/pytype/dataclasses_transform_class.toml @@ -0,0 +1,15 @@ +conformant = "Unsupported" +notes = """ +Does not understand @dataclass_transform. +""" +output = """ +File "dataclasses_transform_class.py", line 23, in : Arguments to dataclass_transform not supported yet [not-supported-yet] +File "dataclasses_transform_class.py", line 57, in : Invalid keyword argument other_name to function Customer1.__init__ [wrong-keyword-args] + Expected: (self, id, name, name2) + Actually passed: (self, id, name, other_name) +File "dataclasses_transform_class.py", line 82, in : Arguments to dataclass_transform not supported yet [not-supported-yet] +File "dataclasses_transform_class.py", line 103, in : Invalid keyword argument id to function GenericCustomer.__init__ [wrong-keyword-args] + Expected: (self) + Actually passed: (self, id) +File "dataclasses_transform_class.py", line 106, in : Arguments to dataclass_transform not supported yet [not-supported-yet] +""" diff --git a/conformance/results/pytype/dataclasses_transform_field.toml b/conformance/results/pytype/dataclasses_transform_field.toml new file mode 100644 index 000000000..0f07a79e8 --- /dev/null +++ b/conformance/results/pytype/dataclasses_transform_field.toml @@ -0,0 +1,11 @@ +conformant = "Unsupported" +notes = """ +Does not understand @dataclass_transform. +""" +output = """ +File "dataclasses_transform_field.py", line 48, in : Arguments to dataclass_transform not supported yet [not-supported-yet] +File "dataclasses_transform_field.py", line 50, in create_model: bad return type [bad-return-type] + Expected: Callable[[type], type] + Actually returned: None + Attributes of protocol Callable[[Type[T]], Type[T]] are not implemented on None: __call__ +""" diff --git a/conformance/results/pytype/dataclasses_transform_func.toml b/conformance/results/pytype/dataclasses_transform_func.toml new file mode 100644 index 000000000..9c8ab708e --- /dev/null +++ b/conformance/results/pytype/dataclasses_transform_func.toml @@ -0,0 +1,36 @@ +conformant = "Unsupported" +notes = """ +Does not understand @dataclass_transform. +""" +output = """ +File "dataclasses_transform_func.py", line 13, in : Arguments to dataclass_transform not supported yet [not-supported-yet] +File "dataclasses_transform_func.py", line 19, in : Arguments to dataclass_transform not supported yet [not-supported-yet] +File "dataclasses_transform_func.py", line 30, in create_model: bad return type [bad-return-type] + Expected: Callable[[Any], Any] + Actually returned: None + Attributes of protocol Callable[[T], T] are not implemented on None: __call__ +File "dataclasses_transform_func.py", line 30, in create_model: bad return type [bad-return-type] + Expected: Type[Customer3Subclass] + Actually returned: None +Called from (traceback): + line 89, in current file +File "dataclasses_transform_func.py", line 50, in : Invalid keyword arguments (id, name) to function Customer1.__init__ [wrong-keyword-args] + Expected: (self) + Actually passed: (self, id, name) +File "dataclasses_transform_func.py", line 53, in : Function Customer1.__init__ expects 1 arg(s), got 3 [wrong-arg-count] + Expected: (self) + Actually passed: (self, _, _) +File "dataclasses_transform_func.py", line 57, in : Type annotation for name does not match type of assignment [annotation-type-mismatch] + Annotation: str + Assignment: int +File "dataclasses_transform_func.py", line 65, in : Invalid keyword arguments (id, name, salary) to function Customer1.__init__ [wrong-keyword-args] + Expected: (self) + Actually passed: (self, id, name, salary) +File "dataclasses_transform_func.py", line 67, in : Invalid keyword arguments (id, name) to function Customer2.__init__ [wrong-keyword-args] + Expected: (self) + Actually passed: (self, id, name) +File "dataclasses_transform_func.py", line 71, in : Function Customer2.__init__ expects 1 arg(s), got 3 [wrong-arg-count] + Expected: (self) + Actually passed: (self, _, _) +File "dataclasses_transform_func.py", line 76, in : Arguments to dataclass_transform not supported yet [not-supported-yet] +""" diff --git a/conformance/results/pytype/dataclasses_transform_meta.toml b/conformance/results/pytype/dataclasses_transform_meta.toml new file mode 100644 index 000000000..969655be0 --- /dev/null +++ b/conformance/results/pytype/dataclasses_transform_meta.toml @@ -0,0 +1,11 @@ +conformant = "Unsupported" +notes = """ +Does not understand @dataclass_transform. +""" +output = """ +File "dataclasses_transform_meta.py", line 21, in : Arguments to dataclass_transform not supported yet [not-supported-yet] +File "dataclasses_transform_meta.py", line 57, in : Invalid keyword argument other_name to function Customer1.__init__ [wrong-keyword-args] + Expected: (self, id, name, name2) + Actually passed: (self, id, name, other_name) +File "dataclasses_transform_meta.py", line 83, in : Arguments to dataclass_transform not supported yet [not-supported-yet] +""" diff --git a/conformance/results/pytype/dataclasses_usage.toml b/conformance/results/pytype/dataclasses_usage.toml new file mode 100644 index 000000000..883ea2462 --- /dev/null +++ b/conformance/results/pytype/dataclasses_usage.toml @@ -0,0 +1,38 @@ +conformant = "Pass" +output = """ +File "dataclasses_usage.py", line 51, in : Missing parameter 'unit_price' in call to function InventoryItem.__init__ [missing-parameter] + Expected: (self, name, unit_price, quantity_on_hand) + Actually passed: (self, name) +File "dataclasses_usage.py", line 52, in : Function InventoryItem.__init__ was called with the wrong arguments [wrong-arg-types] + Expected: (self, name, unit_price: float, ...) + Actually passed: (self, name, unit_price: str) +File "dataclasses_usage.py", line 53, in : Function InventoryItem.__init__ expects 3 arg(s), got 5 [wrong-arg-count] + Expected: (self, name, unit_price, quantity_on_hand) + Actually passed: (self, name, unit_price, quantity_on_hand, _) +File "dataclasses_usage.py", line 60, in : In method __init__, non-default argument b follows default argument [invalid-function-definition] +File "dataclasses_usage.py", line 66, in : In method __init__, non-default argument b follows default argument [invalid-function-definition] +File "dataclasses_usage.py", line 72, in : In method __init__, non-default argument b follows default argument [invalid-function-definition] +File "dataclasses_usage.py", line 84, in : Function DC4.__init__ expects 2 arg(s), got 3 [wrong-arg-count] + Expected: (self, b) + Actually passed: (self, b, _) +File "dataclasses_usage.py", line 89, in DC5: Type annotation for a does not match type of assignment [annotation-type-mismatch] + Annotation: int + Assignment: str +File "dataclasses_usage.py", line 127, in : Function DC7.__init__ expects 2 arg(s), got 3 [wrong-arg-count] + Expected: (self, x) + Actually passed: (self, x, _) +File "dataclasses_usage.py", line 130, in : Missing parameter 'y' in call to function DC8.__init__ [missing-parameter] + Expected: (self, a, y) + Actually passed: (self, a) +File "dataclasses_usage.py", line 152, in __init__: Type annotation for x_squared does not match type of assignment [annotation-type-mismatch] + Annotation: int + Assignment: float + In assignment of type: Union[float, int] +File "dataclasses_usage.py", line 165, in __init__: Type annotation for x_squared does not match type of assignment [annotation-type-mismatch] + Annotation: int + Assignment: float + In assignment of type: Union[float, int] +File "dataclasses_usage.py", line 179, in : Function DC13.__init__ expects 1 arg(s), got 2 [wrong-arg-count] + Expected: (self) + Actually passed: (self, _) +""" diff --git a/conformance/results/pytype/generics_self_advanced.toml b/conformance/results/pytype/generics_self_advanced.toml new file mode 100644 index 000000000..d980d2f3d --- /dev/null +++ b/conformance/results/pytype/generics_self_advanced.toml @@ -0,0 +1,51 @@ +conformant = "Unsupported" +notes = """ +Does not understand `Self` type. +""" +output = """ +File "generics_self_advanced.py", line 12, in prop1: bad return type [bad-return-type] + Expected: ParentA + Actually returned: None +Called from (traceback): + line 18, in current file +File "generics_self_advanced.py", line 12, in prop1: bad return type [bad-return-type] + Expected: ParentA + Actually returned: None +Called from (traceback): + line 19, in current file +File "generics_self_advanced.py", line 19, in : ParentA [assert-type] + Expected: ChildA + Actual: ParentA +File "generics_self_advanced.py", line 29, in method1: bad return type [bad-return-type] + Expected: ChildB + Actually returned: None +Called from (traceback): + line 38, in method2 +File "generics_self_advanced.py", line 29, in method1: bad return type [bad-return-type] + Expected: ParentB + Actually returned: None +File "generics_self_advanced.py", line 35, in method2: ChildB [assert-type] + Expected: Any + Actual: ChildB +File "generics_self_advanced.py", line 36, in method2: List[ChildB] [assert-type] + Expected: list + Actual: List[ChildB] +File "generics_self_advanced.py", line 37, in method2: ChildB [assert-type] + Expected: Any + Actual: ChildB +File "generics_self_advanced.py", line 38, in method2: ChildB [assert-type] + Expected: Any + Actual: ChildB +File "generics_self_advanced.py", line 42, in method3: Type[ChildB] [assert-type] + Expected: Any + Actual: Type[ChildB] +File "generics_self_advanced.py", line 43, in method3: List[ChildB] [assert-type] + Expected: list + Actual: List[ChildB] +File "generics_self_advanced.py", line 44, in method3: ChildB [assert-type] + Expected: Any + Actual: ChildB +File "generics_self_advanced.py", line 45, in method3: ChildB [assert-type] + Expected: Any + Actual: ChildB +""" diff --git a/conformance/results/pytype/generics_self_attributes.toml b/conformance/results/pytype/generics_self_attributes.toml new file mode 100644 index 000000000..ec77829e1 --- /dev/null +++ b/conformance/results/pytype/generics_self_attributes.toml @@ -0,0 +1,9 @@ +conformant = "Unsupported" +notes = """ +Does not understand `Self` type. +""" +output = """ +File "generics_self_attributes.py", line 26, in : Function OrdinalLinkedList.__init__ was called with the wrong arguments [wrong-arg-types] + Expected: (self, value, next: Optional[OrdinalLinkedList] = ...) + Actually passed: (self, value, next: LinkedList[int]) +""" diff --git a/conformance/results/pytype/generics_self_basic.toml b/conformance/results/pytype/generics_self_basic.toml new file mode 100644 index 000000000..768d64c2a --- /dev/null +++ b/conformance/results/pytype/generics_self_basic.toml @@ -0,0 +1,52 @@ +conformant = "Unsupported" +notes = """ +Does not understand `Self` type. +""" +output = """ +File "generics_self_basic.py", line 13, in set_scale: Shape [assert-type] + Expected: Any + Actual: Shape +File "generics_self_basic.py", line 13, in set_scale: Circle [assert-type] + Expected: Any + Actual: Circle +Called from (traceback): + line 51, in current file +File "generics_self_basic.py", line 26, in from_config: Type[Shape] [assert-type] + Expected: Any + Actual: Type[Shape] +Called from (traceback): + line 53, in current file +File "generics_self_basic.py", line 26, in from_config: Type[Circle] [assert-type] + Expected: Any + Actual: Type[Circle] +Called from (traceback): + line 54, in current file +File "generics_self_basic.py", line 27, in from_config: Function Shape.__init__ expects 1 arg(s), got 2 [wrong-arg-count] + Expected: (self) + Actually passed: (self, _) +Called from (traceback): + line 53, in current file +File "generics_self_basic.py", line 27, in from_config: Function Circle.__init__ expects 1 arg(s), got 2 [wrong-arg-count] + Expected: (self) + Actually passed: (self, _) +Called from (traceback): + line 54, in current file +File "generics_self_basic.py", line 39, in difference: Shape [assert-type] + Expected: Any + Actual: Shape +File "generics_self_basic.py", line 61, in set_value: bad return type [bad-return-type] + Expected: Container + Actually returned: None +File "generics_self_basic.py", line 61, in set_value: bad return type [bad-return-type] + Expected: Container[int] + Actually returned: None +Called from (traceback): + line 71, in object_with_concrete_type +File "generics_self_basic.py", line 61, in set_value: bad return type [bad-return-type] + Expected: Container[str] + Actually returned: None +Called from (traceback): + line 72, in object_with_concrete_type +File "generics_self_basic.py", line 64, in Container: unsupported operand type(s) for item retrieval: 'Self: TypeVar' and 'int: Type[int]' [unsupported-operands] + No attribute '__getitem__' on 'Self: TypeVar' +""" diff --git a/conformance/results/pytype/generics_self_protocols.toml b/conformance/results/pytype/generics_self_protocols.toml new file mode 100644 index 000000000..4b3a0000a --- /dev/null +++ b/conformance/results/pytype/generics_self_protocols.toml @@ -0,0 +1,6 @@ +conformant = "Partial" +notes = """ +Does not reject protocol compatibility due to method `Self` return type. +""" +output = """ +""" diff --git a/conformance/results/pytype/generics_self_usage.toml b/conformance/results/pytype/generics_self_usage.toml new file mode 100644 index 000000000..0326533e3 --- /dev/null +++ b/conformance/results/pytype/generics_self_usage.toml @@ -0,0 +1,31 @@ +conformant = "Unsupported" +notes = """ +Does not understand `Self` type. +""" +output = """ +File "generics_self_usage.py", line 58, in foo: Invalid type annotation 'Self' [invalid-annotation] + Cannot use 'typing.Self' outside of a class +File "generics_self_usage.py", line 73, in : Invalid type annotation 'Self' [invalid-annotation] + Cannot use 'typing.Self' outside of a class +File "generics_self_usage.py", line 76, in : Invalid type annotation 'Self' for bar [invalid-annotation] + TypeVar(s) 'Self' not in scope +File "generics_self_usage.py", line 82, in has_existing_self_annotation: bad return type [bad-return-type] + Expected: Foo2 + Actually returned: None +File "generics_self_usage.py", line 86, in return_concrete_type: bad return type [bad-return-type] + Expected: Foo3Child + Actually returned: Foo3 +Called from (traceback): + line 92, in child_method +File "generics_self_usage.py", line 103, in : Invalid base class: Self [base-class-error] +File "generics_self_usage.py", line 111, in Base: Invalid type annotation 'Self' [invalid-annotation] + Cannot use 'typing.Self' outside of a class +File "generics_self_usage.py", line 116, in Base: Invalid type annotation 'Self' [invalid-annotation] + Cannot use 'typing.Self' outside of a class +File "generics_self_usage.py", line 122, in __new__: bad return type [bad-return-type] + Expected: MyMetaclass + Actually returned: None +File "generics_self_usage.py", line 126, in __mul__: bad return type [bad-return-type] + Expected: List[MyMetaclass] + Actually returned: None +""" diff --git a/conformance/results/pytype/literals_interactions.toml b/conformance/results/pytype/literals_interactions.toml new file mode 100644 index 000000000..6b3e93b19 --- /dev/null +++ b/conformance/results/pytype/literals_interactions.toml @@ -0,0 +1,48 @@ +conformant = "Partial" +notes = """ +Incorrectly rejects some legal Literal annotations. +Does not reject some illegal Literal annotations. +Does not use Literal to distinguish overloads. +Does not narrow based on `x is Literal` type guard pattern. +Does not narrow based on `x == Literal` type guard pattern. +""" +output = """ +File "literals_interactions.py", line 11, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'int' at index 0 +File "literals_interactions.py", line 46, in open: bad return type [bad-return-type] + Expected: IO[str] + Actually returned: None +Called from (traceback): + line 49, in current file +File "literals_interactions.py", line 46, in open: bad return type [bad-return-type] + Expected: IO[bytes] + Actually returned: None +Called from (traceback): + line 50, in current file +File "literals_interactions.py", line 46, in open: bad return type [bad-return-type] + Expected: IO + Actually returned: None +Called from (traceback): + line 51, in current file +File "literals_interactions.py", line 61, in __add__: bad return type [bad-return-type] + Expected: Matrix[int, int] + Actually returned: None +File "literals_interactions.py", line 64, in __matmul__: bad return type [bad-return-type] + Expected: Matrix[int, int] + Actually returned: None +File "literals_interactions.py", line 67, in transpose: bad return type [bad-return-type] + Expected: Matrix[int, int] + Actually returned: None +File "literals_interactions.py", line 72, in func2: Matrix[Any, int] [assert-type] + Expected: Matrix[int, int] + Actual: Matrix[Any, int] +File "literals_interactions.py", line 93, in parse_status1: Union[Status, str] [assert-type] + Expected: str + Actual: Union[Status, str] +File "literals_interactions.py", line 106, in parse_status2: Function expects_bad_status was called with the wrong arguments [wrong-arg-types] + Expected: (status: Literal['ABORTED', 'MALFORMED']) + Actually passed: (status: str) +File "literals_interactions.py", line 109, in parse_status2: Function expects_pending_status was called with the wrong arguments [wrong-arg-types] + Expected: (status: Literal['PENDING']) + Actually passed: (status: str) +""" diff --git a/conformance/results/pytype/literals_literalstring.toml b/conformance/results/pytype/literals_literalstring.toml new file mode 100644 index 000000000..0ffdbe488 --- /dev/null +++ b/conformance/results/pytype/literals_literalstring.toml @@ -0,0 +1,6 @@ +conformant = "Unsupported" +notes = """ +Does not understand `LiteralString` special form. +""" +output = """ +""" diff --git a/conformance/results/pytype/literals_parameterizations.toml b/conformance/results/pytype/literals_parameterizations.toml new file mode 100644 index 000000000..f24946a15 --- /dev/null +++ b/conformance/results/pytype/literals_parameterizations.toml @@ -0,0 +1,41 @@ +conformant = "Unsupported" +notes = """ +Does not understand `Literal` type annotation. +""" +output = """ +File "literals_parameterizations.py", line 17, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'int' at index 0 +File "literals_parameterizations.py", line 18, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'int' at index 0 +File "literals_parameterizations.py", line 19, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'int' at index 0 +File "literals_parameterizations.py", line 32, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'Union' at index 0 + Bad parameter 'Union' at index 1 + Bad parameter 'Union' at index 2 + Bad parameter 'Union' at index 3 +File "literals_parameterizations.py", line 34, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'Union' at index 0 +File "literals_parameterizations.py", line 41, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'str' at index 0 +File "literals_parameterizations.py", line 42, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'complex' at index 0 +File "literals_parameterizations.py", line 46, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'dict' at index 0 +File "literals_parameterizations.py", line 47, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'int' at index 0 +File "literals_parameterizations.py", line 49, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'T' at index 0 +File "literals_parameterizations.py", line 50, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'float' at index 0 +File "literals_parameterizations.py", line 52, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter '...' at index 0 +File "literals_parameterizations.py", line 59, in : Invalid type annotation 'Literal[my_function]' [invalid-annotation] + Invalid type annotation 'Literal' + Bad parameter 'my_function' at index 0 +File "literals_parameterizations.py", line 59, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'my_function' at index 0 +File "literals_parameterizations.py", line 63, in func2: Type annotation for x1 does not match type of assignment [annotation-type-mismatch] + Annotation: Literal['Color.RED'] + Assignment: Color +""" diff --git a/conformance/results/pytype/literals_semantics.toml b/conformance/results/pytype/literals_semantics.toml new file mode 100644 index 000000000..e33ebf37b --- /dev/null +++ b/conformance/results/pytype/literals_semantics.toml @@ -0,0 +1,25 @@ +conformant = "Unsupported" +notes = """ +Does not understand `Literal` type annotation. +""" +output = """ +File "literals_semantics.py", line 10, in : Type annotation for v2 does not match type of assignment [annotation-type-mismatch] + Annotation: Literal[3] + Assignment: Literal[4] +File "literals_semantics.py", line 12, in : Invalid type annotation 'L[-3]' [invalid-annotation] + Invalid type annotation 'Literal' + Bad parameter 'int' at index 0 +File "literals_semantics.py", line 12, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'int' at index 0 +File "literals_semantics.py", line 16, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'int' at index 0 +File "literals_semantics.py", line 17, in func1: Invalid type annotation 'Literal[20]' [invalid-annotation] + Invalid type annotation 'Literal' + Bad parameter 'int' at index 0 +File "literals_semantics.py", line 18, in func1: Invalid type annotation 'Literal[20]' [invalid-annotation] + Invalid type annotation 'Literal' + Bad parameter 'int' at index 0 +File "literals_semantics.py", line 19, in func1: Invalid type annotation 'Literal[20]' [invalid-annotation] + Invalid type annotation 'Literal' + Bad parameter 'int' at index 0 +""" diff --git a/conformance/results/pytype/narrowing_typeguard.toml b/conformance/results/pytype/narrowing_typeguard.toml new file mode 100644 index 000000000..699b0799f --- /dev/null +++ b/conformance/results/pytype/narrowing_typeguard.toml @@ -0,0 +1,6 @@ +conformant = "Partial" +notes = """ +Does not reject TypeGuard method with too few parameters. +""" +output = """ +""" diff --git a/conformance/results/pytype/typeddicts_alt_syntax.toml b/conformance/results/pytype/typeddicts_alt_syntax.toml new file mode 100644 index 000000000..5bc176026 --- /dev/null +++ b/conformance/results/pytype/typeddicts_alt_syntax.toml @@ -0,0 +1,14 @@ +conformant = "Partial" +notes = """ +Does not reject use of variable as second argument to `TypedDict` call. +Does not report when name of TypedDict doesn't match assigned identifier name. +Does not support keyword-argument form of alternative syntax (deprecated in 3.11). +""" +output = """ +File "typeddicts_alt_syntax.py", line 27, in : Function typing.TypedDict was called with the wrong arguments [wrong-arg-types] + Expected: (name, fields: dict, ...) + Actually passed: (name, fields: Dict[int, Type[str]]) +File "typeddicts_alt_syntax.py", line 41, in : Function typing.TypedDict expects 2 arg(s), got 3 [wrong-arg-count] + Expected: (name, fields, *, total) + Actually passed: (name, name, year) +""" diff --git a/conformance/results/pytype/typeddicts_class_syntax.toml b/conformance/results/pytype/typeddicts_class_syntax.toml new file mode 100644 index 000000000..b187f00c5 --- /dev/null +++ b/conformance/results/pytype/typeddicts_class_syntax.toml @@ -0,0 +1,8 @@ +conformant = "Partial" +notes = """ +Does not reject methods within TypedDict class. +Does not report when metaclass is provided. +Does not report when other keyword argument is provided. +""" +output = """ +""" diff --git a/conformance/results/pytype/typeddicts_final.toml b/conformance/results/pytype/typeddicts_final.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pytype/typeddicts_final.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pytype/typeddicts_inheritance.toml b/conformance/results/pytype/typeddicts_inheritance.toml new file mode 100644 index 000000000..6570b22c7 --- /dev/null +++ b/conformance/results/pytype/typeddicts_inheritance.toml @@ -0,0 +1,9 @@ +conformant = "Pass" +output = """ +File "typeddicts_inheritance.py", line 44, in : Invalid base class: NonTypedDict [base-class-error] + TypedDict BadTypedDict cannot inherit from a non-TypedDict class. +File "typeddicts_inheritance.py", line 54, in : Invalid base class: X1 [base-class-error] + Duplicate TypedDict key x in classes X1 and Y1 +File "typeddicts_inheritance.py", line 65, in : Invalid base class: Y2 [base-class-error] + Duplicate TypedDict key x in classes Y2 and X2 +""" diff --git a/conformance/results/pytype/typeddicts_operations.toml b/conformance/results/pytype/typeddicts_operations.toml new file mode 100644 index 000000000..8f53a4408 --- /dev/null +++ b/conformance/results/pytype/typeddicts_operations.toml @@ -0,0 +1,24 @@ +conformant = "Partial" +notes = """ +Does not report type violation with TypedDict value assignment. +Does not report reference to unknown key in TypedDict. +Does not reject `clear` method on TypedDict with required keys. +Does not reject delete operation for required key in TypedDict. +""" +output = """ +File "typeddicts_operations.py", line 28, in : Type annotation for movie does not match type of assignment [annotation-type-mismatch] + Annotation: Movie(TypedDict) + Assignment: Dict[str, str] +File "typeddicts_operations.py", line 29, in : Type annotation for movie does not match type of assignment [annotation-type-mismatch] + Annotation: Movie(TypedDict) + Assignment: Dict[str, Union[float, str]] +File "typeddicts_operations.py", line 32, in : Type annotation for movie does not match type of assignment [annotation-type-mismatch] + Annotation: Movie(TypedDict) + Assignment: Dict[str, Union[int, str]] +File "typeddicts_operations.py", line 37, in func1: Type annotation for movie does not match type of assignment [annotation-type-mismatch] + Annotation: Movie(TypedDict) + Assignment: Dict[str, Union[int, str]] +File "typeddicts_operations.py", line 60, in : str [assert-type] + Expected: Optional[str] + Actual: str +""" diff --git a/conformance/results/pytype/typeddicts_required.toml b/conformance/results/pytype/typeddicts_required.toml new file mode 100644 index 000000000..bdedbc7d6 --- /dev/null +++ b/conformance/results/pytype/typeddicts_required.toml @@ -0,0 +1,8 @@ +conformant = "Partial" +notes = """ +Does not reject use of `Required` in non-TypedDict class. +Does not reject use of `Required` in function parameter annotation. +Does not reject nested use of `Required` in type annotation. +""" +output = """ +""" diff --git a/conformance/results/pytype/typeddicts_type_consistency.toml b/conformance/results/pytype/typeddicts_type_consistency.toml new file mode 100644 index 000000000..b80ed1d7a --- /dev/null +++ b/conformance/results/pytype/typeddicts_type_consistency.toml @@ -0,0 +1,20 @@ +conformant = "Partial" +notes = """ +Does not report some type violations for TypedDict type compatibility. +Incorrectly reports type violation in cases where there is none. +Does not report type incompatibility between TypedDict and `dict[str, Any]`. +""" +output = """ +File "typeddicts_type_consistency.py", line 62, in : Type annotation for a3 does not match type of assignment [annotation-type-mismatch] + Annotation: A3(TypedDict) + Assignment: B3 +File "typeddicts_type_consistency.py", line 65, in : Type annotation for b3 does not match type of assignment [annotation-type-mismatch] + Annotation: B3(TypedDict) + Assignment: Dict[str, int] +File "typeddicts_type_consistency.py", line 69, in : Type annotation for a3_1 does not match type of assignment [annotation-type-mismatch] + Annotation: A3(TypedDict) + Assignment: Dict[str, int] +File "typeddicts_type_consistency.py", line 124, in : Type annotation for o2 does not match type of assignment [annotation-type-mismatch] + Annotation: Outer1(TypedDict) + Assignment: Dict[str, Dict[str, Dict[str, int]]] +""" diff --git a/conformance/results/pytype/typeddicts_usage.toml b/conformance/results/pytype/typeddicts_usage.toml new file mode 100644 index 000000000..41139d698 --- /dev/null +++ b/conformance/results/pytype/typeddicts_usage.toml @@ -0,0 +1,14 @@ +conformant = "Partial" +notes = """ +Does not report errant use of TypedDict in `isinstance` call. +Does not reject use of TypedDict as TypeVar bound. +""" +output = """ +File "typeddicts_usage.py", line 23, in : TypedDict Movie does not contain key director [typed-dict-error] +File "typeddicts_usage.py", line 24, in : Type annotation for key year in TypedDict Movie does not match type of assignment [annotation-type-mismatch] + Annotation: int + Assignment: str +File "typeddicts_usage.py", line 28, in : Type annotation for movie2 does not match type of assignment [annotation-type-mismatch] + Annotation: Movie(TypedDict) + Assignment: Dict[str, Union[int, str]] +""" diff --git a/conformance/results/pytype/version.toml b/conformance/results/pytype/version.toml new file mode 100644 index 000000000..6bfa30f74 --- /dev/null +++ b/conformance/results/pytype/version.toml @@ -0,0 +1,2 @@ +version = "pytype 2023.12.18" +test_duration = 51.5667941570282 diff --git a/conformance/results/results.html b/conformance/results/results.html new file mode 100644 index 000000000..b33903903 --- /dev/null +++ b/conformance/results/results.html @@ -0,0 +1,431 @@ + + + + + + + Type System Test Results + + + + +
+
+

Python Type System Conformance Test Results

+
+
mypy 1.8.0(0.40sec) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Type annotations
     annotations_typeexprPass
+Generics
     generics_self_advancedPartialDoes not infer the type of an unannotated `self` parameter to be type `Self`.
Does not retain `Self` when calling method that returns `Self`.
Does not infer the type of an unannotated `cls` parameter to be type `type[Self]`.
Does not retain `Self` when accessing attribute through `type[Self]`.
     generics_self_attributesPass
     generics_self_basicPartialDoes not properly handle constructor call through `cls` parameter.
     generics_self_protocolsPass
     generics_self_usagePass
+Type aliases
     aliases_explicitPartialDoes not reject specialization of type alias that has already been implicitly specialized.
     aliases_implicitPass
     aliases_newtypePass
     aliases_recursivePass
     aliases_type_statementUnsupportedDoes not support `type` statement.
     aliases_typealiastypeUnsupportedSupport for TypeAliasType is not implemented.
     aliases_variancePass
+Literals
     literals_interactionsPartialDoes not narrow type of `x` with `x in Literal` type guard pattern.
     literals_literalstringUnsupportedSupport for `LiteralString` is not implemented.
     literals_parameterizationsPartialDoes not reject tuple within Literal.
     literals_semanticsPass
+Callables
     callables_annotationPass
     callables_kwargsPass
     callables_protocolPass
+Dataclasses
     dataclasses_descriptorsPartialDoes not correctly evaluate type of descriptor access.
     dataclasses_frozenPass
     dataclasses_hashPartialDoes not report when dataclass is not compatible with Hashable protocol.
     dataclasses_inheritancePass
     dataclasses_kwonlyPartialIncorrectly rejects kw_only field with default before positional field.
     dataclasses_orderPass
     dataclasses_postinitPass
     dataclasses_slotsPartialDoes not reject write to instance variable that is not defined in __slots__.
     dataclasses_transform_classPass
     dataclasses_transform_fieldPartialDoes not properly handle field constructor that has default value for `kw_only` or `init` parameter.
     dataclasses_transform_funcPartialDoes not handle `kw_only=False` override when `kw_only_default=True`.
Does not report error when `order=False` and comparison operators are used.
     dataclasses_transform_metaPass
     dataclasses_usagePass
+Typed dictionaries
     typeddicts_alt_syntaxPartialDoes not support keyword-argument form of alternative syntax (deprecated in 3.11).
     typeddicts_class_syntaxPass
     typeddicts_finalPass
     typeddicts_inheritancePass
     typeddicts_operationsPass
     typeddicts_requiredPartialDoes not support nesting of `Annotated` and `Required` or `NotRequired`.
     typeddicts_type_consistencyPass
     typeddicts_usagePass
+Type narrowing
     narrowing_typeguardPass
+
pyright 1.1.343(1.01sec) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Type annotations
     annotations_typeexprPass
+Generics
     generics_self_advancedPass
     generics_self_attributesPass
     generics_self_basicPass
     generics_self_protocolsPass
     generics_self_usagePass
+Type aliases
     aliases_explicitPartialIncorrectly evaluates type of specialized type alias parameterized with ParamSpec.
Allows some illegal annotation forms to be interpreted as valid type aliases.
     aliases_implicitPartialIncorrectly evaluates type of specialized type alias parameterized with ParamSpec.
Allows some illegal annotation forms to be interpreted as valid type aliases.
     aliases_newtypePartialDoes not reject use of NewType in `isinstance` call.
Does not report inconsistency between name of NewType and assigned identifier name.
Does not reject use of NewType with TypedDict class.
Does not reject use of NewType with another NewType.
Does not reject use of NewType with Any.
     aliases_recursivePass
     aliases_type_statementPartialDoes not reject binary expression when used in type alias definition.
     aliases_typealiastypePartialDoes not reject type alias expression that uses TypeVar that is not in scope and not in `type_params`.
Does not allow access to `__value__` attribute of type alias.
Allows some illegal annotation forms to be interpreted as valid type aliases.
     aliases_variancePass
+Literals
     literals_interactionsPass
     literals_literalstringPass
     literals_parameterizationsPass
     literals_semanticsPass
+Callables
     callables_annotationPass
     callables_kwargsPass
     callables_protocolPartialDoes not report type incompatibility for callback protocol with positional-only parameters.
+Dataclasses
     dataclasses_descriptorsPass
     dataclasses_frozenPass
     dataclasses_hashPass
     dataclasses_inheritancePass
     dataclasses_kwonlyPass
     dataclasses_orderPass
     dataclasses_postinitPartialReports incorrect error for incompatible `__post_init__` method override.
     dataclasses_slotsPass
     dataclasses_transform_classPass
     dataclasses_transform_fieldPass
     dataclasses_transform_funcPass
     dataclasses_transform_metaPass
     dataclasses_usagePass
+Typed dictionaries
     typeddicts_alt_syntaxPass
     typeddicts_class_syntaxPass
     typeddicts_finalPass
     typeddicts_inheritancePass
     typeddicts_operationsPass
     typeddicts_requiredPass
     typeddicts_type_consistencyPass
     typeddicts_usagePass
+Type narrowing
     narrowing_typeguardPass
+
pyre 0.9.19(1.40sec) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Type annotations
     annotations_typeexprPass
+Generics
     generics_self_advancedUnsupportedDoes not understand `Self` type.
     generics_self_attributesUnsupportedDoes not understand `Self` type.
     generics_self_basicUnsupportedDoes not understand `Self` type.
     generics_self_protocolsPartialDoes not reject protocol compatibility due to method `Self` return type.
     generics_self_usageUnsupportedDoes not understand `Self` type.
+Type aliases
     aliases_explicitPartialIncorrectly reports error for type alias defined with ParamSpec.
Incorrectly rejects some valid type aliases when used in annotations.
Incorrectly evaluates generic type alias with ParamSpec with missing type argument.
Does not report some illegal annotation forms as invalid type aliases.
Does not report invalid specialization of generic type aliases.
Incorrectly rejects import alias of `TypeAlias` when used to define type alias.
Does not report invalid specialization of already-specialized generic type alias.
     aliases_implicitPartialIncorrectly reports error for type alias defined with ParamSpec.
Incorrectly rejects some valid type aliases when used in annotations.
Incorrectly evaluates generic type alias with ParamSpec with missing type argument.
Does not report invalid specialization of generic type aliases.
Does not report error for attempt to instantiate union type alias.
Does not report invalid specialization of already-specialized generic type alias.
     aliases_newtypePartialDoes not reject use of NewType in `isinstance` call.
Does not reject use of NewType in class definition statement.
Does not report inconsistency between name of NewType and assigned identifier name.
Does not reject use of NewType with generic class with TypeVar.
Does not reject use of NewType with protocol class.
Does not reject use of NewType with TypedDict class.
Does not reject use of NewType with another NewType.
Does not reject use of NewType with Any.
     aliases_recursivePartialDoes not properly handle some recursive type aliases.
Does not properly handle specialization of generic recursive type aliases.
     aliases_type_statementUnsupportedDoes not support `type` statement.
     aliases_typealiastypeUnsupportedSupport for TypeAliasType is not implemented.
     aliases_variancePass
+Literals
     literals_interactionsPartialDoes not detect out-of-bound tuple literal index.
Does not narrow type of `x` with `x in Literal` type guard pattern.
Does not narrow type of `x` with `x == Literal` type guard pattern.
     literals_literalstringPass
     literals_parameterizationsPartialDoes not support type aliases in Literal type expression.
Does not support nested Literal type expression.
Does not reject unary + operator in Literal type expression.
Does not reject tuple in Literal type expression.
Does not reject "bare" Literal in type expression.
     literals_semanticsPartialDoes not reject augmented operation that modifies literal value.
+Callables
     callables_annotationPartialDoes not evaluate correct type for `*args: int` parameter.
Does not reject illegal form `Callable[[...], int]`.
     callables_kwargsUnsupportedDoes not understand Unpack in the context of **kwargs annotation.
     callables_protocolPartialDoes not correctly handle callback protocol that declares attributes in all functions.
Does not report type incompatibility for callback protocol with positional-only parameters.
Incorrectly reports type compatibility error with callback that has *args and **kwargs.
Does not report type incompatibility for callback missing a default argument for positional parameter.
Does not report type incompatibility for callback missing a default argument for keyword parameter.
+Dataclasses
     dataclasses_descriptorsPartialIncorrectly generates error when calling constructor of dataclass with descriptor.
     dataclasses_frozenPartialDoes not reject frozen dataclass inherited from non-frozen dataclass.
Does not reject non-frozen dataclass inherited from frozen dataclass.
     dataclasses_hashPartialDoes not report when dataclass is not compatible with Hashable protocol.
     dataclasses_inheritancePartialDoes not reject ClassVar that is overridden by instance variable.
Does not reject instance variable that is overridden by ClassVar.
     dataclasses_kwonlyPass
     dataclasses_orderPartialDoes not report type incompatibility with comparison operator.
     dataclasses_postinitUnsupportedDoes not perform validation of `__post_init__` method.
Does not reject access of `InitVar` from object.
     dataclasses_slotsPartialDoes not report error when `slots=True` is used with `__slots__` definition.
Does not reject write to instance variable that is not defined in __slots__.
Does not reject access to `__slots__` from dataclass instance when `slots=False`.
     dataclasses_transform_classUnsupportedDoes not understand @dataclass_transform.
     dataclasses_transform_fieldUnsupportedDoes not understand @dataclass_transform.
     dataclasses_transform_funcUnsupportedDoes not understand @dataclass_transform.
     dataclasses_transform_metaUnsupportedDoes not understand @dataclass_transform.
     dataclasses_usagePartialDoes not report error when field with no default follows field with default.
Incorrectly reports error with InitVar that has default value.
+Typed dictionaries
     typeddicts_alt_syntaxPartialDoes not report when name of TypedDict doesn't match assigned identifier name.
Does not support keyword-argument form of alternative syntax (deprecated in 3.11).
     typeddicts_class_syntaxPartialDoes not reject methods within TypedDict class.
Does not report when metaclass is provided.
Does not report when other keyword argument is provided.
Does not support generic TypedDict class.
     typeddicts_finalPartialDoes not handle value with literal type as index to TypedDict object.
     typeddicts_inheritancePartialDoes not reject TypedDict class that inherits from non-TypedDict class.
     typeddicts_operationsPass
     typeddicts_requiredPartialDoes not reject use of `Required` in function parameter annotation.
Does not reject nested use of `Required` in type annotation.
Does not support recursive TypedDict definitions.
     typeddicts_type_consistencyPartialDoes not reject assignment of TypedDict with missing key.
Does not return non-Optional value from `get` method for required key.
Does not properly handle nested TypedDicts.
     typeddicts_usagePartialDoes not report errant use of TypedDict in `isinstance` call.
Does not reject use of TypedDict as TypeVar bound.
+Type narrowing
     narrowing_typeguardPartialDoes not support `tuple` in `assert_type` call.
Does not reject TypeGuard method with too few parameters.
+
pytype 2023.12.18(51.57sec) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Type annotations
     annotations_typeexprPartialDoes not reject call expressions in type annotation.
Does not reject call lambda expression in type annotation.
Does not reject list expression in type annotation.
Does not reject ternary expression in type annotation.
Does not reject f-string in type annotation.
+Generics
     generics_self_advancedUnsupportedDoes not understand `Self` type.
     generics_self_attributesUnsupportedDoes not understand `Self` type.
     generics_self_basicUnsupportedDoes not understand `Self` type.
     generics_self_protocolsPartialDoes not reject protocol compatibility due to method `Self` return type.
     generics_self_usageUnsupportedDoes not understand `Self` type.
+Type aliases
     aliases_explicitPartialIncorrectly reports error for type alias defined with ParamSpec.
Does not report invalid specialization of generic type alias with bound TypeVar.
Incorrectly evaluates generic type alias with ParamSpec with missing type argument.
Does not report some illegal annotation forms as invalid type aliases.
Does not report invalid specialization of already-specialized generic type alias.
     aliases_implicitPartialIncorrectly reports error for type alias defined with ParamSpec.
Does not report invalid specialization of generic type alias with bound TypeVar.
Incorrectly evaluates generic type alias with ParamSpec with missing type argument.
Allows some illegal annotation forms to be interpreted as valid type aliases.
Does not report invalid specialization of already-specialized generic type alias.
     aliases_newtypePartialDoes not reject use of NewType in `isinstance` call.
Does not reject use of NewType in class definition statement.
Does not report inconsistency between name of NewType and assigned identifier name.
Does not reject use of NewType with generic class with TypeVar.
Does not reject use of NewType with protocol class.
Does not reject use of NewType with TypedDict class.
Does not reject use of NewType with another NewType.
Does not reject use of NewType with Any.
     aliases_recursivePartialDoes not detect type violation for some deeply-nested types.
Does not properly handle `|` for unions in some recursive type alias definitions.
Does not detect cyclical references in recursive type alias definition.
     aliases_type_statementUnsupportedDoes not support `type` statement.
     aliases_typealiastypeUnsupportedSupport for TypeAliasType is not implemented.
     aliases_varianceUnsupportedDoes not detect variance incompatibility.
+Literals
     literals_interactionsPartialIncorrectly rejects some legal Literal annotations.
Does not reject some illegal Literal annotations.
Does not use Literal to distinguish overloads.
Does not narrow based on `x is Literal` type guard pattern.
Does not narrow based on `x == Literal` type guard pattern.
     literals_literalstringUnsupportedDoes not understand `LiteralString` special form.
     literals_parameterizationsUnsupportedDoes not understand `Literal` type annotation.
     literals_semanticsUnsupportedDoes not understand `Literal` type annotation.
+Callables
     callables_annotationPass
     callables_kwargsUnsupportedDoes not understand Unpack in the context of **kwargs annotation.
     callables_protocolUnsupportedDoes not properly handle type compatibility checks with callback protocols.
+Dataclasses
     dataclasses_descriptorsUnsupportedDoes not understand descriptor objects in dataclass.
     dataclasses_frozenUnsupportedDoes not report assignment to field within frozen dataclass instance.
Does not reject frozen dataclass inherited from non-frozen dataclass.
Does not reject non-frozen dataclass inherited from frozen dataclass.
     dataclasses_hashPartialDoes not report when dataclass is not compatible with Hashable protocol.
     dataclasses_inheritancePartialDoes not reject ClassVar that is overridden by instance variable.
Does not reject instance variable that is overridden by ClassVar.
     dataclasses_kwonlyPartialIncorrectly reports error when kw_only field has default value.
Incorrectly rejects kw_only field with default before positional field.
     dataclasses_orderPartialDoes not report type incompatibility with comparison operator.
     dataclasses_postinitPartialDoes not validate `__post_init__` method.
Reports incorrect error for incompatible `__post_init__` method override.
     dataclasses_slotsPartialDoes not report error when `slots=True` is used with `__slots__` definition.
Does not reject write to instance variable that is not defined in __slots__.
Incorrectly reports error when accessing `__slots__` when `slots=True`.
     dataclasses_transform_classUnsupportedDoes not understand @dataclass_transform.
     dataclasses_transform_fieldUnsupportedDoes not understand @dataclass_transform.
     dataclasses_transform_funcUnsupportedDoes not understand @dataclass_transform.
     dataclasses_transform_metaUnsupportedDoes not understand @dataclass_transform.
     dataclasses_usagePass
+Typed dictionaries
     typeddicts_alt_syntaxPartialDoes not reject use of variable as second argument to `TypedDict` call.
Does not report when name of TypedDict doesn't match assigned identifier name.
Does not support keyword-argument form of alternative syntax (deprecated in 3.11).
     typeddicts_class_syntaxPartialDoes not reject methods within TypedDict class.
Does not report when metaclass is provided.
Does not report when other keyword argument is provided.
     typeddicts_finalPass
     typeddicts_inheritancePass
     typeddicts_operationsPartialDoes not report type violation with TypedDict value assignment.
Does not report reference to unknown key in TypedDict.
Does not reject `clear` method on TypedDict with required keys.
Does not reject delete operation for required key in TypedDict.
     typeddicts_requiredPartialDoes not reject use of `Required` in non-TypedDict class.
Does not reject use of `Required` in function parameter annotation.
Does not reject nested use of `Required` in type annotation.
     typeddicts_type_consistencyPartialDoes not report some type violations for TypedDict type compatibility.
Incorrectly reports type violation in cases where there is none.
Does not report type incompatibility between TypedDict and `dict[str, Any]`.
     typeddicts_usagePartialDoes not report errant use of TypedDict in `isinstance` call.
Does not reject use of TypedDict as TypeVar bound.
+Type narrowing
     narrowing_typeguardPartialDoes not reject TypeGuard method with too few parameters.
+ + +
+ + + \ No newline at end of file diff --git a/conformance/src/__init__.py b/conformance/src/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/conformance/src/main.py b/conformance/src/main.py new file mode 100644 index 000000000..be474bac0 --- /dev/null +++ b/conformance/src/main.py @@ -0,0 +1,129 @@ +""" +Type system conformance test for static type checkers. +""" + +import os +from pathlib import Path +import sys +from time import time +from typing import Sequence + +import tomli +import tomlkit + +from reporting import generate_summary +from test_groups import get_test_cases, get_test_groups +from type_checker import TYPE_CHECKERS, TypeChecker + + +def run_tests( + root_dir: Path, + type_checker: TypeChecker, + test_cases: Sequence[Path], +): + print(f"Running tests for {type_checker.name}") + + test_start_time = time() + tests_output = type_checker.run_tests([file.name for file in test_cases]) + test_duration = time() - test_start_time + + results_dir = root_dir / "results" / type_checker.name + + for test_case in test_cases: + update_output_for_test( + type_checker, results_dir, test_case, tests_output.get(test_case.name, "") + ) + + update_type_checker_info(type_checker, root_dir, test_duration) + + +def update_output_for_test( + type_checker: TypeChecker, + results_dir: Path, + test_case: Path, + output: str, +): + test_name = test_case.stem + output = f"\n{output}" + + results_file = results_dir / f"{test_name}.toml" + results_file.parent.mkdir(parents=True, exist_ok=True) + + # Read the existing results file if present. + try: + with open(results_file, "rb") as f: + existing_results = tomli.load(f) + except FileNotFoundError: + existing_results = {} + + old_output = existing_results.get("output", None) + old_output = f"\n{old_output}" + + # Did the type checker output change since last time the + # test was run? + if old_output != output: + print(f"Output changed for {test_name} when running {type_checker.name}") + print(f"Old output: {old_output}") + print(f"New output: {output}") + print("") + + # Use multiline formatting for any strings that contain newlines. + for key, value in existing_results.items(): + if isinstance(value, str) and "\n" in value: + existing_results[key] = tomlkit.string(f"\n{value}", multiline=True) + + existing_results["output"] = tomlkit.string(output, multiline=True) + + results_file.parent.mkdir(parents=True, exist_ok=True) + with open(results_file, "w") as f: + tomlkit.dump(existing_results, f) + + +def update_type_checker_info(type_checker: TypeChecker, root_dir: Path, test_duration: float): + # Record the version of the type checker used for the latest run. + version_file = root_dir / "results" / type_checker.name / "version.toml" + + # Read the existing version file if present. + try: + with open(version_file, "rb") as f: + existing_info = tomli.load(f) + except FileNotFoundError: + existing_info = {} + + existing_info["version"] = type_checker.get_version() + existing_info["test_duration"] = test_duration + + version_file.parent.mkdir(parents=True, exist_ok=True) + with open(version_file, "w") as f: + tomlkit.dump(existing_info, f) + + +def main(): + # Some tests cover features that are available only in the + # latest version of Python (3.12), so we need this version. + assert sys.version_info >= (3, 12) + + root_dir = Path(__file__).resolve().parent.parent + + tests_dir = root_dir / "tests" + assert tests_dir.is_dir() + + test_groups = get_test_groups(root_dir) + test_cases = get_test_cases(test_groups, tests_dir) + + # Switch to the tests directory. + os.chdir(tests_dir) + + # Run each test case with each type checker. + for type_checker in TYPE_CHECKERS: + if not type_checker.install(): + print(f'Skipping tests for {type_checker.name}') + else: + run_tests(root_dir, type_checker, test_cases) + + # Generate a summary report. + generate_summary(root_dir) + + +if __name__ == "__main__": + main() diff --git a/conformance/src/reporting.py b/conformance/src/reporting.py new file mode 100644 index 000000000..95c9c14c3 --- /dev/null +++ b/conformance/src/reporting.py @@ -0,0 +1,104 @@ +""" +Generates a summary of the type checker conformant tests. +""" + +from pathlib import Path + +import tomli + +from test_groups import get_test_cases, get_test_groups +from type_checker import TYPE_CHECKERS + + +def generate_summary(root_dir: Path): + print('Generating summary report') + template_file = root_dir / "src" / "results_template.html" + with open(template_file, "r") as f: + template = f.read() + + summary = template.replace("{{summary}}", generate_summary_html(root_dir)) + + results_file = root_dir / "results" / "results.html" + + with open(results_file, "w") as f: + f.write(summary) + + +def generate_summary_html(root_dir: Path): + test_groups = get_test_groups(root_dir) + test_cases = get_test_cases(test_groups, root_dir / "tests") + + summary_html = "" + + for type_checker in TYPE_CHECKERS: + # Load the version file for the type checker. + version_file = root_dir / "results" / type_checker.name / "version.toml" + + try: + with open(version_file, "rb") as f: + existing_info = tomli.load(f) + except FileNotFoundError: + existing_info = {} + + version = existing_info["version"] or "Unknown version" + test_duration = existing_info.get("test_duration") + + summary_html += f"
{version}" + if test_duration is not None: + summary_html += f"({test_duration:.2f}sec)\n" + summary_html += '
\n' + summary_html += '
\n' + summary_html += '\n' + + for test_group_name, test_group in test_groups.items(): + tests_in_group = [ + case + for case in test_cases + if case.name.startswith(f"{test_group_name}_") + ] + + tests_in_group.sort(key=lambda x: x.name) + + # Are there any test cases in this group? + if len(tests_in_group) > 0: + summary_html += '\n" + + for test_case in tests_in_group: + test_case_name = test_case.stem + + try: + results_file = ( + root_dir + / "results" + / type_checker.name + / f"{test_case_name}.toml" + ) + with open(results_file, "rb") as f: + results = tomli.load(f) + except FileNotFoundError: + results = {} + + conformance = results.get("conformant", "Unknown") + notes = results.get("notes", "").replace("\n", "
") + + conformance_class = ( + "conformant" + if conformance == "Pass" + else "partially-conformant" + if conformance == "Partial" + else "not-conformant" + ) + + summary_html += f"" + summary_html += f'' + summary_html += f'' + summary_html += f'\n' + + # Add spacer row after this group to help with readability. + summary_html += '\n' + + summary_html += "
\n' + summary_html += f'{test_group.name}' + summary_html += "
     {test_case_name}{conformance}{notes}
\n" + + return summary_html diff --git a/conformance/src/results_template.html b/conformance/src/results_template.html new file mode 100644 index 000000000..e15ada5bc --- /dev/null +++ b/conformance/src/results_template.html @@ -0,0 +1,147 @@ + + + + + + + Type System Test Results + + + + +
+
+

Python Type System Conformance Test Results

+
+ {{summary}} + +
+ + + \ No newline at end of file diff --git a/conformance/src/test_groups.py b/conformance/src/test_groups.py new file mode 100644 index 000000000..f0145ff11 --- /dev/null +++ b/conformance/src/test_groups.py @@ -0,0 +1,46 @@ +""" +Reads a template file that describes groups of tests in the +conformance test suite. +""" + +from dataclasses import dataclass +from pathlib import Path +from typing import Mapping, Sequence + +import tomli + + +@dataclass +class TestGroup: + name: str + href: str + + +def get_test_groups(root_dir: Path) -> Mapping[str, TestGroup]: + # Read the TOML file that defines the test groups. Each test + # group has a name that associated test cases must start with. + test_group_file = root_dir / "src" / "test_groups.toml" + with open(test_group_file, "rb") as f: + test_groups = tomli.load(f) + + return { + k: TestGroup(v.get("name", "unknown"), v.get("href", "")) + for k, v in test_groups.items() + } + + +def get_test_cases( + test_groups: Mapping[str, TestGroup], tests_dir: Path +) -> Sequence[Path]: + test_group_names = test_groups.keys() + + # Filter test cases based on test group names. Files that do + # not begin with a known test group name are assumed to be + # files that support one or more tests. + test_cases = [ + p + for p in Path(tests_dir).glob("*.py") + if p.name.split("_")[0] in test_group_names + ] + + return test_cases diff --git a/conformance/src/test_groups.toml b/conformance/src/test_groups.toml new file mode 100644 index 000000000..e7a8ae629 --- /dev/null +++ b/conformance/src/test_groups.toml @@ -0,0 +1,68 @@ + +[concepts] +name = "Type system concepts" +href = "https://typing.readthedocs.io/en/latest/spec/concepts.html" + +[annotations] +name = "Type annotations" +href = "https://typing.readthedocs.io/en/latest/spec/annotations.html" + +[specialtypes] +name = "Special types in annotations" +href = "https://typing.readthedocs.io/en/latest/spec/special-types.html" + +[generics] +name = "Generics" +href = "https://typing.readthedocs.io/en/latest/spec/generics.html" + +[qualifiers] +name = "Type qualifiers" +href = "https://typing.readthedocs.io/en/latest/spec/qualifiers.html" + +[classes] +name = "Class type compatibility" +href = "https://typing.readthedocs.io/en/latest/spec/class-compat.html" + +[aliases] +name = "Type aliases" +href = "https://typing.readthedocs.io/en/latest/spec/aliases.html" + +[literals] +name = "Literals" +href = "https://typing.readthedocs.io/en/latest/spec/literal.html" + +[protocols] +name = "Protocols" +href = "https://typing.readthedocs.io/en/latest/spec/protocol.html" + +[callables] +name = "Callables" +href = "https://typing.readthedocs.io/en/latest/spec/callables.html" + +[overloads] +name = "Overloads" +href = "https://typing.readthedocs.io/en/latest/spec/overload.html" + +[dataclasses] +name = "Dataclasses" +href = "https://typing.readthedocs.io/en/latest/spec/dataclasses.html" + +[typeddicts] +name = "Typed dictionaries" +href = "https://typing.readthedocs.io/en/latest/spec/typeddict.html" + +[narrowing] +name = "Type narrowing" +href = "https://typing.readthedocs.io/en/latest/spec/narrowing.html" + +[directives] +name = "Type checker directives" +href = "https://typing.readthedocs.io/en/latest/spec/directives.html" + +[distribution] +name = "Distributing type information" +href = "https://typing.readthedocs.io/en/latest/spec/distributing.html" + +[historical] +name = "Historical and deprecated features" +href = "https://typing.readthedocs.io/en/latest/spec/historical.html" diff --git a/conformance/src/type_checker.py b/conformance/src/type_checker.py new file mode 100644 index 000000000..905d56296 --- /dev/null +++ b/conformance/src/type_checker.py @@ -0,0 +1,244 @@ +""" +Classes that abstract differences between type checkers. +""" + +from abc import ABC, abstractmethod +import json +from pathlib import Path +import re +from subprocess import PIPE, run +import sys +from typing import Sequence + + +class TypeChecker(ABC): + @property + @abstractmethod + def name(self) -> str: + """ + Returns the name of the type checker. + """ + raise NotImplementedError + + @abstractmethod + def install(self) -> bool: + """ + Ensures that the latest version of the type checker is installed. + Returns False if installation fails. + """ + raise NotImplementedError + + @abstractmethod + def get_version(self) -> str: + """ + Returns the current version string for the type checker. + """ + raise NotImplementedError + + @abstractmethod + def run_tests(self, test_files: Sequence[str]) -> dict[str, str]: + """ + Runs the type checker on the specified test file and + returns the output. + """ + raise NotImplementedError + + +class MypyTypeChecker(TypeChecker): + @property + def name(self) -> str: + return "mypy" + + def install(self) -> bool: + try: + run(f"{sys.executable} -m pip install mypy --upgrade", check=True, shell=True) + return True + except: + print('Unable to install mypy') + return False + + def get_version(self) -> str: + proc = run( + f"{sys.executable} -m mypy --version", stdout=PIPE, text=True, shell=True + ) + version = proc.stdout.strip() + + # Remove the " (compiled)" if it's present. + version = version.split(" (")[0] + return version + + def run_tests(self, test_files: Sequence[str]) -> dict[str, str]: + command = f"{sys.executable} -m mypy . --disable-error-code empty-body" + proc = run(command, stdout=PIPE, text=True, shell=True) + lines = proc.stdout.split("\n") + + # Add results to a dictionary keyed by the file name. + results_dict: dict[str, str] = {} + for line in lines: + file_name = line.split(":")[0].strip() + results_dict[file_name] = results_dict.get(file_name, "") + line + "\n" + + return results_dict + + +class PyrightTypeChecker(TypeChecker): + @property + def name(self) -> str: + return "pyright" + + def install(self) -> bool: + try: + # Install the Python wrapper if it's not installed. + run(f"{sys.executable} -m pip install pyright --upgrade", check=True, shell=True) + + # Force the Python wrapper to install node if needed + # and download the latest version of pyright. + self.get_version() + return True + except: + print('Unable to install pyright') + return False + + def get_version(self) -> str: + proc = run( + f"{sys.executable} -m pyright --version", stdout=PIPE, text=True, shell=True + ) + return proc.stdout.strip() + + def run_tests(self, test_files: Sequence[str]) -> dict[str, str]: + command = f"{sys.executable} -m pyright . --outputjson" + proc = run(command, stdout=PIPE, text=True, shell=True) + output_json = json.loads(proc.stdout) + diagnostics = output_json["generalDiagnostics"] + + # Add results to a dictionary keyed by the file name. + results_dict: dict[str, str] = {} + for diagnostic in diagnostics: + file_path = Path(diagnostic.get("file", "")) + file_name = file_path.name + line_number = diagnostic["range"]["start"]["line"] + 1 + col_number = diagnostic["range"]["start"]["character"] + 1 + severity = diagnostic["severity"] + message = diagnostic["message"] + rule = f" ({diagnostic['rule']})" if "rule" in diagnostic else "" + + line_text = f"{file_name}:{line_number}:{col_number} - {severity}: {message}{rule}\n" + results_dict[file_name] = results_dict.get(file_name, "") + line_text + + return results_dict + + +class PyreTypeChecker(TypeChecker): + @property + def name(self) -> str: + return "pyre" + + def install(self) -> bool: + try: + run(f"{sys.executable} -m pip install pyre-check --upgrade", check=True, shell=True) + + # Generate a default config file. + pyre_config = ( + '{"site_package_search_strategy": "pep561", "source_directories": ["."]}\n' + ) + with open(".pyre_configuration", "w") as f: + f.write(pyre_config) + + return True + except: + print('Unable to install pyre') + return False + + def get_version(self) -> str: + proc = run("pyre --version", stdout=PIPE, text=True, shell=True) + version = proc.stdout.strip() + version = version.replace("Client version:", "pyre") + return version + + def run_tests(self, test_files: Sequence[str]) -> dict[str, str]: + proc = run("pyre check", stdout=PIPE, text=True, shell=True) + lines = proc.stdout.split("\n") + + # Add results to a dictionary keyed by the file name. + results_dict: dict[str, str] = {} + for line in lines: + file_name = line.split(":")[0].strip() + results_dict[file_name] = results_dict.get(file_name, "") + line + "\n" + + return results_dict + + +class PytypeTypeChecker(TypeChecker): + @property + def name(self) -> str: + return "pytype" + + def install(self) -> bool: + try: + run(f"{sys.executable} -m pip install pytype --upgrade", check=True, shell=True) + return True + except: + print('Unable to install pytype on this platform') + return False + + def get_version(self) -> str: + proc = run( + f"{sys.executable} -m pytype --version", stdout=PIPE, text=True, shell=True + ) + version = proc.stdout.strip() + return f"pytype {version}" + + def run_tests(self, test_files: Sequence[str]) -> dict[str, str]: + # Specify 3.11 for now to work around the fact that pytype + # currently doesn't support 3.12 and emits an error when + # running on 3.12. + command = f"{sys.executable} -m pytype -V 3.11 -k *.py" + proc = run(command, stdout=PIPE, text=True, shell=True) + lines = proc.stdout.split("\n") + + # Add results to a dictionary keyed by the file name. + results_dict: dict[str, str] = {} + accumulated_lines: list[str] = [] + file_name: str | None = None + + def log_accumulated(): + if file_name is not None: + results_dict[file_name] = ( + results_dict.get(file_name, "") + "".join(accumulated_lines) + "\n" + ) + + for line in lines: + match = re.search(r'File "(.*?)",', line) + + if not match or match.start() != 0: + # An empty line precedes the summary for the file. Ignore + # everything after that line until we see diagnostics for + # the next file. + if line.strip() == "": + log_accumulated() + file_name = None + accumulated_lines = [] + elif file_name is not None: + accumulated_lines.append("\n" + line) + else: + log_accumulated() + + file_path = Path(match.group(1)) + file_name = file_path.name + + # Replace the full file path with the file name. + line = f'File "{file_name}",{line[match.end():]}' + accumulated_lines = [line] + + # Log the final accumulated lines. + log_accumulated() + + return results_dict + + +TYPE_CHECKERS: Sequence[TypeChecker] = ( + MypyTypeChecker(), + PyrightTypeChecker(), + PyreTypeChecker(), + PytypeTypeChecker(), +) diff --git a/conformance/tests/aliases_explicit.py b/conformance/tests/aliases_explicit.py new file mode 100644 index 000000000..a4168bb60 --- /dev/null +++ b/conformance/tests/aliases_explicit.py @@ -0,0 +1,102 @@ +""" +Tests explicit type aliases defined with `TypeAlias`. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/aliases.html#typealias + +from typing import Any, Callable, Concatenate, Literal, ParamSpec, TypeVar, Union, assert_type +from typing import TypeAlias as TA + +S = TypeVar("S") +T = TypeVar("T") +P = ParamSpec("P") +R = TypeVar("R") + +GoodTypeAlias1: TA = Union[int, str] +GoodTypeAlias2: TA = int | None +GoodTypeAlias3: TA = list[GoodTypeAlias2] +GoodTypeAlias4: TA = list[T] +GoodTypeAlias5: TA = tuple[T, ...] | list[T] +GoodTypeAlias6: TA = tuple[int, int, S, T] +GoodTypeAlias7: TA = Callable[..., int] +GoodTypeAlias8: TA = Callable[[int, T], T] +GoodTypeAlias9: TA = Callable[Concatenate[int, P], R] +GoodTypeAlias10: TA = Any +GoodTypeAlias11: TA = GoodTypeAlias1 | GoodTypeAlias2 | list[GoodTypeAlias4[int]] +GoodTypeAlias12: TA = Callable[P, None] +GoodTypeAlias13: TA = "int | str" +GoodTypeAlias14: TA = list["int | str"] +GoodTypeAlias15: TA = Literal[3, 4, 5, None] + + +def good_type_aliases( + p1: GoodTypeAlias1, + p2: GoodTypeAlias2, + p3: GoodTypeAlias3, + p4: GoodTypeAlias4[int], + p5: GoodTypeAlias5[str], + p6: GoodTypeAlias6[int, str], + p7: GoodTypeAlias7, + p8: GoodTypeAlias8[str], + p9: GoodTypeAlias9[[str, str], None], + p10: GoodTypeAlias10, + p11: GoodTypeAlias11, + p12: GoodTypeAlias12, + p13: GoodTypeAlias13, + p14: GoodTypeAlias14, + p15: GoodTypeAlias15, +): + assert_type(p1, int | str) + assert_type(p2, int | None) + assert_type(p3, list[int | None]) + assert_type(p4, list[int]) + assert_type(p5, tuple[str, ...] | list[str]) + assert_type(p6, tuple[int, int, int, str]) + assert_type(p7, Callable[..., int]) + assert_type(p8, Callable[[int, str], str]) + assert_type(p9, Callable[[int, str, str], None]) + assert_type(p10, Any) + assert_type(p11, int | str | None | list[list[int]]) + assert_type(p12, Callable[..., None]) + assert_type(p13, int | str) + assert_type(p14, list[int | str]) + assert_type(p15, Literal[3, 4, 5, None]) + + +def good_type_aliases_used_badly( + p1: GoodTypeAlias2[int], # Type error: type alias is not generic + p2: GoodTypeAlias3[int], # Type error: type alias is already specialized + p3: GoodTypeAlias4[int, int], # Type error: too many type arguments + p4: GoodTypeAlias8[int, int], # Type error: too many type arguments + p5: GoodTypeAlias9[int, int], # Type error: bad type argument for ParamSpec +): + pass + + +var1 = 3 + +# The following should not be allowed as type aliases. +BadTypeAlias1: TA = eval("".join(map(chr, [105, 110, 116]))) +BadTypeAlias2: TA = [int, str] +BadTypeAlias3: TA = ((int, str),) +BadTypeAlias4: TA = [int for i in range(1)] +BadTypeAlias5: TA = {"a": "b"} +BadTypeAlias6: TA = (lambda: int)() +BadTypeAlias7: TA = [int][0] +BadTypeAlias8: TA = int if 1 < 3 else str +BadTypeAlias9: TA = var1 +BadTypeAlias10: TA = True +BadTypeAlias11: TA = 1 +BadTypeAlias12: TA = list or set +BadTypeAlias13: TA = f"{'int'}" + + +ListAlias: TA = list +ListOrSetAlias: TA = list | set + +x1: list[str] = ListAlias() # OK +assert_type(x1, list[str]) + +x2: ListAlias[int] # Type error: already specialized +x3 = ListOrSetAlias() # Type error: cannot instantiate union +x4: ListOrSetAlias[int] # Type error: already specialized diff --git a/conformance/tests/aliases_implicit.py b/conformance/tests/aliases_implicit.py new file mode 100644 index 000000000..3d700d807 --- /dev/null +++ b/conformance/tests/aliases_implicit.py @@ -0,0 +1,136 @@ +""" +Tests traditional implicit type aliases. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/aliases.html + +from collections.abc import Iterable +from typing import Any, Callable, Concatenate, ParamSpec, TypeVar, Union, assert_type + +TFloat = TypeVar("TFloat", bound=float) +Vector = Iterable[tuple[TFloat, TFloat]] + + +def in_product(v: Vector[TFloat]) -> Iterable[TFloat]: + return [x for x, _ in v] + + +def dilate(v: Vector[float], scale: float) -> Vector[float]: + return ((x * scale, y * scale) for x, y in v) + + +# > Type aliases may be as complex as type hints in annotations – anything +# > that is acceptable as a type hint is acceptable in a type alias. + +S = TypeVar("S") +T = TypeVar("T") +P = ParamSpec("P") +R = TypeVar("R") + +GoodTypeAlias1 = Union[int, str] +GoodTypeAlias2 = int | None +GoodTypeAlias3 = list[GoodTypeAlias2] +GoodTypeAlias4 = list[T] +GoodTypeAlias5 = tuple[T, ...] | list[T] +GoodTypeAlias6 = tuple[int, int, S, T] +GoodTypeAlias7 = Callable[..., int] +GoodTypeAlias8 = Callable[[int, T], T] +GoodTypeAlias9 = Callable[Concatenate[int, P], R] +GoodTypeAlias10 = Any +GoodTypeAlias11 = GoodTypeAlias1 | GoodTypeAlias2 | list[GoodTypeAlias4[int]] +GoodTypeAlias12 = list[TFloat] +GoodTypeAlias13 = Callable[P, None] + + +def good_type_aliases( + p1: GoodTypeAlias1, + p2: GoodTypeAlias2, + p3: GoodTypeAlias3, + p4: GoodTypeAlias4[int], + p5: GoodTypeAlias5[str], + p6: GoodTypeAlias6[int, str], + p7: GoodTypeAlias7, + p8: GoodTypeAlias8[str], + p9: GoodTypeAlias9[[str, str], None], + p10: GoodTypeAlias10, + p11: GoodTypeAlias11, + p12: GoodTypeAlias12[bool], + p13: GoodTypeAlias13 +): + assert_type(p1, int | str) + assert_type(p2, int | None) + assert_type(p3, list[int | None]) + assert_type(p4, list[int]) + assert_type(p5, tuple[str, ...] | list[str]) + assert_type(p6, tuple[int, int, int, str]) + assert_type(p7, Callable[..., int]) + assert_type(p8, Callable[[int, str], str]) + assert_type(p9, Callable[[int, str, str], None]) + assert_type(p10, Any) + assert_type(p11, int | str | None | list[list[int]]) + assert_type(p12, list[bool]) + assert_type(p13, Callable[..., None]) + + +def good_type_aliases_used_badly( + p1: GoodTypeAlias2[int], # Type error: type alias is not generic + p2: GoodTypeAlias3[int], # Type error: type alias is already specialized + p3: GoodTypeAlias4[int, int], # Type error: too many type arguments + p4: GoodTypeAlias8[int, int], # Type error: too many type arguments + p5: GoodTypeAlias9[int, int], # Type error: bad type argument for ParamSpec + p6: GoodTypeAlias12[str], # Type error: type argument doesn't match bound +): + pass + + +var1 = 3 + +# The following should not be considered type aliases. +BadTypeAlias1 = eval("".join(map(chr, [105, 110, 116]))) +BadTypeAlias2 = [int, str] +BadTypeAlias3 = ((int, str),) +BadTypeAlias4 = [int for i in range(1)] +BadTypeAlias5 = {"a": "b"} +BadTypeAlias6 = (lambda: int)() +BadTypeAlias7 = [int][0] +BadTypeAlias8 = int if 1 < 3 else str +BadTypeAlias9 = var1 +BadTypeAlias10 = True +BadTypeAlias11 = 1 +BadTypeAlias12 = list or set +BadTypeAlias13 = f"int" +BadTypeAlias14 = "int | str" + + +def bad_type_aliases( + p1: BadTypeAlias1, # Type error: Invalid type annotation + p2: BadTypeAlias2, # Type error: Invalid type annotation + p3: BadTypeAlias3, # Type error: Invalid type annotation + p4: BadTypeAlias4, # Type error: Invalid type annotation + p5: BadTypeAlias5, # Type error: Invalid type annotation + p6: BadTypeAlias6, # Type error: Invalid type annotation + p7: BadTypeAlias7, # Type error: Invalid type annotation + p8: BadTypeAlias8, # Type error: Invalid type annotation + p9: BadTypeAlias9, # Type error: Invalid type annotation + p10: BadTypeAlias10, # Type error: Invalid type annotation + p11: BadTypeAlias11, # Type error: Invalid type annotation + p12: BadTypeAlias12, # Type error: Invalid type annotation + p13: BadTypeAlias13, # Type error: Invalid type annotation + p14: BadTypeAlias14, # Type error: Invalid type annotation +): + pass + + +ListAlias = list +ListOrSetAlias = list | set + +x1: list[str] = ListAlias() # OK +assert_type(x1, list[str]) + +x2 = ListAlias[int]() # OK +assert_type(x2, list[int]) + +x3 = ListOrSetAlias() # Type error: cannot instantiate union + +x4: ListOrSetAlias[int] # Type error: already specialized + diff --git a/conformance/tests/aliases_newtype.py b/conformance/tests/aliases_newtype.py new file mode 100644 index 000000000..92437c35b --- /dev/null +++ b/conformance/tests/aliases_newtype.py @@ -0,0 +1,62 @@ +""" +Tests the `typing.NewType` function. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/aliases.html#newtype + +from typing import Any, Hashable, Literal, NewType, TypeVar, TypedDict, assert_type + +UserId = NewType("UserId", int) + +UserId("user") # Type error: incorrect type +u1: UserId = 42 # Type error: incorrect type +u2: UserId = UserId(42) # OK + +assert_type(UserId(5) + 1, int) + +# > Both isinstance and issubclass, as well as subclassing will fail for +# > NewType('Derived', Base) since function objects don’t support these +# > operations. +isinstance(u2, UserId) # Type error: not allowed in isinstance call + + +class UserIdDerived(UserId): # Type error: subclassing not allowed + pass + + +# > NewType accepts exactly two arguments: a name for the new unique type, +# > and a base class. The latter should be a proper class (i.e., not a type +# > construct like Union, etc.), or another unique type created by +# > calling NewType. + +GoodName = NewType("BadName", int) # Type error: assigned name does not match + +GoodNewType1 = NewType("GoodNewType1", list) + +nt1: GoodNewType1[int] # Type error: NewType cannot be generic + +TypeAlias1 = dict[str, str] +GoodNewType2 = NewType("GoodNewType2", TypeAlias1) + + +BadNewType1 = NewType("BadNewType1", int | str) # Type error: cannot be generic + +T = TypeVar("T") +BadNewType2 = NewType("BadNewType2", list[T]) # Type error: cannot be generic + +BadNewType3 = NewType("BadNewType3", Hashable) # Type error: cannot be protocol + +BadNewType4 = NewType("BadNewType4", Literal[7]) # Type error: literal not allowed + + +class TD1(TypedDict): + a: int + + +BadNewType5 = NewType("BadNewType5", TD1) # Type error: cannot be TypedDict + +BadNewType6 = NewType("BadNewType6", GoodNewType1) # Type error: cannot be NewType + +BadNewType7 = NewType("BadNewType7", int, int) # Type error: too many arguments + +BadNewType8 = NewType("BadNewType8", Any) # Type error: cannot be Any diff --git a/conformance/tests/aliases_recursive.py b/conformance/tests/aliases_recursive.py new file mode 100644 index 000000000..9a459c9b2 --- /dev/null +++ b/conformance/tests/aliases_recursive.py @@ -0,0 +1,82 @@ +""" +Tests recursive (self-referential) type aliases. +""" + +# The typing specification doesn't mandate support for recursive +# (self-referential) type aliases prior to PEP 695, but it also +# doesn't indicates that they shouldn't work. +# Most type checkers now support them, and many libraries and code +# bases have started to rely on them. +# PEP 695 formally mandates that recursive type aliases work. + +from typing import Mapping, TypeAlias, TypeVar, Union + +Json = Union[None, int, str, float, list["Json"], dict[str, "Json"]] + +j1: Json = [1, {"a": 1}] # OK +j2: Json = 3.4 # OK +j3: Json = [1.2, None, [1.2, [""]]] # OK +j4: Json = {"a": 1, "b": 3j} # Type error: incompatible type +j5: Json = [2, 3j] # Type error: incompatible type + + +# This type alias should be equivalent to Json. +Json2 = Union[None, int, str, float, list["Json2"], dict[str, "Json2"]] + +def func1(j1: Json) -> Json2: + return j1 + + +RecursiveTuple = str | int | tuple["RecursiveTuple", ...] + + +t1: RecursiveTuple = (1, 1) # OK +t2: RecursiveTuple = (1, "1") # OK +t3: RecursiveTuple = (1, "1", 1, "2") # OK +t4: RecursiveTuple = (1, ("1", 1), "2") # OK +t5: RecursiveTuple = (1, ("1", 1), (1, (1, 2))) # OK +t6: RecursiveTuple = (1, ("1", 1), (1, (1, [2]))) # Type error +t6: RecursiveTuple = (1, [1]) # Type error + + +RecursiveMapping = str | int | Mapping[str, "RecursiveMapping"] + +m1: RecursiveMapping = 1 # OK +m2: RecursiveMapping = "1" # OK +m3: RecursiveMapping = {"1": "1"} # OK +m4: RecursiveMapping = {"1": "1", "2": 1} # OK +m5: RecursiveMapping = {"1": "1", "2": 1, "3": {}} # OK +m6: RecursiveMapping = {"1": "1", "2": 1, "3": {"0": "0", "1": "2", "2": {}}} # OK +m7: RecursiveMapping = {"1": [1]} # Type error +m8: RecursiveMapping = {"1": "1", "2": 1, "3": [1, 2]} # Type error +m9: RecursiveMapping = { + "1": "1", + "2": 1, + "3": {"0": "0", "1": 1, "2": [1, 2, 3]}, +} # Type error + + +T1 = TypeVar("T1", str, int) +T2 = TypeVar("T2") + +GenericTypeAlias1 = list["GenericTypeAlias1[T1]" | T1] +SpecializedTypeAlias1 = GenericTypeAlias1[str] + +g1: SpecializedTypeAlias1 = ["hi", ["hi", "hi"]] # OK +g2: GenericTypeAlias1[str] = ["hi", "bye", [""], [["hi"]]] # OK +g3: GenericTypeAlias1[str] = ["hi", [2.4]] # Type error + +GenericTypeAlias2 = list["GenericTypeAlias2[T1, T2]" | T1 | T2] + +g4: GenericTypeAlias2[str, int] = [[3, ["hi"]], "hi"] # OK +g5: GenericTypeAlias2[str, float] = [[3, ["hi", 3.4, [3.4]]], "hi"] # OK +g6: GenericTypeAlias2[str, int] = [[3, ["hi", 3, [3.4]]], "hi"] # Type error + + +RecursiveUnion: TypeAlias = Union["RecursiveUnion", int] # Type error: cyclical reference + +MutualReference1: TypeAlias = Union[ + "MutualReference2", int +] # Type error: cyclical reference +MutualReference2: TypeAlias = Union["MutualReference1", str] + diff --git a/conformance/tests/aliases_type_statement.py b/conformance/tests/aliases_type_statement.py new file mode 100644 index 000000000..d534ebff0 --- /dev/null +++ b/conformance/tests/aliases_type_statement.py @@ -0,0 +1,92 @@ +""" +Tests the "type" statement introduced in Python 3.12. +""" + +from typing import Callable, TypeVar + + +type GoodAlias1 = int +type GoodAlias2[S1, *S2, **S3] = Callable[S3, S1] | tuple[*S2] +type GoodAlias3 = GoodAlias2[int, tuple[int, str], ...] + + +class ClassA: + type GoodAlias4 = int | None + + +GoodAlias1.bit_count # Type error: cannot access attribute + +GoodAlias1() # Type error: cannot call alias + +print(GoodAlias1.__value__) # OK +print(GoodAlias1.__type_params__) # OK +print(GoodAlias1.other_attrib) # Type error: unknown attribute + + +class DerivedInt(GoodAlias1): # Type error: cannot use alias as base class + pass + + +def func2(x: object): + if isinstance(x, GoodAlias1): # Type error: cannot use alias in isinstance + pass + +var1 = 1 + +# The following should not be allowed as type aliases. +type BadTypeAlias1 = eval("".join(map(chr, [105, 110, 116]))) +type BadTypeAlias2 = [int, str] +type BadTypeAlias3 = ((int, str),) +type BadTypeAlias4 = [int for i in range(1)] +type BadTypeAlias5 = {"a": "b"} +type BadTypeAlias6 = (lambda: int)() +type BadTypeAlias7 = [int][0] +type BadTypeAlias8 = int if 1 < 3 else str +type BadTypeAlias9 = var1 +type BadTypeAlias10 = True +type BadTypeAlias11 = 1 +type BadTypeAlias12 = list or set +type BadTypeAlias13 = f"{'int'}" + +if 1 < 2: + type BadTypeAlias14 = int # Type error: redeclared +else: + type BadTypeAlias14 = int + + +def func3(): + type BadTypeAlias15 = int # Type error alias not allowed in function + + + +V = TypeVar("V") + +type TA1[K] = dict[K, V] # Type error: combines old and new TypeVars + + +T1 = TypeVar("T1") + +type TA2 = list[T1] # Type error: uses old TypeVar + + +type RecursiveTypeAlias1[T] = T | list[RecursiveTypeAlias1[T]] + +r1_1: RecursiveTypeAlias1[int] = 1 +r1_2: RecursiveTypeAlias1[int] = [1, [1, 2, 3]] + +type RecursiveTypeAlias2[S: int, T: str, **P] = Callable[P, T] | list[S] | list[RecursiveTypeAlias2[S, T, P]] + +r2_1: RecursiveTypeAlias2[str, str, ...] # Type error: not compatible with S bound +r2_2: RecursiveTypeAlias2[int, str, ...] +r2_3: RecursiveTypeAlias2[int, int, ...] # Type error: not compatible with T bound +r2_4: RecursiveTypeAlias2[int, str, [int, str]] + +type RecursiveTypeAlias3 = RecursiveTypeAlias3 # Type error: circular definition + +type RecursiveTypeAlias4[T] = T | RecursiveTypeAlias4[str] # Type error: circular definition + +type RecursiveTypeAlias5[T] = T | list[RecursiveTypeAlias5[T]] + +type RecursiveTypeAlias6 = RecursiveTypeAlias7 # Type error: circular definition +type RecursiveTypeAlias7 = RecursiveTypeAlias6 + diff --git a/conformance/tests/aliases_typealiastype.py b/conformance/tests/aliases_typealiastype.py new file mode 100644 index 000000000..9adad00f1 --- /dev/null +++ b/conformance/tests/aliases_typealiastype.py @@ -0,0 +1,70 @@ +""" +Tests the TypeAliasType call introduced in Python 3.12. +""" + +from typing import Callable, Generic, ParamSpec, TypeAliasType, TypeVar, TypeVarTuple + +S = TypeVar("S") +T = TypeVar("T") +TStr = TypeVar("TStr", bound=str) +P = ParamSpec("P") +Ts = TypeVarTuple("Ts") + +my_tuple = (S, T) +var1 = 3 + +GoodAlias1 = TypeAliasType("GoodAlias1", int) +GoodAlias2 = TypeAliasType("GoodAlias2", list[T], type_params=(T,)) +GoodAlias3 = TypeAliasType("GoodAlias3", list[T] | list[S], type_params=(S, T)) +GoodAlias4 = TypeAliasType("GoodAlias4", T | list[GoodAlias4[T]], type_params=(T,)) +GoodAlias5 = TypeAliasType( + "GoodAlias5", + Callable[P, TStr] | list[S] | list[GoodAlias5[S, TStr, P]] | tuple[*Ts], + type_params=(S, TStr, P, Ts), +) + +class ClassA(Generic[T]): + GoodAlias6 = TypeAliasType("GoodAlias6", list[T]) + + +print(GoodAlias1.__value__) # OK +print(GoodAlias1.__type_params__) # OK +print(GoodAlias1.other_attrib) # Type error: unknown attribute + + +x1: GoodAlias4[int] = 1 # OK +x2: GoodAlias4[int] = [1] # OK +x3: GoodAlias5[str, str, ..., int, str] # OK +x4: GoodAlias5[int, str, ..., int, str] # OK +x5: GoodAlias5[int, str, [int, str], *tuple[int, str, int]] # OK +x6: GoodAlias5[int, int, ...] # Type error: incorrect type arguments + + +BadAlias1 = TypeAliasType( + "BadAlias1", list[S], type_params=(T,) +) # Type error: S not in scope +BadAlias2 = TypeAliasType("BadAlias2", list[S]) # Type error: S not in scope +BadAlias3 = TypeAliasType( + "BadAlias3", int, type_params=my_tuple +) # Type error: not literal tuple +BadAlias4 = TypeAliasType("BadAlias4", BadAlias4) # Type error: circular dependency +BadAlias5 = TypeAliasType( + "BadAlias5", T | BadAlias5[str], type_params=(T,) +) # Type error: circular dependency +BadAlias6 = TypeAliasType("BadAlias6", BadAlias7) # Type error: circular dependency +BadAlias7 = TypeAliasType("BadAlias7", BadAlias6) + +# The following are invalid type expressions for a type alias. +BadAlias8 = TypeAliasType("BadAlias8", eval("".join(map(chr, [105, 110, 116])))) +BadAlias9 = TypeAliasType("BadAlias9", [int, str]) +BadAlias10 = TypeAliasType("BadAlias10", ((int, str),)) +BadAlias11 = TypeAliasType("BadAlias11", [int for i in range(1)]) +BadAlias12 = TypeAliasType("BadAlias12", {"a": "b"}) +BadAlias13 = TypeAliasType("BadAlias13", (lambda: int)()) +BadAlias14 = TypeAliasType("BadAlias14", [int][0]) +BadAlias15 = TypeAliasType("BadAlias15", int if 1 < 3 else str) +BadAlias16 = TypeAliasType("BadAlias16", var1) +BadAlias17 = TypeAliasType("BadAlias17", True) +BadAlias18 = TypeAliasType("BadAlias18", 1) +BadAlias19 = TypeAliasType("BadAlias19", list or set) +BadAlias20 = TypeAliasType("BadAlias20", f"{'int'}") diff --git a/conformance/tests/aliases_variance.py b/conformance/tests/aliases_variance.py new file mode 100644 index 000000000..b94b10be6 --- /dev/null +++ b/conformance/tests/aliases_variance.py @@ -0,0 +1,45 @@ +""" +Tests generic type aliases used in class declarations for +variance incompatibility. +""" + +from typing import Generic, TypeVar, TypeAlias + +T = TypeVar("T") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + + +class ClassA(Generic[T]): + pass + + +A_Alias_1: TypeAlias = ClassA[T_co] +A_Alias_2: TypeAlias = A_Alias_1[T_co] + +# Specialized type aliases used within a class declaration should +# result in the same variance incompatibility errors as their +# non-aliased counterparts. + +class ClassA_1(ClassA[T_co]): # Type error: incompatible variance + ... + + +class ClassA_2(A_Alias_1[T_co]): # Type error: incompatible variance + ... + + +class ClassA_3(A_Alias_2[T_co]): # Type error: incompatible variance + ... + + + +class ClassB(Generic[T, T_co]): + pass + + +B_Alias_1 = ClassB[T_co, T_contra] + + +class ClassB_1(B_Alias_1[T_contra, T_co]): # Type error: incompatible variance + ... diff --git a/conformance/tests/annotations_typeexpr.py b/conformance/tests/annotations_typeexpr.py new file mode 100644 index 000000000..064cac65c --- /dev/null +++ b/conformance/tests/annotations_typeexpr.py @@ -0,0 +1,99 @@ +""" +Test for type expressions used in annotations. +""" + +import abc +import abc +import types +import types +from typing import Any, Callable, Tuple, Union, assert_type + +# https://typing.readthedocs.io/en/latest/spec/annotations.html#valid-type-expression-forms + +def greeting(name: str) -> str: + return 'Hello ' + name + +assert_type(greeting('Monty'), str) + + +# > Expressions whose type is a subtype of a specific argument type are also accepted for that argument. +class StrSub(str): ... +assert_type(greeting(StrSub('Monty')), str) + + +# > Type hints may be built-in classes (including those defined in standard library or third-party +# > extension modules), abstract base classes, types available in the types module, and user-defined +# > classes (including those defined in the standard library or third-party modules). + + +class UserDefinedClass: ... +class AbstractBaseClass(abc.ABC): + @abc.abstractmethod + def abstract_method(self): ... + +# The following parameter annotations should all be considered +# valid and not generate errors. +def valid_annotations( + p1: int, + p2: str, + p3: bytes, + p4: bytearray, + p5: memoryview, + p6: complex, + p7: float, + p8: bool, + p9: object, + p10: type, + p11: types.ModuleType, + p12: types.FunctionType, + p13: types.BuiltinFunctionType, + p14: UserDefinedClass, + p15: AbstractBaseClass, + p16: int, + p17: Union[int, str], + p18: None, + p19: list, + p20: list[int], + p21: tuple, + p22: Tuple[int, ...], + p23: Tuple[int, int, str], + p24: Callable[..., int], + p25: Callable[[int, str], None], + p26: Any, +): + assert_type(p17, int | str) + assert_type(p19, list[Any]) + assert_type(p20, list[int]) + assert_type(p21, tuple[Any, ...]) + + +# > Annotations should be kept simple or static analysis tools may not be able to interpret the values. + +var1 = 3 + +# The following parameter annotations should all be considered +# invalid and generate errors. +def invalid_annotations( + p1: eval("".join(map(chr, [105, 110, 116]))), + p2: [int, str], + p3: (int, str), + p4: [int for i in range(1)], + p5: {}, + p6: (lambda : int)(), + p7: [int][0], + p8: int if 1 < 3 else str, + p9: var1, + p10: True, + p11: 1, + p12: -1, + p13: int or str, + p14: f"int", +): + pass + + +# > When used in a type hint, the expression None is considered equivalent to type(None). + +def takes_None(x: None) -> None: ... +assert_type(takes_None(None), None) + diff --git a/conformance/tests/callables_annotation.py b/conformance/tests/callables_annotation.py new file mode 100644 index 000000000..759e11d77 --- /dev/null +++ b/conformance/tests/callables_annotation.py @@ -0,0 +1,45 @@ +""" +Tests Callable annotation and parameter annotations for "def" statements. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/callables.html#callable + +from typing import Callable, assert_type + + +def func1(cb: Callable[[int, str], list[str]]) -> None: + assert_type(cb(1, ""), list[str]) + + cb(1) # Type error + cb(1, 2) # Type error + cb(1, "", 1) # Type error + cb(a=1, b="") # Type error + + +def func2(cb: Callable[[], dict[str, str]]) -> None: + assert_type(cb(), dict[str, str]) + + cb(1) # Type error + + +# > It is possible to declare the return type of a callable without specifying +# > the call signature by substituting a literal ellipsis (three dots) for the +# > list of arguments. +def func3(cb: Callable[..., list[str]]): + assert_type(cb(), list[str]) + assert_type(cb(""), list[str]) + assert_type(cb(1, ""), list[str]) + + +def func4(*args: int, **kwargs: int) -> None: + assert_type(args, tuple[int, ...]) + assert_type(kwargs, dict[str, int]) + + +v1: Callable[int] # Illegal form +v2: Callable[int, int] # Illegal form +v3: Callable[[], [int]] # Illegal form +v4: Callable[int, int, int] # Illegal form +v5: Callable[[...], int] # Illegal form + + diff --git a/conformance/tests/callables_kwargs.py b/conformance/tests/callables_kwargs.py new file mode 100644 index 000000000..070a45b71 --- /dev/null +++ b/conformance/tests/callables_kwargs.py @@ -0,0 +1,123 @@ +""" +Tests the use of an unpacked TypedDict for annotating **kwargs. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/callables.html#unpack-for-keyword-arguments + +# This sample tests the handling of Unpack[TypedDict] when used with +# a **kwargs parameter in a function signature. + +from typing import Protocol, TypeVar, TypedDict, NotRequired, Required, Unpack, assert_type + + +class TD1(TypedDict): + v1: Required[int] + v2: NotRequired[str] + + +class TD2(TD1): + v3: Required[str] + + +def func1(**kwargs: Unpack[TD2]) -> None: + v1 = kwargs["v1"] + assert_type(v1, int) + + kwargs["v2"] # Type error: v2 may not be present + + if "v2" in kwargs: + v2 = kwargs["v2"] + assert_type(v2, str) + + v3 = kwargs["v3"] + assert_type(v3, str) + + +def func2(v3: str, **kwargs: Unpack[TD1]) -> None: + # > When Unpack is used, type checkers treat kwargs inside the function + # > body as a TypedDict. + assert_type(kwargs, TD1) + + +def func3() -> None: + func1() # Type error: missing required keyword args + func1(v1=1, v2="", v3="5") # OK + + td2 = TD2(v1=2, v3="4") + func1(**td2) # OK + func1(v1=1, v2="", v3="5", v4=5) # Type error: v4 is not in TD2 + func1(1, "", "5") # Type error: args not passed by position + + # > Passing a dictionary of type dict[str, object] as a **kwargs argument + # > to a function that has **kwargs annotated with Unpack must generate a + # > type checker error. + my_dict: dict[str, str] = {} + func1(**my_dict) # Type error: untyped dict + + d1 = {"v1": 2, "v3": "4", "v4": 4} + func1(**d1) # OK or Type error (spec allows either) + func2(**td2) # OK + func1(v1=2, **td2) # Type error: v1 is already specified + func2(1, **td2) # Type error: v1 is already specified + func2(v1=1, **td2) # Type error: v1 is already specified + + +class TDProtocol1(Protocol): + def __call__(self, *, v1: int, v3: str) -> None: + ... + + +class TDProtocol2(Protocol): + def __call__(self, *, v1: int, v3: str, v2: str = "") -> None: + ... + + +class TDProtocol3(Protocol): + def __call__(self, *, v1: int, v2: int, v3: str) -> None: + ... + + +class TDProtocol4(Protocol): + def __call__(self, *, v1: int) -> None: + ... + + +class TDProtocol5(Protocol): + def __call__(self, v1: int, v3: str) -> None: + ... + + +class TDProtocol6(Protocol): + def __call__(self, **kwargs: Unpack[TD2]) -> None: + ... + +# Specification: https://typing.readthedocs.io/en/latest/spec/callables.html#assignment + +v1: TDProtocol1 = func1 # OK +v2: TDProtocol2 = func1 # OK +v3: TDProtocol3 = func1 # Type error: v2 is wrong type +v4: TDProtocol4 = func1 # Type error: v3 is missing +v5: TDProtocol5 = func1 # Type error: params are positional +v6: TDProtocol6 = func1 # OK + + +def func4(v1: int, /, **kwargs: Unpack[TD2]) -> None: + ... + + +# Type error: parameter v1 overlaps with the TypedDict. +def func5(v1: int, **kwargs: Unpack[TD2]) -> None: + ... + + +T = TypeVar("T", bound=TD2) + +# > TypedDict is the only permitted heterogeneous type for typing **kwargs. +# > Therefore, in the context of typing **kwargs, using Unpack with types other +# > than TypedDict should not be allowed and type checkers should generate +# > errors in such cases. + +# Type error: unpacked value must be a TypedDict, not a TypeVar bound to TypedDict. +def func6(**kwargs: Unpack[T]) -> None: + ... + diff --git a/conformance/tests/callables_protocol.py b/conformance/tests/callables_protocol.py new file mode 100644 index 000000000..dbe8108d7 --- /dev/null +++ b/conformance/tests/callables_protocol.py @@ -0,0 +1,313 @@ +""" +Tests handling of callback protocols. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/callables.html#callback-protocols + +from typing import Any, Callable, ParamSpec, Protocol, TypeVar, cast, overload + +InputT = TypeVar("InputT", contravariant=True) +OutputT = TypeVar("OutputT", covariant=True) + + +class Proto1(Protocol): + def __call__(self, *vals: bytes, max_len: int | None = None) -> list[bytes]: + ... + + +def cb1_good1(*vals: bytes, max_len: int | None = None) -> list[bytes]: + return [] + + +def cb1_bad1(*vals: bytes, max_items: int | None) -> list[bytes]: + return [] + + +def cb1_bad2(*vals: bytes) -> list[bytes]: + return [] + + +def cb1_bad3(*vals: bytes, max_len: str | None) -> list[bytes]: + return [] + + +cb1: Proto1 = cb1_good1 # OK +cb1 = cb1_bad1 # Type error: different names +cb1 = cb1_bad2 # Type error: parameter types +cb1 = cb1_bad3 # Type error: default argument + + +class Proto2(Protocol): + def __call__(self, *vals: bytes, **kwargs: str) -> None: + pass + + +def cb2_good1(*a: bytes, **b: str): + pass + + +def cb2_bad1(*a: bytes): + pass + + +def cb2_bad2(*a: str, **b: str): + pass + + +def cb2_bad3(*a: bytes, **b: bytes): + pass + + +def cb2_bad4(**b: str): + pass + + +cb2: Proto2 = cb2_good1 # OK + +cb2 = cb2_bad1 # Type error: missing **kwargs +cb2 = cb2_bad2 # Type error: parameter type +cb2 = cb2_bad3 # Type error: parameter type +cb2 = cb2_bad4 # Type error: missing parameter + + +class Proto3(Protocol): + def __call__(self) -> None: + pass + + +cb3: Proto3 = cb2_good1 # OK +cb3 = cb2_bad1 # OK +cb3 = cb2_bad2 # OK +cb3 = cb2_bad3 # OK +cb3 = cb2_bad4 # OK + + +# A callback protocol with other attributes. +class Proto4(Protocol): + other_attribute: int + + def __call__(self, x: int) -> None: + pass + + +def cb4_bad1(x: int) -> None: + pass + + +var4: Proto4 = cb4_bad1 # Type error: missing attribute + + +class Proto5(Protocol): + def __call__(self, *, a: int, b: str) -> int: + ... + + +def cb5_good1(a: int, b: str) -> int: + return 0 + + +cb5: Proto5 = cb5_good1 # OK + + +class NotProto6: + def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: + return [] + + +def cb6_bad1(*vals: bytes, max_len: int | None = None) -> list[bytes]: + return [] + + +cb6: NotProto6 = cb6_bad1 # Type error: NotProto6 isn't a protocol class + + +class Proto7(Protocol[InputT, OutputT]): + def __call__(self, inputs: InputT) -> OutputT: + ... + + +class Class7_1: + # Test for unannotated parameter. + def __call__(self, inputs) -> int: + return 5 + + +cb7_1: Proto7[int, int] = Class7_1() # OK + + +class Class7_2: + # Test for parameter with type Any. + def __call__(self, inputs: Any) -> int: + return 5 + + +cb7_2: Proto7[int, int] = Class7_2() # OK + + +class Proto8(Protocol): + @overload + def __call__(self, x: int) -> int: + ... + + @overload + def __call__(self, x: str) -> str: + ... + + def __call__(self, x: Any) -> Any: + ... + + +def cb8_good1(x: Any) -> Any: + return x + + +def cb8_bad1(x: int) -> Any: + return x + + +cb8: Proto8 = cb8_good1 # OK +cb8 = cb8_bad1 # Type error: parameter type + + +P = ParamSpec("P") +R = TypeVar("R", covariant=True) + + +class Proto9(Protocol[P, R]): + other_attribute: int + + def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: + ... + + +def decorator1(f: Callable[P, R]) -> Proto9[P, R]: + converted = cast(Proto9[P, R], f) + converted.other_attribute = 1 + converted.other_attribute = "str" # Type error: incompatible type + converted.xxx = 3 # Type error: unknown attribute + return converted + + +@decorator1 +def cb9_good(x: int) -> str: + return "" + + +print(cb9_good.other_attribute) # OK +print(cb9_good.other_attribute2) # Type error: unknown attribute + +cb9_good(x=3) + + +class Proto10(Protocol): + __name__: str + __module__: str + __qualname__: str + __annotations__: dict[str, Any] + + def __call__(self) -> None: + ... + + +def cb10_good() -> None: + pass + + +cb10: Proto10 = cb10_good # OK + + +class Proto11(Protocol): + def __call__(self, x: int, /, y: str) -> Any: + ... + + +def cb11_good1(x: int, /, y: str, z: None = None) -> Any: + pass + + +def cb11_good2(x: int, y: str, z: None = None) -> Any: + pass + + +def cb11_bad1(x: int, y: str, /) -> Any: + pass + + +cb11: Proto11 = cb11_good1 # OK +cb11 = cb11_good2 # OK +cb11 = cb11_bad1 # Type error: y is position-only + + +class Proto12(Protocol): + def __call__(self, *args: Any, kwarg0: Any, kwarg1: Any) -> None: + ... + + +def cb12_good1(*args: Any, kwarg0: Any, kwarg1: Any) -> None: + pass + + +def cb12_good2(*args: Any, **kwargs: Any) -> None: + pass + + +def cb12_bad1(*args: Any, kwarg0: Any) -> None: + pass + + +cb12: Proto12 = cb12_good1 # OK +cb12 = cb12_good2 # OK +cb12 = cb12_bad1 # Type error: missing kwarg1 + + +class Proto13_Default(Protocol): + # Callback with positional parameter with default arg value + def __call__(self, path: str = ...) -> str: + ... + + +class Proto13_NoDefault(Protocol): + # Callback with positional parameter but no default arg value + def __call__(self, path: str) -> str: + ... + + +def cb13_default(path: str = "") -> str: + return "" + + +def cb13_no_default(path: str) -> str: + return "" + + +cb13_1: Proto13_Default = cb13_default # OK +cb13_2: Proto13_Default = cb13_no_default # Type error: no default + +cb13_3: Proto13_NoDefault = cb13_default # OK +cb13_4: Proto13_NoDefault = cb13_no_default # OK + + +class Proto14_Default(Protocol): + # Callback with keyword parameter with default arg value + def __call__(self, *, path: str = ...) -> str: + ... + + +class Proto14_NoDefault(Protocol): + # Callback with keyword parameter with no default arg value + def __call__(self, *, path: str) -> str: + ... + + +def cb14_default(*, path: str = "") -> str: + return "" + + +def cb14_no_default(*, path: str) -> str: + return "" + + +cb14_1: Proto14_Default = cb14_default # OK +cb14_2: Proto14_Default = cb14_no_default # Type error: no default +cb14_3: Proto14_NoDefault = cb14_default # OK +cb14_4: Proto14_NoDefault = cb14_no_default # OK diff --git a/conformance/tests/dataclasses_descriptors.py b/conformance/tests/dataclasses_descriptors.py new file mode 100644 index 000000000..37dd1b36a --- /dev/null +++ b/conformance/tests/dataclasses_descriptors.py @@ -0,0 +1,68 @@ +""" +Tests the handling of descriptors within a dataclass. +""" + +# This portion of the dataclass spec is under-specified in the documentation, +# but its behavior can be determined from the runtime implementation. + +from dataclasses import dataclass +from typing import Any, Generic, TypeVar, assert_type, overload + +T = TypeVar("T") + + +class Desc1: + @overload + def __get__(self, __obj: None, __owner: Any) -> "Desc1": + ... + + @overload + def __get__(self, __obj: object, __owner: Any) -> int: + ... + + def __get__(self, __obj: object | None, __owner: Any) -> "int | Desc1": + ... + + def __set__(self, __obj: object, __value: int) -> None: + ... + + +@dataclass +class DC1: + y: Desc1 = Desc1() + + +dc1 = DC1(3) + +assert_type(dc1.y, int) +assert_type(DC1.y, Desc1) + + +class Desc2(Generic[T]): + @overload + def __get__(self, instance: None, owner: Any) -> list[T]: + ... + + @overload + def __get__(self, instance: object, owner: Any) -> T: + ... + + def __get__(self, instance: object | None, owner: Any) -> list[T] | T: + ... + + +@dataclass +class DC2: + x: Desc2[int] + y: Desc2[str] + z: Desc2[str] = Desc2() + + +assert_type(DC2.x, list[int]) +assert_type(DC2.y, list[str]) +assert_type(DC2.z, list[str]) + +dc2 = DC2(Desc2(), Desc2(), Desc2()) +assert_type(dc2.x, int) +assert_type(dc2.y, str) +assert_type(dc2.z, str) diff --git a/conformance/tests/dataclasses_frozen.py b/conformance/tests/dataclasses_frozen.py new file mode 100644 index 000000000..1717adf06 --- /dev/null +++ b/conformance/tests/dataclasses_frozen.py @@ -0,0 +1,42 @@ +""" +Tests validation of frozen dataclass instances. +""" + +# Specification: https://peps.python.org/pep-0557/#frozen-instances + +from dataclasses import dataclass + +@dataclass(frozen=True) +class DC1: + a: float + b: str + +dc1 = DC1(1, "") + +dc1.a = 1 # Type error: dataclass is frozen +dc1.b = "" # Type error: dataclass is frozen + + +# This should generate an error because a non-frozen dataclass +# cannot inherit from a frozen dataclass. +@dataclass +class DC2(DC1): + pass + +@dataclass +class DC3: + a: int + +# This should generate an error because a frozen dataclass +# cannot inherit from a non-frozen dataclass. +@dataclass(frozen=True) +class DC4(DC3): + pass + + +@dataclass(frozen=True) +class DC1Child(DC1): + # This should be allowed because attributes within a frozen + # dataclass are covariant rather than invariant. + a: int + diff --git a/conformance/tests/dataclasses_hash.py b/conformance/tests/dataclasses_hash.py new file mode 100644 index 000000000..83952b471 --- /dev/null +++ b/conformance/tests/dataclasses_hash.py @@ -0,0 +1,70 @@ +""" +Tests the synthesis of the __hash__ method in a dataclass. +""" + +from dataclasses import dataclass +from typing import Hashable + + +@dataclass +class DC1: + a: int + + +# This should generate an error because DC1 isn't hashable. +v1: Hashable = DC1(0) + + +@dataclass(eq=True, frozen=True) +class DC2: + a: int + + +v2: Hashable = DC2(0) + + +@dataclass(eq=True) +class DC3: + a: int + + +# This should generate an error because DC3 isn't hashable. +v3: Hashable = DC3(0) + + +@dataclass(frozen=True) +class DC4: + a: int + + +v4: Hashable = DC4(0) + + +@dataclass(eq=True, unsafe_hash=True) +class DC5: + a: int + + +v5: Hashable = DC5(0) + + +@dataclass(eq=True) +class DC6: + a: int + + def __hash__(self) -> int: + return 0 + + +v6: Hashable = DC6(0) + + +@dataclass(frozen=True) +class DC7: + a: int + + def __eq__(self, other) -> bool: + return self.a == other.a + + +v7: Hashable = DC7(0) diff --git a/conformance/tests/dataclasses_inheritance.py b/conformance/tests/dataclasses_inheritance.py new file mode 100644 index 000000000..5ae7e9a48 --- /dev/null +++ b/conformance/tests/dataclasses_inheritance.py @@ -0,0 +1,64 @@ +""" +Tests inheritance rules for dataclasses. +""" + +# Specification: https://peps.python.org/pep-0557/#inheritance + +from dataclasses import dataclass +from typing import Any, ClassVar + + +@dataclass +class DC1: + a: int + b: str = "" + + +@dataclass +class DC2(DC1): + b: str = "" + a: int = 1 + + +dc2_1 = DC2(1, "") + +dc2_2 = DC2() + + +@dataclass +class DC3: + x: float = 15.0 + y: str = "" + + +@dataclass +class DC4(DC3): + z: tuple[int] = (10,) + x: float = 15 + + +dc4_1 = DC4(0.0, "", (1,)) + + +@dataclass +class DC5: + # This should generate an error because a default value of + # type list, dict, or set generate a runtime error. + x: list[int] = [] + + +@dataclass +class DC6: + x: int + y: ClassVar[int] = 1 + + +@dataclass +class DC7(DC6): + # This should generate an error because a ClassVar cannot override + # an instance variable of the same name. + x: ClassVar[int] + + # This should generate an error because an instance variable cannot + # override a class variable of the same name. + y: int diff --git a/conformance/tests/dataclasses_kwonly.py b/conformance/tests/dataclasses_kwonly.py new file mode 100644 index 000000000..011a88eb1 --- /dev/null +++ b/conformance/tests/dataclasses_kwonly.py @@ -0,0 +1,62 @@ +""" +Tests the keyword-only feature of dataclass added in Python 3.10. +""" + +# Specification: https://docs.python.org/3/library/dataclasses.html#module-contents + +from dataclasses import dataclass, KW_ONLY, field + + +@dataclass +class DC1: + a: str + _: KW_ONLY + b: int = 0 + + +DC1("hi") +DC1(a="hi") +DC1(a="hi", b=1) +DC1("hi", b=1) + +# This should generate an error because "b" is keyword-only. +DC1("hi", 1) + + +@dataclass +class DC2: + b: int = field(kw_only=True, default=3) + a: str + + +DC2("hi") +DC2(a="hi") +DC2(a="hi", b=1) +DC2("hi", b=1) + +# This should generate an error because "b" is keyword-only. +DC2("hi", 1) + + +@dataclass(kw_only=True) +class DC3: + a: str = field(kw_only=False) + b: int = 0 + + +DC3("hi") +DC3(a="hi") +DC3(a="hi", b=1) +DC3("hi", b=1) + +# This should generate an error because "b" is keyword-only. +DC3("hi", 1) + + +@dataclass +class DC4(DC3): + c: float + + +DC4("", 0.2, b=3) +DC4(a="", b=3, c=0.2) diff --git a/conformance/tests/dataclasses_order.py b/conformance/tests/dataclasses_order.py new file mode 100644 index 000000000..dce831c7e --- /dev/null +++ b/conformance/tests/dataclasses_order.py @@ -0,0 +1,55 @@ +""" +Tests the synthesized comparison methods for dataclasses. +""" + +from dataclasses import dataclass + +@dataclass(order=True) +class DC1: + a: str + b: int + + +@dataclass(order=True) +class DC2: + a: str + b: int + + +dc1_1 = DC1("", 0) +dc1_2 = DC1("", 0) + +if dc1_1 < dc1_2: + pass + +if dc1_1 <= dc1_2: + pass + +if dc1_1 > dc1_2: + pass + +if dc1_1 >= dc1_2: + pass + +if dc1_1 == dc1_2: + pass + +if dc1_1 != dc1_2: + pass + +if dc1_1 == None: + pass + +if dc1_1 != None: + pass + +dc2_1 = DC2("hi", 2) + +# This should generate an error because the types are +# incompatible. +if dc1_1 < dc2_1: + pass + +if dc1_1 != dc2_1: + pass + diff --git a/conformance/tests/dataclasses_postinit.py b/conformance/tests/dataclasses_postinit.py new file mode 100644 index 000000000..e20b26248 --- /dev/null +++ b/conformance/tests/dataclasses_postinit.py @@ -0,0 +1,55 @@ +""" +Tests type checking of the __post_init__ method in a dataclass. +""" + +# Specification: https://peps.python.org/pep-0557/#post-init-processing + +from dataclasses import InitVar, dataclass, field, replace +from typing import assert_type + + +@dataclass +class DC1: + a: int + b: int + x: InitVar[int] + c: int + y: InitVar[str] + + def __post_init__(self, x: int, y: int) -> None: # Type error: wrong type for y + pass + + +dc1 = DC1(1, 2, 3, 4, "") + +assert_type(dc1.a, int) +assert_type(dc1.b, int) +assert_type(dc1.c, int) +print(dc1.x) # Type error: cannot access InitVar +print(dc1.y) # Type error: cannot access InitVar + +@dataclass +class DC2: + x: InitVar[int] + y: InitVar[str] + + def __post_init__(self, x: int) -> None: # Type error: missing y + pass + + +@dataclass +class DC3: + _name: InitVar[str] = field() + name: str = field(init=False) + + def __post_init__(self, _name: str): + ... + + +@dataclass +class DC4(DC3): + _age: InitVar[int] = field() + age: int = field(init=False) + + def __post_init__(self, _name: str, _age: int): + ... diff --git a/conformance/tests/dataclasses_slots.py b/conformance/tests/dataclasses_slots.py new file mode 100644 index 000000000..3c8c14443 --- /dev/null +++ b/conformance/tests/dataclasses_slots.py @@ -0,0 +1,70 @@ +""" +Tests the slots functionality of dataclass added in Python 3.10. +""" + +# Specification: https://docs.python.org/3/library/dataclasses.html#module-contents + +from dataclasses import dataclass +from typing import Iterable, assert_type + +# This should generate an error because __slots__ is already defined. +@dataclass(slots=True) +class DC1: + x: int + + __slots__ = () + + +@dataclass(slots=True) +class DC2: + x: int + + def __init__(self): + self.x = 3 + + # This should generate an error because "y" is not in slots. + self.y = 3 + + +@dataclass(slots=False) +class DC3: + x: int + + __slots__ = ("x",) + + def __init__(self): + self.x = 3 + + # This should generate an error because "y" is not in slots. + self.y = 3 + + +@dataclass +class DC4: + __slots__ = ("y", "x") + x: int + y: str + + +DC4(1, "bar") + + +@dataclass(slots=True) +class DC5: + a: int + + +DC5.__slots__ +DC5(1).__slots__ + + +@dataclass +class DC6: + a: int + + +# This should generate an error because __slots__ is not defined. +DC6.__slots__ + +# This should generate an error because __slots__ is not defined. +DC6(1).__slots__ diff --git a/conformance/tests/dataclasses_transform_class.py b/conformance/tests/dataclasses_transform_class.py new file mode 100644 index 000000000..ca3278243 --- /dev/null +++ b/conformance/tests/dataclasses_transform_class.py @@ -0,0 +1,119 @@ +""" +Tests the dataclass_transform mechanism when it is applied to a base class. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/dataclasses.html#the-dataclass-transform-decorator + +from typing import Any, Generic, TypeVar, dataclass_transform + +T = TypeVar("T") + + +class ModelField: + def __init__(self, *, init: bool = True, default: Any | None = None) -> None: + ... + + +def model_field( + *, init: bool = True, default: Any | None = None, alias: str | None = None +) -> Any: + ... + + +@dataclass_transform( + kw_only_default=True, + field_specifiers=(ModelField, model_field), +) +class ModelBase: + not_a_field: str + + def __init_subclass__( + cls, + *, + frozen: bool = False, + kw_only: bool = True, + order: bool = True, + ) -> None: + ... + + +class Customer1(ModelBase, frozen=True): + id: int = model_field() + name: str = model_field() + name2: str = model_field(alias="other_name", default="None") + + +# This should generate an error because a non-frozen dataclass cannot +# derive from a frozen one. +class Customer1Subclass(Customer1): + salary: float = model_field() + + +class Customer2(ModelBase, order=True): + id: int + name: str = model_field(default="None") + + +c1_1 = Customer1(id=3, name="Sue", other_name="Susan") + +# This should generate an error because the class is frozen. +c1_1.id = 4 + +# This should generate an error because the class is kw_only. +c1_2 = Customer1(3, "Sue") + +c1_3 = Customer1(id=3, name="John") + +# This should generate an error because comparison methods are +# not synthesized. +v1 = c1_1 < c1_2 + +c2_1 = Customer2(id=0, name="John") + +c2_2 = Customer2(id=1) + +v2 = c2_1 < c2_2 + +# This should generate an error because Customer2 supports +# keyword-only parameters for its constructor. +c2_3 = Customer2(0, "John") + + +@dataclass_transform( + kw_only_default=True, + field_specifiers=(ModelField, model_field), +) +class GenericModelBase(Generic[T]): + not_a_field: T + + def __init_subclass__( + cls, + *, + frozen: bool = False, + kw_only: bool = True, + order: bool = True, + ) -> None: + ... + + +class GenericCustomer(GenericModelBase[int]): + id: int = model_field() + + +gc_1 = GenericCustomer(id=3) + + +@dataclass_transform(frozen_default=True) +class ModelBaseFrozen: + not_a_field: str + + +class Customer3(ModelBaseFrozen): + id: int + name: str + + +c3_1 = Customer3(id=2, name="hi") + +# This should generate an error because Customer3 is frozen. +c3_1.id = 4 diff --git a/conformance/tests/dataclasses_transform_field.py b/conformance/tests/dataclasses_transform_field.py new file mode 100644 index 000000000..09e4ce6a5 --- /dev/null +++ b/conformance/tests/dataclasses_transform_field.py @@ -0,0 +1,77 @@ +""" +Tests the dataclass_transform mechanism honors implicit default values +in field parameters. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/dataclasses.html#field-specifier-parameters + +from typing import Any, Callable, Literal, TypeVar, dataclass_transform, overload + +T = TypeVar("T") + +# > Field specifier functions can use overloads that implicitly specify the +# > value of init using a literal bool value type (Literal[False] or Literal[True]). + +@overload +def field1( + *, + default: str | None = None, + resolver: Callable[[], Any], + init: Literal[False] = False, +) -> Any: + ... + + +@overload +def field1( + *, + default: str | None = None, + resolver: None = None, + init: Literal[True] = True, +) -> Any: + ... + + +def field1( + *, + default: str | None = None, + resolver: Callable[[], Any] | None = None, + init: bool = True, +) -> Any: + ... + + +def field2(*, init: bool = False, kw_only: bool = True) -> Any: + ... + + +@dataclass_transform(kw_only_default=True, field_specifiers=(field1, field2)) +def create_model(*, init: bool = True) -> Callable[[type[T]], type[T]]: + ... + + +@create_model() +class CustomerModel1: + id: int = field1(resolver=lambda: 0) + name: str = field1(default="Voldemort") + + +CustomerModel1() +CustomerModel1(name="hi") + +# This should generate an error because "id" is not +# supposed to be part of the init function. +CustomerModel1(id=1, name="hi") + + +@create_model() +class CustomerModel2: + id: int = field2() + name: str = field2(init=True) + + +# This should generate an error because kw_only is True +# by default for field2. +CustomerModel2(1) + +CustomerModel2(name="Fred") diff --git a/conformance/tests/dataclasses_transform_func.py b/conformance/tests/dataclasses_transform_func.py new file mode 100644 index 000000000..0dc1b2623 --- /dev/null +++ b/conformance/tests/dataclasses_transform_func.py @@ -0,0 +1,97 @@ +""" +Tests the dataclass_transform mechanism when it is applied to a decorator function. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/dataclasses.html#the-dataclass-transform-decorator + +from typing import Any, Callable, TypeVar, dataclass_transform, overload + +T = TypeVar("T") + + +@overload +@dataclass_transform(kw_only_default=True, order_default=True) +def create_model(cls: T) -> T: + ... + + +@overload +@dataclass_transform(kw_only_default=True, order_default=True) +def create_model( + *, + frozen: bool = False, + kw_only: bool = True, + order: bool = True, +) -> Callable[[T], T]: + ... + + +def create_model(*args: Any, **kwargs: Any) -> Any: + ... + + +@create_model(kw_only=False, order=False) +class Customer1: + id: int + name: str + + +@create_model(frozen=True) +class Customer2: + id: int + name: str + + +@create_model(frozen=True) +class Customer2Subclass(Customer2): + salary: float + + +c1_1 = Customer1(id=3, name="Sue") +c1_1.id = 4 + +c1_2 = Customer1(3, "Sue") +c1_2.name = "Susan" + +# This should generate an error because of a type mismatch. +c1_2.name = 3 + +# This should generate an error because comparison methods are +# not synthesized. +v1 = c1_1 < c1_2 + +# This should generate an error because salary is not +# a defined field. +c1_3 = Customer1(id=3, name="Sue", salary=40000) + +c2_1 = Customer2(id=0, name="John") + +# This should generate an error because Customer2 supports +# keyword-only parameters for its constructor. +c2_2 = Customer2(0, "John") + +v2 = c2_1 < c2_2 + + +@dataclass_transform(kw_only_default=True, order_default=True, frozen_default=True) +def create_model_frozen(cls: T) -> T: + ... + + +@create_model_frozen +class Customer3: + id: int + name: str + + +# This should generate an error because a non-frozen class +# cannot inherit from a frozen class. +@create_model +class Customer3Subclass(Customer3): + age: int + + +c3_1 = Customer3(id=2, name="hi") + +# This should generate an error because Customer3 is frozen. +c3_1.id = 4 diff --git a/conformance/tests/dataclasses_transform_meta.py b/conformance/tests/dataclasses_transform_meta.py new file mode 100644 index 000000000..3d1fb76ca --- /dev/null +++ b/conformance/tests/dataclasses_transform_meta.py @@ -0,0 +1,100 @@ +""" +Tests the dataclass_transform mechanism when it is applied to a metaclass. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/dataclasses.html#the-dataclass-transform-decorator + + +from typing import Any, dataclass_transform + +class ModelField: + def __init__(self, *, init: bool = True, default: Any | None = None) -> None: + ... + + +def model_field( + *, init: bool = True, default: Any | None = None, alias: str | None = None +) -> Any: + ... + + +@dataclass_transform( + kw_only_default=True, + field_specifiers=(ModelField, model_field), +) +class ModelMeta(type): + not_a_field: str + + +class ModelBase(metaclass=ModelMeta): + def __init_subclass__( + cls, + *, + frozen: bool = False, + kw_only: bool = True, + order: bool = True, + ) -> None: + ... + + +class Customer1(ModelBase, frozen=True): + id: int = model_field() + name: str = model_field() + name2: str = model_field(alias="other_name", default="None") + + +# This should generate an error because a non-frozen class cannot +# derive from a frozen one. +class Customer1Subclass(Customer1, frozen=False): + salary: float = model_field() + + +class Customer2(ModelBase, order=True): + id: int + name: str = model_field(default="None") + + +c1_1 = Customer1(id=3, name="Sue", other_name="Susan") + +# This should generate an error because the class is frozen. +c1_1.id = 4 + +# This should generate an error because the class is kw_only. +c1_2 = Customer1(3, "Sue") + +# This should generate an error because other_name is missing. +c1_3 = Customer1(id=3, name="John") + +# This should generate an error because comparison methods are +# not synthesized. +v1 = c1_1 < c1_2 + +c2_1 = Customer2(id=0, name="John") + +c2_2 = Customer2(id=1) + +v2 = c2_1 < c2_2 + +# This should generate an error because Customer2 supports +# keyword-only parameters for its constructor. +c2_3 = Customer2(0, "John") + + +@dataclass_transform(frozen_default=True) +class ModelMetaFrozen(type): + pass + + +class ModelBaseFrozen(metaclass=ModelMetaFrozen): + ... + + +class Customer3(ModelBaseFrozen): + id: int + name: str + + +c3_1 = Customer3(id=2, name="hi") + +# This should generate an error because Customer3 is frozen. +c3_1.id = 4 diff --git a/conformance/tests/dataclasses_usage.py b/conformance/tests/dataclasses_usage.py new file mode 100644 index 000000000..d41a93f45 --- /dev/null +++ b/conformance/tests/dataclasses_usage.py @@ -0,0 +1,230 @@ +""" +Tests basic handling of the dataclass factory. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/dataclasses.html +# Also, see https://peps.python.org/pep-0557/ + +from dataclasses import InitVar, dataclass, field +from typing import Any, Callable, ClassVar, Generic, Protocol, TypeVar, assert_type + +T = TypeVar("T") + + +@dataclass(order=True) +class InventoryItem: + x = 0 + name: str + unit_price: float + quantity_on_hand: int = 0 + + def total_cost(self) -> float: + return self.unit_price * self.quantity_on_hand + + +v1 = InventoryItem("soap", 2.3) + + +class InventoryItemInitProto(Protocol): + def __call__( + self, name: str, unit_price: float, quantity_on_hand: int = ... + ) -> None: + ... + + +# Validate the type of the synthesized __init__ method. +x1: InventoryItemInitProto = v1.__init__ + +# Make sure the following additional methods were synthesized. +print(v1.__repr__) +print(v1.__eq__) +print(v1.__ne__) +print(v1.__lt__) +print(v1.__le__) +print(v1.__gt__) +print(v1.__ge__) + +assert_type(v1.name, str) +assert_type(v1.unit_price, float) +assert_type(v1.quantity_on_hand, int) + +v2 = InventoryItem("name") # Type error: missing unit_price +v3 = InventoryItem("name", "price") # Type error: incorrect type for unit_price +v4 = InventoryItem("name", 3.1, 3, 4) # Type error: too many arguments + + +# > TypeError will be raised if a field without a default value follows a +# > field with a default value. This is true either when this occurs in a +# > single class, or as a result of class inheritance. +@dataclass +class DC1: + a: int = 0 + b: int # Error: field with no default cannot follow field with default. + + +@dataclass +class DC2: + a: int = field(default=1) + b: int # Error: field with no default cannot follow field with default. + + +@dataclass +class DC3: + a: InitVar[int] = 0 + b: int # Error: field with no default cannot follow field with default. + + +@dataclass +class DC4: + a: int = field(init=False) + b: int + + +v5 = DC4(0) +v6 = DC4(0, 1) # Type error: too many parameters + + +@dataclass +class DC5: + a: int = field(default_factory=str) # Type error: type mismatch + + +def f(s: str) -> int: + return int(s) + + +@dataclass +class DC6: + a: ClassVar[int] = 0 + b: str + c: Callable[[str], int] = f + + +dc6 = DC6("") +assert_type(dc6.a, int) +assert_type(DC6.a, int) +assert_type(dc6.b, str) +assert_type(dc6.c, Callable[[str], int]) + + +@dataclass +class DC7: + x: int + + +@dataclass(init=False) +class DC8(DC7): + y: int + + def __init__(self, a: DC7, y: int): + self.__dict__ = a.__dict__ + + +a = DC7(3) +b = DC8(a, 5) + +# This should generate an error because there is an extra parameter +DC7(3, 4) + +# This should generate an error because there is one too few parameters +DC8(a) + + +@dataclass +class DC9: + a: str = field(init=False, default="s") + b: bool = field() + + +@dataclass +class DC10(DC9): + a: str = field() + b: bool = field() + + +@dataclass(init=False) +class DC11: + x: int + x_squared: int + + def __init__(self, x: int): + self.x = x + self.x_squared = x**2 + + +DC11(3) + + +@dataclass(init=True) +class DC12: + x: int + x_squared: int + + def __init__(self, x: int): + self.x = x + self.x_squared = x**2 + + +DC12(3) + + +@dataclass(init=False) +class DC13: + x: int + x_squared: int + + +# This should generate an error because there is no +# override __init__ method and no synthesized __init__. +DC13(3) + + +@dataclass +class DC14: + prop_1: str = field(init=False) + prop_2: str = field(default="hello") + prop_3: str = field(default_factory=lambda: "hello") + + +@dataclass +class DC15(DC14): + prop_2: str + + +dc15 = DC15(prop_2="test") + +assert_type(dc15.prop_1, str) +assert_type(dc15.prop_2, str) +assert_type(dc15.prop_3, str) + + +class DataclassProto(Protocol): + # Checking for this attribute seems to currently be + # the most reliable way to ascertain that something is a dataclass + __dataclass_fields__: dict[str, Any] + + +v1: DataclassProto = dc15 + + +@dataclass +class DC16(Generic[T]): + value: T + + +assert_type(DC16(1), DC16[int]) + + +class DC17(DC16[str]): + pass + + +assert_type(DC17(""), DC17) + + +@dataclass +class DC18: + x: int = field() + # This should generate an error because an unannotated field + # will result in a runtime exception. + y = field() diff --git a/conformance/tests/generics_self_advanced.py b/conformance/tests/generics_self_advanced.py new file mode 100644 index 000000000..9a837e40f --- /dev/null +++ b/conformance/tests/generics_self_advanced.py @@ -0,0 +1,46 @@ +""" +Tests for advanced or special-case usage of the typing.Self type. +""" + +from typing import assert_type, Self + + +class ParentA: + # Test for property that returns Self. + @property + def prop1(self) -> Self: + ... + +class ChildA(ParentA): + ... + + +assert_type(ParentA().prop1, ParentA) +assert_type(ChildA().prop1, ChildA) + + +# Test for a child that accesses an attribute within a parent +# whose type is annotated using Self. +class ParentB: + a: list[Self] + + @classmethod + def method1(cls) -> Self: + ... + +class ChildB(ParentB): + b: int = 0 + + def method2(self) -> None: + assert_type(self, Self) + assert_type(self.a, list[Self]) + assert_type(self.a[0], Self) + assert_type(self.method1(), Self) + + @classmethod + def method3(cls) -> None: + assert_type(cls, type[Self]) + assert_type(cls.a, list[Self]) + assert_type(cls.a[0], Self) + assert_type(cls.method1(), Self) + diff --git a/conformance/tests/generics_self_attributes.py b/conformance/tests/generics_self_attributes.py new file mode 100644 index 000000000..2fad31cb9 --- /dev/null +++ b/conformance/tests/generics_self_attributes.py @@ -0,0 +1,34 @@ +""" +Tests for usage of the typing.Self type with attributes. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#use-in-attribute-annotations + +from typing import TypeVar, Generic, Self +from dataclasses import dataclass + + +T = TypeVar("T") + +@dataclass +class LinkedList(Generic[T]): + value: T + next: Self | None = None + + +@dataclass +class OrdinalLinkedList(LinkedList[int]): + def ordinal_value(self) -> str: + return str(self.value) + + +# This should result in a type error. +xs = OrdinalLinkedList(value=1, next=LinkedList[int](value=2)) + +if xs.next is not None: + xs.next = OrdinalLinkedList(value=3, next=None) # OK + + # This should result in a type error. + xs.next = LinkedList[int](value=3, next=None) + + diff --git a/conformance/tests/generics_self_basic.py b/conformance/tests/generics_self_basic.py new file mode 100644 index 000000000..aea671078 --- /dev/null +++ b/conformance/tests/generics_self_basic.py @@ -0,0 +1,82 @@ +""" +Tests for basic usage of the typing.Self type. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#self. + +from typing import Callable, Generic, Self, TypeVar, assert_type + +T = TypeVar("T") + +class Shape: + def set_scale(self, scale: float) -> Self: + assert_type(self, Self) + self.scale = scale + return self + + def method2(self) -> Self: + # This should result in a type error. + return Shape() + + def method3(self) -> "Shape": + return self + + @classmethod + def from_config(cls, config: dict[str, float]) -> Self: + assert_type(cls, type[Self]) + return cls(config["scale"]) + + @classmethod + def cls_method2(cls) -> Self: + # This should result in a type error. + return Shape() + + @classmethod + def cls_method3(cls) -> "Shape": + return cls() + + def difference(self, other: Self) -> float: + assert_type(other, Self) + return 0.0 + + def apply(self, f: Callable[[Self], None]) -> None: + return f(self) + + +class Circle(Shape): + pass + + +assert_type(Shape().set_scale(1.0), Shape) +assert_type(Circle().set_scale(1.0), Circle) + +assert_type(Shape.from_config({}), Shape) +assert_type(Circle.from_config({}), Circle) + + +class Container(Generic[T]): + value: T + + def set_value(self, value: T) -> Self: + ... + + # This should generate an error because Self isn't subscriptable. + def foo(self, other: Self[int]) -> None: + pass + + +def object_with_concrete_type( + int_container: Container[int], str_container: Container[str] +) -> None: + assert_type(int_container.set_value(42), Container[int]) + assert_type(str_container.set_value("hello"), Container[str]) + + +def object_with_generic_type( + container: Container[T], + value: T, +) -> Container[T]: + val = container.set_value(value) + assert_type(val, Container[T]) + return val + diff --git a/conformance/tests/generics_self_protocols.py b/conformance/tests/generics_self_protocols.py new file mode 100644 index 000000000..6f27bf0b9 --- /dev/null +++ b/conformance/tests/generics_self_protocols.py @@ -0,0 +1,64 @@ +""" +Tests for usage of the typing.Self type with protocols. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#use-in-protocols + +from typing import Protocol, Self, assert_type + + +class ShapeProtocol(Protocol): + def set_scale(self, scale: float) -> Self: + ... + + +class ReturnSelf: + scale: float = 1.0 + + def set_scale(self, scale: float) -> Self: + self.scale = scale + return self + + +class ReturnConcreteShape: + scale: float = 1.0 + + def set_scale(self, scale: float) -> "ReturnConcreteShape": + self.scale = scale + return self + + +class BadReturnType: + scale: float = 1.0 + + def set_scale(self, scale: float) -> int: + self.scale = scale + return 42 + + +class ReturnDifferentClass: + scale: float = 1.0 + + def set_scale(self, scale: float) -> ReturnConcreteShape: + return ReturnConcreteShape() + + +def accepts_shape(shape: ShapeProtocol) -> None: + y = shape.set_scale(0.5) + assert_type(y, ShapeProtocol) + + +def main( + return_self_shape: ReturnSelf, + return_concrete_shape: ReturnConcreteShape, + bad_return_type: BadReturnType, + return_different_class: ReturnDifferentClass, +) -> None: + accepts_shape(return_self_shape) # OK + accepts_shape(return_concrete_shape) # OK + + # This should generate a type error. + accepts_shape(bad_return_type) + + # Not OK because it returns a non-subclass. + accepts_shape(return_different_class) diff --git a/conformance/tests/generics_self_usage.py b/conformance/tests/generics_self_usage.py new file mode 100644 index 000000000..657849b24 --- /dev/null +++ b/conformance/tests/generics_self_usage.py @@ -0,0 +1,126 @@ +""" +Tests for valid and invalid usage of the typing.Self type. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#valid-locations-for-self + +from typing import Any, Callable, Generic, Self, TypeAlias, TypeVar + +class ReturnsSelf: + def foo(self) -> Self: # Accepted + return self + + @classmethod + def bar(cls) -> Self: # Accepted + return cls(1) + + def __new__(cls, value: int) -> Self: # Accepted + return cls(1) + + def explicitly_use_self(self: Self) -> Self: # Accepted + return self + + # Accepted (Self can be nested within other types) + def returns_list(self) -> list[Self]: + return [] + + # Accepted (Self can be nested within other types) + @classmethod + def return_cls(cls) -> type[Self]: + return cls + +class Child(ReturnsSelf): + # Accepted (we can override a method that uses Self annotations) + def foo(self) -> Self: + return self + +class TakesSelf: + def foo(self, other: Self) -> bool: # Accepted + return True + +class Recursive: + # Accepted (treated as an @property returning ``Self | None``) + next: Self | None + +class CallableAttribute: + def foo(self) -> int: + return 0 + + # Accepted (treated as an @property returning the Callable type) + bar: Callable[[Self], int] = foo + +class HasNestedFunction: + x: int = 42 + + def foo(self) -> None: + + # Accepted (Self is bound to HasNestedFunction). + def nested(z: int, inner_self: Self) -> Self: + print(z) + print(inner_self.x) + return inner_self + + nested(42, self) # OK + + +class Outer: + class Inner: + def foo(self) -> Self: # Accepted (Self is bound to Inner) + return self + + +# This should generate an error. +def foo(bar: Self) -> Self: ... # Rejected (not within a class) + +# This should generate an error. +bar: Self # Rejected (not within a class) + +TFoo2 = TypeVar("TFoo2", bound="Foo2") + +class Foo2: + # Rejected (Self is treated as unknown). + def has_existing_self_annotation(self: TFoo2) -> Self: ... + +class Foo3: + def return_concrete_type(self) -> Self: + return Foo3() # Rejected (see FooChild below for rationale) + +class Foo3Child(Foo3): + child_value: int = 42 + + def child_method(self) -> None: + y = self.return_concrete_type() + y.child_value + +T = TypeVar("T") + +class Bar(Generic[T]): + def bar(self) -> T: ... + +# This should generate an error. +class Baz(Bar[Self]): ... # Rejected + +class Baz2(Self): ... # Rejected + +# This should generate an error. +TupleSelf: TypeAlias = tuple[Self] # Rejected + +class Base: + @staticmethod + # This should generate an error. + def make() -> Self: # Rejected + ... + + @staticmethod + # This should generate an error. + def return_parameter(foo: Self) -> Self: # Rejected + ... + +class MyMetaclass(type): + # This should generate an error. + def __new__(cls, *args: Any) -> Self: # Rejected + ... + + # This should generate an error. + def __mul__(cls, count: int) -> list[Self]: # Rejected + ... diff --git a/conformance/tests/literals_interactions.py b/conformance/tests/literals_interactions.py new file mode 100644 index 000000000..f9782c4f1 --- /dev/null +++ b/conformance/tests/literals_interactions.py @@ -0,0 +1,116 @@ +""" +Tests interactions between Literal types and other typing features. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/literal.html#interactions-with-other-types-and-features + +from enum import Enum +from typing import IO, Any, Final, Generic, Literal, TypeVar, assert_type, overload + + +def func1(v: tuple[int, str, list[bool]], a: Literal[0], b: Literal[5], c: Literal[-5]): + assert_type(v[a], int) + assert_type(v[2], list[bool]) + + v[b] # Type error: index out of range + v[c] # Type error: index out of range + v[4] # Type error: index out of range + v[-4] # Type error: index out of range + + +_PathType = str | bytes | int + + +@overload +def open( + path: _PathType, + mode: Literal["r", "w", "a", "x", "r+", "w+", "a+", "x+"], +) -> IO[str]: + ... + + +@overload +def open( + path: _PathType, + mode: Literal["rb", "wb", "ab", "xb", "r+b", "w+b", "a+b", "x+b"], +) -> IO[bytes]: + ... + + +@overload +def open(path: _PathType, mode: str) -> IO[Any]: + ... + + +def open(path: _PathType, mode: Any) -> Any: + pass + + +assert_type(open("path", "r"), IO[str]) +assert_type(open("path", "wb"), IO[bytes]) +assert_type(open("path", "other"), IO[Any]) + + +A = TypeVar("A", bound=int) +B = TypeVar("B", bound=int) +C = TypeVar("C", bound=int) + + +class Matrix(Generic[A, B]): + def __add__(self, other: "Matrix[A, B]") -> "Matrix[A, B]": + ... + + def __matmul__(self, other: "Matrix[B, C]") -> "Matrix[A, C]": + ... + + def transpose(self) -> "Matrix[B, A]": + ... + + +def func2(a: Matrix[Literal[2], Literal[3]], b: Matrix[Literal[3], Literal[7]]): + c = a @ b + assert_type(c, Matrix[Literal[2], Literal[7]]) + + +T = TypeVar("T", Literal["a"], Literal["b"], Literal["c"]) +S = TypeVar("S", bound=Literal["foo"]) + + +class Status(Enum): + SUCCESS = 0 + INVALID_DATA = 1 + FATAL_ERROR = 2 + + +def parse_status1(s: str | Status) -> None: + if s is Status.SUCCESS: + assert_type(s, Literal[Status.SUCCESS]) + elif s is Status.INVALID_DATA: + assert_type(s, Literal[Status.INVALID_DATA]) + elif s is Status.FATAL_ERROR: + assert_type(s, Literal[Status.FATAL_ERROR]) + else: + assert_type(s, str) + + +def expects_bad_status(status: Literal["MALFORMED", "ABORTED"]): + ... + + +def expects_pending_status(status: Literal["PENDING"]): + ... + + +def parse_status2(status: str) -> None: + if status in ("MALFORMED", "ABORTED"): + return expects_bad_status(status) + + if status == "PENDING": + expects_pending_status(status) + + +final_val1: Final = 3 +assert_type(final_val1, Literal[3]) + +final_val2: Final = True +assert_type(final_val2, Literal[True]) diff --git a/conformance/tests/literals_literalstring.py b/conformance/tests/literals_literalstring.py new file mode 100644 index 000000000..10b50e080 --- /dev/null +++ b/conformance/tests/literals_literalstring.py @@ -0,0 +1,170 @@ +""" +Tests handling of the LiteralString special form. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/literal.html#literalstring + + +from typing import ( + Any, + Generic, + Literal, + LiteralString, + Sequence, + TypeVar, + assert_type, + overload, +) + + +variable_annotation: LiteralString + + +def my_function(literal_string: LiteralString) -> LiteralString: + ... + + +class Foo: + my_attribute: LiteralString = "" + + +type_argument: list[LiteralString] + +T = TypeVar("T", bound=LiteralString) + + +bad_union: Literal["hello", LiteralString] # Type error +bad_nesting: Literal[LiteralString] # Type error + + +def func1(a: Literal["one"], b: Literal["two"]): + x1: LiteralString = a + + x2: Literal[""] = b # Type error + + +def func2(a: LiteralString, b: LiteralString): + # > Addition: x + y is of type LiteralString if both x and y are compatible with LiteralString. + assert_type(a + b, LiteralString) + + # > Joining: sep.join(xs) is of type LiteralString if sep’s type is + # > compatible with LiteralString and xs’s type is compatible with Iterable[LiteralString]. + assert_type(",".join((a, b)), LiteralString) + assert_type(",".join((a, str(b))), str) + + # > In-place addition: If s has type LiteralString and x has type compatible with + # > LiteralString, then s += x preserves s’s type as LiteralString. + a += "hello" + b += a + + # > String formatting: An f-string has type LiteralString if and only if its constituent + # > expressions are literal strings. s.format(...) has type LiteralString if and only if + # > s and the arguments have types compatible with LiteralString. + assert_type(f"{a} {b}", LiteralString) + + variable = 3 + x1: LiteralString = f"{a} {str(variable)}" # Type error + + assert_type(a + str(1), str) + + # > LiteralString is compatible with the type str + x2: str = a + + # > Other literal types, such as literal integers, are not compatible with LiteralString. + x3: LiteralString = 3 # Type error + x4: LiteralString = b"test" # Type error + + +# > Conditional statements and expressions work as expected. +def condition1() -> bool: + ... + + +def return_literal_string() -> LiteralString: + return "foo" if condition1() else "bar" # OK + + +def return_literal_str2(literal_string: LiteralString) -> LiteralString: + return "foo" if condition1() else literal_string # OK + + +def return_literal_str3() -> LiteralString: + result: LiteralString + if condition1(): + result = "foo" + else: + result = "bar" + + return result # OK + + +# > TypeVars can be bound to LiteralString. +TLiteral = TypeVar("TLiteral", bound=LiteralString) + + +def literal_identity(s: TLiteral) -> TLiteral: + return s + + +def func3(s: Literal["hello"]): + y = literal_identity(s) + assert_type(y, Literal["hello"]) + + +def func4(s: LiteralString): + y2 = literal_identity(s) + assert_type(y2, LiteralString) + + +def func5(s: str): + literal_identity(s) # Type error + + +# > LiteralString can be used as a type argument for generic classes. +class Container(Generic[T]): + def __init__(self, value: T) -> None: + self.value = value + + +def func6(s: LiteralString): + x: Container[LiteralString] = Container(s) # OK + + +def func7(s: str): + x_error: Container[LiteralString] = Container(s) # Type error + + +# > Standard containers like List work as expected. +xs: list[LiteralString] = ["foo", "bar", "baz"] + + +@overload +def func8(x: Literal["foo"]) -> int: + ... + + +@overload +def func8(x: LiteralString) -> bool: + ... + + +@overload +def func8(x: str) -> str: + ... + + +def func8(x: Any) -> Any: + ... + + +assert_type(func8("foo"), int) # First overload +assert_type(func8("bar"), bool) # Second overload +assert_type(func8(str(1)), str) # Third overload + + +def func9(val: list[LiteralString]): + x1: list[str] = val # Type error + + +def func10(val: Sequence[LiteralString]): + x1: Sequence[str] = val # OK diff --git a/conformance/tests/literals_parameterizations.py b/conformance/tests/literals_parameterizations.py new file mode 100644 index 000000000..0edc1d4d8 --- /dev/null +++ b/conformance/tests/literals_parameterizations.py @@ -0,0 +1,67 @@ +""" +Tests legal and illegal parameterizations of Literal. +""" + +# > Literal must be parameterized with at least one type. + +from typing import Any, Literal, TypeVar +from enum import Enum + + +class Color(Enum): + RED = 0 + GREEN = 1 + BLUE = 2 + + +good1: Literal[26] +good2: Literal[0x1A] +good3: Literal[-4] +good4: Literal["hello world"] +good5: Literal[b"hello world"] +good6: Literal["hello world"] +good7: Literal[True] +good8: Literal[Color.RED] +good9: Literal[None] + +ReadOnlyMode = Literal["r", "r+"] +WriteAndTruncateMode = Literal["w", "w+", "wt", "w+t"] +WriteNoTruncateMode = Literal["r+", "r+t"] +AppendMode = Literal["a", "a+", "at", "a+t"] + +AllModes = Literal[ReadOnlyMode, WriteAndTruncateMode, WriteNoTruncateMode, AppendMode] + +good10: Literal[Literal[Literal[1, 2, 3], "foo"], 5, None] + +variable = 3 +T = TypeVar("T") + +# > Arbitrary expressions [are illegal] +bad1: Literal[3 + 4] # Type error +bad2: Literal["foo".replace("o", "b")] # Type error +bad3: Literal[4 + 3j] # Type error +bad4: Literal[+5] # Type error +bad5: Literal[not False] # Type error +bad6: Literal[(1, "foo", "bar")] # Type error +bad7: Literal[{"a": "b", "c": "d"}] # Type error +bad8: Literal[int] # Type error +bad9: Literal[variable] # Type error +bad10: Literal[T] # Type error +bad11: Literal[3.14] # Type error +bad12: Literal[Any] # Type error +bad13: Literal[...] # Type error + + +def my_function(x: Literal[1 + 2]) -> int: # Type error + return x * 3 + +x: Literal # Type error +y: Literal[my_function] = my_function # Type error + + +def func2(a: Literal[Color.RED]): + x1: Literal["Color.RED"] = a # Type error + + x2: "Literal[Color.RED]" = a # OK + + diff --git a/conformance/tests/literals_semantics.py b/conformance/tests/literals_semantics.py new file mode 100644 index 000000000..a75b48c5d --- /dev/null +++ b/conformance/tests/literals_semantics.py @@ -0,0 +1,42 @@ +""" +Tests the semantics of the Literal special form. +""" + +from typing import Literal +from typing import Literal as L + + +v1: Literal[3] = 3 +v2: Literal[3] = 4 # Type error + +v3: L[-3] = -3 + + +# > Literal[20] and Literal[0x14] are equivalent +def func1(a: Literal[20], b: Literal[0x14], c: Literal[0b10100]): + x1: Literal[0x14] = a + x2: Literal[0x14] = b + x3: Literal[0x14] = c + + +# > Literal[0] and Literal[False] are not equivalent +def func2(a: Literal[0], b: Literal[False]): + x1: Literal[False] = a # Type Error + x2: Literal[0] = b # Type Error + + +# > Given some value v that is a member of type T, the type Literal[v] shall +# > be treated as a subtype of T. For example, Literal[3] is a subtype of int. +def func3(a: L[3, 4, 5]): + b = a.__add__(3) + c = a + 3 + a += 3 # Type error + + +# > When a Literal is parameterized with more than one value, it’s treated +# > as exactly equivalent to the union of those types. +def func4(a: L[None, 3] | L[3, "foo", b"bar", True]): + x1: Literal[3, b"bar", True, "foo", None] = a + a = x1 + + diff --git a/conformance/tests/narrowing_typeguard.py b/conformance/tests/narrowing_typeguard.py new file mode 100644 index 000000000..fbdbca821 --- /dev/null +++ b/conformance/tests/narrowing_typeguard.py @@ -0,0 +1,108 @@ +""" +Tests TypeGuard functionality. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/narrowing.html#typeguard + +from typing import Any, Self, TypeGuard, TypeVar, assert_type + + +T = TypeVar("T") + +def is_two_element_tuple(val: tuple[T, ...]) -> TypeGuard[tuple[T, T]]: + return len(val) == 2 + +def func1(names: tuple[str, ...]): + if is_two_element_tuple(names): + assert_type(names, tuple[str, str]) + else: + assert_type(names, tuple[str, ...]) + + +def is_str_list(val: list[object], allow_empty: bool) -> TypeGuard[list[str]]: + if len(val) == 0: + return allow_empty + return all(isinstance(x, str) for x in val) + +def is_set_of(val: set[Any], type: type[T]) -> TypeGuard[set[T]]: + return all(isinstance(x, type) for x in val) + +def func2(val: set[object]): + if is_set_of(val, int): + assert_type(val, set[int]) + else: + assert_type(val, set[object]) + + +T_A = TypeVar("T_A", bound="A") + +class A: + def tg_1(self, val: object) -> TypeGuard[int]: + return isinstance(val, int) + + @classmethod + def tg_2(cls, val: object) -> TypeGuard[int]: + return isinstance(val, int) + + @staticmethod + def tg_3(val: object) -> TypeGuard[int]: + return isinstance(val, int) + + def tg4(self, val: object) -> TypeGuard[Self]: + return isinstance(val, type(self)) + + def tg5(self: T_A, val: object) -> TypeGuard[T_A]: + return isinstance(val, type(self)) + +class B(A): + pass + +# > Type checkers should assume that type narrowing should be applied to +# > the expression that is passed as the first positional argument to a +# > user-defined type guard. If the type guard function accepts more than +# > one argument, no type narrowing is applied to those additional argument +# > expressions. + +def func3() -> None: + val1 = object() + if A().tg_1(val1): + assert_type(val1, int) + + val2 = object() + if A().tg_2(val2): + assert_type(val2, int) + + val3 = object() + if A.tg_2(val3): + assert_type(val3, int) + + val4 = object() + if A().tg_3(val4): + assert_type(val4, int) + + val5 = object() + if A.tg_3(val5): + assert_type(val5, int) + + val6 = object() + if B().tg4(val6): + assert_type(val6, B) + + val7 = object() + if B().tg4(val7): + assert_type(val7, B) + + +# > If a type guard function is implemented as an instance method or class +# > method, the first positional argument maps to the second parameter +# > (after “self” or “cls”). + +class C: + # Type checker should emit error here. + def tg_1(self) -> TypeGuard[int]: + return False + + @classmethod + # Type checker should emit error here. + def tg_2(cls) -> TypeGuard[int]: + return False diff --git a/conformance/tests/typeddicts_alt_syntax.py b/conformance/tests/typeddicts_alt_syntax.py new file mode 100644 index 000000000..1baf016cb --- /dev/null +++ b/conformance/tests/typeddicts_alt_syntax.py @@ -0,0 +1,45 @@ +""" +Tests the "alternative" (non-class) syntax for defining a TypedDict. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/typeddict.html#alternative-syntax + +from typing import TypedDict + + +Movie = TypedDict("Movie", {"name": str, "year": int, "illegal key name": bool}) + +movie: Movie = {"name": "Blade Runner", "year": 1982, "illegal key name": True} + +MovieOptional = TypedDict("MovieOptional", {"name": str, "year": int}, total=False) + +movie_opt1: MovieOptional = {} +movie_opt2: MovieOptional = {"year": 1982} + +# > A type checker is only expected to accept a dictionary display expression as +# > the second argument to TypedDict. In particular, a variable that refers to a +# > dictionary object does not need to be supported, to simplify implementation. +my_dict = {"name": str} +BadTypedDict1 = TypedDict("BadTypedDict1", my_dict) + + +# This should generate an error because it uses a non-str key. +BadTypedDict2 = TypedDict("BadTypedDict2", {1: str}) + + +# This should generate an error because it uses a non-matching name. +BadTypedDict3 = TypedDict("WrongName", {"name": str}) + + +# This should generate an error because of the additional parameter. +BadTypedDict4 = TypedDict("BadTypedDict4", {"name": str}, total=False, other=False) + + +# > The keyword-argument syntax is deprecated in 3.11 and will be removed in 3.13. +# > It may also be unsupported by static type checkers. + +Movie2 = TypedDict("Movie2", name=str, year=int) + +movie2: Movie2 +movie2 = {"name": "Blade Runner", "year": 1982} +movie2 = {"name": "Blade Runner", "year": ""} # Type error: Incorrect type for year diff --git a/conformance/tests/typeddicts_class_syntax.py b/conformance/tests/typeddicts_class_syntax.py new file mode 100644 index 000000000..38ed09c0c --- /dev/null +++ b/conformance/tests/typeddicts_class_syntax.py @@ -0,0 +1,79 @@ +""" +Tests the class syntax for defining a TypedDict. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/typeddict.html#typeddict + +from typing import Generic, TypeVar, TypedDict + + +class Movie(TypedDict): + name: str + year: int + + # > String literal forward references are valid in the value types. + director: "Person" + + +class Person(TypedDict): + name: str + age: int + + +# > Methods are not allowed, since the runtime type of a TypedDict object +# > will always be just dict (it is never a subclass of dict). +class BadTypedDict1(TypedDict): + name: str + + # Methods are not allowed, so this should generate an error. + def method1(self): + pass + + # Methods are not allowed, so this should generate an error. + @classmethod + def method2(cls): + pass + + # Methods are not allowed, so this should generate an error. + @staticmethod + def method3(): + pass + + +# > Specifying a metaclass is not allowed. +class BadTypedDict2(TypedDict, metaclass=type): + name: str + + +# This should generate an error because "other" is not an allowed keyword argument. +class BadTypedDict3(TypedDict, other=True): + name: str + + +# > TypedDicts may be made generic by adding Generic[T] among the bases. +T = TypeVar("T") + + +class GenericTypedDict(Generic[T]): + name: str + value: T + + +# > An empty TypedDict can be created by only including pass in the +# > body (if there is a docstring, pass can be omitted): +class EmptyDict1(TypedDict): + pass + + +class EmptyDict2(TypedDict): + """Docstring""" + + +class MovieTotal(TypedDict, total=True): + name: str + + +class MovieOptional(TypedDict, total=False): + name: str + + diff --git a/conformance/tests/typeddicts_final.py b/conformance/tests/typeddicts_final.py new file mode 100644 index 000000000..1cd08f2f9 --- /dev/null +++ b/conformance/tests/typeddicts_final.py @@ -0,0 +1,26 @@ +""" +Tests the use of Final values when used with TypedDicts. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/typeddict.html#use-of-final-values-and-literal-types + +from typing import Final, Literal, TypedDict + + +class Movie(TypedDict): + name: str + year: int + + +# > Type checkers should allow final names (PEP 591) with string values to be +# > used instead of string literals in operations on TypedDict objects. +YEAR: Final = "year" + +m: Movie = {"name": "Alien", "year": 1979} +years_since_epoch = m[YEAR] - 1970 + + +# > An expression with a suitable literal type (PEP 586) can be used instead of +# > a literal value. +def get_value(movie: Movie, key: Literal["year", "name"]) -> int | str: + return movie[key] diff --git a/conformance/tests/typeddicts_inheritance.py b/conformance/tests/typeddicts_inheritance.py new file mode 100644 index 000000000..38b72403a --- /dev/null +++ b/conformance/tests/typeddicts_inheritance.py @@ -0,0 +1,66 @@ +""" +Tests inheritance rules for TypedDict classes. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/typeddict.html#typeddict + +from typing import TypedDict + + +class Movie(TypedDict): + name: str + year: int + +class BookBasedMovie(Movie): + based_on: str + +class BookBasedMovieAlso(TypedDict): + name: str + year: int + based_on: str + +b1: BookBasedMovie = {"name": "Little Women", "year": 2019, "based_on": "Little Women"} + +b2: BookBasedMovieAlso = b1 + + +class X(TypedDict): + x: int + +class Y(TypedDict): + y: str + +class XYZ(X, Y): + z: bool + +x1 = XYZ(x=1, y="", z=True) + +# > A TypedDict cannot inherit from both a TypedDict type and a +# > non-TypedDict base class other than Generic. + +class NonTypedDict: + pass + +class BadTypedDict(TypedDict, NonTypedDict): + pass + + +# > Changing a field type of a parent TypedDict class in a subclass is +# > not allowed. + +class X1(TypedDict): + x: str + +class Y1(X1): + x: int # Type check error: cannot overwrite TypedDict field "x" + + +# > Multiple inheritance does not allow conflict types for the same name field: +class X2(TypedDict): + x: int + +class Y2(TypedDict): + x: str + +class XYZ2(X2, Y2): # Type check error: cannot overwrite TypedDict field "x" while merging + xyz: bool diff --git a/conformance/tests/typeddicts_operations.py b/conformance/tests/typeddicts_operations.py new file mode 100644 index 000000000..11c449043 --- /dev/null +++ b/conformance/tests/typeddicts_operations.py @@ -0,0 +1,65 @@ +""" +Tests operations provided by a TypedDict object. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/typeddict.html#supported-and-unsupported-operations + + +from typing import TypedDict, assert_type + + +class Movie(TypedDict): + name: str + year: int + + +movie: Movie + +movie = {"name": "Blade Runner", "year": 1982} +movie["name"] = "Other" +movie["year"] = 1981 + +movie["name"] = 1982 # Type error: wrong type +movie["year"] = "" # Type error: wrong type +movie["other"] = "" # Type error: unknown key added + +print(movie["other"]) # Type error: unknown key referenced + +movie = {"name": "Blade Runner"} # Type error: year is missing +movie = {"name": "Blade Runner", "year": 1982.1} # Type error: year is wrong type + +# > The use of a key that is not known to exist should be reported as an error. +movie = {"name": "", "year": 1900, "other": 2} # Type error: extra key + + +def func1(variable_key: str): + # > A key that is not a literal should generally be rejected. + movie: Movie = {variable_key: "", "year": 1900} # Type error: variable key + + +# It's not clear from the spec what type this should be. +movie.get("name") + +# It's not clear from the spec what type this should be. +movie.get("other") + + +movie.clear() # Type error: clear not allowed + +del movie["name"] # Type error: del not allowed for required key + + + +class MovieOptional(TypedDict, total=False): + name: str + year: int + + +movie_optional: MovieOptional = {} + +assert_type(movie_optional.get("name"), str | None) + +movie_optional.clear() # Type error: clear not allowed + +del movie_optional["name"] + diff --git a/conformance/tests/typeddicts_required.py b/conformance/tests/typeddicts_required.py new file mode 100644 index 000000000..22485f0f1 --- /dev/null +++ b/conformance/tests/typeddicts_required.py @@ -0,0 +1,76 @@ +""" +Tests the Required and NotRequired special forms. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/typeddict.html#required-and-notrequired + +from typing import Annotated, NotRequired, Required, TypedDict + + +# Required and NotRequired are valid only within a TypedDict. +class NotTypedDict: + x: Required[int] # Type error: Required not allowed in this context + + +def func1( + x: NotRequired[int], +) -> None: # Type error: Required not allowed in this context + pass + + +class TD1(TypedDict, total=False): + a: int + + +class TD2(TD1, total=True): + b: int + + +class TD3(TypedDict): + a: NotRequired[int] + b: Required[int] + + +class TD4(TypedDict, total=False): + a: int + b: Required[int] + + +class TD5(TypedDict, total=True): + a: NotRequired[int] + b: int + + +td3: TD3 = {"b": 0} +td4: TD4 = {"b": 0} +td5: TD5 = {"b": 0} + +# These are all equivalent types, so they should be +# bidirectionally type compatible. +td3 = td4 +td3 = td5 +td4 = td3 +td4 = td5 +td5 = td3 +td5 = td4 + + +class TD6(TypedDict): + a: Required[Required[int]] # Type error: Nesting not allowed + b: Required[NotRequired[int]] # Type error: Nesting not allowed + + +class TD7(TypedDict): + # > Required[] and NotRequired[] can be used with Annotated[], in any nesting order. + x: Annotated[Required[int], ""] + y: Required[Annotated[int, ""]] + z: Annotated[Required[Annotated[int, ""]], ""] + + +RecursiveMovie = TypedDict( + "RecursiveMovie", {"title": Required[str], "predecessor": NotRequired["RecursiveMovie"]} +) + +movie: RecursiveMovie = {"title": "Beethoven 3", "predecessor": {"title": "Beethoven 2"}} + + diff --git a/conformance/tests/typeddicts_type_consistency.py b/conformance/tests/typeddicts_type_consistency.py new file mode 100644 index 000000000..c782b2cc3 --- /dev/null +++ b/conformance/tests/typeddicts_type_consistency.py @@ -0,0 +1,150 @@ +""" +Tests the type consistency rules for TypedDict objects. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/typeddict.html#type-consistency + +from typing import Any, Literal, Mapping, TypedDict + + +class A1(TypedDict): + x: int | None + + +class B1(TypedDict): + x: int + + +b1: B1 = {"x": 0} + +# > Value types behave invariantly. +a1: A1 = b1 # Type check error: 'B1' not compatible with 'A1' + +# > any TypedDict type is consistent with Mapping[str, object] +v1: Mapping[str, object] = b1 + +# > A TypedDict type with a required key is not consistent with a TypedDict +# > type where the same key is a non-required key. + +class A2(TypedDict, total=False): + x: int + + +class B2(TypedDict): + x: int + + +b2: B2 = {"x": 0} +a2: A2 = b2 # Type check error: 'B2' not compatible with 'A2' + + +# > A TypedDict type A is consistent with TypedDict B if A is structurally +# > compatible with B. This is true if and only if both of these conditions +# > are satisfied: +# > 1. For each key in B, A has the corresponding key and the corresponding +# > value type in A is consistent with the value type in B. For each key in B, +# > the value type in B is also consistent with the corresponding value type +# > in A. +# > 2. For each required key in B, the corresponding key is required in A. For +# > each non-required key in B, the corresponding key is not required in A. + + +class A3(TypedDict): + x: int + + +class B3(TypedDict): + x: int + y: int + + +b3: B3 = {"x": 0, "y": 0} +a3: A3 = b3 + +a3 = {"x": 0} +b3 = a3 # Type checker error + + +# This should generate an error because it's a literal assignment. +a3_1: A3 = {"x": 0, "y": 0} + +# This should not generate an error. +a3_2 = b3 + +# > A TypedDict isn’t consistent with any Dict[...] type. + +d1: dict[str, int] = b3 # Type checker error +d2: dict[str, object] = b3 # Type checker error +d3: dict[Any, Any] = b3 # Type checker error + +# > A TypedDict with all int values is not consistent with Mapping[str, int]. + +m1: Mapping[str, int] = b3 # Type checker error +m2: Mapping[str, object] = b3 # OK +m3: Mapping[str, Any] = b3 # OK + + +# Test "get" method. +UserType1 = TypedDict("UserType1", {"name": str, "age": int}, total=False) +user1: UserType1 = {"name": "Bob", "age": 40} + +name1: str = user1.get("name", "n/a") +age1: int = user1.get("age", 42) + +UserType2 = TypedDict("UserType2", {"name": str, "age": int}) +user2: UserType2 = {"name": "Bob", "age": 40} + +name2: str | None = user2.get("name") + +name3: str = user2.get("name") + +age2: int = user2.get("age", 42) + +age3: int | str = user2.get("age", "42") + +age4: int = user2.get("age", "42") + +# Test nested TypedDicts. +class Inner1(TypedDict): + inner_key: str + + +class Inner2(TypedDict): + inner_key: Inner1 + + +class Outer1(TypedDict): + outer_key: Inner2 + + +o1: Outer1 = {"outer_key": {"inner_key": {"inner_key": "hi"}}} + +# This should generate an error because the inner-most value +# should be a string. +o2: Outer1 = {"outer_key": {"inner_key": {"inner_key": 1}}} + + +class Inner3(TypedDict): + x: int + + +class Inner4(TypedDict): + x: int + + +class Outer2(TypedDict): + y: str + z: Literal[""] | Inner3 + + +class Outer3(TypedDict): + y: str + z: Literal[""] | Inner4 + + +def func1(td: Outer3): + ... + + +o3: Outer2 = {"y": "", "z": {"x": 0}} +o4: Outer3 = o3 diff --git a/conformance/tests/typeddicts_usage.py b/conformance/tests/typeddicts_usage.py new file mode 100644 index 000000000..99232872d --- /dev/null +++ b/conformance/tests/typeddicts_usage.py @@ -0,0 +1,42 @@ +""" +Tests for basic usage of TypedDict. +""" + +from typing import TypeVar, TypedDict + + +class Movie(TypedDict): + name: str + year: int + + +movie: Movie = {"name": "Blade Runner", "year": 1982} + + +def record_movie(movie: Movie) -> None: + ... + + +record_movie({"name": "Blade Runner", "year": 1982}) + + +movie["director"] = "Ridley Scott" # Error: invalid key 'director' +movie["year"] = "1982" # Error: invalid value type ("int" expected) + +# The code below should be rejected, since 'title' is not a valid key, +# and the 'name' key is missing: +movie2: Movie = {"title": "Blade Runner", "year": 1982} + +m = Movie(name='Blade Runner', year=1982) + + +# > TypedDict type objects cannot be used in isinstance() tests such as +# > isinstance(d, Movie). +if isinstance(movie, Movie): + pass + + +# TypedDict should not be allowed as a bound for a TypeVar. +T = TypeVar("T", bound=TypedDict) # Type checker error + + From c366858f36294152974a54d496db3f5c36b94714 Mon Sep 17 00:00:00 2001 From: Rebecca Chen Date: Tue, 2 Jan 2024 19:11:14 -0500 Subject: [PATCH 162/539] Make pytype conformance test a little faster. (#1559) This change gets the pytype test down to ~30s. It's still considerably slower than the others, but any further improvements will likely require untangling the sad state of affairs that is https://github.com/google/pytype/issues/1501, which will take some time. * Runs pytype's 'check' option through pytype.io.check_py to skip the costly type inference that it does by default. * Adds a progress bar so that it doesn't look like pytype is hanging. This generates a few changes to results/pytype/ due to error messages being formatted and parsed in a different way. --- conformance/requirements.txt | 1 + .../pytype/aliases_type_statement.toml | 3 +- .../pytype/literals_literalstring.toml | 25 ++++++++ .../results/pytype/typeddicts_operations.toml | 9 +++ .../results/pytype/typeddicts_required.toml | 2 +- .../pytype/typeddicts_type_consistency.toml | 9 +++ .../results/pytype/typeddicts_usage.toml | 3 + conformance/results/pytype/version.toml | 2 +- conformance/results/results.html | 2 +- conformance/src/type_checker.py | 57 +++++++------------ 10 files changed, 72 insertions(+), 41 deletions(-) diff --git a/conformance/requirements.txt b/conformance/requirements.txt index 3f3979428..0913c917e 100644 --- a/conformance/requirements.txt +++ b/conformance/requirements.txt @@ -1,5 +1,6 @@ tomli tomlkit +tqdm pyright mypy pyre-check diff --git a/conformance/results/pytype/aliases_type_statement.toml b/conformance/results/pytype/aliases_type_statement.toml index 49c698731..b3f2d97da 100644 --- a/conformance/results/pytype/aliases_type_statement.toml +++ b/conformance/results/pytype/aliases_type_statement.toml @@ -3,5 +3,4 @@ notes = """ Does not support `type` statement. """ output = """ -File "aliases_type_statement.py", line 8: Type statement is only supported in Python 3.12 and greater [python-compiler-error] -""" +SyntaxError: Type statement is only supported in Python 3.12 and greater (, line 8)""" diff --git a/conformance/results/pytype/literals_literalstring.toml b/conformance/results/pytype/literals_literalstring.toml index 0ffdbe488..c75d90dc4 100644 --- a/conformance/results/pytype/literals_literalstring.toml +++ b/conformance/results/pytype/literals_literalstring.toml @@ -3,4 +3,29 @@ notes = """ Does not understand `LiteralString` special form. """ output = """ +File "literals_literalstring.py", line 8, in : typing.LiteralString not supported yet [not-supported-yet] +File "literals_literalstring.py", line 24, in my_function: bad return type [bad-return-type] + Expected: str + Actually returned: None +File "literals_literalstring.py", line 36, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'str' at index 1 +File "literals_literalstring.py", line 37, in : Invalid type annotation 'Literal' [invalid-annotation] + Bad parameter 'str' at index 0 +File "literals_literalstring.py", line 43, in func1: Type annotation for x2 does not match type of assignment [annotation-type-mismatch] + Annotation: Literal[''] + Assignment: Literal['two'] +File "literals_literalstring.py", line 74, in func2: Type annotation for x3 does not match type of assignment [annotation-type-mismatch] + Annotation: str + Assignment: int +File "literals_literalstring.py", line 75, in func2: Type annotation for x4 does not match type of assignment [annotation-type-mismatch] + Annotation: str + Assignment: bytes +File "literals_literalstring.py", line 157, in func8: bad return type [bad-return-type] + Expected: int + Actually returned: None +Called from (traceback): + line 160, in current file +File "literals_literalstring.py", line 162, in : bool [assert-type] + Expected: str + Actual: bool """ diff --git a/conformance/results/pytype/typeddicts_operations.toml b/conformance/results/pytype/typeddicts_operations.toml index 8f53a4408..00d222bf6 100644 --- a/conformance/results/pytype/typeddicts_operations.toml +++ b/conformance/results/pytype/typeddicts_operations.toml @@ -9,15 +9,24 @@ output = """ File "typeddicts_operations.py", line 28, in : Type annotation for movie does not match type of assignment [annotation-type-mismatch] Annotation: Movie(TypedDict) Assignment: Dict[str, str] + + TypedDict missing keys: year File "typeddicts_operations.py", line 29, in : Type annotation for movie does not match type of assignment [annotation-type-mismatch] Annotation: Movie(TypedDict) Assignment: Dict[str, Union[float, str]] + + TypedDict type errors: + {'year': ...}: expected int, got float File "typeddicts_operations.py", line 32, in : Type annotation for movie does not match type of assignment [annotation-type-mismatch] Annotation: Movie(TypedDict) Assignment: Dict[str, Union[int, str]] + + TypedDict extra keys: other File "typeddicts_operations.py", line 37, in func1: Type annotation for movie does not match type of assignment [annotation-type-mismatch] Annotation: Movie(TypedDict) Assignment: Dict[str, Union[int, str]] + + TypedDict missing keys: name File "typeddicts_operations.py", line 60, in : str [assert-type] Expected: Optional[str] Actual: str diff --git a/conformance/results/pytype/typeddicts_required.toml b/conformance/results/pytype/typeddicts_required.toml index bdedbc7d6..4bdf74100 100644 --- a/conformance/results/pytype/typeddicts_required.toml +++ b/conformance/results/pytype/typeddicts_required.toml @@ -5,4 +5,4 @@ Does not reject use of `Required` in function parameter annotation. Does not reject nested use of `Required` in type annotation. """ output = """ -""" +RecursionError: maximum recursion depth exceeded""" diff --git a/conformance/results/pytype/typeddicts_type_consistency.toml b/conformance/results/pytype/typeddicts_type_consistency.toml index b80ed1d7a..d18ff82f1 100644 --- a/conformance/results/pytype/typeddicts_type_consistency.toml +++ b/conformance/results/pytype/typeddicts_type_consistency.toml @@ -8,13 +8,22 @@ output = """ File "typeddicts_type_consistency.py", line 62, in : Type annotation for a3 does not match type of assignment [annotation-type-mismatch] Annotation: A3(TypedDict) Assignment: B3 + + TypedDict extra keys: y File "typeddicts_type_consistency.py", line 65, in : Type annotation for b3 does not match type of assignment [annotation-type-mismatch] Annotation: B3(TypedDict) Assignment: Dict[str, int] + + TypedDict missing keys: y File "typeddicts_type_consistency.py", line 69, in : Type annotation for a3_1 does not match type of assignment [annotation-type-mismatch] Annotation: A3(TypedDict) Assignment: Dict[str, int] + + TypedDict extra keys: y File "typeddicts_type_consistency.py", line 124, in : Type annotation for o2 does not match type of assignment [annotation-type-mismatch] Annotation: Outer1(TypedDict) Assignment: Dict[str, Dict[str, Dict[str, int]]] + + TypedDict type errors: + {'outer_key': ...}: expected Inner2, got Dict[str, Dict[str, int]] """ diff --git a/conformance/results/pytype/typeddicts_usage.toml b/conformance/results/pytype/typeddicts_usage.toml index 41139d698..e0b5af136 100644 --- a/conformance/results/pytype/typeddicts_usage.toml +++ b/conformance/results/pytype/typeddicts_usage.toml @@ -11,4 +11,7 @@ File "typeddicts_usage.py", line 24, in : Type annotation for key year i File "typeddicts_usage.py", line 28, in : Type annotation for movie2 does not match type of assignment [annotation-type-mismatch] Annotation: Movie(TypedDict) Assignment: Dict[str, Union[int, str]] + + TypedDict missing keys: name + TypedDict extra keys: title """ diff --git a/conformance/results/pytype/version.toml b/conformance/results/pytype/version.toml index 6bfa30f74..cdc708bb1 100644 --- a/conformance/results/pytype/version.toml +++ b/conformance/results/pytype/version.toml @@ -1,2 +1,2 @@ version = "pytype 2023.12.18" -test_duration = 51.5667941570282 +test_duration = 28.1415696144104 diff --git a/conformance/results/results.html b/conformance/results/results.html index b33903903..a3f2e4f93 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -340,7 +340,7 @@

Python Type System Conformance Test Results

     narrowing_typeguardPartialDoes not support `tuple` in `assert_type` call.
Does not reject TypeGuard method with too few parameters.
-
pytype 2023.12.18(51.57sec) +
pytype 2023.12.18(28.14sec)
diff --git a/conformance/src/type_checker.py b/conformance/src/type_checker.py index 905d56296..272a64b6e 100644 --- a/conformance/src/type_checker.py +++ b/conformance/src/type_checker.py @@ -5,9 +5,14 @@ from abc import ABC, abstractmethod import json from pathlib import Path +import os +from pytype import config as pytype_config +from pytype import io as pytype_io +from pytype import load_pytd as pytype_loader import re from subprocess import PIPE, run import sys +from tqdm import tqdm from typing import Sequence @@ -192,47 +197,27 @@ def run_tests(self, test_files: Sequence[str]) -> dict[str, str]: # Specify 3.11 for now to work around the fact that pytype # currently doesn't support 3.12 and emits an error when # running on 3.12. - command = f"{sys.executable} -m pytype -V 3.11 -k *.py" - proc = run(command, stdout=PIPE, text=True, shell=True) - lines = proc.stdout.split("\n") + options = pytype_config.Options.create( + python_version=(3, 11), + quick=True) + loader = pytype_loader.create_loader(options) # Add results to a dictionary keyed by the file name. results_dict: dict[str, str] = {} - accumulated_lines: list[str] = [] - file_name: str | None = None - def log_accumulated(): - if file_name is not None: - results_dict[file_name] = ( - results_dict.get(file_name, "") + "".join(accumulated_lines) + "\n" - ) - - for line in lines: - match = re.search(r'File "(.*?)",', line) - - if not match or match.start() != 0: - # An empty line precedes the summary for the file. Ignore - # everything after that line until we see diagnostics for - # the next file. - if line.strip() == "": - log_accumulated() - file_name = None - accumulated_lines = [] - elif file_name is not None: - accumulated_lines.append("\n" + line) + for fi in tqdm(os.listdir('.')): + if not fi.endswith('.py'): + continue + options.tweak(input=fi) + with open(fi, 'r') as test_file: + src = test_file.read() + try: + errorlog = pytype_io.check_py( + src, options=options, loader=loader) + except Exception as e: + results_dict[fi] = f'{e.__class__.__name__}: {e}' else: - log_accumulated() - - file_path = Path(match.group(1)) - file_name = file_path.name - - # Replace the full file path with the file name. - line = f'File "{file_name}",{line[match.end():]}' - accumulated_lines = [line] - - # Log the final accumulated lines. - log_accumulated() - + results_dict[fi] = str(errorlog) return results_dict From 69d7412c1b381148b155a4c373baf29d0ffd31ac Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Thu, 4 Jan 2024 17:31:06 -0700 Subject: [PATCH 163/539] Added tests for several more sections of the typing spec (#1554) --- README.md | 2 + .../results/mypy/annotations_coroutines.toml | 3 + .../mypy/annotations_forward_refs.toml | 31 ++ .../results/mypy/annotations_generators.toml | 15 + .../results/mypy/annotations_methods.toml | 7 + .../results/mypy/annotations_typeexpr.toml | 36 +- .../results/mypy/classes_classvar.toml | 31 ++ .../results/mypy/classes_override.toml | 12 + .../results/mypy/directives_assert_type.toml | 11 + conformance/results/mypy/directives_cast.toml | 16 + .../mypy/directives_no_type_check.toml | 3 + .../results/mypy/directives_reveal_type.toml | 11 + .../mypy/directives_type_checking.toml | 3 + .../results/mypy/directives_type_ignore.toml | 10 + .../mypy/directives_type_ignore_file1.toml | 3 + .../mypy/directives_type_ignore_file2.toml | 4 + .../mypy/directives_version_platform.toml | 11 + conformance/results/mypy/overloads_basic.toml | 9 + .../results/mypy/protocols_class_objects.toml | 23 ++ .../results/mypy/protocols_definition.toml | 55 +++ .../results/mypy/protocols_explicit.toml | 13 + .../results/mypy/protocols_generic.toml | 41 +++ .../results/mypy/protocols_merging.toml | 13 + .../results/mypy/protocols_modules.toml | 13 + .../results/mypy/protocols_recursive.toml | 3 + .../mypy/protocols_runtime_checkable.toml | 9 + conformance/results/mypy/protocols_self.toml | 3 + .../results/mypy/protocols_subtyping.toml | 12 + .../results/mypy/protocols_variance.toml | 12 + .../results/mypy/qualifiers_annotated.toml | 26 ++ .../mypy/qualifiers_final_annotation.toml | 38 ++ .../mypy/qualifiers_final_decorator.toml | 18 + .../results/mypy/specialtypes_any.toml | 5 + .../results/mypy/specialtypes_never.toml | 11 + .../results/mypy/specialtypes_none.toml | 6 + .../results/mypy/specialtypes_promotions.toml | 4 + .../results/mypy/specialtypes_tuple.toml | 10 + .../mypy/specialtypes_tuple_unpack.toml | 14 + .../results/mypy/specialtypes_type.toml | 20 + .../results/mypy/typeddicts_alt_syntax.toml | 2 +- conformance/results/mypy/version.toml | 2 +- .../results/pyre/annotations_coroutines.toml | 7 + .../pyre/annotations_forward_refs.toml | 28 ++ .../results/pyre/annotations_generators.toml | 22 ++ .../results/pyre/annotations_methods.toml | 6 + .../results/pyre/annotations_typeexpr.toml | 33 +- .../results/pyre/classes_classvar.toml | 27 ++ .../results/pyre/classes_override.toml | 22 ++ .../results/pyre/directives_assert_type.toml | 8 + conformance/results/pyre/directives_cast.toml | 6 + .../pyre/directives_no_type_check.toml | 11 + .../results/pyre/directives_reveal_type.toml | 8 + .../pyre/directives_type_checking.toml | 3 + .../results/pyre/directives_type_ignore.toml | 3 + .../pyre/directives_type_ignore_file1.toml | 7 + .../pyre/directives_type_ignore_file2.toml | 4 + .../pyre/directives_version_platform.toml | 11 + conformance/results/pyre/overloads_basic.toml | 8 + .../results/pyre/protocols_class_objects.toml | 12 + .../results/pyre/protocols_definition.toml | 24 ++ .../results/pyre/protocols_explicit.toml | 15 + .../results/pyre/protocols_generic.toml | 13 + .../results/pyre/protocols_merging.toml | 11 + .../results/pyre/protocols_modules.toml | 6 + .../results/pyre/protocols_recursive.toml | 3 + .../pyre/protocols_runtime_checkable.toml | 8 + conformance/results/pyre/protocols_self.toml | 3 + .../results/pyre/protocols_subtyping.toml | 10 + .../results/pyre/protocols_variance.toml | 8 + .../results/pyre/qualifiers_annotated.toml | 18 + .../pyre/qualifiers_final_annotation.toml | 34 ++ .../pyre/qualifiers_final_decorator.toml | 18 + .../results/pyre/specialtypes_any.toml | 14 + .../results/pyre/specialtypes_never.toml | 17 + .../results/pyre/specialtypes_none.toml | 10 + .../results/pyre/specialtypes_promotions.toml | 6 + .../results/pyre/specialtypes_tuple.toml | 11 + .../pyre/specialtypes_tuple_unpack.toml | 43 +++ .../results/pyre/specialtypes_type.toml | 25 ++ conformance/results/pyre/version.toml | 2 +- .../results/pyright/aliases_explicit.toml | 10 +- .../results/pyright/aliases_implicit.toml | 10 +- .../results/pyright/aliases_newtype.toml | 17 +- .../pyright/aliases_type_statement.toml | 33 +- .../pyright/aliases_typealiastype.toml | 30 +- .../pyright/annotations_coroutines.toml | 3 + .../pyright/annotations_forward_refs.toml | 37 ++ .../pyright/annotations_generators.toml | 33 ++ .../results/pyright/annotations_methods.toml | 8 + .../results/pyright/annotations_typeexpr.toml | 40 +- .../results/pyright/callables_protocol.toml | 8 +- .../results/pyright/classes_classvar.toml | 24 ++ .../results/pyright/classes_override.toml | 8 + .../results/pyright/dataclasses_postinit.toml | 7 +- .../pyright/directives_assert_type.toml | 9 + .../results/pyright/directives_cast.toml | 8 + .../pyright/directives_no_type_check.toml | 14 + .../pyright/directives_reveal_type.toml | 9 + .../pyright/directives_type_checking.toml | 3 + .../pyright/directives_type_ignore.toml | 3 + .../pyright/directives_type_ignore_file1.toml | 3 + .../pyright/directives_type_ignore_file2.toml | 5 + .../pyright/directives_version_platform.toml | 3 + .../results/pyright/overloads_basic.toml | 8 + .../pyright/protocols_class_objects.toml | 28 ++ .../results/pyright/protocols_definition.toml | 89 +++++ .../results/pyright/protocols_explicit.toml | 26 ++ .../results/pyright/protocols_generic.toml | 40 ++ .../results/pyright/protocols_merging.toml | 16 + .../results/pyright/protocols_modules.toml | 14 + .../results/pyright/protocols_recursive.toml | 3 + .../pyright/protocols_runtime_checkable.toml | 9 + .../results/pyright/protocols_self.toml | 3 + .../results/pyright/protocols_subtyping.toml | 25 ++ .../results/pyright/protocols_variance.toml | 16 + .../results/pyright/qualifiers_annotated.toml | 23 ++ .../pyright/qualifiers_final_annotation.toml | 38 ++ .../pyright/qualifiers_final_decorator.toml | 17 + .../results/pyright/specialtypes_any.toml | 3 + .../results/pyright/specialtypes_never.toml | 14 + .../results/pyright/specialtypes_none.toml | 10 + .../pyright/specialtypes_promotions.toml | 5 + .../results/pyright/specialtypes_tuple.toml | 22 ++ .../pyright/specialtypes_tuple_unpack.toml | 29 ++ .../results/pyright/specialtypes_type.toml | 25 ++ conformance/results/pyright/version.toml | 4 +- .../pytype/annotations_coroutines.toml | 9 + .../pytype/annotations_forward_refs.toml | 45 +++ .../pytype/annotations_generators.toml | 40 ++ .../results/pytype/annotations_methods.toml | 9 + .../results/pytype/annotations_typeexpr.toml | 19 +- .../results/pytype/classes_classvar.toml | 45 +++ .../results/pytype/classes_override.toml | 20 + .../pytype/directives_assert_type.toml | 18 + .../results/pytype/directives_cast.toml | 11 + .../pytype/directives_no_type_check.toml | 17 + .../pytype/directives_reveal_type.toml | 12 + .../pytype/directives_type_checking.toml | 3 + .../pytype/directives_type_ignore.toml | 10 + .../pytype/directives_type_ignore_file1.toml | 3 + .../pytype/directives_type_ignore_file2.toml | 6 + .../pytype/directives_version_platform.toml | 16 + .../results/pytype/overloads_basic.toml | 16 + .../pytype/protocols_class_objects.toml | 8 + .../results/pytype/protocols_definition.toml | 50 +++ .../results/pytype/protocols_explicit.toml | 17 + .../results/pytype/protocols_generic.toml | 36 ++ .../results/pytype/protocols_merging.toml | 22 ++ .../results/pytype/protocols_modules.toml | 8 + .../results/pytype/protocols_recursive.toml | 10 + .../pytype/protocols_runtime_checkable.toml | 8 + .../results/pytype/protocols_self.toml | 29 ++ .../results/pytype/protocols_subtyping.toml | 16 + .../results/pytype/protocols_variance.toml | 11 + .../results/pytype/qualifiers_annotated.toml | 27 ++ .../pytype/qualifiers_final_annotation.toml | 45 +++ .../pytype/qualifiers_final_decorator.toml | 36 ++ .../results/pytype/specialtypes_any.toml | 3 + .../results/pytype/specialtypes_never.toml | 22 ++ .../results/pytype/specialtypes_none.toml | 13 + .../pytype/specialtypes_promotions.toml | 4 + .../results/pytype/specialtypes_tuple.toml | 21 ++ .../pytype/specialtypes_tuple_unpack.toml | 215 +++++++++++ .../results/pytype/specialtypes_type.toml | 32 ++ conformance/results/pytype/version.toml | 2 +- conformance/results/results.html | 246 ++++++++++++- conformance/tests/_protocols_modules1.py | 7 + conformance/tests/_protocols_modules2.py | 11 + .../tests/_qualifiers_final_decorator.pyi | 29 ++ conformance/tests/annotations_coroutines.py | 24 ++ conformance/tests/annotations_forward_refs.py | 93 +++++ conformance/tests/annotations_generators.py | 190 ++++++++++ conformance/tests/annotations_methods.py | 51 +++ conformance/tests/annotations_typeexpr.py | 33 +- conformance/tests/classes_classvar.py | 156 ++++++++ conformance/tests/classes_override.py | 104 ++++++ conformance/tests/directives_assert_type.py | 37 ++ conformance/tests/directives_cast.py | 17 + conformance/tests/directives_no_type_check.py | 21 ++ conformance/tests/directives_reveal_type.py | 24 ++ conformance/tests/directives_type_checking.py | 19 + conformance/tests/directives_type_ignore.py | 20 + .../tests/directives_type_ignore_file1.py | 16 + .../tests/directives_type_ignore_file2.py | 14 + .../tests/directives_version_platform.py | 45 +++ conformance/tests/overloads_basic.py | 81 +++++ conformance/tests/protocols_class_objects.py | 106 ++++++ conformance/tests/protocols_definition.py | 341 ++++++++++++++++++ conformance/tests/protocols_explicit.py | 178 +++++++++ conformance/tests/protocols_generic.py | 147 ++++++++ conformance/tests/protocols_merging.py | 84 +++++ conformance/tests/protocols_modules.py | 49 +++ conformance/tests/protocols_recursive.py | 80 ++++ .../tests/protocols_runtime_checkable.py | 89 +++++ conformance/tests/protocols_self.py | 73 ++++ conformance/tests/protocols_subtyping.py | 142 ++++++++ conformance/tests/protocols_variance.py | 122 +++++++ conformance/tests/qualifiers_annotated.py | 96 +++++ .../tests/qualifiers_final_annotation.py | 155 ++++++++ .../tests/qualifiers_final_decorator.py | 127 +++++++ conformance/tests/specialtypes_any.py | 90 +++++ conformance/tests/specialtypes_never.py | 104 ++++++ conformance/tests/specialtypes_none.py | 41 +++ conformance/tests/specialtypes_promotions.py | 16 + conformance/tests/specialtypes_tuple.py | 43 +++ .../tests/specialtypes_tuple_unpack.py | 41 +++ conformance/tests/specialtypes_type.py | 175 +++++++++ 207 files changed, 6205 insertions(+), 154 deletions(-) create mode 100644 conformance/results/mypy/annotations_coroutines.toml create mode 100644 conformance/results/mypy/annotations_forward_refs.toml create mode 100644 conformance/results/mypy/annotations_generators.toml create mode 100644 conformance/results/mypy/annotations_methods.toml create mode 100644 conformance/results/mypy/classes_classvar.toml create mode 100644 conformance/results/mypy/classes_override.toml create mode 100644 conformance/results/mypy/directives_assert_type.toml create mode 100644 conformance/results/mypy/directives_cast.toml create mode 100644 conformance/results/mypy/directives_no_type_check.toml create mode 100644 conformance/results/mypy/directives_reveal_type.toml create mode 100644 conformance/results/mypy/directives_type_checking.toml create mode 100644 conformance/results/mypy/directives_type_ignore.toml create mode 100644 conformance/results/mypy/directives_type_ignore_file1.toml create mode 100644 conformance/results/mypy/directives_type_ignore_file2.toml create mode 100644 conformance/results/mypy/directives_version_platform.toml create mode 100644 conformance/results/mypy/overloads_basic.toml create mode 100644 conformance/results/mypy/protocols_class_objects.toml create mode 100644 conformance/results/mypy/protocols_definition.toml create mode 100644 conformance/results/mypy/protocols_explicit.toml create mode 100644 conformance/results/mypy/protocols_generic.toml create mode 100644 conformance/results/mypy/protocols_merging.toml create mode 100644 conformance/results/mypy/protocols_modules.toml create mode 100644 conformance/results/mypy/protocols_recursive.toml create mode 100644 conformance/results/mypy/protocols_runtime_checkable.toml create mode 100644 conformance/results/mypy/protocols_self.toml create mode 100644 conformance/results/mypy/protocols_subtyping.toml create mode 100644 conformance/results/mypy/protocols_variance.toml create mode 100644 conformance/results/mypy/qualifiers_annotated.toml create mode 100644 conformance/results/mypy/qualifiers_final_annotation.toml create mode 100644 conformance/results/mypy/qualifiers_final_decorator.toml create mode 100644 conformance/results/mypy/specialtypes_any.toml create mode 100644 conformance/results/mypy/specialtypes_never.toml create mode 100644 conformance/results/mypy/specialtypes_none.toml create mode 100644 conformance/results/mypy/specialtypes_promotions.toml create mode 100644 conformance/results/mypy/specialtypes_tuple.toml create mode 100644 conformance/results/mypy/specialtypes_tuple_unpack.toml create mode 100644 conformance/results/mypy/specialtypes_type.toml create mode 100644 conformance/results/pyre/annotations_coroutines.toml create mode 100644 conformance/results/pyre/annotations_forward_refs.toml create mode 100644 conformance/results/pyre/annotations_generators.toml create mode 100644 conformance/results/pyre/annotations_methods.toml create mode 100644 conformance/results/pyre/classes_classvar.toml create mode 100644 conformance/results/pyre/classes_override.toml create mode 100644 conformance/results/pyre/directives_assert_type.toml create mode 100644 conformance/results/pyre/directives_cast.toml create mode 100644 conformance/results/pyre/directives_no_type_check.toml create mode 100644 conformance/results/pyre/directives_reveal_type.toml create mode 100644 conformance/results/pyre/directives_type_checking.toml create mode 100644 conformance/results/pyre/directives_type_ignore.toml create mode 100644 conformance/results/pyre/directives_type_ignore_file1.toml create mode 100644 conformance/results/pyre/directives_type_ignore_file2.toml create mode 100644 conformance/results/pyre/directives_version_platform.toml create mode 100644 conformance/results/pyre/overloads_basic.toml create mode 100644 conformance/results/pyre/protocols_class_objects.toml create mode 100644 conformance/results/pyre/protocols_definition.toml create mode 100644 conformance/results/pyre/protocols_explicit.toml create mode 100644 conformance/results/pyre/protocols_generic.toml create mode 100644 conformance/results/pyre/protocols_merging.toml create mode 100644 conformance/results/pyre/protocols_modules.toml create mode 100644 conformance/results/pyre/protocols_recursive.toml create mode 100644 conformance/results/pyre/protocols_runtime_checkable.toml create mode 100644 conformance/results/pyre/protocols_self.toml create mode 100644 conformance/results/pyre/protocols_subtyping.toml create mode 100644 conformance/results/pyre/protocols_variance.toml create mode 100644 conformance/results/pyre/qualifiers_annotated.toml create mode 100644 conformance/results/pyre/qualifiers_final_annotation.toml create mode 100644 conformance/results/pyre/qualifiers_final_decorator.toml create mode 100644 conformance/results/pyre/specialtypes_any.toml create mode 100644 conformance/results/pyre/specialtypes_never.toml create mode 100644 conformance/results/pyre/specialtypes_none.toml create mode 100644 conformance/results/pyre/specialtypes_promotions.toml create mode 100644 conformance/results/pyre/specialtypes_tuple.toml create mode 100644 conformance/results/pyre/specialtypes_tuple_unpack.toml create mode 100644 conformance/results/pyre/specialtypes_type.toml create mode 100644 conformance/results/pyright/annotations_coroutines.toml create mode 100644 conformance/results/pyright/annotations_forward_refs.toml create mode 100644 conformance/results/pyright/annotations_generators.toml create mode 100644 conformance/results/pyright/annotations_methods.toml create mode 100644 conformance/results/pyright/classes_classvar.toml create mode 100644 conformance/results/pyright/classes_override.toml create mode 100644 conformance/results/pyright/directives_assert_type.toml create mode 100644 conformance/results/pyright/directives_cast.toml create mode 100644 conformance/results/pyright/directives_no_type_check.toml create mode 100644 conformance/results/pyright/directives_reveal_type.toml create mode 100644 conformance/results/pyright/directives_type_checking.toml create mode 100644 conformance/results/pyright/directives_type_ignore.toml create mode 100644 conformance/results/pyright/directives_type_ignore_file1.toml create mode 100644 conformance/results/pyright/directives_type_ignore_file2.toml create mode 100644 conformance/results/pyright/directives_version_platform.toml create mode 100644 conformance/results/pyright/overloads_basic.toml create mode 100644 conformance/results/pyright/protocols_class_objects.toml create mode 100644 conformance/results/pyright/protocols_definition.toml create mode 100644 conformance/results/pyright/protocols_explicit.toml create mode 100644 conformance/results/pyright/protocols_generic.toml create mode 100644 conformance/results/pyright/protocols_merging.toml create mode 100644 conformance/results/pyright/protocols_modules.toml create mode 100644 conformance/results/pyright/protocols_recursive.toml create mode 100644 conformance/results/pyright/protocols_runtime_checkable.toml create mode 100644 conformance/results/pyright/protocols_self.toml create mode 100644 conformance/results/pyright/protocols_subtyping.toml create mode 100644 conformance/results/pyright/protocols_variance.toml create mode 100644 conformance/results/pyright/qualifiers_annotated.toml create mode 100644 conformance/results/pyright/qualifiers_final_annotation.toml create mode 100644 conformance/results/pyright/qualifiers_final_decorator.toml create mode 100644 conformance/results/pyright/specialtypes_any.toml create mode 100644 conformance/results/pyright/specialtypes_never.toml create mode 100644 conformance/results/pyright/specialtypes_none.toml create mode 100644 conformance/results/pyright/specialtypes_promotions.toml create mode 100644 conformance/results/pyright/specialtypes_tuple.toml create mode 100644 conformance/results/pyright/specialtypes_tuple_unpack.toml create mode 100644 conformance/results/pyright/specialtypes_type.toml create mode 100644 conformance/results/pytype/annotations_coroutines.toml create mode 100644 conformance/results/pytype/annotations_forward_refs.toml create mode 100644 conformance/results/pytype/annotations_generators.toml create mode 100644 conformance/results/pytype/annotations_methods.toml create mode 100644 conformance/results/pytype/classes_classvar.toml create mode 100644 conformance/results/pytype/classes_override.toml create mode 100644 conformance/results/pytype/directives_assert_type.toml create mode 100644 conformance/results/pytype/directives_cast.toml create mode 100644 conformance/results/pytype/directives_no_type_check.toml create mode 100644 conformance/results/pytype/directives_reveal_type.toml create mode 100644 conformance/results/pytype/directives_type_checking.toml create mode 100644 conformance/results/pytype/directives_type_ignore.toml create mode 100644 conformance/results/pytype/directives_type_ignore_file1.toml create mode 100644 conformance/results/pytype/directives_type_ignore_file2.toml create mode 100644 conformance/results/pytype/directives_version_platform.toml create mode 100644 conformance/results/pytype/overloads_basic.toml create mode 100644 conformance/results/pytype/protocols_class_objects.toml create mode 100644 conformance/results/pytype/protocols_definition.toml create mode 100644 conformance/results/pytype/protocols_explicit.toml create mode 100644 conformance/results/pytype/protocols_generic.toml create mode 100644 conformance/results/pytype/protocols_merging.toml create mode 100644 conformance/results/pytype/protocols_modules.toml create mode 100644 conformance/results/pytype/protocols_recursive.toml create mode 100644 conformance/results/pytype/protocols_runtime_checkable.toml create mode 100644 conformance/results/pytype/protocols_self.toml create mode 100644 conformance/results/pytype/protocols_subtyping.toml create mode 100644 conformance/results/pytype/protocols_variance.toml create mode 100644 conformance/results/pytype/qualifiers_annotated.toml create mode 100644 conformance/results/pytype/qualifiers_final_annotation.toml create mode 100644 conformance/results/pytype/qualifiers_final_decorator.toml create mode 100644 conformance/results/pytype/specialtypes_any.toml create mode 100644 conformance/results/pytype/specialtypes_never.toml create mode 100644 conformance/results/pytype/specialtypes_none.toml create mode 100644 conformance/results/pytype/specialtypes_promotions.toml create mode 100644 conformance/results/pytype/specialtypes_tuple.toml create mode 100644 conformance/results/pytype/specialtypes_tuple_unpack.toml create mode 100644 conformance/results/pytype/specialtypes_type.toml create mode 100644 conformance/tests/_protocols_modules1.py create mode 100644 conformance/tests/_protocols_modules2.py create mode 100644 conformance/tests/_qualifiers_final_decorator.pyi create mode 100644 conformance/tests/annotations_coroutines.py create mode 100644 conformance/tests/annotations_forward_refs.py create mode 100644 conformance/tests/annotations_generators.py create mode 100644 conformance/tests/annotations_methods.py create mode 100644 conformance/tests/classes_classvar.py create mode 100644 conformance/tests/classes_override.py create mode 100644 conformance/tests/directives_assert_type.py create mode 100644 conformance/tests/directives_cast.py create mode 100644 conformance/tests/directives_no_type_check.py create mode 100644 conformance/tests/directives_reveal_type.py create mode 100644 conformance/tests/directives_type_checking.py create mode 100644 conformance/tests/directives_type_ignore.py create mode 100644 conformance/tests/directives_type_ignore_file1.py create mode 100644 conformance/tests/directives_type_ignore_file2.py create mode 100644 conformance/tests/directives_version_platform.py create mode 100644 conformance/tests/overloads_basic.py create mode 100644 conformance/tests/protocols_class_objects.py create mode 100644 conformance/tests/protocols_definition.py create mode 100644 conformance/tests/protocols_explicit.py create mode 100644 conformance/tests/protocols_generic.py create mode 100644 conformance/tests/protocols_merging.py create mode 100644 conformance/tests/protocols_modules.py create mode 100644 conformance/tests/protocols_recursive.py create mode 100644 conformance/tests/protocols_runtime_checkable.py create mode 100644 conformance/tests/protocols_self.py create mode 100644 conformance/tests/protocols_subtyping.py create mode 100644 conformance/tests/protocols_variance.py create mode 100644 conformance/tests/qualifiers_annotated.py create mode 100644 conformance/tests/qualifiers_final_annotation.py create mode 100644 conformance/tests/qualifiers_final_decorator.py create mode 100644 conformance/tests/specialtypes_any.py create mode 100644 conformance/tests/specialtypes_never.py create mode 100644 conformance/tests/specialtypes_none.py create mode 100644 conformance/tests/specialtypes_promotions.py create mode 100644 conformance/tests/specialtypes_tuple.py create mode 100644 conformance/tests/specialtypes_tuple_unpack.py create mode 100644 conformance/tests/specialtypes_type.py diff --git a/README.md b/README.md index 9809cd697..eb7472be0 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ This GitHub repository is used for several things: - A [discussion forum](https://github.com/python/typing/discussions) for typing-related user help is hosted here. +- [Conformance test](https://github.com/python/typing/blob/main/conformance/README.md) for Python static type checkers. The [latest conformance test results](https://htmlpreview.github.io/?https://github.com/python/typing/blob/main/conformance/results/results.html) are here. + Historically, this repository also hosted: - The `typing_extensions` package, which now lives in the diff --git a/conformance/results/mypy/annotations_coroutines.toml b/conformance/results/mypy/annotations_coroutines.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/mypy/annotations_coroutines.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/mypy/annotations_forward_refs.toml b/conformance/results/mypy/annotations_forward_refs.toml new file mode 100644 index 000000000..34a604c35 --- /dev/null +++ b/conformance/results/mypy/annotations_forward_refs.toml @@ -0,0 +1,31 @@ +conformant = "Partial" +notes = """ +Does not report error for a forward reference that is not enclosed in quotes. +Does not report error for use of quoted type with "|" operator (runtime error). +Incorrectly generates error for quoted type defined in class scope. +""" +output = """ +annotations_forward_refs.py:41: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:42: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:43: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:44: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:45: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:46: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:47: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:48: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:49: error: Variable "annotations_forward_refs.var1" is not valid as a type [valid-type] +annotations_forward_refs.py:49: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +annotations_forward_refs.py:50: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:51: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:52: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:53: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:54: error: Invalid type comment or annotation [valid-type] +annotations_forward_refs.py:55: error: Module "types" is not valid as a type [valid-type] +annotations_forward_refs.py:55: note: Perhaps you meant to use a protocol matching the module structure? +annotations_forward_refs.py:80: error: Name "ClassF" is not defined [name-defined] +annotations_forward_refs.py:87: error: Function "annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] +annotations_forward_refs.py:87: note: Perhaps you need "Callable[...]" or a callback protocol? +annotations_forward_refs.py:89: error: Function "annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] +annotations_forward_refs.py:89: note: Perhaps you need "Callable[...]" or a callback protocol? +annotations_forward_refs.py:93: error: Expression is of type int?, not "int" [assert-type] +""" diff --git a/conformance/results/mypy/annotations_generators.toml b/conformance/results/mypy/annotations_generators.toml new file mode 100644 index 000000000..fbeeb9cdf --- /dev/null +++ b/conformance/results/mypy/annotations_generators.toml @@ -0,0 +1,15 @@ +conformant = "Partial" +notes = """ +Does not report incompatible Generator type in `yield from` statement. +""" +output = """ +annotations_generators.py:51: error: Missing return statement [return] +annotations_generators.py:54: error: Incompatible return value type (got "bool", expected "C") [return-value] +annotations_generators.py:57: error: Incompatible types in "yield" (actual type "int", expected type "A") [misc] +annotations_generators.py:66: error: Incompatible types in "yield" (actual type "int", expected type "A") [misc] +annotations_generators.py:75: error: Incompatible types in "yield" (actual type "B", expected type "A") [misc] +annotations_generators.py:86: error: The return type of a generator function should be "Generator" or one of its supertypes [misc] +annotations_generators.py:91: error: The return type of an async generator function should be "AsyncGenerator" or one of its supertypes [misc] +annotations_generators.py:118: error: Incompatible types in "yield from" (actual type "A", expected type "B") [misc] +annotations_generators.py:119: error: Incompatible types in "yield from" (actual type "int", expected type "B") [misc] +""" diff --git a/conformance/results/mypy/annotations_methods.toml b/conformance/results/mypy/annotations_methods.toml new file mode 100644 index 000000000..cbbdd307c --- /dev/null +++ b/conformance/results/mypy/annotations_methods.toml @@ -0,0 +1,7 @@ +conformant = "Pass" +notes = """ +Type evaluation differs from other type checkers because of ambiguity in the spec related to method bindings. +""" +output = """ +annotations_methods.py:42: error: Expression is of type "B", not "A" [assert-type] +""" diff --git a/conformance/results/mypy/annotations_typeexpr.toml b/conformance/results/mypy/annotations_typeexpr.toml index b5caec86b..501db9cb4 100644 --- a/conformance/results/mypy/annotations_typeexpr.toml +++ b/conformance/results/mypy/annotations_typeexpr.toml @@ -1,20 +1,22 @@ conformant = "Pass" output = """ -annotations_typeexpr.py:77: error: Invalid type comment or annotation [valid-type] -annotations_typeexpr.py:77: note: Suggestion: use eval[...] instead of eval(...) -annotations_typeexpr.py:78: error: Bracketed expression "[...]" is not valid as a type [valid-type] -annotations_typeexpr.py:79: error: Syntax error in type annotation [syntax] -annotations_typeexpr.py:79: note: Suggestion: Use Tuple[T1, ..., Tn] instead of (T1, ..., Tn) -annotations_typeexpr.py:80: error: Invalid type comment or annotation [valid-type] -annotations_typeexpr.py:81: error: Invalid type comment or annotation [valid-type] -annotations_typeexpr.py:82: error: Invalid type comment or annotation [valid-type] -annotations_typeexpr.py:83: error: Invalid type comment or annotation [valid-type] -annotations_typeexpr.py:84: error: Invalid type comment or annotation [valid-type] -annotations_typeexpr.py:85: error: Variable "annotations_typeexpr.var1" is not valid as a type [valid-type] -annotations_typeexpr.py:85: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases -annotations_typeexpr.py:86: error: Invalid type: try using Literal[True] instead? [valid-type] -annotations_typeexpr.py:87: error: Invalid type: try using Literal[1] instead? [valid-type] -annotations_typeexpr.py:88: error: Invalid type: try using Literal[-1] instead? [valid-type] -annotations_typeexpr.py:89: error: Invalid type comment or annotation [valid-type] -annotations_typeexpr.py:90: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:88: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:88: note: Suggestion: use eval[...] instead of eval(...) +annotations_typeexpr.py:89: error: Bracketed expression "[...]" is not valid as a type [valid-type] +annotations_typeexpr.py:90: error: Syntax error in type annotation [syntax] +annotations_typeexpr.py:90: note: Suggestion: Use Tuple[T1, ..., Tn] instead of (T1, ..., Tn) +annotations_typeexpr.py:91: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:92: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:93: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:94: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:95: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:96: error: Variable "annotations_typeexpr.var1" is not valid as a type [valid-type] +annotations_typeexpr.py:96: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +annotations_typeexpr.py:97: error: Invalid type: try using Literal[True] instead? [valid-type] +annotations_typeexpr.py:98: error: Invalid type: try using Literal[1] instead? [valid-type] +annotations_typeexpr.py:99: error: Invalid type: try using Literal[-1] instead? [valid-type] +annotations_typeexpr.py:100: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:101: error: Invalid type comment or annotation [valid-type] +annotations_typeexpr.py:102: error: Module "types" is not valid as a type [valid-type] +annotations_typeexpr.py:102: note: Perhaps you meant to use a protocol matching the module structure? """ diff --git a/conformance/results/mypy/classes_classvar.toml b/conformance/results/mypy/classes_classvar.toml new file mode 100644 index 000000000..ddb1a097d --- /dev/null +++ b/conformance/results/mypy/classes_classvar.toml @@ -0,0 +1,31 @@ +conformant = "Partial" +notes = """ +Internal error if TypeVarTuple is used in ClassVar. +Does not reject use of ParamSpec in ClassVar. +Rejects ClassVar nested in Annotated. +Does not reject use of ClassVar in TypeAlias definition. +Does not infer type of ClassVar from assignment if no type is provided. +""" +output = """ +classes_classvar.py:36: error: ClassVar[...] must have at most one type argument [valid-type] +classes_classvar.py:37: error: Invalid type: try using Literal[3] instead? [valid-type] +classes_classvar.py:38: error: Name "var" is not defined [name-defined] +classes_classvar.py:43: error: ClassVar cannot contain type variables [misc] +classes_classvar.py:44: error: ClassVar cannot contain type variables [misc] +classes_classvar.py:50: error: Incompatible types in assignment (expression has type "dict[Never, Never]", variable has type "list[str]") [assignment] +classes_classvar.py:52: error: Name "Final" is not defined [name-defined] +classes_classvar.py:53: error: Invalid type: ClassVar nested inside other type [valid-type] +classes_classvar.py:59: error: Invalid type: ClassVar nested inside other type [valid-type] +classes_classvar.py:61: error: ClassVar can only be used for assignments in class body [misc] +classes_classvar.py:62: error: ClassVar can only be used for assignments in class body [misc] +classes_classvar.py:63: error: ClassVar can only be used for assignments in class body [misc] +classes_classvar.py:65: error: ClassVar can only be used for assignments in class body [misc] +classes_classvar.py:69: error: ClassVar can only be used for assignments in class body [misc] +classes_classvar.py:76: error: Expression is of type "Any", not "float" [assert-type] +classes_classvar.py:100: error: Cannot assign to class variable "stats" via instance [misc] +classes_classvar.py:129: error: Incompatible types in assignment (expression has type "ProtoAImpl", variable has type "ProtoA") [assignment] +classes_classvar.py:129: note: "ProtoAImpl" is missing following "ProtoA" protocol member: +classes_classvar.py:129: note: z +classes_classvar.py:129: note: Protocol member ProtoA.x expected class variable, got instance variable +classes_classvar.py:129: note: Protocol member ProtoA.y expected class variable, got instance variable +""" diff --git a/conformance/results/mypy/classes_override.toml b/conformance/results/mypy/classes_override.toml new file mode 100644 index 000000000..ca0ea7847 --- /dev/null +++ b/conformance/results/mypy/classes_override.toml @@ -0,0 +1,12 @@ +conformant = "Partial" +notes = """ +Does not handle case where parent class derives from Any. +""" +output = """ +classes_override.py:53: error: Method "method3" is marked as an override, but no base method was found with this name [misc] +classes_override.py:56: error: Method "method4" is marked as an override, but no base method was found with this name [misc] +classes_override.py:79: error: Method "static_method1" is marked as an override, but no base method was found with this name [misc] +classes_override.py:84: error: Method "class_method1" is marked as an override, but no base method was found with this name [misc] +classes_override.py:91: error: Method "property1" is marked as an override, but no base method was found with this name [misc] +classes_override.py:103: error: Method "method1" is marked as an override, but no base method was found with this name [misc] +""" diff --git a/conformance/results/mypy/directives_assert_type.toml b/conformance/results/mypy/directives_assert_type.toml new file mode 100644 index 000000000..ed6efb743 --- /dev/null +++ b/conformance/results/mypy/directives_assert_type.toml @@ -0,0 +1,11 @@ +conformant = "Pass" +output = """ +directives_assert_type.py:27: error: Expression is of type "int | str", not "int" [assert-type] +directives_assert_type.py:28: error: Expression is of type "Any", not "int" [assert-type] +directives_assert_type.py:29: error: Expression is of type "Literal[4]", not "int" [assert-type] +directives_assert_type.py:31: error: "assert_type" expects 2 arguments [misc] +directives_assert_type.py:31: error: Too few arguments for "assert_type" [call-arg] +directives_assert_type.py:32: error: Expression is of type "Literal['']", not "int" [assert-type] +directives_assert_type.py:33: error: "assert_type" expects 2 arguments [misc] +directives_assert_type.py:33: error: Too many arguments for "assert_type" [call-arg] +""" diff --git a/conformance/results/mypy/directives_cast.toml b/conformance/results/mypy/directives_cast.toml new file mode 100644 index 000000000..c0119f44c --- /dev/null +++ b/conformance/results/mypy/directives_cast.toml @@ -0,0 +1,16 @@ +conformant = "Pass" +output = """ +directives_cast.py:15: error: "cast" expects 2 arguments [misc] +directives_cast.py:15: error: All overload variants of "cast" require at least one argument [call-overload] +directives_cast.py:15: note: Possible overload variants: +directives_cast.py:15: note: def [_T] cast(typ: type[_T], val: Any) -> _T +directives_cast.py:15: note: def cast(typ: str, val: Any) -> Any +directives_cast.py:15: note: def cast(typ: object, val: Any) -> Any +directives_cast.py:16: error: Invalid type: try using Literal[1] instead? [valid-type] +directives_cast.py:17: error: "cast" expects 2 arguments [misc] +directives_cast.py:17: error: No overload variant of "cast" matches argument types "Any", "str", "str" [call-overload] +directives_cast.py:17: note: Possible overload variants: +directives_cast.py:17: note: def [_T] cast(typ: type[_T], val: Any) -> _T +directives_cast.py:17: note: def cast(typ: str, val: Any) -> Any +directives_cast.py:17: note: def cast(typ: object, val: Any) -> Any +""" diff --git a/conformance/results/mypy/directives_no_type_check.toml b/conformance/results/mypy/directives_no_type_check.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/mypy/directives_no_type_check.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/mypy/directives_reveal_type.toml b/conformance/results/mypy/directives_reveal_type.toml new file mode 100644 index 000000000..6612496cc --- /dev/null +++ b/conformance/results/mypy/directives_reveal_type.toml @@ -0,0 +1,11 @@ +conformant = "Pass" +output = """ +directives_reveal_type.py:14: note: Revealed type is "Union[builtins.int, builtins.str]" +directives_reveal_type.py:15: note: Revealed type is "builtins.list[builtins.int]" +directives_reveal_type.py:16: note: Revealed type is "Any" +directives_reveal_type.py:17: note: Revealed type is "directives_reveal_type.ForwardReference" +directives_reveal_type.py:19: error: "reveal_type" expects 1 argument [misc] +directives_reveal_type.py:19: error: Too few arguments for "reveal_type" [call-arg] +directives_reveal_type.py:20: error: "reveal_type" expects 1 argument [misc] +directives_reveal_type.py:20: error: Too many arguments for "reveal_type" [call-arg] +""" diff --git a/conformance/results/mypy/directives_type_checking.toml b/conformance/results/mypy/directives_type_checking.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/mypy/directives_type_checking.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/mypy/directives_type_ignore.toml b/conformance/results/mypy/directives_type_ignore.toml new file mode 100644 index 000000000..0657633de --- /dev/null +++ b/conformance/results/mypy/directives_type_ignore.toml @@ -0,0 +1,10 @@ +conformant = "Partial" +notes = """ +Does not honor "# type: ignore" comment if comment includes additional text. +""" +output = """ +directives_type_ignore.py:11: error: Invalid "type: ignore" comment [syntax] +directives_type_ignore.py:11: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +directives_type_ignore.py:14: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +directives_type_ignore.py:14: note: Error code "assignment" not covered by "type: ignore" comment +""" diff --git a/conformance/results/mypy/directives_type_ignore_file1.toml b/conformance/results/mypy/directives_type_ignore_file1.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/mypy/directives_type_ignore_file1.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/mypy/directives_type_ignore_file2.toml b/conformance/results/mypy/directives_type_ignore_file2.toml new file mode 100644 index 000000000..b5962200b --- /dev/null +++ b/conformance/results/mypy/directives_type_ignore_file2.toml @@ -0,0 +1,4 @@ +conformant = "Pass" +output = """ +directives_type_ignore_file2.py:14: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +""" diff --git a/conformance/results/mypy/directives_version_platform.toml b/conformance/results/mypy/directives_version_platform.toml new file mode 100644 index 000000000..fbc4936dc --- /dev/null +++ b/conformance/results/mypy/directives_version_platform.toml @@ -0,0 +1,11 @@ +conformant = "Pass" +notes = """ +Does not understand three-element form of sys.version checks. +Does not understand os.name checks. +""" +output = """ +directives_version_platform.py:19: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +directives_version_platform.py:27: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +directives_version_platform.py:40: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +directives_version_platform.py:45: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +""" diff --git a/conformance/results/mypy/overloads_basic.toml b/conformance/results/mypy/overloads_basic.toml new file mode 100644 index 000000000..09a06cf60 --- /dev/null +++ b/conformance/results/mypy/overloads_basic.toml @@ -0,0 +1,9 @@ +conformant = "Pass" +output = """ +overloads_basic.py:37: error: No overload variant of "__getitem__" of "Bytes" matches argument type "str" [call-overload] +overloads_basic.py:37: note: Possible overload variants: +overloads_basic.py:37: note: def __getitem__(self, int, /) -> int +overloads_basic.py:37: note: def __getitem__(self, slice, /) -> bytes +overloads_basic.py:62: error: Single overload definition, multiple required [misc] +overloads_basic.py:74: error: An overloaded function outside a stub file must have an implementation [no-overload-impl] +""" diff --git a/conformance/results/mypy/protocols_class_objects.toml b/conformance/results/mypy/protocols_class_objects.toml new file mode 100644 index 000000000..897c0bb25 --- /dev/null +++ b/conformance/results/mypy/protocols_class_objects.toml @@ -0,0 +1,23 @@ +conformant = "Pass" +output = """ +protocols_class_objects.py:29: error: Only concrete class can be given where "type[Proto]" is expected [type-abstract] +protocols_class_objects.py:34: error: Can only assign concrete classes to a variable of type "type[Proto]" [type-abstract] +protocols_class_objects.py:58: error: Incompatible types in assignment (expression has type "type[ConcreteA]", variable has type "ProtoA1") [assignment] +protocols_class_objects.py:58: note: Following member(s) of "ConcreteA" have conflicts: +protocols_class_objects.py:58: note: Expected: +protocols_class_objects.py:58: note: def method1(x: int) -> int +protocols_class_objects.py:58: note: Got: +protocols_class_objects.py:58: note: def method1(self: ConcreteA, x: int) -> int +protocols_class_objects.py:74: error: Incompatible types in assignment (expression has type "type[ConcreteB]", variable has type "ProtoB1") [assignment] +protocols_class_objects.py:74: note: Following member(s) of "ConcreteB" have conflicts: +protocols_class_objects.py:74: note: prop1: expected "int", got "Callable[[ConcreteB], int]" +protocols_class_objects.py:74: note: Only class variables allowed for class object access on protocols, prop1 is an instance variable of "ConcreteB" +protocols_class_objects.py:101: error: Incompatible types in assignment (expression has type "type[ConcreteC1]", variable has type "ProtoC1") [assignment] +protocols_class_objects.py:101: note: ClassVar protocol member ProtoC1.attr1 can never be matched by a class object +protocols_class_objects.py:103: error: Incompatible types in assignment (expression has type "type[ConcreteC2]", variable has type "ProtoC1") [assignment] +protocols_class_objects.py:103: note: Only class variables allowed for class object access on protocols, attr1 is an instance variable of "ConcreteC2" +protocols_class_objects.py:103: note: ClassVar protocol member ProtoC1.attr1 can never be matched by a class object +protocols_class_objects.py:104: error: Incompatible types in assignment (expression has type "type[ConcreteC2]", variable has type "ProtoC2") [assignment] +protocols_class_objects.py:104: note: Only class variables allowed for class object access on protocols, attr1 is an instance variable of "ConcreteC2" +protocols_class_objects.py:105: error: Incompatible types in assignment (expression has type "type[ConcreteC3]", variable has type "ProtoC1") [assignment] +""" diff --git a/conformance/results/mypy/protocols_definition.toml b/conformance/results/mypy/protocols_definition.toml new file mode 100644 index 000000000..96227ab9d --- /dev/null +++ b/conformance/results/mypy/protocols_definition.toml @@ -0,0 +1,55 @@ +conformant = "Partial" +notes = """ +Rejects implicit class variable when matching protocol with explicit ClassVar. +Does not detect protocol mismatch if concrete method is missing annotations. +Does not detect protocol mismatch if concrete method's parameters are position-only. +""" +output = """ +protocols_definition.py:30: error: List item 0 has incompatible type "int"; expected "SupportsClose" [list-item] +protocols_definition.py:67: error: Protocol members cannot be defined via assignment to self [misc] +protocols_definition.py:67: error: "Template" has no attribute "temp" [attr-defined] +protocols_definition.py:114: error: Incompatible types in assignment (expression has type "Concrete2_Good2", variable has type "Template2") [assignment] +protocols_definition.py:114: note: Protocol member Template2.val1 expected class variable, got instance variable +protocols_definition.py:115: error: Incompatible types in assignment (expression has type "Concrete2_Bad1", variable has type "Template2") [assignment] +protocols_definition.py:116: error: Incompatible types in assignment (expression has type "Concrete2_Bad2", variable has type "Template2") [assignment] +protocols_definition.py:116: note: Following member(s) of "Concrete2_Bad2" have conflicts: +protocols_definition.py:116: note: val1: expected "Sequence[int]", got "Sequence[float]" +protocols_definition.py:116: note: Protocol member Template2.val1 expected class variable, got instance variable +protocols_definition.py:117: error: Incompatible types in assignment (expression has type "Concrete2_Bad3", variable has type "Template2") [assignment] +protocols_definition.py:117: note: Following member(s) of "Concrete2_Bad3" have conflicts: +protocols_definition.py:117: note: val1: expected "Sequence[int]", got "list[int]" +protocols_definition.py:117: note: Protocol member Template2.val1 expected class variable, got instance variable +protocols_definition.py:156: error: Incompatible types in assignment (expression has type "Concrete3_Bad1", variable has type "Template3") [assignment] +protocols_definition.py:157: error: Incompatible types in assignment (expression has type "Concrete3_Bad2", variable has type "Template3") [assignment] +protocols_definition.py:157: note: Protocol member Template3.val1 expected instance variable, got class variable +protocols_definition.py:158: error: Incompatible types in assignment (expression has type "Concrete3_Bad3", variable has type "Template3") [assignment] +protocols_definition.py:158: note: Protocol member Template3.val1 expected settable variable, got read-only attribute +protocols_definition.py:159: error: Incompatible types in assignment (expression has type "Concrete3_Bad4", variable has type "Template3") [assignment] +protocols_definition.py:159: note: Following member(s) of "Concrete3_Bad4" have conflicts: +protocols_definition.py:159: note: val1: expected "Sequence[int]", got "Sequence[float]" +protocols_definition.py:160: error: Incompatible types in assignment (expression has type "Concrete3_Bad5", variable has type "Template3") [assignment] +protocols_definition.py:160: note: Following member(s) of "Concrete3_Bad5" have conflicts: +protocols_definition.py:160: note: val1: expected "Sequence[int]", got "list[int]" +protocols_definition.py:218: error: Incompatible types in assignment (expression has type "Concrete4_Bad1", variable has type "Template4") [assignment] +protocols_definition.py:218: note: Following member(s) of "Concrete4_Bad1" have conflicts: +protocols_definition.py:218: note: val1: expected "Sequence[float]", got "Callable[[], Sequence[int]]" +protocols_definition.py:219: error: Incompatible types in assignment (expression has type "Concrete4_Bad2", variable has type "Template4") [assignment] +protocols_definition.py:287: error: Incompatible types in assignment (expression has type "Concrete5_Bad3", variable has type "Template5") [assignment] +protocols_definition.py:287: note: Following member(s) of "Concrete5_Bad3" have conflicts: +protocols_definition.py:287: note: Expected: +protocols_definition.py:287: note: def method1(self, a: int, b: int) -> float +protocols_definition.py:287: note: Got: +protocols_definition.py:287: note: def method1(self, *, a: int, b: int) -> float +protocols_definition.py:289: error: Incompatible types in assignment (expression has type "Concrete5_Bad5", variable has type "Template5") [assignment] +protocols_definition.py:289: note: Following member(s) of "Concrete5_Bad5" have conflicts: +protocols_definition.py:289: note: Expected: +protocols_definition.py:289: note: def method1(self, a: int, b: int) -> float +protocols_definition.py:289: note: Got: +protocols_definition.py:289: note: def method1(self: Any, a: int, b: int) -> float +protocols_definition.py:339: error: Incompatible types in assignment (expression has type "Concrete6_Bad1", variable has type "Template6") [assignment] +protocols_definition.py:339: note: Protocol member Template6.val1 expected settable variable, got read-only attribute +protocols_definition.py:340: error: Incompatible types in assignment (expression has type "Concrete6_Bad2", variable has type "Template6") [assignment] +protocols_definition.py:340: note: Protocol member Template6.val1 expected settable variable, got read-only attribute +protocols_definition.py:341: error: Incompatible types in assignment (expression has type "Concrete6_Bad3", variable has type "Template6") [assignment] +protocols_definition.py:341: note: Protocol member Template6.val1 expected settable variable, got read-only attribute +""" diff --git a/conformance/results/mypy/protocols_explicit.toml b/conformance/results/mypy/protocols_explicit.toml new file mode 100644 index 000000000..388ddbe74 --- /dev/null +++ b/conformance/results/mypy/protocols_explicit.toml @@ -0,0 +1,13 @@ +conformant = "Pass" +notes = """ +Does not report unimplemented attributes for class that explicitly derives from protocol until it is instantiated. +""" +output = """ +protocols_explicit.py:27: error: Call to abstract method "draw" of "PColor" with trivial body via super() is unsafe [safe-super] +protocols_explicit.py:56: error: Incompatible types in assignment (expression has type "tuple[int, int, str]", base class "RGB" defined the type as "tuple[int, int, int]") [assignment] +protocols_explicit.py:63: error: Cannot instantiate abstract class "Point" with abstract attributes "intensity", "other" and "transparency" [abstract] +protocols_explicit.py:92: error: Cannot instantiate abstract class "Concrete1" with abstract attributes "cm1" and "im1" [abstract] +protocols_explicit.py:114: error: Cannot instantiate abstract class "Concrete3" with abstract attributes "cm10", "cm11" and "im1" [abstract] +protocols_explicit.py:140: error: Cannot instantiate abstract class "Concrete5" with abstract attribute "method1" [abstract] +protocols_explicit.py:171: error: Cannot instantiate abstract class "Concrete7A" with abstract attribute "method1" [abstract] +""" diff --git a/conformance/results/mypy/protocols_generic.toml b/conformance/results/mypy/protocols_generic.toml new file mode 100644 index 000000000..586b695fe --- /dev/null +++ b/conformance/results/mypy/protocols_generic.toml @@ -0,0 +1,41 @@ +conformant = "Partial" +notes = """ +Fails protocol matching when method-scoped TypeVar is used in protocol. +""" +output = """ +protocols_generic.py:40: error: Incompatible types in assignment (expression has type "Concrete1", variable has type "Proto1[int, str]") [assignment] +protocols_generic.py:40: note: Following member(s) of "Concrete1" have conflicts: +protocols_generic.py:40: note: Expected: +protocols_generic.py:40: note: def __iter__(self) -> Iterator[str] +protocols_generic.py:40: note: Got: +protocols_generic.py:40: note: def __iter__(self) -> Iterator[int] +protocols_generic.py:40: note: Expected: +protocols_generic.py:40: note: def method1(self, x: int) -> int +protocols_generic.py:40: note: Got: +protocols_generic.py:40: note: def method1(self, x: str) -> str +protocols_generic.py:44: error: Only single Generic[...] or Protocol[...] can be in bases [misc] +protocols_generic.py:44: error: Duplicate type variables in Generic[...] or Protocol[...] [misc] +protocols_generic.py:56: error: Incompatible types in assignment (expression has type "Box[float]", variable has type "Box[int]") [assignment] +protocols_generic.py:66: error: Incompatible types in assignment (expression has type "Sender[int]", variable has type "Sender[float]") [assignment] +protocols_generic.py:74: error: Incompatible types in assignment (expression has type "AttrProto[int]", variable has type "AttrProto[float]") [assignment] +protocols_generic.py:75: error: Incompatible types in assignment (expression has type "AttrProto[float]", variable has type "AttrProto[int]") [assignment] +protocols_generic.py:145: error: Incompatible types in assignment (expression has type "ConcreteHasProperty2", variable has type "HasPropertyProto") [assignment] +protocols_generic.py:145: note: Following member(s) of "ConcreteHasProperty2" have conflicts: +protocols_generic.py:145: note: Expected: +protocols_generic.py:145: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str +protocols_generic.py:145: note: Got: +protocols_generic.py:145: note: def m(self, item: int, callback: Callable[[int], str]) -> str +protocols_generic.py:146: error: Incompatible types in assignment (expression has type "ConcreteHasProperty3", variable has type "HasPropertyProto") [assignment] +protocols_generic.py:146: note: Following member(s) of "ConcreteHasProperty3" have conflicts: +protocols_generic.py:146: note: f: expected "ConcreteHasProperty3", got "int" +protocols_generic.py:146: note: Expected: +protocols_generic.py:146: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str +protocols_generic.py:146: note: Got: +protocols_generic.py:146: note: def m(self, item: int, callback: Callable[[int], str]) -> str +protocols_generic.py:147: error: Incompatible types in assignment (expression has type "ConcreteHasProperty4", variable has type "HasPropertyProto") [assignment] +protocols_generic.py:147: note: Following member(s) of "ConcreteHasProperty4" have conflicts: +protocols_generic.py:147: note: Expected: +protocols_generic.py:147: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str +protocols_generic.py:147: note: Got: +protocols_generic.py:147: note: def m(self, item: str, callback: Callable[[int], str]) -> str +""" diff --git a/conformance/results/mypy/protocols_merging.toml b/conformance/results/mypy/protocols_merging.toml new file mode 100644 index 000000000..5433425a6 --- /dev/null +++ b/conformance/results/mypy/protocols_merging.toml @@ -0,0 +1,13 @@ +conformant = "Pass" +output = """ +protocols_merging.py:52: error: Incompatible types in assignment (expression has type "SCConcrete2", variable has type "SizedAndClosable1") [assignment] +protocols_merging.py:52: note: "SCConcrete2" is missing following "SizedAndClosable1" protocol member: +protocols_merging.py:52: note: __len__ +protocols_merging.py:53: error: Incompatible types in assignment (expression has type "SCConcrete2", variable has type "SizedAndClosable2") [assignment] +protocols_merging.py:53: note: "SCConcrete2" is missing following "SizedAndClosable2" protocol member: +protocols_merging.py:53: note: __len__ +protocols_merging.py:54: error: Incompatible types in assignment (expression has type "SCConcrete2", variable has type "SizedAndClosable3") [assignment] +protocols_merging.py:68: error: All bases of a protocol must be protocols [misc] +protocols_merging.py:83: error: Cannot instantiate abstract class "SizedAndClosable4" with abstract attribute "close" [abstract] +protocols_merging.py:84: error: Incompatible types in assignment (expression has type "SCConcrete1", variable has type "SizedAndClosable4") [assignment] +""" diff --git a/conformance/results/mypy/protocols_modules.toml b/conformance/results/mypy/protocols_modules.toml new file mode 100644 index 000000000..def330375 --- /dev/null +++ b/conformance/results/mypy/protocols_modules.toml @@ -0,0 +1,13 @@ +conformant = "Pass" +output = """ +protocols_modules.py:26: error: Incompatible types in assignment (expression has type Module, variable has type "Options2") [assignment] +protocols_modules.py:26: note: Following member(s) of Module "_protocols_modules1" have conflicts: +protocols_modules.py:26: note: timeout: expected "str", got "int" +protocols_modules.py:48: error: Incompatible types in assignment (expression has type Module, variable has type "Reporter2") [assignment] +protocols_modules.py:48: note: Following member(s) of Module "_protocols_modules2" have conflicts: +protocols_modules.py:48: note: Expected: +protocols_modules.py:48: note: def on_error(x: int) -> int +protocols_modules.py:48: note: Got: +protocols_modules.py:48: note: def on_error(x: int) -> None +protocols_modules.py:49: error: Incompatible types in assignment (expression has type Module, variable has type "Reporter3") [assignment] +""" diff --git a/conformance/results/mypy/protocols_recursive.toml b/conformance/results/mypy/protocols_recursive.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/mypy/protocols_recursive.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/mypy/protocols_runtime_checkable.toml b/conformance/results/mypy/protocols_runtime_checkable.toml new file mode 100644 index 000000000..b6a042ef1 --- /dev/null +++ b/conformance/results/mypy/protocols_runtime_checkable.toml @@ -0,0 +1,9 @@ +conformant = "Partial" +notes = """ +Does not report unsafe overlap for runtime_checkable protocol. +""" +output = """ +protocols_runtime_checkable.py:23: error: Only @runtime_checkable protocols can be used with instance and class checks [misc] +protocols_runtime_checkable.py:55: error: Only protocols that don't have non-method members can be used with issubclass() [misc] +protocols_runtime_checkable.py:55: note: Protocol "DataProtocol" has non-method member(s): name +""" diff --git a/conformance/results/mypy/protocols_self.toml b/conformance/results/mypy/protocols_self.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/mypy/protocols_self.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/mypy/protocols_subtyping.toml b/conformance/results/mypy/protocols_subtyping.toml new file mode 100644 index 000000000..926a57c52 --- /dev/null +++ b/conformance/results/mypy/protocols_subtyping.toml @@ -0,0 +1,12 @@ +conformant = "Pass" +output = """ +protocols_subtyping.py:16: error: Cannot instantiate protocol class "Proto1" [misc] +protocols_subtyping.py:38: error: Incompatible types in assignment (expression has type "Proto2", variable has type "Concrete2") [assignment] +protocols_subtyping.py:55: error: Incompatible types in assignment (expression has type "Proto2", variable has type "Proto3") [assignment] +protocols_subtyping.py:55: note: "Proto2" is missing following "Proto3" protocol member: +protocols_subtyping.py:55: note: method2 +protocols_subtyping.py:79: error: Incompatible types in assignment (expression has type "Proto5[int]", variable has type "Proto4[int, float]") [assignment] +protocols_subtyping.py:80: error: Incompatible types in assignment (expression has type "Proto4[int, int]", variable has type "Proto5[float]") [assignment] +protocols_subtyping.py:102: error: Incompatible types in assignment (expression has type "Proto6[float, float]", variable has type "Proto7[int, float]") [assignment] +protocols_subtyping.py:103: error: Incompatible types in assignment (expression has type "Proto6[float, float]", variable has type "Proto7[float, object]") [assignment] +""" diff --git a/conformance/results/mypy/protocols_variance.toml b/conformance/results/mypy/protocols_variance.toml new file mode 100644 index 000000000..63f5ba01b --- /dev/null +++ b/conformance/results/mypy/protocols_variance.toml @@ -0,0 +1,12 @@ +conformant = "Pass" +output = """ +protocols_variance.py:21: error: Invariant type variable "T1" used in protocol where covariant one is expected [misc] +protocols_variance.py:40: error: Invariant type variable "T3" used in protocol where contravariant one is expected [misc] +protocols_variance.py:56: error: Invariant type variable "T1" used in protocol where contravariant one is expected [misc] +protocols_variance.py:61: error: Covariant type variable "T1_co" used in protocol where contravariant one is expected [misc] +protocols_variance.py:62: error: Cannot use a covariant type variable as a parameter [misc] +protocols_variance.py:66: error: Invariant type variable "T1" used in protocol where covariant one is expected [misc] +protocols_variance.py:71: error: Contravariant type variable "T1_contra" used in protocol where covariant one is expected [misc] +protocols_variance.py:72: error: Cannot use a contravariant type variable as return type [misc] +protocols_variance.py:104: error: Invariant type variable "T1" used in protocol where covariant one is expected [misc] +""" diff --git a/conformance/results/mypy/qualifiers_annotated.toml b/conformance/results/mypy/qualifiers_annotated.toml new file mode 100644 index 000000000..44adc1ec8 --- /dev/null +++ b/conformance/results/mypy/qualifiers_annotated.toml @@ -0,0 +1,26 @@ +conformant = "Partial" +notes = """ +Does not allow ClassVar to be nested within Annotated. +Does not allow Final to be nested within Annotated. +Does not allow Required and NotRequired to be nested within Annotated. +""" +output = """ +qualifiers_annotated.py:41: error: Bracketed expression "[...]" is not valid as a type [valid-type] +qualifiers_annotated.py:42: error: Syntax error in type annotation [syntax] +qualifiers_annotated.py:42: note: Suggestion: Is there a spurious trailing comma? +qualifiers_annotated.py:43: error: Invalid type comment or annotation [valid-type] +qualifiers_annotated.py:44: error: Invalid type comment or annotation [valid-type] +qualifiers_annotated.py:45: error: Invalid type comment or annotation [valid-type] +qualifiers_annotated.py:46: error: Invalid type comment or annotation [valid-type] +qualifiers_annotated.py:47: error: Invalid type comment or annotation [valid-type] +qualifiers_annotated.py:48: error: Name "var1" is not defined [name-defined] +qualifiers_annotated.py:49: error: Invalid type: try using Literal[True] instead? [valid-type] +qualifiers_annotated.py:50: error: Invalid type: try using Literal[1] instead? [valid-type] +qualifiers_annotated.py:51: error: Invalid type comment or annotation [valid-type] +qualifiers_annotated.py:52: error: Invalid type comment or annotation [valid-type] +qualifiers_annotated.py:62: error: Annotated[...] must have exactly one type argument and at least one annotation [valid-type] +qualifiers_annotated.py:73: error: Invalid type: ClassVar nested inside other type [valid-type] +qualifiers_annotated.py:75: error: Final can be only used as an outermost qualifier in a variable annotation [valid-type] +qualifiers_annotated.py:85: error: Required[] can be only used in a TypedDict definition [valid-type] +qualifiers_annotated.py:87: error: NotRequired[] can be only used in a TypedDict definition [valid-type] +""" diff --git a/conformance/results/mypy/qualifiers_final_annotation.toml b/conformance/results/mypy/qualifiers_final_annotation.toml new file mode 100644 index 000000000..f5295752e --- /dev/null +++ b/conformance/results/mypy/qualifiers_final_annotation.toml @@ -0,0 +1,38 @@ +conformant = "Partial" +notes = """ +Does not treat use of Final name as if it was replaced by the literal in NamedTuple definition. +Does not allow conditional assignment of Final instance variable in __init__ method. +Does not allow redefinition of private class variable that is marked Final in parent class. +Does not report modification of local Final variable via "for" statement. +""" +output = """ +qualifiers_final_annotation.py:16: error: Type in Final[...] can only be omitted if there is an initializer [misc] +qualifiers_final_annotation.py:18: error: Final[...] takes at most one type argument [misc] +qualifiers_final_annotation.py:34: error: Type in Final[...] can only be omitted if there is an initializer [misc] +qualifiers_final_annotation.py:38: error: Final name must be initialized with a value [misc] +qualifiers_final_annotation.py:54: error: Cannot assign to final attribute "ID5" [misc] +qualifiers_final_annotation.py:59: error: Cannot assign to final attribute "ID6" [misc] +qualifiers_final_annotation.py:62: error: Can only declare a final attribute in class body or __init__ [misc] +qualifiers_final_annotation.py:63: error: Can only declare a final attribute in class body or __init__ [misc] +qualifiers_final_annotation.py:65: error: Cannot assign to final attribute "ID7" [misc] +qualifiers_final_annotation.py:67: error: Cannot assign to final attribute "ID7" [misc] +qualifiers_final_annotation.py:71: error: Cannot assign to final name "RATE" [misc] +qualifiers_final_annotation.py:81: error: Cannot assign to final attribute "DEFAULT_ID" [misc] +qualifiers_final_annotation.py:94: error: Cannot assign to final name "BORDER_WIDTH" [misc] +qualifiers_final_annotation.py:96: error: Cannot assign to final name "__private" [misc] +qualifiers_final_annotation.py:107: error: Final can be only used as an outermost qualifier in a variable annotation [valid-type] +qualifiers_final_annotation.py:108: error: Variable should not be annotated with both ClassVar and Final [misc] +qualifiers_final_annotation.py:118: error: Final can be only used as an outermost qualifier in a variable annotation [valid-type] +qualifiers_final_annotation.py:121: error: Final can be only used as an outermost qualifier in a variable annotation [valid-type] +qualifiers_final_annotation.py:131: error: Invalid "NamedTuple()" field name [misc] +qualifiers_final_annotation.py:133: error: Unexpected keyword argument "x" for "N" [call-arg] +qualifiers_final_annotation.py:133: error: Unexpected keyword argument "y" for "N" [call-arg] +qualifiers_final_annotation.py:134: error: Unexpected keyword argument "a" for "N" [call-arg] +qualifiers_final_annotation.py:135: error: Unexpected keyword argument "x" for "N" [call-arg] +qualifiers_final_annotation.py:135: error: Unexpected keyword argument "y" for "N" [call-arg] +qualifiers_final_annotation.py:141: error: Cannot assign to final name "ID1" [misc] +qualifiers_final_annotation.py:145: error: Cannot assign to final name "x" [misc] +qualifiers_final_annotation.py:147: error: Cannot assign to final name "x" [misc] +qualifiers_final_annotation.py:152: error: Incompatible types in assignment (expression has type "TextIOWrapper", variable has type "int") [assignment] +qualifiers_final_annotation.py:155: error: Cannot assign to final name "x" [misc] +""" diff --git a/conformance/results/mypy/qualifiers_final_decorator.toml b/conformance/results/mypy/qualifiers_final_decorator.toml new file mode 100644 index 000000000..114785bd1 --- /dev/null +++ b/conformance/results/mypy/qualifiers_final_decorator.toml @@ -0,0 +1,18 @@ +conformant = "Pass" +output = """ +qualifiers_final_decorator.py:21: error: Cannot inherit from final class "Base1" [misc] +qualifiers_final_decorator.py:56: error: Cannot override final attribute "method1" (previously declared in base class "Base2") [misc] +qualifiers_final_decorator.py:59: error: Cannot override final attribute "method2" (previously declared in base class "Base2") [misc] +qualifiers_final_decorator.py:63: error: Cannot override final attribute "method3" (previously declared in base class "Base2") [misc] +qualifiers_final_decorator.py:67: error: Cannot override final attribute "method4" (previously declared in base class "Base2") [misc] +qualifiers_final_decorator.py:80: error: Cannot override final attribute "method" (previously declared in base class "Base3") [misc] +qualifiers_final_decorator.py:84: error: @final should be applied only to overload implementation [misc] +qualifiers_final_decorator.py:94: error: Cannot override final attribute "method" (previously declared in base class "Base4") [misc] +qualifiers_final_decorator.py:118: error: Cannot override final attribute "method" (previously declared in base class "Base5_2") [misc] +qualifiers_final_decorator.py:118: error: Signature of "method" incompatible with supertype "Base5_2" [override] +qualifiers_final_decorator.py:118: note: Superclass: +qualifiers_final_decorator.py:118: note: def method(self, v: int) -> None +qualifiers_final_decorator.py:118: note: Subclass: +qualifiers_final_decorator.py:118: note: def method(self) -> None +qualifiers_final_decorator.py:125: error: @final cannot be used with non-method functions [misc] +""" diff --git a/conformance/results/mypy/specialtypes_any.toml b/conformance/results/mypy/specialtypes_any.toml new file mode 100644 index 000000000..d79075e53 --- /dev/null +++ b/conformance/results/mypy/specialtypes_any.toml @@ -0,0 +1,5 @@ +conformant = "Pass" +output = """ +specialtypes_any.py:37: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs [annotation-unchecked] +specialtypes_any.py:38: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs [annotation-unchecked] +""" diff --git a/conformance/results/mypy/specialtypes_never.toml b/conformance/results/mypy/specialtypes_never.toml new file mode 100644 index 000000000..6fa559923 --- /dev/null +++ b/conformance/results/mypy/specialtypes_never.toml @@ -0,0 +1,11 @@ +conformant = "Partial" +notes = """ +Does not reject NoReturn when used outside of return type annotation. +""" +output = """ +specialtypes_never.py:19: error: Implicit return in function which does not return [misc] +specialtypes_never.py:85: error: Incompatible types in assignment (expression has type "list[NoReturn]", variable has type "list[int]") [assignment] +specialtypes_never.py:85: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance +specialtypes_never.py:85: note: Consider using "Sequence" instead, which is covariant +specialtypes_never.py:104: error: Incompatible return value type (got "ClassC[NoReturn]", expected "ClassC[U]") [return-value] +""" diff --git a/conformance/results/mypy/specialtypes_none.toml b/conformance/results/mypy/specialtypes_none.toml new file mode 100644 index 000000000..3730e8547 --- /dev/null +++ b/conformance/results/mypy/specialtypes_none.toml @@ -0,0 +1,6 @@ +conformant = "Pass" +output = """ +specialtypes_none.py:21: error: Argument 1 to "func1" has incompatible type "type[None]"; expected "None" [arg-type] +specialtypes_none.py:27: error: Incompatible types in assignment (expression has type "None", variable has type "Iterable[Any]") [assignment] +specialtypes_none.py:41: error: Argument 1 to "func2" has incompatible type "None"; expected "type[None]" [arg-type] +""" diff --git a/conformance/results/mypy/specialtypes_promotions.toml b/conformance/results/mypy/specialtypes_promotions.toml new file mode 100644 index 000000000..60e34faf9 --- /dev/null +++ b/conformance/results/mypy/specialtypes_promotions.toml @@ -0,0 +1,4 @@ +conformant = "Pass" +output = """ +specialtypes_promotions.py:13: error: "float" has no attribute "numerator" [attr-defined] +""" diff --git a/conformance/results/mypy/specialtypes_tuple.toml b/conformance/results/mypy/specialtypes_tuple.toml new file mode 100644 index 000000000..d7a670bd8 --- /dev/null +++ b/conformance/results/mypy/specialtypes_tuple.toml @@ -0,0 +1,10 @@ +conformant = "Pass" +output = """ +specialtypes_tuple.py:12: error: Incompatible types in assignment (expression has type "tuple[int, int]", variable has type "tuple[int]") [assignment] +specialtypes_tuple.py:14: error: Incompatible types in assignment (expression has type "tuple[int]", variable has type "tuple[int, int]") [assignment] +specialtypes_tuple.py:15: error: Incompatible types in assignment (expression has type "tuple[int, str]", variable has type "tuple[int, int]") [assignment] +specialtypes_tuple.py:25: error: Incompatible types in assignment (expression has type "tuple[int]", variable has type "tuple[()]") [assignment] +specialtypes_tuple.py:36: error: Incompatible types in assignment (expression has type "tuple[int, int, int, str]", variable has type "tuple[int, ...]") [assignment] +specialtypes_tuple.py:42: error: Incompatible types in assignment (expression has type "tuple[int, ...]", variable has type "tuple[int]") [assignment] +specialtypes_tuple.py:43: error: Incompatible types in assignment (expression has type "tuple[int, ...]", variable has type "tuple[()]") [assignment] +""" diff --git a/conformance/results/mypy/specialtypes_tuple_unpack.toml b/conformance/results/mypy/specialtypes_tuple_unpack.toml new file mode 100644 index 000000000..0d497bf3a --- /dev/null +++ b/conformance/results/mypy/specialtypes_tuple_unpack.toml @@ -0,0 +1,14 @@ +conformant = "Pass" +output = """ +specialtypes_tuple_unpack.py: error: More than one Unpack in a type is not allowed [misc] +specialtypes_tuple_unpack.py:7: error: Incompatible types in assignment (expression has type "tuple[int, str, str]", variable has type "tuple[int, str]") [assignment] +specialtypes_tuple_unpack.py:12: error: Incompatible types in assignment (expression has type "tuple[int, int, str]", variable has type "tuple[int, *tuple[str, ...]]") [assignment] +specialtypes_tuple_unpack.py:13: error: Incompatible types in assignment (expression has type "tuple[int, str, int]", variable has type "tuple[int, *tuple[str, ...]]") [assignment] +specialtypes_tuple_unpack.py:19: error: Incompatible types in assignment (expression has type "tuple[int, str, str]", variable has type "tuple[int, *tuple[str, ...], int]") [assignment] +specialtypes_tuple_unpack.py:20: error: Incompatible types in assignment (expression has type "tuple[int, str, str, float]", variable has type "tuple[int, *tuple[str, ...], int]") [assignment] +specialtypes_tuple_unpack.py:25: error: Incompatible types in assignment (expression has type "tuple[int, str, int]", variable has type "tuple[*tuple[str, ...], int]") [assignment] +specialtypes_tuple_unpack.py:26: error: Incompatible types in assignment (expression has type "tuple[str, str, float]", variable has type "tuple[*tuple[str, ...], int]") [assignment] +specialtypes_tuple_unpack.py:34: error: Incompatible types in assignment (expression has type "tuple[str, str]", variable has type "tuple[str, str, int]") [assignment] +specialtypes_tuple_unpack.py:37: error: Incompatible types in assignment (expression has type "tuple[str, str]", variable has type "tuple[str, str, str, *tuple[str, ...]]") [assignment] +specialtypes_tuple_unpack.py:41: error: Incompatible types in assignment (expression has type "tuple[str, str]", variable has type "tuple[*tuple[str, ...], str, str, str]") [assignment] +""" diff --git a/conformance/results/mypy/specialtypes_type.toml b/conformance/results/mypy/specialtypes_type.toml new file mode 100644 index 000000000..474c3e4a4 --- /dev/null +++ b/conformance/results/mypy/specialtypes_type.toml @@ -0,0 +1,20 @@ +conformant = "Partial" +notes = """ +Does not treat `type` same as `type[Any]` for assert_type. +Does not allow access to unknown attributes from object of type `type[Any]`. +""" +output = """ +specialtypes_type.py:56: error: Argument 1 to "func4" has incompatible type "type[TeamUser]"; expected "type[BasicUser] | type[ProUser]" [arg-type] +specialtypes_type.py:70: error: Argument 1 to "func5" has incompatible type ""; expected "type[Never]" [arg-type] +specialtypes_type.py:76: error: type[...] must have exactly one type argument [valid-type] +specialtypes_type.py:84: error: Expression is of type "type", not "type[Any]" [assert-type] +specialtypes_type.py:99: error: "type" has no attribute "unknown" [attr-defined] +specialtypes_type.py:100: error: "type" has no attribute "unknown" [attr-defined] +specialtypes_type.py:117: error: "type[object]" has no attribute "unknown" [attr-defined] +specialtypes_type.py:120: error: "type[object]" has no attribute "unknown" [attr-defined] +specialtypes_type.py:139: error: Expression is of type "type", not "type[Any]" [assert-type] +specialtypes_type.py:143: error: "" has no attribute "unknown" [attr-defined] +specialtypes_type.py:144: error: "" has no attribute "unknown" [attr-defined] +specialtypes_type.py:145: error: "type[type]" has no attribute "unknown" [attr-defined] +specialtypes_type.py:146: error: "" has no attribute "unknown" [attr-defined] +""" diff --git a/conformance/results/mypy/typeddicts_alt_syntax.toml b/conformance/results/mypy/typeddicts_alt_syntax.toml index bb1da440f..8d540eb0f 100644 --- a/conformance/results/mypy/typeddicts_alt_syntax.toml +++ b/conformance/results/mypy/typeddicts_alt_syntax.toml @@ -1,4 +1,4 @@ -conformant = "Partial" +conformant = "Pass" notes = """ Does not support keyword-argument form of alternative syntax (deprecated in 3.11). """ diff --git a/conformance/results/mypy/version.toml b/conformance/results/mypy/version.toml index 63c2f4d79..1842b0d7a 100644 --- a/conformance/results/mypy/version.toml +++ b/conformance/results/mypy/version.toml @@ -1,2 +1,2 @@ version = "mypy 1.8.0" -test_duration = 0.3814089298248291 +test_duration = 0.6594910621643066 diff --git a/conformance/results/pyre/annotations_coroutines.toml b/conformance/results/pyre/annotations_coroutines.toml new file mode 100644 index 000000000..b04dee644 --- /dev/null +++ b/conformance/results/pyre/annotations_coroutines.toml @@ -0,0 +1,7 @@ +conformant = "Partial" +notes = """ +Does not evaluate correct type for async function. +""" +output = """ +annotations_coroutines.py:19:45 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[Type[Variable[_T_co](covariant)], Type[Variable[_T_contra](contravariant)], Type[Variable[_V_co](covariant)]]` but got `Tuple[object, object, Type[str]]`. +""" diff --git a/conformance/results/pyre/annotations_forward_refs.toml b/conformance/results/pyre/annotations_forward_refs.toml new file mode 100644 index 000000000..3a88c0b03 --- /dev/null +++ b/conformance/results/pyre/annotations_forward_refs.toml @@ -0,0 +1,28 @@ +conformant = "Partial" +notes = """ +Does not report error for a forward reference that is not enclosed in quotes. +Does not report error for use of quoted type with "|" operator (runtime error). +Does not reject f-string in quoted type annotation. +Incorrectly generates error for quoted type defined in class scope. +Does not generate error for unquoted type defined in class scope. +""" +output = """ +annotations_forward_refs.py:19:25 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `UnionType`. +annotations_forward_refs.py:41:8 Undefined or invalid type [11]: Annotation `eval(.join(map(chr, [105, 110, 116])))` is not defined as a type. +annotations_forward_refs.py:42:8 Invalid type [31]: Expression `"[int, str]"` is not a valid type. +annotations_forward_refs.py:43:8 Invalid type [31]: Expression `"(int, str)"` is not a valid type. +annotations_forward_refs.py:44:8 Undefined or invalid type [11]: Annotation `comprehension(int for generators(generator($target$i in range(1) if )))` is not defined as a type. +annotations_forward_refs.py:45:8 Invalid type [31]: Expression `"{ }"` is not a valid type. +annotations_forward_refs.py:46:8 Undefined or invalid type [11]: Annotation `lambda () (int)()` is not defined as a type. +annotations_forward_refs.py:47:8 Invalid type [31]: Expression `[int][0]` is not a valid type. +annotations_forward_refs.py:48:8 Invalid type [31]: Expression `"int if 1 < 3 else str"` is not a valid type. +annotations_forward_refs.py:49:8 Undefined or invalid type [11]: Annotation `var1` is not defined as a type. +annotations_forward_refs.py:50:9 Invalid type [31]: Expression `"True"` is not a valid type. +annotations_forward_refs.py:51:9 Invalid type [31]: Expression `"1"` is not a valid type. +annotations_forward_refs.py:52:9 Invalid type [31]: Expression `"-1"` is not a valid type. +annotations_forward_refs.py:53:9 Invalid type [31]: Expression `"int or str"` is not a valid type. +annotations_forward_refs.py:55:9 Undefined or invalid type [11]: Annotation `types` is not defined as a type. +annotations_forward_refs.py:77:0 Uninitialized attribute [13]: Attribute `ClassC` is declared in class `ClassD` to have type `ClassC` but is never initialized. +annotations_forward_refs.py:80:12 Undefined or invalid type [11]: Annotation `ClassF` is not defined as a type. +annotations_forward_refs.py:87:7 Undefined or invalid type [11]: Annotation `ClassD.int` is not defined as a type. +""" diff --git a/conformance/results/pyre/annotations_generators.toml b/conformance/results/pyre/annotations_generators.toml new file mode 100644 index 000000000..48bda1ea9 --- /dev/null +++ b/conformance/results/pyre/annotations_generators.toml @@ -0,0 +1,22 @@ +conformant = "Partial" +notes = """ +Does not report invalid return type for generator when function implicitly returns None. +Incorrectly evaluates type of call to async generator. +""" +output = """ +annotations_generators.py:54:8 Incompatible return type [7]: Expected `Generator[A, B, C]` but got `Generator[typing.Any, typing.Any, bool]`. +annotations_generators.py:57:8 Incompatible return type [7]: Expected `Generator[A, B, C]` but got `Generator[int, typing.Any, typing.Any]`. +annotations_generators.py:66:8 Incompatible return type [7]: Expected `Generator[A, int, typing.Any]` but got `Generator[int, typing.Any, typing.Any]`. +annotations_generators.py:75:4 Incompatible return type [7]: Expected `Iterator[A]` but got `Generator[B, typing.Any, typing.Any]`. +annotations_generators.py:87:4 Incompatible return type [7]: Expected `int` but got `Generator[None, typing.Any, typing.Any]`. +annotations_generators.py:88:4 Incompatible return type [7]: Expected `int` but got `Generator[typing.Any, typing.Any, int]`. +annotations_generators.py:91:0 Incompatible async generator return type [57]: Expected return annotation to be AsyncGenerator or a superclass but got `int`. +annotations_generators.py:92:4 Incompatible return type [7]: Expected `int` but got `AsyncGenerator[None, typing.Any]`. +annotations_generators.py:118:4 Incompatible return type [7]: Expected `Iterator[B]` but got `Generator[A, None, typing.Any]`. +annotations_generators.py:119:4 Incompatible return type [7]: Expected `Iterator[B]` but got `Generator[int, None, typing.Any]`. +annotations_generators.py:135:4 Incompatible return type [7]: Expected `Generator[None, str, None]` but got `Generator[None, int, typing.Any]`. +annotations_generators.py:167:35 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[Type[Variable[_T_co](covariant)], Type[Variable[_T_contra](contravariant)]]` but got `Tuple[Type[str], None]`. +annotations_generators.py:174:35 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[Type[Variable[_T_co](covariant)], Type[Variable[_T_contra](contravariant)]]` but got `Tuple[Type[str], None]`. +annotations_generators.py:182:48 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[Type[Variable[_T_co](covariant)], Type[Variable[_T_contra](contravariant)], Type[Variable[_V_co](covariant)]]` but got `Tuple[object, object, typing.Any]`. +annotations_generators.py:182:58 Undefined attribute [16]: `AsyncIterator` has no attribute `__getitem__`. +""" diff --git a/conformance/results/pyre/annotations_methods.toml b/conformance/results/pyre/annotations_methods.toml new file mode 100644 index 000000000..d93e17525 --- /dev/null +++ b/conformance/results/pyre/annotations_methods.toml @@ -0,0 +1,6 @@ +conformant = "Pass" +notes = """ +Type evaluation differs from other type checkers because of ambiguity in the spec related to method bindings. +""" +output = """ +""" diff --git a/conformance/results/pyre/annotations_typeexpr.toml b/conformance/results/pyre/annotations_typeexpr.toml index 86bc19108..c1cb1b406 100644 --- a/conformance/results/pyre/annotations_typeexpr.toml +++ b/conformance/results/pyre/annotations_typeexpr.toml @@ -1,19 +1,20 @@ conformant = "Pass" output = """ -annotations_typeexpr.py:65:26 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `object`. -annotations_typeexpr.py:67:27 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[object, typing.Any]`. -annotations_typeexpr.py:77:8 Invalid type [31]: Expression `eval("".join(map(chr, [105, 110, 116])))` is not a valid type. -annotations_typeexpr.py:78:8 Invalid type [31]: Expression `[int, str]` is not a valid type. -annotations_typeexpr.py:79:8 Invalid type [31]: Expression `(int, str)` is not a valid type. -annotations_typeexpr.py:80:8 Invalid type [31]: Expression `comprehension(int for generators(generator($target$i in range(1) if )))` is not a valid type. -annotations_typeexpr.py:81:8 Invalid type [31]: Expression `{ }` is not a valid type. -annotations_typeexpr.py:82:8 Invalid type [31]: Expression `lambda () (int)()` is not a valid type. -annotations_typeexpr.py:83:8 Invalid type [31]: Expression `[int][0]` is not a valid type. -annotations_typeexpr.py:84:8 Invalid type [31]: Expression `int if 1 < 3 else str` is not a valid type. -annotations_typeexpr.py:85:8 Undefined or invalid type [11]: Annotation `var1` is not defined as a type. -annotations_typeexpr.py:86:9 Invalid type [31]: Expression `True` is not a valid type. -annotations_typeexpr.py:87:9 Invalid type [31]: Expression `1` is not a valid type. -annotations_typeexpr.py:88:9 Invalid type [31]: Expression `-1` is not a valid type. -annotations_typeexpr.py:89:9 Invalid type [31]: Expression `int or str` is not a valid type. -annotations_typeexpr.py:90:9 Invalid type [31]: Expression `"int"` is not a valid type. +annotations_typeexpr.py:75:26 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `object`. +annotations_typeexpr.py:77:27 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[object, typing.Any]`. +annotations_typeexpr.py:88:8 Invalid type [31]: Expression `eval("".join(map(chr, [105, 110, 116])))` is not a valid type. +annotations_typeexpr.py:89:8 Invalid type [31]: Expression `[int, str]` is not a valid type. +annotations_typeexpr.py:90:8 Invalid type [31]: Expression `(int, str)` is not a valid type. +annotations_typeexpr.py:91:8 Invalid type [31]: Expression `comprehension(int for generators(generator($target$i in range(1) if )))` is not a valid type. +annotations_typeexpr.py:92:8 Invalid type [31]: Expression `{ }` is not a valid type. +annotations_typeexpr.py:93:8 Invalid type [31]: Expression `lambda () (int)()` is not a valid type. +annotations_typeexpr.py:94:8 Invalid type [31]: Expression `[int][0]` is not a valid type. +annotations_typeexpr.py:95:8 Invalid type [31]: Expression `int if 1 < 3 else str` is not a valid type. +annotations_typeexpr.py:96:8 Undefined or invalid type [11]: Annotation `var1` is not defined as a type. +annotations_typeexpr.py:97:9 Invalid type [31]: Expression `True` is not a valid type. +annotations_typeexpr.py:98:9 Invalid type [31]: Expression `1` is not a valid type. +annotations_typeexpr.py:99:9 Invalid type [31]: Expression `-1` is not a valid type. +annotations_typeexpr.py:100:9 Invalid type [31]: Expression `int or str` is not a valid type. +annotations_typeexpr.py:101:9 Invalid type [31]: Expression `"int"` is not a valid type. +annotations_typeexpr.py:102:9 Undefined or invalid type [11]: Annotation `types` is not defined as a type. """ diff --git a/conformance/results/pyre/classes_classvar.toml b/conformance/results/pyre/classes_classvar.toml new file mode 100644 index 000000000..af8bcfde2 --- /dev/null +++ b/conformance/results/pyre/classes_classvar.toml @@ -0,0 +1,27 @@ +conformant = "Partial" +notes = """ +Does not reject use of TypeVar in ClassVar. +Does not reject use of ParamSpec in ClassVar. +Does not reject use of ClassVar as a generic type argument. +Does not reject use of ClassVar in parameter type annotation. +Does not reject use of ClassVar in local variable annotation. +Does not reject use of ClassVar in instance variable annotation. +Does not reject use of ClassVar in return type annotation. +Does not reject use of ClassVar in type alias definition. +""" +output = """ +classes_classvar.py:33:0 Uninitialized attribute [13]: Attribute `bad1` is declared in class `ClassA` to have type `typing.Any` but is never initialized. +classes_classvar.py:33:0 Uninitialized attribute [13]: Attribute `bad4` is declared in class `ClassA` to have type `Variable[T]` but is never initialized. +classes_classvar.py:33:0 Uninitialized attribute [13]: Attribute `bad5` is declared in class `ClassA` to have type `typing.List[Variable[T]]` but is never initialized. +classes_classvar.py:33:0 Uninitialized attribute [13]: Attribute `bad6` is declared in class `ClassA` to have type `typing.Callable[classes_classvar.P, typing.Any]` but is never initialized. +classes_classvar.py:36:10 Invalid type parameters [24]: Generic type `CV` expects 1 type parameter, received 2. +classes_classvar.py:37:10 Invalid type [31]: Expression `typing.ClassVar[3]` is not a valid type. +classes_classvar.py:38:13 Unbound name [10]: Name `var` is used but not defined in the current scope. +classes_classvar.py:50:4 Incompatible attribute type [8]: Attribute `bad8` declared in class `ClassA` has type `List[str]` but is used as type `Dict[Variable[_KT], Variable[_VT]]`. +classes_classvar.py:52:10 Unbound name [10]: Name `Final` is used but not defined in the current scope. +classes_classvar.py:63:8 Undefined attribute [16]: `ClassA` has no attribute `xx`. +classes_classvar.py:66:8 Incompatible return type [7]: Expected `CV[int]` but got `int`. +classes_classvar.py:79:0 Uninitialized attribute [13]: Attribute `damage` is declared in class `BasicStarship` to have type `int` but is never initialized. +classes_classvar.py:100:0 Invalid assignment [41]: Assigning to class variable through instance, did you mean to assign to `Starship.stats` instead? +classes_classvar.py:129:0 Incompatible variable type [9]: a is declared to have type `ProtoA` but is used as type `ProtoAImpl`. +""" diff --git a/conformance/results/pyre/classes_override.toml b/conformance/results/pyre/classes_override.toml new file mode 100644 index 000000000..0ba1e9365 --- /dev/null +++ b/conformance/results/pyre/classes_override.toml @@ -0,0 +1,22 @@ +conformant = "Unsupported" +notes = """ +Does not yet support the @override decorator. +""" +output = """ +classes_override.py:7:0 Undefined import [21]: Could not find a name `override` defined in module `typing`. +classes_override.py:37:5 Undefined attribute [16]: Module `typing` has no attribute `override`. +classes_override.py:52:5 Undefined attribute [16]: Module `typing` has no attribute `override`. +classes_override.py:53:4 Invalid override [40]: `classes_override.ChildA.method3` is decorated with @override, but no method of the same name exists in superclasses of `ChildA`. +classes_override.py:64:5 Undefined attribute [16]: Module `typing` has no attribute `override`. +classes_override.py:65:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). +classes_override.py:65:4 Invalid override [40]: `classes_override.ChildA.method4` is decorated with @override, but no method of the same name exists in superclasses of `ChildA`. +classes_override.py:78:5 Undefined attribute [16]: Module `typing` has no attribute `override`. +classes_override.py:79:4 Invalid override [40]: `classes_override.ChildA.static_method1` is decorated with @override, but no method of the same name exists in superclasses of `ChildA`. +classes_override.py:83:5 Undefined attribute [16]: Module `typing` has no attribute `override`. +classes_override.py:84:4 Invalid override [40]: `classes_override.ChildA.class_method1` is decorated with @override, but no method of the same name exists in superclasses of `ChildA`. +classes_override.py:90:5 Undefined attribute [16]: Module `typing` has no attribute `override`. +classes_override.py:91:4 Invalid override [40]: `classes_override.ChildA.property1` is decorated with @override, but no method of the same name exists in superclasses of `ChildA`. +classes_override.py:97:14 Invalid inheritance [39]: `typing.Any` is not a valid parent class. +classes_override.py:102:5 Undefined attribute [16]: Module `typing` has no attribute `override`. +classes_override.py:103:4 Invalid override [40]: `classes_override.ChildB.method1` is decorated with @override, but no method of the same name exists in superclasses of `ChildB`. +""" diff --git a/conformance/results/pyre/directives_assert_type.toml b/conformance/results/pyre/directives_assert_type.toml new file mode 100644 index 000000000..b83af4108 --- /dev/null +++ b/conformance/results/pyre/directives_assert_type.toml @@ -0,0 +1,8 @@ +conformant = "Unsupported" +notes = """ +Does not understand "assert_type". +""" +output = """ +directives_assert_type.py:31:4 Missing argument [20]: Call `assert_type` expects argument in position 0. +directives_assert_type.py:33:4 Too many arguments [19]: Call `assert_type` expects 2 positional arguments, 3 were provided. +""" diff --git a/conformance/results/pyre/directives_cast.toml b/conformance/results/pyre/directives_cast.toml new file mode 100644 index 000000000..cbb4ea201 --- /dev/null +++ b/conformance/results/pyre/directives_cast.toml @@ -0,0 +1,6 @@ +conformant = "Pass" +output = """ +directives_cast.py:15:7 Missing argument [20]: Call `cast` expects argument `typ`. +directives_cast.py:16:12 Invalid type [31]: Expression `1` is not a valid type. +directives_cast.py:17:7 Too many arguments [19]: Call `cast` expects 2 positional arguments, 3 were provided. +""" diff --git a/conformance/results/pyre/directives_no_type_check.toml b/conformance/results/pyre/directives_no_type_check.toml new file mode 100644 index 000000000..639a79cb5 --- /dev/null +++ b/conformance/results/pyre/directives_no_type_check.toml @@ -0,0 +1,11 @@ +conformant = "Unsupported" +notes = """ +Does not honor @no_type_check decorator. +""" +output = """ +directives_no_type_check.py:16:12 Unsupported operand [58]: `+` is not supported for operand types `int` and `str`. +directives_no_type_check.py:17:4 Incompatible return type [7]: Expected `None` but got `int`. +directives_no_type_check.py:20:6 Incompatible parameter type [6]: In call `func1`, for 1st positional argument, expected `int` but got `bytes`. +directives_no_type_check.py:20:18 Incompatible parameter type [6]: In call `func1`, for 2nd positional argument, expected `str` but got `bytes`. +directives_no_type_check.py:21:0 Missing argument [20]: Call `func1` expects argument `a`. +""" diff --git a/conformance/results/pyre/directives_reveal_type.toml b/conformance/results/pyre/directives_reveal_type.toml new file mode 100644 index 000000000..fe33115be --- /dev/null +++ b/conformance/results/pyre/directives_reveal_type.toml @@ -0,0 +1,8 @@ +conformant = "Unsupported" +notes = """ +Does not understand reveal_type call. +""" +output = """ +directives_reveal_type.py:19:4 Missing argument [20]: Call `reveal_type` expects argument in position 0. +directives_reveal_type.py:20:4 Too many arguments [19]: Call `reveal_type` expects 1 positional argument, 2 were provided. +""" diff --git a/conformance/results/pyre/directives_type_checking.toml b/conformance/results/pyre/directives_type_checking.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyre/directives_type_checking.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyre/directives_type_ignore.toml b/conformance/results/pyre/directives_type_ignore.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyre/directives_type_ignore.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyre/directives_type_ignore_file1.toml b/conformance/results/pyre/directives_type_ignore_file1.toml new file mode 100644 index 000000000..2dc002af4 --- /dev/null +++ b/conformance/results/pyre/directives_type_ignore_file1.toml @@ -0,0 +1,7 @@ +conformant = "Unsupported" +notes = """ +Does not support file-level `#type: ignore` comment. +""" +output = """ +directives_type_ignore_file1.py:16:0 Incompatible variable type [9]: x is declared to have type `int` but is used as type `str`. +""" diff --git a/conformance/results/pyre/directives_type_ignore_file2.toml b/conformance/results/pyre/directives_type_ignore_file2.toml new file mode 100644 index 000000000..dba9f5dd6 --- /dev/null +++ b/conformance/results/pyre/directives_type_ignore_file2.toml @@ -0,0 +1,4 @@ +conformant = "Pass" +output = """ +directives_type_ignore_file2.py:14:0 Incompatible variable type [9]: x is declared to have type `int` but is used as type `str`. +""" diff --git a/conformance/results/pyre/directives_version_platform.toml b/conformance/results/pyre/directives_version_platform.toml new file mode 100644 index 000000000..f8f4a8d02 --- /dev/null +++ b/conformance/results/pyre/directives_version_platform.toml @@ -0,0 +1,11 @@ +conformant = "Partial" +notes = """ +Does not support sys.platform checks. +Does not support os.name checks. +""" +output = """ +directives_version_platform.py:31:4 Incompatible variable type [9]: val5 is declared to have type `int` but is used as type `str`. +directives_version_platform.py:36:4 Incompatible variable type [9]: val6 is declared to have type `int` but is used as type `str`. +directives_version_platform.py:40:4 Incompatible variable type [9]: val7 is declared to have type `int` but is used as type `str`. +directives_version_platform.py:45:4 Incompatible variable type [9]: val8 is declared to have type `int` but is used as type `str`. +""" diff --git a/conformance/results/pyre/overloads_basic.toml b/conformance/results/pyre/overloads_basic.toml new file mode 100644 index 000000000..0dd93e644 --- /dev/null +++ b/conformance/results/pyre/overloads_basic.toml @@ -0,0 +1,8 @@ +conformant = "Partial" +notes = """ +Does not reject a function with a single @overload signature. +""" +output = """ +overloads_basic.py:37:2 Incompatible parameter type [6]: In call `Bytes.__getitem__`, for 1st positional argument, expected `int` but got `str`. +overloads_basic.py:75:0 Missing overload implementation [42]: Overloaded function `func2` must have an implementation. +""" diff --git a/conformance/results/pyre/protocols_class_objects.toml b/conformance/results/pyre/protocols_class_objects.toml new file mode 100644 index 000000000..f4831c929 --- /dev/null +++ b/conformance/results/pyre/protocols_class_objects.toml @@ -0,0 +1,12 @@ +conformant = "Partial" +notes = """ +Does not reject protocol class assigned to type[Proto]. +Incorrectly reports some class objects as incompatible with a protocol. +Fails to report some class objects as incompatible with a protocol. +""" +output = """ +protocols_class_objects.py:26:11 Invalid class instantiation [45]: Cannot instantiate abstract class `Proto` with abstract method `meth`. +protocols_class_objects.py:58:0 Incompatible variable type [9]: pa1 is declared to have type `ProtoA1` but is used as type `Type[ConcreteA]`. +protocols_class_objects.py:59:0 Incompatible variable type [9]: pa2 is declared to have type `ProtoA2` but is used as type `Type[ConcreteA]`. +protocols_class_objects.py:93:0 Uninitialized attribute [13]: Attribute `attr1` is declared in class `CMeta` to have type `int` but is never initialized. +""" diff --git a/conformance/results/pyre/protocols_definition.toml b/conformance/results/pyre/protocols_definition.toml new file mode 100644 index 000000000..3f95938f1 --- /dev/null +++ b/conformance/results/pyre/protocols_definition.toml @@ -0,0 +1,24 @@ +conformant = "Partial" +notes = """ +Does not reject ClassVar in concrete class when attribute in protocol is not ClassVar. +Does not reject read-only property in concrete class when attribute in protocol is mutable. +Does not reject covariant attribute type when protocol attribute is mutable. +Does not reject read-only property in concrete class when protocol has settable property. +Does not reject immutable named tuple attribute in concrete class when protocol attribute is mutable. +Does not reject immutable frozen dataclass attribute in concrete class when protocol attribute is mutable. +""" +output = """ +protocols_definition.py:30:10 Incompatible parameter type [6]: In call `close_all`, for 1st positional argument, expected `Iterable[SupportsClose]` but got `Iterable[int]`. +protocols_definition.py:67:8 Undefined attribute [16]: `Template` has no attribute `temp`. +protocols_definition.py:115:0 Incompatible variable type [9]: v2_bad1 is declared to have type `Template2` but is used as type `Concrete2_Bad1`. +protocols_definition.py:116:0 Incompatible variable type [9]: v2_bad2 is declared to have type `Template2` but is used as type `Concrete2_Bad2`. +protocols_definition.py:156:0 Incompatible variable type [9]: v3_bad1 is declared to have type `Template3` but is used as type `Concrete3_Bad1`. +protocols_definition.py:159:0 Incompatible variable type [9]: v3_bad4 is declared to have type `Template3` but is used as type `Concrete3_Bad4`. +protocols_definition.py:218:0 Incompatible variable type [9]: v4_bad1 is declared to have type `Template4` but is used as type `Concrete4_Bad1`. +protocols_definition.py:219:0 Incompatible variable type [9]: v4_bad2 is declared to have type `Template4` but is used as type `Concrete4_Bad2`. +protocols_definition.py:285:0 Incompatible variable type [9]: v5_bad1 is declared to have type `Template5` but is used as type `Concrete5_Bad1`. +protocols_definition.py:286:0 Incompatible variable type [9]: v5_bad2 is declared to have type `Template5` but is used as type `Concrete5_Bad2`. +protocols_definition.py:287:0 Incompatible variable type [9]: v5_bad3 is declared to have type `Template5` but is used as type `Concrete5_Bad3`. +protocols_definition.py:288:0 Incompatible variable type [9]: v5_bad4 is declared to have type `Template5` but is used as type `Concrete5_Bad4`. +protocols_definition.py:289:0 Incompatible variable type [9]: v5_bad5 is declared to have type `Template5` but is used as type `Concrete5_Bad5`. +""" diff --git a/conformance/results/pyre/protocols_explicit.toml b/conformance/results/pyre/protocols_explicit.toml new file mode 100644 index 000000000..ac27bcaad --- /dev/null +++ b/conformance/results/pyre/protocols_explicit.toml @@ -0,0 +1,15 @@ +conformant = "Partial" +notes = """ +Does not report error when calling unimplemented protocol method from derived class. +Does not report error when method is not implemented in derived class. +""" +output = """ +protocols_explicit.py:54:0 Uninitialized attribute [13]: Attribute `other` inherited from protocol `RGB` in class `Point` to have type `int` but is never initialized. +protocols_explicit.py:63:4 Invalid class instantiation [45]: Cannot instantiate abstract class `Point` with abstract method `intensity`. +protocols_explicit.py:86:0 Uninitialized attribute [13]: Attribute `cm1` inherited from protocol `Proto1` in class `Concrete1` to have type `int` but is never initialized. +protocols_explicit.py:86:0 Uninitialized attribute [13]: Attribute `im1` inherited from protocol `Proto1` in class `Concrete1` to have type `int` but is never initialized. +protocols_explicit.py:103:0 Uninitialized attribute [13]: Attribute `cm10` inherited from protocol `Proto2` in class `Concrete3` to have type `int` but is never initialized. +protocols_explicit.py:103:0 Uninitialized attribute [13]: Attribute `cm11` inherited from protocol `Proto3` in class `Concrete3` to have type `int` but is never initialized. +protocols_explicit.py:103:0 Uninitialized attribute [13]: Attribute `im1` inherited from protocol `Proto1` in class `Concrete3` to have type `int` but is never initialized. +protocols_explicit.py:171:6 Invalid class instantiation [45]: Cannot instantiate abstract class `Concrete7A` with abstract method `method1`. +""" diff --git a/conformance/results/pyre/protocols_generic.toml b/conformance/results/pyre/protocols_generic.toml new file mode 100644 index 000000000..45e702d11 --- /dev/null +++ b/conformance/results/pyre/protocols_generic.toml @@ -0,0 +1,13 @@ +conformant = "Partial" +notes = """ +Does not reject the use of Protocol and Generic together as base classes. +Does not detect protocol mismatch when method-scoped TypeVar is used in protocol. +""" +output = """ +protocols_generic.py:40:0 Incompatible variable type [9]: p2 is declared to have type `Proto1[int, str]` but is used as type `Concrete1`. +protocols_generic.py:56:4 Incompatible variable type [9]: v2 is declared to have type `Box[int]` but is used as type `Box[float]`. +protocols_generic.py:66:4 Incompatible variable type [9]: v2 is declared to have type `Sender[float]` but is used as type `Sender[int]`. +protocols_generic.py:74:4 Incompatible variable type [9]: v1 is declared to have type `AttrProto[float]` but is used as type `AttrProto[int]`. +protocols_generic.py:75:4 Incompatible variable type [9]: v2 is declared to have type `AttrProto[int]` but is used as type `AttrProto[float]`. +protocols_generic.py:146:0 Incompatible variable type [9]: hp3 is declared to have type `HasPropertyProto` but is used as type `ConcreteHasProperty3`. +""" diff --git a/conformance/results/pyre/protocols_merging.toml b/conformance/results/pyre/protocols_merging.toml new file mode 100644 index 000000000..ee1a86c18 --- /dev/null +++ b/conformance/results/pyre/protocols_merging.toml @@ -0,0 +1,11 @@ +conformant = "Partial" +notes = """ +Does not reject a protocol class that derives from a non-protocol class. +""" +output = """ +protocols_merging.py:52:0 Incompatible variable type [9]: s6 is declared to have type `SizedAndClosable1` but is used as type `SCConcrete2`. +protocols_merging.py:53:0 Incompatible variable type [9]: s7 is declared to have type `SizedAndClosable2` but is used as type `SCConcrete2`. +protocols_merging.py:54:0 Incompatible variable type [9]: s8 is declared to have type `SizedAndClosable3` but is used as type `SCConcrete2`. +protocols_merging.py:83:4 Invalid class instantiation [45]: Cannot instantiate abstract class `SizedAndClosable4` with abstract method `close`. +protocols_merging.py:84:0 Incompatible variable type [9]: y is declared to have type `SizedAndClosable4` but is used as type `SCConcrete1`. +""" diff --git a/conformance/results/pyre/protocols_modules.toml b/conformance/results/pyre/protocols_modules.toml new file mode 100644 index 000000000..8e5b07869 --- /dev/null +++ b/conformance/results/pyre/protocols_modules.toml @@ -0,0 +1,6 @@ +conformant = "Unsupported" +notes = """ +Does not perform protocol checks for modules. +""" +output = """ +""" diff --git a/conformance/results/pyre/protocols_recursive.toml b/conformance/results/pyre/protocols_recursive.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyre/protocols_recursive.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyre/protocols_runtime_checkable.toml b/conformance/results/pyre/protocols_runtime_checkable.toml new file mode 100644 index 000000000..80197221f --- /dev/null +++ b/conformance/results/pyre/protocols_runtime_checkable.toml @@ -0,0 +1,8 @@ +conformant = "Unsupported" +notes = """ +Does not reject isinstance or issubclass call for protocol that is not runtime_checkable. +Does not reject issubclass call for data protocol. +Does not report unsafe overlap for runtime_checkable protocol. +""" +output = """ +""" diff --git a/conformance/results/pyre/protocols_self.toml b/conformance/results/pyre/protocols_self.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyre/protocols_self.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyre/protocols_subtyping.toml b/conformance/results/pyre/protocols_subtyping.toml new file mode 100644 index 000000000..3fe6fc12c --- /dev/null +++ b/conformance/results/pyre/protocols_subtyping.toml @@ -0,0 +1,10 @@ +conformant = "Pass" +output = """ +protocols_subtyping.py:16:5 Invalid class instantiation [45]: Cannot instantiate protocol `Proto1`. +protocols_subtyping.py:38:4 Incompatible variable type [9]: v2 is declared to have type `Concrete2` but is used as type `Proto2`. +protocols_subtyping.py:55:4 Incompatible variable type [9]: v2 is declared to have type `Proto3` but is used as type `Proto2`. +protocols_subtyping.py:79:4 Incompatible variable type [9]: v3 is declared to have type `Proto4[int, float]` but is used as type `Proto5[int]`. +protocols_subtyping.py:80:4 Incompatible variable type [9]: v4 is declared to have type `Proto5[float]` but is used as type `Proto4[int, int]`. +protocols_subtyping.py:102:4 Incompatible variable type [9]: v4 is declared to have type `Proto7[int, float]` but is used as type `Proto6[float, float]`. +protocols_subtyping.py:103:4 Incompatible variable type [9]: v5 is declared to have type `Proto7[float, object]` but is used as type `Proto6[float, float]`. +""" diff --git a/conformance/results/pyre/protocols_variance.toml b/conformance/results/pyre/protocols_variance.toml new file mode 100644 index 000000000..26d50b961 --- /dev/null +++ b/conformance/results/pyre/protocols_variance.toml @@ -0,0 +1,8 @@ +conformant = "Unsupported" +notes = """ +Does not detect incorrect TypeVar variance within generic protocols. +""" +output = """ +protocols_variance.py:62:17 Invalid type variance [46]: The type variable `Variable[T1_co](covariant)` is covariant and cannot be a parameter type. +protocols_variance.py:72:4 Invalid type variance [46]: The type variable `Variable[T1_contra](contravariant)` is contravariant and cannot be a return type. +""" diff --git a/conformance/results/pyre/qualifiers_annotated.toml b/conformance/results/pyre/qualifiers_annotated.toml new file mode 100644 index 000000000..e0d4e1149 --- /dev/null +++ b/conformance/results/pyre/qualifiers_annotated.toml @@ -0,0 +1,18 @@ +conformant = "Partial" +notes = """ +Does not reject Annotated with a single parameter. +""" +output = """ +qualifiers_annotated.py:41:6 Undefined or invalid type [11]: Annotation `` is not defined as a type. +qualifiers_annotated.py:42:6 Invalid type [31]: Expression `typing.Annotated[(((int, str)), "")]` is not a valid type. +qualifiers_annotated.py:43:6 Invalid type [31]: Expression `typing.Annotated[(comprehension(int for generators(generator($target$i in range(1) if ))), "")]` is not a valid type. +qualifiers_annotated.py:44:6 Invalid type [31]: Expression `typing.Annotated[({ "a":"b" }, "")]` is not a valid type. +qualifiers_annotated.py:45:6 Invalid type [31]: Expression `typing.Annotated[(lambda () (int)(), "")]` is not a valid type. +qualifiers_annotated.py:46:6 Invalid type [31]: Expression `typing.Annotated[([int][0], "")]` is not a valid type. +qualifiers_annotated.py:47:6 Invalid type [31]: Expression `typing.Annotated[(int if 1 < 3 else str, "")]` is not a valid type. +qualifiers_annotated.py:48:16 Unbound name [10]: Name `var1` is used but not defined in the current scope. +qualifiers_annotated.py:49:6 Invalid type [31]: Expression `typing.Annotated[(True, "")]` is not a valid type. +qualifiers_annotated.py:50:7 Invalid type [31]: Expression `typing.Annotated[(1, "")]` is not a valid type. +qualifiers_annotated.py:51:7 Invalid type [31]: Expression `typing.Annotated[(list or set, "")]` is not a valid type. +qualifiers_annotated.py:52:7 Invalid type [31]: Expression `typing.Annotated[(f"{"int"}", "")]` is not a valid type. +""" diff --git a/conformance/results/pyre/qualifiers_final_annotation.toml b/conformance/results/pyre/qualifiers_final_annotation.toml new file mode 100644 index 000000000..79bd08fdb --- /dev/null +++ b/conformance/results/pyre/qualifiers_final_annotation.toml @@ -0,0 +1,34 @@ +conformant = "Partial" +notes = """ +Does not report Final variable with missing initialization in module scope. +Does not report error for invalid nesting of Final and ClassVar. +Does not treat use of Final name as if it was replaced by the literal in NamedTuple definition. +""" +output = """ +qualifiers_final_annotation.py:18:6 Invalid type parameters [24]: Generic type `Final` expects 1 type parameter, received 2. +qualifiers_final_annotation.py:28:0 Uninitialized attribute [13]: Attribute `ID3` is declared in class `ClassA` to have type `int` but is never initialized. +qualifiers_final_annotation.py:34:4 Invalid assignment [41]: Cannot reassign final attribute `ClassA.ID2`. +qualifiers_final_annotation.py:54:8 Invalid assignment [41]: Cannot reassign final attribute `self.ID5`. +qualifiers_final_annotation.py:62:8 Undefined attribute [16]: `ClassA` has no attribute `id3`. +qualifiers_final_annotation.py:63:8 Undefined attribute [16]: `ClassA` has no attribute `id4`. +qualifiers_final_annotation.py:65:8 Invalid assignment [41]: Cannot reassign final attribute `self.ID7`. +qualifiers_final_annotation.py:67:8 Invalid assignment [41]: Cannot reassign final attribute `self.ID7`. +qualifiers_final_annotation.py:71:0 Invalid assignment [41]: Cannot reassign final attribute `RATE`. +qualifiers_final_annotation.py:81:0 Invalid assignment [41]: Cannot reassign final attribute `ClassB.DEFAULT_ID`. +qualifiers_final_annotation.py:94:4 Invalid assignment [41]: Cannot reassign final attribute `BORDER_WIDTH`. +qualifiers_final_annotation.py:107:4 Incompatible attribute type [8]: Attribute `VALUE2` declared in class `ClassD` has type `Final` but is used as type `int`. +qualifiers_final_annotation.py:107:4 Invalid type [31]: Expression `Final` is not a valid type. Final cannot be nested. +qualifiers_final_annotation.py:118:0 Invalid type [31]: Expression `typing.List[Final[int]]` is not a valid type. Final cannot be nested. +qualifiers_final_annotation.py:121:10 Invalid type [31]: Parameter `x` cannot be annotated with Final. +qualifiers_final_annotation.py:131:0 Uninitialized attribute [13]: Attribute `($local_qualifiers_final_annotation$X, int)` is declared in class `N` to have type `typing.Any` but is never initialized. +qualifiers_final_annotation.py:131:0 Uninitialized attribute [13]: Attribute `($local_qualifiers_final_annotation$Y, int)` is declared in class `N` to have type `typing.Any` but is never initialized. +qualifiers_final_annotation.py:133:0 Unexpected keyword [28]: Unexpected keyword argument `x` to call `N.__init__`. +qualifiers_final_annotation.py:134:0 Unexpected keyword [28]: Unexpected keyword argument `a` to call `N.__init__`. +qualifiers_final_annotation.py:135:0 Unexpected keyword [28]: Unexpected keyword argument `x` to call `N.__init__`. +qualifiers_final_annotation.py:141:4 Invalid assignment [41]: Cannot reassign final attribute `ID1`. +qualifiers_final_annotation.py:145:4 Invalid assignment [41]: Cannot reassign final attribute `x`. +qualifiers_final_annotation.py:147:9 Invalid assignment [41]: Cannot reassign final attribute `x`. +qualifiers_final_annotation.py:149:8 Invalid assignment [41]: Cannot reassign final attribute `x`. +qualifiers_final_annotation.py:152:29 Invalid assignment [41]: Cannot reassign final attribute `x`. +qualifiers_final_annotation.py:155:8 Invalid assignment [41]: Cannot reassign final attribute `x`. +""" diff --git a/conformance/results/pyre/qualifiers_final_decorator.toml b/conformance/results/pyre/qualifiers_final_decorator.toml new file mode 100644 index 000000000..a9722413d --- /dev/null +++ b/conformance/results/pyre/qualifiers_final_decorator.toml @@ -0,0 +1,18 @@ +conformant = "Partial" +notes = """ +Reports error for overloaded method implementation marked @final if its overloads do not. +Does not report error for overloaded @final method defined in stub file. +Reports misleading error when overload is marked @final but implementation is not. +""" +output = """ +qualifiers_final_decorator.py:21:0 Invalid inheritance [39]: Cannot inherit from final class `Base1`. +qualifiers_final_decorator.py:51:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). +qualifiers_final_decorator.py:56:4 Invalid override [40]: `qualifiers_final_decorator.Derived2.method1` cannot override final method defined in `Base2`. +qualifiers_final_decorator.py:60:4 Invalid override [40]: `qualifiers_final_decorator.Derived2.method2` cannot override final method defined in `Base2`. +qualifiers_final_decorator.py:64:4 Invalid override [40]: `qualifiers_final_decorator.Derived2.method3` cannot override final method defined in `Base2`. +qualifiers_final_decorator.py:75:4 Invalid override [40]: `qualifiers_final_decorator.Derived2.method4` cannot override final method defined in `Base2`. +qualifiers_final_decorator.py:86:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). +qualifiers_final_decorator.py:118:4 Inconsistent override [14]: `qualifiers_final_decorator.Derived5.method` overrides method defined in `Base5_2` inconsistently. Could not find parameter `v` in overriding signature. +qualifiers_final_decorator.py:118:4 Invalid override [40]: `qualifiers_final_decorator.Derived5.method` cannot override final method defined in `Base5_2`. +qualifiers_final_decorator.py:126:0 Invalid inheritance [39]: `final` cannot be used with non-method functions. +""" diff --git a/conformance/results/pyre/specialtypes_any.toml b/conformance/results/pyre/specialtypes_any.toml new file mode 100644 index 000000000..1af969071 --- /dev/null +++ b/conformance/results/pyre/specialtypes_any.toml @@ -0,0 +1,14 @@ +conformant = "Partial" +notes = """ +Does not treat missing type argument as Any in generic type. +Does not support Any as a base class. +""" +output = """ +specialtypes_any.py:48:27 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T]]` but got `object`. +specialtypes_any.py:49:27 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[Type[Variable[_KT]], Type[Variable[_VT]]]` but got `Tuple[object, object]`. +specialtypes_any.py:62:28 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[object, typing.Any]`. +specialtypes_any.py:72:31 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Tuple[typing.Any, Type[Variable[$synthetic_attribute_resolution_variable]]]` but got `Tuple[typing.Any, object]`. +specialtypes_any.py:81:13 Invalid inheritance [39]: `typing.Any` is not a valid parent class. +specialtypes_any.py:87:12 Undefined attribute [16]: `ClassA` has no attribute `method2`. +specialtypes_any.py:88:12 Undefined attribute [16]: `ClassA` has no attribute `method3`. +""" diff --git a/conformance/results/pyre/specialtypes_never.toml b/conformance/results/pyre/specialtypes_never.toml new file mode 100644 index 000000000..fe5fe7b24 --- /dev/null +++ b/conformance/results/pyre/specialtypes_never.toml @@ -0,0 +1,17 @@ +conformant = "Partial" +notes = """ +Does not reject NoReturn when used outside of return type annotation. +Does not treat Never as compatible with all other types. +""" +output = """ +specialtypes_never.py:21:8 Incompatible return type [7]: Function declared non-returnable, but got `None`. +specialtypes_never.py:58:0 Uninitialized attribute [13]: Attribute `x` is declared in class `ClassA` to have type `NoReturn` but is never initialized. +specialtypes_never.py:58:0 Uninitialized attribute [13]: Attribute `y` is declared in class `ClassA` to have type `typing.List[NoReturn]` but is never initialized. +specialtypes_never.py:67:4 Incompatible variable type [9]: v1 is declared to have type `int` but is used as type `Never`. +specialtypes_never.py:68:4 Incompatible variable type [9]: v2 is declared to have type `str` but is used as type `Never`. +specialtypes_never.py:69:4 Incompatible variable type [9]: v3 is declared to have type `List[str]` but is used as type `Never`. +specialtypes_never.py:85:4 Incompatible variable type [9]: v3 is declared to have type `List[int]` but is used as type `List[Never]`. +specialtypes_never.py:86:4 Incompatible variable type [9]: v4 is declared to have type `Never` but is used as type `NoReturn`. +specialtypes_never.py:95:4 Incompatible return type [7]: Expected `ClassB[Variable[U]]` but got `ClassB[Never]`. +specialtypes_never.py:104:4 Incompatible return type [7]: Expected `ClassC[Variable[U]]` but got `ClassC[Never]`. +""" diff --git a/conformance/results/pyre/specialtypes_none.toml b/conformance/results/pyre/specialtypes_none.toml new file mode 100644 index 000000000..d96e591c0 --- /dev/null +++ b/conformance/results/pyre/specialtypes_none.toml @@ -0,0 +1,10 @@ +conformant = "Partial" +notes = """ +Does not correctly handle type annotation type[None]. +""" +output = """ +specialtypes_none.py:21:6 Incompatible parameter type [6]: In call `func1`, for 1st positional argument, expected `None` but got `Type[None]`. +specialtypes_none.py:27:0 Incompatible variable type [9]: none2 is declared to have type `Iterable[typing.Any]` but is used as type `None`. +specialtypes_none.py:36:27 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_meta](covariant)]` but got `None`. +specialtypes_none.py:41:6 Incompatible parameter type [6]: In call `func2`, for 1st positional argument, expected `Type[None]` but got `None`. +""" diff --git a/conformance/results/pyre/specialtypes_promotions.toml b/conformance/results/pyre/specialtypes_promotions.toml new file mode 100644 index 000000000..18ce19078 --- /dev/null +++ b/conformance/results/pyre/specialtypes_promotions.toml @@ -0,0 +1,6 @@ +conformant = "Partial" +notes = """ +Does not reject use of attribute that is compatible only with float. +""" +output = """ +""" diff --git a/conformance/results/pyre/specialtypes_tuple.toml b/conformance/results/pyre/specialtypes_tuple.toml new file mode 100644 index 000000000..646b9965f --- /dev/null +++ b/conformance/results/pyre/specialtypes_tuple.toml @@ -0,0 +1,11 @@ +conformant = "Partial" +notes = """ +Does not report type violation when assigning tuple[T, ...] to tuple[T]. +""" +output = """ +specialtypes_tuple.py:12:0 Incompatible variable type [9]: t1 is declared to have type `Tuple[int]` but is used as type `Tuple[int, int]`. +specialtypes_tuple.py:14:0 Incompatible variable type [9]: t2 is declared to have type `Tuple[int, int]` but is used as type `Tuple[int]`. +specialtypes_tuple.py:15:0 Incompatible variable type [9]: t2 is declared to have type `Tuple[int, int]` but is used as type `Tuple[int, str]`. +specialtypes_tuple.py:25:0 Incompatible variable type [9]: t10 is declared to have type `Tuple[]` but is used as type `Tuple[int]`. +specialtypes_tuple.py:36:0 Incompatible variable type [9]: t20 is declared to have type `typing.Tuple[int, ...]` but is used as type `Tuple[int, int, int, str]`. +""" diff --git a/conformance/results/pyre/specialtypes_tuple_unpack.toml b/conformance/results/pyre/specialtypes_tuple_unpack.toml new file mode 100644 index 000000000..77ce9bff2 --- /dev/null +++ b/conformance/results/pyre/specialtypes_tuple_unpack.toml @@ -0,0 +1,43 @@ +conformant = "Unsupported" +notes = """ +Does not support unpacked tuple in type expression. +""" +output = """ +specialtypes_tuple_unpack.py:6:4 Invalid type [31]: Expression `tuple[(int, *tuple[str])]` is not a valid type. +specialtypes_tuple_unpack.py:7:0 Incompatible variable type [9]: t1 is declared to have type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal['']]` but is used as type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[''], typing_extensions.Literal['']]`. +specialtypes_tuple_unpack.py:9:0 Incompatible variable type [9]: t2 is declared to have type `Tuple[int, unknown]` but is used as type `Tuple[int]`. +specialtypes_tuple_unpack.py:9:4 Invalid type [31]: Expression `tuple[(int, *tuple[(str, ...)])]` is not a valid type. +specialtypes_tuple_unpack.py:10:0 Incompatible variable type [9]: t2 is declared to have type `Tuple[typing_extensions.Literal[1]]` but is used as type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal['']]`. +specialtypes_tuple_unpack.py:11:0 Incompatible variable type [9]: t2 is declared to have type `Tuple[typing_extensions.Literal[1]]` but is used as type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[''], typing_extensions.Literal['']]`. +specialtypes_tuple_unpack.py:12:0 Incompatible variable type [9]: t2 is declared to have type `Tuple[typing_extensions.Literal[1]]` but is used as type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[1], typing_extensions.Literal['']]`. +specialtypes_tuple_unpack.py:13:0 Incompatible variable type [9]: t2 is declared to have type `Tuple[typing_extensions.Literal[1]]` but is used as type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[''], typing_extensions.Literal[1]]`. +specialtypes_tuple_unpack.py:16:0 Incompatible variable type [9]: t3 is declared to have type `Tuple[int, unknown, int]` but is used as type `Tuple[int, int]`. +specialtypes_tuple_unpack.py:16:4 Invalid type [31]: Expression `tuple[(int, *tuple[(str, ...)], int)]` is not a valid type. +specialtypes_tuple_unpack.py:17:0 Incompatible variable type [9]: t3 is declared to have type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[2]]` but is used as type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[''], typing_extensions.Literal[2]]`. +specialtypes_tuple_unpack.py:18:0 Incompatible variable type [9]: t3 is declared to have type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[2]]` but is used as type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[''], typing_extensions.Literal[''], typing_extensions.Literal[2]]`. +specialtypes_tuple_unpack.py:19:0 Incompatible variable type [9]: t3 is declared to have type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[2]]` but is used as type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[''], typing_extensions.Literal['']]`. +specialtypes_tuple_unpack.py:20:0 Incompatible variable type [9]: t3 is declared to have type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[2]]` but is used as type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[''], typing_extensions.Literal[''], float]`. +specialtypes_tuple_unpack.py:22:0 Incompatible variable type [9]: t4 is declared to have type `Tuple[unknown, int]` but is used as type `Tuple[int]`. +specialtypes_tuple_unpack.py:22:4 Invalid type [31]: Expression `tuple[(*tuple[(str, ...)], int)]` is not a valid type. +specialtypes_tuple_unpack.py:23:0 Incompatible variable type [9]: t4 is declared to have type `Tuple[typing_extensions.Literal[1]]` but is used as type `Tuple[typing_extensions.Literal[''], typing_extensions.Literal[1]]`. +specialtypes_tuple_unpack.py:24:0 Incompatible variable type [9]: t4 is declared to have type `Tuple[typing_extensions.Literal[1]]` but is used as type `Tuple[typing_extensions.Literal[''], typing_extensions.Literal[''], typing_extensions.Literal[1]]`. +specialtypes_tuple_unpack.py:25:0 Incompatible variable type [9]: t4 is declared to have type `Tuple[typing_extensions.Literal[1]]` but is used as type `Tuple[typing_extensions.Literal[1], typing_extensions.Literal[''], typing_extensions.Literal[1]]`. +specialtypes_tuple_unpack.py:26:0 Incompatible variable type [9]: t4 is declared to have type `Tuple[typing_extensions.Literal[1]]` but is used as type `Tuple[typing_extensions.Literal[''], typing_extensions.Literal[''], float]`. +specialtypes_tuple_unpack.py:28:4 Invalid type [31]: Expression `tuple[(*tuple[str], *tuple[int])]` is not a valid type. +specialtypes_tuple_unpack.py:29:4 Invalid type [31]: Expression `tuple[(*tuple[(str, ...)], *tuple[(int, ...)])]` is not a valid type. +specialtypes_tuple_unpack.py:33:4 Incompatible variable type [9]: t1 is declared to have type `Tuple[str, str, unknown]` but is used as type `Tuple[str, str]`. +specialtypes_tuple_unpack.py:33:8 Invalid type [31]: Expression `tuple[(str, str, *tuple[(int, ...)])]` is not a valid type. +specialtypes_tuple_unpack.py:34:4 Incompatible variable type [9]: t2 is declared to have type `Tuple[str, str, unknown]` but is used as type `Tuple[str, str]`. +specialtypes_tuple_unpack.py:34:8 Invalid type [31]: Expression `tuple[(str, str, *tuple[int])]` is not a valid type. +specialtypes_tuple_unpack.py:35:8 Invalid type [31]: Expression `tuple[(str, *tuple[(str, ...)])]` is not a valid type. +specialtypes_tuple_unpack.py:36:4 Incompatible variable type [9]: t4 is declared to have type `Tuple[str, str, unknown]` but is used as type `Tuple[str, str]`. +specialtypes_tuple_unpack.py:36:8 Invalid type [31]: Expression `tuple[(str, str, *tuple[(str, ...)])]` is not a valid type. +specialtypes_tuple_unpack.py:37:4 Incompatible variable type [9]: t5 is declared to have type `Tuple[str, str, str, unknown]` but is used as type `Tuple[str, str]`. +specialtypes_tuple_unpack.py:37:8 Invalid type [31]: Expression `tuple[(str, str, str, *tuple[(str, ...)])]` is not a valid type. +specialtypes_tuple_unpack.py:38:4 Incompatible variable type [9]: t6 is declared to have type `Tuple[str, unknown, str]` but is used as type `Tuple[str, str]`. +specialtypes_tuple_unpack.py:38:8 Invalid type [31]: Expression `tuple[(str, *tuple[(int, ...)], str)]` is not a valid type. +specialtypes_tuple_unpack.py:39:8 Invalid type [31]: Expression `tuple[(*tuple[(str, ...)], str)]` is not a valid type. +specialtypes_tuple_unpack.py:40:8 Invalid type [31]: Expression `tuple[(*tuple[(str, ...)], str)]` is not a valid type. +specialtypes_tuple_unpack.py:41:4 Incompatible variable type [9]: t9 is declared to have type `Tuple[unknown, str, str, str]` but is used as type `Tuple[str, str]`. +specialtypes_tuple_unpack.py:41:8 Invalid type [31]: Expression `tuple[(*tuple[(str, ...)], str, str, str)]` is not a valid type. +""" diff --git a/conformance/results/pyre/specialtypes_type.toml b/conformance/results/pyre/specialtypes_type.toml new file mode 100644 index 000000000..c457538bc --- /dev/null +++ b/conformance/results/pyre/specialtypes_type.toml @@ -0,0 +1,25 @@ +conformant = "Partial" +notes = """ +Does not reject Callable when passed to type[T]. +Does not treat `type` same as `type[Any]` for assert_type. +Does not allow access to unknown attributes from object of type `type[Any]`. +Does not reject access to unknown attributes from object of type `Type[object]`. +Reports type incompatibility between `type` and `Callable[..., Any]`. +""" +output = """ +specialtypes_type.py:56:6 Incompatible parameter type [6]: In call `func4`, for 1st positional argument, expected `Type[Union[BasicUser, ProUser]]` but got `Type[TeamUser]`. +specialtypes_type.py:76:11 Invalid type parameters [24]: Generic type `type` expects 1 type parameter, received 2, use `typing.Type[]` to avoid runtime subscripting errors. +specialtypes_type.py:84:24 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_meta](covariant)]` but got `object`. +specialtypes_type.py:98:33 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[type], typing.Any]`. +specialtypes_type.py:102:33 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[type], typing.Any]`. +specialtypes_type.py:106:33 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[type], typing.Any]`. +specialtypes_type.py:110:33 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_co](covariant)]` but got `Tuple[Type[type], typing.Any]`. +specialtypes_type.py:117:4 Undefined attribute [16]: `object` has no attribute `unknown`. +specialtypes_type.py:137:24 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_meta](covariant)]` but got `object`. +specialtypes_type.py:138:24 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_meta](covariant)]` but got `object`. +specialtypes_type.py:139:24 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_meta](covariant)]` but got `object`. +specialtypes_type.py:140:24 Incompatible parameter type [6]: In call `typing.GenericMeta.__getitem__`, for 1st positional argument, expected `Type[Variable[_T_meta](covariant)]` but got `object`. +specialtypes_type.py:143:0 Undefined attribute [16]: `TypeAlias` has no attribute `unknown`. +specialtypes_type.py:165:4 Incompatible variable type [9]: x1 is declared to have type `typing.Callable[..., typing.Any]` but is used as type `Type[typing.Any]`. +specialtypes_type.py:166:4 Incompatible variable type [9]: x2 is declared to have type `typing.Callable[[int, int], int]` but is used as type `Type[typing.Any]`. +""" diff --git a/conformance/results/pyre/version.toml b/conformance/results/pyre/version.toml index 91daa0eeb..1045fff1a 100644 --- a/conformance/results/pyre/version.toml +++ b/conformance/results/pyre/version.toml @@ -1,2 +1,2 @@ version = "pyre 0.9.19" -test_duration = 1.5496571063995361 +test_duration = 2.1209452152252197 diff --git a/conformance/results/pyright/aliases_explicit.toml b/conformance/results/pyright/aliases_explicit.toml index a69c20491..a90f7e995 100644 --- a/conformance/results/pyright/aliases_explicit.toml +++ b/conformance/results/pyright/aliases_explicit.toml @@ -1,10 +1,5 @@ -conformant = "Partial" -notes = """ -Incorrectly evaluates type of specialized type alias parameterized with ParamSpec. -Allows some illegal annotation forms to be interpreted as valid type aliases. -""" +conformant = "Pass" output = """ -aliases_explicit.py:57:17 - error: "assert_type" mismatch: expected "(int, str, str) -> None" but received "(int, str, str) -> None" (reportGeneralTypeIssues) aliases_explicit.py:67:24 - error: Expected no type arguments for class "int" (reportGeneralTypeIssues) aliases_explicit.py:67:24 - error: Expected no type arguments for class "NoneType" (reportGeneralTypeIssues) aliases_explicit.py:68:9 - error: Type "list[int | None]" is already specialized (reportGeneralTypeIssues) @@ -20,7 +15,6 @@ aliases_explicit.py:80:21 - error: Expected type expression but received "list[U aliases_explicit.py:81:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_explicit.py:81:21 - error: Tuple expression not allowed in type annotation   Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) -aliases_explicit.py:81:21 - error: Expected type expression but received "tuple[tuple[type[int], type[str]]]" (reportGeneralTypeIssues) aliases_explicit.py:82:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_explicit.py:82:21 - error: List expression not allowed in type annotation   Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) @@ -31,6 +25,7 @@ aliases_explicit.py:83:21 - error: Dictionary expression not allowed in type ann aliases_explicit.py:83:21 - error: Expected type expression but received "dict[str, str]" (reportGeneralTypeIssues) aliases_explicit.py:84:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_explicit.py:84:21 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +aliases_explicit.py:85:21 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_explicit.py:85:21 - error: List expression not allowed in type annotation   Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) aliases_explicit.py:85:21 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) @@ -40,6 +35,7 @@ aliases_explicit.py:87:21 - error: Variable not allowed in type expression (repo aliases_explicit.py:88:22 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) aliases_explicit.py:89:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_explicit.py:89:22 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +aliases_explicit.py:90:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_explicit.py:90:22 - error: Binary operator not allowed in type annotation aliases_explicit.py:91:22 - error: Expected expression aliases_explicit.py:91:22 - error: Tuple expression not allowed in type annotation diff --git a/conformance/results/pyright/aliases_implicit.toml b/conformance/results/pyright/aliases_implicit.toml index be26d5c86..c7a06c929 100644 --- a/conformance/results/pyright/aliases_implicit.toml +++ b/conformance/results/pyright/aliases_implicit.toml @@ -1,10 +1,5 @@ -conformant = "Partial" -notes = """ -Incorrectly evaluates type of specialized type alias parameterized with ParamSpec. -Allows some illegal annotation forms to be interpreted as valid type aliases. -""" +conformant = "Pass" output = """ -aliases_implicit.py:68:17 - error: "assert_type" mismatch: expected "(int, str, str) -> None" but received "(int, str, str) -> None" (reportGeneralTypeIssues) aliases_implicit.py:76:24 - error: Expected no type arguments for class "int" (reportGeneralTypeIssues) aliases_implicit.py:76:24 - error: Expected no type arguments for class "NoneType" (reportGeneralTypeIssues) aliases_implicit.py:77:9 - error: Type "list[int | None]" is already specialized (reportGeneralTypeIssues) @@ -19,10 +14,13 @@ aliases_implicit.py:107:9 - error: Variable not allowed in type expression (repo aliases_implicit.py:108:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_implicit.py:109:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_implicit.py:110:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:111:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:112:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_implicit.py:113:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_implicit.py:114:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_implicit.py:115:10 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_implicit.py:116:10 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +aliases_implicit.py:117:10 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_implicit.py:118:10 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_implicit.py:119:10 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_implicit.py:133:6 - error: Object of type "type[list[Unknown]] | type[set[Unknown]]" is not callable (reportGeneralTypeIssues) diff --git a/conformance/results/pyright/aliases_newtype.toml b/conformance/results/pyright/aliases_newtype.toml index 5024de109..ffffb1adf 100644 --- a/conformance/results/pyright/aliases_newtype.toml +++ b/conformance/results/pyright/aliases_newtype.toml @@ -1,21 +1,20 @@ -conformant = "Partial" -notes = """ -Does not reject use of NewType in `isinstance` call. -Does not report inconsistency between name of NewType and assigned identifier name. -Does not reject use of NewType with TypedDict class. -Does not reject use of NewType with another NewType. -Does not reject use of NewType with Any. -""" +conformant = "Pass" output = """ aliases_newtype.py:11:8 - error: Argument of type "Literal['user']" cannot be assigned to parameter "_x" of type "int" in function "__init__"   "Literal['user']" is incompatible with "int" (reportGeneralTypeIssues) aliases_newtype.py:12:14 - error: Expression of type "Literal[42]" cannot be assigned to declared type "UserId"   "Literal[42]" is incompatible with "UserId" (reportGeneralTypeIssues) +aliases_newtype.py:20:16 - error: Second argument to "isinstance" must be a class or tuple of classes +  Class created with NewType cannot be used with instance and class checks (reportGeneralTypeIssues) aliases_newtype.py:23:21 - error: Base class "UserId" is marked final and cannot be subclassed +aliases_newtype.py:32:1 - error: NewType must be assigned to a variable with the same name (reportGeneralTypeIssues) aliases_newtype.py:36:19 - error: Expected no type arguments for class "GoodNewType1" (reportGeneralTypeIssues) aliases_newtype.py:42:38 - error: Expected class as second argument to NewType aliases_newtype.py:45:43 - error: Type variable "T" has no meaning in this context (reportGeneralTypeIssues) -aliases_newtype.py:47:38 - error: NewType cannot be used with protocol class +aliases_newtype.py:47:38 - error: NewType cannot be used with structural type (a protocol or TypedDict class) aliases_newtype.py:49:38 - error: NewType cannot be used with Literal type +aliases_newtype.py:56:38 - error: NewType cannot be used with structural type (a protocol or TypedDict class) +aliases_newtype.py:58:38 - error: NewType cannot be used with a class created with NewType aliases_newtype.py:60:15 - error: NewType requires two positional arguments (reportGeneralTypeIssues) +aliases_newtype.py:62:38 - error: The second argument to NewType must be a known class, not Any or Unknown (reportGeneralTypeIssues) """ diff --git a/conformance/results/pyright/aliases_type_statement.toml b/conformance/results/pyright/aliases_type_statement.toml index abc0dc775..0c987a446 100644 --- a/conformance/results/pyright/aliases_type_statement.toml +++ b/conformance/results/pyright/aliases_type_statement.toml @@ -1,10 +1,5 @@ -conformant = "Partial" -notes = """ -Does not reject binary expression when used in type alias definition. -""" +conformant = "Pass" output = """ -aliases_type_statement.py:44:26 - error: Statements must be separated by newlines or semicolons -aliases_type_statement.py:44:35 - error: Expected ":" aliases_type_statement.py:17:12 - error: Cannot access member "bit_count" for type "TypeAliasType"   Member "bit_count" is unknown (reportGeneralTypeIssues) aliases_type_statement.py:19:1 - error: Object of type "TypeAliasType" is not callable (reportGeneralTypeIssues) @@ -16,22 +11,30 @@ aliases_type_statement.py:31:22 - error: Argument of type "TypeAliasType" cannot     "TypeAliasType" is incompatible with "type"     "TypeAliasType" is incompatible with "UnionType"     "TypeAliasType" is incompatible with "tuple[_ClassInfo, ...]" (reportGeneralTypeIssues) -aliases_type_statement.py:37:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) -aliases_type_statement.py:38:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:37:22 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +aliases_type_statement.py:38:22 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) aliases_type_statement.py:38:22 - error: Expected type expression but received "list[Unknown]" (reportGeneralTypeIssues) -aliases_type_statement.py:39:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) -aliases_type_statement.py:39:22 - error: Expected type expression but received "tuple[tuple[type[int], type[str]]]" (reportGeneralTypeIssues) -aliases_type_statement.py:40:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:39:22 - error: Tuple expression not allowed in type annotation +  Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +aliases_type_statement.py:40:22 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) aliases_type_statement.py:40:22 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) -aliases_type_statement.py:41:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:41:22 - error: Dictionary expression not allowed in type annotation +  Use Dict[T1, T2] to indicate a dictionary type (reportGeneralTypeIssues) aliases_type_statement.py:41:22 - error: Expected type expression but received "dict[str, str]" (reportGeneralTypeIssues) -aliases_type_statement.py:42:22 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_type_statement.py:42:22 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +aliases_type_statement.py:43:22 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) aliases_type_statement.py:43:22 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) -aliases_type_statement.py:45:22 - error: Expected type expression but received "int" (reportGeneralTypeIssues) +aliases_type_statement.py:44:22 - error: Ternary expression not allowed in type annotation +aliases_type_statement.py:45:22 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_type_statement.py:46:23 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) -aliases_type_statement.py:47:23 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_type_statement.py:47:23 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +aliases_type_statement.py:48:23 - error: Binary operator not allowed in type annotation aliases_type_statement.py:49:23 - error: Expected expression +aliases_type_statement.py:49:23 - error: Tuple expression not allowed in type annotation +  Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) aliases_type_statement.py:58:10 - error: A type statement can be used only within a module or class scope aliases_type_statement.py:64:23 - error: Type parameter "V" is not included in the type parameter list for "TA1" (reportGeneralTypeIssues) aliases_type_statement.py:69:17 - error: Type parameter "T1" is not included in the type parameter list for "TA2" (reportGeneralTypeIssues) diff --git a/conformance/results/pyright/aliases_typealiastype.toml b/conformance/results/pyright/aliases_typealiastype.toml index 34329cf9d..a71693625 100644 --- a/conformance/results/pyright/aliases_typealiastype.toml +++ b/conformance/results/pyright/aliases_typealiastype.toml @@ -1,37 +1,31 @@ -conformant = "Partial" -notes = """ -Does not reject type alias with TypeVar that is not in scope and not in `type_params`. -Does not allow access to `__value__` attribute of type alias. -Allows some illegal annotation forms to be interpreted as valid type aliases. -""" +conformant = "Pass" output = """ -aliases_typealiastype.py:30:18 - error: Cannot access member "__value__" for type "GoodAlias1" -  Member "__value__" is unknown (reportGeneralTypeIssues) -aliases_typealiastype.py:32:18 - error: Cannot access member "other_attrib" for type "GoodAlias1" +aliases_typealiastype.py:32:18 - error: Cannot access member "other_attrib" for type "TypeAliasType"   Member "other_attrib" is unknown (reportGeneralTypeIssues) aliases_typealiastype.py:40:5 - error: Could not specialize type "GoodAlias5[S@GoodAlias5, TStr@GoodAlias5, P@GoodAlias5, Ts@GoodAlias5]"   Type "int" cannot be assigned to type "str"     "int" is incompatible with "str" aliases_typealiastype.py:44:23 - error: Type variable "S" has no meaning in this context (reportGeneralTypeIssues) +aliases_typealiastype.py:46:45 - error: Type variable "S" has no meaning in this context (reportGeneralTypeIssues) aliases_typealiastype.py:48:35 - error: Type parameter list must be a tuple containing only TypeVar, TypeVarTuple, or ParamSpec aliases_typealiastype.py:50:40 - error: Type alias "BadAlias4" cannot use itself in its definition (reportGeneralTypeIssues) aliases_typealiastype.py:52:18 - error: Type alias "BadAlias5" cannot use itself in its definition (reportGeneralTypeIssues) aliases_typealiastype.py:54:40 - error: Type alias "BadAlias6" cannot use itself in its definition (reportGeneralTypeIssues) -aliases_typealiastype.py:58:40 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) -aliases_typealiastype.py:59:40 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_typealiastype.py:58:40 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) aliases_typealiastype.py:59:40 - error: Expected type expression but received "list[Unknown]" (reportGeneralTypeIssues) -aliases_typealiastype.py:60:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_typealiastype.py:60:42 - error: Expected type expression but received "tuple[tuple[type[int], type[str]]]" (reportGeneralTypeIssues) -aliases_typealiastype.py:61:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_typealiastype.py:61:42 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) -aliases_typealiastype.py:62:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_typealiastype.py:62:42 - error: Expected type expression but received "dict[str, str]" (reportGeneralTypeIssues) -aliases_typealiastype.py:63:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) +aliases_typealiastype.py:63:42 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +aliases_typealiastype.py:64:42 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) aliases_typealiastype.py:64:42 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) -aliases_typealiastype.py:65:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) -aliases_typealiastype.py:66:42 - error: Expected type expression but received "int" (reportGeneralTypeIssues) +aliases_typealiastype.py:65:42 - error: Ternary expression not allowed in type annotation +aliases_typealiastype.py:66:42 - error: Variable not allowed in type expression (reportGeneralTypeIssues) aliases_typealiastype.py:67:42 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) -aliases_typealiastype.py:68:42 - error: Invalid expression form for type alias definition (reportGeneralTypeIssues) aliases_typealiastype.py:68:42 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +aliases_typealiastype.py:69:42 - error: Binary operator not allowed in type annotation aliases_typealiastype.py:70:42 - error: Expected expression +aliases_typealiastype.py:70:42 - error: Tuple expression not allowed in type annotation +  Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) """ diff --git a/conformance/results/pyright/annotations_coroutines.toml b/conformance/results/pyright/annotations_coroutines.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/annotations_coroutines.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/annotations_forward_refs.toml b/conformance/results/pyright/annotations_forward_refs.toml new file mode 100644 index 000000000..1ace3b6bc --- /dev/null +++ b/conformance/results/pyright/annotations_forward_refs.toml @@ -0,0 +1,37 @@ +conformant = "Pass" +output = """ +annotations_forward_refs.py:22:7 - error: "ClassA" is not defined (reportUndefinedVariable) +annotations_forward_refs.py:23:12 - error: "ClassA" is not defined (reportUndefinedVariable) +annotations_forward_refs.py:24:7 - error: Union syntax cannot be used with string operand; use quotes around entire expression (reportGeneralTypeIssues) +annotations_forward_refs.py:25:13 - error: Union syntax cannot be used with string operand; use quotes around entire expression (reportGeneralTypeIssues) +annotations_forward_refs.py:41:9 - error: Expected type but received a string literal (reportGeneralTypeIssues) +annotations_forward_refs.py:42:10 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +annotations_forward_refs.py:42:10 - error: Expected type expression but received "list[Unknown]" (reportGeneralTypeIssues) +annotations_forward_refs.py:43:10 - error: Tuple expression not allowed in type annotation +  Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +annotations_forward_refs.py:44:10 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +annotations_forward_refs.py:44:10 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +annotations_forward_refs.py:45:10 - error: Dictionary expression not allowed in type annotation +  Use Dict[T1, T2] to indicate a dictionary type (reportGeneralTypeIssues) +annotations_forward_refs.py:45:10 - error: Expected type expression but received "dict[Unknown, Unknown]" (reportGeneralTypeIssues) +annotations_forward_refs.py:46:10 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +annotations_forward_refs.py:47:10 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +annotations_forward_refs.py:47:10 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +annotations_forward_refs.py:48:10 - error: Ternary expression not allowed in type annotation +annotations_forward_refs.py:49:10 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +annotations_forward_refs.py:50:11 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) +annotations_forward_refs.py:51:11 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +annotations_forward_refs.py:52:11 - error: Unary operator not allowed in type annotation +annotations_forward_refs.py:53:11 - error: Binary operator not allowed in type annotation +annotations_forward_refs.py:54:11 - error: Expected expression +annotations_forward_refs.py:54:11 - error: Tuple expression not allowed in type annotation +  Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +annotations_forward_refs.py:55:10 - error: Module cannot be used as a type (reportGeneralTypeIssues) +annotations_forward_refs.py:66:26 - error: "ClassB" is not defined (reportUndefinedVariable) +annotations_forward_refs.py:80:14 - error: Type of "ClassF" could not be determined because it refers to itself (reportGeneralTypeIssues) +annotations_forward_refs.py:80:14 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +annotations_forward_refs.py:89:8 - error: Expected type expression but received "(self: Self@ClassD) -> None" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/annotations_generators.toml b/conformance/results/pyright/annotations_generators.toml new file mode 100644 index 000000000..9104ee128 --- /dev/null +++ b/conformance/results/pyright/annotations_generators.toml @@ -0,0 +1,33 @@ +conformant = "Pass" +output = """ +annotations_generators.py:54:16 - error: Expression of type "Literal[False]" cannot be assigned to return type "C" +  "Literal[False]" is incompatible with "C" (reportGeneralTypeIssues) +annotations_generators.py:57:15 - error: Return type of generator function must be compatible with "Generator[Literal[3], Any, Any]" +  "Literal[3]" is incompatible with "A" (reportGeneralTypeIssues) +annotations_generators.py:51:21 - error: Function with declared return type "C" must return value on all code paths +  "None" is incompatible with "C" (reportGeneralTypeIssues) +annotations_generators.py:66:15 - error: Return type of generator function must be compatible with "Generator[Literal[3], Any, Any]" +  "Literal[3]" is incompatible with "A" (reportGeneralTypeIssues) +annotations_generators.py:75:11 - error: Return type of generator function must be compatible with "Generator[B, Any, Any]" +  "B" is incompatible with "A" (reportGeneralTypeIssues) +annotations_generators.py:87:11 - error: Return type of generator function must be compatible with "Generator[None, Any, Any]" +  "Generator[None, Unknown, Unknown]" is incompatible with "int" (reportGeneralTypeIssues) +annotations_generators.py:86:21 - error: Return type of generator function must be compatible with "Generator[Any, Any, Any]" +  "Generator[Any, Any, Any]" is incompatible with "int" (reportGeneralTypeIssues) +annotations_generators.py:92:11 - error: Return type of async generator function must be compatible with "AsyncGenerator[None, Any]" +  "AsyncGenerator[None, Unknown, Unknown]" is incompatible with "int" (reportGeneralTypeIssues) +annotations_generators.py:91:27 - error: Return type of async generator function must be compatible with "AsyncGenerator[Any, Any]" +  "AsyncGenerator[Any, Any, Any]" is incompatible with "int" (reportGeneralTypeIssues) +annotations_generators.py:118:16 - error: Return type of generator function must be compatible with "Generator[A, Any, Any]" +  "Generator[A, Unknown, Unknown]" is incompatible with "Iterator[B]" +    Type parameter "_T_co@Iterator" is covariant, but "A" is not a subtype of "B" +      "A" is incompatible with "B" (reportGeneralTypeIssues) +annotations_generators.py:119:16 - error: Return type of generator function must be compatible with "Generator[int, Any, Any]" +  "Generator[int, Unknown, Unknown]" is incompatible with "Iterator[B]" +    Type parameter "_T_co@Iterator" is covariant, but "int" is not a subtype of "B" +      "int" is incompatible with "B" (reportGeneralTypeIssues) +annotations_generators.py:135:16 - error: Return type of generator function must be compatible with "Generator[None, Any, Any]" +  "Generator[None, int, Unknown]" is incompatible with "Generator[None, str, None]" +    Type parameter "_SendT_contra@Generator" is contravariant, but "int" is not a supertype of "str" +      "str" is incompatible with "int" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/annotations_methods.toml b/conformance/results/pyright/annotations_methods.toml new file mode 100644 index 000000000..3a22f5f82 --- /dev/null +++ b/conformance/results/pyright/annotations_methods.toml @@ -0,0 +1,8 @@ +conformant = "Pass" +notes = """ +Type evaluation differs from other type checkers because of ambiguity in the spec related to method bindings. +""" +output = """ +annotations_methods.py:46:8 - error: Argument of type "A" cannot be assigned to parameter "self" of type "B" in function "copy" +  "A" is incompatible with "B" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/annotations_typeexpr.toml b/conformance/results/pyright/annotations_typeexpr.toml index f1bd93080..bf313b95f 100644 --- a/conformance/results/pyright/annotations_typeexpr.toml +++ b/conformance/results/pyright/annotations_typeexpr.toml @@ -1,29 +1,29 @@ conformant = "Pass" output = """ -annotations_typeexpr.py:77:9 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) -annotations_typeexpr.py:78:9 - error: List expression not allowed in type annotation +annotations_typeexpr.py:88:9 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +annotations_typeexpr.py:89:9 - error: List expression not allowed in type annotation   Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) -annotations_typeexpr.py:78:9 - error: Expected type expression but received "list[Unknown]" (reportGeneralTypeIssues) -annotations_typeexpr.py:79:9 - error: Tuple expression not allowed in type annotation +annotations_typeexpr.py:89:9 - error: Expected type expression but received "list[Unknown]" (reportGeneralTypeIssues) +annotations_typeexpr.py:90:9 - error: Tuple expression not allowed in type annotation   Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) -annotations_typeexpr.py:79:9 - error: Expected type expression but received "tuple[type[int], type[str]]" (reportGeneralTypeIssues) -annotations_typeexpr.py:80:9 - error: List expression not allowed in type annotation +annotations_typeexpr.py:91:9 - error: List expression not allowed in type annotation   Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) -annotations_typeexpr.py:80:9 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) -annotations_typeexpr.py:81:9 - error: Dictionary expression not allowed in type annotation +annotations_typeexpr.py:91:9 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +annotations_typeexpr.py:92:9 - error: Dictionary expression not allowed in type annotation   Use Dict[T1, T2] to indicate a dictionary type (reportGeneralTypeIssues) -annotations_typeexpr.py:81:9 - error: Expected type expression but received "dict[Unknown, Unknown]" (reportGeneralTypeIssues) -annotations_typeexpr.py:82:9 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) -annotations_typeexpr.py:83:9 - error: List expression not allowed in type annotation +annotations_typeexpr.py:92:9 - error: Expected type expression but received "dict[Unknown, Unknown]" (reportGeneralTypeIssues) +annotations_typeexpr.py:93:9 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +annotations_typeexpr.py:94:9 - error: List expression not allowed in type annotation   Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) -annotations_typeexpr.py:83:9 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) -annotations_typeexpr.py:84:9 - error: Ternary expression not allowed in type annotation -annotations_typeexpr.py:85:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) -annotations_typeexpr.py:86:10 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) -annotations_typeexpr.py:87:10 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) -annotations_typeexpr.py:88:10 - error: Unary operator not allowed in type annotation -annotations_typeexpr.py:89:10 - error: Binary operator not allowed in type annotation -annotations_typeexpr.py:90:10 - error: Expected expression -annotations_typeexpr.py:90:10 - error: Tuple expression not allowed in type annotation +annotations_typeexpr.py:94:9 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +annotations_typeexpr.py:95:9 - error: Ternary expression not allowed in type annotation +annotations_typeexpr.py:96:9 - error: Variable not allowed in type expression (reportGeneralTypeIssues) +annotations_typeexpr.py:97:10 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) +annotations_typeexpr.py:98:10 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +annotations_typeexpr.py:99:10 - error: Unary operator not allowed in type annotation +annotations_typeexpr.py:100:10 - error: Binary operator not allowed in type annotation +annotations_typeexpr.py:101:10 - error: Expected expression +annotations_typeexpr.py:101:10 - error: Tuple expression not allowed in type annotation   Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +annotations_typeexpr.py:102:10 - error: Module cannot be used as a type (reportGeneralTypeIssues) """ diff --git a/conformance/results/pyright/callables_protocol.toml b/conformance/results/pyright/callables_protocol.toml index 7e548a2a7..c4393daa5 100644 --- a/conformance/results/pyright/callables_protocol.toml +++ b/conformance/results/pyright/callables_protocol.toml @@ -1,7 +1,4 @@ -conformant = "Partial" -notes = """ -Does not report type incompatibility for callback protocol with positional-only parameters. -""" +conformant = "Pass" output = """ callables_protocol.py:35:7 - error: Expression of type "(*vals: bytes, max_items: int | None) -> list[bytes]" cannot be assigned to declared type "Proto1"   Type "(*vals: bytes, max_items: int | None) -> list[bytes]" cannot be assigned to type "(*vals: bytes, max_len: int | None = None) -> list[bytes]" @@ -47,6 +44,9 @@ callables_protocol.py:187:15 - error: Cannot assign member "xxx" for type "Proto   Member "xxx" is unknown (reportGeneralTypeIssues) callables_protocol.py:197:16 - error: Cannot access member "other_attribute2" for type "Proto9[(x: int), str]"   Member "other_attribute2" is unknown (reportGeneralTypeIssues) +callables_protocol.py:238:8 - error: Expression of type "(x: int, y: str, /) -> Any" cannot be assigned to declared type "Proto11" +  Type "(x: int, y: str, /) -> Any" cannot be assigned to type "(x: int, /, y: str) -> Any" +    Position-only parameter mismatch; parameter "y" is not position-only (reportGeneralTypeIssues) callables_protocol.py:260:8 - error: Expression of type "(*args: Any, kwarg0: Any) -> None" cannot be assigned to declared type "Proto12"   Type "(*args: Any, kwarg0: Any) -> None" cannot be assigned to type "(*args: Any, kwarg0: Any, kwarg1: Any) -> None"     Keyword parameter "kwarg1" is missing in source (reportGeneralTypeIssues) diff --git a/conformance/results/pyright/classes_classvar.toml b/conformance/results/pyright/classes_classvar.toml new file mode 100644 index 000000000..471d0a27d --- /dev/null +++ b/conformance/results/pyright/classes_classvar.toml @@ -0,0 +1,24 @@ +conformant = "Pass" +output = """ +classes_classvar.py:36:25 - error: Expected only one type argument after "ClassVar" +classes_classvar.py:37:14 - error: Expected type expression but received "Literal[3]" (reportGeneralTypeIssues) +classes_classvar.py:38:14 - error: "var" is not defined (reportUndefinedVariable) +classes_classvar.py:43:20 - error: "ClassVar" type cannot include type variables (reportGeneralTypeIssues) +classes_classvar.py:44:20 - error: "ClassVar" type cannot include type variables (reportGeneralTypeIssues) +classes_classvar.py:45:20 - error: "ClassVar" type cannot include type variables (reportGeneralTypeIssues) +classes_classvar.py:50:33 - error: Expression of type "dict[Any, Any]" cannot be assigned to declared type "list[str]" (reportGeneralTypeIssues) +classes_classvar.py:52:11 - error: "Final" is not defined (reportUndefinedVariable) +classes_classvar.py:53:17 - error: "ClassVar" is not allowed in this context +classes_classvar.py:61:26 - error: "ClassVar" is not allowed in this context +classes_classvar.py:62:12 - error: "ClassVar" is not allowed in this context +classes_classvar.py:63:18 - error: "ClassVar" is not allowed in this context +classes_classvar.py:65:26 - error: "ClassVar" is not allowed in this context +classes_classvar.py:69:8 - error: "ClassVar" is not allowed in this context +classes_classvar.py:70:20 - error: "ClassVar" is not allowed in this context +classes_classvar.py:100:14 - error: Cannot assign member "stats" for type "Starship" +  Member "stats" cannot be assigned through a class instance because it is a ClassVar +    Member "__set__" is unknown (reportGeneralTypeIssues) +classes_classvar.py:129:13 - error: Expression of type "ProtoAImpl" cannot be assigned to declared type "ProtoA" +  "ProtoAImpl" is incompatible with protocol "ProtoA" +    "y" is not a class variable (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/classes_override.toml b/conformance/results/pyright/classes_override.toml new file mode 100644 index 000000000..8fa4d0924 --- /dev/null +++ b/conformance/results/pyright/classes_override.toml @@ -0,0 +1,8 @@ +conformant = "Pass" +output = """ +classes_override.py:53:9 - error: Method "method3" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) +classes_override.py:65:9 - error: Method "method4" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) +classes_override.py:79:9 - error: Method "static_method1" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) +classes_override.py:84:9 - error: Method "class_method1" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) +classes_override.py:91:9 - error: Method "property1" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/dataclasses_postinit.toml b/conformance/results/pyright/dataclasses_postinit.toml index 3ac995fe2..8dd024294 100644 --- a/conformance/results/pyright/dataclasses_postinit.toml +++ b/conformance/results/pyright/dataclasses_postinit.toml @@ -1,7 +1,4 @@ -conformant = "Partial" -notes = """ -Reports incorrect error for incompatible `__post_init__` method override. -""" +conformant = "Pass" output = """ dataclasses_postinit.py:19:40 - error: Dataclass __post_init__ method parameter type mismatch for field "y"   "str" is incompatible with "int" (reportGeneralTypeIssues) @@ -10,6 +7,4 @@ dataclasses_postinit.py:28:11 - error: Cannot access member "x" for type "DC1" dataclasses_postinit.py:29:11 - error: Cannot access member "y" for type "DC1"   Member "y" is an init-only field (reportGeneralTypeIssues) dataclasses_postinit.py:36:9 - error: Dataclass __post_init__ incorrect parameter count; number of InitVar fields is 2 (reportGeneralTypeIssues) -dataclasses_postinit.py:54:9 - error: Method "__post_init__" overrides class "DC3" in an incompatible manner -  Positional parameter count mismatch; base method has 2, but override has 3 (reportIncompatibleMethodOverride) """ diff --git a/conformance/results/pyright/directives_assert_type.toml b/conformance/results/pyright/directives_assert_type.toml new file mode 100644 index 000000000..ea8b7aa4a --- /dev/null +++ b/conformance/results/pyright/directives_assert_type.toml @@ -0,0 +1,9 @@ +conformant = "Pass" +output = """ +directives_assert_type.py:27:17 - error: "assert_type" mismatch: expected "int" but received "int | str" (reportGeneralTypeIssues) +directives_assert_type.py:28:17 - error: "assert_type" mismatch: expected "int" but received "Any" (reportGeneralTypeIssues) +directives_assert_type.py:29:17 - error: "assert_type" mismatch: expected "int" but received "Literal[4]" (reportGeneralTypeIssues) +directives_assert_type.py:31:5 - error: "assert_type" expects two positional arguments +directives_assert_type.py:32:17 - error: "assert_type" mismatch: expected "int" but received "Literal['']" (reportGeneralTypeIssues) +directives_assert_type.py:33:5 - error: "assert_type" expects two positional arguments +""" diff --git a/conformance/results/pyright/directives_cast.toml b/conformance/results/pyright/directives_cast.toml new file mode 100644 index 000000000..f23fa43ef --- /dev/null +++ b/conformance/results/pyright/directives_cast.toml @@ -0,0 +1,8 @@ +conformant = "Pass" +output = """ +directives_cast.py:15:8 - error: No overloads for "cast" match the provided arguments +  Argument types: () (reportGeneralTypeIssues) +directives_cast.py:16:13 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +directives_cast.py:17:8 - error: No overloads for "cast" match the provided arguments +  Argument types: (type[int], Literal[''], Literal['']) (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/directives_no_type_check.toml b/conformance/results/pyright/directives_no_type_check.toml new file mode 100644 index 000000000..6bc67f59b --- /dev/null +++ b/conformance/results/pyright/directives_no_type_check.toml @@ -0,0 +1,14 @@ +conformant = "Unsupported" +notes = """ +Intentionally does not honor @no_type_check decorator. +""" +output = """ +directives_no_type_check.py:16:9 - error: Operator "+" not supported for types "int" and "str" (reportGeneralTypeIssues) +directives_no_type_check.py:17:12 - error: Expression of type "Literal[1]" cannot be assigned to return type "None" +  "Literal[1]" is incompatible with "None" (reportGeneralTypeIssues) +directives_no_type_check.py:20:7 - error: Argument of type "Literal[b"invalid"]" cannot be assigned to parameter "a" of type "int" in function "func1" +  "Literal[b"invalid"]" is incompatible with "int" (reportGeneralTypeIssues) +directives_no_type_check.py:20:19 - error: Argument of type "Literal[b"arguments"]" cannot be assigned to parameter "b" of type "str" in function "func1" +  "Literal[b"arguments"]" is incompatible with "str" (reportGeneralTypeIssues) +directives_no_type_check.py:21:1 - error: Arguments missing for parameters "a", "b" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/directives_reveal_type.toml b/conformance/results/pyright/directives_reveal_type.toml new file mode 100644 index 000000000..aa4078ead --- /dev/null +++ b/conformance/results/pyright/directives_reveal_type.toml @@ -0,0 +1,9 @@ +conformant = "Pass" +output = """ +directives_reveal_type.py:14:17 - information: Type of "a" is "int | str" +directives_reveal_type.py:15:17 - information: Type of "b" is "list[int]" +directives_reveal_type.py:16:17 - information: Type of "c" is "Any" +directives_reveal_type.py:17:17 - information: Type of "d" is "ForwardReference" +directives_reveal_type.py:19:5 - error: Expected a single positional argument for "reveal_type" call +directives_reveal_type.py:20:5 - error: Expected a single positional argument for "reveal_type" call +""" diff --git a/conformance/results/pyright/directives_type_checking.toml b/conformance/results/pyright/directives_type_checking.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/directives_type_checking.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/directives_type_ignore.toml b/conformance/results/pyright/directives_type_ignore.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/directives_type_ignore.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/directives_type_ignore_file1.toml b/conformance/results/pyright/directives_type_ignore_file1.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/directives_type_ignore_file1.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/directives_type_ignore_file2.toml b/conformance/results/pyright/directives_type_ignore_file2.toml new file mode 100644 index 000000000..37ecf19c9 --- /dev/null +++ b/conformance/results/pyright/directives_type_ignore_file2.toml @@ -0,0 +1,5 @@ +conformant = "Pass" +output = """ +directives_type_ignore_file2.py:14:10 - error: Expression of type "Literal['']" cannot be assigned to declared type "int" +  "Literal['']" is incompatible with "int" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/directives_version_platform.toml b/conformance/results/pyright/directives_version_platform.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/directives_version_platform.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/overloads_basic.toml b/conformance/results/pyright/overloads_basic.toml new file mode 100644 index 000000000..7d79eea21 --- /dev/null +++ b/conformance/results/pyright/overloads_basic.toml @@ -0,0 +1,8 @@ +conformant = "Pass" +output = """ +overloads_basic.py:37:1 - error: No overloads for "__getitem__" match the provided arguments (reportGeneralTypeIssues) +overloads_basic.py:37:1 - error: Argument of type "Literal['']" cannot be assigned to parameter "__s" of type "slice" in function "__getitem__" +  "Literal['']" is incompatible with "slice" (reportGeneralTypeIssues) +overloads_basic.py:63:5 - error: "func1" is marked as overload, but additional overloads are missing (reportGeneralTypeIssues) +overloads_basic.py:75:5 - error: "func2" is marked as overload, but no implementation is provided (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/protocols_class_objects.toml b/conformance/results/pyright/protocols_class_objects.toml new file mode 100644 index 000000000..8fa46e696 --- /dev/null +++ b/conformance/results/pyright/protocols_class_objects.toml @@ -0,0 +1,28 @@ +conformant = "Partial" +notes = """ +Incorrectly reports some class objects as incompatible with a protocol. +Fails to report some class objects as incompatible with a protocol. +""" +output = """ +protocols_class_objects.py:29:5 - error: Argument of type "type[Proto]" cannot be assigned to parameter "cls" of type "type[Proto]" in function "fun" +  "Proto" is not a concrete class type and cannot be assigned to type "type[Proto]" (reportGeneralTypeIssues) +protocols_class_objects.py:34:7 - error: Expression of type "type[Proto]" cannot be assigned to declared type "type[Proto]" +  "Proto" is not a concrete class type and cannot be assigned to type "type[Proto]" (reportGeneralTypeIssues) +protocols_class_objects.py:58:16 - error: Expression of type "type[ConcreteA]" cannot be assigned to declared type "ProtoA1" +  "type[type]" is incompatible with "type[ConcreteA]" +  "type[type]" is incompatible with "type[ConcreteA]" +  "method1" is an incompatible type +    Type "(self: ConcreteA, x: int) -> int" cannot be assigned to type "(x: int) -> int" +      Parameter name mismatch: "x" versus "self" +      Parameter 1: type "int" cannot be assigned to type "ConcreteA" +        "int" is incompatible with "ConcreteA" (reportGeneralTypeIssues) +protocols_class_objects.py:59:16 - error: Expression of type "type[ConcreteA]" cannot be assigned to declared type "ProtoA2" +  "type[type]" is incompatible with "type[ConcreteA]" +  "type[type]" is incompatible with "type[ConcreteA]" +  "method1" is an incompatible type +    Type "(self: ConcreteA, x: int) -> int" cannot be assigned to type "(obj: Any, x: int) -> int" +      Parameter name mismatch: "obj" versus "self" (reportGeneralTypeIssues) +protocols_class_objects.py:74:16 - error: Expression of type "type[ConcreteB]" cannot be assigned to declared type "ProtoB1" +  "prop1" is an incompatible type +    "property" is incompatible with "int" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/protocols_definition.toml b/conformance/results/pyright/protocols_definition.toml new file mode 100644 index 000000000..bd9150093 --- /dev/null +++ b/conformance/results/pyright/protocols_definition.toml @@ -0,0 +1,89 @@ +conformant = "Partial" +notes = """ +Does not reject ClassVar in concrete class when attribute in protocol is not ClassVar. +""" +output = """ +protocols_definition.py:30:12 - error: Argument of type "list[int]" cannot be assigned to parameter "things" of type "Iterable[SupportsClose]" in function "close_all" +  "Literal[1]" is incompatible with protocol "SupportsClose" +    "close" is not present (reportGeneralTypeIssues) +protocols_definition.py:67:14 - error: Instance or class variables within a Protocol class must be explicitly declared within the class body +protocols_definition.py:115:22 - error: Expression of type "Concrete2_Bad1" cannot be assigned to declared type "Template2" +  "Concrete2_Bad1" is incompatible with protocol "Template2" +    "val1" is not present (reportGeneralTypeIssues) +protocols_definition.py:116:22 - error: Expression of type "Concrete2_Bad2" cannot be assigned to declared type "Template2" +  "Concrete2_Bad2" is incompatible with protocol "Template2" +    "val1" is invariant because it is mutable +    "val1" is an incompatible type +      "Sequence[float]" is incompatible with "Sequence[int]" (reportGeneralTypeIssues) +protocols_definition.py:117:22 - error: Expression of type "Concrete2_Bad3" cannot be assigned to declared type "Template2" +  "Concrete2_Bad3" is incompatible with protocol "Template2" +    "val1" is invariant because it is mutable +    "val1" is an incompatible type +      "list[int]" is incompatible with "Sequence[int]" (reportGeneralTypeIssues) +protocols_definition.py:156:22 - error: Expression of type "Concrete3_Bad1" cannot be assigned to declared type "Template3" +  "Concrete3_Bad1" is incompatible with protocol "Template3" +    "val1" is not present (reportGeneralTypeIssues) +protocols_definition.py:158:22 - error: Expression of type "Concrete3_Bad3" cannot be assigned to declared type "Template3" +  "Concrete3_Bad3" is incompatible with protocol "Template3" +    "val1" is invariant because it is mutable +    "val1" is an incompatible type +      "property" is incompatible with "Sequence[int]" (reportGeneralTypeIssues) +protocols_definition.py:159:22 - error: Expression of type "Concrete3_Bad4" cannot be assigned to declared type "Template3" +  "Concrete3_Bad4" is incompatible with protocol "Template3" +    "val1" is invariant because it is mutable +    "val1" is an incompatible type +      "Sequence[float]" is incompatible with "Sequence[int]" (reportGeneralTypeIssues) +protocols_definition.py:160:22 - error: Expression of type "Concrete3_Bad5" cannot be assigned to declared type "Template3" +  "Concrete3_Bad5" is incompatible with protocol "Template3" +    "val1" is invariant because it is mutable +    "val1" is an incompatible type +      "list[int]" is incompatible with "Sequence[int]" (reportGeneralTypeIssues) +protocols_definition.py:218:22 - error: Expression of type "Concrete4_Bad1" cannot be assigned to declared type "Template4" +  "Concrete4_Bad1" is incompatible with protocol "Template4" +    "val1" is an incompatible type +      "function" is incompatible with "Sequence[float]" (reportGeneralTypeIssues) +protocols_definition.py:219:22 - error: Expression of type "Concrete4_Bad2" cannot be assigned to declared type "Template4" +  "Concrete4_Bad2" is incompatible with protocol "Template4" +    "val1" is not present (reportGeneralTypeIssues) +protocols_definition.py:276:17 - warning: Static methods should not take a "self" or "cls" parameter (reportSelfClsParameterName) +protocols_definition.py:285:22 - error: Expression of type "Concrete5_Bad1" cannot be assigned to declared type "Template5" +  "Concrete5_Bad1" is incompatible with protocol "Template5" +    "method1" is an incompatible type +      Type "(a: Unknown, c: Unknown) -> int" cannot be assigned to type "(a: int, b: int) -> float" +        Parameter name mismatch: "b" versus "c" (reportGeneralTypeIssues) +protocols_definition.py:286:22 - error: Expression of type "Concrete5_Bad2" cannot be assigned to declared type "Template5" +  "Concrete5_Bad2" is incompatible with protocol "Template5" +    "method1" is an incompatible type +      Type "(a: int, c: int) -> int" cannot be assigned to type "(a: int, b: int) -> float" +        Parameter name mismatch: "b" versus "c" (reportGeneralTypeIssues) +protocols_definition.py:287:22 - error: Expression of type "Concrete5_Bad3" cannot be assigned to declared type "Template5" +  "Concrete5_Bad3" is incompatible with protocol "Template5" +    "method1" is an incompatible type +      Type "(*, a: int, b: int) -> float" cannot be assigned to type "(a: int, b: int) -> float" +        Function accepts too many positional parameters; expected 0 but received 2 +          Keyword parameter "a" is missing in destination +          Keyword parameter "b" is missing in destination (reportGeneralTypeIssues) +protocols_definition.py:288:22 - error: Expression of type "Concrete5_Bad4" cannot be assigned to declared type "Template5" +  "Concrete5_Bad4" is incompatible with protocol "Template5" +    "method1" is an incompatible type +      Type "(a: int, b: int, /) -> float" cannot be assigned to type "(a: int, b: int) -> float" +        Position-only parameter mismatch; parameter "a" is not position-only +        Position-only parameter mismatch; parameter "b" is not position-only +        Position-only parameter mismatch; expected 2 but received 0 (reportGeneralTypeIssues) +protocols_definition.py:289:22 - error: Expression of type "Concrete5_Bad5" cannot be assigned to declared type "Template5" +  "Concrete5_Bad5" is incompatible with protocol "Template5" +    "method1" is an incompatible type +      Type "(self: Unknown, a: int, b: int) -> float" cannot be assigned to type "(a: int, b: int) -> float" +        Parameter name mismatch: "a" versus "self" +        Parameter name mismatch: "b" versus "a" (reportGeneralTypeIssues) +protocols_definition.py:339:22 - error: Expression of type "Concrete6_Bad1" cannot be assigned to declared type "Template6" +  "Concrete6_Bad1" is incompatible with protocol "Template6" +    "val1" is an incompatible type +      Property setter method is missing (reportGeneralTypeIssues) +protocols_definition.py:340:22 - error: Expression of type "Concrete6_Bad2" cannot be assigned to declared type "Template6" +  "Concrete6_Bad2" is incompatible with protocol "Template6" +    "val1" is writable in protocol (reportGeneralTypeIssues) +protocols_definition.py:341:22 - error: Expression of type "Concrete6_Bad3" cannot be assigned to declared type "Template6" +  "Concrete6_Bad3" is incompatible with protocol "Template6" +    "val1" is writable in protocol (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/protocols_explicit.toml b/conformance/results/pyright/protocols_explicit.toml new file mode 100644 index 000000000..bacc5d7d1 --- /dev/null +++ b/conformance/results/pyright/protocols_explicit.toml @@ -0,0 +1,26 @@ +conformant = "Partial" +notes = """ +Does not report error when calling unimplemented protocol method from derived class. +""" +output = """ +protocols_explicit.py:56:32 - error: Cannot assign member "rgb" for type "Point*" +  "str" is incompatible with "int" (reportGeneralTypeIssues) +protocols_explicit.py:54:7 - error: Class derives from one or more protocol classes but does not implement all required members +  Member "other" is declared in protocol class "RGB" +  Member "transparency" is declared in protocol class "RGB" (reportGeneralTypeIssues) +protocols_explicit.py:63:5 - error: Cannot instantiate abstract class "Point" +  "RGB.intensity" is abstract (reportGeneralTypeIssues) +protocols_explicit.py:86:7 - error: Class derives from one or more protocol classes but does not implement all required members +  Member "cm1" is declared in protocol class "Proto1" +  Member "im1" is declared in protocol class "Proto1" (reportGeneralTypeIssues) +protocols_explicit.py:103:7 - error: Class derives from one or more protocol classes but does not implement all required members +  Member "im1" is declared in protocol class "Proto1" +  Member "cm10" is declared in protocol class "Proto2" +  Member "cm11" is declared in protocol class "Proto3" (reportGeneralTypeIssues) +protocols_explicit.py:135:7 - error: Class derives from one or more protocol classes but does not implement all required members +  Member "method1" is declared in protocol class "Proto5" (reportGeneralTypeIssues) +protocols_explicit.py:166:7 - error: Class derives from one or more protocol classes but does not implement all required members +  Member "method1" is declared in protocol class "Proto7" (reportGeneralTypeIssues) +protocols_explicit.py:171:7 - error: Cannot instantiate abstract class "Concrete7A" +  "Proto7.method1" is abstract (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/protocols_generic.toml b/conformance/results/pyright/protocols_generic.toml new file mode 100644 index 000000000..d4972dc21 --- /dev/null +++ b/conformance/results/pyright/protocols_generic.toml @@ -0,0 +1,40 @@ +conformant = "Pass" +output = """ +protocols_generic.py:40:24 - error: Expression of type "Concrete1" cannot be assigned to declared type "Proto1[int, str]" +  "Concrete1" is incompatible with protocol "Proto1[int, str]" +    "method1" is an incompatible type +      Type "(x: str) -> str" cannot be assigned to type "(x: S@Proto1) -> S@Proto1" +        Parameter 1: type "S@Proto1" cannot be assigned to type "str" +          "int" is incompatible with "str" +        Function return type "str" is incompatible with type "S@Proto1" +          Type "str" cannot be assigned to type "int" +    "__iter__" is an incompatible type + ... (reportGeneralTypeIssues) +protocols_generic.py:44:30 - error: Only one Generic[...] or Protocol[...] base class allowed (reportGeneralTypeIssues) +protocols_generic.py:56:20 - error: Expression of type "Box[float]" cannot be assigned to declared type "Box[int]" +  "Box[float]" is incompatible with "Box[int]" +    Type parameter "T_co@Box" is covariant, but "float" is not a subtype of "int" +      "float" is incompatible with "int" (reportGeneralTypeIssues) +protocols_generic.py:66:25 - error: Expression of type "Sender[int]" cannot be assigned to declared type "Sender[float]" +  "Sender[int]" is incompatible with "Sender[float]" +    Type parameter "T_contra@Sender" is contravariant, but "int" is not a supertype of "float" +      "float" is incompatible with "int" (reportGeneralTypeIssues) +protocols_generic.py:74:28 - error: Expression of type "AttrProto[int]" cannot be assigned to declared type "AttrProto[float]" +  "AttrProto[int]" is incompatible with "AttrProto[float]" +    Type parameter "T@AttrProto" is invariant, but "int" is not the same as "float" (reportGeneralTypeIssues) +protocols_generic.py:75:26 - error: Expression of type "AttrProto[float]" cannot be assigned to declared type "AttrProto[int]" +  "AttrProto[float]" is incompatible with "AttrProto[int]" +    Type parameter "T@AttrProto" is invariant, but "float" is not the same as "int" (reportGeneralTypeIssues) +protocols_generic.py:146:25 - error: Expression of type "ConcreteHasProperty3" cannot be assigned to declared type "HasPropertyProto" +  "ConcreteHasProperty3" is incompatible with protocol "HasPropertyProto" +    "f" is an incompatible type +      Type "() -> int" cannot be assigned to type "() -> HasPropertyProto" +        Function return type "int" is incompatible with type "HasPropertyProto" +          "int" is incompatible with protocol "HasPropertyProto" (reportGeneralTypeIssues) +protocols_generic.py:147:25 - error: Expression of type "ConcreteHasProperty4" cannot be assigned to declared type "HasPropertyProto" +  "ConcreteHasProperty4" is incompatible with protocol "HasPropertyProto" +    "m" is an incompatible type +      Type "(item: str, callback: (int) -> str) -> str" cannot be assigned to type "(item: T@m, callback: (T@m) -> str) -> str" +        Parameter 2: type "(T@m) -> str" cannot be assigned to type "(int) -> str" +          Type "(str) -> str" cannot be assigned to type "(int) -> str" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/protocols_merging.toml b/conformance/results/pyright/protocols_merging.toml new file mode 100644 index 000000000..3a1308853 --- /dev/null +++ b/conformance/results/pyright/protocols_merging.toml @@ -0,0 +1,16 @@ +conformant = "Pass" +output = """ +protocols_merging.py:52:25 - error: Expression of type "SCConcrete2" cannot be assigned to declared type "SizedAndClosable1" +  "SCConcrete2" is incompatible with protocol "SizedAndClosable1" +    "__len__" is not present (reportGeneralTypeIssues) +protocols_merging.py:53:25 - error: Expression of type "SCConcrete2" cannot be assigned to declared type "SizedAndClosable2" +  "SCConcrete2" is incompatible with protocol "SizedAndClosable2" +    "__len__" is not present (reportGeneralTypeIssues) +protocols_merging.py:54:25 - error: Expression of type "SCConcrete2" cannot be assigned to declared type "SizedAndClosable3" +  "SCConcrete2" is incompatible with "SizedAndClosable3" (reportGeneralTypeIssues) +protocols_merging.py:68:16 - error: Protocol class "type[BadProto]" cannot derive from non-protocol class "type[SizedAndClosable3]" +protocols_merging.py:83:5 - error: Cannot instantiate abstract class "SizedAndClosable4" +  "SizedAndClosable4.close" is abstract (reportGeneralTypeIssues) +protocols_merging.py:84:24 - error: Expression of type "SCConcrete1" cannot be assigned to declared type "SizedAndClosable4" +  "SCConcrete1" is incompatible with "SizedAndClosable4" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/protocols_modules.toml b/conformance/results/pyright/protocols_modules.toml new file mode 100644 index 000000000..caf8f7ae3 --- /dev/null +++ b/conformance/results/pyright/protocols_modules.toml @@ -0,0 +1,14 @@ +conformant = "Pass" +output = """ +protocols_modules.py:26:17 - error: Expression of type "Module("_protocols_modules1")" cannot be assigned to declared type "Options2" +  "timeout" is invariant because it is mutable +  "timeout" is an incompatible type +    "int" is incompatible with "str" (reportGeneralTypeIssues) +protocols_modules.py:48:18 - error: Expression of type "Module("_protocols_modules2")" cannot be assigned to declared type "Reporter2" +  "on_error" is an incompatible type +    Type "(x: int) -> None" cannot be assigned to type "(x: int) -> int" +      Function return type "None" is incompatible with type "int" +        "None" is incompatible with "int" (reportGeneralTypeIssues) +protocols_modules.py:49:18 - error: Expression of type "Module("_protocols_modules2")" cannot be assigned to declared type "Reporter3" +  "not_implemented" is not present (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/protocols_recursive.toml b/conformance/results/pyright/protocols_recursive.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/protocols_recursive.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/protocols_runtime_checkable.toml b/conformance/results/pyright/protocols_runtime_checkable.toml new file mode 100644 index 000000000..de9c7e8d0 --- /dev/null +++ b/conformance/results/pyright/protocols_runtime_checkable.toml @@ -0,0 +1,9 @@ +conformant = "Partial" +notes = """ +Does not reject issubclass call for data protocol. +Does not report unsafe overlap for runtime_checkable protocol. +""" +output = """ +protocols_runtime_checkable.py:23:22 - error: Second argument to "isinstance" must be a class or tuple of classes +  Protocol class must be @runtime_checkable to be used with instance and class checks (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/protocols_self.toml b/conformance/results/pyright/protocols_self.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/protocols_self.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/protocols_subtyping.toml b/conformance/results/pyright/protocols_subtyping.toml new file mode 100644 index 000000000..24f807b22 --- /dev/null +++ b/conformance/results/pyright/protocols_subtyping.toml @@ -0,0 +1,25 @@ +conformant = "Pass" +output = """ +protocols_subtyping.py:16:6 - error: Cannot instantiate protocol class "Proto1" (reportGeneralTypeIssues) +protocols_subtyping.py:38:21 - error: Expression of type "Proto2" cannot be assigned to declared type "Concrete2" +  "Proto2" is incompatible with "Concrete2" (reportGeneralTypeIssues) +protocols_subtyping.py:55:18 - error: Expression of type "Proto2" cannot be assigned to declared type "Proto3" +  "Proto2" is incompatible with protocol "Proto3" +    "method2" is not present (reportGeneralTypeIssues) +protocols_subtyping.py:79:30 - error: Expression of type "Proto5[int]" cannot be assigned to declared type "Proto4[int, float]" +  "Proto5[int]" is incompatible with protocol "Proto4[int, float]" +    Type parameter "T@Proto4" is invariant, but "int" is not the same as "float" (reportGeneralTypeIssues) +protocols_subtyping.py:80:25 - error: Expression of type "Proto4[int, int]" cannot be assigned to declared type "Proto5[float]" +  "Proto4[int, int]" is incompatible with protocol "Proto5[float]" +    Type parameter "T@Proto5" is invariant, but "int" is not the same as "float" (reportGeneralTypeIssues) +protocols_subtyping.py:102:30 - error: Expression of type "Proto6[float, float]" cannot be assigned to declared type "Proto7[int, float]" +  "Proto6[float, float]" is incompatible with protocol "Proto7[int, float]" +    "method1" is an incompatible type +      Type "(a: float) -> Sequence[float]" cannot be assigned to type "(a: T_contra@Proto7) -> Sequence[S_co@Proto7]" +        Function return type "Sequence[float]" is incompatible with type "Sequence[S_co@Proto7]" +          "Sequence[float]" is incompatible with "Sequence[S_co@Proto7]" (reportGeneralTypeIssues) +protocols_subtyping.py:103:33 - error: Expression of type "Proto6[float, float]" cannot be assigned to declared type "Proto7[float, object]" +  "Proto6[float, float]" is incompatible with protocol "Proto7[float, object]" +    Type parameter "T_contra@Proto7" is contravariant, but "float" is not a supertype of "object" +      "object" is incompatible with "float" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/protocols_variance.toml b/conformance/results/pyright/protocols_variance.toml new file mode 100644 index 000000000..383e67a2c --- /dev/null +++ b/conformance/results/pyright/protocols_variance.toml @@ -0,0 +1,16 @@ +conformant = "Partial" +notes = """ +Does not exempt "self" and "cls" parameters from variance checks. +""" +output = """ +protocols_variance.py:21:7 - warning: Type variable "T1" used in generic protocol "AnotherBox" should be covariant (reportInvalidTypeVarUse) +protocols_variance.py:40:7 - warning: Type variable "T3" used in generic protocol "Protocol2" should be contravariant (reportInvalidTypeVarUse) +protocols_variance.py:56:7 - warning: Type variable "T1" used in generic protocol "Protocol4" should be contravariant (reportInvalidTypeVarUse) +protocols_variance.py:62:22 - error: Covariant type variable cannot be used in parameter type (reportGeneralTypeIssues) +protocols_variance.py:61:7 - warning: Type variable "T1_co" used in generic protocol "Protocol5" should be contravariant (reportInvalidTypeVarUse) +protocols_variance.py:66:7 - warning: Type variable "T1" used in generic protocol "Protocol6" should be covariant (reportInvalidTypeVarUse) +protocols_variance.py:72:21 - error: Contravariant type variable cannot be used in return type (reportGeneralTypeIssues) +protocols_variance.py:71:7 - warning: Type variable "T1_contra" used in generic protocol "Protocol7" should be covariant (reportInvalidTypeVarUse) +protocols_variance.py:104:7 - warning: Type variable "T1" used in generic protocol "Protocol12" should be covariant (reportInvalidTypeVarUse) +protocols_variance.py:110:7 - warning: Type variable "T1_contra" used in generic protocol "Protocol13" should be invariant (reportInvalidTypeVarUse) +""" diff --git a/conformance/results/pyright/qualifiers_annotated.toml b/conformance/results/pyright/qualifiers_annotated.toml new file mode 100644 index 000000000..820d04b68 --- /dev/null +++ b/conformance/results/pyright/qualifiers_annotated.toml @@ -0,0 +1,23 @@ +conformant = "Partial" +notes = """ +Does not reject all invalid type expressions within Annotated. +""" +output = """ +qualifiers_annotated.py:42:17 - error: Expected type expression but received "tuple[tuple[type[int], type[str]]]" (reportGeneralTypeIssues) +qualifiers_annotated.py:43:18 - error: Expected type expression but received "Generator[type[int], None, None]" (reportGeneralTypeIssues) +qualifiers_annotated.py:44:17 - error: Expected type expression but received "dict[str, str]" (reportGeneralTypeIssues) +qualifiers_annotated.py:44:17 - error: Dictionary expression not allowed in type annotation +qualifiers_annotated.py:45:17 - error: Call expression not allowed in type expression (reportGeneralTypeIssues) +qualifiers_annotated.py:46:17 - error: List expression not allowed in type annotation +  Use List[T] to indicate a list type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +qualifiers_annotated.py:46:17 - error: Expected type expression but received "list[type[int]]" (reportGeneralTypeIssues) +qualifiers_annotated.py:47:17 - error: Ternary expression not allowed in type annotation +qualifiers_annotated.py:48:17 - error: "var1" is not defined (reportUndefinedVariable) +qualifiers_annotated.py:49:17 - error: Expected type expression but received "Literal[True]" (reportGeneralTypeIssues) +qualifiers_annotated.py:50:18 - error: Expected type expression but received "Literal[1]" (reportGeneralTypeIssues) +qualifiers_annotated.py:51:18 - error: Binary operator not allowed in type annotation +qualifiers_annotated.py:52:18 - error: Expected expression +qualifiers_annotated.py:52:18 - error: Tuple expression not allowed in type annotation +  Use Tuple[T1, ..., Tn] to indicate a tuple type or Union[T1, T2] to indicate a union type (reportGeneralTypeIssues) +qualifiers_annotated.py:62:8 - error: Expected one type argument and one or more annotations for "Annotated" +""" diff --git a/conformance/results/pyright/qualifiers_final_annotation.toml b/conformance/results/pyright/qualifiers_final_annotation.toml new file mode 100644 index 000000000..ee0ee3729 --- /dev/null +++ b/conformance/results/pyright/qualifiers_final_annotation.toml @@ -0,0 +1,38 @@ +conformant = "Partial" +notes = """ +Does not treat use of Final name as if it was replaced by the literal in NamedTuple definition. +""" +output = """ +qualifiers_final_annotation.py:18:7 - error: Expected a single type argument after "Final" +qualifiers_final_annotation.py:54:14 - error: Cannot assign member "ID5" for type "ClassA*" +  Member "ID5" cannot be assigned through a class instance because it is a ClassVar +    Member "__set__" is unknown (reportGeneralTypeIssues) +qualifiers_final_annotation.py:62:19 - error: "Final" is not allowed in this context +qualifiers_final_annotation.py:63:19 - error: "Final" is not allowed in this context +qualifiers_final_annotation.py:65:14 - error: Cannot assign member "ID7" for type "ClassA*" +  Member "ID7" cannot be assigned through a class instance because it is a ClassVar +  "ID7" is declared as Final and cannot be reassigned +    Member "__set__" is unknown (reportGeneralTypeIssues) +qualifiers_final_annotation.py:67:14 - error: Cannot assign member "ID7" for type "ClassA*" +  Member "ID7" cannot be assigned through a class instance because it is a ClassVar +  "ID7" is declared as Final and cannot be reassigned +    Member "__set__" is unknown (reportGeneralTypeIssues) +qualifiers_final_annotation.py:81:8 - error: Cannot assign member "DEFAULT_ID" for type "type[ClassB]" +  "DEFAULT_ID" is declared as Final and cannot be reassigned +    Member "__set__" is unknown (reportGeneralTypeIssues) +qualifiers_final_annotation.py:94:5 - error: "BORDER_WIDTH" cannot be redeclared because parent class "ClassC" declares it as Final +qualifiers_final_annotation.py:107:22 - error: "Final" is not allowed in this context +qualifiers_final_annotation.py:108:19 - error: "ClassVar" is not allowed in this context +qualifiers_final_annotation.py:118:9 - error: "Final" is not allowed in this context +qualifiers_final_annotation.py:121:14 - error: "Final" is not allowed in this context +qualifiers_final_annotation.py:145:5 - error: "x" is declared as Final and cannot be reassigned (reportGeneralTypeIssues) +qualifiers_final_annotation.py:147:10 - error: "x" is declared as Final and cannot be reassigned (reportGeneralTypeIssues) +qualifiers_final_annotation.py:149:9 - error: "x" is declared as Final and cannot be reassigned (reportGeneralTypeIssues) +qualifiers_final_annotation.py:152:30 - error: "x" is declared as Final and cannot be reassigned (reportGeneralTypeIssues) +qualifiers_final_annotation.py:155:9 - error: "x" is declared as Final and cannot be reassigned (reportGeneralTypeIssues) +qualifiers_final_annotation.py:141:5 - error: "ID1" is declared as Final and cannot be reassigned +qualifiers_final_annotation.py:16:1 - error: "BAD1" is declared Final, but value is not assigned +qualifiers_final_annotation.py:71:1 - error: "RATE" is declared as Final and cannot be reassigned +qualifiers_final_annotation.py:34:5 - error: "ID2" is declared Final, but value is not assigned +qualifiers_final_annotation.py:38:5 - error: "ID3" is declared Final, but value is not assigned +""" diff --git a/conformance/results/pyright/qualifiers_final_decorator.toml b/conformance/results/pyright/qualifiers_final_decorator.toml new file mode 100644 index 000000000..2cce4fc8e --- /dev/null +++ b/conformance/results/pyright/qualifiers_final_decorator.toml @@ -0,0 +1,17 @@ +conformant = "Partial" +notes = """ +Does not report override of overloaded method marked @final. +Does not report error for non-method function marked @final. +Does not report error if overload is marked @final but implementation is not. +Does not report error in stub if first overload is not marked @final but subsequent ones are. +""" +output = """ +qualifiers_final_decorator.py:8:6 - warning: Import "_qualifiers_final_decorator" could not be resolved from source (reportMissingModuleSource) +qualifiers_final_decorator.py:21:16 - error: Base class "Base1" is marked final and cannot be subclassed +qualifiers_final_decorator.py:56:9 - error: Method "method1" cannot override final method defined in class "Base2" +qualifiers_final_decorator.py:60:9 - error: Method "method2" cannot override final method defined in class "Base2" +qualifiers_final_decorator.py:64:9 - error: Method "method3" cannot override final method defined in class "Base2" +qualifiers_final_decorator.py:118:9 - error: Method "method" overrides class "Base5_2" in an incompatible manner +  Positional parameter count mismatch; base method has 2, but override has 1 (reportIncompatibleMethodOverride) +qualifiers_final_decorator.py:118:9 - error: Method "method" cannot override final method defined in class "Base5_2" +""" diff --git a/conformance/results/pyright/specialtypes_any.toml b/conformance/results/pyright/specialtypes_any.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pyright/specialtypes_any.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pyright/specialtypes_never.toml b/conformance/results/pyright/specialtypes_never.toml new file mode 100644 index 000000000..f07b88931 --- /dev/null +++ b/conformance/results/pyright/specialtypes_never.toml @@ -0,0 +1,14 @@ +conformant = "Partial" +notes = """ +Does not reject NoReturn when used outside of return type annotation. +""" +output = """ +specialtypes_never.py:19:22 - error: Function with declared return type "NoReturn" cannot return "None" (reportGeneralTypeIssues) +specialtypes_never.py:85:21 - error: Expression of type "list[Never]" cannot be assigned to declared type "list[int]" +  "list[Never]" is incompatible with "list[int]" +    Type parameter "_T@list" is invariant, but "Never" is not the same as "int" +    Consider switching from "list" to "Sequence" which is covariant (reportGeneralTypeIssues) +specialtypes_never.py:104:12 - error: Expression of type "ClassC[Never]" cannot be assigned to return type "ClassC[U@func10]" +  "ClassC[Never]" is incompatible with "ClassC[U@func10]" +    Type parameter "T@ClassC" is invariant, but "Never" is not the same as "U@func10" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/specialtypes_none.toml b/conformance/results/pyright/specialtypes_none.toml new file mode 100644 index 000000000..bbb453938 --- /dev/null +++ b/conformance/results/pyright/specialtypes_none.toml @@ -0,0 +1,10 @@ +conformant = "Pass" +output = """ +specialtypes_none.py:21:7 - error: Argument of type "type[None]" cannot be assigned to parameter "val1" of type "None" in function "func1" +  "type[type]" is incompatible with "type[None]" (reportGeneralTypeIssues) +specialtypes_none.py:27:19 - error: Expression of type "None" cannot be assigned to declared type "Iterable[Unknown]" +  "None" is incompatible with protocol "Iterable[Unknown]" +    "__iter__" is not present (reportGeneralTypeIssues) +specialtypes_none.py:41:7 - error: Argument of type "None" cannot be assigned to parameter "val1" of type "type[None]" in function "func2" +  Type "None" cannot be assigned to type "type[None]" (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/specialtypes_promotions.toml b/conformance/results/pyright/specialtypes_promotions.toml new file mode 100644 index 000000000..18006b2cd --- /dev/null +++ b/conformance/results/pyright/specialtypes_promotions.toml @@ -0,0 +1,5 @@ +conformant = "Pass" +output = """ +specialtypes_promotions.py:13:7 - error: Cannot access member "numerator" for type "float" +  Member "numerator" is unknown (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/specialtypes_tuple.toml b/conformance/results/pyright/specialtypes_tuple.toml new file mode 100644 index 000000000..43cfc580b --- /dev/null +++ b/conformance/results/pyright/specialtypes_tuple.toml @@ -0,0 +1,22 @@ +conformant = "Pass" +output = """ +specialtypes_tuple.py:12:6 - error: Expression of type "tuple[Literal[1], Literal[2]]" cannot be assigned to declared type "tuple[int]" +  "tuple[Literal[1], Literal[2]]" is incompatible with "tuple[int]" +    Tuple size mismatch; expected 1 but received 2 (reportGeneralTypeIssues) +specialtypes_tuple.py:14:6 - error: Expression of type "tuple[Literal[1]]" cannot be assigned to declared type "tuple[int, int]" +  "tuple[Literal[1]]" is incompatible with "tuple[int, int]" +    Tuple size mismatch; expected 2 but received 1 (reportGeneralTypeIssues) +specialtypes_tuple.py:15:10 - error: Expression of type "tuple[Literal[1], Literal['']]" cannot be assigned to declared type "tuple[int, int]" +  "Literal['']" is incompatible with "int" (reportGeneralTypeIssues) +specialtypes_tuple.py:25:7 - error: Expression of type "tuple[Literal[1]]" cannot be assigned to declared type "tuple[()]" +  "tuple[Literal[1]]" is incompatible with "tuple[()]" +    Tuple size mismatch; expected 0 but received 1 (reportGeneralTypeIssues) +specialtypes_tuple.py:36:17 - error: Expression of type "tuple[Literal[1], Literal[2], Literal[3], Literal['']]" cannot be assigned to declared type "tuple[int, ...]" +  "Literal['']" is incompatible with "int" (reportGeneralTypeIssues) +specialtypes_tuple.py:42:22 - error: Expression of type "tuple[int, ...]" cannot be assigned to declared type "tuple[int]" +  "tuple[int, ...]" is incompatible with "tuple[int]" +    Tuple size mismatch; expected 1 but received indeterminate (reportGeneralTypeIssues) +specialtypes_tuple.py:43:21 - error: Expression of type "tuple[int, ...]" cannot be assigned to declared type "tuple[()]" +  "tuple[int, ...]" is incompatible with "tuple[()]" +    Tuple size mismatch; expected 0 but received indeterminate (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/specialtypes_tuple_unpack.toml b/conformance/results/pyright/specialtypes_tuple_unpack.toml new file mode 100644 index 000000000..3a59fbdf4 --- /dev/null +++ b/conformance/results/pyright/specialtypes_tuple_unpack.toml @@ -0,0 +1,29 @@ +conformant = "Pass" +output = """ +specialtypes_tuple_unpack.py:7:6 - error: Expression of type "tuple[Literal[1], Literal[''], Literal['']]" cannot be assigned to declared type "tuple[int, str]" +  "tuple[Literal[1], Literal[''], Literal['']]" is incompatible with "tuple[int, str]" +    Tuple size mismatch; expected 2 but received 3 (reportGeneralTypeIssues) +specialtypes_tuple_unpack.py:12:10 - error: Expression of type "tuple[Literal[1], Literal[1], Literal['']]" cannot be assigned to declared type "tuple[int, *tuple[str, ...]]" +  "Literal[1]" is incompatible with "str" (reportGeneralTypeIssues) +specialtypes_tuple_unpack.py:13:14 - error: Expression of type "tuple[Literal[1], Literal[''], Literal[1]]" cannot be assigned to declared type "tuple[int, *tuple[str, ...]]" +  "Literal[1]" is incompatible with "str" (reportGeneralTypeIssues) +specialtypes_tuple_unpack.py:19:14 - error: Expression of type "tuple[Literal[1], Literal[''], Literal['']]" cannot be assigned to declared type "tuple[int, *tuple[str, ...], int]" +  "Literal['']" is incompatible with "int" (reportGeneralTypeIssues) +specialtypes_tuple_unpack.py:20:18 - error: Expression of type "tuple[Literal[1], Literal[''], Literal[''], float]" cannot be assigned to declared type "tuple[int, *tuple[str, ...], int]" +  "float" is incompatible with "int" (reportGeneralTypeIssues) +specialtypes_tuple_unpack.py:25:7 - error: Expression of type "tuple[Literal[1], Literal[''], Literal[1]]" cannot be assigned to declared type "tuple[*tuple[str, ...], int]" +  "Literal[1]" is incompatible with "str" (reportGeneralTypeIssues) +specialtypes_tuple_unpack.py:26:15 - error: Expression of type "tuple[Literal[''], Literal[''], float]" cannot be assigned to declared type "tuple[*tuple[str, ...], int]" +  "float" is incompatible with "int" (reportGeneralTypeIssues) +specialtypes_tuple_unpack.py:28:25 - error: Type argument list can have at most one unpacked TypeVarTuple or Tuple +specialtypes_tuple_unpack.py:29:30 - error: Type argument list can have at most one unpacked TypeVarTuple or Tuple +specialtypes_tuple_unpack.py:34:40 - error: Expression of type "tuple[str, str]" cannot be assigned to declared type "tuple[str, str, int]" +  "tuple[str, str]" is incompatible with "tuple[str, str, int]" +    Tuple size mismatch; expected 3 but received 2 (reportGeneralTypeIssues) +specialtypes_tuple_unpack.py:37:50 - error: Expression of type "tuple[str, str]" cannot be assigned to declared type "tuple[str, str, str, *tuple[str, ...]]" +  "tuple[str, str]" is incompatible with "tuple[str, str, str, *tuple[str, ...]]" +    Tuple size mismatch; expected 4 but received 2 (reportGeneralTypeIssues) +specialtypes_tuple_unpack.py:41:50 - error: Expression of type "tuple[str, str]" cannot be assigned to declared type "tuple[*tuple[str, ...], str, str, str]" +  "tuple[str, str]" is incompatible with "tuple[*tuple[str, ...], str, str, str]" +    Tuple size mismatch; expected 4 but received 2 (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/specialtypes_type.toml b/conformance/results/pyright/specialtypes_type.toml new file mode 100644 index 000000000..985cf8dc3 --- /dev/null +++ b/conformance/results/pyright/specialtypes_type.toml @@ -0,0 +1,25 @@ +conformant = "Partial" +notes = """ +Does not reject Callable when passed to type[T]. +""" +output = """ +specialtypes_type.py:56:7 - error: Argument of type "type[TeamUser]" cannot be assigned to parameter "user_class" of type "type[BasicUser] | type[ProUser]" in function "func4" +  Type "type[TeamUser]" cannot be assigned to type "type[BasicUser] | type[ProUser]" +    "type[TeamUser]" is incompatible with "type[BasicUser]" +    Type "type[TeamUser]" cannot be assigned to type "type[BasicUser]" +    "type[TeamUser]" is incompatible with "type[ProUser]" +    Type "type[TeamUser]" cannot be assigned to type "type[ProUser]" (reportGeneralTypeIssues) +specialtypes_type.py:76:22 - error: Too many type arguments provided for "type"; expected 1 but received 2 +specialtypes_type.py:117:7 - error: Cannot access member "unknown" for type "type[object]" +  Member "unknown" is unknown (reportGeneralTypeIssues) +specialtypes_type.py:120:7 - error: Cannot access member "unknown" for type "type[object]" +  Member "unknown" is unknown (reportGeneralTypeIssues) +specialtypes_type.py:143:5 - error: Cannot access member "unknown" for type "TA1" +  Member "unknown" is unknown (reportGeneralTypeIssues) +specialtypes_type.py:144:5 - error: Cannot access member "unknown" for type "TA2" +  Member "unknown" is unknown (reportGeneralTypeIssues) +specialtypes_type.py:145:5 - error: Cannot access member "unknown" for type "TA3" +  Member "unknown" is unknown (reportGeneralTypeIssues) +specialtypes_type.py:146:5 - error: Cannot access member "unknown" for type "TA4" +  Member "unknown" is unknown (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pyright/version.toml b/conformance/results/pyright/version.toml index 281a67b40..60b29ec76 100644 --- a/conformance/results/pyright/version.toml +++ b/conformance/results/pyright/version.toml @@ -1,2 +1,2 @@ -version = "pyright 1.1.343" -test_duration = 1.0995278358459473 +version = "pyright 1.1.344" +test_duration = 1.2410261631011963 diff --git a/conformance/results/pytype/annotations_coroutines.toml b/conformance/results/pytype/annotations_coroutines.toml new file mode 100644 index 000000000..7f77af071 --- /dev/null +++ b/conformance/results/pytype/annotations_coroutines.toml @@ -0,0 +1,9 @@ +conformant = "Partial" +notes = """ +Does not evaluate correct type for async function. +""" +output = """ +File "annotations_coroutines.py", line 19, in : Callable[[int], str] [assert-type] + Expected: Callable[[int], Coroutine[Any, Any, str]] + Actual: Callable[[int], str] +""" diff --git a/conformance/results/pytype/annotations_forward_refs.toml b/conformance/results/pytype/annotations_forward_refs.toml new file mode 100644 index 000000000..86a252c55 --- /dev/null +++ b/conformance/results/pytype/annotations_forward_refs.toml @@ -0,0 +1,45 @@ +conformant = "Partial" +notes = """ +Does not reject some illegal type expression forms when quoted. +Incorrectly generates error for quoted type defined in class scope. +Evaluates incorrect type for class variable annotated with quoted type expression. +""" +output = """ +File "annotations_forward_refs.py", line 22, in : Name 'ClassA' is not defined [name-error] +File "annotations_forward_refs.py", line 23, in : Name 'ClassA' is not defined [name-error] +File "annotations_forward_refs.py", line 24, in : unsupported operand type(s) for |: ''ClassA': str' and 'int: Type[int]' [unsupported-operands] + No attribute '__or__' on ''ClassA': str' or '__ror__' on 'int: Type[int]' +File "annotations_forward_refs.py", line 25, in : Missing parameter 'y' in call to function int.__or__ [missing-parameter] + Expected: (self, y) + Actually passed: (self) +File "annotations_forward_refs.py", line 40, in : invalid syntax [python-compiler-error] +File "annotations_forward_refs.py", line 40, in : Invalid type annotation '[int, str]' for p2 [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 40, in : Invalid type annotation '(int, str)' for p3 [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 40, in : Invalid type annotation '' for p4 [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 40, in : Invalid type annotation '{}' for p5 [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 40, in : Invalid type annotation '1' for p9 [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 40, in : Invalid type annotation 'True' for p10 [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 40, in : Invalid type annotation '1' for p11 [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 40, in : Invalid type annotation '-1' for p12 [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 40, in : Invalid type annotation '' for p15 [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 66, in ClassB: Name 'ClassB' is not defined [name-error] +File "annotations_forward_refs.py", line 80, in ClassD: Name 'ClassF' is not defined [name-error] +File "annotations_forward_refs.py", line 82, in ClassD: Invalid type annotation '' for str [invalid-annotation] + Must be constant +File "annotations_forward_refs.py", line 87, in ClassD: Invalid type annotation '' for x [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 89, in ClassD: Invalid type annotation '' for y [invalid-annotation] + Not a type +File "annotations_forward_refs.py", line 93, in : Any [assert-type] + Expected: int + Actual: Any +""" diff --git a/conformance/results/pytype/annotations_generators.toml b/conformance/results/pytype/annotations_generators.toml new file mode 100644 index 000000000..c08eb1d06 --- /dev/null +++ b/conformance/results/pytype/annotations_generators.toml @@ -0,0 +1,40 @@ +conformant = "Partial" +notes = """ +Does not report invalid return type for generator when function implicitly returns None. +Reports invalid error when return type of generator is annotated as a compatible protocol. +Does not report type violation in `yield from` statement. +""" +output = """ +File "annotations_generators.py", line 54, in generator2: bad return type [bad-return-type] + Expected: C + Actually returned: bool +File "annotations_generators.py", line 57, in generator2: bad return type [bad-return-type] + Expected: C + Actually returned: None +File "annotations_generators.py", line 57, in generator2: bad return type [bad-return-type] + Expected: A + Actually returned: int +File "annotations_generators.py", line 66, in generator3: bad return type [bad-return-type] + Expected: A + Actually returned: int +File "annotations_generators.py", line 75, in generator5: bad return type [bad-return-type] + Expected: A + Actually returned: B +File "annotations_generators.py", line 86, in : Bad return type 'int' for generator function generator8 [bad-yield-annotation] + Expected Generator, Iterable or Iterator +File "annotations_generators.py", line 91, in : Bad return type 'int' for async generator function generator9 [bad-yield-annotation] + Expected AsyncGenerator, AsyncIterable or AsyncIterator +File "annotations_generators.py", line 100, in : Bad return type 'IntIterator' for generator function generator15 [bad-yield-annotation] + Expected Generator, Iterable or Iterator +File "annotations_generators.py", line 109, in : Bad return type 'AsyncIntIterator' for async generator function generator16 [bad-yield-annotation] + Expected AsyncGenerator, AsyncIterable or AsyncIterator +File "annotations_generators.py", line 118, in generator18: bad return type [bad-return-type] + Expected: B + Actually returned: A +File "annotations_generators.py", line 119, in generator18: bad return type [bad-return-type] + Expected: B + Actually returned: A +File "annotations_generators.py", line 182, in : Callable[[], AsyncIterator[int]] [assert-type] + Expected: Callable[[], Coroutine[Any, Any, AsyncIterator[int]]] + Actual: Callable[[], AsyncIterator[int]] +""" diff --git a/conformance/results/pytype/annotations_methods.toml b/conformance/results/pytype/annotations_methods.toml new file mode 100644 index 000000000..8feb2032f --- /dev/null +++ b/conformance/results/pytype/annotations_methods.toml @@ -0,0 +1,9 @@ +conformant = "Pass" +notes = """ +Type evaluation differs from other type checkers because of ambiguity in the spec related to method bindings. +""" +output = """ +File "annotations_methods.py", line 42, in : B [assert-type] + Expected: A + Actual: B +""" diff --git a/conformance/results/pytype/annotations_typeexpr.toml b/conformance/results/pytype/annotations_typeexpr.toml index 6b91be0f3..6f76e031d 100644 --- a/conformance/results/pytype/annotations_typeexpr.toml +++ b/conformance/results/pytype/annotations_typeexpr.toml @@ -5,22 +5,25 @@ Does not reject call lambda expression in type annotation. Does not reject list expression in type annotation. Does not reject ternary expression in type annotation. Does not reject f-string in type annotation. +Does not reject module in type annotation. """ output = """ -File "annotations_typeexpr.py", line 76, in : Invalid type annotation '[int, str]' for p2 [invalid-annotation] +File "annotations_typeexpr.py", line 87, in : Invalid type annotation '[int, str]' for p2 [invalid-annotation] Not a type -File "annotations_typeexpr.py", line 76, in : Invalid type annotation '(int, str)' for p3 [invalid-annotation] +File "annotations_typeexpr.py", line 87, in : Invalid type annotation '(int, str)' for p3 [invalid-annotation] Not a type -File "annotations_typeexpr.py", line 76, in : Invalid type annotation '' for p4 [invalid-annotation] +File "annotations_typeexpr.py", line 87, in : Invalid type annotation '' for p4 [invalid-annotation] Not a type -File "annotations_typeexpr.py", line 76, in : Invalid type annotation '{}' for p5 [invalid-annotation] +File "annotations_typeexpr.py", line 87, in : Invalid type annotation '{}' for p5 [invalid-annotation] Not a type -File "annotations_typeexpr.py", line 76, in : Invalid type annotation '3' for p9 [invalid-annotation] +File "annotations_typeexpr.py", line 87, in : Invalid type annotation '3' for p9 [invalid-annotation] Not a type -File "annotations_typeexpr.py", line 76, in : Invalid type annotation 'True' for p10 [invalid-annotation] +File "annotations_typeexpr.py", line 87, in : Invalid type annotation 'True' for p10 [invalid-annotation] Not a type -File "annotations_typeexpr.py", line 76, in : Invalid type annotation '1' for p11 [invalid-annotation] +File "annotations_typeexpr.py", line 87, in : Invalid type annotation '1' for p11 [invalid-annotation] Not a type -File "annotations_typeexpr.py", line 76, in : Invalid type annotation '-1' for p12 [invalid-annotation] +File "annotations_typeexpr.py", line 87, in : Invalid type annotation '-1' for p12 [invalid-annotation] + Not a type +File "annotations_typeexpr.py", line 87, in : Invalid type annotation '' for p15 [invalid-annotation] Not a type """ diff --git a/conformance/results/pytype/classes_classvar.toml b/conformance/results/pytype/classes_classvar.toml new file mode 100644 index 000000000..38c3c84c1 --- /dev/null +++ b/conformance/results/pytype/classes_classvar.toml @@ -0,0 +1,45 @@ +conformant = "Partial" +notes = """ +Does not reject use of TypeVar in ClassVar. +Does not reject use of ParamSpec in ClassVar. +Does not reject use of ClassVar as a generic type argument. +Rejects initialization of ClassVar if no type argument is provided. +Does not reject use of ClassVar in parameter type annotation. +Does not reject use of ClassVar in local variable annotation. +Does not reject use of ClassVar in instance variable annotation. +Does not reject use of ClassVar in return type annotation. +Does not reject use of ClassVar in type alias definition. +Does not reject assignment of ClassVar through instance of class. +""" +output = """ +File "classes_classvar.py", line 7, in : typing.TypeVarTuple not supported yet [not-supported-yet] +File "classes_classvar.py", line 27, in : Function TypeVarTuple.__init__ expects 1 arg(s), got 2 [wrong-arg-count] + Expected: (self) + Actually passed: (self, _) +File "classes_classvar.py", line 33, in : Invalid type annotation 'Generic' [invalid-annotation] + Parameters to Generic[...] must all be type variables +File "classes_classvar.py", line 36, in ClassA: Invalid type annotation 'ClassVar[int, str]' [invalid-annotation] + ClassVar[_T] expected 1 parameter, got 2 +File "classes_classvar.py", line 37, in ClassA: class ClassVar is not indexable [not-indexable] +File "classes_classvar.py", line 38, in ClassA: Name 'var' is not defined [name-error] +File "classes_classvar.py", line 50, in ClassA: Type annotation for bad8 does not match type of assignment [annotation-type-mismatch] + Annotation: List[str] + Assignment: Dict[nothing, nothing] +File "classes_classvar.py", line 52, in ClassA: Name 'Final' is not defined [name-error] +File "classes_classvar.py", line 58, in ClassA: Type annotation for good4 does not match type of assignment [annotation-type-mismatch] + Annotation: ClassVar + Assignment: float +File "classes_classvar.py", line 66, in method2: bad return type [bad-return-type] + Expected: ClassVar[int] + Actually returned: int +File "classes_classvar.py", line 76, in : ClassVar [assert-type] + Expected: float + Actual: ClassVar +File "classes_classvar.py", line 119, in ProtoA: Type annotation for z does not match type of assignment [annotation-type-mismatch] + Annotation: ClassVar + Assignment: List[str] +File "classes_classvar.py", line 129, in : Type annotation for a does not match type of assignment [annotation-type-mismatch] + Annotation: ProtoA + Assignment: ProtoAImpl + Attributes of protocol ProtoA are not implemented on ProtoAImpl: z +""" diff --git a/conformance/results/pytype/classes_override.toml b/conformance/results/pytype/classes_override.toml new file mode 100644 index 000000000..dbce6db9f --- /dev/null +++ b/conformance/results/pytype/classes_override.toml @@ -0,0 +1,20 @@ +conformant = "Unsupported" +notes = """ +Does not yet support the @override decorator. +""" +output = """ +File "classes_override.py", line 7, in : typing.override not supported yet [not-supported-yet] + Import override from typing_extensions in Python versions before 3.12. +File "classes_override.py", line 30, in method2: bad return type [bad-return-type] + Expected: str + Actually returned: int +File "classes_override.py", line 50, in method2: bad return type [bad-return-type] + Expected: str + Actually returned: int +File "classes_override.py", line 53, in ChildA: Attribute 'method3' not found on any parent class [override-error] +File "classes_override.py", line 57, in ChildA: Attribute 'method4' not found on any parent class [override-error] +File "classes_override.py", line 66, in method4: bad return type [bad-return-type] + Expected: str + Actually returned: int +File "classes_override.py", line 103, in ChildB: Attribute 'method1' not found on any parent class [override-error] +""" diff --git a/conformance/results/pytype/directives_assert_type.toml b/conformance/results/pytype/directives_assert_type.toml new file mode 100644 index 000000000..caec764c9 --- /dev/null +++ b/conformance/results/pytype/directives_assert_type.toml @@ -0,0 +1,18 @@ +conformant = "Pass" +output = """ +File "directives_assert_type.py", line 27, in func1: Union[int, str] [assert-type] + Expected: int + Actual: Union[int, str] +File "directives_assert_type.py", line 28, in func1: Any [assert-type] + Expected: int + Actual: Any +File "directives_assert_type.py", line 31, in func1: Function assert_type expects 2 arg(s), got 0 [wrong-arg-count] + Expected: (variable, type) + Actually passed: () +File "directives_assert_type.py", line 32, in func1: str [assert-type] + Expected: int + Actual: str +File "directives_assert_type.py", line 33, in func1: Function assert_type expects 2 arg(s), got 3 [wrong-arg-count] + Expected: (variable, type) + Actually passed: (variable, type, _) +""" diff --git a/conformance/results/pytype/directives_cast.toml b/conformance/results/pytype/directives_cast.toml new file mode 100644 index 000000000..b971ffc15 --- /dev/null +++ b/conformance/results/pytype/directives_cast.toml @@ -0,0 +1,11 @@ +conformant = "Partial" +notes = """ +Does not reject a call to "cast" with additional arguments. +""" +output = """ +File "directives_cast.py", line 15, in : Missing parameter 'typ' in call to function typing.cast [missing-parameter] + Expected: (typ, val) + Actually passed: () +File "directives_cast.py", line 16, in : Invalid type annotation '1' for typing.cast [invalid-annotation] + Not a type +""" diff --git a/conformance/results/pytype/directives_no_type_check.toml b/conformance/results/pytype/directives_no_type_check.toml new file mode 100644 index 000000000..81bfe75b3 --- /dev/null +++ b/conformance/results/pytype/directives_no_type_check.toml @@ -0,0 +1,17 @@ +conformant = "Unsupported" +notes = """ +Does not honor @no_type_check decorator. +""" +output = """ +File "directives_no_type_check.py", line 16, in func1: unsupported operand type(s) for +: int and str [unsupported-operands] + Function __add__ on int expects int +File "directives_no_type_check.py", line 17, in func1: bad return type [bad-return-type] + Expected: None + Actually returned: int +File "directives_no_type_check.py", line 20, in : Function func1 was called with the wrong arguments [wrong-arg-types] + Expected: (a: int, ...) + Actually passed: (a: bytes, ...) +File "directives_no_type_check.py", line 21, in : Missing parameter 'a' in call to function func1 [missing-parameter] + Expected: (a, b) + Actually passed: () +""" diff --git a/conformance/results/pytype/directives_reveal_type.toml b/conformance/results/pytype/directives_reveal_type.toml new file mode 100644 index 000000000..814702713 --- /dev/null +++ b/conformance/results/pytype/directives_reveal_type.toml @@ -0,0 +1,12 @@ +conformant = "Partial" +notes = """ +Does not reject call to reveal_type with zero arguments. +Does not reject call to reveal_type with too many arguments. +""" +output = """ +File "directives_reveal_type.py", line 14, in func1: Union[int, str] [reveal-type] +File "directives_reveal_type.py", line 15, in func1: List[int] [reveal-type] +File "directives_reveal_type.py", line 16, in func1: Any [reveal-type] +File "directives_reveal_type.py", line 17, in func1: ForwardReference [reveal-type] +File "directives_reveal_type.py", line 20, in func1: Union[int, str] [reveal-type] +""" diff --git a/conformance/results/pytype/directives_type_checking.toml b/conformance/results/pytype/directives_type_checking.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pytype/directives_type_checking.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pytype/directives_type_ignore.toml b/conformance/results/pytype/directives_type_ignore.toml new file mode 100644 index 000000000..3e26df616 --- /dev/null +++ b/conformance/results/pytype/directives_type_ignore.toml @@ -0,0 +1,10 @@ +conformant = "Partial" +notes = """ +Does not honor "# type: ignore" comment if comment includes additional text. +""" +output = """ +File "directives_type_ignore.py", line 11: Stray type comment: ignore - additional stuff [ignored-type-comment] +File "directives_type_ignore.py", line 11, in : Type annotation for y does not match type of assignment [annotation-type-mismatch] + Annotation: int + Assignment: str +""" diff --git a/conformance/results/pytype/directives_type_ignore_file1.toml b/conformance/results/pytype/directives_type_ignore_file1.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pytype/directives_type_ignore_file1.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pytype/directives_type_ignore_file2.toml b/conformance/results/pytype/directives_type_ignore_file2.toml new file mode 100644 index 000000000..fe1d892b9 --- /dev/null +++ b/conformance/results/pytype/directives_type_ignore_file2.toml @@ -0,0 +1,6 @@ +conformant = "Partial" +notes = """ +Does not ignore `# type: ignore` if it occurs after docstrings in the file. +""" +output = """ +""" diff --git a/conformance/results/pytype/directives_version_platform.toml b/conformance/results/pytype/directives_version_platform.toml new file mode 100644 index 000000000..1128de03a --- /dev/null +++ b/conformance/results/pytype/directives_version_platform.toml @@ -0,0 +1,16 @@ +conformant = "Pass" +notes = """ +Does not understand three-element form of sys.version checks. +Does not understand os.name checks. +""" +output = """ +File "directives_version_platform.py", line 27, in : Type annotation for val3 does not match type of assignment [annotation-type-mismatch] + Annotation: int + Assignment: str +File "directives_version_platform.py", line 40, in : Type annotation for val7 does not match type of assignment [annotation-type-mismatch] + Annotation: int + Assignment: str +File "directives_version_platform.py", line 45, in : Type annotation for val8 does not match type of assignment [annotation-type-mismatch] + Annotation: int + Assignment: str +""" diff --git a/conformance/results/pytype/overloads_basic.toml b/conformance/results/pytype/overloads_basic.toml new file mode 100644 index 000000000..24e7a5708 --- /dev/null +++ b/conformance/results/pytype/overloads_basic.toml @@ -0,0 +1,16 @@ +conformant = "Partial" +notes = """ +Does not reject a function with a single @overload signature. +Does not reject a function with @overload signature but no implementation. +""" +output = """ +File "overloads_basic.py", line 31, in __getitem__: bad return type [bad-return-type] + Expected: int + Actually returned: bytes +File "overloads_basic.py", line 37, in : unsupported operand type(s) for item retrieval: Bytes and str [unsupported-operands] + Function __getitem__ on Bytes expects int +File "overloads_basic.py", line 58, in map: bad return type [bad-return-type] + Expected: Iterator + Actually returned: None + Attributes of protocol Iterator[S] are not implemented on None: __next__ +""" diff --git a/conformance/results/pytype/protocols_class_objects.toml b/conformance/results/pytype/protocols_class_objects.toml new file mode 100644 index 000000000..4d89ba6a3 --- /dev/null +++ b/conformance/results/pytype/protocols_class_objects.toml @@ -0,0 +1,8 @@ +conformant = "Partial" +notes = """ +Does not reject protocol class assigned to type[Proto]. +Incorrectly reports some class objects as incompatible with a protocol. +Fails to report some class objects as incompatible with a protocol. +""" +output = """ +""" diff --git a/conformance/results/pytype/protocols_definition.toml b/conformance/results/pytype/protocols_definition.toml new file mode 100644 index 000000000..aba256390 --- /dev/null +++ b/conformance/results/pytype/protocols_definition.toml @@ -0,0 +1,50 @@ +conformant = "Partial" +notes = """ +Reports errors for protocol static method with "..." implementation. +Does not report error when instance variable is set through "self" access in protocol class. +Does not report protocol mismatch when concrete class has attribute with covariant type and protocol attribute is mutable. +Does not reject ClassVar in concrete class when attribute in protocol is not ClassVar. +Does not reject read-only property in concrete class when attribute in protocol is mutable. +Does not reject covariant attribute type when protocol attribute is mutable. +Does not detect protocol mismatch if concrete method is missing annotations. +Does not detect protocol mismatch if concrete method's parameters are keyword-only. +Does not detect protocol mismatch if concrete method's parameters are position-only. +Does not detect protocol mismatch if concrete method is a classmethod. +Does not detect protocol mismatch if concrete method is a staticmethod. +Does not reject read-only property in concrete class when protocol has settable property. +Does not reject immutable named tuple attribute in concrete class when protocol attribute is mutable. +Does not reject immutable frozen dataclass attribute in concrete class when protocol attribute is mutable. +""" +output = """ +File "protocols_definition.py", line 30, in : Function close_all was called with the wrong arguments [wrong-arg-types] + Expected: (things: Iterable[SupportsClose]) + Actually passed: (things: List[int]) + Attributes of protocol SupportsClose are not implemented on int: close +File "protocols_definition.py", line 45, in third: bad return type [bad-return-type] + Expected: int + Actually returned: None +File "protocols_definition.py", line 115, in : Type annotation for v2_bad1 does not match type of assignment [annotation-type-mismatch] + Annotation: Template2 + Assignment: Concrete2_Bad1 + Attributes of protocol Template2 are not implemented on Concrete2_Bad1: val1 +File "protocols_definition.py", line 116, in : Type annotation for v2_bad2 does not match type of assignment [annotation-type-mismatch] + Annotation: Template2 + Assignment: Concrete2_Bad2 + Attribute val1 of protocol Template2 has wrong type in Concrete2_Bad2: expected Sequence[int], got Sequence[float] +File "protocols_definition.py", line 156, in : Type annotation for v3_bad1 does not match type of assignment [annotation-type-mismatch] + Annotation: Template3 + Assignment: Concrete3_Bad1 + Attributes of protocol Template3 are not implemented on Concrete3_Bad1: val1 +File "protocols_definition.py", line 159, in : Type annotation for v3_bad4 does not match type of assignment [annotation-type-mismatch] + Annotation: Template3 + Assignment: Concrete3_Bad4 + Attribute val1 of protocol Template3 has wrong type in Concrete3_Bad4: expected Sequence[int], got Sequence[float] +File "protocols_definition.py", line 218, in : Type annotation for v4_bad1 does not match type of assignment [annotation-type-mismatch] + Annotation: Template4 + Assignment: Concrete4_Bad1 + Attribute val1 of protocol Template4 has wrong type in Concrete4_Bad1: expected Sequence[float], got Callable[[Any], Sequence[int]] +File "protocols_definition.py", line 219, in : Type annotation for v4_bad2 does not match type of assignment [annotation-type-mismatch] + Annotation: Template4 + Assignment: Concrete4_Bad2 + Attributes of protocol Template4 are not implemented on Concrete4_Bad2: val1 +""" diff --git a/conformance/results/pytype/protocols_explicit.toml b/conformance/results/pytype/protocols_explicit.toml new file mode 100644 index 000000000..e2b080ebd --- /dev/null +++ b/conformance/results/pytype/protocols_explicit.toml @@ -0,0 +1,17 @@ +conformant = "Partial" +notes = """ +Reports errors for protocol static method with "..." implementation. +Does not report error when calling unimplemented protocol method from derived class. +Does not report type incompatibility when assigning to attribute defined in protocol. +Does not reject instantiation of class that derives from protocol but doesn't implement attribute. +Does not report instantiation of class that derives from protocol but doesn't implement method. +""" +output = """ +File "protocols_explicit.py", line 14, in draw: bad return type [bad-return-type] + Expected: str + Actually returned: None +Called from (traceback): + line 27, in draw +File "protocols_explicit.py", line 63, in : Can't instantiate Point with abstract methods intensity [not-instantiable] +File "protocols_explicit.py", line 171, in : Can't instantiate Concrete7A with abstract methods method1 [not-instantiable] +""" diff --git a/conformance/results/pytype/protocols_generic.toml b/conformance/results/pytype/protocols_generic.toml new file mode 100644 index 000000000..cfc64f916 --- /dev/null +++ b/conformance/results/pytype/protocols_generic.toml @@ -0,0 +1,36 @@ +conformant = "Partial" +notes = """ +Does not correctly enforce contravariance in protocol type compatibility tests. +Does not correctly enforce invariance in protocol type compatibility tests. +Does not detect protocol mismatch when method-scoped TypeVar is used in protocol. +""" +output = """ +File "protocols_generic.py", line 12, in : argument "covariant" to TypeVar not supported yet [not-supported-yet] +File "protocols_generic.py", line 13, in : argument "contravariant" to TypeVar not supported yet [not-supported-yet] +File "protocols_generic.py", line 40, in : Type annotation for p2 does not match type of assignment [annotation-type-mismatch] + Annotation: Proto1[int, str] + Assignment: Concrete1 + + Method __iter__ of protocol Proto1[int, str] has the wrong signature in Concrete1: + + >> Proto1[int, str] expects: + def __iter__(self) -> Iterator[T_co]: ... + + >> Concrete1 defines: + def __iter__(self) -> Iterator[int]: ... +File "protocols_generic.py", line 44, in : Invalid type annotation 'Proto2' [invalid-annotation] + Cannot inherit from Generic[...] multiple times +File "protocols_generic.py", line 56, in func1: Type annotation for v2 does not match type of assignment [annotation-type-mismatch] + Annotation: Box[int] + Assignment: Box[float] +File "protocols_generic.py", line 65, in func2: Type annotation for v1 does not match type of assignment [annotation-type-mismatch] + Annotation: Sender[int] + Assignment: Sender[float] +File "protocols_generic.py", line 75, in func3: Type annotation for v2 does not match type of assignment [annotation-type-mismatch] + Annotation: AttrProto[int] + Assignment: AttrProto[float] +File "protocols_generic.py", line 146, in : Type annotation for hp3 does not match type of assignment [annotation-type-mismatch] + Annotation: HasPropertyProto + Assignment: ConcreteHasProperty3 + Attribute f of protocol HasPropertyProto has wrong type in ConcreteHasProperty3: expected HasPropertyProto, got int +""" diff --git a/conformance/results/pytype/protocols_merging.toml b/conformance/results/pytype/protocols_merging.toml new file mode 100644 index 000000000..3b58f10d0 --- /dev/null +++ b/conformance/results/pytype/protocols_merging.toml @@ -0,0 +1,22 @@ +conformant = "Partial" +notes = """ +Does not reject a protocol class that derives from a non-protocol class. +Does not report attempt to instantiate abstract class downgraded from protocol class. +""" +output = """ +File "protocols_merging.py", line 52, in : Type annotation for s6 does not match type of assignment [annotation-type-mismatch] + Annotation: SizedAndClosable1 + Assignment: SCConcrete2 + Attributes of protocol SizedAndClosable1 are not implemented on SCConcrete2: __len__ +File "protocols_merging.py", line 53, in : Type annotation for s7 does not match type of assignment [annotation-type-mismatch] + Annotation: SizedAndClosable2 + Assignment: SCConcrete2 + Attributes of protocol SizedAndClosable2 are not implemented on SCConcrete2: __len__ +File "protocols_merging.py", line 54, in : Type annotation for s8 does not match type of assignment [annotation-type-mismatch] + Annotation: SizedAndClosable3 + Assignment: SCConcrete2 +File "protocols_merging.py", line 83, in : Can't instantiate SizedAndClosable4 with abstract methods close [not-instantiable] +File "protocols_merging.py", line 84, in : Type annotation for y does not match type of assignment [annotation-type-mismatch] + Annotation: SizedAndClosable4 + Assignment: SCConcrete1 +""" diff --git a/conformance/results/pytype/protocols_modules.toml b/conformance/results/pytype/protocols_modules.toml new file mode 100644 index 000000000..89249cd23 --- /dev/null +++ b/conformance/results/pytype/protocols_modules.toml @@ -0,0 +1,8 @@ +conformant = "Partial" +notes = """ +Does not report incompatibilities for protocol methods. +""" +output = """ +File "protocols_modules.py", line 10, in : Can't find module '_protocols_modules1'. [import-error] +File "protocols_modules.py", line 11, in : Can't find module '_protocols_modules2'. [import-error] +""" diff --git a/conformance/results/pytype/protocols_recursive.toml b/conformance/results/pytype/protocols_recursive.toml new file mode 100644 index 000000000..72190d322 --- /dev/null +++ b/conformance/results/pytype/protocols_recursive.toml @@ -0,0 +1,10 @@ +conformant = "Partial" +notes = """ +Incorrectly reports type error for some recursive protocols. +""" +output = """ +File "protocols_recursive.py", line 11, in : argument "contravariant" to TypeVar not supported yet [not-supported-yet] +File "protocols_recursive.py", line 80, in : Any [assert-type] + Expected: List[int] + Actual: Any +""" diff --git a/conformance/results/pytype/protocols_runtime_checkable.toml b/conformance/results/pytype/protocols_runtime_checkable.toml new file mode 100644 index 000000000..80197221f --- /dev/null +++ b/conformance/results/pytype/protocols_runtime_checkable.toml @@ -0,0 +1,8 @@ +conformant = "Unsupported" +notes = """ +Does not reject isinstance or issubclass call for protocol that is not runtime_checkable. +Does not reject issubclass call for data protocol. +Does not report unsafe overlap for runtime_checkable protocol. +""" +output = """ +""" diff --git a/conformance/results/pytype/protocols_self.toml b/conformance/results/pytype/protocols_self.toml new file mode 100644 index 000000000..42c875453 --- /dev/null +++ b/conformance/results/pytype/protocols_self.toml @@ -0,0 +1,29 @@ +conformant = "Partial" +notes = """ +Does not properly handle Self type within a protocol. +""" +output = """ +File "protocols_self.py", line 37, in : argument "covariant" to TypeVar not supported yet [not-supported-yet] +File "protocols_self.py", line 72, in : Type annotation for a2 does not match type of assignment [annotation-type-mismatch] + Annotation: P2Parent[str] + Assignment: C2[str] + + Method f0 of protocol P2Parent[str] has the wrong signature in C2[str]: + + >> P2Parent[str] expects: + def f0(self, right: Self, /) -> P2Parent[T1]: ... + + >> C2[str] defines: + def f0(self, other: Self) -> C2[T2_co]: ... +File "protocols_self.py", line 73, in : Type annotation for b2 does not match type of assignment [annotation-type-mismatch] + Annotation: P2Child[str] + Assignment: C2[str] + + Method f0 of protocol P2Child[str] has the wrong signature in C2[str]: + + >> P2Child[str] expects: + def f0(self, right: Self, /) -> P2Parent[T1]: ... + + >> C2[str] defines: + def f0(self, other: Self) -> C2[T2_co]: ... +""" diff --git a/conformance/results/pytype/protocols_subtyping.toml b/conformance/results/pytype/protocols_subtyping.toml new file mode 100644 index 000000000..915be92af --- /dev/null +++ b/conformance/results/pytype/protocols_subtyping.toml @@ -0,0 +1,16 @@ +conformant = "Partial" +notes = """ +Does not reject attempt to instantiate protocol class. +Does not report some protocol type compatibility violations involving contravariance. +""" +output = """ +File "protocols_subtyping.py", line 38, in func1: Type annotation for v2 does not match type of assignment [annotation-type-mismatch] + Annotation: Concrete2 + Assignment: Proto2 +File "protocols_subtyping.py", line 55, in func2: Type annotation for v2 does not match type of assignment [annotation-type-mismatch] + Annotation: Proto3 + Assignment: Proto2 + Attributes of protocol Proto3 are not implemented on Proto2: method2 +File "protocols_subtyping.py", line 83, in : argument "covariant" to TypeVar not supported yet [not-supported-yet] +File "protocols_subtyping.py", line 84, in : argument "contravariant" to TypeVar not supported yet [not-supported-yet] +""" diff --git a/conformance/results/pytype/protocols_variance.toml b/conformance/results/pytype/protocols_variance.toml new file mode 100644 index 000000000..58eec53df --- /dev/null +++ b/conformance/results/pytype/protocols_variance.toml @@ -0,0 +1,11 @@ +conformant = "Unsupported" +notes = """ +Does not detect incorrect TypeVar variance within generic protocols. +""" +output = """ +File "protocols_variance.py", line 12, in : argument "covariant" to TypeVar not supported yet [not-supported-yet] +File "protocols_variance.py", line 13, in : argument "contravariant" to TypeVar not supported yet [not-supported-yet] +File "protocols_variance.py", line 15, in : argument "covariant" to TypeVar not supported yet [not-supported-yet] +File "protocols_variance.py", line 84, in : Invalid type annotation 'Protocol' [invalid-annotation] + Parameters to Generic[...] must all be type variables +""" diff --git a/conformance/results/pytype/qualifiers_annotated.toml b/conformance/results/pytype/qualifiers_annotated.toml new file mode 100644 index 000000000..bf0534a02 --- /dev/null +++ b/conformance/results/pytype/qualifiers_annotated.toml @@ -0,0 +1,27 @@ +conformant = "Partial" +notes = """ +Does not reject some illegal type expression forms used in Annotated. +Does not allow TypeVar to be used in type alias when wrapped with Annotated. +""" +output = """ +File "qualifiers_annotated.py", line 10, in : typing.NotRequired not supported yet [not-supported-yet] +File "qualifiers_annotated.py", line 10, in : typing.Required not supported yet [not-supported-yet] +File "qualifiers_annotated.py", line 41, in : Invalid type annotation '[int, str]' for Bad1 [invalid-annotation] + Not a type +File "qualifiers_annotated.py", line 42, in : Invalid type annotation '((int, str),)' for Bad2 [invalid-annotation] + Not a type +File "qualifiers_annotated.py", line 43, in : Invalid type annotation '' for Bad3 [invalid-annotation] + Not a type +File "qualifiers_annotated.py", line 44, in : Invalid type annotation "{'a': 'b'}" for Bad4 [invalid-annotation] + Not a type +File "qualifiers_annotated.py", line 48, in : Name 'var1' is not defined [name-error] +File "qualifiers_annotated.py", line 49, in : Invalid type annotation 'True' for Bad9 [invalid-annotation] + Not a type +File "qualifiers_annotated.py", line 50, in : Invalid type annotation '1' for Bad10 [invalid-annotation] + Not a type +File "qualifiers_annotated.py", line 52, in : Invalid type annotation '' for Bad12 [invalid-annotation] + Must be constant +File "qualifiers_annotated.py", line 62, in : Invalid type annotation 'Annotated' [invalid-annotation] + typing.Annotated must have at least 1 annotation +File "qualifiers_annotated.py", line 96, in : Invalid TypeVar: TypeVar('T') must be stored as 'T', not 'TA3' [invalid-typevar] +""" diff --git a/conformance/results/pytype/qualifiers_final_annotation.toml b/conformance/results/pytype/qualifiers_final_annotation.toml new file mode 100644 index 000000000..c8996bfe2 --- /dev/null +++ b/conformance/results/pytype/qualifiers_final_annotation.toml @@ -0,0 +1,45 @@ +conformant = "Partial" +notes = """ +Does not report Final variable with missing initialization. +Does not reject Final instance variable declared outside of __init__ method. +Does not reject modification of global variable declared Final. +Does not reject modification of local variable declared Final. +""" +output = """ +File "qualifiers_final_annotation.py", line 18, in : Invalid type annotation 'Final[str, int]' [invalid-annotation] + Invalid type annotation 'Final' + typing.Final must wrap a single type +File "qualifiers_final_annotation.py", line 18, in : Invalid type annotation 'Final' [invalid-annotation] + typing.Final must wrap a single type +File "qualifiers_final_annotation.py", line 54, in __init__: Assigning to attribute ID5, which was annotated with Final [final-error] +File "qualifiers_final_annotation.py", line 65, in method1: Assigning to attribute ID7, which was annotated with Final [final-error] +File "qualifiers_final_annotation.py", line 67, in method1: Assigning to attribute ID7, which was annotated with Final [final-error] +File "qualifiers_final_annotation.py", line 71, in : Assigning to variable RATE, which was annotated with Final [final-error] +File "qualifiers_final_annotation.py", line 81, in : Assigning to attribute DEFAULT_ID, which was annotated with Final [final-error] +File "qualifiers_final_annotation.py", line 93, in : Class ClassCChild overrides final class attribute BORDER_WIDTH, defined in base class ClassC [final-error] +File "qualifiers_final_annotation.py", line 107, in ClassD: Invalid type annotation 'ClassVar[Final]' [invalid-annotation] + Invalid use of typing.Final + Final may only be used as the outermost type in assignments or variable annotations. +File "qualifiers_final_annotation.py", line 107, in ClassD: Invalid use of typing.Final [final-error] + Final may only be used as the outermost type in assignments or variable annotations. +File "qualifiers_final_annotation.py", line 107, in ClassD: Type annotation for VALUE2 does not match type of assignment [annotation-type-mismatch] + Annotation: Final + Assignment: int +File "qualifiers_final_annotation.py", line 108, in ClassD: Type annotation for VALUE3 does not match type of assignment [annotation-type-mismatch] + Annotation: ClassVar + Assignment: int +File "qualifiers_final_annotation.py", line 118, in : Invalid type annotation 'list[Final[int]]' [invalid-annotation] + Invalid use of typing.Final + Final may only be used as the outermost type in assignments or variable annotations. +File "qualifiers_final_annotation.py", line 118, in : Invalid use of typing.Final [final-error] + Final may only be used as the outermost type in assignments or variable annotations. +File "qualifiers_final_annotation.py", line 121, in : Invalid use of typing.Final [final-error] + Final may only be used as the outermost type in assignments or variable annotations. +File "qualifiers_final_annotation.py", line 134, in : Invalid keyword argument a to function N.__new__ [wrong-keyword-args] + Expected: (cls, x, y) + Actually passed: (cls, a) +File "qualifiers_final_annotation.py", line 135, in : Function N.__new__ was called with the wrong arguments [wrong-arg-types] + Expected: (cls, x: int, ...) + Actually passed: (cls, x: str, ...) +File "qualifiers_final_annotation.py", line 145, in func2: Assigning to variable x, which was annotated with Final [final-error] +""" diff --git a/conformance/results/pytype/qualifiers_final_decorator.toml b/conformance/results/pytype/qualifiers_final_decorator.toml new file mode 100644 index 000000000..852701ff5 --- /dev/null +++ b/conformance/results/pytype/qualifiers_final_decorator.toml @@ -0,0 +1,36 @@ +conformant = "Partial" +notes = """ +Does not report error for overloaded @final method defined in stub file. +Does not report error for overload that is marked @final when implementation is not. +""" +output = """ +File "qualifiers_final_decorator.py", line 8, in : Couldn't import pyi for '_qualifiers_final_decorator' [pyi-error] + File: "_qualifiers_final_decorator.pyi", line 8 + class Base3: + ^ + ParseError: Overloaded signatures for 'method' disagree on final decorators +File "qualifiers_final_decorator.py", line 21, in : Cannot subclass final class: Base1 [final-error] +File "qualifiers_final_decorator.py", line 52, in method4: bad return type [bad-return-type] + Expected: str + Actually returned: int +File "qualifiers_final_decorator.py", line 55, in : Class Derived2 overrides final method method3, defined in base class Base2 [final-error] +File "qualifiers_final_decorator.py", line 55, in : Class Derived2 overrides final method method1, defined in base class Base2 [final-error] +File "qualifiers_final_decorator.py", line 55, in : Class Derived2 overrides final method method2, defined in base class Base2 [final-error] +File "qualifiers_final_decorator.py", line 55, in : Class Derived2 overrides final method method4, defined in base class Base2 [final-error] +File "qualifiers_final_decorator.py", line 76, in method4: bad return type [bad-return-type] + Expected: str + Actually returned: int +File "qualifiers_final_decorator.py", line 90, in method: bad return type [bad-return-type] + Expected: str + Actually returned: int +File "qualifiers_final_decorator.py", line 103, in method: bad return type [bad-return-type] + Expected: str + Actually returned: int +File "qualifiers_final_decorator.py", line 117, in : Class Derived5 overrides final method method, defined in base class Base5_2 [final-error] +File "qualifiers_final_decorator.py", line 118, in Derived5: Overriding method signature mismatch [signature-mismatch] + Base signature: 'def Base5_2.method(self, v: int) -> None'. + Subclass signature: 'def Derived5.method(self) -> None'. + Not enough positional parameters in overriding method. +File "qualifiers_final_decorator.py", line 126, in : Cannot apply @final decorator to func1 [final-error] + @final can only be applied to classes and methods. +""" diff --git a/conformance/results/pytype/specialtypes_any.toml b/conformance/results/pytype/specialtypes_any.toml new file mode 100644 index 000000000..2146548eb --- /dev/null +++ b/conformance/results/pytype/specialtypes_any.toml @@ -0,0 +1,3 @@ +conformant = "Pass" +output = """ +""" diff --git a/conformance/results/pytype/specialtypes_never.toml b/conformance/results/pytype/specialtypes_never.toml new file mode 100644 index 000000000..c9b75df75 --- /dev/null +++ b/conformance/results/pytype/specialtypes_never.toml @@ -0,0 +1,22 @@ +conformant = "Unsupported" +notes = """ +Does not understand NoReturn or Never. +""" +output = """ +File "specialtypes_never.py", line 11, in : argument "covariant" to TypeVar not supported yet [not-supported-yet] +File "specialtypes_never.py", line 21, in func1: bad return type [bad-return-type] + Expected: Never + Actually returned: None +File "specialtypes_never.py", line 67, in func6: Type annotation for v1 does not match type of assignment [annotation-type-mismatch] + Annotation: int + Assignment: nothing +File "specialtypes_never.py", line 68, in func6: Type annotation for v2 does not match type of assignment [annotation-type-mismatch] + Annotation: str + Assignment: nothing +File "specialtypes_never.py", line 69, in func6: Type annotation for v3 does not match type of assignment [annotation-type-mismatch] + Annotation: List[str] + Assignment: nothing +File "specialtypes_never.py", line 85, in func8: Type annotation for v3 does not match type of assignment [annotation-type-mismatch] + Annotation: List[int] + Assignment: List[nothing] +""" diff --git a/conformance/results/pytype/specialtypes_none.toml b/conformance/results/pytype/specialtypes_none.toml new file mode 100644 index 000000000..e4e0cdbef --- /dev/null +++ b/conformance/results/pytype/specialtypes_none.toml @@ -0,0 +1,13 @@ +conformant = "Partial" +notes = """ +Does not detect type incompatibility between None and type[None]. +Does not detect type incompatibility between None and incompatible protocol. +""" +output = """ +File "specialtypes_none.py", line 21, in : Function func1 was called with the wrong arguments [wrong-arg-types] + Expected: (val1: None) + Actually passed: (val1: Type[None]) +File "specialtypes_none.py", line 41, in : Function func2 was called with the wrong arguments [wrong-arg-types] + Expected: (val1: Type[None]) + Actually passed: (val1: None) +""" diff --git a/conformance/results/pytype/specialtypes_promotions.toml b/conformance/results/pytype/specialtypes_promotions.toml new file mode 100644 index 000000000..91f796476 --- /dev/null +++ b/conformance/results/pytype/specialtypes_promotions.toml @@ -0,0 +1,4 @@ +conformant = "Pass" +output = """ +File "specialtypes_promotions.py", line 13, in func1: No attribute 'numerator' on float [attribute-error] +""" diff --git a/conformance/results/pytype/specialtypes_tuple.toml b/conformance/results/pytype/specialtypes_tuple.toml new file mode 100644 index 000000000..9ea092cae --- /dev/null +++ b/conformance/results/pytype/specialtypes_tuple.toml @@ -0,0 +1,21 @@ +conformant = "Partial" +notes = """ +Does not report type violation when assigning tuple[T, ...] to tuple[T]. +""" +output = """ +File "specialtypes_tuple.py", line 12, in : Type annotation for t1 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[int] + Assignment: Tuple[int, int] +File "specialtypes_tuple.py", line 14, in : Type annotation for t2 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[int, int] + Assignment: Tuple[int] +File "specialtypes_tuple.py", line 15, in : Type annotation for t2 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[int, int] + Assignment: Tuple[int, str] +File "specialtypes_tuple.py", line 25, in : Type annotation for t10 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[()] + Assignment: Tuple[int] +File "specialtypes_tuple.py", line 36, in : Type annotation for t20 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[int, ...] + Assignment: Tuple[int, int, int, str] +""" diff --git a/conformance/results/pytype/specialtypes_tuple_unpack.toml b/conformance/results/pytype/specialtypes_tuple_unpack.toml new file mode 100644 index 000000000..ce85f1715 --- /dev/null +++ b/conformance/results/pytype/specialtypes_tuple_unpack.toml @@ -0,0 +1,215 @@ +conformant = "Unsupported" +notes = """ +Does not support unpacked tuple in type expression. +""" +output = """ +File "specialtypes_tuple_unpack.py", line 6, in : Invalid type annotation 'tuple[int, *tuple[str]]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 6, in : Type annotation for t1 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, str] +File "specialtypes_tuple_unpack.py", line 6, in : Invalid type annotation '' [invalid-annotation] + Not a type +File "specialtypes_tuple_unpack.py", line 7, in : Type annotation for t1 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, str, str] +File "specialtypes_tuple_unpack.py", line 9, in : Invalid type annotation 'tuple[int, *tuple[str, ...]]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 9, in : Invalid type annotation '' [invalid-annotation] + Not a type +File "specialtypes_tuple_unpack.py", line 10, in : Type annotation for t2 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, str] +File "specialtypes_tuple_unpack.py", line 11, in : Type annotation for t2 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, str, str] +File "specialtypes_tuple_unpack.py", line 12, in : Type annotation for t2 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, int, str] +File "specialtypes_tuple_unpack.py", line 13, in : Type annotation for t2 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, str, int] +File "specialtypes_tuple_unpack.py", line 16, in : Invalid type annotation 'tuple[int, *tuple[str, ...], int]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 16, in : Type annotation for t3 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, int] +File "specialtypes_tuple_unpack.py", line 16, in : Invalid type annotation '' [invalid-annotation] + Not a type +File "specialtypes_tuple_unpack.py", line 17, in : Type annotation for t3 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, str, int] +File "specialtypes_tuple_unpack.py", line 18, in : Type annotation for t3 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, str, str, int] +File "specialtypes_tuple_unpack.py", line 19, in : Type annotation for t3 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, str, str] +File "specialtypes_tuple_unpack.py", line 20, in : Type annotation for t3 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, str, str, float] +File "specialtypes_tuple_unpack.py", line 22, in : Invalid type annotation 'tuple[*tuple[str, ...], int]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 22, in : Invalid type annotation '' [invalid-annotation] + Not a type +File "specialtypes_tuple_unpack.py", line 23, in : Type annotation for t4 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, int] +File "specialtypes_tuple_unpack.py", line 24, in : Type annotation for t4 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str, int] +File "specialtypes_tuple_unpack.py", line 25, in : Type annotation for t4 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[int, str, int] +File "specialtypes_tuple_unpack.py", line 26, in : Type annotation for t4 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str, float] +File "specialtypes_tuple_unpack.py", line 28, in : Function list.extend was called with the wrong arguments [wrong-arg-types] + Expected: (self, i: Iterable) + Actually passed: (self, i: Type[Tuple[int]]) + Attributes of protocol Iterable[_T2] are not implemented on type: __iter__ +File "specialtypes_tuple_unpack.py", line 28, in : Invalid type annotation '' [invalid-annotation] + Not a type +File "specialtypes_tuple_unpack.py", line 29, in : Function list.extend was called with the wrong arguments [wrong-arg-types] + Expected: (self, i: Iterable) + Actually passed: (self, i: Type[Tuple[int, ...]]) + Attributes of protocol Iterable[_T2] are not implemented on type: __iter__ +File "specialtypes_tuple_unpack.py", line 29, in : Invalid type annotation '' [invalid-annotation] + Not a type +File "specialtypes_tuple_unpack.py", line 33, in func1: Invalid type annotation 'tuple[str, str, *tuple[int, ...]]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 33, in func1: Type annotation for t1 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str] +File "specialtypes_tuple_unpack.py", line 34, in func1: Invalid type annotation 'tuple[str, str, *tuple[int]]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 34, in func1: Type annotation for t2 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str] +File "specialtypes_tuple_unpack.py", line 35, in func1: Invalid type annotation 'tuple[str, *tuple[str, ...]]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 35, in func1: Type annotation for t3 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str] +File "specialtypes_tuple_unpack.py", line 36, in func1: Invalid type annotation 'tuple[str, str, *tuple[str, ...]]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 36, in func1: Type annotation for t4 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str] +File "specialtypes_tuple_unpack.py", line 37, in func1: Invalid type annotation 'tuple[str, str, str, *tuple[str, ...]]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 37, in func1: Type annotation for t5 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str] +File "specialtypes_tuple_unpack.py", line 38, in func1: Invalid type annotation 'tuple[str, *tuple[int, ...], str]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 38, in func1: Type annotation for t6 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str] +File "specialtypes_tuple_unpack.py", line 39, in func1: Invalid type annotation 'tuple[*tuple[str, ...], str]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 39, in func1: Type annotation for t7 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str] +File "specialtypes_tuple_unpack.py", line 40, in func1: Invalid type annotation 'tuple[*tuple[str, ...], str]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 40, in func1: Type annotation for t8 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str] +File "specialtypes_tuple_unpack.py", line 41, in func1: Invalid type annotation 'tuple[*tuple[str, ...], str, str, str]' [invalid-annotation] + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type + Invalid type annotation '' + Not a type +File "specialtypes_tuple_unpack.py", line 41, in func1: Type annotation for t9 does not match type of assignment [annotation-type-mismatch] + Annotation: Tuple[Any] + Assignment: Tuple[str, str] +""" diff --git a/conformance/results/pytype/specialtypes_type.toml b/conformance/results/pytype/specialtypes_type.toml new file mode 100644 index 000000000..08e2faaa4 --- /dev/null +++ b/conformance/results/pytype/specialtypes_type.toml @@ -0,0 +1,32 @@ +conformant = "Partial" +notes = """ +Does not reject Callable when passed to type[T]. +Does not allow access to known attributes from object of type `type[Any]`. +""" +output = """ +File "specialtypes_type.py", line 56, in : Function func4 was called with the wrong arguments [wrong-arg-types] + Expected: (user_class: Type[Union[BasicUser, ProUser]]) + Actually passed: (user_class: Type[TeamUser]) +File "specialtypes_type.py", line 64, in : Invalid type annotation 'T' [invalid-annotation] + TypeVar 'T' appears only once in the function signature +File "specialtypes_type.py", line 76, in : Invalid type annotation 'type[int, str]' [invalid-annotation] + type[_T] expected 1 parameter, got 2 +File "specialtypes_type.py", line 98, in func7: Any [assert-type] + Expected: Tuple[type, ...] + Actual: Any +File "specialtypes_type.py", line 102, in func7: Any [assert-type] + Expected: Tuple[type, ...] + Actual: Any +File "specialtypes_type.py", line 106, in func7: Any [assert-type] + Expected: Tuple[type, ...] + Actual: Any +File "specialtypes_type.py", line 110, in func7: Any [assert-type] + Expected: Tuple[type, ...] + Actual: Any +File "specialtypes_type.py", line 117, in func8: No attribute 'unknown' on Type[object] [attribute-error] +File "specialtypes_type.py", line 120, in func8: No attribute 'unknown' on Type[object] [attribute-error] +File "specialtypes_type.py", line 143, in : No attribute 'unknown' on Type[type] [attribute-error] +File "specialtypes_type.py", line 144, in : No attribute 'unknown' on Type[type] [attribute-error] +File "specialtypes_type.py", line 145, in : No attribute 'unknown' on Type[type] [attribute-error] +File "specialtypes_type.py", line 146, in : No attribute 'unknown' on Type[type] [attribute-error] +""" diff --git a/conformance/results/pytype/version.toml b/conformance/results/pytype/version.toml index cdc708bb1..30f7c2a2f 100644 --- a/conformance/results/pytype/version.toml +++ b/conformance/results/pytype/version.toml @@ -1,2 +1,2 @@ version = "pytype 2023.12.18" -test_duration = 28.1415696144104 +test_duration = 26.710652112960815 diff --git a/conformance/results/results.html b/conformance/results/results.html index a3f2e4f93..2f4ca5a1d 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -127,15 +127,29 @@

Python Type System Conformance Test Results

-
mypy 1.8.0(0.40sec) +
mypy 1.8.0(0.66sec)
+ + + + + + + + + + + + + @@ -144,6 +158,17 @@

Python Type System Conformance Test Results

+ + + + + + + + + @@ -161,12 +186,30 @@

Python Type System Conformance Test Results

+ + + + + + + + + + + + + + + + @@ -184,7 +227,7 @@

Python Type System Conformance Test Results

- + @@ -197,16 +240,42 @@

Python Type System Conformance Test Results

Type narrowing + + + + + + + + + + +
Type annotations
     annotations_coroutinesPass
     annotations_forward_refsPartialDoes not report error for a forward reference that is not enclosed in quotes.
Does not report error for use of quoted type with "|" operator (runtime error).
Incorrectly generates error for quoted type defined in class scope.
     annotations_generatorsPartialDoes not report incompatible Generator type in `yield from` statement.
     annotations_methodsPassType evaluation differs from other type checkers because of ambiguity in the spec related to method bindings.
     annotations_typeexprPass
+Special types in annotations
     specialtypes_anyPass
     specialtypes_neverPartialDoes not reject NoReturn when used outside of return type annotation.
     specialtypes_nonePass
     specialtypes_promotionsPass
     specialtypes_tuplePass
     specialtypes_tuple_unpackPass
     specialtypes_typePartialDoes not treat `type` same as `type[Any]` for assert_type.
Does not allow access to unknown attributes from object of type `type[Any]`.
Generics
     generics_self_advancedPartialDoes not infer the type of an unannotated `self` parameter to be type `Self`.
Does not retain `Self` when calling method that returns `Self`.
Does not infer the type of an unannotated `cls` parameter to be type `type[Self]`.
Does not retain `Self` when accessing attribute through `type[Self]`.
     generics_self_attributesPass
     generics_self_usagePass
+Type qualifiers
     qualifiers_annotatedPartialDoes not allow ClassVar to be nested within Annotated.
Does not allow Final to be nested within Annotated.
Does not allow Required and NotRequired to be nested within Annotated.
     qualifiers_final_annotationPartialDoes not treat use of Final name as if it was replaced by the literal in NamedTuple definition.
Does not allow conditional assignment of Final instance variable in __init__ method.
Does not allow redefinition of private class variable that is marked Final in parent class.
Does not report modification of local Final variable via "for" statement.
     qualifiers_final_decoratorPass
+Class type compatibility
     classes_classvarPartialInternal error if TypeVarTuple is used in ClassVar.
Does not reject use of ParamSpec in ClassVar.
Rejects ClassVar nested in Annotated.
Does not reject use of ClassVar in TypeAlias definition.
Does not infer type of ClassVar from assignment if no type is provided.
     classes_overridePartialDoes not handle case where parent class derives from Any.
Type aliases
     aliases_explicitPartialDoes not reject specialization of type alias that has already been implicitly specialized.
     aliases_implicitPass
     literals_semanticsPass
+Protocols
     protocols_class_objectsPass
     protocols_definitionPartialRejects implicit class variable when matching protocol with explicit ClassVar.
Does not detect protocol mismatch if concrete method is missing annotations.
Does not detect protocol mismatch if concrete method's parameters are position-only.
     protocols_explicitPassDoes not report unimplemented attributes for class that explicitly derives from protocol until it is instantiated.
     protocols_genericPartialFails protocol matching when method-scoped TypeVar is used in protocol.
     protocols_mergingPass
     protocols_modulesPass
     protocols_recursivePass
     protocols_runtime_checkablePartialDoes not report unsafe overlap for runtime_checkable protocol.
     protocols_selfPass
     protocols_subtypingPass
     protocols_variancePass
Callables
     callables_annotationPass
     callables_kwargsPass
     callables_protocolPass
+Overloads
     overloads_basicPass
Dataclasses
     dataclasses_descriptorsPartialDoes not correctly evaluate type of descriptor access.
     dataclasses_frozenPass
Typed dictionaries
     typeddicts_alt_syntaxPartialDoes not support keyword-argument form of alternative syntax (deprecated in 3.11).
     typeddicts_alt_syntaxPassDoes not support keyword-argument form of alternative syntax (deprecated in 3.11).
     typeddicts_class_syntaxPass
     typeddicts_finalPass
     typeddicts_inheritancePass
     narrowing_typeguardPass
+Type checker directives
     directives_assert_typePass
     directives_castPass
     directives_no_type_checkPass
     directives_reveal_typePass
     directives_type_checkingPass
     directives_type_ignorePartialDoes not honor "# type: ignore" comment if comment includes additional text.
     directives_type_ignore_file1Pass
     directives_type_ignore_file2Pass
     directives_version_platformPassDoes not understand three-element form of sys.version checks.
Does not understand os.name checks.
-
pyright 1.1.343(1.01sec) +
pyright 1.1.344(1.24sec)
+ + + + + + + + + + + + + @@ -215,13 +284,24 @@

Python Type System Conformance Test Results

+ + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + - + + + + @@ -245,7 +343,7 @@

Python Type System Conformance Test Results

- + @@ -268,16 +366,42 @@

Python Type System Conformance Test Results

Type narrowing + + + + + + + + + + +
Type annotations
     annotations_coroutinesPass
     annotations_forward_refsPass
     annotations_generatorsPass
     annotations_methodsPassType evaluation differs from other type checkers because of ambiguity in the spec related to method bindings.
     annotations_typeexprPass
+Special types in annotations
     specialtypes_anyPass
     specialtypes_neverPartialDoes not reject NoReturn when used outside of return type annotation.
     specialtypes_nonePass
     specialtypes_promotionsPass
     specialtypes_tuplePass
     specialtypes_tuple_unpackPass
     specialtypes_typePartialDoes not reject Callable when passed to type[T].
Generics
     generics_self_advancedPass
     generics_self_attributesPass
     generics_self_usagePass
+Type qualifiers
     qualifiers_annotatedPartialDoes not reject all invalid type expressions within Annotated.
     qualifiers_final_annotationPartialDoes not treat use of Final name as if it was replaced by the literal in NamedTuple definition.
     qualifiers_final_decoratorPartialDoes not report override of overloaded method marked @final.
Does not report error for non-method function marked @final.
Does not report error if overload is marked @final but implementation is not.
Does not report error in stub if first overload is not marked @final but subsequent ones are.
+Class type compatibility
     classes_classvarPass
     classes_overridePass
Type aliases
     aliases_explicitPartialIncorrectly evaluates type of specialized type alias parameterized with ParamSpec.
Allows some illegal annotation forms to be interpreted as valid type aliases.
     aliases_implicitPartialIncorrectly evaluates type of specialized type alias parameterized with ParamSpec.
Allows some illegal annotation forms to be interpreted as valid type aliases.
     aliases_newtypePartialDoes not reject use of NewType in `isinstance` call.
Does not report inconsistency between name of NewType and assigned identifier name.
Does not reject use of NewType with TypedDict class.
Does not reject use of NewType with another NewType.
Does not reject use of NewType with Any.
     aliases_explicitPass
     aliases_implicitPass
     aliases_newtypePass
     aliases_recursivePass
     aliases_type_statementPartialDoes not reject binary expression when used in type alias definition.
     aliases_typealiastypePartialDoes not reject type alias expression that uses TypeVar that is not in scope and not in `type_params`.
Does not allow access to `__value__` attribute of type alias.
Allows some illegal annotation forms to be interpreted as valid type aliases.
     aliases_type_statementPass
     aliases_typealiastypePass
     aliases_variancePass
@@ -232,10 +312,28 @@

Python Type System Conformance Test Results

     literals_semanticsPass
+Protocols
     protocols_class_objectsPartialIncorrectly reports some class objects as incompatible with a protocol.
Fails to report some class objects as incompatible with a protocol.
     protocols_definitionPartialDoes not reject ClassVar in concrete class when attribute in protocol is not ClassVar.
     protocols_explicitPartialDoes not report error when calling unimplemented protocol method from derived class.
     protocols_genericPass
     protocols_mergingPass
     protocols_modulesPass
     protocols_recursivePass
     protocols_runtime_checkablePartialDoes not reject issubclass call for data protocol.
Does not report unsafe overlap for runtime_checkable protocol.
     protocols_selfPass
     protocols_subtypingPass
     protocols_variancePartialDoes not exempt "self" and "cls" parameters from variance checks.
Callables
     callables_annotationPass
     callables_kwargsPass
     callables_protocolPartialDoes not report type incompatibility for callback protocol with positional-only parameters.
     callables_protocolPass
+Overloads
     overloads_basicPass
Dataclasses
     dataclasses_inheritancePass
     dataclasses_kwonlyPass
     dataclasses_orderPass
     dataclasses_postinitPartialReports incorrect error for incompatible `__post_init__` method override.
     dataclasses_postinitPass
     dataclasses_slotsPass
     dataclasses_transform_classPass
     dataclasses_transform_fieldPass
     narrowing_typeguardPass
+Type checker directives
     directives_assert_typePass
     directives_castPass
     directives_no_type_checkUnsupportedIntentionally does not honor @no_type_check decorator.
     directives_reveal_typePass
     directives_type_checkingPass
     directives_type_ignorePass
     directives_type_ignore_file1Pass
     directives_type_ignore_file2Pass
     directives_version_platformPass
-
pyre 0.9.19(1.40sec) +
pyre 0.9.19(2.12sec)
+ + + + + + + + + + + + + @@ -286,6 +410,17 @@

Python Type System Conformance Test Results

+ + + + + + + + + @@ -303,12 +438,30 @@

Python Type System Conformance Test Results

+ + + + + + + + + + + + + + + + @@ -339,14 +492,40 @@

Python Type System Conformance Test Results

Type narrowing + + + + + + + + + + +
Type annotations
     annotations_coroutinesPartialDoes not evaluate correct type for async function.
     annotations_forward_refsPartialDoes not report error for a forward reference that is not enclosed in quotes.
Does not report error for use of quoted type with "|" operator (runtime error).
Does not reject f-string in quoted type annotation.
Incorrectly generates error for quoted type defined in class scope.
Does not generate error for unquoted type defined in class scope.
     annotations_generatorsPartialDoes not report invalid return type for generator when function implicitly returns None.
Incorrectly evaluates type of call to async generator.
     annotations_methodsPassType evaluation differs from other type checkers because of ambiguity in the spec related to method bindings.
     annotations_typeexprPass
+Special types in annotations
     specialtypes_anyPartialDoes not treat missing type argument as Any in generic type.
Does not support Any as a base class.
     specialtypes_neverPartialDoes not reject NoReturn when used outside of return type annotation.
Does not treat Never as compatible with all other types.
     specialtypes_nonePartialDoes not correctly handle type annotation type[None].
     specialtypes_promotionsPartialDoes not reject use of attribute that is compatible only with float.
     specialtypes_tuplePartialDoes not report type violation when assigning tuple[T, ...] to tuple[T].
     specialtypes_tuple_unpackUnsupportedDoes not support unpacked tuple in type expression.
     specialtypes_typePartialDoes not reject Callable when passed to type[T].
Does not treat `type` same as `type[Any]` for assert_type.
Does not allow access to unknown attributes from object of type `type[Any]`.
Does not reject access to unknown attributes from object of type `Type[object]`.
Reports type incompatibility between `type` and `Callable[..., Any]`.
Generics
     generics_self_advancedUnsupportedDoes not understand `Self` type.
     generics_self_attributesUnsupportedDoes not understand `Self` type.
     generics_self_usageUnsupportedDoes not understand `Self` type.
+Type qualifiers
     qualifiers_annotatedPartialDoes not reject Annotated with a single parameter.
     qualifiers_final_annotationPartialDoes not report Final variable with missing initialization in module scope.
Does not report error for invalid nesting of Final and ClassVar.
Does not treat use of Final name as if it was replaced by the literal in NamedTuple definition.
     qualifiers_final_decoratorPartialReports error for overloaded method implementation marked @final if its overloads do not.
Does not report error for overloaded @final method defined in stub file.
Reports misleading error when overload is marked @final but implementation is not.
+Class type compatibility
     classes_classvarPartialDoes not reject use of TypeVar in ClassVar.
Does not reject use of ParamSpec in ClassVar.
Does not reject use of ClassVar as a generic type argument.
Does not reject use of ClassVar in parameter type annotation.
Does not reject use of ClassVar in local variable annotation.
Does not reject use of ClassVar in instance variable annotation.
Does not reject use of ClassVar in return type annotation.
Does not reject use of ClassVar in type alias definition.
     classes_overrideUnsupportedDoes not yet support the @override decorator.
Type aliases
     aliases_explicitPartialIncorrectly reports error for type alias defined with ParamSpec.
Incorrectly rejects some valid type aliases when used in annotations.
Incorrectly evaluates generic type alias with ParamSpec with missing type argument.
Does not report some illegal annotation forms as invalid type aliases.
Does not report invalid specialization of generic type aliases.
Incorrectly rejects import alias of `TypeAlias` when used to define type alias.
Does not report invalid specialization of already-specialized generic type alias.
     aliases_implicitPartialIncorrectly reports error for type alias defined with ParamSpec.
Incorrectly rejects some valid type aliases when used in annotations.
Incorrectly evaluates generic type alias with ParamSpec with missing type argument.
Does not report invalid specialization of generic type aliases.
Does not report error for attempt to instantiate union type alias.
Does not report invalid specialization of already-specialized generic type alias.
     literals_semanticsPartialDoes not reject augmented operation that modifies literal value.
+Protocols
     protocols_class_objectsPartialDoes not reject protocol class assigned to type[Proto].
Incorrectly reports some class objects as incompatible with a protocol.
Fails to report some class objects as incompatible with a protocol.
     protocols_definitionPartialDoes not reject ClassVar in concrete class when attribute in protocol is not ClassVar.
Does not reject read-only property in concrete class when attribute in protocol is mutable.
Does not reject covariant attribute type when protocol attribute is mutable.
Does not reject read-only property in concrete class when protocol has settable property.
Does not reject immutable named tuple attribute in concrete class when protocol attribute is mutable.
Does not reject immutable frozen dataclass attribute in concrete class when protocol attribute is mutable.
     protocols_explicitPartialDoes not report error when calling unimplemented protocol method from derived class.
Does not report error when method is not implemented in derived class.
     protocols_genericPartialDoes not reject the use of Protocol and Generic together as base classes.
Does not detect protocol mismatch when method-scoped TypeVar is used in protocol.
     protocols_mergingPartialDoes not reject a protocol class that derives from a non-protocol class.
     protocols_modulesUnsupportedDoes not perform protocol checks for modules.
     protocols_recursivePass
     protocols_runtime_checkableUnsupportedDoes not reject isinstance or issubclass call for protocol that is not runtime_checkable.
Does not reject issubclass call for data protocol.
Does not report unsafe overlap for runtime_checkable protocol.
     protocols_selfPass
     protocols_subtypingPass
     protocols_varianceUnsupportedDoes not detect incorrect TypeVar variance within generic protocols.
Callables
     callables_annotationPartialDoes not evaluate correct type for `*args: int` parameter.
Does not reject illegal form `Callable[[...], int]`.
     callables_kwargsUnsupportedDoes not understand Unpack in the context of **kwargs annotation.
     callables_protocolPartialDoes not correctly handle callback protocol that declares attributes in all functions.
Does not report type incompatibility for callback protocol with positional-only parameters.
Incorrectly reports type compatibility error with callback that has *args and **kwargs.
Does not report type incompatibility for callback missing a default argument for positional parameter.
Does not report type incompatibility for callback missing a default argument for keyword parameter.
+Overloads
     overloads_basicPartialDoes not reject a function with a single @overload signature.
Dataclasses
     dataclasses_descriptorsPartialIncorrectly generates error when calling constructor of dataclass with descriptor.
     dataclasses_frozenPartialDoes not reject frozen dataclass inherited from non-frozen dataclass.
Does not reject non-frozen dataclass inherited from frozen dataclass.
     narrowing_typeguardPartialDoes not support `tuple` in `assert_type` call.
Does not reject TypeGuard method with too few parameters.
+Type checker directives
     directives_assert_typeUnsupportedDoes not understand "assert_type".
     directives_castPass
     directives_no_type_checkUnsupportedDoes not honor @no_type_check decorator.
     directives_reveal_typeUnsupportedDoes not understand reveal_type call.
     directives_type_checkingPass
     directives_type_ignorePass
     directives_type_ignore_file1UnsupportedDoes not support file-level `#type: ignore` comment.
     directives_type_ignore_file2Pass
     directives_version_platformPartialDoes not support sys.platform checks.
Does not support os.name checks.
-
pytype 2023.12.18(28.14sec) +
pytype 2023.12.18(26.71sec)
- + + + + + + + + + + + + + + @@ -357,6 +536,17 @@

Python Type System Conformance Test Results

+ + + + + + + + + @@ -374,12 +564,30 @@

Python Type System Conformance Test Results

+ + + + + + + + + + + + + + + + @@ -410,6 +618,18 @@

Python Type System Conformance Test Results

Type narrowing + + + + + + + + + + +
Type annotations
     annotations_typeexprPartialDoes not reject call expressions in type annotation.
Does not reject call lambda expression in type annotation.
Does not reject list expression in type annotation.
Does not reject ternary expression in type annotation.
Does not reject f-string in type annotation.
     annotations_coroutinesPartialDoes not evaluate correct type for async function.
     annotations_forward_refsPartialDoes not reject some illegal type expression forms when quoted.
Incorrectly generates error for quoted type defined in class scope.
Evaluates incorrect type for class variable annotated with quoted type expression.
     annotations_generatorsPartialDoes not report invalid return type for generator when function implicitly returns None.
Reports invalid error when return type of generator is annotated as a compatible protocol.
Does not report type violation in `yield from` statement.
     annotations_methodsPassType evaluation differs from other type checkers because of ambiguity in the spec related to method bindings.
     annotations_typeexprPartialDoes not reject call expressions in type annotation.
Does not reject call lambda expression in type annotation.
Does not reject list expression in type annotation.
Does not reject ternary expression in type annotation.
Does not reject f-string in type annotation.
Does not reject module in type annotation.
+Special types in annotations
     specialtypes_anyPass
     specialtypes_neverUnsupportedDoes not understand NoReturn or Never.
     specialtypes_nonePartialDoes not detect type incompatibility between None and type[None].
Does not detect type incompatibility between None and incompatible protocol.
     specialtypes_promotionsPass
     specialtypes_tuplePartialDoes not report type violation when assigning tuple[T, ...] to tuple[T].
     specialtypes_tuple_unpackUnsupportedDoes not support unpacked tuple in type expression.
     specialtypes_typePartialDoes not reject Callable when passed to type[T].
Does not allow access to known attributes from object of type `type[Any]`.
Generics
     generics_self_usageUnsupportedDoes not understand `Self` type.
+Type qualifiers
     qualifiers_annotatedPartialDoes not reject some illegal type expression forms used in Annotated.
Does not allow TypeVar to be used in type alias when wrapped with Annotated.
     qualifiers_final_annotationPartialDoes not report Final variable with missing initialization.
Does not reject Final instance variable declared outside of __init__ method.
Does not reject modification of global variable declared Final.
Does not reject modification of local variable declared Final.
     qualifiers_final_decoratorPartialDoes not report error for overloaded @final method defined in stub file.
Does not report error for overload that is marked @final when implementation is not.
+Class type compatibility
     classes_classvarPartialDoes not reject use of TypeVar in ClassVar.
Does not reject use of ParamSpec in ClassVar.
Does not reject use of ClassVar as a generic type argument.
Rejects initialization of ClassVar if no type argument is provided.
Does not reject use of ClassVar in parameter type annotation.
Does not reject use of ClassVar in local variable annotation.
Does not reject use of ClassVar in instance variable annotation.
Does not reject use of ClassVar in return type annotation.
Does not reject use of ClassVar in type alias definition.
Does not reject assignment of ClassVar through instance of class.
     classes_overrideUnsupportedDoes not yet support the @override decorator.
Type aliases
     aliases_explicitPartialIncorrectly reports error for type alias defined with ParamSpec.
Does not report invalid specialization of generic type alias with bound TypeVar.
Incorrectly evaluates generic type alias with ParamSpec with missing type argument.
Does not report some illegal annotation forms as invalid type aliases.
Does not report invalid specialization of already-specialized generic type alias.
     aliases_implicitPartialIncorrectly reports error for type alias defined with ParamSpec.
Does not report invalid specialization of generic type alias with bound TypeVar.
Incorrectly evaluates generic type alias with ParamSpec with missing type argument.
Allows some illegal annotation forms to be interpreted as valid type aliases.
Does not report invalid specialization of already-specialized generic type alias.
     literals_semanticsUnsupportedDoes not understand `Literal` type annotation.
+Protocols
     protocols_class_objectsPartialDoes not reject protocol class assigned to type[Proto].
Incorrectly reports some class objects as incompatible with a protocol.
Fails to report some class objects as incompatible with a protocol.
     protocols_definitionPartialReports errors for protocol static method with "..." implementation.
Does not report error when instance variable is set through "self" access in protocol class.
Does not report protocol mismatch when concrete class has attribute with covariant type and protocol attribute is mutable.
Does not reject ClassVar in concrete class when attribute in protocol is not ClassVar.
Does not reject read-only property in concrete class when attribute in protocol is mutable.
Does not reject covariant attribute type when protocol attribute is mutable.
Does not detect protocol mismatch if concrete method is missing annotations.
Does not detect protocol mismatch if concrete method's parameters are keyword-only.
Does not detect protocol mismatch if concrete method's parameters are position-only.
Does not detect protocol mismatch if concrete method is a classmethod.
Does not detect protocol mismatch if concrete method is a staticmethod.
Does not reject read-only property in concrete class when protocol has settable property.
Does not reject immutable named tuple attribute in concrete class when protocol attribute is mutable.
Does not reject immutable frozen dataclass attribute in concrete class when protocol attribute is mutable.
     protocols_explicitPartialReports errors for protocol static method with "..." implementation.
Does not report error when calling unimplemented protocol method from derived class.
Does not report type incompatibility when assigning to attribute defined in protocol.
Does not reject instantiation of class that derives from protocol but doesn't implement attribute.
Does not report instantiation of class that derives from protocol but doesn't implement method.
     protocols_genericPartialDoes not correctly enforce contravariance in protocol type compatibility tests.
Does not correctly enforce invariance in protocol type compatibility tests.
Does not detect protocol mismatch when method-scoped TypeVar is used in protocol.
     protocols_mergingPartialDoes not reject a protocol class that derives from a non-protocol class.
Does not report attempt to instantiate abstract class downgraded from protocol class.
     protocols_modulesPartialDoes not report incompatibilities for protocol methods.
     protocols_recursivePartialIncorrectly reports type error for some recursive protocols.
     protocols_runtime_checkableUnsupportedDoes not reject isinstance or issubclass call for protocol that is not runtime_checkable.
Does not reject issubclass call for data protocol.
Does not report unsafe overlap for runtime_checkable protocol.
     protocols_selfPartialDoes not properly handle Self type within a protocol.
     protocols_subtypingPartialDoes not reject attempt to instantiate protocol class.
Does not report some protocol type compatibility violations involving contravariance.
     protocols_varianceUnsupportedDoes not detect incorrect TypeVar variance within generic protocols.
Callables
     callables_annotationPass
     callables_kwargsUnsupportedDoes not understand Unpack in the context of **kwargs annotation.
     callables_protocolUnsupportedDoes not properly handle type compatibility checks with callback protocols.
+Overloads
     overloads_basicPartialDoes not reject a function with a single @overload signature.
Does not reject a function with @overload signature but no implementation.
Dataclasses
     dataclasses_descriptorsUnsupportedDoes not understand descriptor objects in dataclass.
     dataclasses_frozenUnsupportedDoes not report assignment to field within frozen dataclass instance.
Does not reject frozen dataclass inherited from non-frozen dataclass.
Does not reject non-frozen dataclass inherited from frozen dataclass.
     narrowing_typeguardPartialDoes not reject TypeGuard method with too few parameters.
+Type checker directives
     directives_assert_typePass
     directives_castPartialDoes not reject a call to "cast" with additional arguments.
     directives_no_type_checkUnsupportedDoes not honor @no_type_check decorator.
     directives_reveal_typePartialDoes not reject call to reveal_type with zero arguments.
Does not reject call to reveal_type with too many arguments.
     directives_type_checkingPass
     directives_type_ignorePartialDoes not honor "# type: ignore" comment if comment includes additional text.
     directives_type_ignore_file1Pass
     directives_type_ignore_file2PartialDoes not ignore `# type: ignore` if it occurs after docstrings in the file.
     directives_version_platformPassDoes not understand three-element form of sys.version checks.
Does not understand os.name checks.