934 Commits
Author SHA1 Message Date
Shourya Singh cfa50b0093 [DOC] Fix outdated results in examples
Some example results in the docs no longer match what Ruby prints:

* Array#flatten and Array#flatten!: Set#inspect now shows Set[6, 7]
* Module: Mod.constants also includes Math::DomainError
* Module#const_get and Module#const_set: Floats are shown with full
  precision
* Complex#imag: sin(PI/4) is 0.7071067811865475
* String#<<: missing # in #<Encoding:UTF-8>
* Enumerable#inject: typo #=>>
2026-10-21 17:52:28 +09:00
Nozomi Hijikata 81c5830a66 ZJIT: Inline BasicObject#!= through a JIT hook (#19162)
Closes: https://github.com/Shopify/ruby/issues/884

Inline `BasicObject#!=` by using a ZJIT hook. This expands specialization coverage to additional cases beyond the existing String and Integer specializations.

Although this might add some overhead in the interpreter when ZJIT is enabled, as the `opt_neq` fast path requires the C definition, the benchmarks below show speedups in several cases (e.g. lobsters, railsbench). I ran the benchmarks several times. The speedups varied between runs, but I consistently observed improvements over the previous implementation.

```
zjit-master: ruby 4.1.0dev (2026-10-10T08:21:06Z master 05917fd1e6) +ZJIT stats +PRISM [arm64-darwin25]
zjit-basicobject-hook: ruby 4.1.0dev (2026-10-10T13:30:56Z zjit-basicobject-n.. a6f95cadaa) +ZJIT stats +PRISM [arm64-darwin25]
ruby-bench: 3cc371202311fe2c821a6adb5adfced364f029de

-------------  ----------------  --------------------------  -----------------------------  ---------------------------------
bench          zjit-master (ms)  zjit-basicobject-hook (ms)  zjit-basicobject-hook 1st itr  zjit-master/zjit-basicobject-hook
liquid-render      44.6 ± 10.2%                 43.9 ± 6.3%                          0.933                              1.014
lobsters           415.1 ± 6.7%                384.1 ± 4.7%                          1.070                              1.081
railsbench         592.1 ± 5.8%                545.8 ± 2.0%                          1.117                              1.085
-------------  ----------------  --------------------------  -----------------------------  ---------------------------------
```
---

<details>
<summary>Resolved: specialization with statically known receiver types</summary>

Note: Currently, the `==` call inside `BasicObject#!=` uses a profile shared across callers. When that profile is polymorphic, `==` can fall back to a dynamic send even when the receiver's exact type is known in the caller.
As a follow-up, we could improve specialization of `==` inside the inlined `!=` by using receiver types known at the call site, even when the shared profile for `==` is polymorphic.

```rb
2.times do
  "x" != nil
  1 != nil
end

NULL = Object.new
def test(value) = NULL != value

4.times { test(nil) }
```
```sh
#  ./miniruby --zjit-dump-hir --zjit-call-threshold=4 --zjit-num-profiles=4 ../test.rb
```
On master:
```
v30:ObjectExact[VALUE(...)] = Const Value(VALUE(...))
PatchPoint MethodRedefined(Object@..., !=@..., cme:...)
PatchPoint MethodRedefined(Object@..., ==@..., cme:...)
v51:CBool = IsBitNotEqual v30, v11
v52:BoolExact = BoxBool v51
```

On current branch:
```
v30:ObjectExact[VALUE(...)] = Const Value(VALUE(...))
PatchPoint NoSingletonClass(Object@...)
PatchPoint MethodRedefined(Object@..., !=@..., cme:...)
PushInlineFrame :!=, v30 (...), num_args=1
v68:BasicObject = Send v30, :==, v11 # SendFallbackReason: Send: polymorphic fallback
v71:CBool = Test v68
CondBranch v71, bb14(), bb8()

// Removing the initial 2.times block lets the current branch specialize the comparison to IsBitEqual.
```

</details>
2026-10-14 18:00:32 +00:00
John Hawthorn f6963df7eb Remove code checking for uninitialized class 2026-10-13 17:02:46 -07:00
John Hawthorn 0adee861cb Prevent uninitialized classes from being allocated 2026-10-13 17:02:46 -07:00
Nobuyoshi Nakada 9cffa03aef Substite with NIL_OR_UNDEF_P more 2026-10-11 22:40:45 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 ee3d6c987b Use rb_len_t for the lengths and indices left in long
These still held String and Array lengths and indices in long, or cast them to long. rb_len_t is still long everywhere, so the generated code does not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:45:25 +09:00
Hiroshi SHIBATAandClaude Opus 5 593b42ec70 Use rb_len_t for string lengths in numeric conversions
rb_big_pack() and rb_big_unpack() keep `long`, because num_longs counts the words of an `unsigned long` buffer rather than a String or Array length.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Peter Zhu 004f523bbd Fix assertion failure in allocating instance of uninitialized class
If a class is allocated and has a singleton class, it is incorrectly marked
as initialized which can cause an assertion error when an instance is
allocated.

For example, the following code causes an assertion error:

    c = Class.allocate
    c.singleton_class
    c.new

Crashes with:

    vm_method.c:1861: Assertion Failed: rb_get_alloc_func:allocator
    ruby 4.1.0dev (2026-10-07T14:31:40Z io-uninitialized-e.. 67f4ea55a3) +PRISM [arm64-darwin27]

    -- Crash Report log information --------------------------------------------
      See Crash Report log file in one of the following locations:
        * ~/Library/Logs/DiagnosticReports
        * /Library/Logs/DiagnosticReports
      for more details.
    Don't forget to include the above Crash Report log file in bug reports.

    -- Control frame information -----------------------------------------------
    c:0002 p:0017 s:0009 E:000780 l:n b:---- r:0x0 EVAL   test.rb:3 [FINISH]
    c:0001 p:0000 s:0003 E:000128 l:y b:---- r:0x0 DUMMY  [FINISH]

    -- Ruby level backtrace information ----------------------------------------
    test.rb:3:in '<main>'

    -- Threading information ---------------------------------------------------
    Total ractor count: 1
    Ruby thread count for this ractor: 1

    -- C level backtrace information -------------------------------------------
    miniruby(rb_assert_failure_detail+0xf0) [0x102b57024] error.c:1268
    miniruby(rb_assert_failure_detail) (null):0
    miniruby(rb_assert_failure_detail+0x0) [0x102b56f34] error.c:1244
    miniruby(rb_assert_failure) (null):0
    miniruby(rb_get_alloc_func.cold.1+0x28) [0x102b663e0] vm_method.c:1861
    miniruby(rb_get_alloc_func.cold.3) (null):0
    miniruby(rb_type+0x0) [0x1028af338] vm_method.c:1861
    miniruby(rb_get_alloc_func) (null):0
    miniruby(rb_class_alloc+0x48) [0x102756210] object.c:2285
    miniruby(vm_exec_core+0x469c) [0x1028a6114] insns.def:936
    miniruby(vm_exec_loop+0x0) [0x10289f25c] vm.c:2909
    miniruby(rb_vm_exec) vm.c:2912
    miniruby(rb_ec_exec_node+0xa0) [0x1026ac450] eval.c:300
    miniruby(ruby_run_node+0x64) [0x1026ac354] eval.c:338
    miniruby(rb_main+0x1c) [0x1025e09b0] main.c:42
    miniruby(main) main.c:62
2026-10-08 17:41:51 +09:00
Luke Gruber bec5e51c85 Fix Module/Class#dup of frozen object
This code should not raise an error:
```
@var = Class.new.freeze.dup # unfrozen copy
@var.instance_variable_set(:@test, 123)
```

It was raising `Ractor::IsolationError` because of a bug in `rb_fields_tbl_copy`.

This patch also fixes the following assertion failure in debug mode:

```
Module.new.freeze.singleton_class.instance_variable_set("@a", 1)
```

Now we get the proper behavior:

```
can't modify frozen Class: #<Class:#<Module:0x00000001094d9900>> (FrozenError)
```

Fixes [Bug #22378]
2026-09-25 14:53:06 -04:00
Nobuyoshi Nakada f74cedf78f Simplify rb_obj_freeze
The frozen check in `OBJ_FREEZE` makes the separate check redundant,
and immediate values are always frozen, so the old assertion is
unreachable.
2026-09-19 18:51:41 +09:00
Nobuyoshi Nakada a323b13af9 Skip already frozen objects in RB_OBJ_FREEZE
Avoid calling `rb_obj_freeze_inline` for already frozen objects,
including immediate values, to match `rb_obj_freeze` semantics.
2026-09-19 18:51:41 +09:00
Koichi SasadaandClaude Opus 5 f8485f7d26 Ractor: make freeze and set_temporary_name owner-only
Requested by matz on [Feature #22226]: a non-owner freezing somebody else's
class is a hole, because it lets a third party make a class permanently
unmodifiable.  Naming is the same kind of change to the class itself.

Module#freeze checks only when it would actually change something, so an
already frozen class stays a no-op for every Ractor and idempotent .freeze
calls keep working.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:02:03 +09:00
Koichi SasadaandClaude Fable 5 2e5bf429e4 Ractor: restrict class/module modification to the creator Ractor
Only the Ractor which created a class/module may modify it: define, remove or
undefine methods, alias, include/prepend, refine, define or remove constants,
register an autoload, and set the class's own instance variables.  Reading is
unchanged.  This replaces the assorted "main Ractor only" rules with one
creator-based rule, and in exchange a Ractor gets full use of the classes it
creates itself -- unshareable constant and instance variable values included,
which even the main Ractor could not do for its own classes before.

Three paths reach a class's tables without going through rb_class_modify_check,
so they get the check of their own: Module#refine (which writes its refinement
tables into the receiver as well as into the refined class), Class#initialize
(which writes the superclass of a not-yet-initialized class, and had no frozen
check either), and Module#ruby2_keywords.

The constant inline cache has to move with the rule.  Its hit condition was
"shareable value, or we are the main Ractor", which was sound only because a
non-main Ractor could not have an unshareable constant in the first place.  Now
it can, so an owner priming the cache would let any other Ractor read that value
straight out of it, never reaching the owner check in vm_get_ev_const.  The
entry records the Ractor which filled it instead -- the one that passed the
check -- which also gives a non-main owner a fast path it never had.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:02:03 +09:00
Jean Boussier 60a4894bdb Dynamic ar_table size
Instead of always allocating `ar_table` backed hashes in 160B
slots for them to have capacity of `RHASH_AR_TABLE_MAX_SIZE` (`8`),
they can now be allocated in any slot size between `64B` and `160B`.

`64B` is the minimum slot size that still allows transitioning to
an `st_table` if necessary.

The assumption is that there is a large number of literal hashes
and keyword argument hashes being built, and the overwhelming majority
of them aren't appended to.

When this assumption hold, this patch saves memory and reduce GC
pressure by allocating in a smaller slot than before.

However when the assumption doesn't hold, it has the oppositve effect
as the Hash will transition to an `st_table` when before it may have
stayed an `ar_table`.

Currently the main blocked for this change is that the compiler
has a tendency to generate code that make the final Hash size unknown.

For instance: `{a: 4, b: 4 + 1}` is compiled as:

```
0000 duphash                                {a: 4}                    (   1)[Li]
0002 putspecialobject                       1
0004 swap
0005 putobject                              :b
0007 putobject                              4
0009 putobject_INT2FIX_1_
0010 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0012 opt_send_without_block                 <calldata!mid:core#hash_merge_ptr, argc:3, ARGS_SIMPLE>
```

Here `duphash` has no information about how large of a hash it needs to allocate.
For this patch to be more efficient the compiler should generate something like:

```
0000 putobject                              {a: 4}                    (   1)[Li]
0002 putspecialobject                       1
0004 swap
0005 putobject                              :b
0007 putobject                              4
0009 putobject_INT2FIX_1_
0010 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0012 opt_send_without_block                 <calldata!mid:core#hash_merge_ptr, argc:3, ARGS_SIMPLE>
```

So that it's `hash_merge_ptr` that is responsible for allocating the final hash,
given it does have a much better clue of how large it would need to be.

Demo:

```ruby
require 'objspace'
def size(hash)
  puts "size: #{hash.size}\tmemsize: #{ObjectSpace.memsize_of(hash)}"
end

size({})
size(a:1)
size(a:1, b:2)
size(a:1, b:2, c:3)
size(a:1, b:2, c:3, d:4)
size(a:1, b:2, c:3, d:4, e:5)
size(a:1, b:2, c:3, d:4, e:5, f:6)
size(a:1, b:2, c:3, d:4, e:5, f:6, g:7)
size(a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8)
size(a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9)
```

Before:

```
size: 0	memsize: 160
size: 1	memsize: 160
size: 2	memsize: 160
size: 3	memsize: 160
size: 4	memsize: 160
size: 5	memsize: 160
size: 6	memsize: 160
size: 7	memsize: 160
size: 8	memsize: 160
size: 9	memsize: 464
```

After:

```
size: 0	memsize: 64
size: 1	memsize: 64
size: 2	memsize: 64
size: 3	memsize: 80
size: 4	memsize: 96
size: 5	memsize: 128
size: 6	memsize: 128
size: 7	memsize: 160
size: 8	memsize: 160
size: 9	memsize: 448
```
2026-08-25 15:24:55 +02:00
Jean Boussier 56abc30ec2 Righ-size more hashes
The pattern of allocating an empty hash with `rb_hash_new()` and
then appending to it is relatively wasteful, because every time
we go over the hash capacity, we'll have to `realloc`, rehash etc.

It may even get worse if https://github.com/ruby/ruby/pull/18345
lands, as right now an empty hash starts with a capacity of 8,
but it may very well start with a capacity of 2.

Hence in cases where the final hash size is easy to predict, we
should use `rb_hash_new_capa()`.
2026-08-23 11:50:40 +02:00
Shugo MaedaandClaude Fable 5 9658933451 [Feature #9779] Add Module#descendants
Module#descendants returns an array of classes and modules that have
the receiver in their ancestors; it is the inverse of Module#ancestors.
The receiver itself, singleton classes, and refinements are not included.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 23:05:57 +09:00
Jean Boussier 86951165b8 Get rid of ROBJECT_HEAP flag
This flag is now entirely redundant with the shape_id layout bits.
2026-07-15 15:43:28 +02:00
Jean Boussier 24337e3cbe Refactor rb_obj_copy_ivar
Share more code with `rb_copy_generic_ivar`.
2026-07-14 23:50:17 +02:00
Jean Boussier 6f0548a0bd Simplify rb_obj_copy_ivar and rb_copy_generic_ivar
By handling complex shapes in `rb_shape_rebuild` we can merge
the two complex cases (when `src` is complex and when `dest`
becomes complex).
2026-07-14 23:50:17 +02:00
Jean Boussier 71ee81f7d9 imemo.c: embed the st_table inside rb_fields
Followup: https://github.com/ruby/ruby/pull/17631

Now that complex `RObject` always have an IMEMO/fields, we no
longer need to have an external `st_table` to replicate the
memory layout of `RObject`.
2026-07-14 23:50:17 +02:00
e0083cffa3 Refactor RObject to use T_IMEMO/fields_obj on overflow
Instead of spilling into a raw buffer, `RObject` now spills
into an `IMEMO/fields` object like other types.

From an instance variable layout standpoint, an extended `RObject`
is now identical to a `RTypedData`, as in they both store the
reference to their `IMEMO/fields` at the same offset (`VALUE * 2`).

One positive consequence of this is that the only case where a `T_OBJECT`
needs sweeping is if a finalizer was registered.

YJIT now side exit when it need to write a new ivar into a `RObject`
that is out of space. This is unlikely to cause a performance regression
as this codepath isn't supposed to happen after warmup given `max_iv_count`
is recorded on classes, so future objects should be large enough.
Hence it's best not to waste executable memory for such codepath.

ZJIT lost support for writting into extended `RObject`.
Most of the code to support it is there it's just missing an
implementation of `RBASIC_SET_SHAPE_ID`, which recently changed
to strip some bits out of the shape (see comments).
I will leave it to the ZJIT team to implement it (sorry).

There are a few future cleanups planned that I keep for followups:

  - We can now get rid of the `ROBJECT_HEAP` flag, it's redundant with
    the shape layout bits.
  - `imemo_fields` can now embed the `st_table` when complex.

Co-Authored-By: John Hawthorn <john@hawthorn.email>
Co-authored-by: Randy Stauner <randy@r4s6.net>
2026-07-14 10:44:39 +02:00
Jean Boussier b416f906cf Remove String#to_f and Kernel#Float out of range warning
[Feature #22085]

This warning is only actionable for float literals in source code.
When parsing user input into a float, most of the time this warning
isn't actionable and is pure noise.
2026-07-10 08:33:52 +02:00
Luke Gruber 98a60a9e81 Inline fastpath for rb_check_convert_type_with_id
This is an often-called internal function that has a fastpath, so the fastpath
should be inlined.

Benchmark:

```ruby
require 'benchmark/ips'

ARR = [1, 2, 3]

Benchmark.ips(quiet: true) do |x|
  x.config(time: 2, warmup: 1)

  # Control: C method, does not route through rb_check_convert_type_with_id. Detects drift.
  x.report("control itself") do |t|
    a = ARR
    i = 0; while i < t; a.itself; i += 1; end
  end

  # Target: Array(arr) -> rb_Array -> rb_check_array_type -> inlined RB_TYPE_P fast path
  x.report("Array(arr)") do |t|
    a = ARR
    i = 0; while i < t; Array(a); i += 1; end
  end
end
```

When run 10 times, we see a 4% average improvement.
2026-07-07 09:54:42 -04:00
Koichi SasadaandClaude Opus 4.8 45633fa7b6 object.c: initialize clone freeze-kwarg caches atomically
rb_get_freeze_opt and rb_obj_clone_setup lazily initialized shared static
caches (the freeze keyword id and the `{freeze: true/false}` hashes) with a
plain `if (!cache)` guard. When Ractors run #clone concurrently this races:
a thread can publish a half-built hash that another freezes before it is
filled, later triggering "can't modify frozen Hash".

Use the preinterned idFreeze directly, and publish each frozen, pinned hash
with a single atomic CAS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:08:42 +09:00
Burdette Lamar b3b2492947 [DOC] Doc for frozen objects 2026-06-09 15:11:05 +09:00
Earlopain 4f6b9d80fb [DOC] Fix code sample formatting for TrueClass#& 2026-06-02 13:21:01 +09:00
Peter Zhu bfba263724 [DOC] Fix hash style in Hash method 2026-05-13 21:07:58 -04:00
Jean Boussier 4eeec614bb shapes: Rename TOO_COMPLEX in just COMPLEX
The `too_` prefix wasn't consistently used and just make the
thing longer for no benefit.
2026-05-06 10:10:16 +02:00
Jean Boussier beffa58b65 shape: Get rid of the "raw_id" terminology in favor of "offset"
It better describe that part of a `shape_id`.
2026-05-03 23:40:36 +02:00
Jean Boussier c9b53473e3 Refactor shape transition functions
Expose both `rb_obj_shape_` functions that take a `VALUE`
and `rb_shape_` functions that take a `shape_id`.

Make common transition functions such as `complex` and `frozen`
inlineable.

Also get rid of RB_SET_SHAPE_ID and rb_set_boxed_class_shape_id.
2026-05-03 11:36:26 +02:00
Jean Boussier 45d586ad60 Never increment max_iv_count on Object / BasicObject.
Otherwise some code defining ivars on a naked object early during
program boot can cause all objects to be larger than needed.

This seem like it was always the intention, but wasn't quite
properly prevented.

Also stop updating `max_iv_count` during GC marking as it is
redundant.
2026-05-02 08:46:36 +02:00
John Hawthorn f2d0ef269b Add and use rb_ivar_foreach_buffered
Previously, rb_ivar_foreach would walk up the shape tree, but yield
instance variables to the callback as it went. If the object shape was
modified during this callback, particularly with removing an instance
variable, it could result in reading free'd memory.

This commit solves this by adding a version which buffering all instance
variable names and values before calling the callback, giving a snapshot
of the object at the time rb_ivar_foreach_buffered is called.

The buffer is made with ALLOCV_N, so the performance difference should
be minimal, and I don't think this method is particuarly heavily used.

[Bug #21996]
2026-04-26 17:14:39 +09:00
John Hawthorn a2a69b420a Add assertion to rb_class_alloc
We should only ever call rb_class_alloc with a T_CLASS. Adding this
assert/assumption allows us to elide later checks for T_CLASS>
2026-04-06 15:36:54 -07:00
John Hawthorn 3e851ea77d Avoid box st_lookup in rb_class_real 2026-04-06 15:33:42 -07:00
John Hawthorn 2e753bf278 Move rb_class_allocate_instance into gc.c 2026-03-27 10:09:30 -07:00
John Hawthorn 851b8f8523 Remove class alloc check
This checks that the value returned from the function registered with
rb_define_alloc_func is of the correct class. When this was first
introduced in 1fe40b7cc5 (by Matz on 2001-10-03), allocation was done
via user-defined Object#allocate, so it made sense to have a runtime
check in release builds. Now that it's defined via rb_define_alloc_func
in the C extension API, I don't think it's necessary.

The check is surprisingly expensive. Removing it makes Object.new about
10% faster. It allows the C compiler to optimize the call to the
function pointer as a tail-call. Removing this also allows ZJIT/YJIT to
call the function directly (ZJIT already does this by having a list of
known safe allocation functions).

There's no way for users to ever have seen this check, other than by
writing a misbehaving C extension, which returns objects with the wrong
class.

[Feature #21966]
2026-03-27 09:00:25 -07:00
John Hawthorn 111215de27 Copy allocator to subclasses at boot and on change
Copy the allocator from superclass to subclass at class creation time,
so rb_get_alloc_func no longer needs to walk the ancestor chain.

This expands on 68ffc8db08, which did this
copy, but only for classes defined in Ruby (ex. `class Foo`). And allows
us to simple read the allocator field for any class to get the correct
value.

To keep this correct when rb_define_alloc_func is called after
subclasses already exist, propagate the new allocator down to all
subclasses. An RCLASS_ALLOCATOR_DEFINED flag distinguishes classes with
an explicitly set allocator from those that inherited one, so
propagation stops at subclasses that define their own.
2026-03-25 12:51:46 -07:00
Jean BoussierandBenoit Daloze d066b9e01c Refactor type error to be more consistent
[Bug #21864]

Co-Authored-By:  Benoit Daloze <eregontp@gmail.com>
2026-02-07 10:06:36 +01:00
Burdette Lamar 48d2c7fd62 [DOC] Harmonize certain Module methods (#15804) 2026-02-04 17:08:08 -05:00
Burdette Lamar 3ab35e5e2e [DOC] Doc for Module.new 2026-02-01 21:56:21 -05:00
Peter Zhu 491e38902c [DOC] Fix links to What's Here
The RDoc link format has changed so these are all broken links.
2026-01-26 17:18:19 -05:00
Peter Zhu 0d4538b57d [DOC] Improve docs for Module#>= 2026-01-10 09:31:35 -05:00
Peter Zhu f0f4a683b4 [DOC] Improve docs for Module#<= 2026-01-10 09:31:35 -05:00
Peter Zhu 0b83346f1c [DOC] Improve docs for Module#< 2026-01-10 09:31:35 -05:00
Peter Zhu 98c414a678 [DOC] Improve docs for Module#> 2026-01-10 09:31:35 -05:00
Burdette Lamar 3d242a82a3 [DOC] Harmonize #> methods 2026-01-09 17:36:26 -05:00
Burdette Lamar 58fb95af36 [DOC] Harmonize #== methods (#15805) 2026-01-06 17:13:15 -05:00
BurdetteLamar dd67874ad9 [DOC] Harmonize #=== methods 2026-01-05 18:30:40 -05:00
Burdette Lamar 2d69facd20 [DOC] Tweaks for Object#<=> 2025-12-21 14:05:38 -05:00
BurdetteLamar 7c1e37cfe1 [DOC] Tweaks for Module#<=> 2025-12-20 18:12:56 -05:00