diff --git a/struct.c b/struct.c index 817e0a1b9b..df627b3955 100644 --- a/struct.c +++ b/struct.c @@ -816,16 +816,57 @@ rb_struct_init_copy(VALUE copy, VALUE s) return copy; } -static VALUE -rb_struct_aref_sym(VALUE s, VALUE name) +static int +rb_struct_pos(VALUE s, VALUE *name) { - int pos = struct_member_pos(s, name); - if (pos != -1) { - return RSTRUCT_GET(s, pos); - } - rb_name_err_raise("no member '%1$s' in struct", s, name); + long i; + VALUE idx = *name; - UNREACHABLE; + if (RB_TYPE_P(idx, T_SYMBOL)) { + return struct_member_pos(s, idx); + } + else if (RB_TYPE_P(idx, T_STRING)) { + idx = rb_check_symbol(name); + if (NIL_P(idx)) return -1; + return struct_member_pos(s, idx); + } + else { + long len; + i = NUM2LONG(idx); + len = RSTRUCT_LEN(s); + if (i < 0) { + if (i + len < 0) { + *name = LONG2FIX(i); + return -1; + } + i += len; + } + else if (len <= i) { + *name = LONG2FIX(i); + return -1; + } + return (int)i; + } +} + +NORETURN(static void invalid_struct_pos(VALUE s, VALUE idx)); +static void +invalid_struct_pos(VALUE s, VALUE idx) +{ + if (FIXNUM_P(idx)) { + long i = FIX2INT(idx), len = RSTRUCT_LEN(s); + if (i < 0) { + rb_raise(rb_eIndexError, "offset %ld too small for struct(size:%ld)", + i, len); + } + else { + rb_raise(rb_eIndexError, "offset %ld too large for struct(size:%ld)", + i, len); + } + } + else { + rb_name_err_raise("no member '%1$s' in struct", s, idx); + } } /* @@ -848,46 +889,11 @@ rb_struct_aref_sym(VALUE s, VALUE name) VALUE rb_struct_aref(VALUE s, VALUE idx) { - long i; - - if (RB_TYPE_P(idx, T_SYMBOL)) { - return rb_struct_aref_sym(s, idx); - } - else if (RB_TYPE_P(idx, T_STRING)) { - ID id = rb_check_id(&idx); - if (!id) { - rb_name_err_raise("no member '%1$s' in struct", - s, idx); - } - return rb_struct_aref_sym(s, ID2SYM(id)); - } - - i = NUM2LONG(idx); - if (i < 0) i = RSTRUCT_LEN(s) + i; - if (i < 0) - rb_raise(rb_eIndexError, "offset %ld too small for struct(size:%ld)", - i, RSTRUCT_LEN(s)); - if (RSTRUCT_LEN(s) <= i) - rb_raise(rb_eIndexError, "offset %ld too large for struct(size:%ld)", - i, RSTRUCT_LEN(s)); + int i = rb_struct_pos(s, &idx); + if (i < 0) invalid_struct_pos(s, idx); return RSTRUCT_GET(s, i); } -static VALUE -rb_struct_aset_sym(VALUE s, VALUE name, VALUE val) -{ - int pos = struct_member_pos(s, name); - if (pos != -1) { - rb_struct_modify(s); - RSTRUCT_SET(s, pos, val); - return val; - } - - rb_name_err_raise("no member '%1$s' in struct", s, name); - - UNREACHABLE; -} - /* * call-seq: * struct[name] = obj -> obj @@ -910,30 +916,8 @@ rb_struct_aset_sym(VALUE s, VALUE name, VALUE val) VALUE rb_struct_aset(VALUE s, VALUE idx, VALUE val) { - long i; - - if (RB_TYPE_P(idx, T_SYMBOL)) { - return rb_struct_aset_sym(s, idx, val); - } - if (RB_TYPE_P(idx, T_STRING)) { - ID id = rb_check_id(&idx); - if (!id) { - rb_name_err_raise("no member '%1$s' in struct", - s, idx); - } - return rb_struct_aset_sym(s, ID2SYM(id), val); - } - - i = NUM2LONG(idx); - if (i < 0) i = RSTRUCT_LEN(s) + i; - if (i < 0) { - rb_raise(rb_eIndexError, "offset %ld too small for struct(size:%ld)", - i, RSTRUCT_LEN(s)); - } - if (RSTRUCT_LEN(s) <= i) { - rb_raise(rb_eIndexError, "offset %ld too large for struct(size:%ld)", - i, RSTRUCT_LEN(s)); - } + int i = rb_struct_pos(s, &idx); + if (i < 0) invalid_struct_pos(s, idx); rb_struct_modify(s); RSTRUCT_SET(s, i, val); return val; diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb index df859f75cb..f51432cfd6 100644 --- a/test/ruby/test_struct.rb +++ b/test/ruby/test_struct.rb @@ -155,8 +155,8 @@ module TestStruct klass = @Struct.new(:a) o = klass.new(1) assert_equal(1, o[0]) - assert_raise(IndexError) { o[-2] } - assert_raise(IndexError) { o[1] } + assert_raise_with_message(IndexError, /offset -2\b/) {o[-2]} + assert_raise_with_message(IndexError, /offset 1\b/) {o[1]} assert_raise_with_message(NameError, /foo/) {o["foo"]} assert_raise_with_message(NameError, /foo/) {o[:foo]} end @@ -166,8 +166,8 @@ module TestStruct o = klass.new(1) o[0] = 2 assert_equal(2, o[:a]) - assert_raise(IndexError) { o[-2] = 3 } - assert_raise(IndexError) { o[1] = 3 } + assert_raise_with_message(IndexError, /offset -2\b/) {o[-2] = 3} + assert_raise_with_message(IndexError, /offset 1\b/) {o[1] = 3} assert_raise_with_message(NameError, /foo/) {o["foo"] = 3} assert_raise_with_message(NameError, /foo/) {o[:foo] = 3} end