Skip to content

Fix TypedKey equals/hashCode symmetry with Key#14025

Closed
gunjanjaswal wants to merge 2 commits into
PaperMC:mainfrom
gunjanjaswal:fix/13678-typedkey-equals-symmetry
Closed

Fix TypedKey equals/hashCode symmetry with Key#14025
gunjanjaswal wants to merge 2 commits into
PaperMC:mainfrom
gunjanjaswal:fix/13678-typedkey-equals-symmetry

Conversation

@gunjanjaswal

Copy link
Copy Markdown
Contributor

Summary

Fixes #13678.

TypedKeyImpl is a record implementing Key, but it never overrides equals/hashCode, so Java generates them from both record components and only matches another TypedKeyImpl. Adventure's KeyImpl.equals compares any Key by namespace + value, so equality is one-directional:

Key key = Key.key("minecraft:stone");
TypedKey<ItemType> typedKey = TypedKey.create(RegistryKey.ITEM, key);
key.equals(typedKey);   // true
typedKey.equals(key);   // false

That breaks the symmetry clause of Object.equals, and the two hash differently, so a TypedKey can go missing in a HashSet<Key> built from plain keys, and contains/remove turn order dependent when both types are mixed.

This overrides equals/hashCode on TypedKeyImpl:

  • Against another TypedKey, compare underlying key and registry key (same as before, so keys from different registries stay distinct).
  • Against a plain Key, compare namespace + value to match adventure and stay symmetric.
  • hashCode returns the underlying key's hash so equal keys share a hash.

One tradeoff: a TypedKey now equals its plain Key, so two TypedKeys with the same key but different registries stay unequal while both equal that plain key, which can break transitivity in that mixed case. That is inherent to TypedKey extends Key (Effective Java item 10). This follows the approach suggested in the issue; if you'd rather have strict transitivity, I can make equality key-only instead.

Changes

  • TypedKeyImpl: override equals/hashCode.
  • Add TypedKeyEqualityTest covering symmetry, hashCode consistency, and registry distinction.

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

I'm strongly against this, TypedKey should just not extends Key, Keyed maybe at best.

@github-project-automation github-project-automation Bot moved this from Awaiting review to Changes required in Paper PR Queue Jul 4, 2026
@lynxplay

lynxplay commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Agree with Lulu. It is a bit nasty, a typed key should be able to extend Key, it is identifying something via a namespace and value but the fact that a the plain key implementation considers any key with equal namespace and value equal means we should probably bite the bullet and make this keyed instead. A pretty meh equal impl of KeyImpl imo, idk if this is worth bringing up with adventure folks.

@gunjanjaswal

Copy link
Copy Markdown
Contributor Author

Makes sense, thanks both. Agree that having TypedKey extend Keyed rather than Key is the cleaner fix instead of patching equals. That's a breaking API change and a call for you all (and possibly the adventure folks), so I'll close this and leave #13678 for that direction. Appreciate the look.

@gunjanjaswal gunjanjaswal deleted the fix/13678-typedkey-equals-symmetry branch July 4, 2026 21:20
@github-project-automation github-project-automation Bot moved this from Changes required to Closed in Paper PR Queue Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Closed

Development

Successfully merging this pull request may close these issues.

Equivalence between KeyImpl and TypedKeyImpl is not symmetric

3 participants