Thanks for your work on this PEP. The idea certainly has a compelling consistency, and I do think there are use cases for it.
That said, I don’t feel that this PEP is ready. I did a review just now, including of the discussion thread, and I think there are critical questions (including some that were already discussed extensively in the thread) that are still not addressed at all, or are not addressed with adequate precision, in the PEP text. Any significant topic that has been raised in the discussion should appear in the PEP.
The most important thing that I think this PEP needs before it can be considered is a reference implementation in at least one type checker. This is both the hardest and the most valuable part of any typing PEP. It invariably uncovers (and helps to answer) tricky edge cases and design questions, like some of the ones I mention below. In recent years a reference implementation has been a baseline requirement for all typing PEPs; some have had multiple reference implementations!
(The reference implementation also serves another useful role, which is that finding someone sufficiently motivated to put in the time to do it helps indicate that the use cases in the PEP really are motivating.)
Since I did my review, I’ll go ahead and post my notes below, in case they are useful (sorry in advance for the length!). But I would recommend prioritizing a reference implementation above all else.
- The PEP should clarify that the runtime
__getitem__does not validate whether the function is generic, let alone how many type parameters it has; this is left purely for static verification by type checkers. (This can be discovered by looking at the runtime implementation, but the PEP should be clear about it.) - Should type checkers allow a symbol typed with a
Callabletype to be subscripted, or only objects the type checker knows to be instances of one of the five mentioned “function types”?- If so, that for sure will lead to runtime errors not caught by the type checker. Many objects are callable (and thus can inhabit
Callabletypes) and will not be subscriptable at runtime, including many common standard library types:functools.partial,functools.lru_cache,functools.cache… If the proposal is that this risk is acceptable, the PEP needs to make a good case for why. - If not, that will dramatically limit applicability. Most decorators can’t return anything more precise than a
Callabletype, which would effectively mean that most decorated functions can never be subscripted, which will feel to users like a strange and arbitrary limitation. - This was previously asked in the discussion thread and there was a fair bit of discussion around it, but the PEP remains entirely silent on it.
- In general the PEP relies quite a bit on an assumed “we already have GenericAlias for classes, so it’ll work just as well here”, but this issue doesn’t exist in the same way for classes, so it’s important for the PEP to address it in detail.
- If so, that for sure will lead to runtime errors not caught by the type checker. Many objects are callable (and thus can inhabit
- Another runtime issue:
functools.wrapscrashes if used on instances oftypes.GenericAlias(their attribute proxy is read-only), so if a generic alias is ever passed to a function accepting a callable and usingfunctools.wraps, that will cause a crash. - I personally find the section on “monomorphization and reification” very unconvincing. It’s fine for something to be left as separate future work, but there’s so little concept of how this could even possibly work presented in the PEP, that I don’t even find it justified to claim that this PEP meaningfully “opens the door” for it. In a certain sense, that door has already been opened and we already walked through it with PEP 747. But suggesting that PEP 718 can improve on PEP 747 in some way requires at least suggesting an answer to the question of how a function that expects reified generics would receive them if it is called without being explicitly specialized via subscript. Or would such functions only work if called via a
GenericAlias? - For introspection purposes, there should be a publicly supported way to tell whether a
GenericAliasis for a function or class, without having toisinstancecheck its__origin__against five different types. - The overload section needs a clearer specification of the modification to the overload algorithm, including precision about what is meant by “subscription may succeed.”
- TypeVarTuple section should include an example of how to successfully subscript a function generic over a TypeVarTuple.
- Can you subscript a function generic over a ParamSpec? Example? What about a function generic over both a TypeVarTuple and a ParamSpec? What about a TypeVarTuple followed by a regular TypeVar? Maybe there are existing rules for some of this in the typing spec for classes that we can follow; that should be said explicitly.
- Is it ever allowed to subscript like
foo[*tuple[int, str]]in order to disambiguate when subscripting a function parameterized by aTypeVarTuple? PEP 646 allows this in some contexts. - The example
C[int].method[str](0, "")is marked# OKbut is wrong,methodshould be called on an instance ofCor an explicitselfmust be passed. - There was a fair amount of discussion in the thread about performance, some measurements were done, and there was a lot of talk about why it does or doesn’t matter. The topic isn’t mentioned in the PEP.