Mypy: __subclasses__() -> "Only concrete class can be given where "Type[C]" is expected"

Created on 16 Jan 2019  路  7Comments  路  Source: python/mypy

this seems related to #5374 but only in the error message (?)

I'm trying to port a section of code that was previously using a metaclass to just using __subclasses__. Here's a minimal example of what I'm trying to do:

import abc
import inspect
from typing import Dict
from typing import List
from typing import Type


class C(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def foo(self):
        raise NotImplementedError(foo.__name__)


def _make_reg() -> Dict[str, Type[C]]:
    ret: Dict[str, Type[C]] = {}

    #subclasses: List[Type[C]] = C.__subclasses__()
    subclasses = C.__subclasses__()
    reveal_type(C.__subclasses__())
    reveal_type(subclasses)
    while subclasses:
        cls = subclasses.pop()
        subclasses.extend(cls.__subclasses__())

        if inspect.isabstract(cls):
            continue

        if cls.__name__ in ret:
            raise AssertionError(cls.__name__, ret[cls.__name__], cls)
        else:
            ret[cls.__name__] = cls

    return ret


REG = _make_reg()

This, as written, fails with:

$ mypy --version
mypy 0.650
$ mypy t.py
t.py:19: error: Revealed type is 'builtins.list[def () -> t.C]'
t.py:20: error: Revealed type is 'builtins.list[def () -> t.C]'
t.py:31: error: Only concrete class can be given where "Type[C]" is expected

If I replace the assignment of subclasses with the commented out line, it succeeds:

$ mypy t.py
t.py:19: error: Revealed type is 'builtins.list[def () -> t.C]'
t.py:20: error: Revealed type is 'builtins.list[Type[t.C]]'
  1. why does the annotation make this allowable?
  2. is the original message a bug? I believe I'm dealing entirely in Type[C] objects in a typesafe manner even without the inspect.isabstract call (notice I'm never even attempting to _construct_ a Type[C])

Most helpful comment

@asottile OK, sorry, sometimes it is hard to get the tone from written text :-)

All 7 comments

This is a duplicate of #5374 and https://github.com/python/mypy/issues/4717. You can read the issues for why it may be unsafe (essentially in this case we can't exclude that dict.__setitem__() instantiates cls, but as @JukkaL mentioned in both issues, this looks like an overkill).

under what circumstance would dict.__setitem__ cause cls to be instantiated?

this also still doesn't explain why adding an annotation causes it to be ok (which is why I created a separate issue from those two)

under what circumstance would dict.__setitem__ cause cls to be instantiated?

Although in your code ret is just a dict (not a subclass), a subclass of dict can do this.

this also still doesn't explain why adding an annotation causes it to be ok

It does. Because with your annotation cls will be Type[C], and passing Type[C] where Type[C] is expected is OK.

isn't the place where I added the annotation also an assignment from () -> C to Type[C]? I would think that would also trigger this error

No, that check was added only as an additional special-case check when the types appear as "top level" in an assignment or function call, it doesn't affect subtyping, so doesn't trigger when the types appear in a nested position.

This whole abstract Type[...] was already a compromise between false positives and false negatives, and from what we see now it is probably a bit too "aggressive".

Also please stop arguing, it is not helpful.

I'm not arguing, simply curious -- thanks for the explanation that's all I wanted here <3

@asottile OK, sorry, sometimes it is hard to get the tone from written text :-)

Was this page helpful?
0 / 5 - 0 ratings