Skip to content

Commit 116d402

Browse files
nobukddnewton
authored andcommitted
[Feature #19979] Method definition with &nil
Allow methods to declare that they don't accept a block via `&nil`.
1 parent 2065b55 commit 116d402

10 files changed

Lines changed: 149 additions & 9 deletions

File tree

compile.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,7 +2096,7 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons
20962096

20972097
if (node_args) {
20982098
struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq);
2099-
struct rb_args_info *args = &RNODE_ARGS(node_args)->nd_ainfo;
2099+
const struct rb_args_info *const args = &RNODE_ARGS(node_args)->nd_ainfo;
21002100
ID rest_id = 0;
21012101
int last_comma = 0;
21022102
ID block_id = 0;
@@ -2193,7 +2193,10 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons
21932193
body->param.flags.accepts_no_kwarg = TRUE;
21942194
}
21952195

2196-
if (block_id) {
2196+
if (args->no_blockarg) {
2197+
body->param.flags.accepts_no_block = TRUE;
2198+
}
2199+
else if (block_id) {
21972200
body->param.block_start = arg_size++;
21982201
body->param.flags.has_block = TRUE;
21992202
iseq_set_use_block(iseq);
@@ -13678,7 +13681,8 @@ ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq)
1367813681
(body->param.flags.anon_rest << 10) |
1367913682
(body->param.flags.anon_kwrest << 11) |
1368013683
(body->param.flags.use_block << 12) |
13681-
(body->param.flags.forwardable << 13) ;
13684+
(body->param.flags.forwardable << 13) |
13685+
(body->param.flags.accepts_no_block << 14);
1368213686

1368313687
#if IBF_ISEQ_ENABLE_LOCAL_BUFFER
1368413688
# define IBF_BODY_OFFSET(x) (x)
@@ -13898,6 +13902,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset)
1389813902
load_body->param.flags.anon_kwrest = (param_flags >> 11) & 1;
1389913903
load_body->param.flags.use_block = (param_flags >> 12) & 1;
1390013904
load_body->param.flags.forwardable = (param_flags >> 13) & 1;
13905+
load_body->param.flags.accepts_no_block = (param_flags >> 14) & 1;
1390113906
load_body->param.size = param_size;
1390213907
load_body->param.lead_num = param_lead_num;
1390313908
load_body->param.opt_num = param_opt_num;

parse.y

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6562,6 +6562,11 @@ f_block_arg : blkarg_mark tIDENTIFIER
65626562
$$ = $2;
65636563
/*% ripper: blockarg!($:2) %*/
65646564
}
6565+
| blkarg_mark keyword_nil
6566+
{
6567+
$$ = idNil;
6568+
/*% ripper: blockarg!(ID2VAL(idNil)) %*/
6569+
}
65656570
| blkarg_mark
65666571
{
65676572
arg_var(p, idFWD_BLOCK);
@@ -14474,6 +14479,10 @@ new_args_tail(struct parser_params *p, rb_node_kw_arg_t *kw_args, ID kw_rest_arg
1447414479
struct rb_args_info *args = &node->nd_ainfo;
1447514480
if (p->error_p) return node;
1447614481

14482+
if (block == idNil) {
14483+
block = 0;
14484+
args->no_blockarg = TRUE;
14485+
}
1447714486
args->block_arg = block;
1447814487
args->kw_args = kw_args;
1447914488

rubyparser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,7 @@ struct rb_args_info {
764764

765765
struct RNode_OPT_ARG *opt_args;
766766
unsigned int no_kwarg: 1;
767+
unsigned int no_blockarg: 1;
767768
unsigned int forwarding: 1;
768769
};
769770

spec/ruby/language/block_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,17 @@ def o.it
11101110
end
11111111
end
11121112
end
1113+
1114+
ruby_version_is "3.4" do
1115+
it "works alongside disallowed block argument" do
1116+
no_block = eval <<-EOF
1117+
proc {|arg1, &nil| arg1}
1118+
EOF
1119+
1120+
no_block.call(:a).should == :a
1121+
-> { no_block.call(:a) {} }.should raise_error(ArgumentError, 'no block accepted')
1122+
end
1123+
end
11131124
end
11141125

11151126
# Duplicates specs in language/it_parameter_spec.rb

spec/ruby/language/method_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,18 @@ def m(a, b = nil, c = nil, d, e: nil, **f)
11271127
result = m(1, {foo: :bar})
11281128
result.should == [1, nil, nil, {foo: :bar}, nil, {}]
11291129
end
1130+
1131+
ruby_version_is "3.4" do
1132+
evaluate <<-ruby do
1133+
def m(a, &nil); a end;
1134+
ruby
1135+
1136+
m(1).should == 1
1137+
1138+
-> { m(1) {} }.should raise_error(ArgumentError, 'no block accepted')
1139+
-> { m(1, &proc {}) }.should raise_error(ArgumentError, 'no block accepted')
1140+
end
1141+
end
11301142
end
11311143

11321144
context 'when passing an empty keyword splat to a method that does not accept keywords' do

test/ruby/test_syntax.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,49 @@ def test_argument_forwarding_with_anon_rest_kwrest_and_block
202202
assert_syntax_error("def f(...); g(&); end", /no anonymous block parameter/)
203203
end
204204

205+
def test_no_block_argument_in_method
206+
assert_valid_syntax("def f(&nil) end")
207+
assert_valid_syntax("def f(a, &nil) end")
208+
assert_valid_syntax("def f(*rest, &nil) end")
209+
assert_valid_syntax("def f(*rest, p, &nil) end")
210+
assert_valid_syntax("def f(a, *rest, &nil) end")
211+
assert_valid_syntax("def f(a, *rest, p, &nil) end")
212+
assert_valid_syntax("def f(a, k: nil, &nil) end")
213+
assert_valid_syntax("def f(a, k: nil, **kw, &nil) end")
214+
assert_valid_syntax("def f(a, *rest, k: nil, &nil) end")
215+
assert_valid_syntax("def f(a, *rest, k: nil, **kw, &nil) end")
216+
assert_valid_syntax("def f(a, *rest, p, k: nil, &nil) end")
217+
assert_valid_syntax("def f(a, *rest, p, k: nil, **kw, &nil) end")
218+
219+
obj = Object.new
220+
obj.instance_eval "def f(&nil) end"
221+
assert_raise_with_message(ArgumentError, /block accepted/) {obj.f {}}
222+
assert_raise_with_message(ArgumentError, /block accepted/) {obj.f(&proc {})}
223+
end
224+
225+
def test_no_block_argument_in_block
226+
assert_valid_syntax("proc do |&nil| end")
227+
assert_valid_syntax("proc do |a, &nil| end")
228+
assert_valid_syntax("proc do |*rest, &nil| end")
229+
assert_valid_syntax("proc do |*rest, p, &nil| end")
230+
assert_valid_syntax("proc do |a, *rest, &nil| end")
231+
assert_valid_syntax("proc do |a, *rest, p, &nil| end")
232+
assert_valid_syntax("proc do |a, k: nil, &nil| end")
233+
assert_valid_syntax("proc do |a, k: nil, **kw, &nil| end")
234+
assert_valid_syntax("proc do |a, *rest, k: nil, &nil| end")
235+
assert_valid_syntax("proc do |a, *rest, k: nil, **kw, &nil| end")
236+
assert_valid_syntax("proc do |a, *rest, p, k: nil, &nil| end")
237+
assert_valid_syntax("proc do |a, *rest, p, k: nil, **kw, &nil| end")
238+
239+
pr = eval "proc {|&nil|}"
240+
assert_nil(pr.call)
241+
assert_raise_with_message(ArgumentError, /block accepted/) {pr.call {}}
242+
pr = eval "proc {|a, &nil| a}"
243+
assert_nil(pr.call)
244+
assert_equal(1, pr.call(1))
245+
assert_raise_with_message(ArgumentError, /block accepted/) {pr.call {}}
246+
end
247+
205248
def test_newline_in_block_parameters
206249
bug = '[ruby-dev:45292]'
207250
["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params|

vm_args.c

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ NORETURN(static void raise_argument_error(rb_execution_context_t *ec, const rb_i
1212
NORETURN(static void argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const int miss_argc, const int min_argc, const int max_argc));
1313
NORETURN(static void argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const char *error, const VALUE keys));
1414
VALUE rb_keyword_error_new(const char *error, VALUE keys); /* class.c */
15+
static VALUE set_error_backtrace(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const VALUE exc);
16+
1517
static VALUE method_missing(rb_execution_context_t *ec, VALUE obj, ID id, int argc, const VALUE *argv,
1618
enum method_missing_reason call_status, int kw_splat);
1719
const rb_callable_method_entry_t *rb_resolve_refined_method_callable(VALUE refinements, const rb_callable_method_entry_t *me);
@@ -959,7 +961,15 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
959961
argument_kw_error(ec, iseq, cme, "unknown", rb_hash_keys(keyword_hash));
960962
}
961963

962-
if (ISEQ_BODY(iseq)->param.flags.has_block) {
964+
if (ISEQ_BODY(iseq)->param.flags.accepts_no_block) {
965+
VALUE given_block;
966+
args_setup_block_parameter(ec, calling, &given_block);
967+
if (!NIL_P(given_block)) {
968+
VALUE exc = rb_exc_new_cstr(rb_eArgError, "no block accepted");
969+
rb_exc_raise(set_error_backtrace(ec, iseq, cme, exc));
970+
}
971+
}
972+
else if (ISEQ_BODY(iseq)->param.flags.has_block) {
963973
if (ISEQ_BODY(iseq)->local_iseq == iseq) {
964974
/* Do nothing */
965975
}
@@ -981,8 +991,8 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
981991
return opt_pc;
982992
}
983993

984-
static void
985-
raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const VALUE exc)
994+
static VALUE
995+
set_error_backtrace(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const VALUE exc)
986996
{
987997
VALUE at;
988998

@@ -1000,6 +1010,13 @@ raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb
10001010

10011011
rb_ivar_set(exc, idBt_locations, at);
10021012
rb_exc_set_backtrace(exc, at);
1013+
return exc;
1014+
}
1015+
1016+
static void
1017+
raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const VALUE exc)
1018+
{
1019+
set_error_backtrace(ec, iseq, cme, exc);
10031020
rb_exc_raise(exc);
10041021
}
10051022

vm_core.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ struct rb_iseq_constant_body {
456456
unsigned int anon_kwrest: 1;
457457
unsigned int use_block: 1;
458458
unsigned int forwardable: 1;
459+
unsigned int accepts_no_block: 1;
459460
} flags;
460461

461462
unsigned int size;

vm_insnhelper.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2732,7 +2732,8 @@ rb_simple_iseq_p(const rb_iseq_t *iseq)
27322732
ISEQ_BODY(iseq)->param.flags.has_kwrest == FALSE &&
27332733
ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg == FALSE &&
27342734
ISEQ_BODY(iseq)->param.flags.forwardable == FALSE &&
2735-
ISEQ_BODY(iseq)->param.flags.has_block == FALSE;
2735+
ISEQ_BODY(iseq)->param.flags.has_block == FALSE &&
2736+
ISEQ_BODY(iseq)->param.flags.accepts_no_block == FALSE;
27362737
}
27372738

27382739
bool
@@ -2745,7 +2746,8 @@ rb_iseq_only_optparam_p(const rb_iseq_t *iseq)
27452746
ISEQ_BODY(iseq)->param.flags.has_kwrest == FALSE &&
27462747
ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg == FALSE &&
27472748
ISEQ_BODY(iseq)->param.flags.forwardable == FALSE &&
2748-
ISEQ_BODY(iseq)->param.flags.has_block == FALSE;
2749+
ISEQ_BODY(iseq)->param.flags.has_block == FALSE &&
2750+
ISEQ_BODY(iseq)->param.flags.accepts_no_block == FALSE;
27492751
}
27502752

27512753
bool
@@ -2757,7 +2759,8 @@ rb_iseq_only_kwparam_p(const rb_iseq_t *iseq)
27572759
ISEQ_BODY(iseq)->param.flags.has_kw == TRUE &&
27582760
ISEQ_BODY(iseq)->param.flags.has_kwrest == FALSE &&
27592761
ISEQ_BODY(iseq)->param.flags.forwardable == FALSE &&
2760-
ISEQ_BODY(iseq)->param.flags.has_block == FALSE;
2762+
ISEQ_BODY(iseq)->param.flags.has_block == FALSE &&
2763+
ISEQ_BODY(iseq)->param.flags.accepts_no_block == FALSE;
27612764
}
27622765

27632766
#define ALLOW_HEAP_ARGV (-2)

zjit/src/cruby_bindings.inc.rs

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)