From: Nobuyoshi Nakada Date: 2008-11-14T15:09:52+09:00 Subject: [ruby-core:19927] Re: {Proc,Method}#parameters (Re: Proposal: Method#get_args) Hi, At Thu, 13 Nov 2008 18:44:53 +0900, Brian Candler wrote in [ruby-core:19915]: > That does give me an idea. Since "?" is not legal in a variable name, then a > very simple option could be > > def foo(a, b, c=123, d=nil, *e, &blk) > > => ["a", "b", "?c", "?d", "*e", "&blk"] It's simplest. > define_method(:bar) { |a,(b,c)| } > > => ["a", ["b", "c"]] Is it really necessary? Index: iseq.c =================================================================== --- iseq.c (revision 20232) +++ iseq.c (working copy) @@ -1275,4 +1275,44 @@ rb_iseq_clone(VALUE iseqval, VALUE newcb } +VALUE +rb_iseq_parameters(const rb_iseq_t *iseq) +{ + int i, r; + VALUE a, n, args = rb_ary_new2(iseq->arg_size); + + for (i = 0; i < iseq->argc; i++) { + a = rb_id2str(iseq->local_table[i]); + rb_ary_push(args, a ? a : Qnil); + } + r = iseq->arg_rest != -1 ? iseq->arg_rest : + iseq->arg_post_len > 0 ? iseq->arg_post_start : + iseq->arg_block != -1 ? iseq->arg_block : + iseq->arg_size; + for (; i < r; i++) { + a = rb_id2str(iseq->local_table[i]); + n = rb_str_new2("?"); + if (a) rb_str_append(n, a); + rb_ary_push(args, n); + } + if (iseq->arg_rest != -1) { + a = rb_id2str(iseq->local_table[iseq->arg_rest]); + n = rb_str_new2("*"); + if (a) rb_str_append(n, a); + rb_ary_push(args, n); + } + r = iseq->arg_post_start + iseq->arg_post_len; + for (i = iseq->arg_post_start; i < r; i++) { + a = rb_id2str(iseq->local_table[i]); + rb_ary_push(args, a ? a : Qnil); + } + if (iseq->arg_block != -1) { + a = rb_id2str(iseq->local_table[iseq->arg_block]); + n = rb_str_new2("&"); + if (a) rb_str_append(n, a); + rb_ary_push(args, n); + } + return args; +} + /* ruby2cext */ Index: proc.c =================================================================== --- proc.c (revision 20232) +++ proc.c (working copy) @@ -26,7 +26,10 @@ VALUE rb_cBinding; VALUE rb_cProc; +VALUE rb_iseq_parameters(const rb_iseq_t *iseq); + static VALUE bmcall(VALUE, VALUE); static int method_arity(VALUE); static VALUE rb_obj_is_method(VALUE m); +static rb_iseq_t *get_method_iseq(VALUE method); /* Proc */ @@ -616,6 +619,12 @@ get_proc_iseq(VALUE self) GetProcPtr(self, proc); iseq = proc->block.iseq; - if (!RUBY_VM_NORMAL_ISEQ_P(iseq)) - return 0; + if (!RUBY_VM_NORMAL_ISEQ_P(iseq)) { + NODE *node = (NODE *)iseq; + iseq = 0; + if (nd_type(node) == NODE_IFUNC && node->nd_cfnc == bmcall) { + /* method(:foo).to_proc */ + iseq = get_method_iseq(node->nd_tval); + } + } return iseq; } @@ -651,4 +660,34 @@ rb_proc_location(VALUE self) } +static VALUE +unnamed_parameters(int arity) +{ + VALUE param = rb_ary_new2((arity < 0) ? -arity : arity); + if (arity < 0) { + rb_ary_store(param, ~arity, rb_str_new2("*")); + } + else { + rb_ary_store(param, arity-1, Qnil); + } + return param; +} + +/* + * call-seq: + * proc.parameters => array + * + * returns the parameter information of this proc + */ + +static VALUE +rb_proc_parameters(VALUE self) +{ + rb_iseq_t *iseq = get_proc_iseq(self); + if (!iseq) { + return unnamed_parameters(proc_arity(self)); + } + return rb_iseq_parameters(iseq); +} + /* * call-seq: @@ -1461,4 +1500,6 @@ get_method_iseq(VALUE method) body = data->body; switch (nd_type(body)) { + case NODE_BMETHOD: + return get_proc_iseq(body->nd_cval); case RUBY_VM_METHOD_NODE: GetISeqPtr((VALUE)body->nd_body, iseq); @@ -1485,4 +1526,22 @@ rb_method_location(VALUE method) /* + * call-seq: + * meth.parameters => array + * + * returns the parameter information of this method + * or nil if this method was not defined in ruby (i.e. native) + */ + +static VALUE +rb_method_parameters(VALUE method) +{ + rb_iseq_t *iseq = get_method_iseq(method); + if (!iseq) { + return unnamed_parameters(method_arity(method)); + } + return rb_iseq_parameters(iseq); +} + +/* * call-seq: * meth.to_s => string @@ -1812,4 +1871,5 @@ Init_Proc(void) rb_define_method(rb_cProc, "curry", proc_curry, -1); rb_define_method(rb_cProc, "source_location", rb_proc_location, 0); + rb_define_method(rb_cProc, "parameters", rb_proc_parameters, 0); /* Exceptions */ @@ -1847,4 +1907,5 @@ 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_mKernel, "method", rb_obj_method, 1); rb_define_method(rb_mKernel, "public_method", rb_obj_public_method, 1); @@ -1865,4 +1926,5 @@ 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); /* Module#*_method */ Index: test/ruby/test_method.rb =================================================================== --- test/ruby/test_method.rb (revision 20232) +++ test/ruby/test_method.rb (working copy) @@ -21,4 +21,6 @@ class TestMethod < Test::Unit::TestCase def mo5(a, *b, c) end def mo6(a, *b, c, &d) end + def mo7(a, b = nil, *c, d, &e) end + def ma1((a), &b) end class Base @@ -222,3 +224,71 @@ class TestMethod < Test::Unit::TestCase assert_raise(ArgumentError) { o.method(:foo).call(1) } end + + define_method(:pm0) {||} + define_method(:pm1) {|a|} + define_method(:pm2) {|a, b|} + define_method(:pmo1) {|a = nil, &b|} + define_method(:pmo2) {|a, b = nil|} + define_method(:pmo3) {|*a|} + define_method(:pmo4) {|a, *b, &c|} + define_method(:pmo5) {|a, *b, c|} + define_method(:pmo6) {|a, *b, c, &d|} + define_method(:pmo7) {|a, b = nil, *c, d, &e|} + define_method(:pma1) {|(a), &b|} + + def test_bound_parameters + assert_equal(%w(), method(:m0).parameters) + assert_equal(%w(a), method(:m1).parameters) + assert_equal(%w(a b), method(:m2).parameters) + assert_equal(%w(?a &b), method(:mo1).parameters) + assert_equal(%w(a ?b), method(:mo2).parameters) + assert_equal(%w(*a), method(:mo3).parameters) + assert_equal(%w(a *b &c), method(:mo4).parameters) + assert_equal(%w(a *b c), method(:mo5).parameters) + assert_equal(%w(a *b c &d), method(:mo6).parameters) + assert_equal(%w(a ?b *c d &e), method(:mo7).parameters) + assert_equal([nil, "&b"], method(:ma1).parameters) + end + + def test_unbound_parameters + assert_equal(%w(), self.class.instance_method(:m0).parameters) + assert_equal(%w(a), self.class.instance_method(:m1).parameters) + assert_equal(%w(a b), self.class.instance_method(:m2).parameters) + assert_equal(%w(?a &b), self.class.instance_method(:mo1).parameters) + assert_equal(%w(a ?b), self.class.instance_method(:mo2).parameters) + assert_equal(%w(*a), self.class.instance_method(:mo3).parameters) + assert_equal(%w(a *b &c), self.class.instance_method(:mo4).parameters) + assert_equal(%w(a *b c), self.class.instance_method(:mo5).parameters) + assert_equal(%w(a *b c &d), self.class.instance_method(:mo6).parameters) + assert_equal(%w(a ?b *c d &e), self.class.instance_method(:mo7).parameters) + assert_equal([nil, "&b"], self.class.instance_method(:ma1).parameters) + end + + def test_bmethod_bound_parameters + assert_equal(%w(), method(:pm0).parameters) + assert_equal(%w(a), method(:pm1).parameters) + assert_equal(%w(a b), method(:pm2).parameters) + assert_equal(%w(?a &b), method(:pmo1).parameters) + assert_equal(%w(a ?b), method(:pmo2).parameters) + assert_equal(%w(*a), method(:pmo3).parameters) + assert_equal(%w(a *b &c), method(:pmo4).parameters) + assert_equal(%w(a *b c), method(:pmo5).parameters) + assert_equal(%w(a *b c &d), method(:pmo6).parameters) + assert_equal(%w(a ?b *c d &e), method(:pmo7).parameters) + assert_equal([nil, "&b"], method(:pma1).parameters) + end + + def test_bmethod_unbound_parameters + assert_equal(%w(), self.class.instance_method(:pm0).parameters) + assert_equal(%w(a), self.class.instance_method(:pm1).parameters) + assert_equal(%w(a b), self.class.instance_method(:pm2).parameters) + assert_equal(%w(?a &b), self.class.instance_method(:pmo1).parameters) + assert_equal(%w(a ?b), self.class.instance_method(:pmo2).parameters) + assert_equal(%w(*a), self.class.instance_method(:pmo3).parameters) + assert_equal(%w(a *b &c), self.class.instance_method(:pmo4).parameters) + assert_equal(%w(a *b c), self.class.instance_method(:pmo5).parameters) + assert_equal(%w(a *b c &d), self.class.instance_method(:pmo6).parameters) + assert_equal(%w(a ?b *c d &e), self.class.instance_method(:pmo7).parameters) + assert_equal([nil, "&b"], self.class.instance_method(:pma1).parameters) + end end Index: test/ruby/test_proc.rb =================================================================== --- test/ruby/test_proc.rb (revision 20232) +++ test/ruby/test_proc.rb (working copy) @@ -674,3 +674,46 @@ class TestProc < Test::Unit::TestCase assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]") end + + def test_parameters + assert_equal(%w(), proc {}.parameters) + assert_equal(%w(), proc {||}.parameters) + assert_equal(%w(a), proc {|a|}.parameters) + assert_equal(%w(a b), proc {|a, b|}.parameters) + assert_equal(%w(?a &b), proc {|a=:a, &b|}.parameters) + assert_equal(%w(a ?b), proc {|a, b=:b|}.parameters) + assert_equal(%w(*a), proc {|*a|}.parameters) + assert_equal(%w(a *b &c), proc {|a, *b, &c|}.parameters) + assert_equal(%w(a *b c), proc {|a, *b, c|}.parameters) + assert_equal(%w(a *b c &d), proc {|a, *b, c, &d|}.parameters) + assert_equal(%w(a ?b *c d &e), proc {|a, b=:b, *c, d, &e|}.parameters) + assert_equal([nil, "&b"], proc {|(a), &b|}.parameters) + assert_equal(%w[a b ?c ?d *e f g &h], proc {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters) + end + + def pm0() end + def pm1(a) end + def pm2(a, b) end + def pmo1(a = nil, &b) end + def pmo2(a, b = nil) end + def pmo3(*a) end + def pmo4(a, *b, &c) end + def pmo5(a, *b, c) end + def pmo6(a, *b, c, &d) end + def pmo7(a, b = nil, *c, d, &e) end + def pma1((a), &b) end + + + def test_bound_parameters + assert_equal(%w(), method(:pm0).to_proc.parameters) + assert_equal(%w(a), method(:pm1).to_proc.parameters) + assert_equal(%w(a b), method(:pm2).to_proc.parameters) + assert_equal(%w(?a &b), method(:pmo1).to_proc.parameters) + assert_equal(%w(a ?b), method(:pmo2).to_proc.parameters) + assert_equal(%w(*a), method(:pmo3).to_proc.parameters) + assert_equal(%w(a *b &c), method(:pmo4).to_proc.parameters) + assert_equal(%w(a *b c), method(:pmo5).to_proc.parameters) + assert_equal(%w(a *b c &d), method(:pmo6).to_proc.parameters) + assert_equal(%w(a ?b *c d &e), method(:pmo7).to_proc.parameters) + assert_equal([nil, "&b"], method(:pma1).to_proc.parameters) + end end -- Nobu Nakada