Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
jakelishman opened this issue Dec 8, 2021 · 4 comments · Fixed by #12943
Closed

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

jakelishman opened this issue Dec 8, 2021 · 4 comments · Fixed by #12943
Labels
crash topic-descriptors Properties, class vs. instance attributes

Comments

@jakelishman
Copy link
Contributor

jakelishman commented Dec 8, 2021

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
@sobolevn
Copy link
Member

sobolevn commented Dec 8, 2021

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

@jakelishman
Copy link
Contributor Author

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.

@AlexWaygood AlexWaygood added the topic-descriptors Properties, class vs. instance attributes label Mar 26, 2022
@AlexWaygood
Copy link
Member

AlexWaygood commented Mar 26, 2022

Similar traceback to #11981 and #12109

@jakelishman
Copy link
Contributor Author

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.

JukkaL pushed a commit that referenced this issue Jul 8, 2022
Attributes can still be partially typed (e.g. `<partial list[?]>`) after
a parent-class definition if the block they are declared in is deferred.
The first pass for child classes might then encounter this type when
considering method overrides, which could cause a crash when attempting
to determine subtype compatibility.

Fixes #11686
Fixes #11981
Gobot1234 pushed a commit to Gobot1234/mypy that referenced this issue Aug 12, 2022
Attributes can still be partially typed (e.g. `<partial list[?]>`) after
a parent-class definition if the block they are declared in is deferred.
The first pass for child classes might then encounter this type when
considering method overrides, which could cause a crash when attempting
to determine subtype compatibility.

Fixes python#11686
Fixes python#11981
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crash topic-descriptors Properties, class vs. instance attributes
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants