diff --git a/typing_extensions/src_py2/test_typing_extensions.py b/typing_extensions/src_py2/test_typing_extensions.py index 7825e407..5c21a6df 100644 --- a/typing_extensions/src_py2/test_typing_extensions.py +++ b/typing_extensions/src_py2/test_typing_extensions.py @@ -7,7 +7,7 @@ from typing_extensions import Annotated, NoReturn, ClassVar, IntVar from typing_extensions import ContextManager, Counter, Deque, DefaultDict -from typing_extensions import NewType, overload +from typing_extensions import NewType, TypeAlias, overload from typing import Dict, List import typing import typing_extensions @@ -377,6 +377,47 @@ def test_annotated_in_other_types(self): 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): diff --git a/typing_extensions/src_py2/typing_extensions.py b/typing_extensions/src_py2/typing_extensions.py index c4c93749..62d1e46c 100644 --- a/typing_extensions/src_py2/typing_extensions.py +++ b/typing_extensions/src_py2/typing_extensions.py @@ -241,5 +241,43 @@ class Annotated(object): __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/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index 04edac27..103a0377 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -13,6 +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 try: from typing_extensions import Protocol, runtime, runtime_checkable except ImportError: @@ -1822,6 +1823,52 @@ 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) + + 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) + + if SUBCLASS_CHECK_FORBIDDEN: + 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 + + 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 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 5b4502a1..a6f4281b 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -2071,3 +2071,98 @@ def get_args(tp): res = (list(res[:-1]), res[-1]) return res return () + + +if hasattr(typing, 'TypeAlias'): + TypeAlias = typing.TypeAlias +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("{} is not subscriptable".format(self)) + +elif sys.version_info[:2] >= (3, 7): + 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.""") + +elif hasattr(typing, '_FinalTypingBase'): + 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) +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__ = ()