From f2845edc9d8ca6e66ef100b4d57564f87f3b5aaa Mon Sep 17 00:00:00 2001 From: Gunjan Jaswal Date: Fri, 3 Jul 2026 03:34:13 +0530 Subject: [PATCH 1/2] Fix TypedKeyImpl equals/hashCode symmetry with Key (fixes #13678) --- .../papermc/paper/registry/TypedKeyImpl.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/paper-api/src/main/java/io/papermc/paper/registry/TypedKeyImpl.java b/paper-api/src/main/java/io/papermc/paper/registry/TypedKeyImpl.java index a4063783c318..9a4fea73cf4e 100644 --- a/paper-api/src/main/java/io/papermc/paper/registry/TypedKeyImpl.java +++ b/paper-api/src/main/java/io/papermc/paper/registry/TypedKeyImpl.java @@ -7,6 +7,27 @@ @NullMarked record TypedKeyImpl(Key key, RegistryKey registryKey) implements TypedKey { // Wrap key methods to make this easier to use + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (other instanceof final TypedKey otherTyped) { + return this.key.equals(otherTyped.key()) && this.registryKey.equals(otherTyped.registryKey()); + } + // A TypedKey is a Key, so it must compare equal to a plain Key with the same + // namespace and value (adventure's Key equality) to keep equals symmetric. + return other instanceof final Key otherKey + && this.key.namespace().equals(otherKey.namespace()) + && this.key.value().equals(otherKey.value()); + } + + @Override + public int hashCode() { + // Match KeyImpl#hashCode so a TypedKey and an equal plain Key share a hash code. + return this.key.hashCode(); + } + @KeyPattern.Namespace @Override public String namespace() { From e778334fe6b1bcc12134af68dc1e6542b3f58753 Mon Sep 17 00:00:00 2001 From: Gunjan Jaswal Date: Fri, 3 Jul 2026 03:34:26 +0530 Subject: [PATCH 2/2] Add symmetry/hashCode tests for TypedKey vs Key (#13678) --- .../paper/registry/TypedKeyEqualityTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 paper-api/src/test/java/io/papermc/paper/registry/TypedKeyEqualityTest.java diff --git a/paper-api/src/test/java/io/papermc/paper/registry/TypedKeyEqualityTest.java b/paper-api/src/test/java/io/papermc/paper/registry/TypedKeyEqualityTest.java new file mode 100644 index 000000000000..921eb050f89b --- /dev/null +++ b/paper-api/src/test/java/io/papermc/paper/registry/TypedKeyEqualityTest.java @@ -0,0 +1,36 @@ +package io.papermc.paper.registry; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import net.kyori.adventure.key.Key; +import org.bukkit.inventory.ItemType; +import org.junit.jupiter.api.Test; + +class TypedKeyEqualityTest { + + @Test + void typedKeyIsSymmetricWithPlainKey() { + final Key key = Key.key("minecraft:stone"); + final TypedKey typedKey = TypedKey.create(RegistryKey.ITEM, key); + + // equals must be symmetric across the Key hierarchy + assertTrue(key.equals(typedKey), "plain Key should equal an equal TypedKey"); + assertTrue(typedKey.equals(key), "TypedKey should equal an equal plain Key"); + assertEquals(key.equals(typedKey), typedKey.equals(key), "equals must be symmetric"); + + // equal keys must share a hash code + assertEquals(key.hashCode(), typedKey.hashCode()); + } + + @Test + void typedKeysDifferByRegistry() { + final Key key = Key.key("minecraft:stone"); + final TypedKey itemKey = TypedKey.create(RegistryKey.ITEM, key); + final TypedKey blockKey = TypedKey.create(RegistryKey.BLOCK, key); + + // same underlying key but different registries are not equal + assertNotEquals(itemKey, blockKey); + } +}