Do not intersect types in isinstance checks if at least one is final. by tyralla · Pull Request #16330 · python/mypy

The sphinx issue caused me some headaches, but I think I've collected some hints.

The argument cls of function restify is annotated with type | None but should be annotated (at least) with str | type | None. Mypy should complain about this in the implemented if isinstance(cls, str): check but doesn't because it assumes the intersection "sphinx.util.typing.<subclass of "type" and "str">" which in my opinion is impossible because of instance layout conflicts (and I cannot think of a way this makes sense).

Interestingly, Mypy 1.6 agrees with my reasoning when annotating with Type instead of type:

from typing import Type

t1: type
t2: Type

if isinstance(t1, str):
    reveal_type(t1)  # note: Revealed type is "temp.<subclass of "type" and "str">"
if isinstance(t2, str):
    reveal_type(t2)  # error: Statement is unreachable

If I change the annotation of cls from type | None to Type | None, Mypy 1.6 also does not require the type: ignore[attr-defined] comment anymore because it considers the affected line unreachable.

So, everything seems clear. However, when applying Mypy 1.6 on the following example code involving _SpecialForm instead of the complete sphinx code, it finds out about the unreachability no matter if annotating with type or Type:

from typing import Type, _SpecialForm

t1: type
t2: Type

if isinstance(t1, _SpecialForm):  # error: Subclass of "type" and "<typing special form>" cannot exist: would have incompatible method signatures
    reveal_type(t1)  # error: Statement is unreachable
if isinstance(t2, _SpecialForm):
    reveal_type(t2)  # error: Statement is unreachable

To conclude, strange things might be going on, but they are not related to this pull request.