Skip to content

Restore hashability of Extension objects (broken since 84.0.0 dataclass conversion) - #5306

Closed
aryansk wants to merge 1 commit into
pypa:mainfrom
aryansk:fix-extension-unhashable-5303
Closed

aryansk wants to merge 1 commit into
pypa:mainfrom
aryansk:fix-extension-unhashable-5303

Conversation

@aryansk

@aryansk aryansk commented Aug 23, 2026

Copy link
Copy Markdown

Fixes #5303

Problem

Since v84.0.0, Extension instances are unhashable:

>>> from setuptools.extension import Extension
>>> hash(Extension('x', ['y.c']))
TypeError: unhashable type: 'Extension'

The dataclass conversion of the vendored distutils.extension.Extension (pypa/distutils#373) used the default eq=True, which generates __eq__ and therefore sets __hash__ = None. Beyond the TypeError, this also silently changed equality semantics: two distinct Extension instances with equal field values now compare equal, so ext in some_list can be true for an object that was never appended.

Change

Pass eq=False to lenient_dataclass in setuptools/_distutils/extension.py, restoring the identity-based equality and hashability Extension always had before the conversion. The lenient unknown-keyword behaviour is unchanged.

Note: the same conversion exists upstream in pypa/distutils, so the vendored copy will need this fix re-applied at the next sync unless it lands there first — happy to send that PR if preferred.

Validation

  • Reproduced on clean main (hash() → TypeError, Extension.__hash__ is None) with CPython 3.13; after the change: hashable, distinct instances unequal, usable as set members/dict keys.
  • Lenient behaviour preserved: unknown keyword arguments still emit the deprecation warning.
  • Added a regression test (test_extension_is_hashable); setuptools/_distutils/tests/test_extension.py: 4 passed, 2 skipped (optional mypy inference checks).
  • News fragment added (newsfragments/5303.bugfix.rst).

The dataclass conversion of distutils.extension.Extension used the
default eq=True, which generates __eq__ and therefore sets __hash__
to None. Extension instances became unhashable, breaking setup
scripts that collect extensions in sets or use them as dict keys
(e.g. pywin32 builds).

Pass eq=False to lenient_dataclass so Extension keeps the
identity-based equality and hashability it had before the
conversion.

Fixes pypa#5303
@abravalheri

Copy link
Copy Markdown
Contributor

Thank you for the contribution @aryansk .

Please have a look on https://setuptools.pypa.io/en/latest/development/developer-guide.html#code-conventions-and-other-practices regarding the _distutils folder.

There is already pypa/distutils#427 pending of review in the pypa/distutils repo.

@aryansk

aryansk commented Aug 23, 2026

Copy link
Copy Markdown
Author

Thank you for the pointer @abravalheri — you're right that _distutils changes belong upstream. I see pypa/distutils#427 already covers this exact regression (#426), so closing this draft as superseded. Thanks for the quick review.

@aryansk aryansk closed this Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] 84.0.0: Extension is unhashable (dataclass conversion sets __hash__ = None) — breaks setup scripts using sets of extensions

3 participants