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

struct.c: add keyword_init option to Struct.new

to initialize struct with keyword arguments.

[Feature #11925] [close GH-1771]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@61137 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
k0kubun 2017-12-12 08:12:43 +00:00
parent a679472c0e
commit 02015974a3
3 changed files with 92 additions and 11 deletions

View file

@ -60,7 +60,7 @@ describe "Struct.new" do
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

View file

@ -23,7 +23,7 @@ const rb_iseq_t *rb_method_for_self_aref(VALUE name, VALUE arg, rb_insn_func_t f
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, const char *name, ...)
/*
* 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, const char *name, ...)
* 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, const char *name, ...)
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, VALUE klass)
--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, VALUE klass)
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)
/*
*/
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 VALUE *argv, VALUE self)
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)
{
id_members = rb_intern("__members__");
id_back_members = rb_intern("__members_back__");
id_keyword_init = rb_intern("__keyword_init__");
InitVM(Struct);
}

View file

@ -92,6 +92,23 @@ module TestStruct
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) }