[bugfix] NumericValue + BooleanValue: hashCode contract for op:same-key (PR #6333 family)#6336
Merged
Conversation
Closes the family of AtomicValue subclasses whose equals/hashCode contract is violated under op:same-key, the same Bifurcan-bucket hazard PR eXist-db#6333 closed on xs:duration: IntegerValue(1).equals(DecimalValue(1)) // true IntegerValue(1).hashCode() == 1 DecimalValue(1).hashCode() == 31 // contract broken IntegerValue(1).hashCode() == DoubleValue(1.0).hashCode() // 1 vs 1072693248 The numeric subclasses each inherited NumericValue.equals (cross-type op:numeric-equal) but overrode hashCode with type-local BigInteger / BigDecimal / Double / Float hashes. Spec-equal cross-type pairs landed in different Bifurcan buckets and map:contains / map:get silently mis-reported containment. BooleanValue had the same shape: an equals override on value, no hashCode override, so non-singleton instances hashed by identity. Fix mirrors PR eXist-db#6333: a canonical hash invariant across the equals-equivalence class. - NumericValue: single hashCode() using NaN / +-INF sentinels and otherwise ((DecimalValue) convertTo(DECIMAL)).getValue().hashCode() over the trailing-zero-stripped canonical decimal form. - IntegerValue / DecimalValue / DoubleValue / FloatValue: drop per-class hashCode overrides; inherit the canonical form. - BooleanValue: add hashCode = Boolean.hashCode(value). HashCodeContractTest (new, 14 assertions) covers the cross-type numeric pairs, NaN, +-INF, non-singleton BooleanValue, and a 100-iter determinism guard. maps.xqm adds 9 XQuery regression cases mirroring map-contains-017 across the numeric cluster, NaN, infinities, and non-singleton booleans. Two existing tests asserted specific map iteration orders. Per the XPath 3.1 spec map:keys and map:for-each are implementation-defined-order; the assertions were guarding Bifurcan bucket layout, which changes with hashCode. Updated: - mt:for-each in maps.xqm now sorts its result before assertion. - custom-assertion.xqm sorts its "Additional keys found" diagnostic and the ca:map-assertion-additional-key expectation is updated for the new bucket layout. Out of scope: DurationValue family (closed by PR eXist-db#6333). Date/time, binary, QName, and codepoint clusters already satisfy the contract (verified). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
reinhapa
approved these changes
May 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the family of AtomicValue subclasses that violate the
equals/hashCodecontract underop:same-keysemantics. PR #6333 fixedxs:duration; this sweep coversxs:integer/xs:decimal/xs:double/xs:floatandxs:boolean.Without these guards, Bifurcan-backed map operations (
map:contains,map:get, ...) bucket spec-equal cross-type keys differently and silently mis-report containment. The diagnostic signature is the same "1-in-N pass rate" symptom that producedmap-contains-017onxs:duration.Why
In
MapType.KEY_HASH_FN = AtomicValue::hashCode, Bifurcan uses each value'shashCodefor bucketing and only consultssameKeyfor equality within a bucket. If two spec-equal cross-type values hash to different buckets, the equality check is never reached.Confirmed contract violations on develop (b917e1a)
equalshashCodeIntegerValue(1)vsDecimalValue(1)IntegerValue(1)vsDoubleValue(1.0)DecimalValue(1.0)vsDoubleValue(1.0)DoubleValue(1.0)vsFloatValue(1.0f)DoubleValue(+INF)vsFloatValue(+INF)new BooleanValue(true)vsnew BooleanValue(true)What changed
NumericValue.javahashCode(): NaN / ±INF sentinels, else((DecimalValue) convertTo(DECIMAL)).getValue().hashCode()(trailing-zero-stripped at construction). Equals already uses op:numeric-equal, so the contract now holds.IntegerValue.java,DecimalValue.java,DoubleValue.java,FloatValue.javahashCode()overrides; inherit the canonical form fromNumericValue.BooleanValue.javahashCode()=Boolean.hashCode(value). Non-singleton instances previously hashed by identity.HashCodeContractTest.java(new)maps.xqmmap-contains-017(numeric cross-type, NaN, infinities, non-singleton booleans).mt:for-eachupdated to sort its result — the previous assertion relied on Bifurcan bucket iteration order, which has no spec guarantee.custom-assertion.xqmInventory of AtomicValue subclasses surveyed
IntegerValueDecimalValueDoubleValueFloatValueBooleanValueDurationValue(+OrderedDurationValue/YearMonthDurationValue/DayTimeDurationValue)StringValue,AnyURIValue,UntypedAtomicValueString.hashCode)AbstractDateTimeValue(+ all date/time/gXxx subclasses)calendarcalendar.hashCodeBinaryValue(+BinaryValueFromBinaryString,BinaryValueFromFile,BinaryValueFromInputStream,Base64BinaryDocument)getClass()+ contentequalsandfn:deep-equalreturns false tooQNameValueqnameqname.hashCodeFunctionReference,JavaObjectValueSpec references
Maphashing contractDurationValuemap-contains-017- canonical reproduction shapeTest plan
HashCodeContractTest(14/14 pass)MapTestsXQuery suite (134 pass, was failingmt:for-eachdue to bucket order)XQSuiteTests(90 pass aftercustom-assertion.xqmdeterminism fix)mvn test -pl exist-coreunder shared lock (6615 tests, no hashCode-related failures)project_xqts_runner_info_drift_warnings.md)Risk and rollback
Hash bucket layout for numeric and boolean keys changes. Map iteration order is implementation-defined per spec; tests that asserted specific orderings have been updated to sort. Each value-class change is independently revertable.