[前][次][番号順一覧][スレッド一覧]

ruby-changes:69813

From: Jeremy <ko1@a...>
Date: Fri, 19 Nov 2021 07:18:10 +0900 (JST)
Subject: [ruby-changes:69813] 4adb012926 (master): Anonymous block forwarding allows a method to forward a passed

https://git.ruby-lang.org/ruby.git/commit/?id=4adb012926

From 4adb012926f8bd6011168327d8832cf19976de40 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@j...>
Date: Thu, 18 Nov 2021 12:44:19 -0800
Subject: Anonymous block forwarding allows a method to forward a passed block
 to another method without having to provide a name for the block parameter.

Implements [Feature #11256]

Co-authored-by: Yusuke Endoh mame@r...
Co-authored-by: Nobuyoshi Nakada nobu@r...
---
 NEWS.md                  | 10 ++++++++++
 doc/syntax/methods.rdoc  |  9 ++++++++-
 parse.y                  | 21 +++++++++++++++++++++
 test/ruby/test_iseq.rb   | 18 ++++++++++++++++++
 test/ruby/test_parse.rb  |  2 +-
 test/ruby/test_syntax.rb | 12 ++++++++++++
 6 files changed, 70 insertions(+), 2 deletions(-)

diff --git a/NEWS.md b/NEWS.md
index 2e4cbc6742d..3e8035714bb 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -7,6 +7,15 @@ Note that each entry is kept to a minimum, see links for details. https://github.com/ruby/ruby/blob/trunk/NEWS.md#L7
 
 ## Language changes
 
+* The block arguments can be now be anonymous, if the block will
+  only be passed to another method. [[Feature #11256]]
+
+    ```ruby
+    def foo(&)
+      bar(&)
+    end
+    ```
+
 * Pin operator now takes an expression. [[Feature #17411]]
 
     ```ruby
@@ -412,6 +421,7 @@ See [the repository](https://github.com/ruby/error_highlight) in detail. https://github.com/ruby/ruby/blob/trunk/NEWS.md#L421
 
 [Bug #4443]:      https://bugs.ruby-lang.org/issues/4443
 [Feature #6210]:  https://bugs.ruby-lang.org/issues/6210
+[Feature #11256]: https://bugs.ruby-lang.org/issues/11256
 [Feature #12194]: https://bugs.ruby-lang.org/issues/12194
 [Feature #12495]: https://bugs.ruby-lang.org/issues/12495
 [Feature #14256]: https://bugs.ruby-lang.org/issues/14256
diff --git a/doc/syntax/methods.rdoc b/doc/syntax/methods.rdoc
index e86cc2c00ce..2bb350def18 100644
--- a/doc/syntax/methods.rdoc
+++ b/doc/syntax/methods.rdoc
@@ -515,8 +515,15 @@ Most frequently the block argument is used to pass a block to another method: https://github.com/ruby/ruby/blob/trunk/doc/syntax/methods.rdoc#L515
     @items.each(&block)
   end
 
+You are not required to give a name to the block if you will just be passing
+it to another method:
+
+  def each_item(&)
+    @items.each(&)
+  end
+
 If you are only going to call the block and will not otherwise manipulate it
-or send it to another method using <code>yield</code> without an explicit
+or send it to another method, using <code>yield</code> without an explicit
 block parameter is preferred.  This method is equivalent to the first method
 in this section:
 
diff --git a/parse.y b/parse.y
index 8bfb7f2757c..b6498651bfb 100644
--- a/parse.y
+++ b/parse.y
@@ -427,6 +427,8 @@ static void token_info_drop(struct parser_params *p, const char *token, rb_code_ https://github.com/ruby/ruby/blob/trunk/parse.y#L427
 
 #define lambda_beginning_p() (p->lex.lpar_beg == p->lex.paren_nest)
 
+#define ANON_BLOCK_ID '&'
+
 static enum yytokentype yylex(YYSTYPE*, YYLTYPE*, struct parser_params*);
 
 #ifndef RIPPER
@@ -2846,6 +2848,17 @@ block_arg	: tAMPER arg_value https://github.com/ruby/ruby/blob/trunk/parse.y#L2848
 		    /*% %*/
 		    /*% ripper: $2 %*/
 		    }
+                | tAMPER
+                    {
+                    /*%%%*/
+                        if (!local_id(p, ANON_BLOCK_ID)) {
+                            compile_error(p, "no anonymous block parameter");
+                        }
+                        $$ = NEW_BLOCK_PASS(NEW_LVAR(ANON_BLOCK_ID, &@1), &@$);
+                    /*%
+                    $$ = Qnil;
+                    %*/
+                    }
 		;
 
 opt_block_arg	: ',' block_arg
@@ -5541,6 +5554,14 @@ f_block_arg	: blkarg_mark tIDENTIFIER https://github.com/ruby/ruby/blob/trunk/parse.y#L5554
 		    /*% %*/
 		    /*% ripper: blockarg!($2) %*/
 		    }
+                | blkarg_mark
+                    {
+                    /*%%%*/
+                        arg_var(p, shadowing_lvar(p, get_id(ANON_BLOCK_ID)));
+                    /*%
+                    $$ = dispatch1(blockarg, Qnil);
+                    %*/
+                    }
 		;
 
 opt_f_block_arg	: ',' f_block_arg
diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb
index 692549efa03..49f12019dcf 100644
--- a/test/ruby/test_iseq.rb
+++ b/test/ruby/test_iseq.rb
@@ -125,6 +125,16 @@ class TestISeq < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_iseq.rb#L125
     assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
   end
 
+  def test_super_with_anonymous_block
+    iseq = compile(<<~EOF)
+      def touch3(&block) # :nodoc:
+        foo { super }
+      end
+      42
+    EOF
+    assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+  end
+
   def test_ractor_unshareable_outer_variable
     name = "\u{2603 26a1}"
     y = eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call
@@ -373,6 +383,14 @@ class TestISeq < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_iseq.rb#L383
     assert_equal [2], param_names
   end
 
+  def anon_block(&); end
+
+  def test_anon_block_param_in_disasm
+    iseq = RubyVM::InstructionSequence.of(method(:anon_block))
+    param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
+    assert_equal [:&], param_names
+  end
+
   def strip_lineno(source)
     source.gsub(/^.*?: /, "")
   end
diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb
index 3120016e60d..d697a29c1c8 100644
--- a/test/ruby/test_parse.rb
+++ b/test/ruby/test_parse.rb
@@ -1044,7 +1044,7 @@ x = __ENCODING__ https://github.com/ruby/ruby/blob/trunk/test/ruby/test_parse.rb#L1044
     end;
 
     assert_syntax_error("def\nf(000)end", /^  \^~~/)
-    assert_syntax_error("def\nf(&)end", /^   \^/)
+    assert_syntax_error("def\nf(&0)end", /^   \^/)
   end
 
   def test_method_location_in_rescue
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb
index 11953ab5635..ce1e489992e 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -66,6 +66,18 @@ class TestSyntax < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_syntax.rb#L66
     f&.close!
   end
 
+  def test_anonymous_block_forwarding
+    assert_syntax_error("def b; c(&); end", /no anonymous block parameter/)
+    assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+    begin;
+        def b(&); c(&) end
+        def c(&); yield 1 end
+        a = nil
+        b{|c| a = c}
+        assert_equal(1, a)
+    end;
+  end
+
   def test_newline_in_block_parameters
     bug = '[ruby-dev:45292]'
     ["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params|
-- 
cgit v1.2.1


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]