close
Skip to content

Alternate draft implementation for PEP 718 -- stricter rules - #21881

Draft
gvanrossum wants to merge 4 commits into
python:masterfrom
gvanrossum:pep718-strict
Draft

Alternate draft implementation for PEP 718 -- stricter rules#21881
gvanrossum wants to merge 4 commits into
python:masterfrom
gvanrossum:pep718-strict

Conversation

@gvanrossum

Copy link
Copy Markdown
Member

Fixes #21337.

This is a variant of #21880 (also vibe-coded by Fable 5) that is stricter about which callables support subscription, and only support scallables that are created using def.

Fable had a lot to say about it:

Fable's review of this change This is the "strict" horn of Carl's dilemma — subscription only where the checker can prove runtime FunctionType.__getitem__ exists. Mypy has exactly the right evidence for this: CallableType.definition, which is set to the FuncDef when the type came from analyzing an actual def, and — crucially — is preserved through identity-typed decorators ([F: Callable](f: F) -> F returns the same callable type) but absent from types built out of annotations (x: Callable[[int], int]). So the semantics should fall out of a definition check. Let me implement and then map exactly where it draws the line.

Ran 14 commands, edited 2 files, edited a memory

Implemented, and the experiment produced a sharp result about what the rule can and can't mean.

The mechanism. The value-subscription path now requires the callable to be statically traceable to a def, using CallableType.definition as evidence. The nice property that motivated this: definition survives identity-typed decorators (which return the argument's own type) but is absent from types built out of annotations. So the line falls almost exactly where you'd want:

Still subscriptable: plain defs, aliases (g = f), bound methods, classmethods, decorated defs.
Now rejected, with a targeted message (Only functions or methods defined with "def" can be subscripted with type arguments): a variable annotated Callable[[T], T], or the result of a function returning a bare generic Callable. Non-generic non-def callables fall through to the ordinary getitem error, unchanged.

Notably, Carl's headline crash cases (functools.partial, lru_cache) were already excluded even under the permissive rule — mypy types those as Instances, which never reach this code path. The strict rule's real bite is on bare-Callable-typed values.

The caveat, which is the interesting finding. definition is error-message metadata: checker.visit_decorator re-attaches it to any CallableType a decorator returns. I probed the consequence: a decorator annotated (...) -> Callable[[T], T] that at runtime returns a callable object rather than a function — the decorated name stays "def-defined" in the checker's eyes, so victim[int] is allowed and would fail at runtime. The identical type produced without decoration (make()[int]) is correctly rejected. So the implementable rule is really "def-defined, where decoration is assumed to preserve def-ness." I don't think a checker can do better without tracking decorator runtime behavior, so if this rule goes in the PEP, that assumption should be stated — and a production implementation would want a dedicated, serialized from_def bit on CallableType rather than piggybacking on definition.

Verification. All 15 unit tests pass (one new strict-mode case), self-check clean, and ~3,700 targeted regression tests pass — the full suite started exceeding this environment's command time limit, so I ran the subsets covering generics, overloads, ParamSpec, functions, classes, inference, and decorators instead; worth one full CI run on your side before treating it as fully clean. Updated patch (427 lines) and the two probe files are above.

One design question worth deciding before this goes to the thread: should the bare-function path also be strict? Currently f[int] via a direct reference is always allowed since semanal proved it's a FuncDef — that's consistent. But an unannotated lambda assigned to a variable is a FunctionType at runtime and would be subscriptable under the PEP's runtime rules, while never generic in practice — I left that case alone.

@github-actions

Copy link
Copy Markdown
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add reference implementation of PEP 718

2 participants