Commit e2debef5 authored by Chris Fallin's avatar Chris Fallin

Ruby extension: added oneof accessor.

parent e1b7d38d
...@@ -77,7 +77,8 @@ static const void *newoneofhandlerdata(upb_handlers *h, ...@@ -77,7 +77,8 @@ static const void *newoneofhandlerdata(upb_handlers *h,
// we don't expose these numbers to the user, so the only requirement is that // we don't expose these numbers to the user, so the only requirement is that
// we have some unique ID for each union case/possibility. The field tag // we have some unique ID for each union case/possibility. The field tag
// numbers are already present and are easy to use so there's no reason to // numbers are already present and are easy to use so there's no reason to
// create a separate ID space. // create a separate ID space. In addition, using the field tag number here
// lets us easily look up the field in the oneof accessor.
hd->oneof_case_num = upb_fielddef_number(f); hd->oneof_case_num = upb_fielddef_number(f);
if (upb_fielddef_type(f) == UPB_TYPE_MESSAGE) { if (upb_fielddef_type(f) == UPB_TYPE_MESSAGE) {
hd->md = upb_fielddef_msgsubdef(f); hd->md = upb_fielddef_msgsubdef(f);
......
...@@ -70,6 +70,36 @@ VALUE Message_alloc(VALUE klass) { ...@@ -70,6 +70,36 @@ VALUE Message_alloc(VALUE klass) {
return ret; return ret;
} }
static VALUE which_oneof_field(MessageHeader* self, const upb_oneofdef* o) {
// If no fields in the oneof, always nil.
if (upb_oneofdef_numfields(o) == 0) {
return Qnil;
}
// Grab the first field in the oneof so we can get its layout info to find the
// oneof_case field.
upb_oneof_iter it;
upb_oneof_begin(&it, o);
assert(!upb_oneof_done(&it));
const upb_fielddef* first_field = upb_oneof_iter_field(&it);
assert(upb_fielddef_containingoneof(first_field) != NULL);
size_t case_ofs =
self->descriptor->layout->
fields[upb_fielddef_index(first_field)].case_offset;
uint32_t oneof_case = *((uint32_t*)(Message_data(self) + case_ofs));
// oneof_case == 0 indicates no field set.
if (oneof_case == 0) {
return Qnil;
}
// oneof_case is a field index, so find that field.
const upb_fielddef* f = upb_oneofdef_itof(o, oneof_case);
assert(f != NULL);
return ID2SYM(rb_intern(upb_fielddef_name(f)));
}
/* /*
* call-seq: * call-seq:
* Message.method_missing(*args) * Message.method_missing(*args)
...@@ -82,6 +112,10 @@ VALUE Message_alloc(VALUE klass) { ...@@ -82,6 +112,10 @@ VALUE Message_alloc(VALUE klass) {
* *
* msg.foo = 42 * msg.foo = 42
* puts msg.foo * puts msg.foo
*
* This method also provides read-only accessors for oneofs. If a oneof exists
* with name 'my_oneof', then msg.my_oneof will return a Ruby symbol equal to
* the name of the field in that oneof that is currently set, or nil if none.
*/ */
VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) { VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
MessageHeader* self; MessageHeader* self;
...@@ -104,6 +138,17 @@ VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) { ...@@ -104,6 +138,17 @@ VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
name_len--; name_len--;
} }
// Check for a oneof name first.
const upb_oneofdef* o = upb_msgdef_ntoo(self->descriptor->msgdef,
name, name_len);
if (o != NULL) {
if (setter) {
rb_raise(rb_eRuntimeError, "Oneof accessors are read-only.");
}
return which_oneof_field(self, o);
}
// Otherwise, check for a field with that name.
const upb_fielddef* f = upb_msgdef_ntof(self->descriptor->msgdef, const upb_fielddef* f = upb_msgdef_ntof(self->descriptor->msgdef,
name, name_len); name, name_len);
......
...@@ -615,30 +615,35 @@ module BasicTest ...@@ -615,30 +615,35 @@ module BasicTest
assert d.b == nil assert d.b == nil
assert d.c == nil assert d.c == nil
assert d.d == nil assert d.d == nil
assert d.my_oneof == nil
d.a = "hi" d.a = "hi"
assert d.a == "hi" assert d.a == "hi"
assert d.b == nil assert d.b == nil
assert d.c == nil assert d.c == nil
assert d.d == nil assert d.d == nil
assert d.my_oneof == :a
d.b = 42 d.b = 42
assert d.a == nil assert d.a == nil
assert d.b == 42 assert d.b == 42
assert d.c == nil assert d.c == nil
assert d.d == nil assert d.d == nil
assert d.my_oneof == :b
d.c = TestMessage2.new(:foo => 100) d.c = TestMessage2.new(:foo => 100)
assert d.a == nil assert d.a == nil
assert d.b == nil assert d.b == nil
assert d.c.foo == 100 assert d.c.foo == 100
assert d.d == nil assert d.d == nil
assert d.my_oneof == :c
d.d = :C d.d = :C
assert d.a == nil assert d.a == nil
assert d.b == nil assert d.b == nil
assert d.c == nil assert d.c == nil
assert d.d == :C assert d.d == :C
assert d.my_oneof == :d
d2 = OneofMessage.decode(OneofMessage.encode(d)) d2 = OneofMessage.decode(OneofMessage.encode(d))
assert d2 == d assert d2 == d
...@@ -669,6 +674,7 @@ module BasicTest ...@@ -669,6 +674,7 @@ module BasicTest
d5.a = nil d5.a = nil
assert d5.a == nil assert d5.a == nil
assert OneofMessage.encode(d5) == '' assert OneofMessage.encode(d5) == ''
assert d5.my_oneof == nil
end end
def test_enum_field def test_enum_field
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment