diff --git a/compile.c b/compile.c index ce2f7473fd8279..fb4b11707139dc 100644 --- a/compile.c +++ b/compile.c @@ -2023,7 +2023,7 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons if (node_args) { struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); - struct rb_args_info *args = &RNODE_ARGS(node_args)->nd_ainfo; + const struct rb_args_info *const args = &RNODE_ARGS(node_args)->nd_ainfo; ID rest_id = 0; int last_comma = 0; ID block_id = 0; @@ -2121,7 +2121,10 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons body->param.flags.accepts_no_kwarg = TRUE; } - if (block_id) { + if (args->no_blockarg) { + body->param.flags.accepts_no_block = TRUE; + } + else if (block_id) { body->param.block_start = arg_size++; body->param.flags.has_block = TRUE; iseq_set_use_block(iseq); @@ -13118,7 +13121,8 @@ ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq) (body->param.flags.anon_rest << 10) | (body->param.flags.anon_kwrest << 11) | (body->param.flags.use_block << 12) | - (body->param.flags.forwardable << 13) ; + (body->param.flags.forwardable << 13) | + (body->param.flags.accepts_no_block << 14); #if IBF_ISEQ_ENABLE_LOCAL_BUFFER # define IBF_BODY_OFFSET(x) (x) @@ -13336,6 +13340,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) load_body->param.flags.anon_kwrest = (param_flags >> 11) & 1; load_body->param.flags.use_block = (param_flags >> 12) & 1; load_body->param.flags.forwardable = (param_flags >> 13) & 1; + load_body->param.flags.accepts_no_block = (param_flags >> 14) & 1; load_body->param.size = param_size; load_body->param.lead_num = param_lead_num; load_body->param.opt_num = param_opt_num; diff --git a/iseq.c b/iseq.c index ad6ed332e84ff6..22ca76d15b0229 100644 --- a/iseq.c +++ b/iseq.c @@ -3586,7 +3586,13 @@ rb_iseq_parameters(const rb_iseq_t *iseq, int is_proc) } rb_ary_push(args, a); } - if (body->param.flags.has_block) { + if (body->param.flags.accepts_no_block) { + ID noblock; + CONST_ID(noblock, "noblock"); + PARAM_TYPE(noblock); + rb_ary_push(args, a); + } + else if (body->param.flags.has_block) { CONST_ID(block, "block"); rb_ary_push(args, PARAM(body->param.block_start, block)); } diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb index aa6a18cf296209..ca0b0f41050c70 100644 --- a/lib/prism/node_ext.rb +++ b/lib/prism/node_ext.rb @@ -297,7 +297,13 @@ def signature names << [:nokey] end - names << [:block, block.name || :&] if block + case block + when BlockParameterNode + names << [:block, block.name || :&] + when NoBlockParameterNode + names << [:noblock] + end + names end end diff --git a/parse.y b/parse.y index ec4a4ac6a8ae83..8fbb8dff880335 100644 --- a/parse.y +++ b/parse.y @@ -6683,6 +6683,11 @@ f_block_arg : blkarg_mark tIDENTIFIER $$ = $2; /*% ripper: blockarg!($:2) %*/ } + | blkarg_mark keyword_nil + { + $$ = idNil; + /*% ripper: blockarg!(ID2VAL(idNil)) %*/ + } | blkarg_mark { arg_var(p, idFWD_BLOCK); @@ -14442,6 +14447,10 @@ new_args_tail(struct parser_params *p, rb_node_kw_arg_t *kw_args, ID kw_rest_arg struct rb_args_info *args = &node->nd_ainfo; if (p->error_p) return node; + if (block == idNil) { + block = 0; + args->no_blockarg = TRUE; + } args->block_arg = block; args->kw_args = kw_args; diff --git a/prism/config.yml b/prism/config.yml index 9001b388eefdd7..eb9fbc1e6575d0 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -3103,6 +3103,18 @@ nodes: nil ^^^ + - name: NoBlockParameterNode + fields: + - name: operator_loc + type: location + - name: keyword_loc + type: location + comment: | + Represents the use of `&nil` inside method arguments. + + def a(&nil) + ^^^^ + end - name: NoKeywordsParameterNode fields: - name: operator_loc @@ -3249,7 +3261,9 @@ nodes: - NoKeywordsParameterNode - name: block type: node? - kind: BlockParameterNode + kind: + - BlockParameterNode + - NoBlockParameterNode comment: | Represents the list of parameters on a method, block, or lambda definition. diff --git a/prism/prism.c b/prism/prism.c index 6e93e2a96169ef..6fe48c7957d58c 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -6025,6 +6025,30 @@ pm_nil_node_create(pm_parser_t *parser, const pm_token_t *token) { return node; } +/** + * Allocate and initialize a new NoKeywordsParameterNode node. + */ +static pm_no_block_parameter_node_t * +pm_no_block_parameter_node_create(pm_parser_t *parser, const pm_token_t *operator, const pm_token_t *keyword) { + assert(operator->type == PM_TOKEN_UAMPERSAND || operator->type == PM_TOKEN_AMPERSAND); + assert(keyword->type == PM_TOKEN_KEYWORD_NIL); + pm_no_block_parameter_node_t *node = PM_ALLOC_NODE(parser, pm_no_block_parameter_node_t); + + *node = (pm_no_block_parameter_node_t) { + { + .type = PM_NO_BLOCK_PARAMETER_NODE, + .location = { + .start = operator->start, + .end = keyword->end + } + }, + .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), + .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword) + }; + + return node; +} + /** * Allocate and initialize a new NoKeywordsParameterNode node. */ @@ -6282,9 +6306,9 @@ pm_parameters_node_keyword_rest_set(pm_parameters_node_t *params, pm_node_t *par * Set the block parameter on a ParametersNode node. */ static void -pm_parameters_node_block_set(pm_parameters_node_t *params, pm_block_parameter_node_t *param) { +pm_parameters_node_block_set(pm_parameters_node_t *params, pm_node_t *param) { assert(params->block == NULL); - pm_parameters_node_location_set(params, (pm_node_t *) param); + pm_parameters_node_location_set(params, param); params->block = param; } @@ -14230,27 +14254,34 @@ parse_parameters( parser_lex(parser); pm_token_t operator = parser->previous; - pm_token_t name; + pm_node_t *param; - bool repeated = false; - if (accept1(parser, PM_TOKEN_IDENTIFIER)) { - name = parser->previous; - repeated = pm_parser_parameter_name_check(parser, &name); - pm_parser_local_add_token(parser, &name, 1); + if (accept1(parser, PM_TOKEN_KEYWORD_NIL)) { + param = (pm_node_t *) pm_no_block_parameter_node_create(parser, &operator, &parser->previous); } else { - name = not_provided(parser); - parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_BLOCK; - } + pm_token_t name; - pm_block_parameter_node_t *param = pm_block_parameter_node_create(parser, &name, &operator); - if (repeated) { - pm_node_flag_set_repeated_parameter((pm_node_t *)param); + bool repeated = false; + if (accept1(parser, PM_TOKEN_IDENTIFIER)) { + name = parser->previous; + repeated = pm_parser_parameter_name_check(parser, &name); + pm_parser_local_add_token(parser, &name, 1); + } else { + name = not_provided(parser); + parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_BLOCK; + } + + param = (pm_node_t *) pm_block_parameter_node_create(parser, &name, &operator); + if (repeated) { + pm_node_flag_set_repeated_parameter(param); + } } + if (params->block == NULL) { pm_parameters_node_block_set(params, param); } else { - pm_parser_err_node(parser, (pm_node_t *) param, PM_ERR_PARAMETER_BLOCK_MULTI); - pm_parameters_node_posts_append(params, (pm_node_t *) param); + pm_parser_err_node(parser, param, PM_ERR_PARAMETER_BLOCK_MULTI); + pm_parameters_node_posts_append(params, param); } break; diff --git a/prism_compile.c b/prism_compile.c index 467da684f15f1c..34a0452aab8252 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -7774,6 +7774,12 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } + case PM_NO_BLOCK_PARAMETER_NODE: { + // def foo(&nil); end + // ^^^^ + ISEQ_BODY(iseq)->param.flags.accepts_no_block = TRUE; + return; + } case PM_NO_KEYWORDS_PARAMETER_NODE: { // def foo(**nil); end // ^^^^^ @@ -8779,25 +8785,30 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^ if (parameters_node->block) { - body->param.block_start = local_index; - body->param.flags.has_block = true; + if (PM_NODE_TYPE(parameters_node->block) == PM_NO_BLOCK_PARAMETER_NODE) { + body->param.flags.accepts_no_block = true; + } + else { + body->param.flags.has_block = true; + body->param.block_start = local_index; - pm_constant_id_t name = ((const pm_block_parameter_node_t *) parameters_node->block)->name; + pm_constant_id_t name = ((const pm_block_parameter_node_t *) parameters_node->block)->name; - if (name) { - if (PM_NODE_FLAG_P(parameters_node->block, PM_PARAMETER_FLAGS_REPEATED_PARAMETER)) { - ID local = pm_constant_id_lookup(scope_node, name); - local_table_for_iseq->ids[local_index] = local; + if (name) { + if (PM_NODE_FLAG_P(parameters_node->block, PM_PARAMETER_FLAGS_REPEATED_PARAMETER)) { + ID local = pm_constant_id_lookup(scope_node, name); + local_table_for_iseq->ids[local_index] = local; + } + else { + pm_insert_local_index(name, local_index, index_lookup_table, local_table_for_iseq, scope_node); + } } else { - pm_insert_local_index(name, local_index, index_lookup_table, local_table_for_iseq, scope_node); + pm_insert_local_special(idAnd, local_index, index_lookup_table, local_table_for_iseq); } - } - else { - pm_insert_local_special(idAnd, local_index, index_lookup_table, local_table_for_iseq); - } - local_index++; + local_index++; + } } } diff --git a/proc.c b/proc.c index 09b288fecdc45e..b1eb5e86f97bbe 100644 --- a/proc.c +++ b/proc.c @@ -3177,6 +3177,7 @@ method_inspect(VALUE method) const VALUE keyrest = ID2SYM(rb_intern("keyrest")); const VALUE block = ID2SYM(rb_intern("block")); const VALUE nokey = ID2SYM(rb_intern("nokey")); + const VALUE noblock = ID2SYM(rb_intern("noblock")); int forwarding = 0; rb_str_buf_cat2(str, "("); @@ -3210,6 +3211,9 @@ method_inspect(VALUE method) else if (kind == nokey) { name = rb_str_new2("nil"); } + else if (kind == noblock) { + name = rb_str_new2("nil"); + } } if (kind == req) { @@ -3259,6 +3263,9 @@ method_inspect(VALUE method) else if (kind == nokey) { rb_str_buf_cat2(str, "**nil"); } + else if (kind == noblock) { + rb_str_buf_cat2(str, "&nil"); + } if (i < RARRAY_LEN(params) - 1) { rb_str_buf_cat2(str, ", "); diff --git a/rjit_c.rb b/rjit_c.rb index 8c9615c6bbde1f..1ea661abb1b56c 100644 --- a/rjit_c.rb +++ b/rjit_c.rb @@ -1100,6 +1100,7 @@ def C.rb_iseq_constant_body anon_kwrest: [CType::BitField.new(1, 3), 11], use_block: [CType::BitField.new(1, 4), 12], forwardable: [CType::BitField.new(1, 5), 13], + accepts_no_block: [CType::BitField.new(1, 6), 14], ), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, flags)")], size: [CType::Immediate.parse("unsigned int"), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, size)")], lead_num: [CType::Immediate.parse("int"), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, lead_num)")], diff --git a/rubyparser.h b/rubyparser.h index d763bd10e7d466..4f5a386aa28150 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -786,6 +786,7 @@ struct rb_args_info { struct RNode_OPT_ARG *opt_args; unsigned int no_kwarg: 1; + unsigned int no_blockarg: 1; unsigned int ruby2_keywords: 1; unsigned int forwarding: 1; }; diff --git a/spec/ruby/core/method/parameters_spec.rb b/spec/ruby/core/method/parameters_spec.rb index 8495aef4d2d32d..1eb2eb79a15338 100644 --- a/spec/ruby/core/method/parameters_spec.rb +++ b/spec/ruby/core/method/parameters_spec.rb @@ -22,6 +22,12 @@ def one_splat_one_block(*args, &block) local_is_not_parameter = {} end + ruby_version_is "3.4" do + eval <<-RUBY + def one_noblock(&nil); end + RUBY + end + def forward_parameters(...) end def underscore_parameters(_, _, _ = 1, *_, _:, _: 2, **_, &_); end @@ -187,6 +193,13 @@ def underscore_parameters(_, _, _ = 1, *_, _:, _: 2, **_, &_); end m.parameters.should == [[:nokey]] end + ruby_version_is "3.4" do + it "returns [[:noblock]] for a method with a single &nil parameter" do + m = MethodSpecs::Methods.instance_method(:one_noblock) + m.parameters.should == [[:noblock]] + end + end + it "works with ->(){} as the value of an optional argument" do m = MethodSpecs::Methods.instance_method(:one_opt_with_stabby) m.parameters.should == [[:opt,:a]] diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb index 972596d2ea8809..729912c05576d9 100644 --- a/spec/ruby/core/proc/parameters_spec.rb +++ b/spec/ruby/core/proc/parameters_spec.rb @@ -174,4 +174,12 @@ it "returns :nokey for **nil parameter" do proc { |**nil| }.parameters.should == [[:nokey]] end + + ruby_version_is "3.4" do + it "returns :noblock for &nil parameter" do + eval <<~RUBY + proc { |&nil| }.parameters.should == [[:noblock]] + RUBY + end + end end diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb index cf0931b6884d33..e0f7123b7b7b85 100644 --- a/spec/ruby/language/block_spec.rb +++ b/spec/ruby/language/block_spec.rb @@ -1071,4 +1071,15 @@ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, ** all_kwrest(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1 end end + + ruby_version_is "3.4" do + it "works alongside disallowed block argument" do + no_block = eval <<-EOF + proc {|arg1, &nil| arg1} + EOF + + no_block.call(:a).should == :a + -> { no_block.call(:a) {} }.should raise_error(ArgumentError, 'no block accepted') + end + end end diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index 9abe4cde204ccd..3fbec4b0e2bc17 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -1127,6 +1127,18 @@ def m(a, b = nil, c = nil, d, e: nil, **f) result = m(1, {foo: :bar}) result.should == [1, nil, nil, {foo: :bar}, nil, {}] end + + ruby_version_is "3.4" do + evaluate <<-ruby do + def m(a, &nil); a end; + ruby + + m(1).should == 1 + + -> { m(1) {} }.should raise_error(ArgumentError, 'no block accepted') + -> { m(1, &proc {}) }.should raise_error(ArgumentError, 'no block accepted') + end + end end context 'when passing an empty keyword splat to a method that does not accept keywords' do diff --git a/test/prism/result/source_location_test.rb b/test/prism/result/source_location_test.rb index ca74b36e6f86a7..962ecb0a5d65f9 100644 --- a/test/prism/result/source_location_test.rb +++ b/test/prism/result/source_location_test.rb @@ -650,6 +650,10 @@ def test_NilNode assert_location(NilNode, "nil") end + def test_NoBlockParameterNode + assert_location(NoBlockParameterNode, "def foo(&nil); end", 8...12) { |node| node.parameters.block } + end + def test_NoKeywordsParameterNode assert_location(NoKeywordsParameterNode, "def foo(**nil); end", 8...13) { |node| node.parameters.keyword_rest } end diff --git a/test/prism/ruby/parameters_signature_test.rb b/test/prism/ruby/parameters_signature_test.rb index 9256bcc070382b..a054f0afb773a5 100644 --- a/test/prism/ruby/parameters_signature_test.rb +++ b/test/prism/ruby/parameters_signature_test.rb @@ -50,6 +50,10 @@ def test_nokey assert_parameters([[:nokey]], "**nil") end + def test_noblock + assert_parameters([[:noblock]], "&nil") + end + def test_keyrest_anonymous assert_parameters([[:keyrest, :**]], "**") end diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index c946d588c10bd2..f4009c27c30dd4 100644 --- a/test/ruby/test_iseq.rb +++ b/test/ruby/test_iseq.rb @@ -633,6 +633,17 @@ def foo(**nil); end assert_equal([[:nokey]], iseq.eval.singleton_method(:foo).parameters) end + def test_to_binary_dumps_noblock + iseq = assert_iseq_to_binary(<<-RUBY) + o = Object.new + class << o + def foo(&nil); end + end + o + RUBY + assert_equal([[:noblock]], iseq.eval.singleton_method(:foo).parameters) + end + def test_to_binary_line_info assert_iseq_to_binary("#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #14660]').eval begin; diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index a355f86a175438..e6af8fd1eaeecc 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -32,6 +32,7 @@ def mk6(a, b = nil, c, **o) nil && o end def mk7(a, b = nil, *c, d, **o) nil && o end def mk8(a, b = nil, *c, d, e:, f: nil, **o) nil && o end def mnk(**nil) end + def mnb(&nil) end def mf(...) end class Base @@ -577,6 +578,7 @@ def obj.respond_to_missing?(id, *) define_method(:pmk7) {|a, b = nil, *c, d, **o|} define_method(:pmk8) {|a, b = nil, *c, d, e:, f: nil, **o|} define_method(:pmnk) {|**nil|} + define_method(:pmnb) {|&nil|} def test_bound_parameters assert_equal([], method(:m0).parameters) @@ -600,6 +602,7 @@ def test_bound_parameters assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:mk7).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:mk8).parameters) assert_equal([[:nokey]], method(:mnk).parameters) + assert_equal([[:noblock]], method(:mnb).parameters) # pending assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], method(:mf).parameters) end @@ -626,6 +629,7 @@ def test_unbound_parameters assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:mk7).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:mk8).parameters) assert_equal([[:nokey]], self.class.instance_method(:mnk).parameters) + assert_equal([[:noblock]], self.class.instance_method(:mnb).parameters) # pending assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], self.class.instance_method(:mf).parameters) end @@ -651,6 +655,7 @@ def test_bmethod_bound_parameters assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:pmk7).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:pmk8).parameters) assert_equal([[:nokey]], method(:pmnk).parameters) + assert_equal([[:noblock]], method(:pmnb).parameters) end def test_bmethod_unbound_parameters @@ -675,6 +680,7 @@ def test_bmethod_unbound_parameters assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:pmk7).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:pmk8).parameters) assert_equal([[:nokey]], self.class.instance_method(:pmnk).parameters) + assert_equal([[:noblock]], self.class.instance_method(:pmnb).parameters) end def test_hidden_parameters diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 8aa3a54084ab8e..08e04c7a8f00c8 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -193,6 +193,49 @@ def test_argument_forwarding_with_anon_rest_kwrest_and_block assert_syntax_error("def f(...); g(&); end", /no anonymous block parameter/) end + def test_no_block_argument_in_method + assert_valid_syntax("def f(&nil) end") + assert_valid_syntax("def f(a, &nil) end") + assert_valid_syntax("def f(*rest, &nil) end") + assert_valid_syntax("def f(*rest, p, &nil) end") + assert_valid_syntax("def f(a, *rest, &nil) end") + assert_valid_syntax("def f(a, *rest, p, &nil) end") + assert_valid_syntax("def f(a, k: nil, &nil) end") + assert_valid_syntax("def f(a, k: nil, **kw, &nil) end") + assert_valid_syntax("def f(a, *rest, k: nil, &nil) end") + assert_valid_syntax("def f(a, *rest, k: nil, **kw, &nil) end") + assert_valid_syntax("def f(a, *rest, p, k: nil, &nil) end") + assert_valid_syntax("def f(a, *rest, p, k: nil, **kw, &nil) end") + + obj = Object.new + obj.instance_eval "def f(&nil) end" + assert_raise_with_message(ArgumentError, /block accepted/) {obj.f {}} + assert_raise_with_message(ArgumentError, /block accepted/) {obj.f(&proc {})} + end + + def test_no_block_argument_in_block + assert_valid_syntax("proc do |&nil| end") + assert_valid_syntax("proc do |a, &nil| end") + assert_valid_syntax("proc do |*rest, &nil| end") + assert_valid_syntax("proc do |*rest, p, &nil| end") + assert_valid_syntax("proc do |a, *rest, &nil| end") + assert_valid_syntax("proc do |a, *rest, p, &nil| end") + assert_valid_syntax("proc do |a, k: nil, &nil| end") + assert_valid_syntax("proc do |a, k: nil, **kw, &nil| end") + assert_valid_syntax("proc do |a, *rest, k: nil, &nil| end") + assert_valid_syntax("proc do |a, *rest, k: nil, **kw, &nil| end") + assert_valid_syntax("proc do |a, *rest, p, k: nil, &nil| end") + assert_valid_syntax("proc do |a, *rest, p, k: nil, **kw, &nil| end") + + pr = eval "proc {|&nil|}" + assert_nil(pr.call) + assert_raise_with_message(ArgumentError, /block accepted/) {pr.call {}} + pr = eval "proc {|a, &nil| a}" + assert_nil(pr.call) + assert_equal(1, pr.call(1)) + assert_raise_with_message(ArgumentError, /block accepted/) {pr.call {}} + end + def test_newline_in_block_parameters bug = '[ruby-dev:45292]' ["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params| diff --git a/vm_args.c b/vm_args.c index b4a8fcb8fb34f2..7147dde034bc71 100644 --- a/vm_args.c +++ b/vm_args.c @@ -12,6 +12,8 @@ NORETURN(static void raise_argument_error(rb_execution_context_t *ec, const rb_i NORETURN(static void argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int miss_argc, const int min_argc, const int max_argc)); NORETURN(static void argument_kw_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const char *error, const VALUE keys)); VALUE rb_keyword_error_new(const char *error, VALUE keys); /* class.c */ +static VALUE set_error_backtrace(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VALUE exc); + static VALUE method_missing(rb_execution_context_t *ec, VALUE obj, ID id, int argc, const VALUE *argv, enum method_missing_reason call_status, int kw_splat); const rb_callable_method_entry_t *rb_resolve_refined_method_callable(VALUE refinements, const rb_callable_method_entry_t *me); @@ -868,7 +870,15 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co argument_kw_error(ec, iseq, "unknown", rb_hash_keys(keyword_hash)); } - if (ISEQ_BODY(iseq)->param.flags.has_block) { + if (ISEQ_BODY(iseq)->param.flags.accepts_no_block) { + VALUE given_block; + args_setup_block_parameter(ec, calling, &given_block); + if (!NIL_P(given_block)) { + VALUE exc = rb_exc_new_cstr(rb_eArgError, "no block accepted"); + rb_exc_raise(set_error_backtrace(ec, iseq, exc)); + } + } + else if (ISEQ_BODY(iseq)->param.flags.has_block) { if (ISEQ_BODY(iseq)->local_iseq == iseq) { /* Do nothing */ } @@ -890,8 +900,8 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co return opt_pc; } -static void -raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VALUE exc) +static VALUE +set_error_backtrace(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VALUE exc) { VALUE at; @@ -910,6 +920,13 @@ raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VA rb_ivar_set(exc, idBt_locations, at); rb_exc_set_backtrace(exc, at); + return exc; +} + +static void +raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const VALUE exc) +{ + set_error_backtrace(ec, iseq, exc); rb_exc_raise(exc); } diff --git a/vm_core.h b/vm_core.h index 2a75332820ec66..1c03e5875b1e7b 100644 --- a/vm_core.h +++ b/vm_core.h @@ -419,6 +419,7 @@ struct rb_iseq_constant_body { unsigned int anon_kwrest: 1; unsigned int use_block: 1; unsigned int forwardable: 1; + unsigned int accepts_no_block: 1; } flags; unsigned int size; diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 9293fd6231e526..9591b25b403d29 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2614,7 +2614,8 @@ rb_simple_iseq_p(const rb_iseq_t *iseq) ISEQ_BODY(iseq)->param.flags.has_kwrest == FALSE && ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg == FALSE && ISEQ_BODY(iseq)->param.flags.forwardable == FALSE && - ISEQ_BODY(iseq)->param.flags.has_block == FALSE; + ISEQ_BODY(iseq)->param.flags.has_block == FALSE && + ISEQ_BODY(iseq)->param.flags.accepts_no_block == FALSE; } bool @@ -2627,7 +2628,8 @@ rb_iseq_only_optparam_p(const rb_iseq_t *iseq) ISEQ_BODY(iseq)->param.flags.has_kwrest == FALSE && ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg == FALSE && ISEQ_BODY(iseq)->param.flags.forwardable == FALSE && - ISEQ_BODY(iseq)->param.flags.has_block == FALSE; + ISEQ_BODY(iseq)->param.flags.has_block == FALSE && + ISEQ_BODY(iseq)->param.flags.accepts_no_block == FALSE; } bool