Project

General

Profile

Feature #16122 ยป struct_value.patch

zverok (Victor Shepelev), 08/23/2019 05:40 PM

View differences:

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
    (1-1/1)