Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong Module#method_added description (continuation of #579) #696

Open
AlexWayfer opened this issue Mar 6, 2021 · 4 comments
Open

Wrong Module#method_added description (continuation of #579) #696

AlexWayfer opened this issue Mar 6, 2021 · 4 comments

Comments

@AlexWayfer
Copy link
Contributor

Excuse me, there is a Module#method_added method now, but with very strange description:

https://rubyapi.org/2.7/o/module#method-i-method_added

image

While Ruby Doc has appropriate one, even with examples:

https://ruby-doc.org/core-2.7.2/Module.html#method-i-method_added

image

@colby-swandale
Copy link
Member

colby-swandale commented Apr 7, 2021

TIL method_added is an alias but it has its own description. At the moment we assume aliased methods don't have a description and just print the "an alias for X" instead.

We will need to update the page to allow for displaying both.

@natematykiewicz
Copy link
Contributor

@colby-swandale I think it's an RDoc bug or something. It's not an alias. The fact that Module#included points to a dummy method is probably part of the problem: https://rubyapi.org/3.0/o/module#method-i-included

It thinks the definition of included is:
https://github.com/ruby/ruby/blob/v3_0_0/object.c#L1146-L1150

From what I can tell, this is the real method_added definition:
https://github.com/ruby/ruby/blob/58660e943488778563b9e41005a601e9660ce21f/vm_method.c#L889-L895

I'm no C expert. But included is supposed to run when a module is included into a class/module, where method_added is supposed to run when a method is added to an object.

@natematykiewicz
Copy link
Contributor

Or is the problem that they're hooks and don't actually do anything? I guess that's why it's a dummy method.

Is there a better C code definition for these or are they both just this dummy method?

Where does it say they're aliases? Or does it consider multiple methods using the rb_object_dummy1 hook to be aliases?

@natematykiewicz
Copy link
Contributor

I really think this is an RDoc bug. I added some debugging code to lib/rubyapi_rdoc_generator.rb.

doc.method_list.each do |method_doc|
  method = {
    name: method_doc.name,
    description: clean_description(doc.full_name, method_doc.description),
    object_constant: doc.full_name,
    method_type: "#{method_doc.type}_method",
    source_location: "#{@release.version}:#{method_path(method_doc)}:#{method_doc.line}",
    alias: {
      path: clean_path(method_doc.is_alias_for&.path, constant: doc.full_name),
      name: method_doc.is_alias_for&.name
    },
    call_sequence: call_sequence_for_method_doc(method_doc),
    source_body: format_method_source_body(method_doc),
    metadata: {
      depth: constant_depth(doc.full_name)
    }
  }

  # ------------------
  if doc.full_name == 'Module' && %w[included method_added method_removed method_undefined].include?(method_doc.name)
    puts method.inspect
    puts "\n\n\n"
  end
  # ------------------

  next if methods.any? { |m| m[:name] == method[:name] && m[:method_type] == method[:method_type] }

  methods << method
end

That output this:

{:name=>"included", :description=>"\n<p>Callback invoked whenever the receiver is included in another module or class. This should be used in preference to <code>Module.append_features</code> if your code wants to perform some action when a module is included in another.</p>\n\n<di
v class=\"ruby\" data-controller=\"code-example\" data-target=\"code-example.block\" data-code-example-version=\"3.0\"></div><pre class=\"ruby\"><span class=\"ruby-keyword\">module</span> <span class=\"ruby-constant\">A</span>\n  <span class=\"ruby-keyword\">def</span> <span class=\"ruby-constant\">A</span>.<span cla
ss=\"ruby-identifier ruby-title\">included</span>(<span class=\"ruby-identifier\">mod</span>)\n    <span class=\"ruby-identifier\">puts</span> <span class=\"ruby-node\">&quot;\#{self} included in \#{mod}&quot;</span>\n  <span class=\"ruby-keyword\">end</span>\n<span class=\"ruby-keyword\">end</span>\n<span class=\"ru
by-keyword\">module</span> <span class=\"ruby-constant\">Enumerable</span>\n  <span class=\"ruby-identifier\">include</span> <span class=\"ruby-constant\">A</span>\n<span class=\"ruby-keyword\">end</span>\n <span class=\"ruby-comment\"># =&gt; prints &quot;A included in Enumerable&quot;</span>\n</pre>\n", :object_con
stant=>"Module", :method_type=>"instance_method", :source_location=>"3.0.0:object.c:1146", :alias=>{:path=>nil, :name=>nil}, :call_sequence=>["included(othermod)"], :source_body=>"<div class=\"line\"><span class=\"k\">static</span> <span class=\"n\">VALUE</span>\n</div><div class=\"line\"><span class=\"nf\">rb_obj_du
mmy1</span><span class=\"p\">(</span><span class=\"n\">VALUE</span> <span class=\"n\">_x</span><span class=\"p\">,</span> <span class=\"n\">VALUE</span> <span class=\"n\">_y</span><span class=\"p\">)</span>\n</div><div class=\"line\"><span class=\"p\">{</span>\n</div><div class=\"line\">    <span class=\"k\">return</
span> <span class=\"n\">rb_obj_dummy</span><span class=\"p\">();</span>\n</div><div class=\"line\"><span class=\"p\">}</span>\n</div>", :metadata=>{:depth=>1}}



{:name=>"method_added", :description=>"\n<p>Callback invoked whenever the receiver is included in another module or class. This should be used in preference to <code>Module.append_features</code> if your code wants to perform some action when a module is included in another.</p>\n\n<div class=\"ruby\" data-controller
=\"code-example\" data-target=\"code-example.block\" data-code-example-version=\"3.0\"></div><pre class=\"ruby\"><span class=\"ruby-keyword\">module</span> <span class=\"ruby-constant\">A</span>\n  <span class=\"ruby-keyword\">def</span> <span class=\"ruby-constant\">A</span>.<span class=\"ruby-identifier ruby-title\
">included</span>(<span class=\"ruby-identifier\">mod</span>)\n    <span class=\"ruby-identifier\">puts</span> <span class=\"ruby-node\">&quot;\#{self} included in \#{mod}&quot;</span>\n  <span class=\"ruby-keyword\">end</span>\n<span class=\"ruby-keyword\">end</span>\n<span class=\"ruby-keyword\">module</span> <span class=\"ruby-identifier\">include</span> <span class=\"ruby-constant\">A</span>\n<span class=\"ruby-keyword\">end</span>\n <span class=\"ruby-comment\"># =&gt; prints &quot;A included in Enumerable&quot;</span>\n</pre>\n", :object_constant=>"Module", :method_type=>"instance_method", :source_location
=>"3.0.0:object.c:", :alias=>{:path=>"/3.0/o/module#method-i-included", :name=>"included"}, :call_sequence=>["method_added(p1)"], :source_body=>"", :metadata=>{:depth=>1}}



{:name=>"method_removed", :description=>"\n<p>Callback invoked whenever the receiver is included in another module or class. This should be used in preference to <code>Module.append_features</code> if your code wants to perform some action when a module is included in another.</p>\n\n<div class=\"ruby\" data-controll
er=\"code-example\" data-target=\"code-example.block\" data-code-example-version=\"3.0\"></div><pre class=\"ruby\"><span class=\"ruby-keyword\">module</span> <span class=\"ruby-constant\">A</span>\n  <span class=\"ruby-keyword\">def</span> <span class=\"ruby-constant\">A</span>.<span class=\"ruby-identifier ruby-titl
e\">included</span>(<span class=\"ruby-identifier\">mod</span>)\n    <span class=\"ruby-identifier\">puts</span> <span class=\"ruby-node\">&quot;\#{self} included in \#{mod}&quot;</span>\n  <span class=\"ruby-keyword\">end</span>\n<span class=\"ruby-keyword\">end</span>\n<span class=\"ruby-keyword\">module</span> <sp
an class=\"ruby-constant\">Enumerable</span>\n  <span class=\"ruby-identifier\">include</span> <span class=\"ruby-constant\">A</span>\n<span class=\"ruby-keyword\">end</span>\n <span class=\"ruby-comment\"># =&gt; prints &quot;A included in Enumerable&quot;</span>\n</pre>\n", :object_constant=>"Module", :method_type=
>"instance_method", :source_location=>"3.0.0:object.c:", :alias=>{:path=>"/3.0/o/module#method-i-included", :name=>"included"}, :call_sequence=>["method_removed(p1)"], :source_body=>"", :metadata=>{:depth=>1}}



{:name=>"method_undefined", :description=>"\n<p>Callback invoked whenever the receiver is included in another module or class. This should be used in preference to <code>Module.append_features</code> if your code wants to perform some action when a module is included in another.</p>\n\n<div class=\"ruby\" data-contro
ller=\"code-example\" data-target=\"code-example.block\" data-code-example-version=\"3.0\"></div><pre class=\"ruby\"><span class=\"ruby-keyword\">module</span> <span class=\"ruby-constant\">A</span>\n  <span class=\"ruby-keyword\">def</span> <span class=\"ruby-constant\">A</span>.<span class=\"ruby-identifier ruby-ti
tle\">included</span>(<span class=\"ruby-identifier\">mod</span>)\n    <span class=\"ruby-identifier\">puts</span> <span class=\"ruby-node\">&quot;\#{self} included in \#{mod}&quot;</span>\n  <span class=\"ruby-keyword\">end</span>\n<span class=\"ruby-keyword\">end</span>\n<span class=\"ruby-keyword\">module</span> <
span class=\"ruby-constant\">Enumerable</span>\n  <span class=\"ruby-identifier\">include</span> <span class=\"ruby-constant\">A</span>\n<span class=\"ruby-keyword\">end</span>\n <span class=\"ruby-comment\"># =&gt; prints &quot;A included in Enumerable&quot;</span>\n</pre>\n", :object_constant=>"Module", :method_typ
e=>"instance_method", :source_location=>"3.0.0:object.c:", :alias=>{:path=>"/3.0/o/module#method-i-included", :name=>"included"}, :call_sequence=>["method_undefined(p1)"], :source_body=>"", :metadata=>{:depth=>1}}

The C code shows different descriptions for all of them:
https://github.com/ruby/ruby/blob/v3_0_0/object.c#L907-L981

All of the method hooks are defined here:
https://github.com/ruby/ruby/blob/v3_0_0/object.c#L4525-L4531

inherited, included, extended, prepended, method_added, method_removed, method_undefined are all callback methods. They're basically just defined as:

def self.inherited(mod)
  return(nil)
end

This method is always called by Ruby. If you override the method, it'll run your code instead.

Rather than defining empty methods for all of these, Ruby has decided to define a single "dummy" method (rb_obj_dummy1) with the correct argument signature that does nothing and returns nil. They defined all 7 of these callback methods a simply being rb_obj_dummy1. RDoc assumes that this means that inherited is the real method and the rest are aliases (because that's how all real aliases are defined). The fact that they all share rb_obj_dummy1 is just an optimization on Ruby's part. The methods are not actually related.

It seems to be losing the fact that they all have their own descriptions and just using included's. What's interesting is that it does maintain the call sequence. I think that the alias detection code in RDoc needs some work.

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

No branches or pull requests

3 participants