python / python/typing

Assignability to a Protocol Using `Self` Must Respect Variance

Open
#2,051 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

topic: typing spec
Dominant language
Python
Stars
1.8k
Forks
302
Avg merge
23h
Merged PRs (30d)
8

Description

I just stumbled across this paragraph:

https://github.com/python/typing/blob/2d88da2a407b556f5b52b4c6ad1bd659873bb9ac/docs/spec/generics.rst?plain=1#L2540-L2544

If I understand it correctly, then the following code is fine, i.e., Foo is assignable to Proto according to the explanation:

from __future__ import annotations

from typing import Protocol, Self


class Proto(Protocol):
    def f(self, x: Self) -> None: ...


class Foo:
    def f(self, x: Sub) -> None:
        pass


class Sub(Foo):
    pass


x: Proto = Foo()

However, type checkers like mypy and pyright reject this code. This aligns with my expectation: If I have an instance x: Proto, then I should be able to call x.f(x). But for y: Foo, I cannot call y.f(y).

The paragraph in question should distinguish between covariant, contravariant, and invariant occurences of Self:

from __future__ import annotations

from typing import Protocol, Self


class Proto(Protocol):
    def f(self, x: Self) -> None: ...  # contravariant
    def g(self, x: Self) -> Self: ...  # x: contravariant, return type: covariant
    def h(self, x: list[Self]) -> None: ...  # invariant


class Sup:
    pass


class Foo(Sup):
    def f(self, x: Sup) -> None:  # x can be of type Foo or any superclass
        pass

    def g(self, x: Sup) -> Sub:  # return type can be Foo or any subclass
        raise RuntimeError()

    def h(self, x: list[Foo]) -> None:  # no sub-/superclass allowed as argument to list
        pass


class Sub(Foo):
    pass


x: Proto = Foo()  # OK, also according to mypy and pyright

I assume that all the uses of Self are valid in this example, at least mypy and pyright do not complain and I couldn’t find any statement that would restrict Self in protocols to covariant positions or even return types only. Maybe it also makes sense to adjust the example after the paragraph in question. Currently, it only uses Self in a return type.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with docs/spec/generics.rst at the linked lines 2540-2544 and review the surrounding discussion of Self in protocols. Run the supplied examples through mypy and pyright, then update the paragraph and nearby example to explain contravariant, covariant, and invariant uses of Self; done means the specification matches the demonstrated checker behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
documentation
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.