Skip to content

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

Open

Description

@ariebovenberg

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

Activity

  1. 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[/+] on Sep 7, 2022
  2. self-assigned this
    on Sep 7, 2022
  3. AlexWaygood commented on Sep 7, 2022

    @AlexWaygood
    Member

    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

  4. sobolevn commented on Sep 7, 2022

    @sobolevn
    Member

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

  5. sobolevn commented on Sep 8, 2022

    @sobolevn
    Member

    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.

  6. ariebovenberg commented on Sep 8, 2022

    @ariebovenberg
    Author

    @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].

  7. sobolevn commented on Sep 8, 2022

    @sobolevn
    Member

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

  8. sobolevn commented on Sep 9, 2022

    @sobolevn
    Member

    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.

  9. sobolevn commented on Sep 9, 2022

    @sobolevn
    Member

    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

Metadata

Metadata

Assignees

Labels

bugmypy got something wrongtopic-match-statementPython 3.10's match statement

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions