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

Cannot determine consistent method resolution order (MRO) when inheriting from both MutableSequence and list #11427

Open
abravalheri opened this issue Nov 1, 2021 · 3 comments
Labels
bug mypy got something wrong topic-inheritance Inheritance and incompatible overrides topic-runtime-semantics mypy doesn't model runtime semantics correctly

Comments

@abravalheri
Copy link

Bug Report

There are some scenarios where inheriting from MutableSequence and built-in list (in this exact order) is important. This is the case for atoml and tomlkit.

However this results in a typecheck error with mypy: Cannot determine consistent method resolution order (MRO)

Please notice no runtime error is found.

To Reproduce

Consider the following simplified example (extracted from atoml/tomlkit):

example.py

from typing import Optional
from collections.abc import MutableSequence


class Item:
    def __init__(self, comment: Optional[str] = None):
        self._comment = comment


class Array(Item, MutableSequence, list):
    def __init__(self, values: list, comment: Optional[str] = None):
        Item.__init__(self, comment)
        list.__init__(self, values)


if __name__ == '__main__':
    a = Array([0, 1, 2])
    print([p.__name__ for p in a.__class__.__mro__])

Expected Behavior

I would expect the MRO detected by mypy is exactly the same one given in runtime, i.e.:

['Array', 'Item', 'MutableSequence', 'Sequence', 'Reversible', 'Collection', 'Sized', 'Iterable', 'Container', 'list', 'object']

Actual Behavior
During runtime the MRO is well defined and works as expected:

$ python3 example.py
['Array', 'Item', 'MutableSequence', 'Sequence', 'Reversible', 'Collection', 'Sized', 'Iterable', 'Container', 'list', 'object']

But mypy fails to detect it and get completely lost when checking the Array class.

$ mypy example.py
example.py: note: In class "Array":
example.py:10: error: Cannot determine consistent method resolution order (MRO) for "Array"  [misc]
    class Array(Item, MutableSequence, list):
    ^
example.py: note: In member "__init__" of class "Array":
example.py:12: error: Argument 1 to "__init__" of "Item" has incompatible type "Array"; expected "Item"
 [arg-type]
            Item.__init__(self, comment)
                          ^
example.py:13: error: No overload variant of "__init__" of "list" matches argument types "Array",
"List[Any]"  [call-overload]
            list.__init__(self, values)
            ^
example.py:13: note: Possible overload variant:
example.py:13: note:     def [_T] __init__(self, self: List[_T], iterable: Iterable[_T]) -> None
example.py:13: note:     <1 more non-matching overload not shown>
Found 3 errors in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 0.910
  • Mypy command-line flags: None
  • Mypy configuration options from mypy.ini (and other config files):
    mypy.ini
[mypy]
pretty = True
show_error_codes = True
show_error_context = True
show_traceback = True
ignore_missing_imports = True
warn_redundant_casts = True
warn_unused_ignores = True
  • Python version used: 3.8.0
  • Operating system and version: Ubuntu 18.04.5 LTS

Please notice that while this particular choice of inheritance is debatable, it is fundamental for the way atoml/tomlkit work. We need MutableSequence as a mixin to implement the custom logic, but we also want the objects to be considered lists, so the users can use the API transparently as if they were dealing with built-in objects (this is an important aspect of the design).

@abravalheri abravalheri added the bug mypy got something wrong label Nov 1, 2021
@erictraut
Copy link

I think mypy is doing the correct thing here based on the type information provided in typeshed. Pyright also emits the same error in this case.

The problem appears to be due to a "lie" in the definition of the list class in builtins.pyi. It indicates that list derives from MutableSequence, but at runtime it does not. I don't see a way to fix this without breaking a lot of existing assumptions.

You may need to simply ignore the error in this case by using a # type: ignore comment.

@abravalheri
Copy link
Author

abravalheri commented Nov 1, 2021

I am afraid # type: ignore is not a good solution here (it was the first thing I tried to be sincere 😅).

What happens is that once mypy have problems to detect the MRO any subsequent usage of objects from the Array class is compromised in terms of typechecking. In practice mypy forgets the class inherits from any of its parents.

Going for a # type: ignore approach, means adding it to all the calls to inherited methods and in practice doing no type check in any of them.


Just as an example, by doing:

class Array(Item, MutableSequence, list):  # type: ignore
   ...

The errors would still be:

$ mypy example.py
example.py: note: In member "__init__" of class "Array":
example.py:12: error: Argument 1 to "__init__" of "Item" has incompatible type "Array"; expected "Item"
 [arg-type]
            Item.__init__(self, comment)
                          ^
example.py:13: error: No overload variant of "__init__" of "list" matches argument types "Array",
"List[Any]"  [call-overload]
            list.__init__(self, values)
            ^
example.py:13: note: Possible overload variant:
example.py:13: note:     def [_T] __init__(self, self: List[_T], iterable: Iterable[_T]) -> None
example.py:13: note:     <1 more non-matching overload not shown>
Found 2 errors in 1 file (checked 1 source file)

@erictraut
Copy link

Ah yeah, that's a problem. Maybe the best fix is for mypy to build an MRO even if it's not consistent rather than forgetting the entire class hierarchy. This is what pyright does in this circumstance, and it appears to work fine, at least in this case. Other than the initial error (reporting the MRO consistency issue), subsequent uses of the class work fine.

abravalheri added a commit to abravalheri/atoml that referenced this issue Nov 2, 2021
As reported in python/mypy#11427, a "white lie" in typeshed
messes completely the type inference for classes inheriting from
``(MutableMapping, dict)`` and ``(MutableSequence, list)``.

This is a workaround that results in useful type inference.
abravalheri added a commit to abravalheri/atoml that referenced this issue Nov 2, 2021
As reported in python/mypy#11427, a "white lie" in typeshed
messes completely the type inference for classes inheriting from
``(MutableMapping, dict)`` and ``(MutableSequence, list)``.

This is a workaround that results in useful type inference.
@AlexWaygood AlexWaygood added topic-inheritance Inheritance and incompatible overrides topic-runtime-semantics mypy doesn't model runtime semantics correctly labels Apr 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-inheritance Inheritance and incompatible overrides topic-runtime-semantics mypy doesn't model runtime semantics correctly
Projects
None yet
Development

No branches or pull requests

3 participants