From 0e849c53b26fcd2dfa939b374ebab5ec7cbebe38 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sat, 11 Mar 2023 23:40:36 +0000 Subject: [PATCH 01/11] Base implementation of PEP 696 Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- Doc/library/typing.rst | 20 ++++++++- Lib/test/test_typing.py | 91 +++++++++++++++++++++-------------------- Lib/typing.py | 43 ++++++++++++++++--- 3 files changed, 103 insertions(+), 51 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 80a969e6335abe..5d7ad1b11d2ca9 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1292,6 +1292,22 @@ These are not used in annotations. They are building blocks for creating generic c = concatenate('one', b'two') # error: type variable 'A' can be either str or bytes in a function call, but not both + If a generic type is commonly generic over just one type you can use + ``default`` to specify this type:: + + T = TypeVar("T", default=int) + + class Box(Generic[T]): + def __init__(self, value: T | None = None): + self.value = value + + reveal_type(Box()) # type is Box[int] + reveal_type(Box(value="Hello World!")) # type is Box[str] + + A TypeVar without a default cannot follow a TypeVar with a default. + + .. TODO add more about this + At runtime, ``isinstance(x, T)`` will raise :exc:`TypeError`. In general, :func:`isinstance` and :func:`issubclass` should not be used with types. @@ -1395,6 +1411,8 @@ These are not used in annotations. They are building blocks for creating generic See :pep:`646` for more details on type variable tuples. + .. TODO docs on default + .. versionadded:: 3.11 .. data:: Unpack @@ -1423,7 +1441,7 @@ These are not used in annotations. They are building blocks for creating generic .. versionadded:: 3.11 -.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False) +.. class:: ParamSpec(name, *, bound=None, default=..., covariant=False, contravariant=False) Parameter specification variable. A specialized version of :class:`type variables `. diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index c17be6cd0bbc4a..0833871e657918 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1,52 +1,35 @@ -import contextlib +import abc import collections -from collections import defaultdict -from functools import lru_cache, wraps +import contextlib import inspect import itertools import pickle import re import sys -import warnings -from unittest import TestCase, main, skipUnless, skip -from unittest.mock import patch -from copy import copy, deepcopy - -from typing import Any, NoReturn, Never, assert_never -from typing import overload, get_overloads, clear_overloads -from typing import TypeVar, TypeVarTuple, Unpack, AnyStr -from typing import T, KT, VT # Not in __all__. -from typing import Union, Optional, Literal -from typing import Tuple, List, Dict, MutableMapping -from typing import Callable -from typing import Generic, ClassVar, Final, final, Protocol -from typing import assert_type, cast, runtime_checkable -from typing import get_type_hints -from typing import get_origin, get_args -from typing import override -from typing import is_typeddict -from typing import reveal_type -from typing import dataclass_transform -from typing import no_type_check, no_type_check_decorator -from typing import Type -from typing import NamedTuple, NotRequired, Required, TypedDict -from typing import IO, TextIO, BinaryIO -from typing import Pattern, Match -from typing import Annotated, ForwardRef -from typing import Self, LiteralString -from typing import TypeAlias -from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs -from typing import TypeGuard -import abc import textwrap +import types import typing +import warnings import weakref -import types - -from test.support import import_helper, captured_stderr, cpython_only -from test import mod_generics_cache -from test import _typed_dict_helper - +from collections import defaultdict +from copy import copy, deepcopy +from functools import lru_cache, wraps +from test import _typed_dict_helper, mod_generics_cache +from test.support import captured_stderr, cpython_only, import_helper +from typing import (IO, KT, VT, Annotated, Any, AnyStr, # Not in __all__. + BinaryIO, Callable, ClassVar, Concatenate, Dict, Final, + ForwardRef, Generic, List, Literal, LiteralString, Match, + MutableMapping, NamedTuple, Never, NoReturn, NotRequired, + Optional, ParamSpec, ParamSpecArgs, ParamSpecKwargs, + Pattern, Protocol, Required, Self, T, TextIO, Tuple, Type, + TypeAlias, TypedDict, TypeGuard, TypeVar, TypeVarTuple, + Union, Unpack, assert_never, assert_type, cast, + clear_overloads, dataclass_transform, final, get_args, + get_origin, get_overloads, get_type_hints, is_typeddict, + no_type_check, no_type_check_decorator, overload, override, + reveal_type, runtime_checkable) +from unittest import TestCase, main, skip, skipUnless +from unittest.mock import patch py_typing = import_helper.import_fresh_module('typing', blocked=['_typing']) c_typing = import_helper.import_fresh_module('typing', fresh=['_typing']) @@ -447,6 +430,22 @@ def test_bound_errors(self): with self.assertRaises(TypeError): TypeVar('X', str, float, bound=Employee) + def test_default_error(self): + with self.assertRaises(TypeError): + TypeVar('X', default=Union) + + def test_default_ordering(self): + T = TypeVar("T") + U = TypeVar("U", default=int) + V = TypeVar("V", default=float) + + class Foo(Generic[T, U]): ... + with self.assertRaises(TypeError): + class Bar(Generic[U, T]): ... + + class Baz(Foo[V]): ... + + def test_missing__name__(self): # See bpo-39942 code = ("import typing\n" @@ -3698,7 +3697,8 @@ def test_immutability_by_copy_and_pickle(self): TPB = TypeVar('TPB', bound=int) TPV = TypeVar('TPV', bytes, str) PP = ParamSpec('PP') - for X in [TP, TPB, TPV, PP, + TD = TypeVar('TD', default=int) + for X in [TP, TPB, TPV, PP, TD, List, typing.Mapping, ClassVar, typing.Iterable, Union, Any, Tuple, Callable]: with self.subTest(thing=X): @@ -3706,14 +3706,15 @@ def test_immutability_by_copy_and_pickle(self): self.assertIs(deepcopy(X), X) for proto in range(pickle.HIGHEST_PROTOCOL + 1): self.assertIs(pickle.loads(pickle.dumps(X, proto)), X) - del TP, TPB, TPV, PP + del TP, TPB, TPV, PP, TD # Check that local type variables are copyable. TL = TypeVar('TL') TLB = TypeVar('TLB', bound=int) TLV = TypeVar('TLV', bytes, str) PL = ParamSpec('PL') - for X in [TL, TLB, TLV, PL]: + TDL = TypeVar('TDL', default=int) + for X in [TL, TLB, TLV, PL, TDL]: with self.subTest(thing=X): self.assertIs(copy(X), X) self.assertIs(deepcopy(X), X) @@ -4415,6 +4416,7 @@ def test_errors(self): # We need this to make sure that `@no_type_check` respects `__module__` attr: from test import ann_module8 + @no_type_check class NoTypeCheck_Outer: Inner = ann_module8.NoTypeCheck_Outer.Inner @@ -6981,7 +6983,7 @@ def stuff(a: BinaryIO) -> bytes: def test_io_submodule(self): with warnings.catch_warnings(record=True) as w: warnings.filterwarnings("default", category=DeprecationWarning) - from typing.io import IO, TextIO, BinaryIO, __all__, __name__ + from typing.io import IO, BinaryIO, TextIO, __all__, __name__ self.assertIs(IO, typing.IO) self.assertIs(TextIO, typing.TextIO) self.assertIs(BinaryIO, typing.BinaryIO) @@ -8058,6 +8060,7 @@ class AllTests(BaseTestCase): def test_all(self): from typing import __all__ as a + # Just spot-check the first and last of every category. self.assertIn('AbstractSet', a) self.assertIn('ValuesView', a) diff --git a/Lib/typing.py b/Lib/typing.py index 8d40e923bb1d08..2cff6c67bb4ce2 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -250,16 +250,27 @@ def _collect_parameters(args): _collect_parameters((T, Callable[P, T])) == (T, P) """ parameters = [] + seen_default = False for t in args: # We don't want __parameters__ descriptor of a bare Python class. if isinstance(t, type): continue if hasattr(t, '__typing_subst__'): if t not in parameters: + if t.__default__ is not None: + seen_default = True + elif seen_default: + raise TypeError("TypeVarLike without a default follows one with a default") + parameters.append(t) else: for x in getattr(t, '__parameters__', ()): if x not in parameters: + if x.__default__ is not None: + seen_default = True + elif seen_default: + raise TypeError("TypeVarLike without a default follows one with a default") + parameters.append(x) return tuple(parameters) @@ -956,7 +967,24 @@ def __mro_entries__(self, bases): raise TypeError(f"Cannot subclass an instance of {type(self).__name__}") -class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin, +_marker = object() + + +class _DefaultMixin: + """Mixin for TypeVarLike defaults.""" + + def __init__(self, default): + if isinstance(default, (tuple, list)): + self.__default__ = default.__class__( + (_type_check(d, "Defaults must all be types") for d in default) + ) + elif default != _marker: + self.__default__ = _type_check(default, "Default must be a type") + else: + self.__default__ = None + + +class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin, _DefaultMixin, _root=True): """Type variable. @@ -1001,10 +1029,11 @@ def longest(x: A, y: A) -> A: Note that only type variables defined in global scope can be pickled. """ - def __init__(self, name, *constraints, bound=None, + def __init__(self, name, *constraints, bound=None, default=_marker, covariant=False, contravariant=False): self.__name__ = name super().__init__(bound, covariant, contravariant) + _DefaultMixin.__init__(self, default) if constraints and bound is not None: raise TypeError("Constraints cannot be combined with bound=...") if constraints and len(constraints) == 1: @@ -1024,7 +1053,7 @@ def __typing_subst__(self, arg): return arg -class TypeVarTuple(_Final, _Immutable, _PickleUsingNameMixin, _root=True): +class TypeVarTuple(_Final, _Immutable, _PickleUsingNameMixin, _DefaultMixin, _root=True): """Type variable tuple. Usage: @@ -1049,8 +1078,9 @@ class C(Generic[*Ts]): ... Note that only TypeVarTuples defined in global scope can be pickled. """ - def __init__(self, name): + def __init__(self, name, *, default=_marker): self.__name__ = name + _DefaultMixin.__init__(self, default) # Used for pickling. def_mod = _caller() @@ -1160,7 +1190,7 @@ def __mro_entries__(self, bases): raise TypeError(f"Cannot subclass an instance of {type(self).__name__}") -class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin, +class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin, _DefaultMixin, _root=True): """Parameter specification variable. @@ -1215,9 +1245,10 @@ def args(self): def kwargs(self): return ParamSpecKwargs(self) - def __init__(self, name, *, bound=None, covariant=False, contravariant=False): + def __init__(self, name, *, bound=None, default=None, covariant=False, contravariant=False): self.__name__ = name super().__init__(bound, covariant, contravariant) + _DefaultMixin.__init__(self, default) def_mod = _caller() if def_mod != 'typing': self.__module__ = def_mod From 2c278ca383ab8e4bc9d51fe2196867e29199be1f Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Tue, 4 Apr 2023 01:03:48 +0100 Subject: [PATCH 02/11] C is fun PT2 (ignore this) --- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 2 + Lib/test/test_types.py | 20 ++++++++++ Objects/genericaliasobject.c | 40 +++++++++++++++++++ 6 files changed, 65 insertions(+) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 4b12ae523c3260..166fe2af914225 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -607,6 +607,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__contains__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__copy__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__ctypes_from_outparam__)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__default__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__del__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__delattr__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__delete__)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 17fb9ffbbf9f11..2076e3640febf5 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -93,6 +93,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(__contains__) STRUCT_FOR_ID(__copy__) STRUCT_FOR_ID(__ctypes_from_outparam__) + STRUCT_FOR_ID(__default__) STRUCT_FOR_ID(__del__) STRUCT_FOR_ID(__delattr__) STRUCT_FOR_ID(__delete__) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index b240be57369d9d..b64c272618503c 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -599,6 +599,7 @@ extern "C" { INIT_ID(__contains__), \ INIT_ID(__copy__), \ INIT_ID(__ctypes_from_outparam__), \ + INIT_ID(__default__), \ INIT_ID(__del__), \ INIT_ID(__delattr__), \ INIT_ID(__delete__), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 52af37a8e60aa8..01f21d694b77df 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -92,6 +92,8 @@ _PyUnicode_InitStaticStrings(void) { PyUnicode_InternInPlace(&string); string = &_Py_ID(__ctypes_from_outparam__); PyUnicode_InternInPlace(&string); + string = &_Py_ID(__default__); + PyUnicode_InternInPlace(&string); string = &_Py_ID(__del__); PyUnicode_InternInPlace(&string); string = &_Py_ID(__delattr__); diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index af095632a36fcb..21799baf878239 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -828,6 +828,14 @@ def test_union_parameter_chaining(self): self.assertEqual((list[T] | list[S])[int, T], list[int] | list[T]) self.assertEqual((list[T] | list[S])[int, int], list[int]) + def test_union_parameter_default_ordering(self): + T = typing.TypeVar("T") + U = typing.TypeVar("U", default=int) + + self.assertEqual((list[U] | list[T]).__parameters__, (U, T)) + with self.assertRaises(TypeError): + list[U] | list[T] + def test_union_parameter_substitution(self): def eq(actual, expected, typed=True): self.assertEqual(actual, expected) @@ -967,6 +975,18 @@ def __eq__(self, other): with self.assertRaises(TypeError): issubclass(int, type_) + def test_generic_alias_subclass_with_defaults(self): + T = typing.TypeVar("T") + U = typing.TypeVar("U", default=int) + class MyGeneric: + __class_getitem__ = classmethod(types.GenericAlias) + + class Fine(MyGeneric[T, U]): + ... + + with self.assertRaises(TypeError): + class NonDefaultFollows(MyGeneric[U, T]): ... + def test_or_type_operator_with_bad_module(self): class BadMeta(type): __qualname__ = 'TypeVar' diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 888cb16edd1b46..3a7c469de36fe3 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -214,9 +214,11 @@ _Py_make_parameters(PyObject *args) if (parameters == NULL) return NULL; Py_ssize_t iparam = 0; + bool seen_default = false; for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { PyObject *t = PyTuple_GET_ITEM(args, iarg); PyObject *subst; + // We don't want __parameters__ descriptor of a bare Python class. if (PyType_Check(t)) { continue; @@ -225,7 +227,28 @@ _Py_make_parameters(PyObject *args) Py_DECREF(parameters); return NULL; } + printf("Here\n"); if (subst) { + printf("subst checking contains\n"); + PyObject_Print(t, stdout, 0); + PyObject_Print(parameters, stdout, 0); + PyObject *default_; + bool does_not_contain = tuple_index(parameters, nargs, t) == -1; + if (does_not_contain) { + if (_PyObject_LookupAttr(t, &_Py_ID(__default__), &default_) < 0) { + Py_DECREF(default_); + Py_DECREF(subst); + return NULL; + } + PyObject_Print(default_, stdout, 0); + if (default_ && !!Py_IsNone(default_)) { + seen_default = true; + } else if (seen_default) { + PyErr_Format(PyExc_TypeError, "TypeVarLike without a default follows one with a default"); + } + printf("\n%d seen default\n", seen_default); + } + iparam += tuple_add(parameters, iparam, t); Py_DECREF(subst); } @@ -248,7 +271,24 @@ _Py_make_parameters(PyObject *args) } } for (Py_ssize_t j = 0; j < len2; j++) { + PyObject *default_; PyObject *t2 = PyTuple_GET_ITEM(subparams, j); + + bool does_not_contain = tuple_index(parameters, nargs, t) == -1; + if (does_not_contain) { + if (_PyObject_LookupAttr(t2, &_Py_ID(__default__), &default_) < 0) { + Py_DECREF(default_); + Py_DECREF(subst); + return NULL; + } + PyObject_Print(default_, stdout, 0); + printf("\n%d seen default\n", seen_default); + if (default_ && !!Py_IsNone(default_)) { + seen_default = true; + } else if (seen_default) { + PyErr_Format(PyExc_TypeError, "TypeVarLike without a default follows one with a default"); + } + } iparam += tuple_add(parameters, iparam, t2); } } From fcb20ae21a95b1ae631c1f38a1b91a68ecd77f67 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Thu, 13 Apr 2023 15:47:57 +0100 Subject: [PATCH 03/11] Make error messages actually work --- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 2 ++ Lib/typing.py | 8 ++++-- Objects/genericaliasobject.c | 26 +++++++++---------- 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 166fe2af914225..804ce7d199793e 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -996,6 +996,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(kw2)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(lambda)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last_exc)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last_node)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last_traceback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last_type)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 2076e3640febf5..f9525743041b3a 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -482,6 +482,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(kw2) STRUCT_FOR_ID(lambda) STRUCT_FOR_ID(last) + STRUCT_FOR_ID(last_exc) STRUCT_FOR_ID(last_node) STRUCT_FOR_ID(last_traceback) STRUCT_FOR_ID(last_type) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index b64c272618503c..4b420916e239c0 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -988,6 +988,7 @@ extern "C" { INIT_ID(kw2), \ INIT_ID(lambda), \ INIT_ID(last), \ + INIT_ID(last_exc), \ INIT_ID(last_node), \ INIT_ID(last_traceback), \ INIT_ID(last_type), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 01f21d694b77df..3e1a71c87c89c4 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -870,6 +870,8 @@ _PyUnicode_InitStaticStrings(void) { PyUnicode_InternInPlace(&string); string = &_Py_ID(last); PyUnicode_InternInPlace(&string); + string = &_Py_ID(last_exc); + PyUnicode_InternInPlace(&string); string = &_Py_ID(last_node); PyUnicode_InternInPlace(&string); string = &_Py_ID(last_traceback); diff --git a/Lib/typing.py b/Lib/typing.py index 2cff6c67bb4ce2..a9f528cedf3c82 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -260,7 +260,9 @@ def _collect_parameters(args): if t.__default__ is not None: seen_default = True elif seen_default: - raise TypeError("TypeVarLike without a default follows one with a default") + raise TypeError( + f"TypeVarLike {t!r} without a default follows one with a default" + ) parameters.append(t) else: @@ -269,7 +271,9 @@ def _collect_parameters(args): if x.__default__ is not None: seen_default = True elif seen_default: - raise TypeError("TypeVarLike without a default follows one with a default") + raise TypeError( + f"TypeVarLike {t!r} without a default follows one with a default" + ) parameters.append(x) return tuple(parameters) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 3a7c469de36fe3..ac5f4ad1dad51e 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -227,11 +227,7 @@ _Py_make_parameters(PyObject *args) Py_DECREF(parameters); return NULL; } - printf("Here\n"); if (subst) { - printf("subst checking contains\n"); - PyObject_Print(t, stdout, 0); - PyObject_Print(parameters, stdout, 0); PyObject *default_; bool does_not_contain = tuple_index(parameters, nargs, t) == -1; if (does_not_contain) { @@ -240,13 +236,15 @@ _Py_make_parameters(PyObject *args) Py_DECREF(subst); return NULL; } - PyObject_Print(default_, stdout, 0); - if (default_ && !!Py_IsNone(default_)) { + if (!Py_IsNone(default_)) { seen_default = true; } else if (seen_default) { - PyErr_Format(PyExc_TypeError, "TypeVarLike without a default follows one with a default"); + return PyErr_Format( + PyExc_TypeError, + "type parameter %R without a default follows one with a default", + t + ); } - printf("\n%d seen default\n", seen_default); } iparam += tuple_add(parameters, iparam, t); @@ -274,19 +272,21 @@ _Py_make_parameters(PyObject *args) PyObject *default_; PyObject *t2 = PyTuple_GET_ITEM(subparams, j); - bool does_not_contain = tuple_index(parameters, nargs, t) == -1; + bool does_not_contain = tuple_index(parameters, nargs, t2) == -1; if (does_not_contain) { if (_PyObject_LookupAttr(t2, &_Py_ID(__default__), &default_) < 0) { Py_DECREF(default_); Py_DECREF(subst); return NULL; } - PyObject_Print(default_, stdout, 0); - printf("\n%d seen default\n", seen_default); - if (default_ && !!Py_IsNone(default_)) { + if (default_ && !Py_IsNone(default_)) { seen_default = true; } else if (seen_default) { - PyErr_Format(PyExc_TypeError, "TypeVarLike without a default follows one with a default"); + return PyErr_Format( + PyExc_TypeError, + "type parameter %R without a default follows one with a default", + t2 + ); } } iparam += tuple_add(parameters, iparam, t2); From ef4ab4e92ad6677d8deebfea71ef22f88b653e0c Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sun, 30 Apr 2023 22:17:17 +0100 Subject: [PATCH 04/11] Fix typo in ParamSpec default --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index a9f528cedf3c82..4eb7534f1c83a8 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1249,7 +1249,7 @@ def args(self): def kwargs(self): return ParamSpecKwargs(self) - def __init__(self, name, *, bound=None, default=None, covariant=False, contravariant=False): + def __init__(self, name, *, bound=None, default=_marker, covariant=False, contravariant=False): self.__name__ = name super().__init__(bound, covariant, contravariant) _DefaultMixin.__init__(self, default) From 5bddbadb1904b021ccd0d9fac578f3654cd551cd Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sun, 30 Apr 2023 23:33:45 +0100 Subject: [PATCH 05/11] Fix pointless changes --- Lib/test/test_typing.py | 67 ++++++++++++++++++++++-------------- Objects/genericaliasobject.c | 2 -- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index ef04e2af116db7..40d48e224de35d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1,35 +1,52 @@ -import abc -import collections import contextlib +import collections +from collections import defaultdict +from functools import lru_cache, wraps import inspect import itertools import pickle import re import sys +import warnings +from unittest import TestCase, main, skipUnless, skip +from unittest.mock import patch +from copy import copy, deepcopy + +from typing import Any, NoReturn, Never, assert_never +from typing import overload, get_overloads, clear_overloads +from typing import TypeVar, TypeVarTuple, Unpack, AnyStr +from typing import T, KT, VT # Not in __all__. +from typing import Union, Optional, Literal +from typing import Tuple, List, Dict, MutableMapping +from typing import Callable +from typing import Generic, ClassVar, Final, final, Protocol +from typing import assert_type, cast, runtime_checkable +from typing import get_type_hints +from typing import get_origin, get_args +from typing import override +from typing import is_typeddict +from typing import reveal_type +from typing import dataclass_transform +from typing import no_type_check, no_type_check_decorator +from typing import Type +from typing import NamedTuple, NotRequired, Required, TypedDict +from typing import IO, TextIO, BinaryIO +from typing import Pattern, Match +from typing import Annotated, ForwardRef +from typing import Self, LiteralString +from typing import TypeAlias +from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs +from typing import TypeGuard +import abc import textwrap -import types import typing -import warnings import weakref -from collections import defaultdict -from copy import copy, deepcopy -from functools import lru_cache, wraps -from test import _typed_dict_helper, mod_generics_cache -from test.support import captured_stderr, cpython_only, import_helper -from typing import (IO, KT, VT, Annotated, Any, AnyStr, # Not in __all__. - BinaryIO, Callable, ClassVar, Concatenate, Dict, Final, - ForwardRef, Generic, List, Literal, LiteralString, Match, - MutableMapping, NamedTuple, Never, NoReturn, NotRequired, - Optional, ParamSpec, ParamSpecArgs, ParamSpecKwargs, - Pattern, Protocol, Required, Self, T, TextIO, Tuple, Type, - TypeAlias, TypedDict, TypeGuard, TypeVar, TypeVarTuple, - Union, Unpack, assert_never, assert_type, cast, - clear_overloads, dataclass_transform, final, get_args, - get_origin, get_overloads, get_type_hints, is_typeddict, - no_type_check, no_type_check_decorator, overload, override, - reveal_type, runtime_checkable) -from unittest import TestCase, main, skip, skipUnless -from unittest.mock import patch +import types + +from test.support import import_helper, captured_stderr, cpython_only +from test import mod_generics_cache +from test import _typed_dict_helper + CANNOT_SUBCLASS_TYPE = 'Cannot subclass special typing classes' NOT_A_BASE_TYPE = "type 'typing.%s' is not an acceptable base type" @@ -4734,7 +4751,6 @@ def test_errors(self): # We need this to make sure that `@no_type_check` respects `__module__` attr: from test import ann_module8 - @no_type_check class NoTypeCheck_Outer: Inner = ann_module8.NoTypeCheck_Outer.Inner @@ -7344,7 +7360,7 @@ def stuff(a: BinaryIO) -> bytes: def test_io_submodule(self): with warnings.catch_warnings(record=True) as w: warnings.filterwarnings("default", category=DeprecationWarning) - from typing.io import IO, BinaryIO, TextIO, __all__, __name__ + from typing.io import IO, TextIO, BinaryIO, __all__, __name__ self.assertIs(IO, typing.IO) self.assertIs(TextIO, typing.TextIO) self.assertIs(BinaryIO, typing.BinaryIO) @@ -8568,7 +8584,6 @@ class AllTests(BaseTestCase): def test_all(self): from typing import __all__ as a - # Just spot-check the first and last of every category. self.assertIn('AbstractSet', a) self.assertIn('ValuesView', a) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index ac5f4ad1dad51e..7e4717b35d6c93 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -218,7 +218,6 @@ _Py_make_parameters(PyObject *args) for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { PyObject *t = PyTuple_GET_ITEM(args, iarg); PyObject *subst; - // We don't want __parameters__ descriptor of a bare Python class. if (PyType_Check(t)) { continue; @@ -271,7 +270,6 @@ _Py_make_parameters(PyObject *args) for (Py_ssize_t j = 0; j < len2; j++) { PyObject *default_; PyObject *t2 = PyTuple_GET_ITEM(subparams, j); - bool does_not_contain = tuple_index(parameters, nargs, t2) == -1; if (does_not_contain) { if (_PyObject_LookupAttr(t2, &_Py_ID(__default__), &default_) < 0) { From 85f3973bf776cf7c112877d212a1736af0a8e7fb Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 30 Apr 2023 23:37:32 +0100 Subject: [PATCH 06/11] Respond to initial review --- Lib/typing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 98d349ce83c48f..269286c00e4729 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -274,22 +274,22 @@ def _collect_parameters(args): parameters.append(collected) elif hasattr(t, '__typing_subst__'): if t not in parameters: - if t.__default__ is not None: + if hasattr(t, "__default__") and t.__default__ is not None: seen_default = True elif seen_default: raise TypeError( - f"TypeVarLike {t!r} without a default follows one with a default" + f"Type parameter {t!r} without a default follows one with a default" ) parameters.append(t) else: for x in getattr(t, '__parameters__', ()): if x not in parameters: - if x.__default__ is not None: + if hasattr(x, "__default__") and x.__default__ is not None: seen_default = True elif seen_default: raise TypeError( - f"TypeVarLike {t!r} without a default follows one with a default" + f"Type parameter {t!r} without a default follows one with a default" ) parameters.append(x) From 1f4b8c405deaaa23449eabf4b4a4011ab4eada63 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 30 Apr 2023 23:40:09 +0100 Subject: [PATCH 07/11] Standardise error message --- Lib/typing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 269286c00e4729..ee15d7a595cafc 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -278,7 +278,7 @@ def _collect_parameters(args): seen_default = True elif seen_default: raise TypeError( - f"Type parameter {t!r} without a default follows one with a default" + f"non-default type parameter {t!r} follows default type parameter" ) parameters.append(t) @@ -289,7 +289,7 @@ def _collect_parameters(args): seen_default = True elif seen_default: raise TypeError( - f"Type parameter {t!r} without a default follows one with a default" + f"non-default type parameter {x!r} follows default type parameter" ) parameters.append(x) From 2ac3116d578820994e5b22876bd963483c3e45a8 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 30 Apr 2023 23:43:32 +0100 Subject: [PATCH 08/11] But wait there's more --- Objects/genericaliasobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 7e4717b35d6c93..720e7f57d246cf 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -240,7 +240,7 @@ _Py_make_parameters(PyObject *args) } else if (seen_default) { return PyErr_Format( PyExc_TypeError, - "type parameter %R without a default follows one with a default", + "non-default type parameter %R follows default type parameter", t ); } @@ -282,7 +282,7 @@ _Py_make_parameters(PyObject *args) } else if (seen_default) { return PyErr_Format( PyExc_TypeError, - "type parameter %R without a default follows one with a default", + "non-default type parameter %R follows default type parameter", t2 ); } From 77ce496f369b11cc9271b0765cb35afefc5df618 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Sun, 30 Apr 2023 23:47:26 +0100 Subject: [PATCH 09/11] Documentation be gone! --- Doc/library/typing.rst | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index e1578a7cc5599c..3bf925f704ac02 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1302,22 +1302,6 @@ These are not used in annotations. They are building blocks for creating generic c = concatenate('one', b'two') # error: type variable 'A' can be either str or bytes in a function call, but not both - If a generic type is commonly generic over just one type you can use - ``default`` to specify this type:: - - T = TypeVar("T", default=int) - - class Box(Generic[T]): - def __init__(self, value: T | None = None): - self.value = value - - reveal_type(Box()) # type is Box[int] - reveal_type(Box(value="Hello World!")) # type is Box[str] - - A TypeVar without a default cannot follow a TypeVar with a default. - - .. TODO add more about this - At runtime, ``isinstance(x, T)`` will raise :exc:`TypeError`. In general, :func:`isinstance` and :func:`issubclass` should not be used with types. @@ -1421,8 +1405,6 @@ These are not used in annotations. They are building blocks for creating generic See :pep:`646` for more details on type variable tuples. - .. TODO docs on default - .. versionadded:: 3.11 .. data:: Unpack @@ -1468,7 +1450,7 @@ These are not used in annotations. They are building blocks for creating generic .. versionadded:: 3.11 -.. class:: ParamSpec(name, *, bound=None, default=..., covariant=False, contravariant=False) +.. class:: ParamSpec(name, *, bound=None covariant=False, contravariant=False) Parameter specification variable. A specialized version of :class:`type variables `. From 037ac22de4879cb6c79b47527ddbbf2548e69557 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 1 May 2023 21:38:13 -0700 Subject: [PATCH 10/11] Update Doc/library/typing.rst --- Doc/library/typing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 3bf925f704ac02..409a95d528b5d3 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1450,7 +1450,7 @@ These are not used in annotations. They are building blocks for creating generic .. versionadded:: 3.11 -.. class:: ParamSpec(name, *, bound=None covariant=False, contravariant=False) +.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False) Parameter specification variable. A specialized version of :class:`type variables `. From a05c2f31f580bd001dcac34668bb3cbd8d3e0eb0 Mon Sep 17 00:00:00 2001 From: Gobot1234 Date: Thu, 22 Jun 2023 13:39:58 +0100 Subject: [PATCH 11/11] DECREF in right? place --- Objects/genericaliasobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 720e7f57d246cf..81ca783d77db4e 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -231,7 +231,6 @@ _Py_make_parameters(PyObject *args) bool does_not_contain = tuple_index(parameters, nargs, t) == -1; if (does_not_contain) { if (_PyObject_LookupAttr(t, &_Py_ID(__default__), &default_) < 0) { - Py_DECREF(default_); Py_DECREF(subst); return NULL; } @@ -244,6 +243,7 @@ _Py_make_parameters(PyObject *args) t ); } + Py_DECREF(default_); } iparam += tuple_add(parameters, iparam, t); @@ -273,7 +273,6 @@ _Py_make_parameters(PyObject *args) bool does_not_contain = tuple_index(parameters, nargs, t2) == -1; if (does_not_contain) { if (_PyObject_LookupAttr(t2, &_Py_ID(__default__), &default_) < 0) { - Py_DECREF(default_); Py_DECREF(subst); return NULL; } @@ -286,6 +285,7 @@ _Py_make_parameters(PyObject *args) t2 ); } + Py_DECREF(default_); } iparam += tuple_add(parameters, iparam, t2); }