From 6f4858b07a6be9dbc510c9ffe4879ff2d1f20632 Mon Sep 17 00:00:00 2001 From: bobby-palmer <112954547+bobby-palmer@users.noreply.github.com> Date: Sun, 18 Jun 2023 00:47:17 -0400 Subject: [PATCH 1/2] updated _collections_abc.py and its tests in test_collections.py --- Lib/_collections_abc.py | 68 +++++++----------------------------- Lib/test/test_collections.py | 50 ++++++++++++++++---------- 2 files changed, 44 insertions(+), 74 deletions(-) diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 87a9cd2d46..e96e4c3535 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -430,25 +430,13 @@ def __new__(cls, origin, args): raise TypeError( "Callable must be used as Callable[[arg, ...], result].") t_args, t_result = args - if isinstance(t_args, list): + if isinstance(t_args, (tuple, 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__() @@ -468,54 +456,24 @@ def __getitem__(self, item): # 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]) + # A special case in PEP 612 where if X = Callable[P, int], + # then X[int, str] == X[[int, str]]. + if (len(self.__parameters__) == 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) + item = (item,) + + new_args = super().__getitem__(item).__args__ # args[0] occurs due to things like Z[[int, str, bool]] from PEP 612 - if not isinstance(new_args[0], list): + if not isinstance(new_args[0], (tuple, 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 @@ -868,7 +826,7 @@ class KeysView(MappingView, Set): __slots__ = () @classmethod - def _from_iterable(self, it): + def _from_iterable(cls, it): return set(it) def __contains__(self, key): @@ -886,7 +844,7 @@ class ItemsView(MappingView, Set): __slots__ = () @classmethod - def _from_iterable(self, it): + def _from_iterable(cls, it): return set(it) def __contains__(self, item): @@ -1064,10 +1022,10 @@ def index(self, value, start=0, stop=None): while stop is None or i < stop: try: v = self[i] - if v is value or v == value: - return i except IndexError: break + if v is value or v == value: + return i i += 1 raise ValueError diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index c8f6880d09..68ca288fb1 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -685,14 +685,16 @@ def test_field_descriptor(self): self.assertRaises(AttributeError, Point.x.__set__, p, 33) self.assertRaises(AttributeError, Point.x.__delete__, p) - class NewPoint(tuple): - x = pickle.loads(pickle.dumps(Point.x)) - y = pickle.loads(pickle.dumps(Point.y)) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + class NewPoint(tuple): + x = pickle.loads(pickle.dumps(Point.x, proto)) + y = pickle.loads(pickle.dumps(Point.y, proto)) - np = NewPoint([1, 2]) + np = NewPoint([1, 2]) - self.assertEqual(np.x, 1) - self.assertEqual(np.y, 2) + self.assertEqual(np.x, 1) + self.assertEqual(np.y, 2) # TODO: RUSTPYTHON @unittest.expectedFailure @@ -706,6 +708,18 @@ def test_match_args(self): Point = namedtuple('Point', 'x y') self.assertEqual(Point.__match_args__, ('x', 'y')) + def test_non_generic_subscript(self): + # For backward compatibility, subscription works + # on arbitrary named tuple types. + Group = collections.namedtuple('Group', 'key group') + A = Group[int, list[int]] + self.assertEqual(A.__origin__, Group) + self.assertEqual(A.__parameters__, ()) + self.assertEqual(A.__args__, (int, list[int])) + a = A(1, [2]) + self.assertIs(type(a), Group) + self.assertEqual(a, (1, [2])) + ################################################################################ ### Abstract Base Classes @@ -800,6 +814,8 @@ def throw(self, typ, val=None, tb=None): def __await__(self): yield + self.validate_abstract_methods(Awaitable, '__await__') + non_samples = [None, int(), gen(), object()] for x in non_samples: self.assertNotIsInstance(x, Awaitable) @@ -852,6 +868,8 @@ def throw(self, typ, val=None, tb=None): def __await__(self): yield + self.validate_abstract_methods(Coroutine, '__await__', 'send', 'throw') + non_samples = [None, int(), gen(), object(), Bar()] for x in non_samples: self.assertNotIsInstance(x, Coroutine) @@ -1597,6 +1615,7 @@ def __len__(self): containers = [ seq, ItemsView({1: nan, 2: obj}), + KeysView({1: nan, 2: obj}), ValuesView({1: nan, 2: obj}) ] for container in containers: @@ -1866,6 +1885,8 @@ def test_MutableMapping_subclass(self): mymap['red'] = 5 self.assertIsInstance(mymap.keys(), Set) self.assertIsInstance(mymap.keys(), KeysView) + self.assertIsInstance(mymap.values(), Collection) + self.assertIsInstance(mymap.values(), ValuesView) self.assertIsInstance(mymap.items(), Set) self.assertIsInstance(mymap.items(), ItemsView) @@ -2381,19 +2402,10 @@ def test_gt(self): self.assertFalse(Counter(a=2, b=1, c=0) > Counter('aab')) -################################################################################ -### Run tests -################################################################################ - -def test_main(verbose=None): - NamedTupleDocs = doctest.DocTestSuite(module=collections) - test_classes = [TestNamedTuple, NamedTupleDocs, TestOneTrickPonyABCs, - TestCollectionABCs, TestCounter, TestChainMap, - TestUserObjects, - ] - support.run_unittest(*test_classes) - support.run_doctest(collections, verbose) +def load_tests(loader, tests, pattern): + tests.addTest(doctest.DocTestSuite(collections)) + return tests if __name__ == "__main__": - test_main(verbose=True) + unittest.main() From c5b4789383121200fb8d7ede49166f89bf8a0e45 Mon Sep 17 00:00:00 2001 From: bobby-palmer <112954547+bobby-palmer@users.noreply.github.com> Date: Sun, 18 Jun 2023 08:29:49 -0400 Subject: [PATCH 2/2] decorated failing tests in test_typing.py --- Lib/test/test_typing.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index c7bbde5092..abcc03ce2d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -690,6 +690,10 @@ def test_consistency(self): class CollectionsCallableTests(BaseCallableTests, BaseTestCase): Callable = collections.abc.Callable + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_errors(self): # TODO: RUSTPYTHON, remove when this passes + super().test_errors() # TODO: RUSTPYTHON, remove when this passes # TODO: RUSTPYTHON, AssertionError: 'collections.abc.Callable[__main__.ParamSpec, typing.TypeVar]' != 'collections.abc.Callable[~P, ~T]' @unittest.expectedFailure @@ -4916,6 +4920,8 @@ def test_basic_plain(self): self.assertEqual(P, P) self.assertIsInstance(P, ParamSpec) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_valid_uses(self): P = ParamSpec('P') T = TypeVar('T')