From 5dfaabb71d1ab801998b83d2a5ecee9a7947e83d Mon Sep 17 00:00:00 2001 From: tompng Date: Mon, 31 Aug 2026 00:05:24 +0900 Subject: [PATCH 1/3] Rename #initialize to ::new before registering it to the container The rename of #initialize to ::new happened after container.add_method, with a comment claiming this is "to register duplicated 'new' and 'initialize' defined in c and ruby". The real reason is older: the Ripper-based streaming parser read documentation modifiers (:notnew:) after the method line, so whether to rename was simply unknown at add_method time ("Having now read the method parameters and documentation modifiers, we now know whether we have to rename #initialize to ::new"). The Prism parser processes directives and modifier lines before add_method, so that constraint is gone and the post-add mutation pattern has no remaining reason to exist. The old placement also had a real cost: the method was registered under the '#initialize' key and renamed afterwards, which left Context#methods_hash keyed by a stale name and bypassed duplicate detection. A class documenting both ::new (an explicit `def self.new`, or a C-defined new) and #initialize ended up with two ::new entries on its page. With the rename moved before add_method, the normal deduplication applies: the first registration wins and the duplicate is reported by the existing "Duplicate method" warning (visible with --verbose). record_location is also moved before add_method so that the warning can name the file the duplicate came from. Corpus diff (per-class method lists, before vs after): - ruby/ruby: 4 classes lose a duplicated ::new entry (Gem::Package::TarReader, Gem::Package::TarWriter, Gem::Resolver::APISpecification, and JSON::Ext::Generator::State -- the last one is the C new + Ruby initialize case) - activesupport: 1 class (ActiveSupport::Deprecation::DeprecatedConstantProxy) - rdoc itself: no change All other entries are identical. Co-Authored-By: Claude Fable 5 --- lib/rdoc/parser/ruby.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb index 6e943e5d6d..a4b44a0549 100644 --- a/lib/rdoc/parser/ruby.rb +++ b/lib/rdoc/parser/ruby.rb @@ -746,15 +746,9 @@ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility: meth.calls_super = calls_super meth.block_params ||= block_params if block_params meth.type_signature_lines = type_signature_lines - container.add_method(meth) - record_location(meth) - meth.start_collecting_tokens(:ruby) - tokens.each do |token| - meth.token_stream << token - end - # Rename after add_method to register duplicated 'new' and 'initialize' - # defined in c and ruby. + # An instance method `initialize` is documented as `::new` unless the + # :notnew: directive is given if !dont_rename_initialize && method_name == 'initialize' && !singleton if meth.dont_rename_initialize meth.visibility = :protected @@ -764,6 +758,13 @@ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility: meth.visibility = :public end end + + record_location(meth) + container.add_method(meth) + meth.start_collecting_tokens(:ruby) + tokens.each do |token| + meth.token_stream << token + end end # Find or create module or class from a given module name using Ruby lexical From a3ef80e5442b38251b283a446289b2bd95d90230 Mon Sep 17 00:00:00 2001 From: tompng Date: Mon, 31 Aug 2026 00:22:43 +0900 Subject: [PATCH 2/3] Remove dead dont_rename_initialize keyword argument The dont_rename_initialize keyword argument of internal_add_method has never been passed a truthy value since it was introduced in b92986a5: one call site passes an explicit false and the other relies on the false default. It is unrelated to AnyMethod#dont_rename_initialize (set by the :notnew: directive), which remains the live mechanism, and removing the constant-false parameter also removes the confusion of two same-named flags in one method. Co-Authored-By: Claude Fable 5 --- lib/rdoc/parser/ruby.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb index a4b44a0549..bb90776b68 100644 --- a/lib/rdoc/parser/ruby.rb +++ b/lib/rdoc/parser/ruby.rb @@ -451,7 +451,6 @@ def handle_meta_method_comment(comment, directives, node) @container, comment: comment, directives: directives, - dont_rename_initialize: false, line_no: line_no, visibility: visibility, singleton: @singleton || singleton_method, @@ -722,7 +721,7 @@ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility: ) end - private def internal_add_method(method_name, container, comment:, dont_rename_initialize: false, directives:, modifier_comment_lines: nil, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, type_signature_lines: nil) # :nodoc: + private def internal_add_method(method_name, container, comment:, directives:, modifier_comment_lines: nil, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, type_signature_lines: nil) # :nodoc: meth = RDoc::AnyMethod.new(method_name, singleton: singleton) meth.comment = comment handle_code_object_directives(meth, directives) if directives @@ -749,7 +748,7 @@ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility: # An instance method `initialize` is documented as `::new` unless the # :notnew: directive is given - if !dont_rename_initialize && method_name == 'initialize' && !singleton + if method_name == 'initialize' && !singleton if meth.dont_rename_initialize meth.visibility = :protected else From b2e43fa102898927a8f31174d9b91539704a2892 Mon Sep 17 00:00:00 2001 From: tompng Date: Wed, 2 Sep 2026 00:07:12 +0900 Subject: [PATCH 3/3] Add test for a class defining both ::new and #initialize Asserts that only one ::new is registered regardless of definition order, that the first definition's comment is kept, and that Context#methods_hash is not left with a stale '#initialize' key. Co-Authored-By: Claude Fable 5 --- test/rdoc/parser/ruby_test.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/rdoc/parser/ruby_test.rb b/test/rdoc/parser/ruby_test.rb index 3ebfd5891b..40cc6f4893 100644 --- a/test/rdoc/parser/ruby_test.rb +++ b/test/rdoc/parser/ruby_test.rb @@ -700,6 +700,31 @@ def initialize(*args) assert_equal expected, arglists end +def test_class_new_and_initialize_are_registered_once + util_parser <<~RUBY + class A + # new doc + def self.new(x); super; end + # initialize doc + def initialize(x); end + end + + class B + # initialize doc + def initialize(x); end + # new doc + def self.new(x); super; end + end + RUBY + + a, b = @top_level.classes + assert_equal ['A::new'], a.method_list.map(&:full_name) + assert_equal 'new doc', a.method_list.first.comment.text + assert_equal ['::new'], a.methods_hash.keys + assert_equal ['B::new'], b.method_list.map(&:full_name) + assert_equal 'initialize doc', b.method_list.first.comment.text +end + def test_class_mistaken_for_module util_parser <<~RUBY class A::Foo; end