Mypy: understanding how/where Self is handled

TLDR: I’m trying to navigate the mypy codebase and work out where/how Self is handled.

Given the code

from typing import Self, reveal_type
class C:
    def foo(self: C) -> None:
        pass
    def bar(self) -> None:
        pass
    def baz(self: Self) -> None:
        pass

reveal_type(C.foo)
reveal_type(C.bar)
reveal_type(C.baz)

mypy shows

main.py:10: note: Revealed type is "def (self: __main__.C)"
main.py:11: note: Revealed type is "def (self: __main__.C)"
main.py:12: note: Revealed type is "def [Self <: __main__.C] (self: Self`551)"

mypy’s syntax for generic type parameters is the square brackets, so looks like its treating the Self type as a shorthand for a generic type with an upper bound for the enclosing class, and unannotated functions as implicitly of the enclosing class.

I’m trying to work out where the 2 cases are handled in the mypy codebase and trying to work out how it might be possible to switch mypy to infer a generic Self by default but mypy is a tricky code base to navigate due to all the visitor calls. Any pointers would be appreciated!

1 Like

See python/mypy#17760 and python/mypy#14075 for previous discussions about this. Things were left off with a proposed revision to the typing spec to clarify that this behavior is allowed: python/typing#1860 (plus some open concerns about performance).

I mention a relevant snippet here

2 Likes