Skip to content

not isinstance(x, cls) inside classmethod does not narrow type #21271

@robsdedude

Description

@robsdedude

Bug Report

Inside a @classmethod, not isinstance(x, cls) does not appear to narrow the type, while isinstance(x, cls) does.

To Reproduce

This is what I wanted to write:

class Foo:
    def __init__(self, x: float) -> None:
        ...
    
    @classmethod
    def from_foo_or_float_cls(cls, x: Self | float) -> Self:
        if isinstance(x, cls):
            return x
        return cls(x)  # error: Argument 1 to "Foo" has incompatible type "Self | float"; expected "float"  [arg-type]

Further reduced:

class Foo:
    @classmethod
    def foo_1(cls, x: Self | float) -> None:
        assert isinstance(x, cls)
        reveal_type(x)  # expected: "Self"; was: "Self`0"; ✅

    @classmethod
    def foo_2(cls, x: Self | float) -> None:
        assert not isinstance(x, cls)
        reveal_type(x)  # expected: "float"; was: "Self`0" | builtins.float"; ❌

Expected Behavior

not isinstance(x, cls) should narrow the type union to remove Self.

Actual Behavior

Self is still part of the type union.

Your Environment

  • Mypy version used: 1.19.1
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.10

Same behavior can be observers on mypy master with Python 3.14 (tested on the playground).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions