pytype icon indicating copy to clipboard operation
pytype copied to clipboard

Protocol bounds and descriptors cause infinite recursion

Open eltoder opened this issue 2 years ago • 1 comments

Consider the following example:

from typing import Protocol, TypeVar

class HasClassId(Protocol):
    id: "ClassId"

IdT = TypeVar("IdT", bound=HasClassId)

class ClassId:
    def __get__(self, obj: IdT | None, owner: type[IdT] | None = None) -> type[IdT]:
        if obj is None:
            assert owner is not None
            return owner
        return obj.__class__

class Base:
    id = ClassId()

def test() -> type[Base]:
    return Base.id

When I run this through pytype, the generated pyi file contains an internal error with infinite recursion:

from typing import Any
def __getattr__(name: Any) -> Any: ...
# Caught error in pytype: maximum recursion depth exceeded in comparison
# Traceback (most recent call last):
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/io.py", line 169, in check_or_generate_pyi
#     errorlog, result, ast = generate_pyi(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/io.py", line 123, in generate_pyi
#     ret = _call(analyze.infer_types, src, options, loader, ctx=ctx)
<snip>
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 1616, in _match_protocol_attribute
#     left_attribute, left_is_bound = self._get_attribute_for_protocol_matching(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 1515, in _get_attribute_for_protocol_matching
#     _, attribute = self.ctx.attribute_handler.get_attribute(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/attribute.py", line 57, in get_attribute
#     return self._get_class_attribute(node, obj, name, valself)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/attribute.py", line 253, in _get_class_attribute
#     return self._get_attribute(node, cls, meta, name, valself)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/attribute.py", line 301, in _get_attribute
#     node, attr = self._lookup_from_mro_and_handle_descriptors(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/attribute.py", line 398, in _lookup_from_mro_and_handle_descriptors
#     node2, get_result = function.call_function(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/abstract/function.py", line 989, in call_function
#     new_node, one_result = func.call(node, funcb, args)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/abstract/_function_base.py", line 243, in call
#     node, ret = self.underlying.call(node, func, args,
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/abstract/_interpreter_function.py", line 421, in call
#     sig, substs, callargs = self._find_matching_sig(node, args, alias_map)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/abstract/_interpreter_function.py", line 327, in _find_matching_sig
#     substs, callargs = f.match_and_map_args(node, args, alias_map)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/abstract/_function_base.py", line 444, in match_and_map_args
#     return self.match_args(node, args, alias_map), self._map_args(node, args)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/abstract/_interpreter_function.py", line 263, in match_args
#     return super().match_args(node, args, alias_map, match_all_views)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/abstract/_function_base.py", line 72, in match_args
#     return self._match_args_sequentially(node, args, alias_map, match_all_views)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/abstract/_function_base.py", line 566, in _match_args_sequentially
#     matches = matcher.compute_matches(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 323, in compute_matches
#     match_result = self.compute_one_match(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 368, in compute_one_match
#     subst = self.match_var_against_type(var, other_type, subst, view)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 437, in match_var_against_type
#     return self._match_value_against_type(view[var], other_type, subst, view)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 511, in _match_value_against_type
#     subst = self._match_nonfinal_value_against_type(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 631, in _match_nonfinal_value_against_type
#     new_subst = self._match_value_against_type(value, t, subst, view)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 511, in _match_value_against_type
#     subst = self._match_nonfinal_value_against_type(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 580, in _match_nonfinal_value_against_type
#     new_subst = self._match_value_against_type(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 511, in _match_value_against_type
#     subst = self._match_nonfinal_value_against_type(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 614, in _match_nonfinal_value_against_type
#     return self._match_type_against_type(left, other_type, subst, view)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 828, in _match_type_against_type
#     return self._match_instance_against_type(left, other_type, subst, view)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 1175, in _match_instance_against_type
#     return self._match_against_protocol(left, other_type, subst, view)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 1491, in _match_against_protocol
#     new_subst = self._match_protocol_attribute(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 1616, in _match_protocol_attribute
#     left_attribute, left_is_bound = self._get_attribute_for_protocol_matching(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/matcher.py", line 1515, in _get_attribute_for_protocol_matching
#     _, attribute = self.ctx.attribute_handler.get_attribute(
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/attribute.py", line 57, in get_attribute
#     return self._get_class_attribute(node, obj, name, valself)
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/attribute.py", line 242, in _get_class_attribute
#     if (not valself or not abstract_utils.equivalent_to(valself, cls) or
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/abstract/abstract_utils.py", line 355, in equivalent_to
#     return (_isinstance(binding.data, "Class") and
#   File "/home/eltoder/.local/share/ext-python/python-3.10.11.15/lib/python3.10/site-packages/pytype/abstract/abstract_utils.py", line 227, in _isinstance
#     if name_or_names.__class__ == tuple:
# RecursionError: maximum recursion depth exceeded in comparison

Pytype version: 2023.07.12 Python version: 3.10.11

eltoder avatar Jul 19 '23 00:07 eltoder

I cannot reproduce this with Python 3.10.16 and pytype 2023.07.12. I do get a RecursionError on Python 3.11 but that is fixed as of commit a61776fae33f3f9a3229eea7c0594dd39ea0da7b (in 2023.08.22).

cebtenzzre avatar Apr 04 '25 18:04 cebtenzzre

Hi, thank you for your report. Google is shifting its effort to a different approach for type checking apart from pytype, and we're not planning to put in any effort in the near future in pytype, so we'll close this issue. Please see the announcement here.

h-joo avatar Aug 20 '25 17:08 h-joo