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
This commit is contained in:
Takashi Kokubun 2021-01-17 01:35:54 -08:00 committed by GitHub
parent e033c9d7db
commit 8d099aa040
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
Notes: git 2021-01-17 18:36:20 +09:00
Merged-By: k0kubun <takashikkbn@gmail.com>
3 changed files with 34 additions and 7 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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]')