Skip to content

mypy should type infer variable as superclass when needed #10965

Closed

Description

@nipunn1313

Bug Report

from itertools import chain
arr = [1,2,3]
doubles = map(lambda x: 2 * x, arr)
doubles = chain(doubles, [8, 10])
print(list(doubles))
➜ python3 test.py
[2, 4, 6, 8, 10]
➜  mypy test.py
test.py:4: error: Incompatible types in assignment (expression has type "chain[int]", variable has type "map[int]")
Found 1 error in 1 file (checked 1 source file)

Based on context, I think mypy should be able to infer that doubles should be a variable of type Iterator[int], since both chain[int] and map[int] are subclasses (Iterator is covariant I believe)

Can fix the above code by explicitly marking doubles: Iterator[int]

Here is a real life example
pypa/pip#10343 (comment)

Expected Behavior

Expected variable to be inferred as Iterator[int]

Activity

  1. A5rocks commented on Aug 10, 2021

    @A5rocks
    Collaborator

    If you want to be pedantic, you could say object (or was it type? Python subclassing is weird) is the superclass of everything here, which doesn't seem very useful...

    Where would you draw the line?

  2. nipunn1313 commented on Aug 10, 2021

    @nipunn1313
    ContributorAuthor

    Yep - fair point.

    In a case like above, map[int] is not a particularly useful class to distinguish from Iterator[int]. Its use comes from overloading the __init__ constructor.

    If we were to solve this use case, I can think of two directions to solve it

    1. Change something about the typing stub annotations of class map to make it clear that we want to return an Iterator[T] rather than map[T] from construction.

    2. Change something about mypy - to help it infer in cases where relevant. "draw a line" as you mentioned. One idea that comes to mind is to draw the line directly below "object", but I can imagine that being more harmful than useful in some cases. Eg (pseudocode)

    from google.protobuf import Message
    class M1(Message): ...
    class M2(Message): ...
    a = M1()
    b = M2()
    # some other code
    a = M1()
    a = M2()  # potentially this is a typo? Would we want it inferred as `Message` or as an error?
  3. JelleZijlstra commented on Aug 10, 2021

    @JelleZijlstra
    Member

    Does mypy's --allow-redefinitions flag help here?

  4. nipunn1313 commented on Aug 10, 2021

    @nipunn1313
    ContributorAuthor

    allow-redefinition would certainly cause the error to go away, but I don't know if it's actually helping.

    In solution 1) We'd want to disallow redefinitions, but somehow treat the results of map and itertools.chain as having the same return type of Iterator[int] (is this doable in typeshed? Can we make the class map constructor return Iterator[int] instead of map[int]?)

    In solution 2) We'd want to disallow redefinitions, but allow mypy to infer a superclass when the redefinition has a common non-trivial superclass (not object).

    From this discussion, I'd probably lean toward something like solution 1 if possible

  5. hauntsaninja commented on Aug 10, 2021

    @hauntsaninja
    Collaborator

    re solution 1: This whole situation is actually caused by typeshed changes, see python/typeshed#5145 . I'd be in favour of going back to the way things were, but I was in the minority when things were changed.

    re solution 2: I'd have to look at how mypy implements --allow-redefinition, but something like joining the new inferred type with the previous type could work. You'd want to ensure that if there's an explicit type annotation it's always respected, but maybe more feasible than I thought when first reading the issue (my mind went first to collecting all constraints from a function and solving them all simultaneously, which would be a very hard change to make to mypy).

  6. nipunn1313 commented on Aug 11, 2021

    @nipunn1313
    ContributorAuthor

    Wow I am amazed how python/typeshed#5219 (comment) actually found the exact issue that caused me to file this. Impressive!
    It sounds like this has already been considered. Ok with closing this - typing it manually isn't such a big deal.

    For map and itertools it might be nice to return an Iterator, since the class doesn't add anything on top, but there's a totally reasonable counterargument to it being a lie compared to runtime (eg messing up documentation).

  7. hauntsaninja commented on Aug 11, 2021

    @hauntsaninja
    Collaborator

    That comment comes from https://github.com/hauntsaninja/mypy_primer :-) If you have any projects you'd want to surface mypy / typeshed regressions on, add them there!

  8. AlexWaygood commented on Mar 30, 2022

    @AlexWaygood
    Member

    I'm closing this issue. I think "inferring variables as superclasses when needed" would be problematic, and quite difficult to generalise, for the reasons @A5rocks stated above.

    I'm sympathetic to the argument that stubbing these classes "truthfully" in typeshed does more harm than good. I'd usually argue strongly for sticking as closely to the runtime as possible, but there sure do seem to be a lot of issues in this bug tracker as a result of stubbing them as classes over at typeshed.

    But regardless, such discussions should be held over at the typeshed issue tracker, rather than here.

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

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions