diff --git a/compar.c b/compar.c
index 0fb7e5f6584e73..142cb12a0cfe77 100644
--- a/compar.c
+++ b/compar.c
@@ -95,10 +95,13 @@ cmpint(VALUE x, VALUE y)
/*
* call-seq:
- * obj > other -> true or false
+ * self > other -> true or false
*
- * Compares two objects based on the receiver's <=>
- * method, returning true if it returns a value greater than 0.
+ * Returns whether +self+ is "greater than" +other+;
+ * equivalent to (self <=> other) > 0:
+ *
+ * 'foo' > 'foo' # => false
+ * 'food' > 'foo' # => true
*/
static VALUE
diff --git a/enumerator.c b/enumerator.c
index 292ddb0419fb8a..b60036a3dedc17 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -34,79 +34,93 @@
/*
* Document-class: Enumerator
*
- * A class which allows both internal and external iteration.
+ * \Class \Enumerator supports:
*
- * An Enumerator can be created by the following methods.
- * - Object#to_enum
- * - Object#enum_for
- * - Enumerator.new
+ * - {External iteration}[rdoc-ref:Enumerator@External+Iteration].
+ * - {Internal iteration}[rdoc-ref:Enumerator@Internal+Iteration].
*
- * Most methods have two forms: a block form where the contents
- * are evaluated for each item in the enumeration, and a non-block form
- * which returns a new Enumerator wrapping the iteration.
+ * An \Enumerator may be created by the following methods:
*
- * enumerator = %w(one two three).each
- * puts enumerator.class # => Enumerator
+ * - Object#to_enum.
+ * - Object#enum_for.
+ * - Enumerator.new.
*
- * enumerator.each_with_object("foo") do |item, obj|
- * puts "#{obj}: #{item}"
- * end
+ * In addition, certain Ruby methods return \Enumerator objects:
+ * a Ruby iterator method that accepts a block
+ * may return an \Enumerator if no block is given.
+ * There are many such methods, for example, in classes Array and Hash.
+ * (In the documentation for those classes, search for `new_enumerator`.)
*
- * # foo: one
- * # foo: two
- * # foo: three
+ * == Internal Iteration
*
- * enum_with_obj = enumerator.each_with_object("foo")
- * puts enum_with_obj.class # => Enumerator
+ * In _internal iteration_, an iterator method drives the iteration
+ * and the caller's block handles the processing;
+ * this example uses method #each_with_index:
*
- * enum_with_obj.each do |item, obj|
- * puts "#{obj}: #{item}"
- * end
+ * words = %w[foo bar baz] # => ["foo", "bar", "baz"]
+ * enumerator = words.each # => #
+ * enumerator.each_with_index {|word, i| puts "#{i}: #{word}" }
+ * 0: foo
+ * 1: bar
+ * 2: baz
*
- * # foo: one
- * # foo: two
- * # foo: three
+ * Iterator methods in class \Enumerator include:
*
- * This allows you to chain Enumerators together. For example, you
- * can map a list's elements to strings containing the index
- * and the element as a string via:
+ * - #each:
+ * passes each item to the block.
+ * - #each_with_index:
+ * passes each item and its index to the block.
+ * - #each_with_object (aliased as #with_object):
+ * passes each item and a given object to the block.
+ * - #with_index:
+ * like #each_with_index, but starting at a given offset (instead of zero).
*
- * puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
- * # => ["0:foo", "1:bar", "2:baz"]
+ * \Class \Enumerator includes module Enumerable,
+ * which provides many more iterator methods.
*
* == External Iteration
*
- * An Enumerator can also be used as an external iterator.
- * For example, Enumerator#next returns the next value of the iterator
- * or raises StopIteration if the Enumerator is at the end.
- *
- * e = [1,2,3].each # returns an enumerator object.
- * puts e.next # => 1
- * puts e.next # => 2
- * puts e.next # => 3
- * puts e.next # raises StopIteration
- *
- * +next+, +next_values+, +peek+, and +peek_values+ are the only methods
- * which use external iteration (and Array#zip(Enumerable-not-Array) which uses +next+ internally).
- *
- * These methods do not affect other internal enumeration methods,
- * unless the underlying iteration method itself has side-effect, e.g. IO#each_line.
- *
- * FrozenError will be raised if these methods are called against a frozen enumerator.
- * Since +rewind+ and +feed+ also change state for external iteration,
- * these methods may raise FrozenError too.
- *
- * External iteration differs *significantly* from internal iteration
- * due to using a Fiber:
- * - The Fiber adds some overhead compared to internal enumeration.
- * - The stacktrace will only include the stack from the Enumerator, not above.
- * - Fiber-local variables are *not* inherited inside the Enumerator Fiber,
- * which instead starts with no Fiber-local variables.
- * - Fiber storage variables *are* inherited and are designed
- * to handle Enumerator Fibers. Assigning to a Fiber storage variable
- * only affects the current Fiber, so if you want to change state
- * in the caller Fiber of the Enumerator Fiber, you need to use an
- * extra indirection (e.g., use some object in the Fiber storage
+ * In _external iteration_, the user's program both drives the iteration
+ * and handles the processing in stream-like fashion;
+ * this example uses method #next:
+ *
+ * words = %w[foo bar baz]
+ * enumerator = words.each
+ * enumerator.next # => "foo"
+ * enumerator.next # => "bar"
+ * enumerator.next # => "baz"
+ * enumerator.next # Raises StopIteration: iteration reached an end
+ *
+ * External iteration methods in class \Enumerator include:
+ *
+ * - #feed:
+ * sets the value that is next to be returned.
+ * - #next:
+ * returns the next value and increments the position.
+ * - #next_values:
+ * returns the next value in a 1-element array and increments the position.
+ * - #peek:
+ * returns the next value but does not increment the position.
+ * - #peek_values:
+ * returns the next value in a 1-element array but does not increment the position.
+ * - #rewind:
+ * sets the position to zero.
+ *
+ * Each of these methods raises FrozenError if called from a frozen \Enumerator.
+ *
+ * == External Iteration and \Fiber
+ *
+ * External iteration that uses Fiber differs *significantly* from internal iteration:
+ *
+ * - Using \Fiber adds some overhead compared to internal enumeration.
+ * - The stacktrace will only include the stack from the \Enumerator, not above.
+ * - \Fiber-local variables are *not* inherited inside the \Enumerator \Fiber,
+ * which instead starts with no \Fiber-local variables.
+ * - \Fiber storage variables *are* inherited and are designed
+ * to handle \Enumerator Fibers. Assigning to a \Fiber storage variable
+ * only affects the current \Fiber, so if you want to change state
+ * in the caller \Fiber of the \Enumerator \Fiber, you need to use an
+ * extra indirection (e.g., use some object in the \Fiber storage
* variable and mutate some ivar of it).
*
* Concretely:
@@ -126,7 +140,7 @@
* e.each { p _1 }
* p Fiber[:storage_var] # => 2 (it ran in the same Fiber/"stack" as the current Fiber)
*
- * == Convert External Iteration to Internal Iteration
+ * == Converting External Iteration to Internal Iteration
*
* You can use an external iterator to implement an internal iterator as follows:
*
@@ -445,28 +459,31 @@ convert_to_feasible_size_value(VALUE obj)
/*
* call-seq:
- * Enumerator.new(size = nil) { |yielder| ... }
+ * Enumerator.new(size = nil) {|yielder| ... }
*
- * Creates a new Enumerator object, which can be used as an
- * Enumerable.
+ * Returns a new \Enumerator object that can be used for iteration.
*
- * Iteration is defined by the given block, in
- * which a "yielder" object, given as block parameter, can be used to
- * yield a value by calling the +yield+ method (aliased as <<):
+ * The given block defines the iteration;
+ * it is called with a "yielder" object that can yield an object
+ * via a call to method yielder.yield:
*
- * fib = Enumerator.new do |y|
- * a = b = 1
- * loop do
- * y << a
- * a, b = b, a + b
+ * fib = Enumerator.new do |yielder|
+ * n = next_n = 1
+ * while true do
+ * yielder.yield(n)
+ * n, next_n = next_n, n + next_n
* end
* end
*
* fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
*
- * The optional parameter can be used to specify how to calculate the size
- * in a lazy fashion (see Enumerator#size). It can either be a value or
- * a callable object.
+ * Parameter +size+ specifies how the size is to be calculated (see #size);
+ * it can either be a value or a callable object:
+ *
+ * Enumerator.new{}.size # => nil
+ * Enumerator.new(42){}.size # => 42
+ * Enumerator.new(-> {42}){}.size # => 42
+ *
*/
static VALUE
enumerator_initialize(int argc, VALUE *argv, VALUE obj)
diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c
index 8b3045fda322c4..ba755b1ad60f71 100644
--- a/ext/objspace/objspace.c
+++ b/ext/objspace/objspace.c
@@ -336,35 +336,6 @@ count_symbols(int argc, VALUE *argv, VALUE os)
return hash;
}
-/*
- * call-seq:
- * ObjectSpace.count_nodes([result_hash]) -> hash
- *
- * Counts nodes for each node type.
- *
- * This method is only for MRI developers interested in performance and memory
- * usage of Ruby programs.
- *
- * It returns a hash as:
- *
- * {:NODE_METHOD=>2027, :NODE_FBODY=>1927, :NODE_CFUNC=>1798, ...}
- *
- * If the optional argument, result_hash, is given, it is overwritten and
- * returned. This is intended to avoid probe effect.
- *
- * Note:
- * The contents of the returned hash is implementation defined.
- * It may be changed in future.
- *
- * This method is only expected to work with C Ruby.
- */
-
-static VALUE
-count_nodes(int argc, VALUE *argv, VALUE os)
-{
- return setup_hash(argc, argv);
-}
-
static void
cto_i(VALUE v, void *data)
{
@@ -834,7 +805,6 @@ Init_objspace(void)
rb_define_module_function(rb_mObjSpace, "count_objects_size", count_objects_size, -1);
rb_define_module_function(rb_mObjSpace, "count_symbols", count_symbols, -1);
- rb_define_module_function(rb_mObjSpace, "count_nodes", count_nodes, -1);
rb_define_module_function(rb_mObjSpace, "count_tdata_objects", count_tdata_objects, -1);
rb_define_module_function(rb_mObjSpace, "count_imemo_objects", count_imemo_objects, -1);
diff --git a/hash.c b/hash.c
index 28b729516474a7..c1cc13383b701b 100644
--- a/hash.c
+++ b/hash.c
@@ -4971,10 +4971,9 @@ rb_hash_ge(VALUE hash, VALUE other)
/*
* call-seq:
- * self > other_hash -> true or false
+ * self > other -> true or false
*
- * Returns +true+ if the entries of +self+ are a proper superset of the entries of +other_hash+,
- * +false+ otherwise:
+ * Returns whether the entries of +self+ are a proper superset of the entries of +other+:
*
* h = {foo: 0, bar: 1, baz: 2}
* h > {foo: 0, bar: 1} # => true # Proper superset.
diff --git a/numeric.c b/numeric.c
index ef44febe1b29cd..e8df2a6aa0568c 100644
--- a/numeric.c
+++ b/numeric.c
@@ -1638,7 +1638,8 @@ rb_float_cmp(VALUE x, VALUE y)
* call-seq:
* self > other -> true or false
*
- * Returns +true+ if +self+ is numerically greater than +other+:
+ * Returns whether the value of +self+ is greater than the value of +other+;
+ * +other+ must be numeric, but may not be Complex:
*
* 2.0 > 1 # => true
* 2.0 > 1.0 # => true
@@ -4958,7 +4959,8 @@ fix_gt(VALUE x, VALUE y)
* call-seq:
* self > other -> true or false
*
- * Returns +true+ if the value of +self+ is greater than that of +other+:
+ * Returns whether the value of +self+ is greater than the value of +other+;
+ * +other+ must be numeric, but may not be Complex:
*
* 1 > 0 # => true
* 1 > 1 # => false
diff --git a/object.c b/object.c
index 2b3535158fe305..7dda2b6c0d49cd 100644
--- a/object.c
+++ b/object.c
@@ -2009,14 +2009,23 @@ rb_mod_ge(VALUE mod, VALUE arg)
/*
* call-seq:
- * mod > other -> true, false, or nil
+ * self > other -> true, false, or nil
*
- * Returns true if mod is an ancestor of other. Returns
- * false if mod is the same as other
- * or mod is a descendant of other.
- * Returns nil if there's no relationship between the two.
- * (Think of the relationship in terms of the class definition:
- * "class A < B" implies "B > A".)
+ * If +self+ is a class, returns +true+ if +self+ is a superclass of +other+,
+ * returns +false+ if +self+ is the same as +other+ or if +self+ is a subclass
+ * of +other+, and returns +nil+ if there are no relationship between the two:
+ *
+ * Numeric > Float # => true
+ * Float > Numeric # => false
+ * Float > Float # => false
+ * Float > Hash # => nil
+ *
+ * If +self+ is a module, returns +true+ if +other+ includes +self+,
+ * returns +false+ if +self+ is the same as +other+ or if +self+ includes
+ * +other+, and returns +nil+ if there are no relationship between the two:
+ *
+ * Enumerable > Array # => true
+ * Enumerable > String # => nil
*
*/
diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb
index 2c0c6195e6ba46..78947a095bf44c 100644
--- a/test/objspace/test_objspace.rb
+++ b/test/objspace/test_objspace.rb
@@ -76,16 +76,6 @@ def test_count_objects_size_with_wrong_type
assert_raise(TypeError) { ObjectSpace.count_objects_size(0) }
end
- def test_count_nodes
- res = ObjectSpace.count_nodes
- assert_not_empty(res)
- arg = {}
- ObjectSpace.count_nodes(arg)
- assert_not_empty(arg)
- bug8014 = '[ruby-core:53130] [Bug #8014]'
- assert_empty(arg.select {|k, v| !(Symbol === k && Integer === v)}, bug8014)
- end if false
-
def test_count_tdata_objects
res = ObjectSpace.count_tdata_objects
assert_not_empty(res)
diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb
index 6488661c2d3a6c..53b056d3359c85 100644
--- a/test/ruby/test_yjit.rb
+++ b/test/ruby/test_yjit.rb
@@ -657,6 +657,26 @@ def foo(obj)
RUBY
end
+ def test_struct_aset_guards_recv_is_not_frozen
+ assert_compiles(<<~RUBY, result: :ok, exits: { opt_send_without_block: 1 })
+ def foo(obj)
+ obj.foo = 123
+ end
+
+ Foo = Struct.new(:foo)
+ obj = Foo.new(123)
+ 100.times do
+ foo(obj)
+ end
+ obj.freeze
+ begin
+ foo(obj)
+ rescue FrozenError
+ :ok
+ end
+ RUBY
+ end
+
def test_getblockparam
assert_compiles(<<~'RUBY', insns: [:getblockparam])
def foo &blk
diff --git a/tool/rbs_skip_tests b/tool/rbs_skip_tests
index 6d9ecb40992e26..4bcb5707a51d9d 100644
--- a/tool/rbs_skip_tests
+++ b/tool/rbs_skip_tests
@@ -37,6 +37,9 @@ TestInstanceNetHTTPResponse depending on external resources
test_TOPDIR(RbConfigSingletonTest) `TOPDIR` is `nil` during CI while RBS type is declared as `String`
+# Failing because ObjectSpace.count_nodes has been removed
+test_count_nodes(ObjectSpaceTest)
+
## Unknown failures
# NoMethodError: undefined method 'inspect' for an instance of RBS::UnitTest::Convertibles::ToInt
diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs
index 0fc39322fb13b6..1cf8247a2a905a 100644
--- a/yjit/src/codegen.rs
+++ b/yjit/src/codegen.rs
@@ -8973,6 +8973,17 @@ fn gen_struct_aset(
assert!(unsafe { RB_TYPE_P(comptime_recv, RUBY_T_STRUCT) });
assert!((off as i64) < unsafe { RSTRUCT_LEN(comptime_recv) });
+ // Even if the comptime recv was not frozen, future recv may be. So we need to emit a guard
+ // that the recv is not frozen.
+ // We know all structs are heap objects, so we can check the flag directly.
+ let recv = asm.stack_opnd(1);
+ let recv = asm.load(recv);
+ let flags = asm.load(Opnd::mem(VALUE_BITS, recv, RUBY_OFFSET_RBASIC_FLAGS));
+ asm.test(flags, (RUBY_FL_FREEZE as u64).into());
+ asm.jnz(Target::side_exit(Counter::opt_aset_frozen));
+
+ // Not frozen, so we can proceed.
+
asm_comment!(asm, "struct aset");
let val = asm.stack_pop(1);
diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs
index 84549fa5d34963..105def2fff8577 100644
--- a/yjit/src/stats.rs
+++ b/yjit/src/stats.rs
@@ -489,6 +489,7 @@ make_counters! {
opt_aset_not_array,
opt_aset_not_fixnum,
opt_aset_not_hash,
+ opt_aset_frozen,
opt_case_dispatch_megamorphic,