diff options
author | Takashi Kokubun <takashikkbn@gmail.com> | 2021-01-17 01:35:54 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-17 01:35:54 -0800 |
commit | 8d099aa040427aede04e42c3ec9380afd431ffe3 (patch) | |
tree | d3c827abba8cf514f9cb3607723270c2419b539e | |
parent | e033c9d7db02a4e8d2973364ecb47744b63aecd3 (diff) |
Warn Struct#initialize with only keyword args (#4070)
* Warn Struct#initialize with only keyword args
A part of [Feature #16806]
* Do not warn if `keyword_init: false`
is explicitly specified
* Add a NEWS entry
* s/in/from/
* Make sure all fields are initialized
Notes
Notes:
Merged-By: k0kubun <takashikkbn@gmail.com>
-rw-r--r-- | NEWS.md | 7 | ||||
-rw-r--r-- | struct.c | 22 | ||||
-rw-r--r-- | test/ruby/test_struct.rb | 12 |
3 files changed, 34 insertions, 7 deletions
@@ -28,6 +28,12 @@ Outstanding ones only. modify the ancestor chain if the receiver has already prepended the argument. [[Bug #17423]] +* Struct + + * Passing only keyword arguments to Struct#initialize is warned. + You need to use a Hash literal to set a Hash to a first member. + [[Feature #16806]] + ## Stdlib updates Outstanding ones only. @@ -55,5 +61,6 @@ Excluding feature bug fixes. ## Miscellaneous changes +[Feature #16806]: https://bugs.ruby-lang.org/issues/16806 [Feature #17312]: https://bugs.ruby-lang.org/issues/17312 [Bug #17423]: https://bugs.ruby-lang.org/issues/17423 @@ -554,7 +554,7 @@ rb_struct_define_under(VALUE outer, const char *name, ...) static VALUE rb_struct_s_def(int argc, VALUE *argv, VALUE klass) { - VALUE name, rest, keyword_init = Qfalse; + VALUE name, rest, keyword_init = Qnil; long i; VALUE st; st_table *tbl; @@ -577,7 +577,7 @@ rb_struct_s_def(int argc, VALUE *argv, VALUE klass) } rb_get_kwargs(argv[argc-1], keyword_ids, 0, 1, &keyword_init); if (keyword_init == Qundef) { - keyword_init = Qfalse; + keyword_init = Qnil; } --argc; } @@ -657,11 +657,15 @@ static VALUE rb_struct_initialize_m(int argc, const VALUE *argv, VALUE self) { VALUE klass = rb_obj_class(self); - long i, n; - rb_struct_modify(self); - n = num_members(klass); - if (argc > 0 && RTEST(rb_struct_s_keyword_init(klass))) { + long n = num_members(klass); + if (argc == 0) { + rb_mem_clear((VALUE *)RSTRUCT_CONST_PTR(self), n); + return Qnil; + } + + VALUE keyword_init = rb_struct_s_keyword_init(klass); + if (RTEST(keyword_init)) { struct struct_hash_set_arg arg; if (argc > 1 || !RB_TYPE_P(argv[0], T_HASH)) { rb_raise(rb_eArgError, "wrong number of arguments (given %d, expected 0)", argc); @@ -679,7 +683,11 @@ rb_struct_initialize_m(int argc, const VALUE *argv, VALUE self) if (n < argc) { rb_raise(rb_eArgError, "struct size differs"); } - for (i=0; i<argc; i++) { + if (keyword_init == Qnil && argc == 1 && RB_TYPE_P(argv[0], T_HASH) && rb_keyword_given_p()) { + rb_warn("Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2. "\ + "Please use a Hash literal like .new({k: v}) instead of .new(k: v)."); + } + for (long i=0; i<argc; i++) { RSTRUCT_SET(self, i, argv[i]); } if (n > argc) { diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb index c313ab0dbe..dc6a0b99fd 100644 --- a/test/ruby/test_struct.rb +++ b/test/ruby/test_struct.rb @@ -350,6 +350,18 @@ module TestStruct end end + def test_keyword_args_warning + warning = /warning: Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3\.2\./ + assert_match(warning, EnvUtil.verbose_warning { assert_equal({a: 1}, @Struct.new(:a).new(a: 1).a) }) + assert_match(warning, EnvUtil.verbose_warning { assert_equal({a: 1}, @Struct.new(:a, keyword_init: nil).new(a: 1).a) }) + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a).new({a: 1}).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, :b).new(1, a: 1).b) } + assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: true).new(a: 1).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: nil).new({a: 1}).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: false).new(a: 1).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: false).new({a: 1}).a) } + end + def test_nonascii struct_test = @Struct.new(name = "R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}") assert_equal(@Struct.const_get("R\u{e9}sum\u{e9}"), struct_test, '[ruby-core:24849]') |