diff --git a/compile.c b/compile.c index b345c23691..ae127e59c9 100644 --- a/compile.c +++ b/compile.c @@ -8848,6 +8848,8 @@ compile_op_asgn1(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node int asgnflag = 0; ID id = RNODE_OP_ASGN1(node)->nd_mid; int boff = 0; + int keyword_len = 0; + struct rb_callinfo_kwarg *keywords = NULL; /* * a[x] (op)= y @@ -8885,12 +8887,28 @@ compile_op_asgn1(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node boff = 1; /* fall through */ default: - argc = setup_args(iseq, ret, RNODE_OP_ASGN1(node)->nd_index, &flag, NULL); + argc = setup_args(iseq, ret, RNODE_OP_ASGN1(node)->nd_index, &flag, &keywords); + if (flag & VM_CALL_KW_SPLAT) { + if (boff) { + ADD_INSN(ret, node, splatkw); + } + else { + /* Make sure to_hash is only called once and not twice */ + ADD_INSN(ret, node, dup); + ADD_INSN(ret, node, splatkw); + ADD_INSN(ret, node, pop); + } + } CHECK(!NIL_P(argc)); } - ADD_INSN1(ret, node, dupn, FIXNUM_INC(argc, 1 + boff)); + int dup_argn = FIX2INT(argc) + 1 + boff; + if (keywords) { + keyword_len = keywords->keyword_len; + dup_argn += keyword_len; + } + ADD_INSN1(ret, node, dupn, INT2FIX(dup_argn)); flag |= asgnflag; - ADD_SEND_WITH_FLAG(ret, node, idAREF, argc, INT2FIX(flag)); + ADD_SEND_R(ret, node, idAREF, argc, NULL, INT2FIX(flag), keywords); if (id == idOROP || id == idANDOP) { /* a[x] ||= y or a[x] &&= y @@ -8915,62 +8933,114 @@ compile_op_asgn1(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node CHECK(COMPILE(ret, "NODE_OP_ASGN1 nd_rvalue: ", RNODE_OP_ASGN1(node)->nd_rvalue)); if (!popped) { - ADD_INSN1(ret, node, setn, FIXNUM_INC(argc, 2+boff)); + ADD_INSN1(ret, node, setn, INT2FIX(dup_argn+1)); } if (flag & VM_CALL_ARGS_SPLAT) { - ADD_INSN1(ret, node, newarray, INT2FIX(1)); - if (boff > 0) { - ADD_INSN1(ret, node, dupn, INT2FIX(3)); + if (flag & VM_CALL_KW_SPLAT) { + ADD_INSN1(ret, node, topn, INT2FIX(2 + boff)); ADD_INSN(ret, node, swap); + ADD_INSN1(ret, node, newarray, INT2FIX(1)); + ADD_INSN(ret, node, concatarray); + ADD_INSN1(ret, node, setn, INT2FIX(2 + boff)); ADD_INSN(ret, node, pop); } - ADD_INSN(ret, node, concatarray); + else { + ADD_INSN1(ret, node, newarray, INT2FIX(1)); + if (boff > 0) { + ADD_INSN1(ret, node, dupn, INT2FIX(3)); + ADD_INSN(ret, node, swap); + ADD_INSN(ret, node, pop); + } + ADD_INSN(ret, node, concatarray); + if (boff > 0) { + ADD_INSN1(ret, node, setn, INT2FIX(3)); + ADD_INSN(ret, node, pop); + ADD_INSN(ret, node, pop); + } + } + ADD_SEND_R(ret, node, idASET, argc, NULL, INT2FIX(flag), keywords); + } + else if (flag & VM_CALL_KW_SPLAT) { if (boff > 0) { + ADD_INSN1(ret, node, topn, INT2FIX(2)); + ADD_INSN(ret, node, swap); ADD_INSN1(ret, node, setn, INT2FIX(3)); ADD_INSN(ret, node, pop); - ADD_INSN(ret, node, pop); } - ADD_SEND_WITH_FLAG(ret, node, idASET, argc, INT2FIX(flag)); + ADD_INSN(ret, node, swap); + ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); + } + else if (keyword_len) { + ADD_INSN1(ret, node, opt_reverse, INT2FIX(keyword_len+boff+1)); + ADD_INSN1(ret, node, opt_reverse, INT2FIX(keyword_len+boff+0)); + ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); } else { if (boff > 0) ADD_INSN(ret, node, swap); - ADD_SEND_WITH_FLAG(ret, node, idASET, FIXNUM_INC(argc, 1), INT2FIX(flag)); + ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); } ADD_INSN(ret, node, pop); ADD_INSNL(ret, node, jump, lfin); ADD_LABEL(ret, label); if (!popped) { - ADD_INSN1(ret, node, setn, FIXNUM_INC(argc, 2+boff)); + ADD_INSN1(ret, node, setn, INT2FIX(dup_argn+1)); } - ADD_INSN1(ret, node, adjuststack, FIXNUM_INC(argc, 2+boff)); + ADD_INSN1(ret, node, adjuststack, INT2FIX(dup_argn+1)); ADD_LABEL(ret, lfin); } else { CHECK(COMPILE(ret, "NODE_OP_ASGN1 nd_rvalue: ", RNODE_OP_ASGN1(node)->nd_rvalue)); ADD_SEND(ret, node, id, INT2FIX(1)); if (!popped) { - ADD_INSN1(ret, node, setn, FIXNUM_INC(argc, 2+boff)); + ADD_INSN1(ret, node, setn, INT2FIX(dup_argn+1)); } if (flag & VM_CALL_ARGS_SPLAT) { - ADD_INSN1(ret, node, newarray, INT2FIX(1)); - if (boff > 0) { - ADD_INSN1(ret, node, dupn, INT2FIX(3)); + if (flag & VM_CALL_KW_SPLAT) { + ADD_INSN1(ret, node, topn, INT2FIX(2 + boff)); ADD_INSN(ret, node, swap); + ADD_INSN1(ret, node, newarray, INT2FIX(1)); + ADD_INSN(ret, node, concatarray); + ADD_INSN1(ret, node, setn, INT2FIX(2 + boff)); ADD_INSN(ret, node, pop); } - ADD_INSN(ret, node, concatarray); + else { + ADD_INSN1(ret, node, newarray, INT2FIX(1)); + if (boff > 0) { + ADD_INSN1(ret, node, dupn, INT2FIX(3)); + ADD_INSN(ret, node, swap); + ADD_INSN(ret, node, pop); + } + ADD_INSN(ret, node, concatarray); + if (boff > 0) { + ADD_INSN1(ret, node, setn, INT2FIX(3)); + ADD_INSN(ret, node, pop); + ADD_INSN(ret, node, pop); + } + } + ADD_SEND_R(ret, node, idASET, argc, NULL, INT2FIX(flag), keywords); + } + else if (flag & VM_CALL_KW_SPLAT) { if (boff > 0) { + ADD_INSN1(ret, node, topn, INT2FIX(2)); + ADD_INSN(ret, node, swap); ADD_INSN1(ret, node, setn, INT2FIX(3)); ADD_INSN(ret, node, pop); - ADD_INSN(ret, node, pop); } - ADD_SEND_WITH_FLAG(ret, node, idASET, argc, INT2FIX(flag)); + ADD_INSN(ret, node, swap); + ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); + } + else if (keyword_len) { + ADD_INSN(ret, node, dup); + ADD_INSN1(ret, node, opt_reverse, INT2FIX(keyword_len+boff+2)); + ADD_INSN1(ret, node, opt_reverse, INT2FIX(keyword_len+boff+1)); + ADD_INSN(ret, node, pop); + ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); } else { if (boff > 0) ADD_INSN(ret, node, swap); - ADD_SEND_WITH_FLAG(ret, node, idASET, FIXNUM_INC(argc, 1), INT2FIX(flag)); + ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); } ADD_INSN(ret, node, pop); } diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb index 92c5e604b5..f17d5848b9 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -121,6 +121,166 @@ class TestCall < Test::Unit::TestCase assert_equal([10], a(10)) end + def test_call_op_asgn_keywords + h = Class.new do + attr_reader :get, :set + def v; yield; [*@get, *@set] end + def [](*a, **b, &c) @get = [a, b, c]; @set = []; 3 end + def []=(*a, **b, &c) @set = [a, b, c] end + end.new + + a = [] + kw = {} + b = lambda{} + + # +=, without block, non-popped + assert_equal([[], {}, nil, [4], {}, nil], h.v{h[**kw] += 1}) + assert_equal([[0], {}, nil, [0, 4], {}, nil], h.v{h[0, **kw] += 1}) + assert_equal([[0], {}, nil, [0, 4], {}, nil], h.v{h[0, *a, **kw] += 1}) + assert_equal([[], {kw: 5}, nil, [4], {kw: 5}, nil], h.v{h[kw: 5] += 1}) + assert_equal([[], {kw: 5, a: 2}, nil, [4], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] += 1}) + assert_equal([[], {kw: 5, a: 2}, nil, [4], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] += 1}) + assert_equal([[0], {kw: 5, a: 2}, nil, [0, 4], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] += 1}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil, [0, 4], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] += 1}) + + # +=, with block, non-popped + assert_equal([[], {}, b, [4], {}, b], h.v{h[**kw, &b] += 1}) + assert_equal([[0], {}, b, [0, 4], {}, b], h.v{h[0, **kw, &b] += 1}) + assert_equal([[0], {}, b, [0, 4], {}, b], h.v{h[0, *a, **kw, &b] += 1}) + assert_equal([[], {kw: 5}, b, [4], {kw: 5}, b], h.v{h[kw: 5, &b] += 1}) + assert_equal([[], {kw: 5, a: 2}, b, [4], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] += 1}) + assert_equal([[], {kw: 5, a: 2}, b, [4], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] += 1}) + assert_equal([[0], {kw: 5, a: 2}, b, [0, 4], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] += 1}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b, [0, 4], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] += 1}) + + # +=, without block, popped + assert_equal([[], {}, nil, [4], {}, nil], h.v{h[**kw] += 1; nil}) + assert_equal([[0], {}, nil, [0, 4], {}, nil], h.v{h[0, **kw] += 1; nil}) + assert_equal([[0], {}, nil, [0, 4], {}, nil], h.v{h[0, *a, **kw] += 1; nil}) + assert_equal([[], {kw: 5}, nil, [4], {kw: 5}, nil], h.v{h[kw: 5] += 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil, [4], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] += 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil, [4], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] += 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, nil, [0, 4], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] += 1; nil}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil, [0, 4], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] += 1; nil}) + + # +=, with block, popped + assert_equal([[], {}, b, [4], {}, b], h.v{h[**kw, &b] += 1; nil}) + assert_equal([[0], {}, b, [0, 4], {}, b], h.v{h[0, **kw, &b] += 1; nil}) + assert_equal([[0], {}, b, [0, 4], {}, b], h.v{h[0, *a, **kw, &b] += 1; nil}) + assert_equal([[], {kw: 5}, b, [4], {kw: 5}, b], h.v{h[kw: 5, &b] += 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b, [4], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] += 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b, [4], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] += 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, b, [0, 4], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] += 1; nil}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b, [0, 4], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] += 1; nil}) + + # &&=, without block, non-popped + assert_equal([[], {}, nil, [1], {}, nil], h.v{h[**kw] &&= 1}) + assert_equal([[0], {}, nil, [0, 1], {}, nil], h.v{h[0, **kw] &&= 1}) + assert_equal([[0], {}, nil, [0, 1], {}, nil], h.v{h[0, *a, **kw] &&= 1}) + assert_equal([[], {kw: 5}, nil, [1], {kw: 5}, nil], h.v{h[kw: 5] &&= 1}) + assert_equal([[], {kw: 5, a: 2}, nil, [1], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] &&= 1}) + assert_equal([[], {kw: 5, a: 2}, nil, [1], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] &&= 1}) + assert_equal([[0], {kw: 5, a: 2}, nil, [0, 1], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] &&= 1}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil, [0, 1], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1}) + + # &&=, with block, non-popped + assert_equal([[], {}, b, [1], {}, b], h.v{h[**kw, &b] &&= 1}) + assert_equal([[0], {}, b, [0, 1], {}, b], h.v{h[0, **kw, &b] &&= 1}) + assert_equal([[0], {}, b, [0, 1], {}, b], h.v{h[0, *a, **kw, &b] &&= 1}) + assert_equal([[], {kw: 5}, b, [1], {kw: 5}, b], h.v{h[kw: 5, &b] &&= 1}) + assert_equal([[], {kw: 5, a: 2}, b, [1], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] &&= 1}) + assert_equal([[], {kw: 5, a: 2}, b, [1], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] &&= 1}) + assert_equal([[0], {kw: 5, a: 2}, b, [0, 1], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] &&= 1}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b, [0, 1], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] &&= 1}) + + # &&=, without block, popped + assert_equal([[], {}, nil, [1], {}, nil], h.v{h[**kw] &&= 1; nil}) + assert_equal([[0], {}, nil, [0, 1], {}, nil], h.v{h[0, **kw] &&= 1; nil}) + assert_equal([[0], {}, nil, [0, 1], {}, nil], h.v{h[0, *a, **kw] &&= 1; nil}) + assert_equal([[], {kw: 5}, nil, [1], {kw: 5}, nil], h.v{h[kw: 5] &&= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil, [1], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] &&= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil, [1], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] &&= 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, nil, [0, 1], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] &&= 1; nil}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil, [0, 1], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1; nil}) + + # &&=, with block, popped + assert_equal([[], {}, b, [1], {}, b], h.v{h[**kw, &b] &&= 1; nil}) + assert_equal([[0], {}, b, [0, 1], {}, b], h.v{h[0, **kw, &b] &&= 1; nil}) + assert_equal([[0], {}, b, [0, 1], {}, b], h.v{h[0, *a, **kw, &b] &&= 1; nil}) + assert_equal([[], {kw: 5}, b, [1], {kw: 5}, b], h.v{h[kw: 5, &b] &&= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b, [1], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] &&= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b, [1], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] &&= 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, b, [0, 1], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] &&= 1; nil}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b, [0, 1], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] &&= 1; nil}) + + # ||=, without block, non-popped + assert_equal([[], {}, nil], h.v{h[**kw] ||= 1}) + assert_equal([[0], {}, nil], h.v{h[0, **kw] ||= 1}) + assert_equal([[0], {}, nil], h.v{h[0, *a, **kw] ||= 1}) + assert_equal([[], {kw: 5}, nil], h.v{h[kw: 5] ||= 1}) + assert_equal([[], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] ||= 1}) + assert_equal([[], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] ||= 1}) + assert_equal([[0], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] ||= 1}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1}) + + # ||=, with block, non-popped + assert_equal([[], {}, b], h.v{h[**kw, &b] ||= 1}) + assert_equal([[0], {}, b], h.v{h[0, **kw, &b] ||= 1}) + assert_equal([[0], {}, b], h.v{h[0, *a, **kw, &b] ||= 1}) + assert_equal([[], {kw: 5}, b], h.v{h[kw: 5, &b] ||= 1}) + assert_equal([[], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] ||= 1}) + assert_equal([[], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] ||= 1}) + assert_equal([[0], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] ||= 1}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] ||= 1}) + + # ||=, without block, popped + assert_equal([[], {}, nil], h.v{h[**kw] ||= 1; nil}) + assert_equal([[0], {}, nil], h.v{h[0, **kw] ||= 1; nil}) + assert_equal([[0], {}, nil], h.v{h[0, *a, **kw] ||= 1; nil}) + assert_equal([[], {kw: 5}, nil], h.v{h[kw: 5] ||= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] ||= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, nil], h.v{h[kw: 5, a: 2] ||= 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, nil], h.v{h[0, kw: 5, a: 2] ||= 1; nil}) + assert_equal([[0], {kw: 5, a: 2, nil: 3}, nil], h.v{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1; nil}) + + # ||=, with block, popped + assert_equal([[], {}, b], h.v{h[**kw, &b] ||= 1; nil}) + assert_equal([[0], {}, b], h.v{h[0, **kw, &b] ||= 1; nil}) + assert_equal([[0], {}, b], h.v{h[0, *a, **kw, &b] ||= 1; nil}) + assert_equal([[], {kw: 5}, b], h.v{h[kw: 5, &b] ||= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] ||= 1; nil}) + assert_equal([[], {kw: 5, a: 2}, b], h.v{h[kw: 5, a: 2, &b] ||= 1; nil}) + assert_equal([[0], {kw: 5, a: 2}, b], h.v{h[0, kw: 5, a: 2, &b] ||= 1; nil}) + assert_equal([[0], {kw: 5, a: 2, b: 3}, b], h.v{h[0, *a, kw: 5, a: 2, b: 3, &b] ||= 1; nil}) + + end + + def test_kwsplat_block_order_op_asgn + o = Object.new + ary = [] + o.define_singleton_method(:to_a) {ary << :to_a; []} + o.define_singleton_method(:to_hash) {ary << :to_hash; {}} + o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}} + + def o.[](...) 2 end + def o.[]=(...) end + + o[kw: 1] += 1 + assert_equal([], ary) + + o[**o] += 1 + assert_equal([:to_hash], ary) + + ary.clear + o[**o, &o] += 1 + # to_proc called twice because no VM instruction for coercing to proc + assert_equal([:to_hash, :to_proc, :to_proc], ary) + + ary.clear + o[*o, **o, &o] += 1 + assert_equal([:to_a, :to_hash, :to_proc, :to_proc], ary) + end + def test_call_splat_order bug12860 = '[ruby-core:77701] [Bug# 12860]' ary = [1, 2]