Skip to content

Fix incorrect type inference for enum.auto() in IntEnum members - #3202

Open
Genny-oo wants to merge 2 commits into
pylint-dev:mainfrom
Genny-oo:fix-enum-auto-type-inference
Open

Fix incorrect type inference for enum.auto() in IntEnum members#3202
Genny-oo wants to merge 2 commits into
pylint-dev:mainfrom
Genny-oo:fix-enum-auto-type-inference

Conversation

@Genny-oo

@Genny-oo Genny-oo commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

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(), returning the string "enum.auto()". This caused astroid to infer .value as type auto instead of int, producing false-positive E1101 errors.

Fix: Uses the existing _looks_like() helper to detect both enum.auto() and auto() (unqualified, after from enum import auto) spellings, substituting integer 1 so the stub infers the correct type (int) for IntEnum members.

Tests: Added two tests to EnumBrainTest in tests/brain/test_enum.py covering both the qualified and unqualified auto() spellings.

Closes #1847

Type of Changes

Type
🐛 Bug fix

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
@codspeed-hq

codspeed-hq Bot commented Aug 5, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 3 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing Genny-oo:fix-enum-auto-type-inference (aae6606) with main (8666418)2

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

  2. No successful run was found on main (8577346) during the generation of this report, so 8666418 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.64%. Comparing base (cf2db9b) to head (aae6606).
⚠️ Report is 79 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            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     
Flag Coverage Δ
linux 93.50% <100.00%> (+0.03%) ⬆️
pypy 93.64% <100.00%> (+0.04%) ⬆️
windows 93.62% <100.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
astroid/brain/brain_namedtuple_enum.py 94.13% <100.00%> (+0.82%) ⬆️

... and 31 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kdelay kdelay left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@Genny-oo

Genny-oo commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review! I've addressed all three points:

  1. Switched to _looks_like(stmt.value, "auto") to cover both enum.auto() and auto() spellings
  2. Moved the tests into EnumBrainTest in tests/brain/test_enum.py and added a test for the unqualified form
  3. Updated the comment to clarify that 1 is used for type inference only and runtime values may differ for later members

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 DanielNoord left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Using auto enum values provides incorrect type

4 participants