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

a Type is not recognized properly for subclassing #10962

Open
hydrargyrum opened this issue Aug 10, 2021 · 9 comments
Open

a Type is not recognized properly for subclassing #10962

hydrargyrum opened this issue Aug 10, 2021 · 9 comments
Labels
bug mypy got something wrong

Comments

@hydrargyrum
Copy link

hydrargyrum commented Aug 10, 2021

Bug Report

I'm explicitly typing a variable with Type (or type) and mypy thinks I should not subclass it.

To Reproduce

from typing import Type

class Base:
    ...

def get_base() -> Type[Base]:
    return Base

BaseAlias: Type[Base] = get_base()

class Derived(BaseAlias):
    ...

Expected Behavior

There should be no error. I'm subclassing a type, which is ok. Furthermore, mypy has the information that it's a type so subclassing should not be questioned.

Actual Behavior

footypes.py:11: error: Variable "footypes.BaseAlias" is not valid as a type
footypes.py:11: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
footypes.py:11: error: Invalid base class "BaseAlias"
Found 2 errors in 1 file (checked 1 source file)

Same behavior if specifying BaseAlias: type = get_base() instead.

Your Environment

  • Mypy version used: 0.910
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.9.0
  • Operating system and version: python:3.9 docker image
@hydrargyrum hydrargyrum added the bug mypy got something wrong label Aug 10, 2021
@sobolevn
Copy link
Member

Even this does not work:

class Base:
    ...

def get_base() -> Base:
    ...

BaseAlias = get_base()

class Derived(BaseAlias):
    ...

Outputs:

ex.py:11: error: Variable "ex.BaseAlias" is not valid as a type
ex.py:11: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
ex.py:11: error: Invalid base class "BaseAlias"

I'll see what we can do here.

@sobolevn
Copy link
Member

I guess that the easiest way to explain this is with this example:

from typing import Type

class Base:
    ...

class NotSoBase(Base):
    a = 1

def get_base() -> Type[Base]:
    return NotSoBase

BaseAlias: Type[Base] = get_base()

class Derived(BaseAlias):
    ...

This example will raise the same error. But, it differs in what type we return from get_base(). Notice, that -> Type[Base] return annotation is still repsected, but we return NotSoBase type instead, which has extra fields.

In your example you expect Mypy to understand that we use Base as a supertype, but my counter-example shows that this is not trivial. We can end up with quite broken TypeInfos this way. 😞

@yrd
Copy link

yrd commented Dec 2, 2021

@sobolevn Would you mind explaining you couterexample a bit more? As far as I understand, using BaseAlias as a superclass should basically only say "I'm defining something that implements the interface of Base", because the alias has type Type[Base]. Even if you have another superclass between them in the chain (NotSoBase in your example), the substitution principle shouldn't be violated.

I'm asking because I think I have another example which is related to this issue:
https://mypy-play.net/?mypy=latest&python=3.10&gist=4f4e45da223f0991300d6a71bb14724d

@sobolevn
Copy link
Member

sobolevn commented Dec 2, 2021

@yrd I think that this:

def get_base() -> Type[Base]:
    return NotSoBase

BaseAlias: Type[Base] = get_base()

class Some(BaseAlias): ...

Will generate inconsistent MRO for mypy. Imagine that this code is valid. Then, mypy would think that Some is a subtype of Base, but in reallity it would be a subtype of NotSoBase.

@yrd
Copy link

yrd commented Dec 6, 2021

Hm, I see that now - thanks!

yrd added a commit to iWeltAG/zucker that referenced this issue Dec 16, 2021
This removes the `client.Module` syntax we had before, because that was
a nightmare to type correctly. This is more or less due to this[1] issue
in mypy. Once we find a good way to make type checkers happy, the syntax
may return. But for now, using SyncModule and AsyncModule as a
superclass will suffice.

1: python/mypy#10962
@jab
Copy link
Contributor

jab commented Oct 1, 2023

I've been hitting what I think is this same issue in bidict too. I've been silently 'type: ignoring' it, but figured I'd chime in here, in case this is another useful test case:

def namedbidict(
    typename: str,
    keyname: str,
    valname: str,
    *,
    base_type: type[BidictBase[KT, VT]] = bidict,
) -> type[BidictBase[KT, VT]]:
    ...
    class NamedBidict(base_type):  # false positive errors here (see below)
        ...
$ mypy bidict
bidict/_named.py: note: In function "namedbidict":
bidict/_named.py:72:23: error: Variable "base_type" is not valid as a type
[valid-type]
        class NamedBidict(base_type):
                          ^
bidict/_named.py:72:23: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
bidict/_named.py:72:23: error: Invalid base class "base_type"  [misc]
        class NamedBidict(base_type):
                          ^
Found 2 errors in 1 file (checked 26 source files)

@sobolevn
Copy link
Member

sobolevn commented Oct 1, 2023

You can model namedbidict as a class :)

@jab
Copy link
Contributor

jab commented Oct 1, 2023

If I understand you correctly, the namedbidict API has been in bidict since 2009, long before Python had type hints. Sharing this example in case it’s desirable for mypy to support such APIs. If not, fair enough!

@Avasam
Copy link
Sponsor Contributor

Avasam commented Aug 22, 2024

This affects setuptool's get_unpatched method, which is typed as such:

def get_unpatched(item: _T) -> _T: ...

# Used like:
_Distribution = get_unpatched(distutils.core.Distribution)

It also lead to unexpected invalid-type errors for downstream users of libraries not aware of this problem. For instance, platformdirs.PlatformDirs is currently an invalid type: https://github.com/jaraco/jaraco.abode/blob/8843f360ee5d9bc1afb1bdb3119157addb4aaf06/jaraco/abode/config.py#L4C7-L4C19

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

5 participants