parse.y: implement &nil in formal parameters

`&nil` is recently introduced in CRuby to explicitly declare that
a method does not accept a block. When a block is passed,
ArgumentError "no block accepted" is raised. This is analogous to
`**nil` for keyword arguments.

The noblock flag is encoded in bit 23 of OP_ENTER's aspec operand
(24=n1:m5:o5:r1:m5:k5:d1:b1), avoiding the need for a new opcode.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-02-19 16:54:17 +09:00
parent 337cf4bfd5
commit b07518e85c
8 changed files with 2826 additions and 2846 deletions
+5
View File
@@ -922,6 +922,11 @@ MRB_API struct RClass* mrb_define_module_under_id(mrb_state *mrb, struct RClass
*/
#define MRB_ARGS_BLOCK() ((mrb_aspec)1)
/**
* Function does not accept a block (&nil)
*/
#define MRB_ARGS_NOBLOCK() ((mrb_aspec)(1 << 23))
/**
* Function accepts any number of arguments
*/
+1 -1
View File
@@ -69,7 +69,7 @@ OPCODE(CALL, Z) /* self.call(*, **, &) (But overlay the current cal
OPCODE(BLKCALL, BB) /* R[a] = R[a].call(R[a+1],... ,R[a+b]); direct block call */
OPCODE(SUPER, BB) /* R[a] = super(R[a+1],... ,R[a+b+1]) */
OPCODE(ARGARY, BS) /* R[a] = argument array (16=m5:r1:m5:d1:lv4) */
OPCODE(ENTER, W) /* arg setup according to flags (23=m5:o5:r1:m5:k5:d1:b1) */
OPCODE(ENTER, W) /* arg setup according to flags (24=n1:m5:o5:r1:m5:k5:d1:b1) */
OPCODE(KEY_P, BB) /* R[a] = kdict.key?(Syms[b]) */
OPCODE(KEYEND, Z) /* raise unless kdict.empty? */
OPCODE(KARG, BB) /* R[a] = kdict[Syms[b]]; kdict.delete(Syms[b]) */
+1
View File
@@ -72,6 +72,7 @@ struct RProc {
#define MRB_ASPEC_KEY(a) (((a) >> 2) & 0x1f)
#define MRB_ASPEC_KDICT(a) (((a) >> 1) & 0x1)
#define MRB_ASPEC_BLOCK(a) ((a) & 1)
#define MRB_ASPEC_NOBLOCK(a) (((a) >> 23) & 0x1)
#define MRB_PROC_CFUNC_FL 128
#define MRB_PROC_CFUNC_P(p) (((p)->flags & MRB_PROC_CFUNC_FL) != 0)
+6 -3
View File
@@ -2401,13 +2401,16 @@ lambda_body(codegen_scope *s, node *locals, struct mrb_ast_args *args, node *bod
/* keyword arguments */
ka = args->keyword_args ? node_len(args->keyword_args) : 0;
kd = args->kwrest_arg ? 1 : 0;
ba = args->block_arg ? 1 : 0;
/* &nil: no block accepted (noblock flag in aspec) */
mrb_bool noblock = args->block_arg == MRB_SYM(nil);
ba = (args->block_arg && !noblock) ? 1 : 0;
if (ma > 0x1f || oa > 0x1f || pa > 0x1f || ka > 0x1f) {
codegen_error(s, "too many formal arguments");
}
/* (23bits = 5:5:1:5:5:1:1) */
a = MRB_ARGS_REQ(ma)
/* (24bits = 1:5:5:1:5:5:1:1) */
a = (noblock ? MRB_ARGS_NOBLOCK() : 0)
| MRB_ARGS_REQ(ma)
| MRB_ARGS_OPT(oa)
| (ra ? MRB_ARGS_REST() : 0)
| MRB_ARGS_POST(pa)
+7 -1
View File
@@ -1215,7 +1215,7 @@ new_args_tail(parser_state *p, node *kws, mrb_sym kwrest, mrb_sym blk)
}
local_add_blk(p);
if (blk) local_add_f(p, blk);
if (blk && blk != MRB_SYM(nil)) local_add_f(p, blk);
/* allocate register for keywords arguments */
/* order is for Proc#parameters */
@@ -4861,6 +4861,10 @@ f_block_arg : blkarg_mark tIDENTIFIER
{
$$ = $2;
}
| blkarg_mark keyword_nil
{
$$ = MRB_SYM(nil);
}
| blkarg_mark
{
$$ = intern_op(and);
@@ -8047,6 +8051,8 @@ dump_args(mrb_state *mrb, struct mrb_ast_args *args, int offset, uint16_t lineno
dump_prefix(offset, lineno);
if (blk == MRB_OPSYM(and))
printf("blk=&\n");
else if (blk == MRB_SYM(nil))
printf("blk=&nil\n");
else
printf("blk=&%s\n", mrb_sym_name(mrb, blk));
}
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -374,14 +374,15 @@ codedump(mrb_state *mrb, const mrb_irep *irep, FILE *out)
print_lv_a(mrb, irep, a, out);
break;
CASE(OP_ENTER, W):
fprintf(out, "ENTER\t\t%d:%d:%d:%d:%d:%d:%d (0x%x)\n",
fprintf(out, "ENTER\t\t%d:%d:%d:%d:%d:%d:%d:%d (0x%x)\n",
MRB_ASPEC_REQ(a),
MRB_ASPEC_OPT(a),
MRB_ASPEC_REST(a),
MRB_ASPEC_POST(a),
MRB_ASPEC_KEY(a),
MRB_ASPEC_KDICT(a),
MRB_ASPEC_BLOCK(a), a);
MRB_ASPEC_BLOCK(a),
MRB_ASPEC_NOBLOCK(a), a);
break;
CASE(OP_KEY_P, BB):
fprintf(out, "KEY_P\t\tR%d\t:%s", a, mrb_sym_dump(mrb, irep->syms[b]));
+6
View File
@@ -2558,6 +2558,12 @@ RETRY_TRY_BLOCK:
mrb_value * const argv0 = argv;
mrb_value blk = regs[ci_bidx(ci)];
/* &nil: reject block */
if (MRB_ASPEC_NOBLOCK(a) && !mrb_nil_p(blk)) {
RAISE_LIT(mrb, E_ARGUMENT_ERROR, "no block accepted");
}
mrb_value kdict = mrb_nil_value();
/* keyword arguments */