Skip to content

abstractmethod + generic subclass resulting in failed inference #8873

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

Closed
beezee opened this issue May 22, 2020 · 4 comments
Closed

abstractmethod + generic subclass resulting in failed inference #8873

beezee opened this issue May 22, 2020 · 4 comments
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal

Comments

@beezee
Copy link
Contributor

beezee commented May 22, 2020

This appears to be a bug.

Minimal repro:

from abc import ABC, abstractmethod
from typing import Generic, TypeVar

A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')

class TwoTypes(Generic[A, B]): pass

class ReproBase(ABC, Generic[C]):

  @abstractmethod
  def repro(self, a: A) -> TwoTypes[C, A]: pass

class ReproSub(ReproBase[C]):

  @abstractmethod
  def repro_defer(self, a: A) -> TwoTypes[C, A]: pass

  def repro(self, a: A) -> TwoTypes[C, A]:
    return self.repro_defer(a)

class ReproSub2(ReproBase[C], Generic[C]):

  @abstractmethod
  def repro_defer(self, a: A) -> TwoTypes[C, A]: pass

  def repro(self, a: A) -> TwoTypes[C, A]:
    return self.repro_defer(a)

Type-checking results in error: Incompatible return value type (got "TwoTypes[A, A]", expected "TwoTypes[C, A]")

mypy doesn't like ReproSub or ReproSub2 for the same reason, but interestingly only complains about the first it encounters, depending on which order you put the subclasses in.

I'd expect C to be captured correctly and the return type inferred on any concrete definition of the repro method to be TwoTypes[C, A], as is specified by the delegated defer_repro method in both cases.

Using mypy 0.770, Python 3.7.6

Issue appears to be fixed in master

Not using any flags

Considering it's fixed in master, if someone knows specifically what the fix is, I'd love a link to the commit(s) just to understand what I'm running into on current release, and any info about when the fix might become generally available.

@JukkaL
Copy link
Collaborator

JukkaL commented May 22, 2020

This is bizarre. I was able to reduce the repro to this:

from typing import Generic, TypeVar

A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')

class TwoTypes(Generic[A, B]): pass

class X(Generic[C]):
    def f(self, a: A) -> TwoTypes[C, A]: pass

class Y(X[C]):
    def g(self, a: A) -> TwoTypes[C, A]: pass

    def f(self, a: A) -> TwoTypes[C, A]:
        return self.g(a)  # Error here

class Z(X[C]):
    def g(self, a: A) -> TwoTypes[C, A]: pass

    def f(self, a: A) -> TwoTypes[C, A]:
        return self.g(a)  # But not here!

If I switch classes Y with Z, the error moves to the other class. My hypothesis is that somehow the textual order of the classes plays a role.

@JukkaL JukkaL added bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal labels May 22, 2020
@beezee
Copy link
Contributor Author

beezee commented May 22, 2020

Yeah it's strange. I'd note that it's an error even with only one subclass of X - it seems that only the first encountered is reported.

@beezee
Copy link
Contributor Author

beezee commented May 23, 2020

New information @JukkaL - this is not fixed on master, and it appears related to the cache. Are there different logical pathways for use of cache vs not?

On both master and the current release, I observe the following:

This code typechecks initially after mypy installation (seems to be a hard cache clear here):

from abc import ABC, abstractmethod
from typing import Callable, Generic, TypeVar

A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')

class TwoTypes(Generic[A, B]): pass

class ReproBase(ABC, Generic[C]):

  @abstractmethod
  def repro(self, a: A) -> TwoTypes[C, A]: pass

class ReproSub(ReproBase[C]):

  @abstractmethod
  def repro_defer(self, a: A) -> TwoTypes[C, A]: pass

  def repro(self, a: A) -> TwoTypes[C, A]:
    return self.repro_defer(a)

If I purposely break ReproSub().repro by returning None, I get the error I'd expect error: Incompatible return value type (got "None", expected "TwoTypes[C, A]")

Now if I fix ReproSub().repro by returning to it's previous implementation, the error doesn't clear, rather it changes to the unexpected and incorrect error highlighting a failed inference error: Incompatible return value type (got "TwoTypes[A, A]", expected "TwoTypes[C, A]")

Now if I run with mypy --cache-dir=/dev/null repro.py, the error clears, but only with cache bypassed. Something bad is getting into the cache and not coming out.

It's worth noting this inference problem looks identical to one I fixed around 6 months ago here #7933 - I had thought this was a regression, now I'm wondering if there are more divergent/duplicated pathways between cached vs uncached operation.

Appreciate any insight or pointers, would be open to taking a crack at a fix if it's not a super heavy lift.

@erictraut
Copy link

It looks like this bug has been fixed at some point. I'm not able to repro any of the errors in the code samples above. I also can't repro the apparent cache issue. Following the repro steps above, mypy correctly emits an error when it should and then clears it when the error is eliminated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal
Projects
None yet
Development

No branches or pull requests

4 participants