diff options
-rw-r--r-- | test/ruby/test_keyword.rb | 166 | ||||
-rw-r--r-- | test/ruby/test_struct.rb | 3 | ||||
-rw-r--r-- | vm_args.c | 71 | ||||
-rw-r--r-- | vm_insnhelper.c | 4 |
4 files changed, 203 insertions, 41 deletions
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 862b6733ea..295b499530 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -313,9 +313,19 @@ class TestKeywordArguments < Test::Unit::TestCase sc = Class.new c = sc.new - def c.m(*args, **kw) - super + redef = -> do + if defined?(c.m) + class << c + remove_method(:m) + end + end + eval <<-END + def c.m(*args, **kw) + super(*args, **kw) + end + END end + redef[] sc.class_eval do def m(*args) args @@ -350,6 +360,7 @@ class TestKeywordArguments < Test::Unit::TestCase assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal(kw, c.m(**{})) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal(kw, c.m(**kw)) end @@ -386,24 +397,31 @@ class TestKeywordArguments < Test::Unit::TestCase [arg, args] end end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do c.m(**{}) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do c.m(**kw) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h, kw], c.m(**h)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h, kw], c.m(a: 1)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h2, kw], c.m(**h2)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h3, kw], c.m(**h3)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h3, kw], c.m(a: 1, **h2)) end @@ -431,9 +449,19 @@ class TestKeywordArguments < Test::Unit::TestCase sc = Class.new c = sc.new - def c.m(*args, **kw) - super(*args, **kw) + redef = -> do + if defined?(c.m) + class << c + remove_method(:m) + end + end + eval <<-END + def c.m(*args, **kw) + super(*args, **kw) + end + END end + redef[] sc.class_eval do def m(*args) args @@ -468,6 +496,7 @@ class TestKeywordArguments < Test::Unit::TestCase assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal(kw, c.m(**{})) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal(kw, c.m(**kw)) end @@ -504,24 +533,31 @@ class TestKeywordArguments < Test::Unit::TestCase [arg, args] end end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do c.m(**{}) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do c.m(**kw) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h, kw], c.m(**h)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h, kw], c.m(a: 1)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h2, kw], c.m(**h2)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h3, kw], c.m(**h3)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h3, kw], c.m(a: 1, **h2)) end @@ -2092,13 +2128,19 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { c.m(**h3) } assert_raise(ArgumentError) { c.m(a: 1, **h2) } - c.singleton_class.remove_method(:method_missing) - def c.method_missing(_, args) - args + redef = -> do + c.singleton_class.remove_method(:method_missing) + eval <<-END + def c.method_missing(_, args) + args + end + END end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal(kw, c.m(**{})) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal(kw, c.m(**kw)) end @@ -2127,28 +2169,39 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { c.m(h3) } end - c.singleton_class.remove_method(:method_missing) - def c.method_missing(_, arg, **args) - [arg, args] + redef = -> do + c.singleton_class.remove_method(:method_missing) + eval <<-END + def c.method_missing(_, arg, **args) + [arg, args] + end + END end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([kw, kw], c.m(**{})) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([kw, kw], c.m(**kw)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h, kw], c.m(**h)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h, kw], c.m(a: 1)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h2, kw], c.m(**h2)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h3, kw], c.m(**h3)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h3, kw], c.m(a: 1, **h2)) end @@ -2950,6 +3003,12 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h1], o.baz(1, h1)) assert_equal([h1], o.baz(h1, **{})) + c.class_eval do + remove_method(:bar) + def bar(*args, **kw) + [args, kw] + end + end assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do assert_equal([[1], h1], o.foo(:pass_bar, 1, :a=>1)) end @@ -4601,13 +4660,19 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_raise(ArgumentError) { c.call(**h3, &:m) } assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) } - c.singleton_class.remove_method(:m) - def c.m(args) - args + redef = -> do + c.singleton_class.remove_method(:m) + eval <<-END + def c.m(args) + args + end + END end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal(kw, c.call(**{}, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal(kw, c.call(**kw, &:m)) end @@ -4636,28 +4701,39 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_raise(ArgumentError) { c.call(h3, &:m) } end - c.singleton_class.remove_method(:m) - def c.m(arg, **args) - [arg, args] + redef = -> do + c.singleton_class.remove_method(:m) + eval <<-END + def c.m(arg, **args) + [arg, args] + end + END end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([kw, kw], c.call(**{}, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([kw, kw], c.call(**kw, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h, kw], c.call(**h, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h, kw], c.call(a: 1, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h2, kw], c.call(**h2, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h3, kw], c.call(**h3, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do assert_equal([h3, kw], c.call(a: 1, **h2, &:m)) end @@ -4704,13 +4780,19 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_raise(ArgumentError) { c.call(**h3, &:m) } assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) } - c.singleton_class.remove_method(:method_missing) - def c.method_missing(_, args) - args + redef = -> do + c.singleton_class.remove_method(:method_missing) + eval <<-END + def c.method_missing(_, args) + args + end + END end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal(kw, c.call(**{}, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal(kw, c.call(**kw, &:m)) end @@ -4739,28 +4821,39 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_raise(ArgumentError) { c.call(h3, &:m2) } end - c.singleton_class.remove_method(:method_missing) - def c.method_missing(_, arg, **args) - [arg, args] + redef = -> do + c.singleton_class.remove_method(:method_missing) + eval <<-END + def c.method_missing(_, arg, **args) + [arg, args] + end + END end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([kw, kw], c.call(**{}, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([kw, kw], c.call(**kw, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h, kw], c.call(**h, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h, kw], c.call(a: 1, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h2, kw], c.call(**h2, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h3, kw], c.call(**h3, &:m)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h3, kw], c.call(a: 1, **h2, &:m)) end @@ -4807,13 +4900,19 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_raise(ArgumentError) { c.call(**h3, &:m2) } assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m2) } - c.singleton_class.remove_method(:method_missing) - def c.method_missing(_, args) - args + redef = -> do + c.singleton_class.remove_method(:method_missing) + eval <<-END + def c.method_missing(_, args) + args + end + END end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal(kw, c.call(**{}, &:m2)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal(kw, c.call(**kw, &:m2)) end @@ -4842,28 +4941,39 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_raise(ArgumentError) { c.call(h3, &:m2) } end - c.singleton_class.remove_method(:method_missing) - def c.method_missing(_, arg, **args) - [arg, args] + redef = -> do + c.singleton_class.remove_method(:method_missing) + eval <<-END + def c.method_missing(_, arg, **args) + [arg, args] + end + END end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([kw, kw], c.call(**{}, &:m2)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([kw, kw], c.call(**kw, &:m2)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h, kw], c.call(**h, &:m2)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h, kw], c.call(a: 1, &:m2)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h2, kw], c.call(**h2, &:m2)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h3, kw], c.call(**h3, &:m2)) end + redef[] assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do assert_equal([h3, kw], c.call(a: 1, **h2, &:m2)) end diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb index 2f5ef80c9b..884fbe00ed 100644 --- a/test/ruby/test_struct.rb +++ b/test/ruby/test_struct.rb @@ -113,7 +113,8 @@ module TestStruct assert_equal @Struct::KeywordInitTrue.new(a: 1, b: 2).values, @Struct::KeywordInitFalse.new(1, 2).values assert_equal "#{@Struct}::KeywordInitFalse", @Struct::KeywordInitFalse.inspect assert_equal "#{@Struct}::KeywordInitTrue(keyword_init: true)", @Struct::KeywordInitTrue.inspect - k = Class.new(@Struct::KeywordInitFalse) {def initialize(**) end} + # eval is neede to prevent the warning duplication filter + k = eval("Class.new(@Struct::KeywordInitFalse) {def initialize(**) end}") assert_warn(/The last argument is used as the keyword parameter/) {k.new(a: 1, b: 2)} k = Class.new(@Struct::KeywordInitTrue) {def initialize(**) end} assert_warn('') {k.new(a: 1, b: 2)} @@ -585,9 +585,56 @@ ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq) { VALUE rb_iseq_location(const rb_iseq_t *iseq); +/* -- Remove In 3.0 -- */ + +/* This is a map from caller PC to a set of callee methods. + * When a warning about keyword argument change is printed, + * it keeps the pair of callee and caller. + */ +static st_table *caller_to_callees = 0; + +static VALUE rb_warn_check(const rb_execution_context_t * const ec, const void *const callee) +{ + const rb_control_frame_t * const cfp = rb_vm_get_ruby_level_next_cfp(ec, ec->cfp); + + if (!cfp) return 0; + + const void *const caller = cfp->pc; + + if (!caller_to_callees) { + caller_to_callees = st_init_numtable(); + } + + st_data_t val; + if (st_lookup(caller_to_callees, (st_data_t) caller, &val)) { + st_table *callees; + + if (val & 1) { + val &= ~(st_data_t)1; + if (val == (st_data_t) callee) return 1; /* already warned */ + + callees = st_init_numtable(); + st_insert(callees, val, 1); + } + else { + callees = (st_table *) val; + if (st_is_member(callees, (st_data_t) callee)) return 1; /* already warned */ + } + st_insert(callees, (st_data_t) callee, 1); + st_insert(caller_to_callees, (st_data_t) caller, (st_data_t) callees); + } + else { + st_insert(caller_to_callees, (st_data_t) caller, ((st_data_t) callee) | 1); + } + + return 0; /* not warned yet for the pair of caller and callee */ +} + static inline void -rb_warn_keyword_to_last_hash(struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq) +rb_warn_keyword_to_last_hash(rb_execution_context_t * const ec, struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq) { + if (rb_warn_check(ec, iseq)) return; + VALUE name, loc; if (calling->recv == Qundef) { rb_warn("The keyword argument is passed as the last hash parameter"); @@ -613,8 +660,10 @@ rb_warn_keyword_to_last_hash(struct rb_calling_info *calling, const struct rb_ca } static inline void -rb_warn_split_last_hash_to_keyword(struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq) +rb_warn_split_last_hash_to_keyword(rb_execution_context_t * const ec, struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq) { + if (rb_warn_check(ec, iseq)) return; + VALUE name, loc; name = rb_id2str(ci->mid); loc = rb_iseq_location(iseq); @@ -636,8 +685,10 @@ rb_warn_split_last_hash_to_keyword(struct rb_calling_info *calling, const struct } static inline void -rb_warn_last_hash_to_keyword(struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq) +rb_warn_last_hash_to_keyword(rb_execution_context_t * const ec, struct rb_calling_info *calling, const struct rb_call_info *ci, const rb_iseq_t * const iseq) { + if (rb_warn_check(ec, iseq)) return; + VALUE name, loc; name = rb_id2str(ci->mid); loc = rb_iseq_location(iseq); @@ -766,7 +817,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co } } else { - rb_warn_keyword_to_last_hash(calling, ci, iseq); + rb_warn_keyword_to_last_hash(ec, calling, ci, iseq); } } else if (!remove_empty_keyword_hash && rest_last) { @@ -789,7 +840,7 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co } } else { - rb_warn_keyword_to_last_hash(calling, ci, iseq); + rb_warn_keyword_to_last_hash(ec, calling, ci, iseq); } } else if (!remove_empty_keyword_hash) { @@ -856,10 +907,10 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co } else if (check_only_symbol) { if (keyword_hash != Qnil) { - rb_warn_split_last_hash_to_keyword(calling, ci, iseq); + rb_warn_split_last_hash_to_keyword(ec, calling, ci, iseq); } else { - rb_warn_keyword_to_last_hash(calling, ci, iseq); + rb_warn_keyword_to_last_hash(ec, calling, ci, iseq); } } } @@ -868,15 +919,15 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co * def foo(k:1) p [k]; end * foo({k:42}) #=> 42 */ - rb_warn_last_hash_to_keyword(calling, ci, iseq); + rb_warn_last_hash_to_keyword(ec, calling, ci, iseq); given_argc--; } else if (keyword_hash != Qnil) { - rb_warn_split_last_hash_to_keyword(calling, ci, iseq); + rb_warn_split_last_hash_to_keyword(ec, calling, ci, iseq); } } else if (given_argc == min_argc && kw_flag) { - rb_warn_keyword_to_last_hash(calling, ci, iseq); + rb_warn_keyword_to_last_hash(ec, calling, ci, iseq); } } diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 94fc7c72bd..9eb349d54e 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2926,7 +2926,7 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st case VM_METHOD_TYPE_ATTRSET: CALLER_SETUP_ARG(cfp, calling, ci); if (calling->argc == 1 && calling->kw_splat && RHASH_EMPTY_P(cfp->sp[-1])) { - rb_warn_keyword_to_last_hash(calling, ci, NULL); + rb_warn_keyword_to_last_hash(ec, calling, ci, NULL); } else { CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci); @@ -3264,7 +3264,7 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca CALLER_SETUP_ARG(cfp, calling, ci); if (calling->kw_splat && calling->argc == iseq->body->param.lead_num + iseq->body->param.post_num && RHASH_EMPTY_P(cfp->sp[-1])) { - rb_warn_keyword_to_last_hash(calling, ci, iseq); + rb_warn_keyword_to_last_hash(ec, calling, ci, iseq); } else { CALLER_REMOVE_EMPTY_KW_SPLAT(cfp, calling, ci); |