Forum: Ruby-core Feature Proposal: Method#super_method

F1d6cc2b735bfd82c8773172da2aeab9?d=identicon&s=25 Nobuyoshi Nakada (nobu)
on 2014-04-29 03:59
(Received via mailing list)
Issue #9781 has been updated by Nobuyoshi Nakada.

Description updated

A patch.
No tests yet.

~~~diff
diff --git a/proc.c b/proc.c
index 8153cc9..d1db478 100644
--- a/proc.c
+++ b/proc.c
@@ -1481,11 +1481,17 @@ method_owner(VALUE obj)
     return defined_class;
 }

-void
-rb_method_name_error(VALUE klass, VALUE str)
+struct method_name_error {
+    VALUE class_name;
+    const char *type;
+};
+
+static struct method_name_error
+prepare_method_name_error(VALUE klass)
 {
     const char *s0 = " class";
     VALUE c = klass;
+    struct method_name_error e;

     if (FL_TEST(c, FL_SINGLETON)) {
   VALUE obj = rb_ivar_get(klass, attached);
@@ -1500,8 +1506,22 @@ rb_method_name_error(VALUE klass, VALUE str)
     else if (RB_TYPE_P(c, T_MODULE)) {
   s0 = " module";
     }
-    rb_name_error_str(str, "undefined method `%"PRIsVALUE"' for%s
`%"PRIsVALUE"'",
-          QUOTE(str), s0, rb_class_name(c));
+    e.class_name = rb_class_name(c);
+    e.type = s0;
+    return e;
+}
+
+#define method_name_error(klass, str, t) do { \
+  struct method_name_error e = prepare_method_name_error(klass); \
+  rb_name_error_str(str, t" method `%"PRIsVALUE"' for%s
`%"PRIsVALUE"'", \
+        QUOTE(str), e.type, e.class_name); \
+    } while (0)
+
+
+void
+rb_method_name_error(VALUE klass, VALUE str)
+{
+    method_name_error(klass, str, "undefined");
 }

 /*
@@ -2430,6 +2450,23 @@ method_proc(VALUE method)
     return procval;
 }

+static VALUE
+method_super_method(VALUE method)
+{
+    struct METHOD *data;
+    VALUE defined_class, super_class;
+
+    TypedData_Get_Struct(method, struct METHOD, &method_data_type,
data);
+    defined_class = data->defined_class;
+    if (BUILTIN_TYPE(defined_class) == T_MODULE) defined_class =
data->rclass;
+    super_class = RCLASS_SUPER(defined_class);
+    if (!super_class) {
+  method_name_error(defined_class, rb_id2str(data->id), "no
superclass");
+    }
+    return mnew(super_class, data->recv, data->id,
+    rb_obj_class(method), FALSE);
+}
+
 /*
  * call-seq:
  *   local_jump_error.exit_value  -> obj
@@ -2735,6 +2772,7 @@ Init_Proc(void)
     rb_define_method(rb_cMethod, "unbind", method_unbind, 0);
     rb_define_method(rb_cMethod, "source_location", rb_method_location,
0);
     rb_define_method(rb_cMethod, "parameters", rb_method_parameters,
0);
+    rb_define_method(rb_cMethod, "super_method", method_super_method,
0);
     rb_define_method(rb_mKernel, "method", rb_obj_method, 1);
     rb_define_method(rb_mKernel, "public_method", rb_obj_public_method,
1);
     rb_define_method(rb_mKernel, "singleton_method",
rb_obj_singleton_method, 1);
@@ -2756,6 +2794,7 @@ Init_Proc(void)
     rb_define_method(rb_cUnboundMethod, "bind", umethod_bind, 1);
     rb_define_method(rb_cUnboundMethod, "source_location",
rb_method_location, 0);
     rb_define_method(rb_cUnboundMethod, "parameters",
rb_method_parameters, 0);
+    rb_define_method(rb_cUnboundMethod, "super_method",
method_super_method, 0);

     /* Module#*_method */
     rb_define_method(rb_cModule, "instance_method",
rb_mod_instance_method, 1);
~~~


----------------------------------------
Feature #9781: Feature Proposal: Method#super_method
https://bugs.ruby-lang.org/issues/9781#change-46358

* Author: Richard Schneeman
* Status: Open
* Priority: Normal
* Assignee:
* Category: core
* Target version:
----------------------------------------

When `super` is called in a method the Ruby VM knows how to find the
next ancestor that has that method and call it. It is difficult to do
this manually, so I propose we expose this information in
Method#super_location.

Ruby Method class (http://www.ruby-doc.org/core-2.1.1/Method.html) is
returned by calling Object.method and passing in a method name
(http://www.ruby-doc.org/core-2.1.1/Object.html#met...). This
is useful for debugging:

```ruby
# /tmp/code.rb
class Foo
  def bar
  end
end

puts Foo.new.method(:bar).source_location
# => ["/tmp/code.rb", 3]
```

The Object#method allows a ruby developer to easily track the source
location of the method and makes debugging very easy. However if the
code is being invoked by a call to `super` it is difficult to track
down:

```ruby
# /tmp/code.rb

class BigFoo
  def bar
  end
end

class Foo < BigFoo
  def bar
    super
  end
end
```

In this code sample it is easy to find the method definition inside of
Foo but it is very difficult in large projects to find what code exactly
`super` is calling. This simple example is easy, but it can be hard when
there are many ancestors. Currently if I wanted to find this we can
inspect ancestors

```ruby
Foo.ancestors[1..-1].map do |ancestor|
  next unless ancestor.method_defined?(:bar)
  ancestor.instance_method(:bar)
end.compact.first.source_location
```

To make this process simpler I am proposing a method on the Method class
that would return the result of `super`


It could be called like this:

```ruby
Foo.new.method(:bar).super_method
```

I believe adding Method#super_method, or exposing this same information
somewhere else, could greatly help developers to debug large systems
easily.
This topic is locked and can not be replied to.