仮引数名のリスト

Ruby においてキーワード引数の話は定期的に出るネタで、先日某 IRC でもその辺について話していたのですが、その中で、とりあえず method.arity みたいな感じで仮引数名のリストを取れたらいいんじゃないかという流れになったので、パッチにしてみました。

--- proc.c      (revision 15843)
+++ proc.c      (working copy)
@@ -709,6 +709,36 @@ proc_to_proc(VALUE self)
     return self;
 }

+static VALUE
+iseq_get_parameters(rb_iseq_t *iseq)
+{
+    if (iseq->arg_rest == -1 && iseq->arg_opts == 0) {
+       int i;
+       VALUE args = rb_ary_new2(3);
+       for (i = 0; i < iseq->argc; i++) {
+           rb_ary_push(args, rb_str_new2(rb_id2name(iseq->local_table[i])));
+       }
+       return args;
+    }
+    return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     prc.parameters -> array
+ *
+ *  return the list of the name of this proc.
+ *  When this proc takes a variable number of arguments, returns nil.
+ */
+
+static VALUE
+proc_parameters(VALUE self)
+{
+    rb_proc_t *proc;
+    GetProcPtr(self, proc);
+    return iseq_get_parameters(proc->block.iseq);
+}
+
 static void
 bm_mark(struct METHOD *data)
 {
@@ -936,6 +966,35 @@ method_owner(VALUE obj)

 /*
  *  call-seq:
+ *     meth.parameters -> array
+ *
+ *  return the list of the name of this method.
+ *  When this method takes a variable number of arguments, returns nil.
+ */
+
+static VALUE
+method_parameters(VALUE method)
+{
+    NODE *body = rb_method_body(method);
+    if (body == 0) {
+       return Qnil;
+    }
+    else if (nd_type(body) == NODE_METHOD) {
+       rb_iseq_t *iseq;
+       GetISeqPtr((VALUE)body->nd_body, iseq);
+       return iseq_get_parameters(iseq);
+    }
+    else if (nd_type(body) == NODE_CFUNC) {
+    }
+    else {
+       fprintf(stderr,"%s (%s):%d: %d %s\n", __FILE__, __func__, __LINE__,
+               nd_type(body), ruby_node_name(nd_type(body)));
+    }
+    return Qnil;
+}
+
+/*
+ *  call-seq:
  *     obj.method(sym)    => method
  *
  *  Looks up the named method as a receiver in <i>obj</i>, returning a
@@ -1729,6 +1788,7 @@ Init_Proc(void)
     rb_define_method(rb_cProc, "lambda?", proc_lambda_p, 0);
     rb_define_method(rb_cProc, "binding", proc_binding, 0);
     rb_define_method(rb_cProc, "curry", proc_curry, -1);
+    rb_define_method(rb_cProc, "parameters", proc_parameters, 0);

     /* Exceptions */
     rb_eLocalJumpError = rb_define_class("LocalJumpError", rb_eStandardError);
@@ -1762,6 +1822,7 @@ Init_Proc(void)
     rb_define_method(rb_cMethod, "name", method_name, 0);
     rb_define_method(rb_cMethod, "owner", method_owner, 0);
     rb_define_method(rb_cMethod, "unbind", method_unbind, 0);
+    rb_define_method(rb_cMethod, "parameters", method_parameters, 0);
     rb_define_method(rb_mKernel, "method", rb_obj_method, 1);
     rb_define_method(rb_mKernel, "public_method", rb_obj_public_method, 1);

@@ -1779,6 +1840,7 @@ Init_Proc(void)
     rb_define_method(rb_cUnboundMethod, "name", method_name, 0);
     rb_define_method(rb_cUnboundMethod, "owner", method_owner, 0);
     rb_define_method(rb_cUnboundMethod, "bind", umethod_bind, 1);
+    rb_define_method(rb_cUnboundMethod, "parameters", method_parameters, 0);

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