1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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 modify the ancestor chain if the receiver has already prepended
the argument. [[Bug #17423]] 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 ## Stdlib updates
Outstanding ones only. Outstanding ones only.
@ -55,5 +61,6 @@ Excluding feature bug fixes.
## Miscellaneous changes ## Miscellaneous changes
[Feature #16806]: https://bugs.ruby-lang.org/issues/16806
[Feature #17312]: https://bugs.ruby-lang.org/issues/17312 [Feature #17312]: https://bugs.ruby-lang.org/issues/17312
[Bug #17423]: https://bugs.ruby-lang.org/issues/17423 [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 static VALUE
rb_struct_s_def(int argc, VALUE *argv, VALUE klass) rb_struct_s_def(int argc, VALUE *argv, VALUE klass)
{ {
VALUE name, rest, keyword_init = Qfalse; VALUE name, rest, keyword_init = Qnil;
long i; long i;
VALUE st; VALUE st;
st_table *tbl; 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); rb_get_kwargs(argv[argc-1], keyword_ids, 0, 1, &keyword_init);
if (keyword_init == Qundef) { if (keyword_init == Qundef) {
keyword_init = Qfalse; keyword_init = Qnil;
} }
--argc; --argc;
} }
@ -657,11 +657,15 @@ static VALUE
rb_struct_initialize_m(int argc, const VALUE *argv, VALUE self) rb_struct_initialize_m(int argc, const VALUE *argv, VALUE self)
{ {
VALUE klass = rb_obj_class(self); VALUE klass = rb_obj_class(self);
long i, n;
rb_struct_modify(self); rb_struct_modify(self);
n = num_members(klass); long n = num_members(klass);
if (argc > 0 && RTEST(rb_struct_s_keyword_init(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; struct struct_hash_set_arg arg;
if (argc > 1 || !RB_TYPE_P(argv[0], T_HASH)) { if (argc > 1 || !RB_TYPE_P(argv[0], T_HASH)) {
rb_raise(rb_eArgError, "wrong number of arguments (given %d, expected 0)", argc); 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) { if (n < argc) {
rb_raise(rb_eArgError, "struct size differs"); 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]); RSTRUCT_SET(self, i, argv[i]);
} }
if (n > argc) { if (n > argc) {

View file

@ -350,6 +350,18 @@ module TestStruct
end end
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 def test_nonascii
struct_test = @Struct.new(name = "R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}") 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]') assert_equal(@Struct.const_get("R\u{e9}sum\u{e9}"), struct_test, '[ruby-core:24849]')