Skip to content

Abstract RubyHash#9262

Merged
headius merged 3 commits intojruby:masterfrom
headius:abstract_hash
Feb 25, 2026
Merged

Abstract RubyHash#9262
headius merged 3 commits intojruby:masterfrom
headius:abstract_hash

Conversation

@headius
Copy link
Copy Markdown
Member

@headius headius commented Feb 25, 2026

This PR is a first pass at making RubyHash abstract to allow new subclass implementations for specific uses. The original implementation, based on pre-direct-addressing CRuby logic, now lives in RubyHashLinkedBuckets.

The impact to consumers of the public RubyHash API should be minimal, except for the now non-functional constructors.

The goal here is to allow RubyHash API-compatible subclasses that either wrap other Hash-like data structures or which implement specific variations like an associative array or a direct- addressing hash. We may also use this to implement thread-safe Hash variants.

At least one external library has already run afoul of the inoperable constructors: jruby-openssl

diff --git a/src/main/java/org/jruby/ext/openssl/X509Name.java b/src/main/java/org/jruby/ext/openssl/X509Name.java
index 58b6a4e4..9bab2b16 100644
--- a/src/main/java/org/jruby/ext/openssl/X509Name.java
+++ b/src/main/java/org/jruby/ext/openssl/X509Name.java
@@ -123,7 +123,8 @@ public class X509Name extends RubyObject {
         final RubyFixnum IA5_STRING = runtime.newFixnum(BERTags.IA5_STRING);
 
         final ThreadContext context = runtime.getCurrentContext();
-        final RubyHash hash = new RubyHash(runtime, UTF8_STRING);
+        final RubyHash hash = RubyHash.newHash(runtime);
+        hash.default_value_set(UTF8_STRING);
         hash.op_aset(context, newString(runtime, new byte[] { 'C' }), PRINTABLE_STRING);
         final byte[] countryName = { 'c','o','u','n','t','r','y','N','a','m','e' };
         hash.op_aset(context, newString(runtime, countryName), PRINTABLE_STRING);

@headius headius added this to the JRuby 10.1.0.0 milestone Feb 25, 2026
@headius
Copy link
Copy Markdown
Member Author

headius commented Feb 25, 2026

Given the immediate problem of jruby-openssl calling one of the RubyHash constructors, I have devised a way to allow it to still be constructed.

  • RubyHash aggregates a single Object state field that can be used in place or by subclasses.
  • For RubyHash constructed directly, the state field will hold a delegated RubyHashLinkedBuckets and all formerly abstract methods will call through that delegate.
  • For other subclasses like RubyHashLinkedBuckets, the state field will hold some piece of implementation state and be used normally by that implementation's methods.

This allows constructing RubyHash directly (via deprecated constructors) while still allowing all of our internal uses to construct one of the available subclasses.

After some time (perhaps 10.2) and all users of the RubyHash constructors have been fixed, the state field should be moved down into each implementation and the RubyHash class and delegated methods returned to abstract.

@headius headius force-pushed the abstract_hash branch 6 times, most recently from dd79404 to 8c27a3c Compare February 25, 2026 11:42
This commit moves RubyHash to RubyHashLinkedBuckets to provide a
better history during abstractification.
@headius headius force-pushed the abstract_hash branch 2 times, most recently from 2dd6841 to 2165c5a Compare February 25, 2026 19:23
This patch shifts RubyHash to a pseudo-abstract form to allow new
subclass implementations for specific uses. The original
implementation, based on pre-direct-addressing CRuby logic, now
lives in RubyHashLinkedBuckets.

In order to preserve the ability to construct RubyHash directly,
all methods intended to be abstract instead delegate to a wrapped
instance of RubyHashLinkedBuckets, only used this way when
constructed directly through deprecated constructor paths. All
internal constructions of Hash use the blessed
RubyHashLinkedBuckets constructors and do not use the delegated
logic.

All RubyHash construction should in the future proceed through
factory methods. The factory-first approach ensures that:

* The formerly public constructors are all now deprecated and used
  only by legacy code.
* All constructors in RubyHashLinkedBuckets pass through a new non-
  delegating constructor in RubyHash.
* All unprotected constructors in RubyHashLinkedBuckets are only
  reachable through factory methods.

This avoids us exposing new constructors that would prevent us
from easily swapping the implementation in the future.

The `state` field is used to either hold the delegated hash or to
hold the actual state for the real implementation. This allows all
proper uses of the RubyHashLinkedBuckets to avoid excessive object
size. Other implementations can wrap this field in the same way
until we can move it down into those implementations.

This implementation passes all tests that do not specifically
depend on Hash instances being RubyHash objects.
Hash is now primarily implemented by RubyHashLinkedBuckets and may
have other implementations in the future. Tests that expect to see
RubyHash as the object's class must be modified to expect the
correct class or some wildcarded version of it.
@headius headius merged commit 121a0b1 into jruby:master Feb 25, 2026
2 of 79 checks passed
@headius headius deleted the abstract_hash branch February 25, 2026 19:47
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.

1 participant