[前][次][番号順一覧][スレッド一覧]

ruby-changes:49022

From: k0kubun <ko1@a...>
Date: Tue, 12 Dec 2017 17:12:48 +0900 (JST)
Subject: [ruby-changes:49022] k0kubun:r61137 (trunk): struct.c: add keyword_init option to Struct.new

k0kubun	2017-12-12 17:12:43 +0900 (Tue, 12 Dec 2017)

  New Revision: 61137

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=61137

  Log:
    struct.c: add keyword_init option to Struct.new
    
    to initialize struct with keyword arguments.
    
    [Feature #11925] [close GH-1771]

  Modified files:
    trunk/spec/ruby/core/struct/new_spec.rb
    trunk/struct.c
    trunk/test/ruby/test_struct.rb
Index: struct.c
===================================================================
--- struct.c	(revision 61136)
+++ struct.c	(revision 61137)
@@ -23,7 +23,7 @@ const rb_iseq_t *rb_method_for_self_aref https://github.com/ruby/ruby/blob/trunk/struct.c#L23
 const rb_iseq_t *rb_method_for_self_aset(VALUE name, VALUE arg, rb_insn_func_t func);
 
 VALUE rb_cStruct;
-static ID id_members, id_back_members;
+static ID id_members, id_back_members, id_keyword_init;
 
 static VALUE struct_alloc(VALUE);
 
@@ -437,6 +437,7 @@ rb_struct_define_under(VALUE outer, cons https://github.com/ruby/ruby/blob/trunk/struct.c#L437
 /*
  *  call-seq:
  *    Struct.new([class_name] [, member_name]+)                        -> StructClass
+ *    Struct.new([class_name] [, member_name]+, keyword_init: true)    -> StructClass
  *    Struct.new([class_name] [, member_name]+) {|StructClass| block } -> StructClass
  *    StructClass.new(value, ...)                                      -> object
  *    StructClass[value, ...]                                          -> object
@@ -463,6 +464,13 @@ rb_struct_define_under(VALUE outer, cons https://github.com/ruby/ruby/blob/trunk/struct.c#L464
  *     Customer.new("Dave", "123 Main")
  *     #=> #<struct Customer name="Dave", address="123 Main">
  *
+ *  If keyword_init: true option is given, .new takes Hash instead of Array.
+ *
+ *     Customer = Struct.new(:name, :address, keyword_init: true)
+ *     #=> Customer
+ *     Customer.new(name: "Dave", address: "123 Main")
+ *     #=> #<struct Customer name="Dave", address="123 Main">
+ *
  *  If a block is given it will be evaluated in the context of
  *  +StructClass+, passing the created class as a parameter:
  *
@@ -492,7 +500,7 @@ rb_struct_define_under(VALUE outer, cons https://github.com/ruby/ruby/blob/trunk/struct.c#L500
 static VALUE
 rb_struct_s_def(int argc, VALUE *argv, VALUE klass)
 {
-    VALUE name, rest;
+    VALUE name, rest, keyword_init;
     long i;
     VALUE st;
     st_table *tbl;
@@ -506,6 +514,21 @@ rb_struct_s_def(int argc, VALUE *argv, V https://github.com/ruby/ruby/blob/trunk/struct.c#L514
 	--argc;
 	++argv;
     }
+
+    if (RB_TYPE_P(argv[argc-1], T_HASH)) {
+	VALUE kwargs[1];
+	static ID keyword_ids[1];
+
+	if (!keyword_ids[0]) {
+	    keyword_ids[0] = rb_intern("keyword_init");
+	}
+	rb_get_kwargs(argv[argc-1], keyword_ids, 0, 1, kwargs);
+	--argc;
+	keyword_init = kwargs[0];
+    } else {
+	keyword_init = Qfalse;
+    }
+
     rest = rb_ident_hash_new();
     RBASIC_CLEAR_CLASS(rest);
     tbl = RHASH_TBL(rest);
@@ -526,6 +549,7 @@ rb_struct_s_def(int argc, VALUE *argv, V https://github.com/ruby/ruby/blob/trunk/struct.c#L549
 	st = new_struct(name, klass);
     }
     setup_struct(st, rest);
+    rb_ivar_set(st, id_keyword_init, keyword_init);
     if (rb_block_given_p()) {
 	rb_mod_module_eval(0, 0, st);
     }
@@ -547,6 +571,30 @@ num_members(VALUE klass) https://github.com/ruby/ruby/blob/trunk/struct.c#L571
 /*
  */
 
+struct struct_hash_set_arg {
+    VALUE self;
+    VALUE unknown_keywords;
+};
+
+static int rb_struct_pos(VALUE s, VALUE *name);
+
+static int
+struct_hash_set_i(VALUE key, VALUE val, VALUE arg)
+{
+    struct struct_hash_set_arg *args = (struct struct_hash_set_arg *)arg;
+    int i = rb_struct_pos(args->self, &key);
+    if (i < 0) {
+	if (args->unknown_keywords == Qnil) {
+	    args->unknown_keywords = rb_ary_new();
+	}
+	rb_ary_push(args->unknown_keywords, key);
+    } else {
+	rb_struct_modify(args->self);
+	RSTRUCT_SET(args->self, i, val);
+    }
+    return ST_CONTINUE;
+}
+
 static VALUE
 rb_struct_initialize_m(int argc, const VALUE *argv, VALUE self)
 {
@@ -555,14 +603,29 @@ rb_struct_initialize_m(int argc, const V https://github.com/ruby/ruby/blob/trunk/struct.c#L603
 
     rb_struct_modify(self);
     n = num_members(klass);
-    if (n < argc) {
-	rb_raise(rb_eArgError, "struct size differs");
-    }
-    for (i=0; i<argc; i++) {
-	RSTRUCT_SET(self, i, argv[i]);
-    }
-    if (n > argc) {
-	rb_mem_clear((VALUE *)RSTRUCT_CONST_PTR(self)+argc, n-argc);
+    if (argc > 0 && RTEST(struct_ivar_get(klass, id_keyword_init))) {
+	struct struct_hash_set_arg arg;
+	if (argc > 2 || !RB_TYPE_P(argv[0], T_HASH)) {
+	    rb_raise(rb_eArgError, "wrong number of arguments (given %d, expected 0)", argc);
+	}
+	rb_mem_clear((VALUE *)RSTRUCT_CONST_PTR(self), n);
+	arg.self = self;
+	arg.unknown_keywords = Qnil;
+	rb_hash_foreach(argv[0], struct_hash_set_i, (VALUE)&arg);
+	if (arg.unknown_keywords != Qnil) {
+	    rb_raise(rb_eArgError, "unknown keywords: %s",
+		     RSTRING_PTR(rb_ary_join(arg.unknown_keywords, rb_str_new2(", "))));
+	}
+    } else {
+	if (n < argc) {
+	    rb_raise(rb_eArgError, "struct size differs");
+	}
+	for (i=0; i<argc; i++) {
+	    RSTRUCT_SET(self, i, argv[i]);
+	}
+	if (n > argc) {
+	    rb_mem_clear((VALUE *)RSTRUCT_CONST_PTR(self)+argc, n-argc);
+	}
     }
     return Qnil;
 }
@@ -1226,6 +1289,7 @@ Init_Struct(void) https://github.com/ruby/ruby/blob/trunk/struct.c#L1289
 {
     id_members = rb_intern("__members__");
     id_back_members = rb_intern("__members_back__");
+    id_keyword_init = rb_intern("__keyword_init__");
 
     InitVM(Struct);
 }
Index: test/ruby/test_struct.rb
===================================================================
--- test/ruby/test_struct.rb	(revision 61136)
+++ test/ruby/test_struct.rb	(revision 61137)
@@ -92,6 +92,23 @@ module TestStruct https://github.com/ruby/ruby/blob/trunk/test/ruby/test_struct.rb#L92
     assert_equal([:utime, :stime, :cutime, :cstime], Process.times.members)
   end
 
+  def test_struct_new_with_keyword_init
+    @Struct.new("KeywordArgsTrue", :a, :b, keyword_init: true)
+    @Struct.new("KeywordArgsFalse", :a, :b, keyword_init: false)
+
+    assert_raise(ArgumentError) { @Struct::KeywordArgsTrue.new(1, 2) }
+    assert_nothing_raised { @Struct::KeywordArgsFalse.new(1, 2) }
+    assert_nothing_raised { @Struct::KeywordArgsTrue.new(a: 1, b: 2) }
+    assert_raise(ArgumentError) { @Struct::KeywordArgsTrue.new(1, b: 2) }
+    assert_raise(ArgumentError) { @Struct::KeywordArgsTrue.new(a: 1, b: 2, c: 3) }
+    assert_equal @Struct::KeywordArgsTrue.new(a: 1, b: 2).values, @Struct::KeywordArgsFalse.new(1, 2).values
+
+    @Struct.instance_eval do
+      remove_const(:KeywordArgsTrue)
+      remove_const(:KeywordArgsFalse)
+    end
+  end
+
   def test_initialize
     klass = @Struct.new(:a)
     assert_raise(ArgumentError) { klass.new(1, 2) }
Index: spec/ruby/core/struct/new_spec.rb
===================================================================
--- spec/ruby/core/struct/new_spec.rb	(revision 61136)
+++ spec/ruby/core/struct/new_spec.rb	(revision 61137)
@@ -60,7 +60,7 @@ describe "Struct.new" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/struct/new_spec.rb#L60
     lambda { Struct.new(:animal, nil)                  }.should raise_error(TypeError)
     lambda { Struct.new(:animal, true)                 }.should raise_error(TypeError)
     lambda { Struct.new(:animal, ['chris', 'evan'])    }.should raise_error(TypeError)
-    lambda { Struct.new(:animal, { name: 'chris' }) }.should raise_error(TypeError)
+    lambda { Struct.new(:animal, { name: 'chris' }) }.should raise_error(ArgumentError)
   end
 
   it "raises a TypeError if object is not a Symbol" do

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]