From b25eec4438d76ab7b7730c022b13ff01e4a34a61 Mon Sep 17 00:00:00 2001 From: Steven Troxler Date: Thu, 13 Jan 2022 09:05:06 -0800 Subject: [PATCH 01/10] PEP 677: Add Detailed Runtime Behavior Spec --- pep-0677.rst | 107 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 19 deletions(-) diff --git a/pep-0677.rst b/pep-0677.rst index 2e182176477..7821005b23b 100644 --- a/pep-0677.rst +++ b/pep-0677.rst @@ -635,24 +635,92 @@ callable types and ``=>`` for lambdas. Runtime Behavior ---------------- -Our tentative plan is that: +The new AST nodes need to evaluate to runtime types, and we have two goals for the +behavior of these runtime types: -- The ``__repr__`` will show an arrow syntax literal. -- We will provide a new API where the runtime data structure can be - accessed in the same manner as the AST data structure. -- We will ensure that we provide an API that is backward-compatible - with ``typing.Callable`` and ``typing.Concatenate``, specifically - the behavior of ``__args__`` and ``__parameters__``. +- They should expose a structured API as similar as possible to the AST where + the parameter and return types have descriptive names. +- They should also expose an API that is fully backward-compatible with + ``typing.Callable`` -Because these details are still under debate we are currently -maintaining `a separate doc -`_ -with details about the new builtins, the evaluation model, how to -provide both a backward-compatible and more structured API, and -possible alternatives to the current plan. +Evaluation and Structured API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We intend to create new builtin types to which the new AST nodes will +evaluate, exposing them in the ``types`` module. + +The ``CallableType`` and ``AsyncCallableType`` AST nodes should evaluate +to ``types.CallableType`` and ``types.AsyncCallableType``. Each of these +should have fields: + + - ``args`` containing an evaluated arguments list (see below) + - ``returns`` containing the evaluated return type + +The arguments list AST nodes should evaluate as follows: +- ``AnyArguments`` evaluates to ``types.CallableAnyArguments`` + + - This type has no fields + +- ``ArgumentsList`` evaluates to ``types.CallableARgumentsList``, having + + - A field ``posonlyargs`` which will be a tuple of all parameter types. + +- ``Concatenation`` evaluates to ``types.CallableConcatenation``, having + + - A field ``posonlyargs`` which will be a tuple of the normal parameter types. + - A field ``param_spec`` which will be the final ``ParamSpec`` argument. + +Backward-Compatible API +~~~~~~~~~~~~~~~~~~~~~~~ + +To get backward compatibility with the existing ``types.Callable`` API, +which relies on fields ``__args__`` and ``__parameters__``, we define them +as follows: + +- For ``types.CallableType``: + + - ``__args__`` should be the tuple result of appending the return type to the + result of fetching ``__args__`` on ``args`` (see below). + - ``__parameters__`` should be the result of concatenating ``__parameters__`` + of the arguments list with ``__parameters__`` of the return type. + +- ``types.AsyncCallableType`` should work the same, except that its return + value should automatically be wrapped in a ``typing.Awaitable`` when + evaluating ``__args__``. +- For ``types.CallableAnyAguments``: + + - ``__args__`` should evaluate to ``(Ellipses,)``. + - ``__parameters__`` should evaluate to ``()``. + +- For ``types.CallableArgumentsList``: + + - ``__args__`` should evaluate to ``posonlyargs``. + - ``__parameters__`` should be the result of concatenating the + ``__parameters__`` of each type in ``posonlyargs``. + +- For ``types.CallableConcatenation``: + + - ``__args__`` should be the tuple result of appending ``param_spec`` to + ``posonlyargs``. + - ``__parameters__`` should be the result of concatinating the + ``__parameters__`` of each type in ``posonlyargs``, and also. + + + +Behavior of other methods +~~~~~~~~~~~~~~~~~~~~~~~~~ + +- The ``__eq__`` method should treat equivalent ``typing.Callable`` + values as equal to values constructed using the builtin syntax. +- The ``__repr__`` method of ``types.CallableType`` and + ``types.AsyncCallableType`` should produce a representation in arrow syntax. + + - The argument list types should produce a placeholder for ``__repr__`` + rather than a pretty tuple-like value because they cannot be evaluated + except inside of a full arrow type literal. + - But we can pretty-print them in their ``__str__`` method and use that + in the ``__repr__`` method of ``types.CallableType``. -Once the plan is finalized we will include a full specification of -runtime behavior in this section of the PEP. Rejected Alternatives ===================== @@ -1033,10 +1101,11 @@ Open Issues Details of the Runtime API -------------------------- -Once we have finalized all details of the runtime behavior, we -will need to add a full specification of the behavior to the -`Runtime Behavior`_ section of this PEP as well as include that -behavior in our reference implementation. +We have attempted to provide a complete behavior specification in +the `Runtime Behavior`_ section of this PEP. + +But there are probably more details that we will not realize we +need to define until we build a full reference implementation. Optimizing ``SyntaxError`` messages ----------------------------------- From 89c313fb42b72eae702934c4442e17475ddb0ef8 Mon Sep 17 00:00:00 2001 From: Steven Troxler Date: Fri, 14 Jan 2022 06:00:41 -0800 Subject: [PATCH 02/10] Rework: flatten the API some, use python pseudocode rather than prose --- pep-0677.rst | 190 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 128 insertions(+), 62 deletions(-) diff --git a/pep-0677.rst b/pep-0677.rst index 7821005b23b..bc33a98b7bb 100644 --- a/pep-0677.rst +++ b/pep-0677.rst @@ -638,10 +638,11 @@ Runtime Behavior The new AST nodes need to evaluate to runtime types, and we have two goals for the behavior of these runtime types: -- They should expose a structured API as similar as possible to the AST where - the parameter and return types have descriptive names. +- They should expose a structured API that is descriptive and powerful + enough to be compatible with extending the type to include new features + like named and variadic arguments. - They should also expose an API that is fully backward-compatible with - ``typing.Callable`` + ``typing.Callable``. Evaluation and Structured API ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -649,77 +650,92 @@ Evaluation and Structured API We intend to create new builtin types to which the new AST nodes will evaluate, exposing them in the ``types`` module. -The ``CallableType`` and ``AsyncCallableType`` AST nodes should evaluate -to ``types.CallableType`` and ``types.AsyncCallableType``. Each of these -should have fields: +Our plan is to expose a structured API as if they were defined as follows:: - - ``args`` containing an evaluated arguments list (see below) - - ``returns`` containing the evaluated return type - -The arguments list AST nodes should evaluate as follows: -- ``AnyArguments`` evaluates to ``types.CallableAnyArguments`` - - - This type has no fields + class CallableType: + is_async: bool + arguments: Ellipses | CallableArgumentsList | CallableConcatenation + return_type:: Typing.type + + class CallableArgumentsList: + positional_only: tuple[typing.Type] -- ``ArgumentsList`` evaluates to ``types.CallableARgumentsList``, having + class CallableConcatenation: + positional_only: tuple[typing.Type] + param_spec: typing.ParamSpec - - A field ``posonlyargs`` which will be a tuple of all parameter types. +The evaluation model is that: -- ``Concatenation`` evaluates to ``types.CallableConcatenation``, having + - ``AnyArguments`` evaluates to ``Ellipses``. + - ``ArgumentsList`` evaluates to ``CallableArgumentsList``. + - ``Concatenation`` evaluates to ``CallableConcatenation``. + - To evaluate a ``CallableType`` or ``AsyncCallableType`` to a + ``types.CallableType`` value ``t``: - - A field ``posonlyargs`` which will be a tuple of the normal parameter types. - - A field ``param_spec`` which will be the final ``ParamSpec`` argument. + - Define ``t.is_async`` in the obvious way + - Evaluate ``arguments`` to define ``t.arguments`` + - Evaluate ``returns`` to define ``t.return_type`` Backward-Compatible API ~~~~~~~~~~~~~~~~~~~~~~~ To get backward compatibility with the existing ``types.Callable`` API, -which relies on fields ``__args__`` and ``__parameters__``, we define them -as follows: - -- For ``types.CallableType``: - - - ``__args__`` should be the tuple result of appending the return type to the - result of fetching ``__args__`` on ``args`` (see below). - - ``__parameters__`` should be the result of concatenating ``__parameters__`` - of the arguments list with ``__parameters__`` of the return type. - -- ``types.AsyncCallableType`` should work the same, except that its return - value should automatically be wrapped in a ``typing.Awaitable`` when - evaluating ``__args__``. -- For ``types.CallableAnyAguments``: - - - ``__args__`` should evaluate to ``(Ellipses,)``. - - ``__parameters__`` should evaluate to ``()``. - -- For ``types.CallableArgumentsList``: - - - ``__args__`` should evaluate to ``posonlyargs``. - - ``__parameters__`` should be the result of concatenating the - ``__parameters__`` of each type in ``posonlyargs``. - -- For ``types.CallableConcatenation``: - - - ``__args__`` should be the tuple result of appending ``param_spec`` to - ``posonlyargs``. - - ``__parameters__`` should be the result of concatinating the - ``__parameters__`` of each type in ``posonlyargs``, and also. - - - -Behavior of other methods -~~~~~~~~~~~~~~~~~~~~~~~~~ +which relies on fields ``__args__`` and ``__parameters__``, we can define +them as if they were written in terms of the following:: + + import itertools + import typing + + def get_args( + t: CallableType + ): + return_type_arg = ( + typing.Awaitable[t.return_type] + if t.is_async + else t.return_type + ) + arguments = t.arguments + if isinstance(arguments, Ellipses): + argument_args = (Ellipses,) + elif isinstance(arguments, CallableArgumentsList): + argument_args = arguments.positional_only + else: + argument_args = (*arguments.positional_only, arguments.param_spec) + return (*arguments_args, return_type_arg) + + def _positional_only_parameters( + positional_only: tuple[typing.Type] + ): + return tuple(itertools.chain( + arg.__parameters__ for arg in positional_only + )) + + def get_parameters(t: CallableType): + arguments = t.arguments + if isinstance(arguments, Ellipses): + argument_parameters = () + elif isinstance(arguments, Typ): + argument_parameters = _positional_only_parameters( + arguments.positional_only + ) + else: + argument_parameters = ( + *_positional_only_parameters(arguments.positional_only) + arguments.param_spec + ) + return ( + *argument_parameters, + *t.return_type.__parameters__ + ) + +Additional Behaviors of ``types.CallableType`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - The ``__eq__`` method should treat equivalent ``typing.Callable`` - values as equal to values constructed using the builtin syntax. -- The ``__repr__`` method of ``types.CallableType`` and - ``types.AsyncCallableType`` should produce a representation in arrow syntax. - - - The argument list types should produce a placeholder for ``__repr__`` - rather than a pretty tuple-like value because they cannot be evaluated - except inside of a full arrow type literal. - - But we can pretty-print them in their ``__str__`` method and use that - in the ``__repr__`` method of ``types.CallableType``. + values as equal to values constructed using the builtin syntax, and + otherwise should behave like the ``__eq__`` of ``typing.Callable``. +- The ``__repr__`` method should produce an arrow syntax representation that, + when evaluated, gives us back an equal ``types.CallableType`` instance. Rejected Alternatives @@ -1059,6 +1075,56 @@ Moreover, none of these ideas help as much with reducing verbosity as the current proposal, nor do they introduce as strong a visual cue as the ``->`` between the parameter types and the return type. +Alternative Runtime Behaviors +----------------------------- + +The hard requirements on our runtime API are that: + +- It must preserve backward compatibility with ``typing.Callable`` via + ``__args__`` and ``__params__``. +- It must provide a structured API, which should be extensible if + in the future we try to support named and variadic arguments. + +Using a flat API +~~~~~~~~~~~~~~~~ + +Our runtime API mirrors the AST in that we distinguish the case +where the arguments is ``Ellipses`` versus ``CallableArgumentsList`` +versus ``CallableConcatenation``. + +An alternative would be just have a single ``types.CallableType`` with +fields +- ``is_async: bool`` +- ``is_any_arguments: bool`` +- ``positional_only: tuple[typing.Type]`` +- ``param_spec: typing.ParamSpec | None`` + +This would make the implementation simpler. But it becomes harder +to see which combinations of fields are legal, and to use pattern +matching or refinement to understand what the type represents. + +Since the users of this API would be developers implementing runtime typing +libraries, our view is that they would likely prefer the more structured API +we have proposed, in which legal combinations of arguments-related fields can be +determined by matching against the type of the ``arguments`` field. + +Using the plain return type in ``__args__`` for async types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is debatable whether we are required to preserve backward compatiblity +of ``__args__`` for async callable types like ``async (int) -> str``. The +reason is that one could argue they are not expressible directly +using ``typing.Callable``, and therefore it would be fine to set +``__args__`` as ``(int, int)`` rather than ``(int, typing.Awaitable[int])``. + +But we believe this would be problematic. By preserving the appearance +of a backward-compatible API while actually breaking its semantics on +async types, we would cause runtime type libraries that attempt to +interpret ``Callable`` using ``__args__`` to fail silently. + +It is for this reason that we automatically wrap the return type in +``Awaitable``. + Backward Compatibility ====================== From 2cdf37d843fdafb83666598671c857326c2efcca Mon Sep 17 00:00:00 2001 From: Steven Troxler Date: Fri, 14 Jan 2022 06:14:15 -0800 Subject: [PATCH 03/10] Change Ellipses to builtins.Ellipsis --- pep-0677.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pep-0677.rst b/pep-0677.rst index bc33a98b7bb..196e5b4a410 100644 --- a/pep-0677.rst +++ b/pep-0677.rst @@ -654,7 +654,7 @@ Our plan is to expose a structured API as if they were defined as follows:: class CallableType: is_async: bool - arguments: Ellipses | CallableArgumentsList | CallableConcatenation + arguments: builtins.Ellipses | CallableArgumentsList | CallableConcatenation return_type:: Typing.type class CallableArgumentsList: @@ -666,7 +666,7 @@ Our plan is to expose a structured API as if they were defined as follows:: The evaluation model is that: - - ``AnyArguments`` evaluates to ``Ellipses``. + - ``AnyArguments`` evaluates to ``builtins.Ellipsis``. - ``ArgumentsList`` evaluates to ``CallableArgumentsList``. - ``Concatenation`` evaluates to ``CallableConcatenation``. - To evaluate a ``CallableType`` or ``AsyncCallableType`` to a @@ -1089,7 +1089,7 @@ Using a flat API ~~~~~~~~~~~~~~~~ Our runtime API mirrors the AST in that we distinguish the case -where the arguments is ``Ellipses`` versus ``CallableArgumentsList`` +where the arguments is ``Ellipsis`` versus ``CallableArgumentsList`` versus ``CallableConcatenation``. An alternative would be just have a single ``types.CallableType`` with From d46ec9c7dda4ee08848cabe9ef4fae6739fa1dbe Mon Sep 17 00:00:00 2001 From: Steven Troxler Date: Fri, 14 Jan 2022 06:17:30 -0800 Subject: [PATCH 04/10] Update pep-0677.rst Co-authored-by: Jelle Zijlstra --- pep-0677.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0677.rst b/pep-0677.rst index 196e5b4a410..c7618af56ba 100644 --- a/pep-0677.rst +++ b/pep-0677.rst @@ -654,7 +654,7 @@ Our plan is to expose a structured API as if they were defined as follows:: class CallableType: is_async: bool - arguments: builtins.Ellipses | CallableArgumentsList | CallableConcatenation + arguments: builtins.Ellipsis | CallableArgumentsList | CallableConcatenation return_type:: Typing.type class CallableArgumentsList: From e8c71b831fdbcebf3d9bb5ddcb2911a41567b7cd Mon Sep 17 00:00:00 2001 From: Steven Troxler Date: Sat, 15 Jan 2022 11:22:40 -0800 Subject: [PATCH 05/10] Switch to an inspect.Signature-like API --- pep-0677.rst | 123 +++++++++++++++++++++++---------------------------- 1 file changed, 56 insertions(+), 67 deletions(-) diff --git a/pep-0677.rst b/pep-0677.rst index c7618af56ba..de84087c590 100644 --- a/pep-0677.rst +++ b/pep-0677.rst @@ -654,27 +654,50 @@ Our plan is to expose a structured API as if they were defined as follows:: class CallableType: is_async: bool - arguments: builtins.Ellipsis | CallableArgumentsList | CallableConcatenation + arguments: Ellipsis | tuple[CallableTypeArgument] return_type:: Typing.type - - class CallableArgumentsList: - positional_only: tuple[typing.Type] - class CallableConcatenation: - positional_only: tuple[typing.Type] - param_spec: typing.ParamSpec + class CallableTypeArgument: + kind: CallableTypeArgumentKind + annotation: typing.Type | typing.TypeVar | typing.ParamSpec -The evaluation model is that: + class CallableTypeArgumentKind(Enum): + POSITIONAL_ONLY: int = ... + PARAM_SPEC: int = ... - - ``AnyArguments`` evaluates to ``builtins.Ellipsis``. - - ``ArgumentsList`` evaluates to ``CallableArgumentsList``. - - ``Concatenation`` evaluates to ``CallableConcatenation``. - - To evaluate a ``CallableType`` or ``AsyncCallableType`` to a - ``types.CallableType`` value ``t``: - - Define ``t.is_async`` in the obvious way - - Evaluate ``arguments`` to define ``t.arguments`` - - Evaluate ``returns`` to define ``t.return_type`` +The evaluation rules are expressed in terms of the following +pseudocode:: + + def evaluate_callable_type(callable_type): + return CallableType( + is_async=isinstance(callable_type, ast.AsyncCallableType), + arguments=evaluate_arguments(callable_type.arguments), + return_type=evaluate_expression(callable_type.returns), + ) + + def evaluate_arguments(arguments): + match arguments: + case ast.AnyArguments(): + return Ellipsis + case ast.ArgumentsList(posonlyargs): + return tuple( + evaluate_arg(arg) for arg in args + ) + case ast.ArgumentsListConcatenation(posonlyargs, param_spec): + return tuple( + *(evaluate_arg(arg) for arg in args), + evaluate_arg(arg=param_spec, kind=PARAM_SPEC) + ) + if isinstance(arguments, Any + return Ellipsis + + def evaluate_arg(arg, kind=POSITIONAL_ONLY): + return CallableTypeArgument( + kind=POSITIONAL_ONLY, + annotation=evaluate_expression(value) + ) + Backward-Compatible API ~~~~~~~~~~~~~~~~~~~~~~~ @@ -695,37 +718,13 @@ them as if they were written in terms of the following:: else t.return_type ) arguments = t.arguments - if isinstance(arguments, Ellipses): - argument_args = (Ellipses,) - elif isinstance(arguments, CallableArgumentsList): - argument_args = arguments.positional_only + if isinstance(arguments, Ellipsis): + argument_args = (Ellipsis,) else: - argument_args = (*arguments.positional_only, arguments.param_spec) - return (*arguments_args, return_type_arg) - - def _positional_only_parameters( - positional_only: tuple[typing.Type] - ): - return tuple(itertools.chain( - arg.__parameters__ for arg in positional_only - )) - - def get_parameters(t: CallableType): - arguments = t.arguments - if isinstance(arguments, Ellipses): - argument_parameters = () - elif isinstance(arguments, Typ): - argument_parameters = _positional_only_parameters( - arguments.positional_only - ) - else: - argument_parameters = ( - *_positional_only_parameters(arguments.positional_only) - arguments.param_spec - ) + argument_args = (arg.annotation for arg in arguments) return ( - *argument_parameters, - *t.return_type.__parameters__ + *arguments_args, + return_type_arg ) Additional Behaviors of ``types.CallableType`` @@ -992,9 +991,9 @@ We rejected this change because: syntax errors. - Moreover, if a type is complicated enough that readability is a concern we can always use type aliases, for example:: - + IntToIntFunction: (int) -> int - + def make_adder() -> IntToIntFunction: return lambda x: x + 1 @@ -1085,28 +1084,18 @@ The hard requirements on our runtime API are that: - It must provide a structured API, which should be extensible if in the future we try to support named and variadic arguments. -Using a flat API +Alternative APIs ~~~~~~~~~~~~~~~~ -Our runtime API mirrors the AST in that we distinguish the case -where the arguments is ``Ellipsis`` versus ``CallableArgumentsList`` -versus ``CallableConcatenation``. - -An alternative would be just have a single ``types.CallableType`` with -fields -- ``is_async: bool`` -- ``is_any_arguments: bool`` -- ``positional_only: tuple[typing.Type]`` -- ``param_spec: typing.ParamSpec | None`` - -This would make the implementation simpler. But it becomes harder -to see which combinations of fields are legal, and to use pattern -matching or refinement to understand what the type represents. - -Since the users of this API would be developers implementing runtime typing -libraries, our view is that they would likely prefer the more structured API -we have proposed, in which legal combinations of arguments-related fields can be -determined by matching against the type of the ``arguments`` field. +We considered having the runtime data ``types.CallableType`` use a +more structured API where there would be separate fields for +``posonlyargs`` and ``param_spec``. The current proposal was +was inspired by the ``inspect.Signature`` type. + +We use "argument" in our field and type names, unlike "parameter" +as in ``inspect.Signature``, in order to avoid confusion with +the ``callable_type.__parameters__`` field from the legacy API +that refers to type parameters rather than callable parameters. Using the plain return type in ``__args__`` for async types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From c1df8f588fa321ab4a64b6e9ece95fb66b9d05ba Mon Sep 17 00:00:00 2001 From: Steven Troxler Date: Tue, 18 Jan 2022 08:38:24 -0800 Subject: [PATCH 06/10] Apply suggestions from code review Co-authored-by: Guido van Rossum --- pep-0677.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pep-0677.rst b/pep-0677.rst index de84087c590..b8e5f88e810 100644 --- a/pep-0677.rst +++ b/pep-0677.rst @@ -655,7 +655,7 @@ Our plan is to expose a structured API as if they were defined as follows:: class CallableType: is_async: bool arguments: Ellipsis | tuple[CallableTypeArgument] - return_type:: Typing.type + return_type: typing.Type class CallableTypeArgument: kind: CallableTypeArgumentKind @@ -709,9 +709,7 @@ them as if they were written in terms of the following:: import itertools import typing - def get_args( - t: CallableType - ): + def get_args(t: CallableType): return_type_arg = ( typing.Awaitable[t.return_type] if t.is_async From 6af28c3658653316717b919b688a04a89a305cd1 Mon Sep 17 00:00:00 2001 From: Steven Troxler Date: Tue, 18 Jan 2022 09:14:53 -0800 Subject: [PATCH 07/10] Changes in response to code review comments --- pep-0677.rst | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/pep-0677.rst b/pep-0677.rst index b8e5f88e810..5ca37fa3b3c 100644 --- a/pep-0677.rst +++ b/pep-0677.rst @@ -641,7 +641,7 @@ behavior of these runtime types: - They should expose a structured API that is descriptive and powerful enough to be compatible with extending the type to include new features like named and variadic arguments. -- They should also expose an API that is fully backward-compatible with +- They should also expose an API that is backward-compatible with ``typing.Callable``. Evaluation and Structured API @@ -655,13 +655,14 @@ Our plan is to expose a structured API as if they were defined as follows:: class CallableType: is_async: bool arguments: Ellipsis | tuple[CallableTypeArgument] - return_type: typing.Type + return_type class CallableTypeArgument: kind: CallableTypeArgumentKind - annotation: typing.Type | typing.TypeVar | typing.ParamSpec + annotation - class CallableTypeArgumentKind(Enum): + @enum.global_enum + class CallableTypeArgumentKind(enum.IntEnum): POSITIONAL_ONLY: int = ... PARAM_SPEC: int = ... @@ -669,30 +670,32 @@ Our plan is to expose a structured API as if they were defined as follows:: The evaluation rules are expressed in terms of the following pseudocode:: - def evaluate_callable_type(callable_type): + def evaluate_callable_type( + callable_type: ast.CallableType | ast.AsyncCallableType: + ) -> CallableType: return CallableType( is_async=isinstance(callable_type, ast.AsyncCallableType), - arguments=evaluate_arguments(callable_type.arguments), + arguments=_evaluate_arguments(callable_type.arguments), return_type=evaluate_expression(callable_type.returns), ) - def evaluate_arguments(arguments): + def _evaluate_arguments(arguments): match arguments: case ast.AnyArguments(): return Ellipsis case ast.ArgumentsList(posonlyargs): return tuple( - evaluate_arg(arg) for arg in args + _evaluate_arg(arg) for arg in args ) case ast.ArgumentsListConcatenation(posonlyargs, param_spec): return tuple( *(evaluate_arg(arg) for arg in args), - evaluate_arg(arg=param_spec, kind=PARAM_SPEC) + _evaluate_arg(arg=param_spec, kind=PARAM_SPEC) ) if isinstance(arguments, Any return Ellipsis - def evaluate_arg(arg, kind=POSITIONAL_ONLY): + def _evaluate_arg(arg, kind=POSITIONAL_ONLY): return CallableTypeArgument( kind=POSITIONAL_ONLY, annotation=evaluate_expression(value) @@ -709,7 +712,7 @@ them as if they were written in terms of the following:: import itertools import typing - def get_args(t: CallableType): + def get_args(t: CallableType) -> tuple[object]: return_type_arg = ( typing.Awaitable[t.return_type] if t.is_async @@ -725,9 +728,12 @@ them as if they were written in terms of the following:: return_type_arg ) + Additional Behaviors of ``types.CallableType`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +As with the `A | B` syntax for unions introduced in PEP 604: + - The ``__eq__`` method should treat equivalent ``typing.Callable`` values as equal to values constructed using the builtin syntax, and otherwise should behave like the ``__eq__`` of ``typing.Callable``. From d7b87deff78284a68ad904d0e8a268e38d0381b6 Mon Sep 17 00:00:00 2001 From: Steven Troxler Date: Tue, 18 Jan 2022 09:15:11 -0800 Subject: [PATCH 08/10] Add missing --- pep-0677.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pep-0677.rst b/pep-0677.rst index 5ca37fa3b3c..d753ac8c0f7 100644 --- a/pep-0677.rst +++ b/pep-0677.rst @@ -728,6 +728,15 @@ them as if they were written in terms of the following:: return_type_arg ) + def get_parameters(t: CallableType) -> tuple[object]: + out = [] + for arg in get_args(t): + if isinstance(arg, typing.ParamSpec): + out.append(t) + else: + out.extend(arg.__parameters__) + return tuple(out) + Additional Behaviors of ``types.CallableType`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From b6aef564c1bdd59521b88d4b79be43aba97d1f2e Mon Sep 17 00:00:00 2001 From: Steven Troxler Date: Tue, 18 Jan 2022 09:18:28 -0800 Subject: [PATCH 09/10] Fix markdown-style code block --- pep-0677.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0677.rst b/pep-0677.rst index d753ac8c0f7..832eef08c74 100644 --- a/pep-0677.rst +++ b/pep-0677.rst @@ -741,7 +741,7 @@ them as if they were written in terms of the following:: Additional Behaviors of ``types.CallableType`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -As with the `A | B` syntax for unions introduced in PEP 604: +As with the ``A | B`` syntax for unions introduced in PEP 604: - The ``__eq__`` method should treat equivalent ``typing.Callable`` values as equal to values constructed using the builtin syntax, and From 9ecdc43d5dc0e467ba8e2722d4c8c4bf976c99ac Mon Sep 17 00:00:00 2001 From: Steven Troxler Date: Tue, 18 Jan 2022 10:35:37 -0800 Subject: [PATCH 10/10] Add missing annotations --- pep-0677.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0677.rst b/pep-0677.rst index 832eef08c74..1db064d9e50 100644 --- a/pep-0677.rst +++ b/pep-0677.rst @@ -655,11 +655,11 @@ Our plan is to expose a structured API as if they were defined as follows:: class CallableType: is_async: bool arguments: Ellipsis | tuple[CallableTypeArgument] - return_type + return_type: object class CallableTypeArgument: kind: CallableTypeArgumentKind - annotation + annotation: object @enum.global_enum class CallableTypeArgumentKind(enum.IntEnum):