Skip to content

Crash when overriding partially typed attribute with partially typed property #11686

Closed

Description

@jakelishman

Crash Report

I've been seeing mypy crashes with the released 0.910 and the current state of the dev branch. It appears to occur when a child class overrides a partially type instance attribute in its parent with a property that's also partially typed, but it also seems to matter that there's a second property in the parent that has at least a setter method.

I've cut it down as far as I could to the reproducer below the traceback.

Traceback

partial_list.py:15: error: INTERNAL ERROR -- Please try using mypy master on Github:
https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.920+dev.01e6aba30c787c664ac88d849b45f7d303316951
Traceback (most recent call last):
  File "/Users/jake/.miniconda3/envs/tmp/bin/mypy", line 33, in <module>
    sys.exit(load_entry_point('mypy', 'console_scripts', 'mypy')())
  File "/Users/jake/code/mypy/mypy/__main__.py", line 12, in console_entry
    main(None, sys.stdout, sys.stderr)
  File "/Users/jake/code/mypy/mypy/main.py", line 96, in main
    res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
  File "/Users/jake/code/mypy/mypy/main.py", line 173, in run_build
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
  File "/Users/jake/code/mypy/mypy/build.py", line 180, in build
    result = _build(
  File "/Users/jake/code/mypy/mypy/build.py", line 256, in _build
    graph = dispatch(sources, manager, stdout)
  File "/Users/jake/code/mypy/mypy/build.py", line 2715, in dispatch
    process_graph(graph, manager)
  File "/Users/jake/code/mypy/mypy/build.py", line 3046, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/Users/jake/code/mypy/mypy/build.py", line 3144, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/Users/jake/code/mypy/mypy/build.py", line 2180, in type_check_first_pass
    self.type_checker().check_first_pass()
  File "/Users/jake/code/mypy/mypy/checker.py", line 319, in check_first_pass
    self.accept(d)
  File "/Users/jake/code/mypy/mypy/checker.py", line 425, in accept
    stmt.accept(self)
  File "/Users/jake/code/mypy/mypy/nodes.py", line 1000, in accept
    return visitor.visit_class_def(self)
  File "/Users/jake/code/mypy/mypy/checker.py", line 1790, in visit_class_def
    self.accept(defn.defs)
  File "/Users/jake/code/mypy/mypy/checker.py", line 425, in accept
    stmt.accept(self)
  File "/Users/jake/code/mypy/mypy/nodes.py", line 1069, in accept
    return visitor.visit_block(self)
  File "/Users/jake/code/mypy/mypy/checker.py", line 2052, in visit_block
    self.accept(s)
  File "/Users/jake/code/mypy/mypy/checker.py", line 425, in accept
    stmt.accept(self)
  File "/Users/jake/code/mypy/mypy/nodes.py", line 561, in accept
    return visitor.visit_overloaded_func_def(self)
  File "/Users/jake/code/mypy/mypy/checker.py", line 458, in visit_overloaded_func_def
    self._visit_overloaded_func_def(defn)
  File "/Users/jake/code/mypy/mypy/checker.py", line 482, in _visit_overloaded_func_def
    self.check_method_override(defn)
  File "/Users/jake/code/mypy/mypy/checker.py", line 1474, in check_method_override
    if self.check_method_or_accessor_override_for_base(defn, base):
  File "/Users/jake/code/mypy/mypy/checker.py", line 1502, in check_method_or_accessor_override_for_base
    if self.check_method_override_for_base_with_name(defn, name, base):
  File "/Users/jake/code/mypy/mypy/checker.py", line 1596, in check_method_override_for_base_with_name
    elif is_equivalent(original_type, typ):
  File "/Users/jake/code/mypy/mypy/subtypes.py", line 164, in is_equivalent
    is_subtype(a, b, ignore_type_params=ignore_type_params,
  File "/Users/jake/code/mypy/mypy/subtypes.py", line 93, in is_subtype
    return _is_subtype(left, right,
  File "/Users/jake/code/mypy/mypy/subtypes.py", line 147, in _is_subtype
    return left.accept(SubtypeVisitor(orig_right,
  File "/Users/jake/code/mypy/mypy/types.py", line 1967, in accept
    return visitor.visit_partial_type(self)
  File "/Users/jake/code/mypy/mypy/subtypes.py", line 515, in visit_partial_type
    raise RuntimeError(f'Partial type "{left}" cannot be checked with "issubtype()"')
RuntimeError: Partial type "<partial list[?]>" cannot be checked with "issubtype()"
partial_list.py:15: : note: use --pdb to drop into pdb

To Reproduce

The traceback above was generated by the file partial_list.py:

class Parent:
    # No error if there's no type hint in the constructor (type unimportant)
    def __init__(self, args: int):
        # The next line succeeds if it's a tuple, but fails on list.
        self.var_a = []
        # The type of this variable doesn't seem to matter at all, but it needs
        # to exist and be trying to set a property.
        self.var_b = None

    # This needs to be a property, but the getter doesn't affect anything.
    var_b = property(fset=lambda self, value: None)


class Child(Parent):
    @property  # Error reported on this line.
    def var_a(self):
        return self._var_a

    @var_a.setter
    def var_a(self, value):
        self._var_a = []

This was run with mypy --show-traceback partial_list.py in an empty venv, after deleting the .mypy-cache directory, and I don't think there's any other configuration being pulled from anywhere.

This was as small a reproducer as I could find. It seems to matter that there are two properties (although one of them only needs a setter), and the child class seems to need to be overriding a partially typed instance variable with a getter property that's also partially typed.

Your Environment

  • Mypy version used: master: 0.920.dev @ 01e6aba
  • Mypy command-line flags: --show-traceback
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.9 and 3.10
  • Operating system and version: macOS 11.6

Activity

  1. sobolevn commented on Dec 8, 2021

    @sobolevn
    Member

    This small change fixes the crash. But, I am not sure it is correct:
    Снимок экрана 2021-12-08 в 22 06 25

    Maybe you will be interested in creating a PR? You can add your case somewhere here: https://github.com/python/mypy/blob/master/test-data/unit/check-classes.test

  2. jakelishman commented on Dec 9, 2021

    @jakelishman
    ContributorAuthor

    Thanks for the response! This change seems to solve this crash, but I'm not 100% it's not treating a symptom and not the cause - the inferred type of var_a seems to depend on the presence of the other property in the class, and a type hint in the parent's constructor, where it seems like it shouldn't.

    If I remove all the var_b property stuff, then the crash goes away and mypy reports the errors I expected to see, but to me, the type of var_b shouldn't have any impact on the type of var_a.

    I'd be happy to make a PR, but in this case I don't think I understand the underlying cause myself yet - if your solution is correct, I don't fully understand why - so I'd rather wait on that. I can try and have a look myself, to try and satisfy myself that I understand the solution completely, but it may take me a while.

  3. AlexWaygood commented on Mar 26, 2022

    @AlexWaygood
    Member

    Similar traceback to #11981 and #12109

  4. jakelishman commented on Jun 5, 2022

    @jakelishman
    ContributorAuthor

    This transpires to be more general than just @property decorators - it's caused when any child method attempts to override a value whose type has only been partially inferred. The reproducer here can be done in several different ways, but this is probably the purest, most minimal example, touching the least number of different components:

    class Base:
        def __init__(self, arg: int):
            self.partial_type = []
            self.force_deferral = []
    
        force_deferral = []
    
    
    class Derived(Base):
        def partial_type(self) -> int:
            ...

    The Base.__init__ block will be deferred because of the multiple definitions of force_deferral, which leaves Base.partial_type inferred as <partial list[?]> after the first pass (the arg: int just forces us to use complete type checking within the __init__). Analysis of Derived.partial_type then needs to check if the types def () -> int and <partial list[?]> are compatible, which fails with the "cannot check a partial type with issubtype" error in the main post.

    I think the correct solution to this is what's given above in #11686 (comment), since I now understand why the seemingly unrelated force_deferral was affecting whether the crash appeared. I've made a PR #12943 to that effect.

  5. added a commit that references this issue on Jul 8, 2022
    9f99400
  6. added a commit that references this issue on Aug 12, 2022
    81d082c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions