diff --git a/Lib/test/ann_module5.py b/Lib/test/ann_module5.py new file mode 100644 index 000000000..837041e12 --- /dev/null +++ b/Lib/test/ann_module5.py @@ -0,0 +1,10 @@ +# Used by test_typing to verify that Final wrapped in ForwardRef works. + +from __future__ import annotations + +from typing import Final + +name: Final[str] = "final" + +class MyClass: + value: Final = 3000 diff --git a/Lib/test/ann_module6.py b/Lib/test/ann_module6.py new file mode 100644 index 000000000..679175669 --- /dev/null +++ b/Lib/test/ann_module6.py @@ -0,0 +1,7 @@ +# Tests that top-level ClassVar is not allowed + +from __future__ import annotations + +from typing import ClassVar + +wrong: ClassVar[int] = 1 diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index be26f4d85..86825bce2 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -249,6 +249,15 @@ class TypeVarTests(BaseTestCase): with self.assertRaises(ValueError): TypeVar('T', covariant=True, contravariant=True) + def test_bad_var_substitution(self): + T = TypeVar('T') + for arg in (), (int, str): + with self.subTest(arg=arg): + with self.assertRaises(TypeError): + List[T][arg] + with self.assertRaises(TypeError): + list[T][arg] + class UnionTests(BaseTestCase): @@ -320,6 +329,15 @@ class UnionTests(BaseTestCase): u = Union[int | float] self.assertEqual(repr(u), 'typing.Union[int, float]') + u = Union[None, str] + self.assertEqual(repr(u), 'typing.Optional[str]') + u = Union[str, None] + self.assertEqual(repr(u), 'typing.Optional[str]') + u = Union[None, str, int] + self.assertEqual(repr(u), 'typing.Union[NoneType, str, int]') + u = Optional[str] + self.assertEqual(repr(u), 'typing.Optional[str]') + def test_cannot_subclass(self): with self.assertRaises(TypeError): class C(Union): @@ -415,6 +433,8 @@ class TupleTests(BaseTestCase): class MyTuple(tuple): pass self.assertIsSubclass(MyTuple, Tuple) + self.assertIsSubclass(Tuple, Tuple) + self.assertIsSubclass(tuple, Tuple) def test_tuple_instance_type_error(self): with self.assertRaises(TypeError): @@ -442,6 +462,7 @@ class BaseCallableTests: with self.assertRaises(TypeError): issubclass(types.FunctionType, Callable[[int], int]) self.assertIsSubclass(types.FunctionType, Callable) + self.assertIsSubclass(Callable, Callable) def test_eq_hash(self): Callable = self.Callable @@ -517,6 +538,10 @@ class BaseCallableTests: # Shouldn't crash; see https://github.com/python/typing/issues/259 typing.List[Callable[..., str]] + def test_or_and_ror(self): + Callable = self.Callable + self.assertEqual(Callable | Tuple, Union[Callable, Tuple]) + self.assertEqual(Tuple | Callable, Union[Tuple, Callable]) def test_basic(self): Callable = self.Callable @@ -549,8 +574,11 @@ class BaseCallableTests: C2 = Callable[[KT, T], VT] C3 = Callable[..., T] self.assertEqual(C1[str], Callable[[int, str], str]) + if Callable is typing.Callable: + self.assertEqual(C1[None], Callable[[int, type(None)], type(None)]) self.assertEqual(C2[int, float, str], Callable[[int, float], str]) self.assertEqual(C3[int], Callable[..., int]) + self.assertEqual(C3[NoReturn], Callable[..., NoReturn]) # multi chaining C4 = C2[int, VT, str] @@ -610,10 +638,31 @@ class BaseCallableTests: def test_concatenate(self): Callable = self.Callable fullname = f"{Callable.__module__}.Callable" + T = TypeVar('T') P = ParamSpec('P') - C1 = Callable[typing.Concatenate[int, P], int] - self.assertEqual(repr(C1), - f"{fullname}[typing.Concatenate[int, ~P], int]") + P2 = ParamSpec('P2') + C = Callable[Concatenate[int, P], T] + self.assertEqual(repr(C), + f"{fullname}[typing.Concatenate[int, ~P], ~T]") + self.assertEqual(C[P2, int], Callable[Concatenate[int, P2], int]) + self.assertEqual(C[[str, float], int], Callable[[int, str, float], int]) + self.assertEqual(C[[], int], Callable[[int], int]) + self.assertEqual(C[Concatenate[str, P2], int], + Callable[Concatenate[int, str, P2], int]) + with self.assertRaises(TypeError): + C[..., int] + + C = Callable[Concatenate[int, P], int] + self.assertEqual(repr(C), + f"{fullname}[typing.Concatenate[int, ~P], int]") + self.assertEqual(C[P2], Callable[Concatenate[int, P2], int]) + self.assertEqual(C[[str, float]], Callable[[int, str, float], int]) + self.assertEqual(C[str, float], Callable[[int, str, float], int]) + self.assertEqual(C[[]], Callable[[int], int]) + self.assertEqual(C[Concatenate[str, P2]], + Callable[Concatenate[int, str, P2], int]) + with self.assertRaises(TypeError): + C[...] def test_errors(self): Callable = self.Callable @@ -652,6 +701,11 @@ class CollectionsCallableTests(BaseCallableTests, BaseTestCase): def test_paramspec(self): # TODO: RUSTPYTHON, remove when this passes super().test_paramspec() # TODO: RUSTPYTHON, remove when this passes + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_concatenate(self): # TODO: RUSTPYTHON, remove when this passes + super().test_concatenate() # TODO: RUSTPYTHON, remove when this passes + class LiteralTests(BaseTestCase): def test_basics(self): @@ -720,6 +774,8 @@ class LiteralTests(BaseTestCase): self.assertNotEqual(Literal[True], Literal[1]) self.assertNotEqual(Literal[1], Literal[2]) self.assertNotEqual(Literal[1, True], Literal[1]) + self.assertNotEqual(Literal[1, True], Literal[1, 1]) + self.assertNotEqual(Literal[1, 2], Literal[True, 2]) self.assertEqual(Literal[1], Literal[1]) self.assertEqual(Literal[1, 2], Literal[2, 1]) self.assertEqual(Literal[1, 2, 3], Literal[1, 2, 3, 3]) @@ -1057,6 +1113,8 @@ class ProtocolTests(BaseTestCase): with self.assertRaises(TypeError): issubclass(PG, PG[int]) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_protocols_issubclass_non_callable(self): class C: x = 1 @@ -1115,6 +1173,8 @@ class ProtocolTests(BaseTestCase): with self.assertRaises(TypeError): isinstance(C(), BadPG) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_protocols_isinstance_py36(self): class APoint: def __init__(self, x, y, label): @@ -1187,6 +1247,8 @@ class ProtocolTests(BaseTestCase): self.assertIsInstance(D1(), C) self.assertIsSubclass(D2, C) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_protocols_support_register(self): @runtime_checkable class P(Protocol): @@ -1222,6 +1284,8 @@ class ProtocolTests(BaseTestCase): self.assertIsInstance(B(), P) self.assertIsInstance(C(), P) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_none_on_callable_blocks_implementation(self): @runtime_checkable class P(Protocol): @@ -1483,6 +1547,8 @@ class ProtocolTests(BaseTestCase): class Concrete(Proto): pass + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_none_treated_correctly(self): @runtime_checkable class P(Protocol): @@ -2659,10 +2725,18 @@ class ForwardRefTests(BaseTestCase): with self.assertRaises(TypeError): issubclass(int, fr) + def test_forwardref_only_str_arg(self): + with self.assertRaises(TypeError): + typing.ForwardRef(1) # only `str` type is allowed + def test_forward_equality(self): fr = typing.ForwardRef('int') self.assertEqual(fr, typing.ForwardRef('int')) self.assertNotEqual(List['int'], List[int]) + self.assertNotEqual(fr, typing.ForwardRef('int', module=__name__)) + frm = typing.ForwardRef('int', module=__name__) + self.assertEqual(frm, typing.ForwardRef('int', module=__name__)) + self.assertNotEqual(frm, typing.ForwardRef('int', module='__other_name__')) def test_forward_equality_gth(self): c1 = typing.ForwardRef('C') @@ -2699,6 +2773,14 @@ class ForwardRefTests(BaseTestCase): self.assertEqual(hash(c1_gth), hash(c2_gth)) self.assertEqual(hash(c1), hash(c1_gth)) + c3 = typing.ForwardRef('int', module=__name__) + c4 = typing.ForwardRef('int', module='__other_name__') + + self.assertNotEqual(hash(c3), hash(c1)) + self.assertNotEqual(hash(c3), hash(c1_gth)) + self.assertNotEqual(hash(c3), hash(c4)) + self.assertEqual(hash(c3), hash(typing.ForwardRef('int', module=__name__))) + def test_forward_equality_namespace(self): class A: pass @@ -2834,6 +2916,20 @@ class ForwardRefTests(BaseTestCase): self.assertEqual(get_type_hints(foo, globals(), locals()), {'a': Callable[..., T]}) + def test_special_forms_forward(self): + + class C: + a: Annotated['ClassVar[int]', (3, 5)] = 4 + b: Annotated['Final[int]', "const"] = 4 + + class CF: + b: List['Final[int]'] = 4 + + self.assertEqual(get_type_hints(C, globals())['a'], ClassVar[int]) + self.assertEqual(get_type_hints(C, globals())['b'], Final[int]) + with self.assertRaises(TypeError): + get_type_hints(CF, globals()), + def test_syntax_error(self): with self.assertRaises(SyntaxError): @@ -3030,7 +3126,7 @@ else: # Definitions needed for features introduced in Python 3.6 -from test import ann_module, ann_module2, ann_module3 +from test import ann_module, ann_module2, ann_module3, ann_module5, ann_module6 from typing import AsyncContextManager class A: @@ -3165,6 +3261,12 @@ class GetTypeHintTests(BaseTestCase): 'my_inner_a2': mod_generics_cache.B.A, 'my_outer_a': mod_generics_cache.A}) + def test_get_type_hints_classes_no_implicit_optional(self): + class WithNoneDefault: + field: int = None # most type-checkers won't be happy with it + + self.assertEqual(gth(WithNoneDefault), {'field': int}) + def test_respect_no_type_check(self): @no_type_check class NoTpCheck: @@ -3281,6 +3383,15 @@ class GetTypeHintTests(BaseTestCase): {"x": typing.Annotated[int | float, "const"]} ) + def test_get_type_hints_annotated_in_union(self): # bpo-46603 + def with_union(x: int | list[Annotated[str, 'meta']]): ... + + self.assertEqual(get_type_hints(with_union), {'x': int | list[str]}) + self.assertEqual( + get_type_hints(with_union, include_extras=True), + {'x': int | list[Annotated[str, 'meta']]}, + ) + def test_get_type_hints_annotated_refs(self): Const = Annotated[T, "Const"] @@ -3396,6 +3507,22 @@ class GetUtilitiesTestCase(TestCase): (Concatenate[int, P], int)) self.assertEqual(get_args(list | str), (list, str)) + def test_forward_ref_and_final(self): + # https://bugs.python.org/issue45166 + hints = get_type_hints(ann_module5) + self.assertEqual(hints, {'name': Final[str]}) + + hints = get_type_hints(ann_module5.MyClass) + self.assertEqual(hints, {'value': Final}) + + def test_top_level_class_var(self): + # https://bugs.python.org/issue45166 + with self.assertRaisesRegex( + TypeError, + r'typing.ClassVar\[int\] is not valid as type argument', + ): + get_type_hints(ann_module6) + class CollectionsAbcTests(BaseTestCase): @@ -3476,11 +3603,10 @@ class CollectionsAbcTests(BaseTestCase): self.assertNotIsInstance(42, typing.Container) def test_collection(self): - if hasattr(typing, 'Collection'): - self.assertIsInstance(tuple(), typing.Collection) - self.assertIsInstance(frozenset(), typing.Collection) - self.assertIsSubclass(dict, typing.Collection) - self.assertNotIsInstance(42, typing.Collection) + self.assertIsInstance(tuple(), typing.Collection) + self.assertIsInstance(frozenset(), typing.Collection) + self.assertIsSubclass(dict, typing.Collection) + self.assertNotIsInstance(42, typing.Collection) def test_abstractset(self): self.assertIsInstance(set(), typing.AbstractSet) @@ -3877,6 +4003,10 @@ class CollectionsAbcTests(BaseTestCase): A.register(B) self.assertIsSubclass(B, typing.Mapping) + def test_or_and_ror(self): + self.assertEqual(typing.Sized | typing.Awaitable, Union[typing.Sized, typing.Awaitable]) + self.assertEqual(typing.Coroutine | typing.Hashable, Union[typing.Coroutine, typing.Hashable]) + class OtherABCTests(BaseTestCase): @@ -4132,6 +4262,19 @@ class NamedTupleTests(BaseTestCase): self.assertEqual(a.typename, 'foo') self.assertEqual(a.fields, [('bar', tuple)]) + def test_empty_namedtuple(self): + NT = NamedTuple('NT') + + class CNT(NamedTuple): + pass # empty body + + for struct in [NT, CNT]: + with self.subTest(struct=struct): + self.assertEqual(struct._fields, ()) + self.assertEqual(struct._field_defaults, {}) + self.assertEqual(struct.__annotations__, {}) + self.assertIsInstance(struct(), struct) + def test_namedtuple_errors(self): with self.assertRaises(TypeError): NamedTuple.__new__() @@ -4327,6 +4470,93 @@ class TypedDictTests(BaseTestCase): 'voice': str, } + def test_multiple_inheritance(self): + class One(TypedDict): + one: int + class Two(TypedDict): + two: str + class Untotal(TypedDict, total=False): + untotal: str + Inline = TypedDict('Inline', {'inline': bool}) + class Regular: + pass + + class Child(One, Two): + child: bool + self.assertEqual( + Child.__required_keys__, + frozenset(['one', 'two', 'child']), + ) + self.assertEqual( + Child.__optional_keys__, + frozenset([]), + ) + self.assertEqual( + Child.__annotations__, + {'one': int, 'two': str, 'child': bool}, + ) + + class ChildWithOptional(One, Untotal): + child: bool + self.assertEqual( + ChildWithOptional.__required_keys__, + frozenset(['one', 'child']), + ) + self.assertEqual( + ChildWithOptional.__optional_keys__, + frozenset(['untotal']), + ) + self.assertEqual( + ChildWithOptional.__annotations__, + {'one': int, 'untotal': str, 'child': bool}, + ) + + class ChildWithTotalFalse(One, Untotal, total=False): + child: bool + self.assertEqual( + ChildWithTotalFalse.__required_keys__, + frozenset(['one']), + ) + self.assertEqual( + ChildWithTotalFalse.__optional_keys__, + frozenset(['untotal', 'child']), + ) + self.assertEqual( + ChildWithTotalFalse.__annotations__, + {'one': int, 'untotal': str, 'child': bool}, + ) + + class ChildWithInlineAndOptional(Untotal, Inline): + child: bool + self.assertEqual( + ChildWithInlineAndOptional.__required_keys__, + frozenset(['inline', 'child']), + ) + self.assertEqual( + ChildWithInlineAndOptional.__optional_keys__, + frozenset(['untotal']), + ) + self.assertEqual( + ChildWithInlineAndOptional.__annotations__, + {'inline': bool, 'untotal': str, 'child': bool}, + ) + + wrong_bases = [ + (One, Regular), + (Regular, One), + (One, Two, Regular), + (Inline, Regular), + (Untotal, Regular), + ] + for bases in wrong_bases: + with self.subTest(bases=bases): + with self.assertRaisesRegex( + TypeError, + 'cannot inherit from both a TypedDict type and a non-TypedDict', + ): + class Wrong(*bases): + pass + def test_is_typeddict(self): assert is_typeddict(Point2D) is True assert is_typeddict(Union[str, int]) is False @@ -4339,6 +4569,21 @@ class TypedDictTests(BaseTestCase): {'a': typing.Optional[int], 'b': int} ) + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_non_generic_subscript(self): + # For backward compatibility, subscription works + # on arbitrary TypedDict types. + class TD(TypedDict): + a: T + A = TD[int] + self.assertEqual(A.__origin__, TD) + self.assertEqual(A.__parameters__, ()) + self.assertEqual(A.__args__, (int,)) + a = A(a = 1) + self.assertIs(type(a), dict) + self.assertEqual(a, {'a': 1}) + class IOTests(BaseTestCase): @@ -4439,6 +4684,13 @@ class RETests(BaseTestCase): class AnnotatedTests(BaseTestCase): + def test_new(self): + with self.assertRaisesRegex( + TypeError, + 'Type Annotated cannot be instantiated', + ): + Annotated() + def test_repr(self): self.assertEqual( repr(Annotated[int, 4, 5]), @@ -4523,6 +4775,14 @@ class AnnotatedTests(BaseTestCase): A.x = 5 self.assertEqual(C.x, 5) + def test_special_form_containment(self): + class C: + classvar: Annotated[ClassVar[int], "a decoration"] = 4 + const: Annotated[Final[int], "Const"] = 4 + + self.assertEqual(get_type_hints(C, globals())['classvar'], ClassVar[int]) + self.assertEqual(get_type_hints(C, globals())['const'], Final[int]) + def test_hash_eq(self): self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) @@ -4546,6 +4806,10 @@ class AnnotatedTests(BaseTestCase): with self.assertRaises(TypeError): issubclass(int, Annotated[int, "positive"]) + def test_too_few_type_args(self): + with self.assertRaisesRegex(TypeError, 'at least two arguments'): + Annotated[int] + # TODO: RUSTPYTHON @unittest.expectedFailure def test_pickle(self): @@ -4632,6 +4896,11 @@ class TypeAliasTests(BaseTestCase): with self.assertRaises(TypeError): isinstance(42, TypeAlias) + def test_stringized_usage(self): + class A: + a: "TypeAlias" + self.assertEqual(get_type_hints(A), {'a': TypeAlias}) + def test_no_issubclass(self): with self.assertRaises(TypeError): issubclass(Employee, TypeAlias) @@ -4682,15 +4951,35 @@ class ParamSpecTests(BaseTestCase): def test_args_kwargs(self): P = ParamSpec('P') + P_2 = ParamSpec('P_2') self.assertIn('args', dir(P)) self.assertIn('kwargs', dir(P)) self.assertIsInstance(P.args, ParamSpecArgs) self.assertIsInstance(P.kwargs, ParamSpecKwargs) self.assertIs(P.args.__origin__, P) self.assertIs(P.kwargs.__origin__, P) + self.assertEqual(P.args, P.args) + self.assertEqual(P.kwargs, P.kwargs) + self.assertNotEqual(P.args, P_2.args) + self.assertNotEqual(P.kwargs, P_2.kwargs) + self.assertNotEqual(P.args, P.kwargs) + self.assertNotEqual(P.kwargs, P.args) + self.assertNotEqual(P.args, P_2.kwargs) self.assertEqual(repr(P.args), "P.args") self.assertEqual(repr(P.kwargs), "P.kwargs") + def test_stringized(self): + P = ParamSpec('P') + class C(Generic[P]): + func: Callable["P", int] + def foo(self, *args: "P.args", **kwargs: "P.kwargs"): + pass + + self.assertEqual(gth(C, globals(), locals()), {"func": Callable[P, int]}) + self.assertEqual( + gth(C.foo, globals(), locals()), {"args": P.args, "kwargs": P.kwargs} + ) + def test_user_generics(self): T = TypeVar("T") P = ParamSpec("P") @@ -4759,6 +5048,17 @@ class ParamSpecTests(BaseTestCase): self.assertEqual(G1.__args__, ((int, str), (bytes,))) self.assertEqual(G2.__args__, ((int,), (str, bytes))) + def test_bad_var_substitution(self): + T = TypeVar('T') + P = ParamSpec('P') + bad_args = (42, int, None, T, int|str, Union[int, str]) + for arg in bad_args: + with self.subTest(arg=arg): + with self.assertRaises(TypeError): + typing.Callable[P, T][arg, str] + with self.assertRaises(TypeError): + collections.abc.Callable[P, T][arg, str] + def test_no_paramspec_in__parameters__(self): # ParamSpec should not be found in __parameters__ # of generics. Usages outside Callable, Concatenate @@ -4788,6 +5088,31 @@ class ParamSpecTests(BaseTestCase): self.assertEqual(G1.__parameters__, (P, T)) self.assertEqual(G2.__parameters__, (P, T)) self.assertEqual(G3.__parameters__, (P, T)) + C = Callable[[int, str], float] + self.assertEqual(G1[[int, str], float], List[C]) + self.assertEqual(G2[[int, str], float], list[C]) + self.assertEqual(G3[[int, str], float], list[C] | int) + + def test_paramspec_gets_copied(self): + # bpo-46581 + P = ParamSpec('P') + P2 = ParamSpec('P2') + C1 = Callable[P, int] + self.assertEqual(C1.__parameters__, (P,)) + self.assertEqual(C1[P2].__parameters__, (P2,)) + self.assertEqual(C1[str].__parameters__, ()) + self.assertEqual(C1[str, T].__parameters__, (T,)) + self.assertEqual(C1[Concatenate[str, P2]].__parameters__, (P2,)) + self.assertEqual(C1[Concatenate[T, P2]].__parameters__, (T, P2)) + self.assertEqual(C1[...].__parameters__, ()) + + C2 = Callable[Concatenate[str, P], int] + self.assertEqual(C2.__parameters__, (P,)) + self.assertEqual(C2[P2].__parameters__, (P2,)) + self.assertEqual(C2[str].__parameters__, ()) + self.assertEqual(C2[str, T].__parameters__, (T,)) + self.assertEqual(C2[Concatenate[str, P2]].__parameters__, (P2,)) + self.assertEqual(C2[Concatenate[T, P2]].__parameters__, (T, P2)) class ConcatenateTests(BaseTestCase): @@ -4815,6 +5140,27 @@ class ConcatenateTests(BaseTestCase): self.assertEqual(C4.__args__, (Concatenate[int, T, P], T)) self.assertEqual(C4.__parameters__, (T, P)) + def test_var_substitution(self): + T = TypeVar('T') + P = ParamSpec('P') + P2 = ParamSpec('P2') + C = Concatenate[T, P] + self.assertEqual(C[int, P2], Concatenate[int, P2]) + self.assertEqual(C[int, [str, float]], (int, str, float)) + self.assertEqual(C[int, []], (int,)) + self.assertEqual(C[int, Concatenate[str, P2]], + Concatenate[int, str, P2]) + with self.assertRaises(TypeError): + C[int, ...] + + C = Concatenate[int, P] + self.assertEqual(C[P2], Concatenate[int, P2]) + self.assertEqual(C[[str, float]], (int, str, float)) + self.assertEqual(C[str, float], (int, str, float)) + self.assertEqual(C[[]], (int,)) + self.assertEqual(C[Concatenate[str, P2]], Concatenate[int, str, P2]) + with self.assertRaises(TypeError): + C[...] class TypeGuardTests(BaseTestCase): def test_basics(self): @@ -4963,6 +5309,8 @@ class SpecialAttrsTests(BaseTestCase): typing.Concatenate[Any, SpecialAttrsP]: 'Concatenate', typing.Final[Any]: 'Final', typing.Literal[Any]: 'Literal', + typing.Literal[1, 2]: 'Literal', + typing.Literal[True, 2]: 'Literal', typing.Optional[Any]: 'Optional', typing.TypeGuard[Any]: 'TypeGuard', typing.Union[Any]: 'Any', @@ -5008,7 +5356,7 @@ class SpecialAttrsTests(BaseTestCase): ) self.assertEqual( SpecialAttrsTests.TypeName.__module__, - 'test.test_typing', + __name__, ) # NewTypes are picklable assuming correct qualname information. for proto in range(pickle.HIGHEST_PROTOCOL + 1): @@ -5022,7 +5370,7 @@ class SpecialAttrsTests(BaseTestCase): # __qualname__ is unnecessary. self.assertEqual(SpecialAttrsT.__name__, 'SpecialAttrsT') self.assertFalse(hasattr(SpecialAttrsT, '__qualname__')) - self.assertEqual(SpecialAttrsT.__module__, 'test.test_typing') + self.assertEqual(SpecialAttrsT.__module__, __name__) # Module-level type variables are picklable. for proto in range(pickle.HIGHEST_PROTOCOL + 1): s = pickle.dumps(SpecialAttrsT, proto) @@ -5031,13 +5379,24 @@ class SpecialAttrsTests(BaseTestCase): self.assertEqual(SpecialAttrsP.__name__, 'SpecialAttrsP') self.assertFalse(hasattr(SpecialAttrsP, '__qualname__')) - self.assertEqual(SpecialAttrsP.__module__, 'test.test_typing') + self.assertEqual(SpecialAttrsP.__module__, __name__) # Module-level ParamSpecs are picklable. for proto in range(pickle.HIGHEST_PROTOCOL + 1): s = pickle.dumps(SpecialAttrsP, proto) loaded = pickle.loads(s) self.assertIs(SpecialAttrsP, loaded) + def test_genericalias_dir(self): + class Foo(Generic[T]): + def bar(self): + pass + baz = 3 + # The class attributes of the original class should be visible even + # in dir() of the GenericAlias. See bpo-45755. + self.assertIn('bar', dir(Foo[int])) + self.assertIn('baz', dir(Foo[int])) + + class AllTests(BaseTestCase): """Tests for __all__.""" @@ -5048,8 +5407,9 @@ class AllTests(BaseTestCase): self.assertIn('ValuesView', a) self.assertIn('cast', a) self.assertIn('overload', a) - if hasattr(contextlib, 'AbstractContextManager'): - self.assertIn('ContextManager', a) + # Context managers. + self.assertIn('ContextManager', a) + self.assertIn('AsyncContextManager', a) # Check that io and re are not exported. self.assertNotIn('io', a) self.assertNotIn('re', a) @@ -5063,7 +5423,6 @@ class AllTests(BaseTestCase): self.assertIn('SupportsComplex', a) def test_all_exported_names(self): - import typing actual_all = set(typing.__all__) computed_all = { diff --git a/Lib/typing.py b/Lib/typing.py index 8ae48b915..a101b5c65 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -134,16 +134,16 @@ __all__ = [ # legitimate imports of those modules. -def _type_convert(arg, module=None): +def _type_convert(arg, module=None, *, allow_special_forms=False): """For converting None to type(None), and strings to ForwardRef.""" if arg is None: return type(None) if isinstance(arg, str): - return ForwardRef(arg, module=module) + return ForwardRef(arg, module=module, is_class=allow_special_forms) return arg -def _type_check(arg, msg, is_argument=True, module=None): +def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms=False): """Check that the argument is a type, and return it (internal helper). As a special case, accept None and return type(None) instead. Also wrap strings @@ -156,18 +156,21 @@ def _type_check(arg, msg, is_argument=True, module=None): We append the repr() of the actual value (truncated to 100 chars). """ invalid_generic_forms = (Generic, Protocol) - if is_argument: - invalid_generic_forms = invalid_generic_forms + (ClassVar, Final) + if not allow_special_forms: + invalid_generic_forms += (ClassVar,) + if is_argument: + invalid_generic_forms += (Final,) - arg = _type_convert(arg, module=module) + arg = _type_convert(arg, module=module, allow_special_forms=allow_special_forms) if (isinstance(arg, _GenericAlias) and arg.__origin__ in invalid_generic_forms): raise TypeError(f"{arg} is not valid as type argument") - if arg in (Any, NoReturn): + if arg in (Any, NoReturn, Final, TypeAlias): return arg if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol): raise TypeError(f"Plain {arg} is not valid as type argument") - if isinstance(arg, (type, TypeVar, ForwardRef, types.UnionType, ParamSpec)): + if isinstance(arg, (type, TypeVar, ForwardRef, types.UnionType, ParamSpec, + ParamSpecArgs, ParamSpecKwargs)): return arg if not callable(arg): raise TypeError(f"{msg} Got {arg!r:.100}.") @@ -317,8 +320,8 @@ def _tp_cache(func=None, /, *, typed=False): def _eval_type(t, globalns, localns, recursive_guard=frozenset()): """Evaluate all forward references in the given type t. For use of globalns and localns see the docstring for get_type_hints(). - recursive_guard is used to prevent prevent infinite recursion - with recursive ForwardRef. + recursive_guard is used to prevent infinite recursion with a recursive + ForwardRef. """ if isinstance(t, ForwardRef): return t._evaluate(globalns, localns, recursive_guard) @@ -401,9 +404,10 @@ class _SpecialForm(_Final, _root=True): class _LiteralSpecialForm(_SpecialForm, _root=True): - @_tp_cache(typed=True) def __getitem__(self, parameters): - return self._getitem(self, parameters) + if not isinstance(parameters, tuple): + parameters = (parameters,) + return self._getitem(self, *parameters) @_SpecialForm @@ -526,7 +530,8 @@ def Optional(self, parameters): return Union[arg, type(None)] @_LiteralSpecialForm -def Literal(self, parameters): +@_tp_cache(typed=True) +def Literal(self, *parameters): """Special typing form to define literal types (a.k.a. value types). This form can be used to indicate to type checkers that the corresponding @@ -549,9 +554,6 @@ def Literal(self, parameters): """ # There is no '_type_check' call because arguments to Literal[...] are # values, not types. - if not isinstance(parameters, tuple): - parameters = (parameters,) - parameters = _flatten_literal_params(parameters) try: @@ -597,8 +599,10 @@ def Concatenate(self, parameters): raise TypeError("The last parameter to Concatenate should be a " "ParamSpec variable.") msg = "Concatenate[arg, ...]: each arg must be a type." - parameters = tuple(_type_check(p, msg) for p in parameters) - return _ConcatenateGenericAlias(self, parameters) + parameters = (*(_type_check(p, msg) for p in parameters[:-1]), parameters[-1]) + return _ConcatenateGenericAlias(self, parameters, + _typevar_types=(TypeVar, ParamSpec), + _paramspec_tvars=True) @_SpecialForm @@ -654,9 +658,10 @@ class ForwardRef(_Final, _root=True): __slots__ = ('__forward_arg__', '__forward_code__', '__forward_evaluated__', '__forward_value__', - '__forward_is_argument__', '__forward_module__') + '__forward_is_argument__', '__forward_is_class__', + '__forward_module__') - def __init__(self, arg, is_argument=True, module=None): + def __init__(self, arg, is_argument=True, module=None, *, is_class=False): if not isinstance(arg, str): raise TypeError(f"Forward reference must be a string -- got {arg!r}") try: @@ -668,6 +673,7 @@ class ForwardRef(_Final, _root=True): self.__forward_evaluated__ = False self.__forward_value__ = None self.__forward_is_argument__ = is_argument + self.__forward_is_class__ = is_class self.__forward_module__ = module def _evaluate(self, globalns, localns, recursive_guard): @@ -684,10 +690,11 @@ class ForwardRef(_Final, _root=True): globalns = getattr( sys.modules.get(self.__forward_module__, None), '__dict__', globalns ) - type_ =_type_check( + type_ = _type_check( eval(self.__forward_code__, globalns, localns), "Forward references must evaluate to types.", is_argument=self.__forward_is_argument__, + allow_special_forms=self.__forward_is_class__, ) self.__forward_value__ = _eval_type( type_, globalns, localns, recursive_guard | {self.__forward_arg__} @@ -701,10 +708,11 @@ class ForwardRef(_Final, _root=True): if self.__forward_evaluated__ and other.__forward_evaluated__: return (self.__forward_arg__ == other.__forward_arg__ and self.__forward_value__ == other.__forward_value__) - return self.__forward_arg__ == other.__forward_arg__ + return (self.__forward_arg__ == other.__forward_arg__ and + self.__forward_module__ == other.__forward_module__) def __hash__(self): - return hash(self.__forward_arg__) + return hash((self.__forward_arg__, self.__forward_module__)) def __repr__(self): return f'ForwardRef({self.__forward_arg__!r})' @@ -826,6 +834,11 @@ class ParamSpecArgs(_Final, _Immutable, _root=True): def __repr__(self): return f"{self.__origin__.__name__}.args" + def __eq__(self, other): + if not isinstance(other, ParamSpecArgs): + return NotImplemented + return self.__origin__ == other.__origin__ + class ParamSpecKwargs(_Final, _Immutable, _root=True): """The kwargs for a ParamSpec object. @@ -845,6 +858,11 @@ class ParamSpecKwargs(_Final, _Immutable, _root=True): def __repr__(self): return f"{self.__origin__.__name__}.kwargs" + def __eq__(self, other): + if not isinstance(other, ParamSpecKwargs): + return NotImplemented + return self.__origin__ == other.__origin__ + class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True): """Parameter specification variable. @@ -857,7 +875,7 @@ class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True): type checkers. They are used to forward the parameter types of one callable to another callable, a pattern commonly found in higher order functions and decorators. They are only valid when used in ``Concatenate``, - or s the first argument to ``Callable``, or as parameters for user-defined + or as the first argument to ``Callable``, or as parameters for user-defined Generics. See class Generic for more information on generic types. An example for annotating a decorator:: @@ -960,7 +978,7 @@ class _BaseGenericAlias(_Final, _root=True): return self._name or self.__origin__.__name__ # We are careful for copy and pickle. - # Also for simplicity we just don't relay all dunder names + # Also for simplicity we don't relay any dunder names if '__origin__' in self.__dict__ and not _is_dunder(attr): return getattr(self.__origin__, attr) raise AttributeError(attr) @@ -979,6 +997,9 @@ class _BaseGenericAlias(_Final, _root=True): raise TypeError("Subscripted generics cannot be used with" " class and instance checks") + def __dir__(self): + return list(set(super().__dir__() + + [attr for attr in dir(self.__origin__) if not _is_dunder(attr)])) # Special typing constructs Union, Optional, Generic, Callable and Tuple # use three special attributes for internal bookkeeping of generic types: @@ -1060,7 +1081,9 @@ class _GenericAlias(_BaseGenericAlias, _root=True): return self.copy_with(tuple(new_args)) def copy_with(self, params): - return self.__class__(self.__origin__, params, name=self._name, inst=self._inst) + return self.__class__(self.__origin__, params, name=self._name, inst=self._inst, + _typevar_types=self._typevar_types, + _paramspec_tvars=self._paramspec_tvars) def __repr__(self): if self._name: @@ -1262,10 +1285,15 @@ class _LiteralGenericAlias(_GenericAlias, _root=True): class _ConcatenateGenericAlias(_GenericAlias, _root=True): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs, - _typevar_types=(TypeVar, ParamSpec), - _paramspec_tvars=True) + def copy_with(self, params): + if isinstance(params[-1], (list, tuple)): + return (*params[:-1], *params[-1]) + if isinstance(params[-1], _ConcatenateGenericAlias): + params = (*params[:-1], *params[-1].__args__) + elif not isinstance(params[-1], ParamSpec): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable.") + return super().copy_with(params) class Generic: @@ -1441,9 +1469,7 @@ def _allow_reckless_class_checks(depth=3): issubclass() on the whole MRO of a user class, which may contain protocols. """ try: - # XXX RUSTPYTHON: added _py_abc; I think CPython was fine because abc called - # directly into the _abc builtin module, which wasn't in the frame stack - return sys._getframe(3).f_globals['__name__'] in ['abc', 'functools', '_py_abc'] + return sys._getframe(depth).f_globals['__name__'] in ['abc', 'functools'] except (AttributeError, ValueError): # For platforms without _getframe(). return True @@ -1672,7 +1698,7 @@ class Annotated: "with at least two arguments (a type and an " "annotation).") msg = "Annotated[t, ...]: t must be a type." - origin = _type_check(params[0], msg) + origin = _type_check(params[0], msg, allow_special_forms=True) metadata = tuple(params[1:]) return _AnnotatedAlias(origin, metadata) @@ -1802,7 +1828,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): if value is None: value = type(None) if isinstance(value, str): - value = ForwardRef(value, is_argument=False) + value = ForwardRef(value, is_argument=False, is_class=True) value = _eval_type(value, base_globals, base_locals) hints[name] = value return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()} @@ -1834,7 +1860,13 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): if value is None: value = type(None) if isinstance(value, str): - value = ForwardRef(value) + # class-level forward refs were handled above, this must be either + # a module-level annotation or a function argument annotation + value = ForwardRef( + value, + is_argument=not isinstance(obj, types.ModuleType), + is_class=False, + ) value = _eval_type(value, globalns, localns) if name in defaults and defaults[name] is None: value = Optional[value] @@ -2423,7 +2455,7 @@ class NewType: """NewType creates simple unique types with almost zero runtime overhead. NewType(name, tp) is considered a subtype of tp by static type checkers. At runtime, NewType(name, tp) returns - a dummy function that simply returns its argument. Usage:: + a dummy callable that simply returns its argument. Usage:: UserId = NewType('UserId', int) @@ -2628,6 +2660,7 @@ class io: TextIO = TextIO BinaryIO = BinaryIO + io.__name__ = __name__ + '.io' sys.modules[io.__name__] = io