Skip to content

Cannot infer type of generic attributes in match statements when inheritance is involved #13620

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

Open
ariebovenberg opened this issue Sep 7, 2022 · 7 comments
Assignees
Labels
bug mypy got something wrong topic-match-statement Python 3.10's match statement

Comments

@ariebovenberg
Copy link

ariebovenberg commented Sep 7, 2022

Bug Report

mypy has trouble inferring the type of generic attributes in match statements when inheritance is involved.

This issue was encountered while investigating #13612

To Reproduce

from typing import Generic, TypeVar

T = TypeVar("T")


class Base(Generic[T]):
    ...

class A(Base[T]):
    x: T

    __match_args__ = ('x', )

    def __init__(self, x: T):
        self.x = x


a: Base[str] = A("foo")
reveal_type(a)  # Base[str] (correct)

match a:
    case A(b):
        reveal_type(b)  # Any (incorrect! Should be builtins.str)

Expected Behavior

The attribute on the last line above is revealed to be str

Actual Behavior

It is revealed to be Any

Your Environment

  • Mypy version used: mypy-0.980+dev.b031f1c04e1ee4331e4d6957c7a9b727293328a9
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.10.5
  • Operating system and version: n/a
@ariebovenberg ariebovenberg added the bug mypy got something wrong label Sep 7, 2022
@ariebovenberg ariebovenberg changed the title Cannot infer type of attributes in match statements when inheritance is involved Cannot infer type of generic attributes in match statements when inheritance is involved Sep 7, 2022
@sobolevn sobolevn added the topic-match-statement Python 3.10's match statement label Sep 7, 2022
@sobolevn sobolevn self-assigned this Sep 7, 2022
@AlexWaygood
Copy link
Member

AlexWaygood commented Sep 7, 2022

This is possibly a repro without pattern-matching:

from typing import Generic, TypeVar

T = TypeVar("T")

class Foo(Generic[T]):
    attr: T
    def __init__(self, attr: T) -> None:
        self.attr = attr
        
class Bar(Foo[T]): ...

x: Foo[str] = Bar("idk")

if isinstance(x, Bar):
    reveal_type(x.attr)  # Any (should be str!)

https://mypy-play.net/?mypy=latest&python=3.10&gist=5ba0af842f3a9d75ab0498009e0d92cb

@sobolevn
Copy link
Member

sobolevn commented Sep 7, 2022

I think it is something similar to #13607 🤔
But, I am not sure. I didn't have any time to debug this yet.

@sobolevn
Copy link
Member

sobolevn commented Sep 8, 2022

There are couple of this with this example:

from typing import Generic, TypeVar

T = TypeVar("T")


class Base(Generic[T]):
    ...

class A(Base[T]):
    x: T

    __match_args__ = ('x', )

    def __init__(self, x: T):
        self.x = x


a: Base[str] = A("foo")
reveal_type(a)  # Base[str] (correct)

match a:
    case A(b):
        reveal_type(b)  # Any (incorrect! Should be builtins.str)

Right now it is unclear to me: how to properly handle Base in the first place? It does not have x, it does not have __init__, it does not have __match_args__. Should it be even allowed to match A(b)? Probably yes, but this example is hard to process.

@ariebovenberg
Copy link
Author

ariebovenberg commented Sep 8, 2022

@sobolevn my understanding is that it doesn't matter that Base doesn't have __match_args__ or __init__, this should only matter for the case statements.

...but in the end the problem doesn't appear specific to match as @AlexWaygood demonstrates.

The core problem is that mypy should infer that the T in A[T] can only be str once the value is narrowed to Base[str].

@sobolevn
Copy link
Member

sobolevn commented Sep 8, 2022

Yeap, this is another problem :)
I am actually working on code that @AlexWaygood provided. It is much easier for me to understand.

@sobolevn
Copy link
Member

sobolevn commented Sep 9, 2022

Ok, the problem is in this line:

return t

Ideally, we should use something like map_instance_to_supertype to preserve type information about current type arguments.

But, we need a completely new approach for this. It is not implemented at all.

So, we take the subtype, go for all the route inside mro tree to find our supertype.
Then, in reverse order we apply type arguments from one parent to its subclass until we reach the our instance.

Then, we return the mapped instance and it will solve our problem.

@sobolevn
Copy link
Member

sobolevn commented Sep 9, 2022

Ok, I found map_type_from_supertype

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-match-statement Python 3.10's match statement
Projects
None yet
Development

No branches or pull requests

3 participants