Merge pull request #3351 from Snowapril/fix-generic-alias

Add missing methods on `GenericAlias`
This commit is contained in:
Jim Fasarakis-Hilliard
2021-10-22 16:32:18 +03:00
committed by GitHub
30 changed files with 736 additions and 44 deletions

View File

@@ -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):

3
Lib/_weakrefset.py vendored
View File

@@ -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)

View File

@@ -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.

View File

@@ -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):

View File

@@ -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."""

View File

@@ -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:

5
Lib/dataclasses.py vendored
View File

@@ -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',

3
Lib/difflib.py vendored
View File

@@ -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.

5
Lib/functools.py vendored
View File

@@ -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)

View File

@@ -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__',

View File

@@ -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
#

View File

@@ -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)

5
Lib/queue.py vendored
View File

@@ -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

5
Lib/tempfile.py vendored
View File

@@ -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)

350
Lib/test/test_genericalias.py vendored Normal file
View File

@@ -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.<locals>.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()

View File

@@ -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__)

View File

@@ -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."""

View File

@@ -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<PyCode> {
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 {}

View File

@@ -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<Self>) -> 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 {

View File

@@ -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 {

View File

@@ -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<Self>, 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<Self>, _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<Self>, _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<PyObjectRef> = 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<Self>, 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<Self>,
other: &PyObject,
op: PyComparisonOp,
vm: &VirtualMachine,
) -> PyResult<PyComparisonValue> {
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<Self>, vm: &VirtualMachine) -> PyResult<hash::PyHash> {

View File

@@ -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 {

View File

@@ -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<PyDictRef>)> {
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<PyDictRef>)> {
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 {

View File

@@ -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 {

View File

@@ -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!("<weakref at {:#x}; dead>", id)
}
}
#[pyclassmethod(magic)]
fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
PyGenericAlias::new(cls, args, vm)
}
}
impl Hashable for PyWeak {

View File

@@ -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,));
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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]

View File

@@ -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]