1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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 ]

Co-authored-by: Yusuke Endoh mame@ruby-lang.org
Co-authored-by: Nobuyoshi Nakada nobu@ruby-lang.org
This commit is contained in:
Jeremy Evans 2021-11-18 12:44:19 -08:00
parent ea02b93bb9
commit 4adb012926
Notes: git 2021-11-19 07:18:18 +09:00
6 changed files with 70 additions and 2 deletions

10
NEWS.md
View file

@ -7,6 +7,15 @@ Note that each entry is kept to a minimum, see links for details.
## Language changes ## 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]] * Pin operator now takes an expression. [[Feature #17411]]
```ruby ```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 [Bug #4443]: https://bugs.ruby-lang.org/issues/4443
[Feature #6210]: https://bugs.ruby-lang.org/issues/6210 [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 #12194]: https://bugs.ruby-lang.org/issues/12194
[Feature #12495]: https://bugs.ruby-lang.org/issues/12495 [Feature #12495]: https://bugs.ruby-lang.org/issues/12495
[Feature #14256]: https://bugs.ruby-lang.org/issues/14256 [Feature #14256]: https://bugs.ruby-lang.org/issues/14256

View file

@ -515,8 +515,15 @@ Most frequently the block argument is used to pass a block to another method:
@items.each(&block) @items.each(&block)
end 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 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 block parameter is preferred. This method is equivalent to the first method
in this section: in this section:

21
parse.y
View file

@ -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 lambda_beginning_p() (p->lex.lpar_beg == p->lex.paren_nest)
#define ANON_BLOCK_ID '&'
static enum yytokentype yylex(YYSTYPE*, YYLTYPE*, struct parser_params*); static enum yytokentype yylex(YYSTYPE*, YYLTYPE*, struct parser_params*);
#ifndef RIPPER #ifndef RIPPER
@ -2846,6 +2848,17 @@ block_arg : tAMPER arg_value
/*% %*/ /*% %*/
/*% ripper: $2 %*/ /*% 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 opt_block_arg : ',' block_arg
@ -5541,6 +5554,14 @@ f_block_arg : blkarg_mark tIDENTIFIER
/*% %*/ /*% %*/
/*% ripper: blockarg!($2) %*/ /*% 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 opt_f_block_arg : ',' f_block_arg

View file

@ -125,6 +125,16 @@ class TestISeq < Test::Unit::TestCase
assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval) assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
end 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 def test_ractor_unshareable_outer_variable
name = "\u{2603 26a1}" name = "\u{2603 26a1}"
y = eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call y = eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call
@ -373,6 +383,14 @@ class TestISeq < Test::Unit::TestCase
assert_equal [2], param_names assert_equal [2], param_names
end 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) def strip_lineno(source)
source.gsub(/^.*?: /, "") source.gsub(/^.*?: /, "")
end end

View file

@ -1044,7 +1044,7 @@ x = __ENCODING__
end; end;
assert_syntax_error("def\nf(000)end", /^ \^~~/) assert_syntax_error("def\nf(000)end", /^ \^~~/)
assert_syntax_error("def\nf(&)end", /^ \^/) assert_syntax_error("def\nf(&0)end", /^ \^/)
end end
def test_method_location_in_rescue def test_method_location_in_rescue

View file

@ -66,6 +66,18 @@ class TestSyntax < Test::Unit::TestCase
f&.close! f&.close!
end 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 def test_newline_in_block_parameters
bug = '[ruby-dev:45292]' bug = '[ruby-dev:45292]'
["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params| ["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params|