From: Mauricio Fernandez Date: 2005-08-09T07:26:11+09:00 Subject: Re: [BUG] bug in variable assignment On Mon, Aug 08, 2005 at 11:36:22AM +0900, nobuyoshi nakada wrote: > At Mon, 8 Aug 2005 01:42:38 +0900, > Mauricio Fern�ndez wrote in [ruby-core:05536]: > > $ ~/ruby1.9/bin/ruby -e "->(&b){}.call(1); p 1 " -e:1: [BUG] > > bug in variable assignment ruby 1.9.0 (2005-08-06) > > [i686-linux] > Does this fix it? > > --- eval.c 5 Aug 2005 00:50:46 -0000 1.811 > +++ eval.c 8 Aug 2005 02:34:27 -0000 > @@ -5270,4 +5270,11 @@ assign(self, lhs, val, pcall) > break; > > + case NODE_BLOCK: > + lhs = lhs->nd_head; > + if (nd_type(lhs) == NODE_ARGS) { > + formal_assign(self, lhs, 1, &val, 0); > + break; > + } > + > default: > rb_bug("bug in variable assignment"); Not quite: $ ./ruby -v -e "->(&b){b.call(1)}.call{|x| puts x}" ruby 1.9.0 (2005-08-08) [i686-linux] -e:1: warning: multiple values for a block parameter (0 for 1) from -e:1 -e:1: wrong number of arguments (1 for 0) (ArgumentError) from -e:1 Here's my attempt to fix it: Index: parse.y =================================================================== RCS file: /src/ruby/parse.y,v retrieving revision 1.400 diff -u -b -p -r1.400 parse.y --- parse.y 8 Aug 2005 01:21:47 -0000 1.400 +++ parse.y 8 Aug 2005 22:15:07 -0000 @@ -4192,7 +4192,11 @@ f_block_arg : blkarg_mark tIDENTIFIER yyerror("block argument must be local variable"); else if (!dyna_in_block() && local_id($2)) yyerror("duplicated block argument name"); + if (dyna_in_block()) + $$ = assignable($2, 0); + else $$ = NEW_BLOCK_ARG($2); + /*% $$ = $2; %*/ Index: eval.c =================================================================== RCS file: /src/ruby/eval.c,v retrieving revision 1.812 diff -u -b -p -r1.812 eval.c --- eval.c 8 Aug 2005 13:14:08 -0000 1.812 +++ eval.c 8 Aug 2005 22:15:13 -0000 @@ -4893,6 +4893,13 @@ rb_yield_0(val, self, klass, flags, aval } formal_assign(self, var, RARRAY(val)->len, RARRAY(val)->ptr, 0); } + else if (nd_type(var) == NODE_BLOCK) { + if (var->nd_next) { + bvar = var->nd_next->nd_head; + } + var = var->nd_head; + goto block_var; + } else { int len = 0; if (avalue) { @@ -5269,13 +5276,6 @@ assign(self, lhs, val, pcall) } break; - case NODE_BLOCK: - lhs = lhs->nd_head; - if (nd_type(lhs) == NODE_ARGS) { - formal_assign(self, lhs, 1, &val, 0); - break; - } - default: rb_bug("bug in variable assignment"); break; After applying: $ ./ruby -v -e "->(&b){b.call(1)}.call{|x| puts x}" ruby 1.9.0 (2005-08-08) [i686-linux] 1 $ ./ruby -v -e "->(a,&b){b.call(a)}.call(2){|x| puts x}" ruby 1.9.0 (2005-08-08) [i686-linux] 2 I also wrote some tests to make sure the fix is sane enough: $ cat tester.rb require 'test/unit' class Test_Lambda_Parameters < Test::Unit::TestCase def test_call_simple assert_equal(1, ->(a){ a }.call(1)) assert_equal([1,2], ->(a,b){ [a,b] }.call(1,2)) assert_raises(ArgumentError) { ->(a){ }.call(1,2) } assert_raises(ArgumentError) { ->(a){ }.call() } assert_raises(ArgumentError) { ->(){ }.call(1) } assert_raises(ArgumentError) { ->(a,b){ }.call(1,2,3) } end def test_call_rest_args assert_equal([1,2], ->(*a){ a }.call(1,2)) assert_equal([1,2,[]], ->(a,b,*c){ [a,b,c] }.call(1,2)) assert_raises(ArgumentError){ ->(a,*b){ }.call() } end def test_call_opt_args assert_equal([1,2,3,4], ->(a,b,c=3,d=4){ [a,b,c,d] }.call(1,2)) assert_equal([1,2,3,4], ->(a,b,c=0,d=4){ [a,b,c,d] }.call(1,2,3)) assert_raises(ArgumentError){ ->(a,b=1){ }.call() } assert_raises(ArgumentError){ ->(a,b=1){ }.call(1,2,3) } end def test_call_rest_and_opt assert_equal([1,2,3,[]], ->(a,b=2,c=3,*d){ [a,b,c,d] }.call(1)) assert_equal([1,2,3,[]], ->(a,b=0,c=3,*d){ [a,b,c,d] }.call(1,2)) assert_equal([1,2,3,[4,5,6]], ->(a,b=0,c=0,*d){ [a,b,c,d] }.call(1,2,3,4,5,6)) assert_raises(ArgumentError){ ->(a,b=1,*c){ }.call() } end def test_call_with_block f = ->(a,b,c=3,*d,&e){ [a,b,c,d,e.call(d + [a,b,c])] } assert_equal([1,2,3,[],6], f.call(1,2){|z| z.inject{|s,x| s+x} } ) assert_equal(nil, ->(&b){ b }.call) foo { puts "bogus block " } assert_equal(1, ->(&b){ b.call }.call { 1 }) b = nil assert_equal(1, ->(&b){ b.call }.call { 1 }) assert_not_nil(b) end def foo assert_equal(nil, ->(&b){ b }.call) end def test_lambda_as_iterator a = 0 2.times ->(_){ a += 1 } assert_equal(a, 2) end end batsman@tux-chan:/tmp/ruby1.9/ruby.fixed-head$ ./ruby tester.rb Loaded suite tester Started ...... Finished in 0.004576 seconds. 6 tests, 24 assertions, 0 failures, 0 errors -- Mauricio Fernandez