Feature #16122 ยป struct_value.patch
struct.c | ||
---|---|---|
const rb_iseq_t *rb_method_for_self_aref(VALUE name, VALUE arg, rb_insn_func_t func);
|
||
const rb_iseq_t *rb_method_for_self_aset(VALUE name, VALUE arg, rb_insn_func_t func);
|
||
VALUE rb_cStruct;
|
||
VALUE rb_cStruct, rb_cStructValue;
|
||
static ID id_members, id_back_members, id_keyword_init;
|
||
static VALUE struct_alloc(VALUE);
|
||
... | ... | |
}
|
||
static VALUE
|
||
setup_struct(VALUE nstr, VALUE members)
|
||
setup_struct(VALUE nstr, VALUE members, bool is_immutable)
|
||
{
|
||
long i, len;
|
||
... | ... | |
rb_define_alloc_func(nstr, struct_alloc);
|
||
rb_define_singleton_method(nstr, "new", rb_class_new_instance, -1);
|
||
rb_define_singleton_method(nstr, "[]", rb_class_new_instance, -1);
|
||
if(!is_immutable) {
|
||
rb_define_singleton_method(nstr, "[]", rb_class_new_instance, -1);
|
||
}
|
||
rb_define_singleton_method(nstr, "members", rb_struct_s_members_m, 0);
|
||
rb_define_singleton_method(nstr, "inspect", rb_struct_s_inspect, 0);
|
||
len = RARRAY_LEN(members);
|
||
... | ... | |
else {
|
||
define_aref_method(nstr, sym, off);
|
||
}
|
||
define_aset_method(nstr, ID2SYM(rb_id_attrset(id)), off);
|
||
if (!is_immutable) {
|
||
define_aset_method(nstr, ID2SYM(rb_id_attrset(id)), off);
|
||
}
|
||
}
|
||
return nstr;
|
||
... | ... | |
if (!name) st = anonymous_struct(rb_cStruct);
|
||
else st = new_struct(rb_str_new2(name), rb_cStruct);
|
||
return setup_struct(st, ary);
|
||
return setup_struct(st, ary, false);
|
||
}
|
||
VALUE
|
||
... | ... | |
ary = struct_make_members_list(ar);
|
||
va_end(ar);
|
||
return setup_struct(rb_define_class_under(outer, name, rb_cStruct), ary);
|
||
return setup_struct(rb_define_class_under(outer, name, rb_cStruct), ary, false);
|
||
}
|
||
/*
|
||
... | ... | |
else {
|
||
st = new_struct(name, klass);
|
||
}
|
||
setup_struct(st, rest);
|
||
setup_struct(st, rest, false);
|
||
rb_ivar_set(st, id_keyword_init, keyword_init);
|
||
if (rb_block_given_p()) {
|
||
rb_mod_module_eval(0, 0, st);
|
||
... | ... | |
inspect_struct(VALUE s, VALUE dummy, int recur)
|
||
{
|
||
VALUE cname = rb_class_path(rb_obj_class(s));
|
||
VALUE members, str = rb_str_new2("#<struct ");
|
||
VALUE members, str;
|
||
if (rb_obj_is_kind_of(s, rb_cStructValue) == Qtrue) {
|
||
str = rb_str_new2("#<value ");
|
||
} else {
|
||
str = rb_str_new2("#<struct ");
|
||
}
|
||
long i, len;
|
||
char first = RSTRING_PTR(cname)[0];
|
||
... | ... | |
return rb_obj_dig(argc, argv, self, Qnil);
|
||
}
|
||
/*
|
||
* Document-class: Struct::Value
|
||
*
|
||
* Struct::Value is a simplified version of Struct, representing
|
||
* immutable and non-Enumerable value object, useful for producing
|
||
* code in functional style. Value's definition is performed the
|
||
* same way as Struct's, but, unlike Struct, Value lacks the
|
||
* following features:
|
||
*
|
||
* * It does not include Enumerable module, the only way to
|
||
* iterate through contents is with #each_pair method;
|
||
* * It does not provide any way of setting attribute values
|
||
* since the object was created, object is immutable;
|
||
* * It does not provide hash-alike methods, like <tt>[]</tt>,
|
||
* +values_at+ or +values+;
|
||
* * It does not provide +to_a+ method.
|
||
*
|
||
* The latter is because objects with +to_a+ methods could be
|
||
* unpacked unexpectedly, which is usually inconvenient for
|
||
* value objects.
|
||
*
|
||
* S = Struct.new(:a, :b)
|
||
* V = Struct::Value.new(:a, :b)
|
||
*
|
||
* def test(*args)
|
||
* p ["Args:", args]
|
||
* end
|
||
*
|
||
* test(*S.new(1, 2)) # => ["Args:", [1, 2]]
|
||
* test(*V.new(1, 2)) # => ["Args:", [#<value V a=1, b=2>]]
|
||
*
|
||
* The rest of Value's limitations targets making the class'
|
||
* goal and usage more focused: it is just _a value_, not a
|
||
* collection, and not a temporary storage for related yet
|
||
* changing data.
|
||
*
|
||
*/
|
||
/*
|
||
* call-seq:
|
||
* Struct::Value.new([class_name] [, member_name]+) -> ValueClass
|
||
* Struct::Value.new([class_name] [, member_name]+, keyword_init: true) -> ValueClass
|
||
* Struct::Value.new([class_name] [, member_name]+) {|ValueClass| block } -> ValueClass
|
||
* ValueClass.new(value, ...) -> object
|
||
* ValueClass[value, ...] -> object
|
||
*
|
||
* The first two forms are used to create a new Struct::Value subclass +class_name+
|
||
* that can contain a value for each +member_name+. This subclass can be
|
||
* used to create instances of the value like any other Class.
|
||
*
|
||
* See Struct.new for details of defining subclasses, the logic is exactly
|
||
* the same for Value.
|
||
*
|
||
*/
|
||
static VALUE
|
||
rb_struct_value_s_def(int argc, VALUE *argv, VALUE klass)
|
||
{
|
||
VALUE rest, keyword_init;
|
||
VALUE name = Qnil;
|
||
long i;
|
||
VALUE st;
|
||
st_table *tbl;
|
||
rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS);
|
||
if (argc > 0) {
|
||
name = argv[0];
|
||
if (SYMBOL_P(name)) {
|
||
name = Qnil;
|
||
}
|
||
else {
|
||
--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);
|
||
for (i=0; i<argc; i++) {
|
||
VALUE mem = rb_to_symbol(argv[i]);
|
||
if (st_insert(tbl, mem, Qtrue)) {
|
||
rb_raise(rb_eArgError, "duplicate member: %"PRIsVALUE, mem);
|
||
}
|
||
}
|
||
rest = rb_hash_keys(rest);
|
||
st_clear(tbl);
|
||
RBASIC_CLEAR_CLASS(rest);
|
||
OBJ_FREEZE_RAW(rest);
|
||
if (NIL_P(name)) {
|
||
st = anonymous_struct(klass);
|
||
}
|
||
else {
|
||
st = new_struct(name, klass);
|
||
}
|
||
setup_struct(st, rest, true);
|
||
rb_ivar_set(st, id_keyword_init, keyword_init);
|
||
if (rb_block_given_p()) {
|
||
rb_mod_module_eval(0, 0, st);
|
||
}
|
||
return st;
|
||
}
|
||
#if 0 /* for RDoc */
|
||
/*
|
||
* call-seq:
|
||
* value == other -> true or false
|
||
*
|
||
* Equality---Returns +true+ if +other+ has the same value subclass and has
|
||
* equal member values (according to Object#==).
|
||
*
|
||
* Customer = Struct::Value.new(:name, :address, :zip)
|
||
* joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
|
||
* joejr = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
|
||
* jane = Customer.new("Jane Doe", "456 Elm, Anytown NC", 12345)
|
||
* joe == joejr #=> true
|
||
* joe == jane #=> false
|
||
*/
|
||
static VALUE
|
||
rb_struct_value_equal(VALUE s, VALUE s2)
|
||
{
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* value.eql?(other) -> true or false
|
||
*
|
||
* Hash equality---+other+ and +struct+ refer to the same hash key if they
|
||
* have the same value subclass and have equal member values (according to
|
||
* Object#eql?).
|
||
*/
|
||
static VALUE
|
||
rb_struct_value_eql(VALUE s, VALUE s2)
|
||
{
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* value.hash -> integer
|
||
*
|
||
* Returns a hash value based on this value's contents.
|
||
*
|
||
* See also Object#hash.
|
||
*/
|
||
static VALUE
|
||
rb_struct_value_hash(VALUE s)
|
||
{
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* value.to_s -> string
|
||
* value.inspect -> string
|
||
*
|
||
* Returns a description of this value as a string.
|
||
*/
|
||
static VALUE
|
||
rb_struct_value_inspect(VALUE s)
|
||
{
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* value.to_h -> hash
|
||
* value.to_h {|name, value| block } -> hash
|
||
*
|
||
* Returns a Hash containing the names and values for the Value's members.
|
||
*
|
||
* If a block is given, the results of the block on each pair of the receiver
|
||
* will be used as pairs.
|
||
*
|
||
* Customer = Struct::Value.new(:name, :address, :zip)
|
||
* joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
|
||
* joe.to_h[:address] #=> "123 Maple, Anytown NC"
|
||
* joe.to_h{|name, value| [name.upcase, value.to_s.upcase]}[:ADDRESS]
|
||
* #=> "123 MAPLE, ANYTOWN NC"
|
||
*/
|
||
static VALUE
|
||
rb_struct_value_to_h(VALUE s)
|
||
{
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* struct.members -> array
|
||
*
|
||
* Returns the value members as an array of symbols:
|
||
*
|
||
* Customer = Struct::Value.new(:name, :address, :zip)
|
||
* joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
|
||
* joe.members #=> [:name, :address, :zip]
|
||
*/
|
||
static VALUE
|
||
rb_struct_value_members_m(VALUE obj)
|
||
{
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* value.each_pair {|sym, obj| block } -> value
|
||
* value.each_pair -> enumerator
|
||
*
|
||
* Yields the name and value of each struct member in order. If no block is
|
||
* given an enumerator is returned.
|
||
*
|
||
* Customer = Struct::Value.new(:name, :address, :zip)
|
||
* joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
|
||
* joe.each_pair {|name, value| puts("#{name} => #{value}") }
|
||
*
|
||
* Produces:
|
||
*
|
||
* name => Joe Smith
|
||
* address => 123 Maple, Anytown NC
|
||
* zip => 12345
|
||
*/
|
||
static VALUE
|
||
rb_struct_value_each_pair(VALUE s)
|
||
{
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* value.deconstruct -> array
|
||
*
|
||
* Returns the values for usage in a pattern matching:
|
||
*
|
||
* Customer = Struct::Value.new(:name, :address, :zip)
|
||
* joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
|
||
* case joe
|
||
* in "Joe Smith", *rest
|
||
* ....
|
||
*/
|
||
static VALUE
|
||
rb_struct_value_deconstruct(VALUE s)
|
||
{
|
||
}
|
||
#endif
|
||
/*
|
||
* Document-class: Struct
|
||
*
|
||
... | ... | |
rb_define_method(rb_cStruct, "dig", rb_struct_dig, -1);
|
||
rb_define_method(rb_cStruct, "deconstruct", rb_struct_to_a, 0);
|
||
/* Struct::Value */
|
||
rb_cStructValue = rb_define_class_under(rb_cStruct, "Value", rb_cObject);
|
||
rb_undef_alloc_func(rb_cStruct);
|
||
rb_define_singleton_method(rb_cStructValue, "new", rb_struct_value_s_def, -1);
|
||
#if 0 /* for RDoc */
|
||
rb_define_method(rb_cStructValue, "initialize", rb_struct_value_initialize_m, -1);
|
||
rb_define_method(rb_cStructValue, "initialize_copy", rb_struct_value_init_copy, 1);
|
||
rb_define_method(rb_cStructValue, "==", rb_struct_value_equal, 1);
|
||
rb_define_method(rb_cStructValue, "eql?", rb_struct_value_eql, 1);
|
||
rb_define_method(rb_cStructValue, "hash", rb_struct_value_hash, 0);
|
||
rb_define_method(rb_cStructValue, "inspect", rb_struct_value_inspect, 0);
|
||
rb_define_alias(rb_cStructValue, "to_s", "inspect");
|
||
rb_define_method(rb_cStructValue, "to_h", rb_struct_value_to_h, 0);
|
||
rb_define_method(rb_cStructValue, "each_pair", rb_struct_value_each_pair, 0);
|
||
rb_define_method(rb_cStructValue, "members", rb_struct_value_members_m, 0);
|
||
rb_define_method(rb_cStructValue, "deconstruct", rb_struct_value_deconstruct, 0);
|
||
#endif
|
||
rb_define_method(rb_cStructValue, "initialize", rb_struct_initialize_m, -1);
|
||
rb_define_method(rb_cStructValue, "initialize_copy", rb_struct_init_copy, 1);
|
||
rb_define_method(rb_cStructValue, "==", rb_struct_equal, 1);
|
||
rb_define_method(rb_cStructValue, "eql?", rb_struct_eql, 1);
|
||
rb_define_method(rb_cStructValue, "hash", rb_struct_hash, 0);
|
||
rb_define_method(rb_cStructValue, "inspect", rb_struct_inspect, 0);
|
||
rb_define_alias(rb_cStructValue, "to_s", "inspect");
|
||
rb_define_method(rb_cStructValue, "to_h", rb_struct_to_h, 0);
|
||
rb_define_method(rb_cStructValue, "each_pair", rb_struct_each_pair, 0);
|
||
rb_define_method(rb_cStructValue, "members", rb_struct_members_m, 0);
|
||
rb_define_method(rb_cStructValue, "deconstruct", rb_struct_to_a, 0);
|
||
}
|
||
#undef rb_intern
|
test/ruby/test_struct_value.rb | ||
---|---|---|
# -*- coding: us-ascii -*-
|
||
# frozen_string_literal: false
|
||
require 'test/unit'
|
||
require 'timeout'
|
||
# Note that @Value (test instance variable) is used here to run tests both on
|
||
# Value and its subclass. Approach is copied from test_struct.rb
|
||
#
|
||
# Most of the tests also copied from there, as Value C implementation mostly
|
||
# reuses appropriate Struct's methods. This is also the reason test doesn't
|
||
# copy specific bug/very small/very large values tests.
|
||
module TestStructValue
|
||
def test_value_value
|
||
value_test = @Value.new("Test", :foo, :bar)
|
||
assert_equal(@Value::Test, value_test)
|
||
test = value_test.new(1, 2)
|
||
assert_equal(1, test.foo)
|
||
assert_equal(2, test.bar)
|
||
# FIXME: Better to have hash, but currently deconstruct only supports arrays?..
|
||
assert_equal([1, 2], test.deconstruct)
|
||
end
|
||
def test_inherit
|
||
klass = @Value.new(:a)
|
||
klass2 = Class.new(klass)
|
||
o = klass2.new(1)
|
||
assert_equal(1, o.a)
|
||
end
|
||
def test_members
|
||
klass = @Value.new(:a)
|
||
o = klass.new(1)
|
||
assert_equal([:a], klass.members)
|
||
assert_equal([:a], o.members)
|
||
end
|
||
def test_value_new
|
||
assert_raise(NameError) { @Value.new("foo") }
|
||
assert_nothing_raised { @Value.new("Foo") }
|
||
@Value.instance_eval { remove_const(:Foo) }
|
||
assert_nothing_raised { @Value.new(:a) { } }
|
||
assert_raise(RuntimeError) { @Value.new(:a) { raise } }
|
||
end
|
||
def test_empty_value
|
||
val = @Value.new
|
||
assert_equal "#<value >", val.new.inspect
|
||
end
|
||
def test_value_new_with_keyword_init
|
||
@Value.new("KeywordInitTrue", :a, :b, keyword_init: true)
|
||
@Value.new("KeywordInitFalse", :a, :b, keyword_init: false)
|
||
assert_raise(ArgumentError) { @Value::KeywordInitTrue.new(1, 2) }
|
||
assert_nothing_raised { @Value::KeywordInitFalse.new(1, 2) }
|
||
assert_nothing_raised { @Value::KeywordInitTrue.new(a: 1, b: 2) }
|
||
assert_raise(ArgumentError) { @Value::KeywordInitTrue.new(1, b: 2) }
|
||
assert_raise(ArgumentError) { @Value::KeywordInitTrue.new(a: 1, b: 2, c: 3) }
|
||
assert_equal "#{@Value}::KeywordInitFalse", @Value::KeywordInitFalse.inspect
|
||
assert_equal "#{@Value}::KeywordInitTrue(keyword_init: true)", @Value::KeywordInitTrue.inspect
|
||
kw = @Value::KeywordInitTrue.new(a: 1, b: 2)
|
||
assert_equal(1, kw.a)
|
||
assert_equal(2, kw.b)
|
||
@Value.instance_eval do
|
||
remove_const(:KeywordInitTrue)
|
||
remove_const(:KeywordInitFalse)
|
||
end
|
||
end
|
||
def test_initialize
|
||
klass = @Value.new(:a)
|
||
assert_raise(ArgumentError) { klass.new(1, 2) }
|
||
klass = @Value.new(:total) do
|
||
def initialize(a, b)
|
||
super(a+b)
|
||
end
|
||
end
|
||
assert_equal 3, klass.new(1,2).total
|
||
end
|
||
def test_each_pair
|
||
klass = @Value.new(:a, :b)
|
||
o = klass.new(1, 2)
|
||
assert_equal([[:a, 1], [:b, 2]], o.each_pair.to_a)
|
||
end
|
||
def test_inspect
|
||
klass = @Value.new(:a)
|
||
o = klass.new(1)
|
||
assert_equal("#<value a=1>", o.inspect)
|
||
@Value.new("Foo", :a)
|
||
o = @Value::Foo.new(1)
|
||
assert_equal("#<value #@Value::Foo a=1>", o.inspect)
|
||
@Value.instance_eval { remove_const(:Foo) }
|
||
klass = @Value.new(:a, :b)
|
||
o = klass.new(1, 2)
|
||
assert_equal("#<value a=1, b=2>", o.inspect)
|
||
klass = @Value.new(:@a)
|
||
o = klass.new(1)
|
||
assert_equal(1, o.__send__(:@a))
|
||
assert_equal("#<value :@a=1>", o.inspect)
|
||
methods = klass.instance_methods(false)
|
||
assert_equal([:@a].sort.inspect, methods.sort.inspect)
|
||
assert_include(methods, :@a)
|
||
end
|
||
def test_init_copy
|
||
klass = @Value.new(:a)
|
||
o = klass.new(1)
|
||
assert_equal(o, o.dup)
|
||
end
|
||
def test_equal
|
||
klass1 = @Value.new(:a)
|
||
klass2 = @Value.new(:a, :b)
|
||
o1 = klass1.new(1)
|
||
o2 = klass1.new(1)
|
||
o3 = klass2.new(1)
|
||
assert_equal(o1, o2)
|
||
assert_not_equal(o1, o3)
|
||
end
|
||
def test_hash
|
||
klass = @Value.new(:a)
|
||
o = klass.new(1)
|
||
assert_kind_of(Integer, o.hash)
|
||
assert_kind_of(String, o.hash.to_s)
|
||
end
|
||
def test_eql
|
||
klass1 = @Value.new(:a)
|
||
klass2 = @Value.new(:a, :b)
|
||
o1 = klass1.new(1)
|
||
o2 = klass1.new(1)
|
||
o3 = klass2.new(1)
|
||
assert_operator(o1, :eql?, o2)
|
||
assert_not_operator(o1, :eql?, o3)
|
||
end
|
||
def test_error
|
||
assert_raise(TypeError){
|
||
@Value.new(0)
|
||
}
|
||
end
|
||
def test_redefinition_warning
|
||
@Value.new("RedefinitionWarning")
|
||
e = EnvUtil.verbose_warning do
|
||
@Value.new("RedefinitionWarning")
|
||
end
|
||
assert_match(/redefining constant #@Value::RedefinitionWarning/, e)
|
||
end
|
||
def test_nonascii
|
||
value_test = @Value.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}")
|
||
assert_equal(@Value.const_get("R\u{e9}sum\u{e9}"), value_test, '[ruby-core:24849]')
|
||
a = value_test.new(42)
|
||
assert_equal("#<value #@Value::R\u{e9}sum\u{e9} r\u{e9}sum\u{e9}=42>", a.inspect, '[ruby-core:24849]')
|
||
e = EnvUtil.verbose_warning do
|
||
@Value.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}")
|
||
end
|
||
assert_nothing_raised(Encoding::CompatibilityError) do
|
||
assert_match(/redefining constant #@Value::R\u{e9}sum\u{e9}/, e)
|
||
end
|
||
end
|
||
def test_junk
|
||
value_test = @Value.new("Foo", "a\000")
|
||
o = value_test.new(1)
|
||
assert_equal(1, o.send("a\000"))
|
||
@Value.instance_eval { remove_const(:Foo) }
|
||
end
|
||
def test_to_h
|
||
klass = @Value.new(:a, :b, :c, :d, :e, :f)
|
||
o = klass.new(1, 2, 3, 4, 5, 6)
|
||
assert_equal({a:1, b:2, c:3, d:4, e:5, f:6}, o.to_h)
|
||
end
|
||
def test_to_h_block
|
||
klass = @Value.new(:a, :b, :c, :d, :e, :f)
|
||
o = klass.new(1, 2, 3, 4, 5, 6)
|
||
assert_equal({"a" => 1, "b" => 4, "c" => 9, "d" => 16, "e" => 25, "f" => 36},
|
||
o.to_h {|k, v| [k.to_s, v*v]})
|
||
end
|
||
def test_question_mark_in_member
|
||
klass = @Value.new(:a, :b?)
|
||
x = Object.new
|
||
o = klass.new("test", x)
|
||
assert_same(x, o.b?)
|
||
end
|
||
def test_bang_mark_in_member
|
||
klass = @Value.new(:a, :b!)
|
||
x = Object.new
|
||
o = klass.new("test", x)
|
||
assert_same(x, o.b!)
|
||
end
|
||
def test_new_dupilicate
|
||
assert_raise_with_message(ArgumentError, /duplicate member/) {
|
||
@Value.new(:a, :a)
|
||
}
|
||
end
|
||
class TopStructValue < Test::Unit::TestCase
|
||
include TestStructValue
|
||
def initialize(*)
|
||
super
|
||
@Value = Struct::Value
|
||
end
|
||
end
|
||
class SubStructValue < Test::Unit::TestCase
|
||
include TestStructValue
|
||
SubStructValue = Class.new(Struct::Value)
|
||
def initialize(*)
|
||
super
|
||
@Value = SubStructValue
|
||
end
|
||
end
|
||
end
|