python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.47k stars 2.83k forks source link

Segmentation fault on callable protocol with Self annotation #16629

Closed ryo-tagami closed 4 months ago

ryo-tagami commented 11 months ago

Crash Report

mypy crashes with segmentation fault as such:

mypy-segmentation-fault on  main is 📦 v0.1.0 via 🐍 v3.11.6 (mypy-segmentation-fault-py3.11)
❯ mypy --version
mypy 1.7.1 (compiled: yes)

mypy-segmentation-fault on  main is 📦 v0.1.0 via 🐍 v3.11.6 (mypy-segmentation-fault-py3.11)
❯ mypy -p mypy_segmentation_fault
zsh: segmentation fault  mypy -p mypy_segmentation_fault

with mypy_segmentation_fault.py:

from datetime import UTC, datetime
from typing import Protocol, Self

from injector import Binder, Injector, Module

class NowGenerator(Protocol):
    def __call__(self: Self) -> datetime:
        ...

class NowGeneratorImpl:
    def __call__(self: Self) -> datetime:
        return datetime.now(tz=UTC)

class GeneratorsModule(Module):
    def configure(self: Self, binder: Binder) -> None:
        binder.bind(
            NowGenerator,  # type: ignore[type-abstract]
            to=NowGeneratorImpl,
        )

injector = Injector([GeneratorsModule])
print(injector.get(NowGenerator)())  # type: ignore[type-abstract]

My observation is that if I remove Self type annotation, mypy does not crash and I do realise it is not necessary (though, it was a good way to avoid hitting ruff's missing-type-self rule, which in theory I could disable). I don't know if I could reproduce the crash without using python-injector.

Traceback

Unfortunately, nothing useful shows up:

mypy --show-traceback -p mypy_segmentation_fault
zsh: segmentation fault  mypy --show-traceback -p mypy_segmentation_fault

To Reproduce

Your Environment

AlexWaygood commented 11 months ago

As is often the case with mypyc segfaults, it seems this is due to a RecursionError. Here's the last part of the stack trace if you use an uncompiled install of mypy from the master branch:

<many lines>

  File "C:\Users\alexw\coding\mypy\mypy\subtypes.py", line 1223, in find_member
    return find_node_type(method, itype, subtype, class_obj=class_obj)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\subtypes.py", line 1349, in find_node_type
    signature = bind_self(
                ^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\typeops.py", line 326, in bind_self
    typeargs = infer_type_arguments(
               ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\infer.py", line 75, in infer_type_arguments
    return solve_constraints(type_vars, constraints, skip_unsatisfied=skip_unsatisfied)[0]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\solve.py", line 124, in solve_constraints
    res = pre_validate_solutions(res, original_vars, constraints)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\solve.py", line 549, in pre_validate_solutions
    if s is not None and not is_subtype(s, t.upper_bound):
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\subtypes.py", line 179, in is_subtype
    return _is_subtype(left, right, subtype_context, proper_subtype=False)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\subtypes.py", line 352, in _is_subtype
    return left.accept(SubtypeVisitor(orig_right, subtype_context, proper_subtype))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\types.py", line 1970, in accept
    return visitor.visit_callable_type(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\subtypes.py", line 706, in visit_callable_type
    call = find_member("__call__", right, left, is_operator=True)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\subtypes.py", line 1223, in find_member
    return find_node_type(method, itype, subtype, class_obj=class_obj)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\subtypes.py", line 1349, in find_node_type
    signature = bind_self(
                ^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\typeops.py", line 322, in bind_self
    self_ids = {tv.id for tv in get_all_type_vars(self_param_type)}
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\typeops.py", line 955, in get_all_type_vars
    return tp.accept(TypeVarExtractor(include_all=True))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\mypy\mypy\typeops.py", line 960, in __init__
    super().__init__(self._merge)
RecursionError: maximum recursion depth exceeded
erikkemperman commented 9 months ago

I created a similar / duplicate issue https://github.com/python/mypy/issues/16877 -- for anyone looking into this, there's a more compact repro and report of my initial investigation.

bodograumann commented 9 months ago

Afaict this affects version 1.7.0 and up, including the current verison 1.8.0.

kurellajunior commented 7 months ago

Still valid for 1.9.0

mgab commented 6 months ago

Still valid for 1.10.0.

Also, a simpler example that reproduces the crash in my case is

from typing import Protocol, Self

class MyProtocol(Protocol):
    __name__: str

    def __call__(
        self: Self,
    ) -> None: ...

value: MyProtocol = print
JelleZijlstra commented 5 months ago

17314 has a simpler repro case that seems similar though the stack trace is a little different:

from typing import Protocol
from typing import Self

class Printer(Protocol):
    def __call__(self: Self): ...

class Version:
    def __init__(self, printer: Printer): ...

Version(printer=print)

(Thanks Alex for finding this related issue.)

bodograumann commented 2 months ago

From what I can tell a fix was released in 1.11.0. Unfortunately this does not fix our segmentation fault. Should we open a new bug report, or can we reopen this issue, possibly with a new reproduction?

AlexWaygood commented 2 months ago

From what I can tell a fix was released in 1.11.0. Unfortunately this does not fix our segmentation fault. Should we open a new bug report, or can we reopen this issue, possibly with a new reproduction?

yes