summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2021-11-18 12:44:19 -0800
committerJeremy Evans <code@jeremyevans.net>2021-11-18 14:17:57 -0800
commit4adb012926f8bd6011168327d8832cf19976de40 (patch)
tree9457020694002d0a0c07ced988a1d09c79fa74e6
parentea02b93bb95a42439631606269659dffc1981883 (diff)
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@ruby-lang.org Co-authored-by: Nobuyoshi Nakada nobu@ruby-lang.org
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/5051
-rw-r--r--NEWS.md10
-rw-r--r--doc/syntax/methods.rdoc9
-rw-r--r--parse.y21
-rw-r--r--test/ruby/test_iseq.rb18
-rw-r--r--test/ruby/test_parse.rb2
-rw-r--r--test/ruby/test_syntax.rb12
6 files changed, 70 insertions, 2 deletions
diff --git a/NEWS.md b/NEWS.md
index 2e4cbc6742..3e8035714b 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -7,6 +7,15 @@ Note that each entry is kept to a minimum, see links for details.
## 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.
[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 e86cc2c00c..2bb350def1 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:
@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 8bfb7f2757..b6498651bf 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_
#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
/*% %*/
/*% 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
/*% %*/
/*% 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 692549efa0..49f12019dc 100644
--- a/test/ruby/test_iseq.rb
+++ b/test/ruby/test_iseq.rb
@@ -125,6 +125,16 @@ class TestISeq < Test::Unit::TestCase
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
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 3120016e60..d697a29c1c 100644
--- a/test/ruby/test_parse.rb
+++ b/test/ruby/test_parse.rb
@@ -1044,7 +1044,7 @@ x = __ENCODING__
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 11953ab563..ce1e489992 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -66,6 +66,18 @@ class TestSyntax < Test::Unit::TestCase
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|