aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTakashi Kokubun <takashikkbn@gmail.com>2021-01-17 01:35:54 -0800
committerGitHub <noreply@github.com>2021-01-17 01:35:54 -0800
commit8d099aa040427aede04e42c3ec9380afd431ffe3 (patch)
treed3c827abba8cf514f9cb3607723270c2419b539e
parente033c9d7db02a4e8d2973364ecb47744b63aecd3 (diff)
downloadruby-8d099aa040427aede04e42c3ec9380afd431ffe3.tar.gz
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
-rw-r--r--NEWS.md7
-rw-r--r--struct.c22
-rw-r--r--test/ruby/test_struct.rb12
3 files changed, 34 insertions, 7 deletions
diff --git a/NEWS.md b/NEWS.md
index 962a777..8e6aeb4 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -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
diff --git a/struct.c b/struct.c
index d62b6ca..ceb025f 100644
--- a/struct.c
+++ b/struct.c
@@ -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 c313ab0..dc6a0b9 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]')