Skip to content

Resolve bare Protocol and Generic references in type position - #22005

Open
istoolsfox wants to merge 1 commit into
python:masterfrom
istoolsfox:fix/protocol-generic-special-form-as-type
Open

istoolsfox wants to merge 1 commit into
python:masterfrom
istoolsfox:fix/protocol-generic-special-form-as-type

Conversation

@istoolsfox

Copy link
Copy Markdown

Follow-up to the typeshed declaration changes: Protocol is now declared as type[_Protocol] (python/typeshed#16386), the same shape Generic has had since python/typeshed#14583. Both mirror the runtime, where the two names are classes on 3.12+.

Mypy already honors those declarations in expression context -- reveal_type(Protocol) gives type[typing._Protocol] with the synced stubs -- but a bare reference in type position still failed:

from typing import Protocol

x: type[Protocol] = Protocol
# error: Variable "typing.Protocol" is not valid as a type  [valid-type]

This resolves the bare reference to Instance(_Protocol) / Instance(_Generic), so the example checks clean once the stub sync lands. Details:

  • References with arguments (Protocol[T] in type position) keep failing as before -- that is class-base syntax, not a type. Empty subscripts (Protocol[()]) also keep failing.
  • Class bases are untouched; semanal handles those separately.
  • If the stubs in use do not declare the private classes, the regular "not valid as a type" error is kept instead of resolving to Any, so behavior changes only together with the stub declarations -- safe to merge in either order relative to the typeshed sync.
  • The expression-context reproducer from typing.Protocol is explicitly treated as not a type #21940 is already fixed by the stub change itself; this closes the remaining type-position gap.

The unit-test fixtures declared Generic = 0 / Protocol = 0, which would silently change meaning under the new branch, so the typing fixtures now mirror the real declarations.

One divergence worth flagging: pyright 1.1.414 rejects these examples too (reportInvalidTypeForm), and TypeVar("T", bound=Protocol) gets a nearly-empty upper bound under this change. I went with "the typeshed declaration is the contract"; happy to adjust if bare references in type position should keep being rejected.

Typeshed declares the Protocol and Generic special forms as
X: type[_X], mirroring the runtime, where both are classes on 3.12+.
Mypy already uses those declarations when the names appear in
expression context, but a bare reference in type position still
failed with 'Variable ... is not valid as a type', so e.g.
'x: type[Protocol] = Protocol' was rejected.

Resolve a bare reference to Instance(_Protocol)/Instance(_Generic),
matching what the declarations say the runtime objects are. If the
private classes are not defined in the stubs (before the typeshed
sync lands), keep the regular 'not valid as a type' error instead of
resolving to Any, so behavior only changes together with the stubs.
References with arguments (Protocol[T]) keep failing as before, and
class bases are unaffected (handled separately in semanal).

The expression-context reproducer from python#21940 is already fixed by
the typeshed declaration change itself; this handles the remaining
type-position gap. Pyright currently rejects these examples too, so
the divergence may be worth a look on the maintainer side.

Refs python#21940
@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.

1 participant