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:
parent
a679472c0e
commit
02015974a3
3 changed files with 92 additions and 11 deletions
|
@ -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
|
||||
|
|
84
struct.c
84
struct.c
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
|
|
Loading…
Reference in a new issue