Skip to content

itertools.product called on a Flag enum is inferred as object rather than class name #18684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jatkinson1000 opened this issue Feb 16, 2025 · 5 comments
Labels
bug mypy got something wrong

Comments

@jatkinson1000
Copy link

Bug Report

When defining a class based on Flag from the enum package I would expect items of the class to be of the type of the class when I iterate over them using itertools functions.
However, instead mypy raises an error that they are instead of type object.

To Reproduce

See playground link here: https://mypy-play.net/?mypy=latest&python=3.12&gist=5dc24e94f25872e5c5d8ac032c4d8309

Expand for code in playground to reproduce.
from enum import Flag, auto
import itertools


class Test_Flag_1(Flag):
    TF1a = auto()
    TF1b = auto()
    TF1c = auto()

class Test_Flag_2(Flag):
    TF2a = auto()
    TF2b = auto()
    TF2c = auto()

def test_func_1(flagitem: Test_Flag_1) -> None:
    print(flagitem.name)

def test_func_2(flagitem: Test_Flag_2) -> None:
    print(flagitem.name)


if __name__ == "__main__":
    for flagitem1, flagitem2 in itertools.product(
        Test_Flag_1, Test_Flag_2
    ):
        test_func_1(flagitem1)
        test_func_2(flagitem2)

Expected Behavior

I would expect this to pass fine through mypy.

Actual Behavior

Instead items of the Test_Flag_1 or Test_Flag_2 subclasses of Flag when iterated over using itertools.product are picked up by mypy as type object instead of type Test_Flag_1 or Test_Flag_2.

(venv) [user@machine ~]$ mypy mypy_minimal.py
mypy_minimal.py:26: error: Argument 1 to "test_func_1" has incompatible type "object"; expected "Test_Flag_1"  [arg-type]
mypy_minimal.py:27: error: Argument 1 to "test_func_2" has incompatible type "object"; expected "Test_Flag_2"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 1.15.0 (compiled: yes)
  • Mypy command-line flags: None
  • Mypy configuration options from mypy.ini (and other config files): None
  • Python version used: 3.13.1 (also reproduced with 3.12 in the playground link)

Full pip list:

Package           Version
----------------- -------
mypy              1.15.0
mypy-extensions   1.0.0
typing-extensions 4.12.2
@jatkinson1000
Copy link
Author

jatkinson1000 commented Feb 16, 2025

Strangely, when I run this on a subset of the Flag subclass using the | operator rather than the entire subclass mypy passes fine:

if __name__ == "__main__":
    for flagitem1, flagitem2 in itertools.product(
        Test_Flag_1.TF1a | Test_Flag_1.TF1b, Test_Flag_2.TF2a | Test_Flag_2.TF2b
    ):
        test_func_1(flagitem1)
        test_func_2(flagitem2)

@jatkinson1000 jatkinson1000 changed the title itertools.product called on a Flag enum returns product rather than class name itertools.product called on a Flag enum returns object rather than class name Feb 16, 2025
@jatkinson1000 jatkinson1000 changed the title itertools.product called on a Flag enum returns object rather than class name itertools.product called on a Flag enum is inferred as object rather than class name Feb 16, 2025
@jatkinson1000
Copy link
Author

Similarly, expanding the function of itertools.product into two nested loops passes fine, so it seems to be the way in which mypy interacts with the itertools.product over a Flag enum:

if __name__ == "__main__":
    for flagitem1 in Test_Flag_1:
        for flagitem2 in Test_Flag_2:
            test_func_1(flagitem1)
            test_func_2(flagitem2)

@ilevkivskyi
Copy link
Member

I can't reproduce this on the master branch, and we usually close things as soon as they are fixed on master. (You can also try it in the playground like this https://mypy-play.net/?mypy=master&python=3.12&gist=5dc24e94f25872e5c5d8ac032c4d8309)

@jatkinson1000
Copy link
Author

jatkinson1000 commented Feb 16, 2025

Thanks, can confirm that gist passes fine using master branch.

Any idea when the fix will make it into a release, or advice on how one can use the master branch in a project in anticipation?

Also, do you know which commit/PR resolved this - I would be interested in understanding better how mypy works. Don't worry if not though.

@ilevkivskyi
Copy link
Member

To use dev build you can do something like this https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build (although this is not recommended for production code). Next release should be around mid-March.

Also, do you know which commit/PR resolved this - I would be interested in understanding better how mypy works. Don't worry if not though.

I didn't bisect, but most likely it is my recent PR #18587 where I stopped using both class and instance for protocol inference against class object if an attribute is present on both class and the metclass (like __iter__ in this case). Only metaclass one is used now as it should.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

2 participants