From 8baaece6d9a64e8cc51d8d0c9839546e3d039eee Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 11 Jan 2026 22:46:06 +0900 Subject: [PATCH 1/4] [ruby/prism] [Bug #21831] Fix denominator of rational float literal Denominators can contain underscores in fraction part as well as other numeric literals. [Bug #21831]: https://bugs.ruby-lang.org/issues/21831 https://github.com/ruby/prism/commit/e247cb58c7 --- prism/prism.c | 8 ++++++-- test/prism/result/numeric_value_test.rb | 11 +++++++++++ test/ruby/test_literal.rb | 5 +++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 1a8cdf7568fe98..b36a6da20493ba 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -3962,9 +3962,13 @@ pm_float_node_rational_create(pm_parser_t *parser, const pm_token_t *token) { memcpy(digits + (point - start), point + 1, (unsigned long) (end - point - 1)); pm_integer_parse(&node->numerator, PM_INTEGER_BASE_DEFAULT, digits, digits + length - 1); + size_t fract_length = 0; + for (const uint8_t *fract = point; fract < end; ++fract) { + if (*fract != '_') ++fract_length; + } digits[0] = '1'; - if (end - point > 1) memset(digits + 1, '0', (size_t) (end - point - 1)); - pm_integer_parse(&node->denominator, PM_INTEGER_BASE_DEFAULT, digits, digits + (end - point)); + if (fract_length > 1) memset(digits + 1, '0', fract_length - 1); + pm_integer_parse(&node->denominator, PM_INTEGER_BASE_DEFAULT, digits, digits + fract_length); xfree(digits); pm_integers_reduce(&node->numerator, &node->denominator); diff --git a/test/prism/result/numeric_value_test.rb b/test/prism/result/numeric_value_test.rb index 5c89230a1fee41..0207fa6a864917 100644 --- a/test/prism/result/numeric_value_test.rb +++ b/test/prism/result/numeric_value_test.rb @@ -6,16 +6,27 @@ module Prism class NumericValueTest < TestCase def test_numeric_value assert_equal 123, Prism.parse_statement("123").value + assert_equal 123, Prism.parse_statement("1_23").value assert_equal 3.14, Prism.parse_statement("3.14").value + assert_equal 3.14, Prism.parse_statement("3.1_4").value assert_equal 42i, Prism.parse_statement("42i").value + assert_equal 42i, Prism.parse_statement("4_2i").value assert_equal 42.1ri, Prism.parse_statement("42.1ri").value + assert_equal 42.1ri, Prism.parse_statement("42.1_0ri").value assert_equal 3.14i, Prism.parse_statement("3.14i").value + assert_equal 3.14i, Prism.parse_statement("3.1_4i").value assert_equal 42r, Prism.parse_statement("42r").value + assert_equal 42r, Prism.parse_statement("4_2r").value assert_equal 0.5r, Prism.parse_statement("0.5r").value + assert_equal 0.5r, Prism.parse_statement("0.5_0r").value assert_equal 42ri, Prism.parse_statement("42ri").value + assert_equal 42ri, Prism.parse_statement("4_2ri").value assert_equal 0.5ri, Prism.parse_statement("0.5ri").value + assert_equal 0.5ri, Prism.parse_statement("0.5_0ri").value assert_equal 0xFFr, Prism.parse_statement("0xFFr").value + assert_equal 0xFFr, Prism.parse_statement("0xF_Fr").value assert_equal 0xFFri, Prism.parse_statement("0xFFri").value + assert_equal 0xFFri, Prism.parse_statement("0xF_Fri").value end end end diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb index dbff3c4734275d..cff888d4b3a5d4 100644 --- a/test/ruby/test_literal.rb +++ b/test/ruby/test_literal.rb @@ -682,6 +682,11 @@ def test_float $VERBOSE = verbose_bak end + def test_rational_float + assert_equal(12, 0.12r * 100) + assert_equal(12, 0.1_2r * 100) + end + def test_symbol_list assert_equal([:foo, :bar], %i[foo bar]) assert_equal([:"\"foo"], %i["foo]) From cf5c5abe1e7057532d1c945372b68d55cfaf163c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 6 Jan 2026 17:24:45 -0500 Subject: [PATCH 2/4] [DOC] Improve docs for ObjectSpace.count_tdata_objects --- ext/objspace/objspace.c | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index ba755b1ad60f71..1c265c722ab51c 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -365,32 +365,22 @@ cto_i(VALUE v, void *data) /* * call-seq: - * ObjectSpace.count_tdata_objects([result_hash]) -> hash + * ObjectSpace.count_tdata_objects(result_hash = nil) -> hash * - * Counts objects for each +T_DATA+ type. + * Returns a hash containing the number of objects for each +T_DATA+ type. + * The keys are Class objects when the +T_DATA+ object has an associated class, + * or Symbol objects of the name defined in the +rb_data_type_struct+ for internal + * +T_DATA+ objects. * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. - * - * It returns a hash as: - * - * {RubyVM::InstructionSequence=>504, :parser=>5, :barrier=>6, - * :mutex=>6, Proc=>60, RubyVM::Env=>57, Mutex=>1, Encoding=>99, - * ThreadGroup=>1, Binding=>1, Thread=>1, RubyVM=>1, :iseq=>1, - * Random=>1, ARGF.class=>1, Data=>1, :autoload=>3, Time=>2} - * # T_DATA objects existing at startup on r32276. - * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. - * - * The contents of the returned hash is implementation specific and may change - * in the future. + * ObjectSpace.count_tdata_objects + * # => {RBS::Location => 39255, marshal_compat_table: 1, Encoding => 103, mutex: 1, ... } * - * In this version, keys are Class object or Symbol object. + * If the optional argument +result_hash+ is given, it is overwritten and + * returned. This is intended to avoid the probe effect. * - * If object is kind of normal (accessible) object, the key is Class object. - * If object is not a kind of normal (internal) object, the key is symbol - * name, registered by rb_data_type_struct. + * This method is intended for developers interested in performance and memory + * usage of Ruby programs. The contents of the returned hash is implementation + * specific and may change in the future. * * This method is only expected to work with C Ruby. */ From 1267a3ab565523f5181cd33321897e47944e5771 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 6 Jan 2026 19:06:13 -0500 Subject: [PATCH 3/4] [DOC] Improve docs for ObjectSpace.count_imemo_objects --- ext/objspace/objspace.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 1c265c722ab51c..0f55c33a5f9b88 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -419,28 +419,22 @@ count_imemo_objects_i(VALUE v, void *data) /* * call-seq: - * ObjectSpace.count_imemo_objects([result_hash]) -> hash + * ObjectSpace.count_imemo_objects(result_hash = nil) -> hash * - * Counts objects for each +T_IMEMO+ type. + * Returns a hash containing the number of objects for each +T_IMEMO+ type. + * The keys are Symbol objects of the +T_IMEMO+ type name. + * +T_IMEMO+ objects are Ruby internal objects that are not visible to Ruby + * programs. * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. - * - * It returns a hash as: + * ObjectSpace.count_imemo_objects + * # => {imemo_callcache: 5482, imemo_constcache: 1258, imemo_ment: 13906, ... } * - * {:imemo_ifunc=>8, - * :imemo_svar=>7, - * :imemo_cref=>509, - * :imemo_memo=>1, - * :imemo_throw_data=>1} - * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. - * - * The contents of the returned hash is implementation specific and may change - * in the future. + * If the optional argument +result_hash+ is given, it is overwritten and + * returned. This is intended to avoid the probe effect. * - * In this version, keys are symbol objects. + * This method is intended for developers interested in performance and memory + * usage of Ruby programs. The contents of the returned hash is implementation + * specific and may change in the future. * * This method is only expected to work with C Ruby. */ From 61c372a1b7fe045adc9b67196503f29b79bff376 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 7 Jan 2026 18:10:10 -0500 Subject: [PATCH 4/4] [DOC] Improve docs for ObjectSpace.count_symbols --- ext/objspace/objspace.c | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 0f55c33a5f9b88..b12f5cb7c8c80e 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -295,28 +295,27 @@ size_t rb_sym_immortal_count(void); /* * call-seq: - * ObjectSpace.count_symbols([result_hash]) -> hash + * ObjectSpace.count_symbols(result_hash = nil) -> hash * - * Counts symbols for each Symbol type. + * Returns a hash containing the number of objects for each Symbol type. * - * This method is only for MRI developers interested in performance and memory - * usage of Ruby programs. + * The types of Symbols are the following: * - * If the optional argument, result_hash, is given, it is overwritten and - * returned. This is intended to avoid probe effect. + * - +mortal_dynamic_symbol+: Symbols that are garbage collectable. + * - +immortal_dynamic_symbol+: Symbols that are objects allocated from the + * garbage collector, but are not garbage collectable. + * - +immortal_static_symbol+: Symbols that are not allocated from the + * garbage collector, and are thus not garbage collectable. + * - +immortal_symbol+: the sum of +immortal_dynamic_symbol+ and +immortal_static_symbol+. * - * 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. + * If the optional argument +result_hash+ is given, it is overwritten and + * returned. This is intended to avoid the probe effect. * - * On this version of MRI, they have 3 types of Symbols (and 1 total counts). + * This method is intended for developers interested in performance and memory + * usage of Ruby programs. The contents of the returned hash is implementation + * specific and may change in the future. * - * * mortal_dynamic_symbol: GC target symbols (collected by GC) - * * immortal_dynamic_symbol: Immortal symbols promoted from dynamic symbols (do not collected by GC) - * * immortal_static_symbol: Immortal symbols (do not collected by GC) - * * immortal_symbol: total immortal symbols (immortal_dynamic_symbol+immortal_static_symbol) + * This method is only expected to work with C Ruby. */ static VALUE