Fix incorrect type inference for enum.auto() in IntEnum members - #3202
Fix incorrect type inference for enum.auto() in IntEnum members#3202Genny-oo wants to merge 2 commits into
Conversation
When an IntEnum member is assigned via enum.auto(), stmt.value is a nodes.Call node rather than nodes.Const. The previous code fell into the else branch and called .as_string(), which returned the string 'enum.auto()'. This caused astroid to infer .value as type 'auto' instead of 'int', producing false-positive E1101 errors. Fix: detect enum.auto() Call nodes and substitute integer 1 so the generated stub correctly infers int for IntEnum members. Add regression tests in BrainEnumAutoTest covering: - Single auto() member in IntEnum - Mixed auto() and literal-value members Closes pylint-dev#1847
Merging this PR will not alter performance
Comparing Footnotes
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3202 +/- ##
==========================================
+ Coverage 93.60% 93.64% +0.04%
==========================================
Files 92 93 +1
Lines 11364 11569 +205
==========================================
+ Hits 10637 10834 +197
- Misses 727 735 +8
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
kdelay
left a comment
There was a problem hiding this comment.
Checked this out locally at a6cc9d8 and ran it against main. The diagnosis matches what I see, and the two new tests do pin the change: with only the new elif block removed from infer_enum_class, both of them fail; tests/brain/test_brain.py is 142 passed / 2 skipped / 1 xfailed on the branch as it stands.
Three things came out of that.
1. from enum import auto is not covered, and that spelling is the common one.
The guard requires stmt.value.func to be an Attribute, so only the qualified call matches. Inferring Color.RED.value:
import enum / class Color(enum.IntEnum): RED = enum.auto() -> Const.int value=1
from enum import IntEnum, auto / class Color(IntEnum): RED = auto() -> Instance of enum.auto
So the no-member report in #1847 still fires for the unqualified form.
brain_namedtuple_enum.py already has a helper for exactly this shape at line 183, used for namedtuple, Enum and NamedTuple, and it accepts Name as well as Attribute:
elif isinstance(stmt.value, nodes.Call) and _looks_like(stmt.value, "auto"):With that in place both spellings infer Const(1). Full suite on the branch with the swap: 1941 passed, 83 skipped, 15 xfailed, 3 failed. Those three (test_manager.py::test_identify_old_namespace_package_protocol, test_manager.py::test_module_is_not_namespace, test_get_relative_base_path.py::test_symlink_resolution) fail identically on upstream main on this machine, so they are unrelated. It stays name-based, which is what the surrounding transforms already do.
2. The new tests look like they belong in tests/brain/test_enum.py.
EnumBrainTest is where the enum brain is covered, and it already builds enum.auto() members (test_enum_with_ignore, line 547). test_brain.py is the catch-all for brains without their own file.
3. Every auto() member infers 1, including the later ones.
class Color(enum.IntEnum):
RED = enum.auto()
GREEN = enum.auto()
Color.GREEN.value -> Const.int value=1 # 2 at runtime
That is enough for the false positive this closes, since only the type is consulted there, but the stub now carries a value that is wrong rather than unknown, and anything that reads inferred values would follow it. Might be worth either saying so in the comment (it currently reads "so the stub correctly infers int", which is about the type only) or keeping a running counter for the auto members. Either way the type fix on its own closes #1847.
- Use _looks_like() helper to cover both enum.auto() and auto() (unqualified form after 'from enum import auto') - Move new tests from test_brain.py to tests/brain/test_enum.py inside EnumBrainTest, alongside existing auto() coverage - Add test for unqualified auto() spelling - Clarify comment: value 1 is used for type inference only; runtime values for later members will differ
|
Thanks for the thorough review! I've addressed all three points:
|
Pierre-Sassoulas
left a comment
There was a problem hiding this comment.
Hey, thank you for contributing to astroid. Could you add a changelog please ?
| # infers the correct type (int) for IntEnum members; | ||
| # note the value itself may differ at runtime for later | ||
| # members but only the type is consulted for E1101. | ||
| inferred_return_value = 1 |
There was a problem hiding this comment.
StrEnum and custom _generate_next_value_ produce non-int values at runtime class Color(StrEnum): RED = auto() now infers .value as int where runtime is "red". (pre-existing I guess)
DanielNoord
left a comment
There was a problem hiding this comment.
Like @Pierre-Sassoulas said, let's also add a test for StrEnum to ensure we handle both correctly. I strongly beieve the current code doesn't 😄
When an
IntEnummember is assigned viaenum.auto(),stmt.valueis anodes.Callnode rather thannodes.Const. The previous code fell into theelsebranch and called.as_string(), returning the string"enum.auto()". This caused astroid to infer.valueas typeautoinstead ofint, producing false-positive E1101 errors.Fix: Uses the existing
_looks_like()helper to detect bothenum.auto()andauto()(unqualified, afterfrom enum import auto) spellings, substituting integer1so the stub infers the correct type (int) forIntEnummembers.Tests: Added two tests to
EnumBrainTestintests/brain/test_enum.pycovering both the qualified and unqualifiedauto()spellings.Closes #1847
Type of Changes