-
-
Notifications
You must be signed in to change notification settings - Fork 31.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-44796: Unify TypeVar and ParamSpec substitution #31143
Changes from 1 commit
a03c0d1
af9a60c
e4345da
845676d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -174,11 +174,13 @@ def _type_check(arg, msg, is_argument=True, module=None, *, 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, ClassVar, Final): | ||
if arg in (Any, NoReturn): | ||
return arg | ||
if allow_special_forms and arg in (ClassVar, Final): | ||
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)): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this affect putting ParamSpec inside Annotated? Looks like we don't have tests for that yet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked into this. This check is needed in order to allow instances of n.b. this is moot if #31151 gets merged since this line is removed entirely There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not convinced it was the intent of the spec here to disallow from typing import Callable, TypeVar, ParamSpec, get_type_hints
T = TypeVar("T")
P = ParamSpec("P")
def add_logging(f: Callable["P", T]):
pass
get_type_hints(add_logging) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I re-added ParamSpec. Now some errors will not be caught at runtime. We will return to this in future. |
||
return arg | ||
if not callable(arg): | ||
raise TypeError(f"{msg} Got {arg!r:.100}.") | ||
|
@@ -211,21 +213,22 @@ def _type_repr(obj): | |
return repr(obj) | ||
|
||
|
||
def _collect_type_vars(types_, typevar_types=None): | ||
"""Collect all type variable contained | ||
in types in order of first appearance (lexicographic order). For example:: | ||
def _collect_parameters(args): | ||
"""Collect all type variables and parameter specifications in args | ||
in order of first appearance (lexicographic order). For example:: | ||
|
||
_collect_type_vars((T, List[S, T])) == (T, S) | ||
_collect_parameters((T, Callable[P, T])) == (T, P) | ||
""" | ||
if typevar_types is None: | ||
typevar_types = TypeVar | ||
tvars = [] | ||
for t in types_: | ||
if isinstance(t, typevar_types) and t not in tvars: | ||
tvars.append(t) | ||
if isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)): | ||
tvars.extend([t for t in t.__parameters__ if t not in tvars]) | ||
return tuple(tvars) | ||
parameters = [] | ||
for t in args: | ||
if hasattr(t, '__typing_subst__'): | ||
if t not in parameters: | ||
parameters.append(t) | ||
else: | ||
for x in getattr(t, '__parameters__', ()): | ||
if x not in parameters: | ||
parameters.append(x) | ||
return tuple(parameters) | ||
|
||
|
||
def _check_generic(cls, parameters, elen): | ||
|
@@ -818,6 +821,11 @@ def __init__(self, name, *constraints, bound=None, | |
if def_mod != 'typing': | ||
self.__module__ = def_mod | ||
|
||
def __typing_subst__(self, arg): | ||
msg = "Parameters to generic types must be types." | ||
arg = _type_check(arg, msg, is_argument=True) | ||
return arg | ||
|
||
|
||
class ParamSpecArgs(_Final, _Immutable, _root=True): | ||
"""The args for a ParamSpec object. | ||
|
@@ -918,6 +926,14 @@ def __init__(self, name, *, bound=None, covariant=False, contravariant=False): | |
if def_mod != 'typing': | ||
self.__module__ = def_mod | ||
|
||
def __typing_subst__(self, arg): | ||
if isinstance(arg, (list, tuple)): | ||
arg = tuple(_type_check(a, "Expected a type.") for a in arg) | ||
elif not _is_param_expr(arg): | ||
raise TypeError(f"Expected a list of types, an ellipsis, " | ||
f"ParamSpec, or Concatenate. Got {arg}") | ||
return arg | ||
|
||
|
||
def _is_dunder(attr): | ||
return attr.startswith('__') and attr.endswith('__') | ||
|
@@ -972,7 +988,7 @@ def __getattr__(self, attr): | |
|
||
def __setattr__(self, attr, val): | ||
if _is_dunder(attr) or attr in {'_name', '_inst', '_nparams', | ||
'_typevar_types', '_paramspec_tvars'}: | ||
'_paramspec_tvars'}: | ||
super().__setattr__(attr, val) | ||
else: | ||
setattr(self.__origin__, attr, val) | ||
|
@@ -1001,16 +1017,14 @@ def __dir__(self): | |
|
||
class _GenericAlias(_BaseGenericAlias, _root=True): | ||
def __init__(self, origin, params, *, inst=True, name=None, | ||
_typevar_types=TypeVar, | ||
_paramspec_tvars=False): | ||
super().__init__(origin, inst=inst, name=name) | ||
if not isinstance(params, tuple): | ||
params = (params,) | ||
self.__args__ = tuple(... if a is _TypingEllipsis else | ||
() if a is _TypingEmpty else | ||
a for a in params) | ||
self.__parameters__ = _collect_type_vars(params, typevar_types=_typevar_types) | ||
self._typevar_types = _typevar_types | ||
self.__parameters__ = _collect_parameters(params) | ||
self._paramspec_tvars = _paramspec_tvars | ||
if not name: | ||
self.__module__ = origin.__module__ | ||
|
@@ -1047,16 +1061,11 @@ def __getitem__(self, params): | |
subst = dict(zip(self.__parameters__, params)) | ||
new_args = [] | ||
for arg in self.__args__: | ||
if isinstance(arg, self._typevar_types): | ||
if isinstance(arg, ParamSpec): | ||
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] | ||
elif isinstance(arg, (_GenericAlias, GenericAlias, types.UnionType)): | ||
subparams = arg.__parameters__ | ||
substfunc = getattr(arg, '__typing_subst__', None) | ||
if substfunc: | ||
arg = substfunc(subst[arg]) | ||
else: | ||
subparams = getattr(arg, '__parameters__', ()) | ||
if subparams: | ||
subargs = tuple(subst[x] for x in subparams) | ||
arg = arg[subargs] | ||
|
@@ -1172,7 +1181,6 @@ class _CallableType(_SpecialGenericAlias, _root=True): | |
def copy_with(self, params): | ||
return _CallableGenericAlias(self.__origin__, params, | ||
name=self._name, inst=self._inst, | ||
_typevar_types=(TypeVar, ParamSpec), | ||
_paramspec_tvars=True) | ||
|
||
def __getitem__(self, params): | ||
|
@@ -1272,7 +1280,6 @@ def __hash__(self): | |
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): | ||
|
@@ -1333,7 +1340,6 @@ def __class_getitem__(cls, params): | |
else: | ||
_check_generic(cls, params, len(cls.__parameters__)) | ||
return _GenericAlias(cls, params, | ||
_typevar_types=(TypeVar, ParamSpec), | ||
_paramspec_tvars=True) | ||
|
||
def __init_subclass__(cls, *args, **kwargs): | ||
|
@@ -1346,7 +1352,7 @@ def __init_subclass__(cls, *args, **kwargs): | |
if error: | ||
raise TypeError("Cannot inherit from plain Generic") | ||
if '__orig_bases__' in cls.__dict__: | ||
tvars = _collect_type_vars(cls.__orig_bases__, (TypeVar, ParamSpec)) | ||
tvars = _collect_parameters(cls.__orig_bases__) | ||
# Look for Generic[T1, ..., Tn]. | ||
# If found, tvars must be a subset of it. | ||
# If not found, tvars is it. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have to keep
__parameters__
, it's de facto a public API by now.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is inherited from GenericAlias now.