Project

General

Profile

Bug #16154 ยป delegate-keyword-argument-separation.patch

jeremyevans0 (Jeremy Evans), 09/09/2019 02:33 AM

View differences:

lib/delegate.rb
75 75
  #
76 76
  # Handles the magic of delegation through \_\_getobj\_\_.
77 77
  #
78
  def method_missing(m, *args, &block)
78
  pass_positional_hash def method_missing(m, *args, **kw, &block)
79 79
    r = true
80 80
    target = self.__getobj__ {r = false}
81 81

  
82 82
    if r && target.respond_to?(m)
83
      target.__send__(m, *args, &block)
83
      target.__send__(m, *args, **kw, &block)
84 84
    elsif ::Kernel.method_defined?(m) || ::Kernel.private_method_defined?(m)
85 85
      ::Kernel.instance_method(m).bind(self).(*args, &block)
86 86
    else
87
      super(m, *args, &block)
87
      super
88 88
    end
89 89
  end
90 90

  
mjit_compile.c
217 217
    fprintf(f, "    calling.argc = %d;\n", inline_context->orig_argc);
218 218
    fprintf(f, "    calling.recv = reg_cfp->self;\n");
219 219
    fprintf(f, "    reg_cfp->self = orig_self;\n");
220
    fprintf(f, "    vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d);\n\n",
220
    fprintf(f, "    vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d, 0);\n\n",
221 221
            inline_context->me, inline_context->param_size, inline_context->local_size); // fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
222 222

  
223 223
    // Start usual cancel from here.
test/ruby/test_keyword.rb
177 177
    assert_equal(["bar", 111111], f[str: "bar", num: 111111])
178 178
  end
179 179

  
180
  def test_pass_positional_hash
181
    c = Class.new do
182
      pass_positional_hash def foo(x, *args, **kw)
183
        send(x ? :bar : :baz, *args, **kw)
184
      end
185

  
186
      def bar(*args, **kw)
187
        [args, kw]
188
      end
189

  
190
      def baz(*args)
191
        args
192
      end
193
    end
194
    o = c.new
195

  
196
    assert_equal([[1], {:a=>1}], o.foo(true, 1, :a=>1))
197
    assert_equal([1, {:a=>1}], o.foo(false, 1, :a=>1))
198

  
199
    assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
200
      assert_equal([[1], {:a=>1}], o.foo(true, 1, {:a=>1}))
201
    end
202
    assert_equal([1, {:a=>1}], o.foo(false, 1, {:a=>1}))
203

  
204
    assert_warn(/Skipping set of pass positional hash flag for baz \(method not defined in Ruby or method does not accept keyword arguments\)/) do
205
      assert_nil(c.send(:pass_positional_hash, :baz))
206
    end
207

  
208
    sc = Class.new(c)
209
    assert_warn(/Skipping set of pass positional hash flag for bar \(can only set in method defining module\)/) do
210
      sc.send(:pass_positional_hash, :bar)
211
    end
212
    m = Module.new
213
    assert_warn(/Skipping set of pass positional hash flag for system \(can only set in method defining module\)/) do
214
      m.send(:pass_positional_hash, :system)
215
    end
216

  
217
    assert_raise(NameError) { c.send(:pass_positional_hash, "a5e36ccec4f5080a1d5e63f8") }
218
    assert_raise(NameError) { c.send(:pass_positional_hash, :quux) }
219

  
220
    c.freeze
221
    assert_raise(FrozenError) { c.send(:pass_positional_hash, :baz) }
222
  end
223

  
180 224
  def test_regular_kwsplat
181 225
    kw = {}
182 226
    h = {:a=>1}
test/test_delegate.rb
184 184
    end
185 185
  end
186 186

  
187
  def test_keyword_and_hash
188
    foo = Object.new
189
    def foo.bar(*args)
190
      args
191
    end
192
    def foo.foo(*args, **kw)
193
      [args, kw]
194
    end
195
    d = SimpleDelegator.new(foo)
196
    assert_equal([[], {}], d.foo)
197
    assert_equal([], d.bar)
198
    assert_equal([[], {:a=>1}], d.foo(:a=>1))
199
    assert_equal([{:a=>1}], d.bar(:a=>1))
200
    assert_warn(/The last argument is used as the keyword parameter.* for `foo'/m) do
201
      assert_equal([[], {:a=>1}], d.foo({:a=>1}))
202
    end
203
    assert_equal([{:a=>1}], d.bar({:a=>1}))
204
  end
205

  
187 206
  def test_private_method
188 207
    foo = Foo.new
189 208
    d = SimpleDelegator.new(foo)
tool/mk_call_iseq_optimized.rb
24 24
#{fname(param, local)}(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
25 25
{
26 26
    RB_DEBUG_COUNTER_INC(ccf_iseq_fix);
27
    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, #{param}, #{local});
27
    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, #{param}, #{local}, 0);
28 28
}
29 29

  
30 30
EOS
tool/ruby_vm/views/_mjit_compile_send.erb
68 68
% # JIT: Special CALL_METHOD. Bypass cc_copy->call and inline vm_call_iseq_setup_normal for vm_call_iseq_setup_func FASTPATH.
69 69
                fprintf(f, "        {\n");
70 70
                fprintf(f, "            VALUE v;\n");
71
                fprintf(f, "            vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d);\n",
71
                fprintf(f, "            vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d, 0);\n",
72 72
                        (VALUE)cc_copy->me, param_size, iseq->body->local_table_size); // fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
73 73
                if (iseq->body->catch_except_p) {
74 74
                    fprintf(f, "            VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n");
vm_args.c
642 642
setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * const iseq,
643 643
			 struct rb_calling_info *const calling,
644 644
			 const struct rb_call_info *ci,
645
			 VALUE * const locals, const enum arg_setup_type arg_setup_type)
645
                         VALUE * const locals, const enum arg_setup_type arg_setup_type,
646
                         int *frame_flag)
646 647
{
647 648
    const int min_argc = iseq->body->param.lead_num + iseq->body->param.post_num;
648 649
    const int max_argc = (iseq->body->param.flags.has_rest == FALSE) ? min_argc + iseq->body->param.opt_num : UNLIMITED_ARGUMENTS;
......
682 683
    args->argv = locals;
683 684
    args->rest_dupped = FALSE;
684 685

  
686
    if (VM_FRAME_PASS_POSITIONAL_HASH_P(ec->cfp)) {
687
        kw_flag &= ~VM_CALL_KW_SPLAT;
688
    }
689

  
685 690
    if (kw_flag & VM_CALL_KWARG) {
686 691
	args->kw_arg = ((struct rb_call_info_with_kwarg *)ci)->kw_arg;
687 692

  
......
808 813
                 * def foo(k:1) p [k]; end
809 814
                 * foo({k:42}) #=> 42
810 815
                 */
811
                rb_warn_last_hash_to_keyword(calling, ci, iseq);
816
                if (iseq->body->param.flags.pass_positional_hash) {
817
                    if (frame_flag) {
818
                        *frame_flag = VM_FRAME_FLAG_PASS_POSITIONAL_HASH;
819
                    }
820
                }
821
                else {
822
                    rb_warn_last_hash_to_keyword(calling, ci, iseq);
823
                }
812 824
                given_argc--;
813 825
            }
814 826
            else if (keyword_hash != Qnil) {
vm_core.h
358 358

  
359 359
	    unsigned int ambiguous_param0 : 1; /* {|a|} */
360 360
	    unsigned int accepts_no_kwarg : 1;
361
            unsigned int pass_positional_hash: 1; /* -- Remove In 3.0 -- */
361 362
	} flags;
362 363

  
363 364
	unsigned int size;
......
1138 1139

  
1139 1140
enum {
1140 1141
    /* Frame/Environment flag bits:
1141
     *   MMMM MMMM MMMM MMMM ____ FFFF FFFF EEEX (LSB)
1142
     *   MMMM MMMM MMMM MMMM ___F FFFF FFFF EEEX (LSB)
1142 1143
     *
1143 1144
     * X   : tag for GC marking (It seems as Fixnum)
1144 1145
     * EEE : 3 bits Env flags
1145
     * FF..: 8 bits Frame flags
1146
     * FF..: 9 bits Frame flags
1146 1147
     * MM..: 15 bits frame magic (to check frame corruption)
1147 1148
     */
1148 1149

  
......
1168 1169
    VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM = 0x0200,
1169 1170
    VM_FRAME_FLAG_CFRAME_KW = 0x0400,
1170 1171
    VM_FRAME_FLAG_CFRAME_EMPTY_KW = 0x0800, /* -- Remove In 3.0 -- */
1172
    VM_FRAME_FLAG_PASS_POSITIONAL_HASH = 0x1000, /* -- Remove In 3.0 -- */
1171 1173

  
1172 1174
    /* env flag */
1173 1175
    VM_ENV_FLAG_LOCAL       = 0x0002,
......
1234 1236
{
1235 1237
    return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_CFRAME_EMPTY_KW) != 0;
1236 1238
}
1239
static inline int
1240
VM_FRAME_PASS_POSITIONAL_HASH_P(const rb_control_frame_t *cfp)
1241
{
1242
    return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_PASS_POSITIONAL_HASH) != 0;
1243
}
1237 1244

  
1238 1245
static inline int
1239 1246
VM_FRAME_FINISHED_P(const rb_control_frame_t *cfp)
vm_insnhelper.c
1674 1674

  
1675 1675
#include "vm_args.c"
1676 1676

  
1677
static inline VALUE vm_call_iseq_setup_2(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, int opt_pc, int param_size, int local_size);
1678
ALWAYS_INLINE(static VALUE vm_call_iseq_setup_normal(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const rb_callable_method_entry_t *me, int opt_pc, int param_size, int local_size));
1677
static inline VALUE vm_call_iseq_setup_2(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, int opt_pc, int param_size, int local_size, int frame_flag);
1678
ALWAYS_INLINE(static VALUE vm_call_iseq_setup_normal(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const rb_callable_method_entry_t *me, int opt_pc, int param_size, int local_size, int frame_flag));
1679 1679
static inline VALUE vm_call_iseq_setup_tailcall(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, int opt_pc);
1680 1680
static VALUE vm_call_super_method(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc);
1681 1681
static VALUE vm_call_method_nome(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc);
......
1700 1700
    const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def);
1701 1701
    int param = iseq->body->param.size;
1702 1702
    int local = iseq->body->local_table_size;
1703
    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local);
1703
    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local, 0);
1704 1704
}
1705 1705

  
1706 1706
MJIT_STATIC bool
......
1822 1822
    }
1823 1823
#endif
1824 1824

  
1825
    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param - delta, local);
1825
    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param - delta, local, 0);
1826 1826
}
1827 1827

  
1828 1828
static void
......
1852 1852

  
1853 1853
    int param = iseq->body->param.size;
1854 1854
    int local = iseq->body->local_table_size;
1855
    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local);
1855
    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local, 0);
1856 1856
}
1857 1857

  
1858 1858
static VALUE
......
1877 1877

  
1878 1878
    int param = iseq->body->param.size;
1879 1879
    int local = iseq->body->local_table_size;
1880
    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local);
1880
    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local, 0);
1881 1881
}
1882 1882

  
1883 1883
static inline int
1884 1884
vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc,
1885
		    const rb_iseq_t *iseq, VALUE *argv, int param_size, int local_size)
1885
                    const rb_iseq_t *iseq, VALUE *argv, int param_size, int local_size, int *frame_flag)
1886 1886
{
1887 1887
    if (LIKELY(!(ci->flag & VM_CALL_KW_SPLAT))) {
1888 1888
        if (LIKELY(rb_simple_iseq_p(iseq))) {
......
1959 1959
        }
1960 1960
    }
1961 1961

  
1962
    return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_method);
1962
    return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_method, frame_flag);
1963 1963
}
1964 1964

  
1965 1965
static VALUE
......
1970 1970
    const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def);
1971 1971
    const int param_size = iseq->body->param.size;
1972 1972
    const int local_size = iseq->body->local_table_size;
1973
    const int opt_pc = vm_callee_setup_arg(ec, calling, ci, cc, def_iseq_ptr(cc->me->def), cfp->sp - calling->argc, param_size, local_size);
1974
    return vm_call_iseq_setup_2(ec, cfp, calling, ci, cc, opt_pc, param_size, local_size);
1973
    int frame_flag = 0;
1974
    const int opt_pc = vm_callee_setup_arg(ec, calling, ci, cc, def_iseq_ptr(cc->me->def), cfp->sp - calling->argc, param_size, local_size, &frame_flag);
1975
    return vm_call_iseq_setup_2(ec, cfp, calling, ci, cc, opt_pc, param_size, local_size, frame_flag);
1975 1976
}
1976 1977

  
1977 1978
static inline VALUE
1978 1979
vm_call_iseq_setup_2(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc,
1979
		     int opt_pc, int param_size, int local_size)
1980
                     int opt_pc, int param_size, int local_size, int frame_flag)
1980 1981
{
1981 1982
    if (LIKELY(!(ci->flag & VM_CALL_TAILCALL))) {
1982
        return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param_size, local_size);
1983
        return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param_size, local_size, frame_flag);
1983 1984
    }
1984 1985
    else {
1985 1986
	return vm_call_iseq_setup_tailcall(ec, cfp, calling, ci, cc, opt_pc);
......
1988 1989

  
1989 1990
static inline VALUE
1990 1991
vm_call_iseq_setup_normal(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const rb_callable_method_entry_t *me,
1991
                          int opt_pc, int param_size, int local_size)
1992
                          int opt_pc, int param_size, int local_size, int frame_flag)
1992 1993
{
1993 1994
    const rb_iseq_t *iseq = def_iseq_ptr(me->def);
1994 1995
    VALUE *argv = cfp->sp - calling->argc;
1995 1996
    VALUE *sp = argv + param_size;
1996 1997
    cfp->sp = argv - 1 /* recv */;
1997 1998

  
1998
    vm_push_frame(ec, iseq, VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling->recv,
1999
    vm_push_frame(ec, iseq, frame_flag | VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling->recv,
1999 2000
                  calling->block_handler, (VALUE)me,
2000 2001
                  iseq->body->iseq_encoded + opt_pc, sp,
2001 2002
                  local_size - param_size,
......
2992 2993
	return 0;
2993 2994
    }
2994 2995
    else {
2995
	return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_type);
2996
        return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_type, 0);
2996 2997
    }
2997 2998
}
2998 2999

  
vm_method.c
1745 1745
    return set_visibility(argc, argv, module, METHOD_VISI_PRIVATE);
1746 1746
}
1747 1747

  
1748
/*
1749
 *  call-seq:
1750
 *     pass_positional_hash(method_name, ...)    -> self
1751
 *
1752
 *  For the given method names, marks each the method as not issuing a warning
1753
 *  if converting a positional hash to a keyword argument, and automatically
1754
 *  converts keyword splats inside the method into positional arguments if
1755
 *  the method was called with positional hash that was converted to a keyword
1756
 *  argument.
1757
 *
1758
 *  This should only be used for methods that delegate keywords to another
1759
 *  method, suppressing a warning when the target method does not accept
1760
 *  keyword arguments the final argument is a hash.
1761
 *
1762
 *  Will only be present in 2.7, will be removed in 3.0, so always check that
1763
 *  the module responds to this method before calling it.
1764
 *
1765
 *    module Mod
1766
 *      def foo(meth, *args, **kw, &block)
1767
 *        send(:"do_#{meth}", *args, **kw, &block)
1768
 *      end
1769
 *      pass_positional_hash(:foo) if respond_to?(:pass_positional_hash, false)
1770
 *    end
1771
 */
1772

  
1773
static VALUE
1774
rb_mod_pass_positional_hash(int argc, VALUE *argv, VALUE module)
1775
{
1776
    int i;
1777
    VALUE origin_class = RCLASS_ORIGIN(module);
1778

  
1779
    rb_check_frozen(module);
1780

  
1781
    for (i = 0; i < argc; i++) {
1782
        VALUE v = argv[i];
1783
        ID name = rb_check_id(&v);
1784
        rb_method_entry_t *me;
1785
        VALUE defined_class;
1786

  
1787
        if (!name) {
1788
            rb_print_undef_str(module, v);
1789
        }
1790

  
1791
        me = search_method(origin_class, name, &defined_class);
1792
        if (!me && RB_TYPE_P(module, T_MODULE)) {
1793
            me = search_method(rb_cObject, name, &defined_class);
1794
        }
1795

  
1796
        if (UNDEFINED_METHOD_ENTRY_P(me) ||
1797
            UNDEFINED_REFINED_METHOD_P(me->def)) {
1798
            rb_print_undef(module, name, METHOD_VISI_UNDEF);
1799
        }
1800

  
1801
        if (module == defined_class || origin_class == defined_class) {
1802
            if (me->def->type == VM_METHOD_TYPE_ISEQ && me->def->body.iseq.iseqptr->body->param.flags.has_kwrest) {
1803
                me->def->body.iseq.iseqptr->body->param.flags.pass_positional_hash = 1;
1804
                rb_clear_method_cache_by_class(module);
1805
            }
1806
            else {
1807
                rb_warn("Skipping set of pass positional hash flag for %s (method not defined in Ruby or method does not accept keyword arguments)", rb_id2name(name));
1808
            }
1809
        }
1810
        else {
1811
            rb_warn("Skipping set of pass positional hash flag for %s (can only set in method defining module)", rb_id2name(name));
1812
        }
1813
    }
1814
    return Qnil;
1815
}
1816

  
1748 1817
/*
1749 1818
 *  call-seq:
1750 1819
 *     mod.public_class_method(symbol, ...)    -> mod
......
2127 2196
    rb_define_private_method(rb_cModule, "protected", rb_mod_protected, -1);
2128 2197
    rb_define_private_method(rb_cModule, "private", rb_mod_private, -1);
2129 2198
    rb_define_private_method(rb_cModule, "module_function", rb_mod_modfunc, -1);
2199
    rb_define_private_method(rb_cModule, "pass_positional_hash", rb_mod_pass_positional_hash, -1); /* -- Remove In 3.0 -- */
2130 2200

  
2131 2201
    rb_define_method(rb_cModule, "method_defined?", rb_mod_method_defined, -1);
2132 2202
    rb_define_method(rb_cModule, "public_method_defined?", rb_mod_public_method_defined, -1);