Skip to content

fix(python): make union case classes hashable again#4805

Merged
dbrattli merged 1 commit into
fable-compiler:mainfrom
caroott:fix/python-union-hash
Jul 17, 2026
Merged

fix(python): make union case classes hashable again#4805
dbrattli merged 1 commit into
fable-compiler:mainfrom
caroott:fix/python-union-hash

Conversation

@caroott

@caroott caroott commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Fixes #4804

Hashing a union value that contains another union as a field raises TypeError: unhashable type at runtime, e.g.

type Inner = | A
type Outer = | Wrap of Inner

(Wrap A).GetHashCode() // TypeError: unhashable type: 'Inner_A'

The same code works on .NET and JS, and breaks things like List.distinct or using unions as dictionary keys on Python.

The tagged_union decorator applies dataclass() to every case class. Since that generates __eq__ without frozen=True, Python sets __hash__ = None on the class, shadowing the __hash__ the Union base inherits from HashableBase. So case classes were explicitly unhashable, and Union.GetHashCode (hash((self.tag, *self.fields))) blows up as soon as a field is itself a union. The decorator's docstring already claims __hash__ is generated. This restores it after the dataclass() call. It's consistent with the dataclass __eq__: equal values have equal (tag, *fields), hence equal hashes.

Added tests for the hash protocol, hash/eq consistency, nested unions and set membership.

One note: type checkers still consider the case classes unhashable statically (dataclass_transform models them as non-frozen dataclasses), even though __hash__ now exists at runtime.

@dbrattli dbrattli 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.

Thanks for fixing!

@dbrattli
dbrattli merged commit 63b8013 into fable-compiler:main Jul 17, 2026
32 checks passed
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.

[Python] Union values are unhashable — dataclass() sets __hash__ = None on case classes

2 participants