diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index c800b1c51..a594fb8e9 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -9,6 +9,12 @@ Unit tests are in test_collections. from abc import ABCMeta, abstractmethod import sys +GenericAlias = type(list[int]) +EllipsisType = type(...) +def _f(): pass +FunctionType = type(_f) +del _f + __all__ = ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator", "AsyncGenerator", "Hashable", "Iterable", "Iterator", "Generator", "Reversible", @@ -110,6 +116,8 @@ class Awaitable(metaclass=ABCMeta): return _check_methods(C, "__await__") return NotImplemented + __class_getitem__ = classmethod(GenericAlias) + class Coroutine(Awaitable): @@ -169,6 +177,8 @@ class AsyncIterable(metaclass=ABCMeta): return _check_methods(C, "__aiter__") return NotImplemented + __class_getitem__ = classmethod(GenericAlias) + class AsyncIterator(AsyncIterable): @@ -255,6 +265,8 @@ class Iterable(metaclass=ABCMeta): return _check_methods(C, "__iter__") return NotImplemented + __class_getitem__ = classmethod(GenericAlias) + class Iterator(Iterable): @@ -384,6 +396,8 @@ class Container(metaclass=ABCMeta): return _check_methods(C, "__contains__") return NotImplemented + __class_getitem__ = classmethod(GenericAlias) + class Collection(Sized, Iterable, Container): __slots__ = () @@ -394,6 +408,141 @@ class Collection(Sized, Iterable, Container): return _check_methods(C, "__len__", "__iter__", "__contains__") return NotImplemented + +class _CallableGenericAlias(GenericAlias): + """ Represent `Callable[argtypes, resulttype]`. + + This sets ``__args__`` to a tuple containing the flattened ``argtypes`` + followed by ``resulttype``. + + Example: ``Callable[[int, str], float]`` sets ``__args__`` to + ``(int, str, float)``. + """ + + __slots__ = () + + def __new__(cls, origin, args): + if not (isinstance(args, tuple) and len(args) == 2): + raise TypeError( + "Callable must be used as Callable[[arg, ...], result].") + t_args, t_result = args + if isinstance(t_args, list): + args = (*t_args, t_result) + elif not _is_param_expr(t_args): + raise TypeError(f"Expected a list of types, an ellipsis, " + f"ParamSpec, or Concatenate. Got {t_args}") + return super().__new__(cls, origin, args) + + @property + def __parameters__(self): + params = [] + for arg in self.__args__: + # Looks like a genericalias + if hasattr(arg, "__parameters__") and isinstance(arg.__parameters__, tuple): + params.extend(arg.__parameters__) + else: + if _is_typevarlike(arg): + params.append(arg) + return tuple(dict.fromkeys(params)) + + def __repr__(self): + if len(self.__args__) == 2 and _is_param_expr(self.__args__[0]): + return super().__repr__() + return (f'collections.abc.Callable' + f'[[{", ".join([_type_repr(a) for a in self.__args__[:-1]])}], ' + f'{_type_repr(self.__args__[-1])}]') + + def __reduce__(self): + args = self.__args__ + if not (len(args) == 2 and _is_param_expr(args[0])): + args = list(args[:-1]), args[-1] + return _CallableGenericAlias, (Callable, args) + + def __getitem__(self, item): + # Called during TypeVar substitution, returns the custom subclass + # rather than the default types.GenericAlias object. Most of the + # code is copied from typing's _GenericAlias and the builtin + # types.GenericAlias. + + # A special case in PEP 612 where if X = Callable[P, int], + # then X[int, str] == X[[int, str]]. + param_len = len(self.__parameters__) + if param_len == 0: + raise TypeError(f'{self} is not a generic class') + if not isinstance(item, tuple): + item = (item,) + if (param_len == 1 and _is_param_expr(self.__parameters__[0]) + and item and not _is_param_expr(item[0])): + item = (list(item),) + item_len = len(item) + if item_len != param_len: + raise TypeError(f'Too {"many" if item_len > param_len else "few"}' + f' arguments for {self};' + f' actual {item_len}, expected {param_len}') + subst = dict(zip(self.__parameters__, item)) + new_args = [] + for arg in self.__args__: + if _is_typevarlike(arg): + if _is_param_expr(arg): + arg = subst[arg] + if not _is_param_expr(arg): + raise TypeError(f"Expected a list of types, an ellipsis, " + f"ParamSpec, or Concatenate. Got {arg}") + else: + arg = subst[arg] + # Looks like a GenericAlias + elif hasattr(arg, '__parameters__') and isinstance(arg.__parameters__, tuple): + subparams = arg.__parameters__ + if subparams: + subargs = tuple(subst[x] for x in subparams) + arg = arg[subargs] + new_args.append(arg) + + # args[0] occurs due to things like Z[[int, str, bool]] from PEP 612 + if not isinstance(new_args[0], list): + t_result = new_args[-1] + t_args = new_args[:-1] + new_args = (t_args, t_result) + return _CallableGenericAlias(Callable, tuple(new_args)) + + +def _is_typevarlike(arg): + obj = type(arg) + # looks like a TypeVar/ParamSpec + return (obj.__module__ == 'typing' + and obj.__name__ in {'ParamSpec', 'TypeVar'}) + +def _is_param_expr(obj): + """Checks if obj matches either a list of types, ``...``, ``ParamSpec`` or + ``_ConcatenateGenericAlias`` from typing.py + """ + if obj is Ellipsis: + return True + if isinstance(obj, list): + return True + obj = type(obj) + names = ('ParamSpec', '_ConcatenateGenericAlias') + return obj.__module__ == 'typing' and any(obj.__name__ == name for name in names) + +def _type_repr(obj): + """Return the repr() of an object, special-casing types (internal helper). + + Copied from :mod:`typing` since collections.abc + shouldn't depend on that module. + """ + if isinstance(obj, GenericAlias): + return repr(obj) + if isinstance(obj, type): + if obj.__module__ == 'builtins': + return obj.__qualname__ + return f'{obj.__module__}.{obj.__qualname__}' + if obj is Ellipsis: + return '...' + if isinstance(obj, FunctionType): + return obj.__name__ + return repr(obj) + + class Callable(metaclass=ABCMeta): __slots__ = () @@ -408,6 +557,8 @@ class Callable(metaclass=ABCMeta): return _check_methods(C, "__call__") return NotImplemented + __class_getitem__ = classmethod(_CallableGenericAlias) + ### SETS ### @@ -703,6 +854,8 @@ class MappingView(Sized): def __repr__(self): return '{0.__class__.__name__}({0._mapping!r})'.format(self) + __class_getitem__ = classmethod(GenericAlias) + class KeysView(MappingView, Set): diff --git a/Lib/_weakrefset.py b/Lib/_weakrefset.py index 7a8482362..b267780f0 100644 --- a/Lib/_weakrefset.py +++ b/Lib/_weakrefset.py @@ -3,6 +3,7 @@ # by abc.py to load everything else at startup. from _weakref import ref +from types import GenericAlias __all__ = ['WeakSet'] @@ -197,3 +198,5 @@ class WeakSet: def __repr__(self): return repr(self.data) + + __class_getitem__ = classmethod(GenericAlias) diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py index d11d28930..82c03330a 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -184,6 +184,9 @@ class Future: context['source_traceback'] = self._source_traceback self._loop.call_exception_handler(context) + def __class_getitem__(cls, type): + return cls + def cancel(self): """Cancel the future and schedule callbacks. diff --git a/Lib/asyncio/queues.py b/Lib/asyncio/queues.py index 2d38972c0..e16c46ae7 100644 --- a/Lib/asyncio/queues.py +++ b/Lib/asyncio/queues.py @@ -81,6 +81,9 @@ class Queue: def __str__(self): return '<{} {}>'.format(type(self).__name__, self._format()) + def __class_getitem__(cls, type): + return cls + def _format(self): result = 'maxsize={!r}'.format(self._maxsize) if getattr(self, '_queue', None): diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index 6001e3bdb..d32c3f07f 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -7,6 +7,7 @@ import collections import logging import threading import time +import types FIRST_COMPLETED = 'FIRST_COMPLETED' FIRST_EXCEPTION = 'FIRST_EXCEPTION' @@ -544,6 +545,8 @@ class Future(object): self._condition.notify_all() self._invoke_callbacks() + __class_getitem__ = classmethod(types.GenericAlias) + class Executor(object): """This is an abstract base class for concrete asynchronous executors.""" diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py index 9e669b219..74cacd885 100644 --- a/Lib/concurrent/futures/thread.py +++ b/Lib/concurrent/futures/thread.py @@ -10,6 +10,7 @@ from concurrent.futures import _base import itertools import queue import threading +import types import weakref import os @@ -62,6 +63,8 @@ class _WorkItem(object): else: self.future.set_result(result) + __class_getitem__ = classmethod(types.GenericAlias) + def _worker(executor_reference, work_queue, initializer, initargs): if initializer is not None: diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index c072fcf06..ddfe4db76 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -7,6 +7,7 @@ import keyword import builtins import functools import _thread +from types import GenericAlias __all__ = ['dataclass', @@ -217,6 +218,8 @@ class InitVar(metaclass=_InitVarMeta): type_name = repr(self.type) return f'dataclasses.InitVar[{type_name}]' + def __class_getitem__(cls, type): + return InitVar(type) # Instances of Field are only ever created from within this module, # and only from the field() function, although Field instances are @@ -285,6 +288,8 @@ class Field: # it. func(self.default, owner, name) + __class_getitem__ = classmethod(GenericAlias) + class _DataclassParams: __slots__ = ('init', diff --git a/Lib/difflib.py b/Lib/difflib.py index 5d756436a..0b14d3c77 100644 --- a/Lib/difflib.py +++ b/Lib/difflib.py @@ -32,6 +32,7 @@ __all__ = ['get_close_matches', 'ndiff', 'restore', 'SequenceMatcher', from heapq import nlargest as _nlargest from collections import namedtuple as _namedtuple +from types import GenericAlias Match = _namedtuple('Match', 'a b size') @@ -685,6 +686,8 @@ class SequenceMatcher: # shorter sequence return _calculate_ratio(min(la, lb), la + lb) + __class_getitem__ = classmethod(GenericAlias) + def get_close_matches(word, possibilities, n=3, cutoff=0.6): """Use SequenceMatcher to return list of the best "good enough" matches. diff --git a/Lib/functools.py b/Lib/functools.py index 59298455d..74db9969a 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -22,6 +22,7 @@ try: from _thread import RLock except ModuleNotFoundError: from _dummy_thread import RLock +from types import GenericAlias ################################################################################ @@ -427,6 +428,8 @@ class partialmethod(object): def __isabstractmethod__(self): return getattr(self.func, "__isabstractmethod__", False) + __class_getitem__ = classmethod(GenericAlias) + # Helper functions def _unwrap_partial(func): @@ -977,3 +980,5 @@ class cached_property: ) raise TypeError(msg) from None return val + + __class_getitem__ = classmethod(GenericAlias) diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py index b9ce84b2d..e298b2cba 100644 --- a/Lib/multiprocessing/managers.py +++ b/Lib/multiprocessing/managers.py @@ -18,6 +18,7 @@ import sys import threading import array import queue +import types from time import time as _time from traceback import format_exc @@ -1078,6 +1079,8 @@ class ValueProxy(BaseProxy): return self._callmethod('set', (value,)) value = property(get, set) + __class_getitem__ = classmethod(types.GenericAlias) + BaseListProxy = MakeProxyType('BaseListProxy', ( '__add__', '__contains__', '__delitem__', '__getitem__', '__len__', diff --git a/Lib/multiprocessing/pool.py b/Lib/multiprocessing/pool.py index ffdf42614..88f4c397d 100644 --- a/Lib/multiprocessing/pool.py +++ b/Lib/multiprocessing/pool.py @@ -20,6 +20,7 @@ import collections import os import time import traceback +import types # If threading is available then ThreadPool should be provided. Therefore # we avoid top-level imports which are liable to fail on some systems. @@ -616,6 +617,8 @@ class ApplyResult(object): self._event.set() del self._cache[self._job] + __class_getitem__ = classmethod(types.GenericAlias) + AsyncResult = ApplyResult # create alias -- see #17805 # diff --git a/Lib/multiprocessing/queues.py b/Lib/multiprocessing/queues.py index dda03ddf5..70e2dca06 100644 --- a/Lib/multiprocessing/queues.py +++ b/Lib/multiprocessing/queues.py @@ -14,6 +14,7 @@ import os import threading import collections import time +import types import weakref import errno @@ -353,3 +354,5 @@ class SimpleQueue(object): else: with self._wlock: self._writer.send_bytes(obj) + + __class_getitem__ = classmethod(types.GenericAlias) diff --git a/Lib/queue.py b/Lib/queue.py index 5bb0431e9..10dbcbc18 100644 --- a/Lib/queue.py +++ b/Lib/queue.py @@ -1,6 +1,7 @@ '''A multi-producer, multi-consumer queue.''' import threading +import types from collections import deque from heapq import heappush, heappop from time import monotonic as time @@ -216,6 +217,8 @@ class Queue: def _get(self): return self.queue.popleft() + __class_getitem__ = classmethod(types.GenericAlias) + class PriorityQueue(Queue): '''Variant of Queue that retrieves open entries in priority order (lowest first). @@ -316,6 +319,8 @@ class _PySimpleQueue: '''Return the approximate size of the queue (not reliable!).''' return len(self._queue) + __class_getitem__ = classmethod(types.GenericAlias) + if SimpleQueue is None: SimpleQueue = _PySimpleQueue diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 927d9ca07..7d6776f52 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -44,6 +44,7 @@ import shutil as _shutil import errno as _errno from random import Random as _Random import sys as _sys +import types as _types import weakref as _weakref try: @@ -646,6 +647,8 @@ class SpooledTemporaryFile: 'encoding': encoding, 'newline': newline, 'dir': dir, 'errors': errors} + __class_getitem__ = classmethod(_types.GenericAlias) + def _check(self, file): if self._rolled: return max_size = self._max_size @@ -833,3 +836,5 @@ class TemporaryDirectory(object): def cleanup(self): if self._finalizer.detach(): self._rmtree(self.name) + + __class_getitem__ = classmethod(_types.GenericAlias) diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py new file mode 100644 index 000000000..53b8ef944 --- /dev/null +++ b/Lib/test/test_genericalias.py @@ -0,0 +1,350 @@ +"""Tests for C-implemented GenericAlias.""" + +import unittest +import pickle +import copy +from collections import ( + defaultdict, deque, OrderedDict, Counter, UserDict, UserList +) +from collections.abc import * +from concurrent.futures import Future +from concurrent.futures.thread import _WorkItem +from contextlib import AbstractContextManager, AbstractAsyncContextManager +# XXX RUSTPYTHON TODO: from contextvars import ContextVar, Token +from dataclasses import Field +from functools import partial, partialmethod, cached_property +# XXX RUSTPYTHON TODO: from mailbox import Mailbox, _PartialFile +try: + import ctypes +except ImportError: + ctypes = None +from difflib import SequenceMatcher +# XXX RUSTPYTHON TODO: from filecmp import dircmp +# XXX RUSTPYTHON TODO: from fileinput import FileInput +from itertools import chain +from http.cookies import Morsel +from multiprocessing.managers import ValueProxy +from multiprocessing.pool import ApplyResult +try: + from multiprocessing.shared_memory import ShareableList +except ImportError: + # multiprocessing.shared_memory is not available on e.g. Android + ShareableList = None +from multiprocessing.queues import SimpleQueue as MPSimpleQueue +from os import DirEntry +from re import Pattern, Match +from types import GenericAlias, MappingProxyType, AsyncGeneratorType +from tempfile import TemporaryDirectory, SpooledTemporaryFile +from urllib.parse import SplitResult, ParseResult +from unittest.case import _AssertRaisesContext +from queue import Queue, SimpleQueue +from weakref import WeakSet, ReferenceType, ref +import typing + +from typing import TypeVar +T = TypeVar('T') +K = TypeVar('K') +V = TypeVar('V') + +class BaseTest(unittest.TestCase): + """Test basics.""" + generic_types = [type, tuple, list, dict, set, frozenset, enumerate, + defaultdict, deque, + SequenceMatcher, + # XXX RUSTPYTHON TODO: dircmp, + # XXX RUSTPYTHON TODO: FileInput, + OrderedDict, Counter, UserDict, UserList, + Pattern, Match, + partialmethod, cached_property, # XXX RUSTPYTHON TODO: partial + # XXX RUSTPYTHON TODO: AbstractContextManager, AbstractAsyncContextManager, + Awaitable, Coroutine, + AsyncIterable, AsyncIterator, + AsyncGenerator, Generator, + Iterable, Iterator, + Reversible, + Container, Collection, + # XXX RUSTPYTHON TODO: Mailbox, _PartialFile, + # XXX RUSTPYTHON TODO: ContextVar, Token, + Field, + Set, MutableSet, + Mapping, MutableMapping, MappingView, + KeysView, ItemsView, ValuesView, + Sequence, MutableSequence, + MappingProxyType, AsyncGeneratorType, + DirEntry, + chain, + TemporaryDirectory, SpooledTemporaryFile, + Queue, SimpleQueue, + _AssertRaisesContext, + SplitResult, ParseResult, + ValueProxy, ApplyResult, + WeakSet, ReferenceType, ref, + ShareableList, MPSimpleQueue, + Future, _WorkItem, + Morsel] + if ctypes is not None: + generic_types.extend((ctypes.Array, ctypes.LibraryLoader)) + + def test_subscriptable(self): + for t in self.generic_types: + if t is None: + continue + tname = t.__name__ + with self.subTest(f"Testing {tname}"): + alias = t[int] + self.assertIs(alias.__origin__, t) + self.assertEqual(alias.__args__, (int,)) + self.assertEqual(alias.__parameters__, ()) + + def test_unsubscriptable(self): + for t in int, str, float, Sized, Hashable: + tname = t.__name__ + with self.subTest(f"Testing {tname}"): + with self.assertRaises(TypeError): + t[int] + + def test_instantiate(self): + for t in tuple, list, dict, set, frozenset, defaultdict, deque: + tname = t.__name__ + with self.subTest(f"Testing {tname}"): + alias = t[int] + self.assertEqual(alias(), t()) + if t is dict: + self.assertEqual(alias(iter([('a', 1), ('b', 2)])), dict(a=1, b=2)) + self.assertEqual(alias(a=1, b=2), dict(a=1, b=2)) + elif t is defaultdict: + def default(): + return 'value' + a = alias(default) + d = defaultdict(default) + self.assertEqual(a['test'], d['test']) + else: + self.assertEqual(alias(iter((1, 2, 3))), t((1, 2, 3))) + + def test_unbound_methods(self): + t = list[int] + a = t() + t.append(a, 'foo') + self.assertEqual(a, ['foo']) + x = t.__getitem__(a, 0) + self.assertEqual(x, 'foo') + self.assertEqual(t.__len__(a), 1) + + def test_subclassing(self): + class C(list[int]): + pass + self.assertEqual(C.__bases__, (list,)) + self.assertEqual(C.__class__, type) + + def test_class_methods(self): + t = dict[int, None] + self.assertEqual(dict.fromkeys(range(2)), {0: None, 1: None}) # This works + self.assertEqual(t.fromkeys(range(2)), {0: None, 1: None}) # Should be equivalent + + def test_no_chaining(self): + t = list[int] + with self.assertRaises(TypeError): + t[int] + + def test_generic_subclass(self): + class MyList(list): + pass + t = MyList[int] + self.assertIs(t.__origin__, MyList) + self.assertEqual(t.__args__, (int,)) + self.assertEqual(t.__parameters__, ()) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_repr(self): + class MyList(list): + pass + self.assertEqual(repr(list[str]), 'list[str]') + self.assertEqual(repr(list[()]), 'list[()]') + self.assertEqual(repr(tuple[int, ...]), 'tuple[int, ...]') + self.assertTrue(repr(MyList[int]).endswith('.BaseTest.test_repr..MyList[int]')) + self.assertEqual(repr(list[str]()), '[]') # instances should keep their normal repr + + def test_exposed_type(self): + import types + a = types.GenericAlias(list, int) + self.assertEqual(str(a), 'list[int]') + self.assertIs(a.__origin__, list) + self.assertEqual(a.__args__, (int,)) + self.assertEqual(a.__parameters__, ()) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_parameters(self): + from typing import List, Dict, Callable + D0 = dict[str, int] + self.assertEqual(D0.__args__, (str, int)) + self.assertEqual(D0.__parameters__, ()) + D1a = dict[str, V] + self.assertEqual(D1a.__args__, (str, V)) + self.assertEqual(D1a.__parameters__, (V,)) + D1b = dict[K, int] + self.assertEqual(D1b.__args__, (K, int)) + self.assertEqual(D1b.__parameters__, (K,)) + D2a = dict[K, V] + self.assertEqual(D2a.__args__, (K, V)) + self.assertEqual(D2a.__parameters__, (K, V)) + D2b = dict[T, T] + self.assertEqual(D2b.__args__, (T, T)) + self.assertEqual(D2b.__parameters__, (T,)) + L0 = list[str] + self.assertEqual(L0.__args__, (str,)) + self.assertEqual(L0.__parameters__, ()) + L1 = list[T] + self.assertEqual(L1.__args__, (T,)) + self.assertEqual(L1.__parameters__, (T,)) + L2 = list[list[T]] + self.assertEqual(L2.__args__, (list[T],)) + self.assertEqual(L2.__parameters__, (T,)) + L3 = list[List[T]] + self.assertEqual(L3.__args__, (List[T],)) + self.assertEqual(L3.__parameters__, (T,)) + L4a = list[Dict[K, V]] + self.assertEqual(L4a.__args__, (Dict[K, V],)) + self.assertEqual(L4a.__parameters__, (K, V)) + L4b = list[Dict[T, int]] + self.assertEqual(L4b.__args__, (Dict[T, int],)) + self.assertEqual(L4b.__parameters__, (T,)) + L5 = list[Callable[[K, V], K]] + self.assertEqual(L5.__args__, (Callable[[K, V], K],)) + self.assertEqual(L5.__parameters__, (K, V)) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_parameter_chaining(self): + from typing import List, Dict, Union, Callable + self.assertEqual(list[T][int], list[int]) + self.assertEqual(dict[str, T][int], dict[str, int]) + self.assertEqual(dict[T, int][str], dict[str, int]) + self.assertEqual(dict[K, V][str, int], dict[str, int]) + self.assertEqual(dict[T, T][int], dict[int, int]) + + self.assertEqual(list[list[T]][int], list[list[int]]) + self.assertEqual(list[dict[T, int]][str], list[dict[str, int]]) + self.assertEqual(list[dict[str, T]][int], list[dict[str, int]]) + self.assertEqual(list[dict[K, V]][str, int], list[dict[str, int]]) + self.assertEqual(dict[T, list[int]][str], dict[str, list[int]]) + + self.assertEqual(list[List[T]][int], list[List[int]]) + self.assertEqual(list[Dict[K, V]][str, int], list[Dict[str, int]]) + self.assertEqual(list[Union[K, V]][str, int], list[Union[str, int]]) + self.assertEqual(list[Callable[[K, V], K]][str, int], + list[Callable[[str, int], str]]) + self.assertEqual(dict[T, List[int]][str], dict[str, List[int]]) + + with self.assertRaises(TypeError): + list[int][int] + dict[T, int][str, int] + dict[str, T][str, int] + dict[T, T][str, int] + + def test_equality(self): + self.assertEqual(list[int], list[int]) + self.assertEqual(dict[str, int], dict[str, int]) + self.assertNotEqual(dict[str, int], dict[str, str]) + self.assertNotEqual(list, list[int]) + self.assertNotEqual(list[int], list) + + def test_isinstance(self): + self.assertTrue(isinstance([], list)) + with self.assertRaises(TypeError): + isinstance([], list[str]) + + def test_issubclass(self): + class L(list): ... + self.assertTrue(issubclass(L, list)) + with self.assertRaises(TypeError): + issubclass(L, list[str]) + + def test_type_generic(self): + t = type[int] + Test = t('Test', (), {}) + self.assertTrue(isinstance(Test, type)) + test = Test() + self.assertEqual(t(test), Test) + self.assertEqual(t(0), int) + + def test_type_subclass_generic(self): + class MyType(type): + pass + with self.assertRaises(TypeError): + MyType[int] + + def test_pickle(self): + alias = GenericAlias(list, T) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(alias, proto) + loaded = pickle.loads(s) + self.assertEqual(loaded.__origin__, alias.__origin__) + self.assertEqual(loaded.__args__, alias.__args__) + self.assertEqual(loaded.__parameters__, alias.__parameters__) + + def test_copy(self): + class X(list): + def __copy__(self): + return self + def __deepcopy__(self, memo): + return self + + for origin in list, deque, X: + alias = GenericAlias(origin, T) + copied = copy.copy(alias) + self.assertEqual(copied.__origin__, alias.__origin__) + self.assertEqual(copied.__args__, alias.__args__) + self.assertEqual(copied.__parameters__, alias.__parameters__) + copied = copy.deepcopy(alias) + self.assertEqual(copied.__origin__, alias.__origin__) + self.assertEqual(copied.__args__, alias.__args__) + self.assertEqual(copied.__parameters__, alias.__parameters__) + + def test_union(self): + a = typing.Union[list[int], list[str]] + self.assertEqual(a.__args__, (list[int], list[str])) + self.assertEqual(a.__parameters__, ()) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_union_generic(self): + a = typing.Union[list[T], tuple[T, ...]] + self.assertEqual(a.__args__, (list[T], tuple[T, ...])) + self.assertEqual(a.__parameters__, (T,)) + + def test_dir(self): + dir_of_gen_alias = set(dir(list[int])) + self.assertTrue(dir_of_gen_alias.issuperset(dir(list))) + for generic_alias_property in ("__origin__", "__args__", "__parameters__"): + self.assertIn(generic_alias_property, dir_of_gen_alias) + + def test_weakref(self): + for t in self.generic_types: + if t is None: + continue + tname = t.__name__ + with self.subTest(f"Testing {tname}"): + alias = t[int] + self.assertEqual(ref(alias)(), alias) + + def test_no_kwargs(self): + # bpo-42576 + with self.assertRaises(TypeError): + GenericAlias(bad=float) + + def test_subclassing_types_genericalias(self): + class SubClass(GenericAlias): ... + alias = SubClass(list, int) + class Bad(GenericAlias): + def __new__(cls, *args, **kwargs): + super().__new__(cls, *args, **kwargs) + + self.assertEqual(alias, list[int]) + with self.assertRaises(TypeError): + Bad(list, int, bad=int) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 73c00e0e1..f2f651214 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1819,26 +1819,25 @@ class GenericTests(BaseTestCase): 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 + class C1(typing.Container[T]): + def __contains__(self, item): + return False 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] + # These don't work because of tuple.__class_item__ + ## 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.assertEqual(C1.__parameters__, (T,)) + self.assertIsInstance(C1(), collections.abc.Container) + self.assertIsSubclass(C1, collections.abc.Container) self.assertIsInstance(T1(), tuple) self.assertIsSubclass(T2, tuple) with self.assertRaises(TypeError): @@ -1871,22 +1870,18 @@ class GenericTests(BaseTestCase): 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]) + self.assertEqual(MyTup[int]().__orig_class__, MyTup[int]) class MyDict(typing.Dict[T, T]): ... self.assertIs(MyDict[int]().__class__, MyDict) - self.assertIs(MyDict[int]().__orig_class__, MyDict[int]) + self.assertEqual(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]) + self.assertEqual(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]) + self.assertEqual(MyChain[int]().__orig_class__, MyChain[int]) def test_all_repr_eq_any(self): objs = (getattr(typing, el) for el in typing.__all__) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index c0170d182..0c5395368 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -10,6 +10,7 @@ import warnings import collections import contextlib import traceback +import types from . import result from .util import (strclass, safe_repr, _count_diff_all_purpose, @@ -217,6 +218,8 @@ class _AssertRaisesContext(_AssertRaisesBaseContext): expected_regex.pattern, str(exc_value))) return True + __class_getitem__ = classmethod(types.GenericAlias) + class _AssertWarnsContext(_AssertRaisesBaseContext): """A context manager used to implement TestCase.assertWarns* methods.""" diff --git a/vm/src/builtins/asyncgenerator.rs b/vm/src/builtins/asyncgenerator.rs index d49e0797a..3e8c44870 100644 --- a/vm/src/builtins/asyncgenerator.rs +++ b/vm/src/builtins/asyncgenerator.rs @@ -1,4 +1,4 @@ -use super::{PyCode, PyStrRef, PyTypeRef}; +use super::{PyCode, PyGenericAlias, PyStrRef, PyTypeRef}; use crate::{ builtins::PyBaseExceptionRef, coroutine::Coro, @@ -123,6 +123,11 @@ impl PyAsyncGen { fn ag_code(&self, _vm: &VirtualMachine) -> PyRef { self.inner.frame().code.clone() } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } impl Unconstructible for PyAsyncGen {} diff --git a/vm/src/builtins/dict.rs b/vm/src/builtins/dict.rs index a7fe30dce..d760bc83b 100644 --- a/vm/src/builtins/dict.rs +++ b/vm/src/builtins/dict.rs @@ -1,6 +1,6 @@ use super::{ - set::PySetInner, IterStatus, PositionIterInternal, PyBaseExceptionRef, PySet, PyStrRef, - PyTypeRef, + set::PySetInner, IterStatus, PositionIterInternal, PyBaseExceptionRef, PyGenericAlias, PySet, + PyStrRef, PyTypeRef, }; use crate::{ builtins::PyTuple, @@ -413,6 +413,11 @@ impl PyDict { fn reversed(zelf: PyRef) -> PyDictReverseKeyIterator { PyDictReverseKeyIterator::new(zelf) } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } impl AsMapping for PyDict { diff --git a/vm/src/builtins/enumerate.rs b/vm/src/builtins/enumerate.rs index 302c5ac99..34809fa45 100644 --- a/vm/src/builtins/enumerate.rs +++ b/vm/src/builtins/enumerate.rs @@ -1,4 +1,4 @@ -use super::{IterStatus, PositionIterInternal, PyIntRef, PyTupleRef, PyTypeRef}; +use super::{IterStatus, PositionIterInternal, PyGenericAlias, PyIntRef, PyTupleRef, PyTypeRef}; use crate::common::lock::{PyMutex, PyRwLock}; use crate::{ function::{IntoPyObject, OptionalArg}, @@ -47,7 +47,12 @@ impl Constructor for PyEnumerate { } #[pyimpl(with(IterNext, Constructor), flags(BASETYPE))] -impl PyEnumerate {} +impl PyEnumerate { + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } +} impl IterNextIterable for PyEnumerate {} impl IterNext for PyEnumerate { diff --git a/vm/src/builtins/genericalias.rs b/vm/src/builtins/genericalias.rs index 903629ab5..a65b8bede 100644 --- a/vm/src/builtins/genericalias.rs +++ b/vm/src/builtins/genericalias.rs @@ -1,10 +1,10 @@ use crate::{ - builtins::{PyList, PyStr, PyStrRef, PyTuple, PyTupleRef, PyTypeRef}, + builtins::{PyList, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef}, common::hash, - function::IntoPyObject, - types::{Constructor, GetAttr, Hashable}, - IdProtocol, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, - TypeProtocol, VirtualMachine, + function::{FuncArgs, IntoPyObject}, + types::{Callable, Comparable, Constructor, GetAttr, Hashable, PyComparisonOp}, + IdProtocol, PyClassImpl, PyComparisonValue, PyContext, PyObject, PyObjectRef, PyRef, PyResult, + PyValue, TryFromObject, TypeProtocol, VirtualMachine, }; use std::fmt; @@ -52,7 +52,10 @@ impl Constructor for PyGenericAlias { } } -#[pyimpl(with(Hashable, Constructor, GetAttr), flags(BASETYPE))] +#[pyimpl( + with(Callable, Comparable, Constructor, GetAttr, Hashable), + flags(BASETYPE) +)] impl PyGenericAlias { pub fn new(origin: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> Self { let args: PyTupleRef = if let Ok(tuple) = PyTupleRef::try_from_object(vm, args.clone()) { @@ -134,9 +137,34 @@ impl PyGenericAlias { } Ok(dir) } + + #[pymethod(magic)] + fn reduce(zelf: PyRef, vm: &VirtualMachine) -> (PyTypeRef, (PyTypeRef, PyTupleRef)) { + ( + vm.ctx.types.generic_alias_type.clone(), + (zelf.origin.clone(), zelf.args.clone()), + ) + } + + #[pymethod(magic)] + fn mro_entries(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyTupleRef { + PyTuple::new_ref(vec![self.origin()], &vm.ctx) + } + + #[pymethod(magic)] + fn instancecheck(_zelf: PyRef, _obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm + .new_type_error("isinstance() argument 2 cannot be a parameterized generic".to_owned())) + } + + #[pymethod(magic)] + fn subclasscheck(_zelf: PyRef, _obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm + .new_type_error("issubclass() argument 2 cannot be a parameterized generic".to_owned())) + } } -fn is_typevar(obj: PyObjectRef) -> bool { +fn is_typevar(obj: &PyObjectRef) -> bool { let class = obj.class(); class.slot_name() == "TypeVar" && class @@ -148,7 +176,7 @@ fn is_typevar(obj: PyObjectRef) -> bool { fn make_parameters(args: &PyTupleRef, vm: &VirtualMachine) -> PyTupleRef { let mut parameters: Vec = vec![]; for arg in args.as_slice() { - if is_typevar(arg.clone()) { + if is_typevar(arg) { parameters.push(arg.clone()); } else if let Ok(tuple) = arg .clone() @@ -164,6 +192,46 @@ fn make_parameters(args: &PyTupleRef, vm: &VirtualMachine) -> PyTupleRef { PyTuple::new_ref(parameters, &vm.ctx) } +impl Callable for PyGenericAlias { + type Args = FuncArgs; + fn call(zelf: &crate::PyObjectView, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + PyType::call(&zelf.origin, args, vm).map(|obj| { + if let Err(exc) = obj.set_attr("__orig_class__", zelf.to_owned(), vm) { + if !exc.isinstance(&vm.ctx.exceptions.attribute_error) + && !exc.isinstance(&vm.ctx.exceptions.type_error) + { + return Err(exc); + } + } + Ok(obj) + })? + } +} + +impl Comparable for PyGenericAlias { + fn cmp( + zelf: &crate::PyObjectView, + other: &PyObject, + op: PyComparisonOp, + vm: &VirtualMachine, + ) -> PyResult { + op.eq_only(|| { + let other = class_or_notimplemented!(Self, other); + Ok(PyComparisonValue::Implemented( + if !zelf + .origin() + .rich_compare_bool(&other.origin(), PyComparisonOp::Eq, vm)? + { + false + } else { + zelf.args() + .rich_compare_bool(&other.args(), PyComparisonOp::Eq, vm)? + }, + )) + }) + } +} + impl Hashable for PyGenericAlias { #[inline] fn hash(zelf: &crate::PyObjectView, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/builtins/mappingproxy.rs b/vm/src/builtins/mappingproxy.rs index f0326ff79..dded8320a 100644 --- a/vm/src/builtins/mappingproxy.rs +++ b/vm/src/builtins/mappingproxy.rs @@ -1,4 +1,4 @@ -use super::{PyDict, PyList, PyStrRef, PyTuple, PyTypeRef}; +use super::{PyDict, PyGenericAlias, PyList, PyStrRef, PyTuple, PyTypeRef}; use crate::{ function::{IntoPyObject, OptionalArg}, protocol::{PyMapping, PyMappingMethods}, @@ -148,6 +148,11 @@ impl PyMappingProxy { }; Ok(format!("mappingproxy({})", vm.to_repr(&obj)?)) } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } impl AsMapping for PyMappingProxy { diff --git a/vm/src/builtins/set.rs b/vm/src/builtins/set.rs index 14966274c..c47578e88 100644 --- a/vm/src/builtins/set.rs +++ b/vm/src/builtins/set.rs @@ -1,7 +1,10 @@ /* * Builtin set type with a sequence of unique items. */ -use super::{builtins_iter, IterStatus, PositionIterInternal, PyDictRef, PyTupleRef, PyTypeRef}; +use super::{ + builtins_iter, IterStatus, PositionIterInternal, PyDictRef, PyGenericAlias, PyTupleRef, + PyTypeRef, +}; use crate::common::{ascii, hash::PyHash, lock::PyMutex, rc::PyRc}; use crate::{ dictdatatype::{self, DictSize}, @@ -619,6 +622,11 @@ impl PySet { ) -> PyResult<(PyTypeRef, PyTupleRef, Option)> { reduce_set(zelf.as_ref(), vm) } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } impl Comparable for PySet { @@ -810,6 +818,11 @@ impl PyFrozenSet { ) -> PyResult<(PyTypeRef, PyTupleRef, Option)> { reduce_set(zelf.as_ref(), vm) } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } impl Hashable for PyFrozenSet { diff --git a/vm/src/builtins/tuple.rs b/vm/src/builtins/tuple.rs index 920123001..497a26245 100644 --- a/vm/src/builtins/tuple.rs +++ b/vm/src/builtins/tuple.rs @@ -1,4 +1,4 @@ -use super::{PositionIterInternal, PyTypeRef}; +use super::{PositionIterInternal, PyGenericAlias, PyTypeRef}; use crate::common::hash::PyHash; use crate::{ function::{IntoPyObject, OptionalArg}, @@ -303,6 +303,11 @@ impl PyTuple { }; (tup_arg,) } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } impl AsMapping for PyTuple { diff --git a/vm/src/builtins/weakref.rs b/vm/src/builtins/weakref.rs index 4de8f2bc8..5b80f301c 100644 --- a/vm/src/builtins/weakref.rs +++ b/vm/src/builtins/weakref.rs @@ -1,4 +1,4 @@ -use super::PyTypeRef; +use super::{PyGenericAlias, PyTypeRef}; use crate::common::hash::PyHash; use crate::{ function::OptionalArg, @@ -83,6 +83,11 @@ impl PyWeak { format!("", id) } } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } impl Hashable for PyWeak { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index c3f423cd0..ebc52250b 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -13,8 +13,8 @@ use crate::{ bytes, getset::{IntoPyGetterFunc, IntoPySetterFunc, PyGetSet}, object, pystr, PyBaseExceptionRef, PyBoundMethod, PyDict, PyDictRef, PyEllipsis, PyFloat, - PyFrozenSet, PyInt, PyIntRef, PyList, PyListRef, PyNone, PyNotImplemented, PyStr, PyTuple, - PyTupleRef, PyType, PyTypeRef, + PyFrozenSet, PyGenericAlias, PyInt, PyIntRef, PyList, PyListRef, PyNone, PyNotImplemented, + PyStr, PyTuple, PyTupleRef, PyType, PyTypeRef, }, dictdatatype::Dict, exceptions, @@ -584,7 +584,12 @@ where match vm.get_special_method(self.to_owned(), "__getitem__")? { Ok(special_method) => return special_method.invoke((key,), vm), Err(obj) => { - if obj.isinstance(&vm.ctx.types.type_type) { + if obj.class().issubclass(&vm.ctx.types.type_type) { + if obj.is(&vm.ctx.types.type_type) { + return PyGenericAlias::new(obj.clone_class(), key.into_pyobject(vm), vm) + .into_pyresult(vm); + } + if let Some(class_getitem) = vm.get_attribute_opt(obj, "__class_getitem__")? { return vm.invoke(&class_getitem, (key,)); } diff --git a/vm/src/stdlib/collections.rs b/vm/src/stdlib/collections.rs index 6bfb7d7d8..2e47a71cf 100644 --- a/vm/src/stdlib/collections.rs +++ b/vm/src/stdlib/collections.rs @@ -2,7 +2,7 @@ pub(crate) use _collections::make_module; #[pymodule] mod _collections { - use crate::builtins::PositionIterInternal; + use crate::builtins::{PositionIterInternal, PyGenericAlias}; use crate::common::lock::{PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::{ builtins::{ @@ -529,6 +529,11 @@ mod _collections { }; Ok(vm.new_pyobj((cls, value, vm.ctx.none(), PyDequeIterator::new(zelf)))) } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } impl Comparable for PyDeque { diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index 60c4490ad..5b6be730f 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -7,7 +7,7 @@ mod decl { rc::PyRc, }; use crate::{ - builtins::{int, PyInt, PyIntRef, PyTuple, PyTupleRef, PyTypeRef}, + builtins::{int, PyGenericAlias, PyInt, PyIntRef, PyTuple, PyTupleRef, PyTypeRef}, function::{ArgCallable, FuncArgs, IntoPyObject, OptionalArg, OptionalOption, PosArgs}, protocol::{PyIter, PyIterReturn}, stdlib::sys, @@ -54,6 +54,11 @@ mod decl { } .into_ref_with_type(vm, cls) } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } impl IterNextIterable for PyItertoolsChain {} impl IterNext for PyItertoolsChain { diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index e55b3425d..2815184cf 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -429,7 +429,9 @@ pub(super) mod _os { }; use crate::common::lock::{OnceCell, PyRwLock}; use crate::{ - builtins::{PyBytesRef, PyIntRef, PyStrRef, PyTuple, PyTupleRef, PyTypeRef}, + builtins::{ + PyBytesRef, PyGenericAlias, PyIntRef, PyStrRef, PyTuple, PyTupleRef, PyTypeRef, + }, crt_fd::{Fd, Offset}, function::{ArgBytesLike, FuncArgs, IntoPyException, IntoPyObject, OptionalArg}, protocol::PyIterReturn, @@ -890,6 +892,11 @@ pub(super) mod _os { Ok(format!("<{}>", zelf.class())) } } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } #[pyattr] diff --git a/vm/src/stdlib/sre.rs b/vm/src/stdlib/sre.rs index 2006283a1..03078ff24 100644 --- a/vm/src/stdlib/sre.rs +++ b/vm/src/stdlib/sre.rs @@ -4,7 +4,8 @@ pub(crate) use _sre::make_module; mod _sre { use crate::{ builtins::{ - PyCallableIterator, PyDictRef, PyInt, PyList, PyStr, PyStrRef, PyTuple, PyTupleRef, + PyCallableIterator, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyStrRef, PyTuple, + PyTupleRef, PyTypeRef, }, common::{ascii, hash::PyHash}, function::{ArgCallable, IntoPyObject, OptionalArg, PosArgs}, @@ -513,6 +514,11 @@ mod _sre { }) }) } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } impl Hashable for Pattern { @@ -787,6 +793,11 @@ mod _sre { } Some(slice_drive(&str_drive, start as usize, end as usize, vm)) } + + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } } #[pyattr]