typeshed icon indicating copy to clipboard operation
typeshed copied to clipboard

NotImplementedType behaves incorrectly in unions with classes

Open robertschweizer opened this issue 1 year ago • 0 comments

from collections.abc import Callable
from types import NotImplementedType


class MyClass:
    pass


callable_union: Callable | NotImplementedType
reveal_type(callable_union)
if not isinstance(callable_union, NotImplementedType):
    reveal_type(callable_union)  # Gets narrowed correctly

class_union: MyClass | NotImplementedType
reveal_type(class_union)
if not isinstance(class_union, NotImplementedType):
    reveal_type(class_union)  # Mypy does not narrow the type

Leads to the following output:

test.py:10: note: Revealed type is "Union[def (*Any, **Any) -> Any, builtins._NotImplementedType]"
test.py:12: note: Revealed type is "def (*Any, **Any) -> Any"
test.py:15: note: Revealed type is "Union[test.MyClass, builtins._NotImplementedType]"
test.py:17: note: Revealed type is "Union[test.MyClass, builtins._NotImplementedType]"

_NotImplementedType in stdlib/builtins.pyi currently inherits from Any. Removing that inheritance as in the below example fixes the issue for me:

from typing import final


@final
class _NotImplementedType:
    # A little weird, but typing the __call__ as NotImplemented makes the error message
    # for NotImplemented() much better
    __call__: NotImplemented  # type: ignore[valid-type]  # pyright: ignore[reportGeneralTypeIssues]


fixed_type: MyClass | _NotImplementedType
reveal_type(fixed_type)
if not isinstance(fixed_type, _NotImplementedType):
    reveal_type(fixed_type)  # Gets narrowed correctly

Edit: The inheritance from Any is actually by design: https://github.com/python/mypy/issues/4791#issuecomment-1694810031

robertschweizer avatar Feb 21 '24 08:02 robertschweizer