python / mypy

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

Recursion Error for Protocols with Self #17474

Closed JasonGrace2282 closed 1 day ago

JasonGrace2282 commented 5 days ago

Crash Report I ran mypy on this code and it crashed with an internal error

Traceback

Click here for full traceback ``` Traceback (most recent call last): File "/sbin/mypy", line 8, in sys.exit(console_entry()) File "/usr/lib/python3.12/site-packages/mypy/__main__.py", line 15, in console_entry main() File "/usr/lib/python3.12/site-packages/mypy/main.py", line 100, in main res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr) File "/usr/lib/python3.12/site-packages/mypy/main.py", line 182, in run_build res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr) File "/usr/lib/python3.12/site-packages/mypy/build.py", line 192, in build ``` I had to cut a lot of this out because it was too long. ``` File "/usr/lib/python3.12/site-packages/mypy/subtypes.py", line 179, in is_subtype return _is_subtype(left, right, subtype_context, proper_subtype=False) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/subtypes.py", line 352, in _is_subtype return left.accept(SubtypeVisitor(orig_right, subtype_context, proper_subtype)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/types.py", line 1984, in accept return visitor.visit_callable_type(self) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/subtypes.py", line 721, in visit_callable_type call = find_member("__call__", right, left, is_operator=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/subtypes.py", line 1238, in find_member return find_node_type(method, itype, subtype, class_obj=class_obj) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/subtypes.py", line 1364, in find_node_type signature = bind_self( ^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/typeops.py", line 292, in bind_self F, Overloaded([bind_self(c, original_type, is_classmethod) for c in method.items]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/typeops.py", line 326, in bind_self typeargs = infer_type_arguments( ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/infer.py", line 75, in infer_type_arguments return solve_constraints(type_vars, constraints, skip_unsatisfied=skip_unsatisfied)[0] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/solve.py", line 124, in solve_constraints res = pre_validate_solutions(res, original_vars, constraints) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/solve.py", line 549, in pre_validate_solutions if s is not None and not is_subtype(s, t.upper_bound): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/subtypes.py", line 179, in is_subtype return _is_subtype(left, right, subtype_context, proper_subtype=False) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/subtypes.py", line 352, in _is_subtype return left.accept(SubtypeVisitor(orig_right, subtype_context, proper_subtype)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/types.py", line 1984, in accept return visitor.visit_callable_type(self) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/subtypes.py", line 721, in visit_callable_type call = find_member("__call__", right, left, is_operator=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/subtypes.py", line 1238, in find_member return find_node_type(method, itype, subtype, class_obj=class_obj) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/subtypes.py", line 1364, in find_node_type signature = bind_self( ^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/typeops.py", line 292, in bind_self F, Overloaded([bind_self(c, original_type, is_classmethod) for c in method.items]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/typeops.py", line 326, in bind_self typeargs = infer_type_arguments( ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/infer.py", line 74, in infer_type_arguments constraints = infer_constraints(template, actual, SUPERTYPE_OF if is_supertype else SUBTYPE_OF) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/constraints.py", line 304, in infer_constraints if has_recursive_types(template) or isinstance(get_proper_type(template), Instance): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/types.py", line 3533, in has_recursive_types return typ.accept(_has_recursive_type) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/types.py", line 644, in accept return visitor.visit_type_var(self) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/type_visitor.py", line 482, in visit_type_var return self.query_types([t.upper_bound, t.default] + t.values) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/type_visitor.py", line 557, in query_types return any(t.accept(self) for t in types) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/type_visitor.py", line 557, in return any(t.accept(self) for t in types) ^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/types.py", line 1444, in accept return visitor.visit_instance(self) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/type_visitor.py", line 500, in visit_instance return self.query_types(t.args) ^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/site-packages/mypy/type_visitor.py", line 562, in query_types return any(t.accept(self) for t in types) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RecursionError: maximum recursion depth exceeded ```

To Reproduce

from typing import TYPE_CHECKING, Any, TypeVar, Protocol, overload

if TYPE_CHECKING:
    from typing_extensions import Self

V = TypeVar("V")

class SimpleAttributeFunc(Protocol[V]):
    # getter
    @overload
    def __call__(self, new: None = None, **kwargs: Any) -> V: ...

    # setter
    @overload
    def __call__(self, new: V, **kwargs: Any) -> Self: ...

class TestMe:
    def testsomething(self) -> SimpleAttributeFunc[str]:
        @overload
        def foo(new: None = None, **kwargs: Any) -> str: ...

        @overload
        def foo(new: str, **kwargs: Any) -> Self: ...

        # this function crashes it for whatever reason
        def foo(new: str | None = None, **kwargs):
            if new is None:
                return str(["hi"])
            else:
                print(f"Setting to {new} with {kwargs}")
                return self
        # specifically this
        return foo

Your Environment

My Goal

While irrelevant to the issue, here was my goal (but working) https://gist.github.com/mypy-play/62fad45174312b2b1ec0c17e555d9ece

JasonGrace2282 commented 5 days ago

Upon further testing, it appears to be because of Self in the Protocol definition, since this works

from typing import TYPE_CHECKING, Any, TypeVar, Protocol, overload

if TYPE_CHECKING:
    from typing_extensions import Self

V = TypeVar("V")
S = TypeVar("S")
C_co = TypeVar("C_co", covariant=True)

class SimpleAttributeFunc(Protocol[C_co, V]):
    # getter
    @overload
    def __call__(self, new: None = None, **kwargs: Any) -> V: ...

    # setter
    @overload
    def __call__(self, new: V, **kwargs: Any) -> C_co: ...

class TestMe:
    def testsomething(self) -> SimpleAttributeFunc[Self, str]:
        def foo(new: str | None = None, **kwargs):
            if new is None:
                return str(["hi"])
            else:
                print(f"Setting to {new} with {kwargs}")
                return self
        return foo
AlexWaygood commented 5 days ago

Similar to #16629 — possibly a duplicate, but the traceback is different

JasonGrace2282 commented 5 days ago

Similar to #16629 — possibly a duplicate, but the traceback is different

Seems similar, I didn't see that before! Should I close this issue then?

ilevkivskyi commented 1 day ago

Yes, this is a duplicate of #16629