mypy icon indicating copy to clipboard operation
mypy copied to clipboard

False positive for "Non-overlapping identity check" when comparing Enum variants

Open kmurphy4 opened this issue 4 years ago • 1 comments

Bug Report

I think I've uncovered a false positive in mypy's type-checking of is operator with Enums. See below for details.

I know that Enums are a bit funky, so it's possible this is expected behavior (actually, there's a related bug report here https://github.com/python/mypy/issues/9817).

To Reproduce

# test.py
from enum import Enum

class Food(Enum):
    SPAM = "spam"
    HAM = "ham"

    def is_spam(self) -> bool:
        return self is self.SPAM

print(Food.SPAM.is_spam())
print(Food.HAM.is_spam())
$ python3 test.py
True
False
$ mypy test.py
test.py: note: In member "is_spam" of class "Food":
test.py:8:16: error: Non-overlapping identity check (left operand type: "Food", right operand type: "str") 
[comparison-overlap]
            return self is self.SPAM
                   ^
Found 1 error in 1 file (checked 1 source file)

This goes away if I write it as

def is_spam(self) -> bool:
    return self is Food.SPAM

though I'd like to avoid having to duplicate the name of the class a bunch of times in methods with lots of branching on the variant.

Your Environment
$ mypy --version
mypy 0.910
$ cat .mypy.ini
[mypy]
# https://mypy.readthedocs.io/en/stable/index.html

python_version = 3.6

pretty = True
show_column_numbers = True
show_error_codes = True
show_error_context = True

check_untyped_defs = True
disallow_any_generics = True
disallow_incomplete_defs = True
disallow_subclassing_any = True
disallow_untyped_calls = True
disallow_untyped_decorators = True
disallow_untyped_defs = True
no_implicit_optional = True
no_implicit_reexport = True
strict_equality = True
warn_redundant_casts = True
warn_return_any = True
warn_unused_configs = True
warn_unused_ignores = True
$ python3 --version
Python 3.6.9
$ lsb_release -ds
Ubuntu 18.04.5 LTS

kmurphy4 avatar Aug 02 '21 06:08 kmurphy4

Same problem in this code:

#!/usr/bin/env -S uv run --script --python 3.13


from enum import Enum


class Animals(Enum):
    CAT = "cat"
    DOG = "dog"
    BIRD = "bird"

    @property
    def is_mammal(self) -> bool:
        # Mypy is confused about the type of the properties when
        # reference via `self.`, it considers them as it would a
        # variable on any other class, and if it was just a variable on
        # a non-enum class, the type of `self.DOG` would be `str`, not
        # `Animals`. So it reports an this error:

        # error: Non-overlapping container check (element type: "Animals", container item type: "str")
        return self in {self.CAT, self.DOG}


class Vehicle(Enum):
    AEROPLANE = "aeroplane"
    CAR = "car"
    HELICOPTER = "helicopter"

    @property
    def is_airborne(self) -> bool:
        # Explicitly getting the type of `self` and then accessing the enum members via the class
        # avoids the mypy error, as it knows that `type(self).AIRPLANE` is of type `Vechicle` and not `str`.

        cls = type(self)
        return self in {cls.AEROPLANE, self.HELICOPTER}


def main() -> None:
    for animal in Animals:
        print(f"{animal = } {animal.is_mammal = }")

    for vehicle in Vehicle:
        print(f"{vehicle = } {vehicle.is_airborne = }")


if __name__ == "__main__":
    main()

Commands:

# run type checking with mypy
uv tool run --from mypy==1.16.0 mypy --strict mypy_enum_property_self.py
# run the script with Python 3.13
uv run --script --python 3.13 mypy_enum_property_self.py

Results:

$ uv tool run --from mypy==1.16.0 mypy --strict mypy_enum_property_self.py
mypy_enum_property_self.py:21: error: Non-overlapping container check (element type: "Animals", container item type: "str")  [comparison-overlap]
Found 1 error in 1 file (checked 1 source file)
$ uv run --script --python 3.13 mypy_enum_property_self.py
animal = <Animals.CAT: 'cat'> animal.is_mammal = True
animal = <Animals.DOG: 'dog'> animal.is_mammal = True
animal = <Animals.BIRD: 'bird'> animal.is_mammal = False
vehicle = <Vehicle.AEROPLANE: 'aeroplane'> vehicle.is_airborne = True
vehicle = <Vehicle.CAR: 'car'> vehicle.is_airborne = False
vehicle = <Vehicle.HELICOPTER: 'helicopter'> vehicle.is_airborne = True

aucampia avatar Jun 03 '25 11:06 aucampia