Bläddra i källkod

Ruby extension: added oneof accessor.

Chris Fallin 10 år sedan
förälder
incheckning
e2debef5d8

+ 2 - 1
ruby/ext/google/protobuf_c/encode_decode.c

@@ -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 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
-  // 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);
   if (upb_fielddef_type(f) == UPB_TYPE_MESSAGE) {
     hd->md = upb_fielddef_msgsubdef(f);

+ 45 - 0
ruby/ext/google/protobuf_c/message.c

@@ -70,6 +70,36 @@ VALUE Message_alloc(VALUE klass) {
   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:
  *     Message.method_missing(*args)
@@ -82,6 +112,10 @@ VALUE Message_alloc(VALUE klass) {
  *
  *     msg.foo = 42
  *     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) {
   MessageHeader* self;
@@ -104,6 +138,17 @@ VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
     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,
                                           name, name_len);
 

+ 6 - 0
ruby/tests/basic.rb

@@ -615,30 +615,35 @@ module BasicTest
       assert d.b == nil
       assert d.c == nil
       assert d.d == nil
+      assert d.my_oneof == nil
 
       d.a = "hi"
       assert d.a == "hi"
       assert d.b == nil
       assert d.c == nil
       assert d.d == nil
+      assert d.my_oneof == :a
 
       d.b = 42
       assert d.a == nil
       assert d.b == 42
       assert d.c == nil
       assert d.d == nil
+      assert d.my_oneof == :b
 
       d.c = TestMessage2.new(:foo => 100)
       assert d.a == nil
       assert d.b == nil
       assert d.c.foo == 100
       assert d.d == nil
+      assert d.my_oneof == :c
 
       d.d = :C
       assert d.a == nil
       assert d.b == nil
       assert d.c == nil
       assert d.d == :C
+      assert d.my_oneof == :d
 
       d2 = OneofMessage.decode(OneofMessage.encode(d))
       assert d2 == d
@@ -669,6 +674,7 @@ module BasicTest
       d5.a = nil
       assert d5.a == nil
       assert OneofMessage.encode(d5) == ''
+      assert d5.my_oneof == nil
     end
 
     def test_enum_field