Przeglądaj źródła

Merge pull request #6797 from haberman/ruby-lazy-wrappers

Ruby lazy wrappers optimization
Joshua Haberman 5 lat temu
rodzic
commit
0eb9b279e2

+ 222 - 15
ruby/ext/google/protobuf_c/encode_decode.c

@@ -278,6 +278,17 @@ static void *appendsubmsg_handler(void *closure, const void *hd) {
   return submsg;
 }
 
+// Appends a wrapper to a repeated field (a regular Ruby array for now).
+static void *appendwrapper_handler(void *closure, const void *hd) {
+  VALUE ary = (VALUE)closure;
+  int size = RepeatedField_size(ary);
+  (void)hd;
+
+  RepeatedField_push(ary, Qnil);
+
+  return RepeatedField_index_native(ary, size);
+}
+
 // Sets a non-repeated submessage field in a message.
 static void *submsg_handler(void *closure, const void *hd) {
   MessageHeader* msg = closure;
@@ -298,6 +309,15 @@ static void *submsg_handler(void *closure, const void *hd) {
   return submsg;
 }
 
+static void* startwrapper(void* closure, const void* hd) {
+  char* msg = closure;
+  const submsg_handlerdata_t* submsgdata = hd;
+
+  set_hasbit(closure, submsgdata->hasbit);
+
+  return msg + submsgdata->ofs;
+}
+
 // Handler data for startmap/endmap handlers.
 typedef struct {
   size_t ofs;
@@ -486,14 +506,39 @@ static void *oneofsubmsg_handler(void *closure,
   // indicating a VALUE is present and expect a valid VALUE. See comment in
   // layout_set() for more detail: basically, the change to the value and the
   // case must be atomic w.r.t. the Ruby VM.
-  DEREF(msg, oneofdata->case_ofs, uint32_t) =
-      oneofdata->oneof_case_num;
+  DEREF(msg, oneofdata->case_ofs, uint32_t) = oneofdata->oneof_case_num;
 
   submsg_rb = DEREF(msg, oneofdata->ofs, VALUE);
   TypedData_Get_Struct(submsg_rb, MessageHeader, &Message_type, submsg);
   return submsg;
 }
 
+static void* oneof_startwrapper(void* closure, const void* hd) {
+  char* msg = closure;
+  const oneof_handlerdata_t *oneofdata = hd;
+
+  DEREF(msg, oneofdata->case_ofs, uint32_t) = oneofdata->oneof_case_num;
+
+  return msg + oneofdata->ofs;
+}
+
+bool is_wrapper(const upb_msgdef* m) {
+  switch (upb_msgdef_wellknowntype(m)) {
+    case UPB_WELLKNOWN_DOUBLEVALUE:
+    case UPB_WELLKNOWN_FLOATVALUE:
+    case UPB_WELLKNOWN_INT64VALUE:
+    case UPB_WELLKNOWN_UINT64VALUE:
+    case UPB_WELLKNOWN_INT32VALUE:
+    case UPB_WELLKNOWN_UINT32VALUE:
+    case UPB_WELLKNOWN_STRINGVALUE:
+    case UPB_WELLKNOWN_BYTESVALUE:
+    case UPB_WELLKNOWN_BOOLVALUE:
+      return true;
+    default:
+      return false;
+  }
+}
+
 // Set up handlers for a repeated field.
 static void add_handlers_for_repeated_field(upb_handlers *h,
                                             const Descriptor* desc,
@@ -535,12 +580,96 @@ static void add_handlers_for_repeated_field(upb_handlers *h,
       VALUE subklass = field_type_class(desc->layout, f);
       upb_handlerattr attr = UPB_HANDLERATTR_INIT;
       attr.handler_data = newsubmsghandlerdata(h, 0, -1, subklass);
-      upb_handlers_setstartsubmsg(h, f, appendsubmsg_handler, &attr);
+      if (is_wrapper(upb_fielddef_msgsubdef(f))) {
+        upb_handlers_setstartsubmsg(h, f, appendwrapper_handler, &attr);
+      } else {
+        upb_handlers_setstartsubmsg(h, f, appendsubmsg_handler, &attr);
+      }
       break;
     }
   }
 }
 
+static bool doublewrapper_handler(void* closure, const void* hd, double val) {
+  VALUE* rbval = closure;
+  *rbval = DBL2NUM(val);
+  return true;
+}
+
+static bool floatwrapper_handler(void* closure, const void* hd, float val) {
+  VALUE* rbval = closure;
+  *rbval = DBL2NUM(val);
+  return true;
+}
+
+static bool int64wrapper_handler(void* closure, const void* hd, int64_t val) {
+  VALUE* rbval = closure;
+  *rbval = LL2NUM(val);
+  return true;
+}
+
+static bool uint64wrapper_handler(void* closure, const void* hd, uint64_t val) {
+  VALUE* rbval = closure;
+  *rbval = ULL2NUM(val);
+  return true;
+}
+
+static bool int32wrapper_handler(void* closure, const void* hd, int32_t val) {
+  VALUE* rbval = closure;
+  *rbval = INT2NUM(val);
+  return true;
+}
+
+static bool uint32wrapper_handler(void* closure, const void* hd, uint32_t val) {
+  VALUE* rbval = closure;
+  *rbval = UINT2NUM(val);
+  return true;
+}
+
+static void* startstringwrapper_handler(void* closure, const void* hd,
+                                        size_t size_hint) {
+  VALUE* rbval = closure;
+  (void)size_hint;
+  *rbval = rb_str_new(NULL, 0);
+  rb_enc_associate(*rbval, kRubyStringUtf8Encoding);
+  return closure;
+}
+
+static size_t stringwrapper_handler(void* closure, const void* hd,
+                                    const char* ptr, size_t len,
+                                    const upb_bufhandle* handle) {
+  VALUE* rbval = closure;
+  *rbval = noleak_rb_str_cat(*rbval, ptr, len);
+  return len;
+}
+
+static void* startbyteswrapper_handler(void* closure, const void* hd,
+                                       size_t size_hint) {
+  VALUE* rbval = closure;
+  (void)size_hint;
+  *rbval = rb_str_new(NULL, 0);
+  rb_enc_associate(*rbval, kRubyString8bitEncoding);
+  return closure;
+}
+
+static size_t byteswrapper_handler(void* closure, const void* hd,
+                                   const char* ptr, size_t len,
+                                   const upb_bufhandle* handle) {
+  VALUE* rbval = closure;
+  *rbval = noleak_rb_str_cat(*rbval, ptr, len);
+  return len;
+}
+
+static bool boolwrapper_handler(void* closure, const void* hd, bool val) {
+  VALUE* rbval = closure;
+  if (val) {
+    *rbval = Qtrue;
+  } else {
+    *rbval = Qfalse;
+  }
+  return true;
+}
+
 // Set up handlers for a singular field.
 static void add_handlers_for_singular_field(const Descriptor* desc,
                                             upb_handlers* h,
@@ -580,8 +709,11 @@ static void add_handlers_for_singular_field(const Descriptor* desc,
       upb_handlerattr attr = UPB_HANDLERATTR_INIT;
       attr.handler_data = newsubmsghandlerdata(
           h, offset, hasbit, field_type_class(desc->layout, f));
-      upb_handlers_setstartsubmsg(h, f, submsg_handler, &attr);
-      break;
+      if (is_wrapper(upb_fielddef_msgsubdef(f))) {
+        upb_handlers_setstartsubmsg(h, f, startwrapper, &attr);
+      } else {
+        upb_handlers_setstartsubmsg(h, f, submsg_handler, &attr);
+      }
     }
   }
 }
@@ -623,6 +755,45 @@ static void add_handlers_for_mapentry(const upb_msgdef* msgdef, upb_handlers* h,
       MESSAGE_FIELD_NO_HASBIT);
 }
 
+static void add_handlers_for_wrapper(const upb_msgdef* msgdef,
+                                     upb_handlers* h) {
+  const upb_fielddef* f = upb_msgdef_itof(msgdef, 1);
+  switch (upb_msgdef_wellknowntype(msgdef)) {
+    case UPB_WELLKNOWN_DOUBLEVALUE:
+      upb_handlers_setdouble(h, f, doublewrapper_handler, NULL);
+      break;
+    case UPB_WELLKNOWN_FLOATVALUE:
+      upb_handlers_setfloat(h, f, floatwrapper_handler, NULL);
+      break;
+    case UPB_WELLKNOWN_INT64VALUE:
+      upb_handlers_setint64(h, f, int64wrapper_handler, NULL);
+      break;
+    case UPB_WELLKNOWN_UINT64VALUE:
+      upb_handlers_setuint64(h, f, uint64wrapper_handler, NULL);
+      break;
+    case UPB_WELLKNOWN_INT32VALUE:
+      upb_handlers_setint32(h, f, int32wrapper_handler, NULL);
+      break;
+    case UPB_WELLKNOWN_UINT32VALUE:
+      upb_handlers_setuint32(h, f, uint32wrapper_handler, NULL);
+      break;
+    case UPB_WELLKNOWN_STRINGVALUE:
+      upb_handlers_setstartstr(h, f, startstringwrapper_handler, NULL);
+      upb_handlers_setstring(h, f, stringwrapper_handler, NULL);
+      break;
+    case UPB_WELLKNOWN_BYTESVALUE:
+      upb_handlers_setstartstr(h, f, startbyteswrapper_handler, NULL);
+      upb_handlers_setstring(h, f, byteswrapper_handler, NULL);
+      break;
+    case UPB_WELLKNOWN_BOOLVALUE:
+      upb_handlers_setbool(h, f, boolwrapper_handler, NULL);
+      return;
+    default:
+      rb_raise(rb_eRuntimeError,
+               "Internal logic error with well-known types.");
+  }
+}
+
 // Set up handlers for a oneof field.
 static void add_handlers_for_oneof_field(upb_handlers *h,
                                          const upb_fielddef *f,
@@ -662,7 +833,11 @@ static void add_handlers_for_oneof_field(upb_handlers *h,
       break;
     }
     case UPB_TYPE_MESSAGE: {
-      upb_handlers_setstartsubmsg(h, f, oneofsubmsg_handler, &attr);
+      if (is_wrapper(upb_fielddef_msgsubdef(f))) {
+        upb_handlers_setstartsubmsg(h, f, oneof_startwrapper, &attr);
+      } else {
+        upb_handlers_setstartsubmsg(h, f, oneofsubmsg_handler, &attr);
+      }
       break;
     }
   }
@@ -683,6 +858,10 @@ static bool unknown_field_handler(void* closure, const void* hd,
   return true;
 }
 
+size_t get_field_offset(MessageLayout* layout, const upb_fielddef* f) {
+  return layout->fields[upb_fielddef_index(f)].offset + sizeof(MessageHeader);
+}
+
 void add_handlers_for_message(const void *closure, upb_handlers *h) {
   const VALUE descriptor_pool = (VALUE)closure;
   const upb_msgdef* msgdef = upb_handlers_msgdef(h);
@@ -706,6 +885,12 @@ void add_handlers_for_message(const void *closure, upb_handlers *h) {
     return;
   }
 
+  // If this is a wrapper type, use special handlers and bail.
+  if (is_wrapper(msgdef)) {
+    add_handlers_for_wrapper(msgdef, h);
+    return;
+  }
+
   upb_handlers_setunknown(h, unknown_field_handler, &attr);
 
   for (upb_msg_field_begin(&i, desc->msgdef);
@@ -713,8 +898,7 @@ void add_handlers_for_message(const void *closure, upb_handlers *h) {
        upb_msg_field_next(&i)) {
     const upb_fielddef *f = upb_msg_iter_field(&i);
     const upb_oneofdef *oneof = upb_fielddef_containingoneof(f);
-    size_t offset = desc->layout->fields[upb_fielddef_index(f)].offset +
-        sizeof(MessageHeader);
+    size_t offset = get_field_offset(desc->layout, f);
 
     if (oneof) {
       size_t oneof_case_offset =
@@ -826,17 +1010,28 @@ VALUE Message_decode(VALUE klass, VALUE data) {
   {
     const upb_pbdecodermethod* method = msgdef_decodermethod(desc);
     const upb_handlers* h = upb_pbdecodermethod_desthandlers(method);
+    const upb_msgdef* m = upb_handlers_msgdef(h);
+    VALUE wrapper = Qnil;
+    void* ptr = msg;
     stackenv se;
     upb_sink sink;
     upb_pbdecoder* decoder;
     stackenv_init(&se, "Error occurred during parsing: %" PRIsVALUE);
 
-    upb_sink_reset(&sink, h, msg);
+    if (is_wrapper(m)) {
+      ptr = &wrapper;
+    }
+
+    upb_sink_reset(&sink, h, ptr);
     decoder = upb_pbdecoder_create(se.arena, method, sink, &se.status);
     upb_bufsrc_putbuf(RSTRING_PTR(data), RSTRING_LEN(data),
                       upb_pbdecoder_input(decoder));
 
     stackenv_uninit(&se);
+
+    if (is_wrapper(m)) {
+      msg_rb = ruby_wrapper_type(msgklass, wrapper);
+    }
   }
 
   return msg_rb;
@@ -890,13 +1085,21 @@ VALUE Message_decode_json(int argc, VALUE* argv, VALUE klass) {
 
   {
     const upb_json_parsermethod* method = msgdef_jsonparsermethod(desc);
+    const upb_handlers* h = get_fill_handlers(desc);
+    const upb_msgdef* m = upb_handlers_msgdef(h);
     stackenv se;
     upb_sink sink;
     upb_json_parser* parser;
     DescriptorPool* pool = ruby_to_DescriptorPool(generated_pool);
     stackenv_init(&se, "Error occurred during parsing: %" PRIsVALUE);
 
-    upb_sink_reset(&sink, get_fill_handlers(desc), msg);
+    if (is_wrapper(m)) {
+      rb_raise(
+          rb_eRuntimeError,
+          "Parsing a wrapper type from JSON at the top level does not work.");
+    }
+
+    upb_sink_reset(&sink, h, msg);
     parser = upb_json_parser_create(se.arena, method, pool->symtab, sink,
                                     &se.status, RTEST(ignore_unknown_fields));
     upb_bufsrc_putbuf(RSTRING_PTR(data), RSTRING_LEN(data),
@@ -969,6 +1172,7 @@ static void putary(VALUE ary, const upb_fielddef* f, upb_sink sink, int depth,
   upb_selector_t sel = 0;
   int size;
   int i;
+  VALUE type_class = ruby_to_RepeatedField(ary)->field_type_class;
 
   if (ary == Qnil) return;
   if (!emit_defaults && NUM2INT(RepeatedField_length(ary)) == 0) return;
@@ -1003,9 +1207,11 @@ static void putary(VALUE ary, const upb_fielddef* f, upb_sink sink, int depth,
       case UPB_TYPE_BYTES:
         putstr(*((VALUE *)memory), f, subsink);
         break;
-      case UPB_TYPE_MESSAGE:
-        putsubmsg(*((VALUE*)memory), f, subsink, depth, emit_defaults, is_json);
+      case UPB_TYPE_MESSAGE: {
+        VALUE val = native_slot_get(UPB_TYPE_MESSAGE, type_class, memory);
+        putsubmsg(val, f, subsink, depth, emit_defaults, is_json);
         break;
+      }
 
 #undef T
 
@@ -1306,8 +1512,10 @@ static void putmsg(VALUE msg_rb, const Descriptor* desc,
         putstr(str, f, sink);
       }
     } else if (upb_fielddef_issubmsg(f)) {
-      putsubmsg(DEREF(msg, offset, VALUE), f, sink, depth,
-                emit_defaults, is_json);
+      // OPT: could try to avoid the layout_get() (which will expand lazy
+      // wrappers).
+      VALUE val = layout_get(desc->layout, Message_data(msg), f);
+      putsubmsg(val, f, sink, depth, emit_defaults, is_json);
     } else {
       upb_selector_t sel = getsel(f, upb_handlers_getprimitivehandlertype(f));
 
@@ -1341,7 +1549,6 @@ static void putmsg(VALUE msg_rb, const Descriptor* desc,
       }
 
 #undef T
-
     }
   }
 

+ 4 - 2
ruby/ext/google/protobuf_c/map.c

@@ -559,7 +559,8 @@ VALUE Map_deep_copy(VALUE _self) {
     void* mem = value_memory(&v);
     upb_value dup;
     void* dup_mem = value_memory(&dup);
-    native_slot_deep_copy(self->value_type, dup_mem, mem);
+    native_slot_deep_copy(self->value_type, self->value_type_class, dup_mem,
+                          mem);
 
     if (!upb_strtable_insert2(&new_self->table,
                               upb_strtable_iter_key(&it),
@@ -631,7 +632,8 @@ VALUE Map_eq(VALUE _self, VALUE _other) {
       return Qfalse;
     }
 
-    if (!native_slot_eq(self->value_type, mem, other_mem)) {
+    if (!native_slot_eq(self->value_type, self->value_type_class, mem,
+                        other_mem)) {
       // Present, but value not equal.
       return Qfalse;
     }

+ 34 - 25
ruby/ext/google/protobuf_c/message.c

@@ -62,13 +62,12 @@ VALUE Message_alloc(VALUE klass) {
   Descriptor* desc = ruby_to_Descriptor(descriptor);
   MessageHeader* msg;
   VALUE ret;
-  size_t size;
 
   if (desc->layout == NULL) {
     create_layout(desc);
   }
 
-  msg = ALLOC_N(uint8_t, sizeof(MessageHeader) + desc->layout->size);
+  msg = (void*)ALLOC_N(uint8_t, sizeof(MessageHeader) + desc->layout->size);
   msg->descriptor = desc;
   msg->unknown_fields = NULL;
   memcpy(Message_data(msg), desc->layout->empty_template, desc->layout->size);
@@ -109,30 +108,36 @@ enum {
 };
 
 // Check if the field is a well known wrapper type
-static bool is_wrapper_type_field(const MessageLayout* layout,
-                                  const upb_fielddef* field) {
-  const char* field_type_name = rb_class2name(field_type_class(layout, field));
-
-  return strcmp(field_type_name, "Google::Protobuf::DoubleValue") == 0 ||
-         strcmp(field_type_name, "Google::Protobuf::FloatValue") == 0 ||
-         strcmp(field_type_name, "Google::Protobuf::Int32Value") == 0 ||
-         strcmp(field_type_name, "Google::Protobuf::Int64Value") == 0 ||
-         strcmp(field_type_name, "Google::Protobuf::UInt32Value") == 0 ||
-         strcmp(field_type_name, "Google::Protobuf::UInt64Value") == 0 ||
-         strcmp(field_type_name, "Google::Protobuf::BoolValue") == 0 ||
-         strcmp(field_type_name, "Google::Protobuf::StringValue") == 0 ||
-         strcmp(field_type_name, "Google::Protobuf::BytesValue") == 0;
+bool is_wrapper_type_field(const upb_fielddef* field) {
+  const upb_msgdef *m;
+  if (upb_fielddef_type(field) != UPB_TYPE_MESSAGE) {
+    return false;
+  }
+  m = upb_fielddef_msgsubdef(field);
+  switch (upb_msgdef_wellknowntype(m)) {
+    case UPB_WELLKNOWN_DOUBLEVALUE:
+    case UPB_WELLKNOWN_FLOATVALUE:
+    case UPB_WELLKNOWN_INT64VALUE:
+    case UPB_WELLKNOWN_UINT64VALUE:
+    case UPB_WELLKNOWN_INT32VALUE:
+    case UPB_WELLKNOWN_UINT32VALUE:
+    case UPB_WELLKNOWN_STRINGVALUE:
+    case UPB_WELLKNOWN_BYTESVALUE:
+    case UPB_WELLKNOWN_BOOLVALUE:
+      return true;
+    default:
+      return false;
+  }
 }
 
 // Get a new Ruby wrapper type and set the initial value
-static VALUE ruby_wrapper_type(const MessageLayout* layout,
-                               const upb_fielddef* field, const VALUE value) {
-  if (is_wrapper_type_field(layout, field) && value != Qnil) {
+VALUE ruby_wrapper_type(VALUE type_class, VALUE value) {
+  if (value != Qnil) {
     VALUE hash = rb_hash_new();
     rb_hash_aset(hash, rb_str_new2("value"), value);
     {
       VALUE args[1] = {hash};
-      return rb_class_new_instance(1, args, field_type_class(layout, field));
+      return rb_class_new_instance(1, args, type_class);
     }
   }
   return Qnil;
@@ -193,8 +198,7 @@ static int extract_method_call(VALUE method_name, MessageHeader* self,
     // Check if field exists and is a wrapper type
     if (upb_msgdef_lookupname(self->descriptor->msgdef, wrapper_field_name,
                               name_len - 9, &test_f_wrapper, &test_o_wrapper) &&
-        upb_fielddef_type(test_f_wrapper) == UPB_TYPE_MESSAGE &&
-        is_wrapper_type_field(self->descriptor->layout, test_f_wrapper)) {
+        is_wrapper_type_field(test_f_wrapper)) {
       // It does exist!
       has_field = true;
       if (accessor_type == METHOD_SETTER) {
@@ -329,12 +333,17 @@ VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
     return layout_has(self->descriptor->layout, Message_data(self), f);
   } else if (accessor_type == METHOD_WRAPPER_GETTER) {
     VALUE value = layout_get(self->descriptor->layout, Message_data(self), f);
-    if (value != Qnil) {
-      value = rb_funcall(value, rb_intern("value"), 0);
+    switch (TYPE(value)) {
+      case T_DATA:
+        return rb_funcall(value, rb_intern("value"), 0);
+      case T_NIL:
+        return Qnil;
+      default:
+        return value;
     }
-    return value;
   } else if (accessor_type == METHOD_WRAPPER_SETTER) {
-    VALUE wrapper = ruby_wrapper_type(self->descriptor->layout, f, argv[1]);
+    VALUE wrapper = ruby_wrapper_type(
+        field_type_class(self->descriptor->layout, f), argv[1]);
     layout_set(self->descriptor->layout, Message_data(self), f, wrapper);
     return Qnil;
   } else if (accessor_type == METHOD_ENUM_GETTER) {

+ 7 - 2
ruby/ext/google/protobuf_c/protobuf.h

@@ -363,8 +363,10 @@ VALUE native_slot_get(upb_fieldtype_t type,
 void native_slot_init(upb_fieldtype_t type, void* memory);
 void native_slot_mark(upb_fieldtype_t type, void* memory);
 void native_slot_dup(upb_fieldtype_t type, void* to, void* from);
-void native_slot_deep_copy(upb_fieldtype_t type, void* to, void* from);
-bool native_slot_eq(upb_fieldtype_t type, void* mem1, void* mem2);
+void native_slot_deep_copy(upb_fieldtype_t type, VALUE type_class, void* to,
+                           void* from);
+bool native_slot_eq(upb_fieldtype_t type, VALUE type_class, void* mem1,
+                    void* mem2);
 
 VALUE native_slot_encode_and_freeze_string(upb_fieldtype_t type, VALUE value);
 void native_slot_check_int_range_precision(const char* name, upb_fieldtype_t type, VALUE value);
@@ -556,6 +558,9 @@ VALUE layout_eq(MessageLayout* layout, void* msg1, void* msg2);
 VALUE layout_hash(MessageLayout* layout, void* storage);
 VALUE layout_inspect(MessageLayout* layout, void* storage);
 
+bool is_wrapper_type_field(const upb_fielddef* field);
+VALUE ruby_wrapper_type(VALUE type_class, VALUE value);
+
 // -----------------------------------------------------------------------------
 // Message class creation.
 // -----------------------------------------------------------------------------

+ 3 - 2
ruby/ext/google/protobuf_c/repeated_field.c

@@ -378,7 +378,7 @@ VALUE RepeatedField_deep_copy(VALUE _self) {
   for (i = 0; i < self->size; i++, off += elem_size) {
     void* to_mem = (uint8_t *)new_rptfield_self->elements + off;
     void* from_mem = (uint8_t *)self->elements + off;
-    native_slot_deep_copy(field_type, to_mem, from_mem);
+    native_slot_deep_copy(field_type, self->field_type_class, to_mem, from_mem);
     new_rptfield_self->size++;
   }
 
@@ -451,7 +451,8 @@ VALUE RepeatedField_eq(VALUE _self, VALUE _other) {
     for (i = 0; i < self->size; i++, off += elem_size) {
       void* self_mem = ((uint8_t *)self->elements) + off;
       void* other_mem = ((uint8_t *)other->elements) + off;
-      if (!native_slot_eq(field_type, self_mem, other_mem)) {
+      if (!native_slot_eq(field_type, self->field_type_class, self_mem,
+                          other_mem)) {
         return Qfalse;
       }
     }

+ 31 - 10
ruby/ext/google/protobuf_c/storage.c

@@ -294,8 +294,20 @@ VALUE native_slot_get(upb_fieldtype_t type,
       return DEREF(memory, int8_t) ? Qtrue : Qfalse;
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
-    case UPB_TYPE_MESSAGE:
       return DEREF(memory, VALUE);
+    case UPB_TYPE_MESSAGE: {
+      VALUE val = DEREF(memory, VALUE);
+
+      // Lazily expand wrapper type if necessary.
+      int type = TYPE(val);
+      if (type != T_DATA && type != T_NIL) {
+        // This must be a wrapper type.
+        val = ruby_wrapper_type(type_class, val);
+        DEREF(memory, VALUE) = val;
+      }
+
+      return val;
+    }
     case UPB_TYPE_ENUM: {
       int32_t val = DEREF(memory, int32_t);
       VALUE symbol = enum_lookup(type_class, INT2NUM(val));
@@ -372,7 +384,8 @@ void native_slot_dup(upb_fieldtype_t type, void* to, void* from) {
   memcpy(to, from, native_slot_size(type));
 }
 
-void native_slot_deep_copy(upb_fieldtype_t type, void* to, void* from) {
+void native_slot_deep_copy(upb_fieldtype_t type, VALUE type_class, void* to,
+                           void* from) {
   switch (type) {
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES: {
@@ -382,7 +395,7 @@ void native_slot_deep_copy(upb_fieldtype_t type, void* to, void* from) {
       break;
     }
     case UPB_TYPE_MESSAGE: {
-      VALUE from_val = DEREF(from, VALUE);
+      VALUE from_val = native_slot_get(type, type_class, from);
       DEREF(to, VALUE) = (from_val != Qnil) ?
           Message_deep_copy(from_val) : Qnil;
       break;
@@ -392,13 +405,14 @@ void native_slot_deep_copy(upb_fieldtype_t type, void* to, void* from) {
   }
 }
 
-bool native_slot_eq(upb_fieldtype_t type, void* mem1, void* mem2) {
+bool native_slot_eq(upb_fieldtype_t type, VALUE type_class, void* mem1,
+                    void* mem2) {
   switch (type) {
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
     case UPB_TYPE_MESSAGE: {
-      VALUE val1 = DEREF(mem1, VALUE);
-      VALUE val2 = DEREF(mem2, VALUE);
+      VALUE val1 = native_slot_get(type, type_class, mem1);
+      VALUE val2 = native_slot_get(type, type_class, mem2);
       VALUE ret = rb_funcall(val1, rb_intern("=="), 1, val2);
       return ret == Qtrue;
     }
@@ -1025,7 +1039,9 @@ void layout_deep_copy(MessageLayout* layout, void* to, void* from) {
       if (slot_read_oneof_case(layout, from, oneof) ==
           upb_fielddef_number(field)) {
         *to_oneof_case = *from_oneof_case;
-        native_slot_deep_copy(upb_fielddef_type(field), to_memory, from_memory);
+        native_slot_deep_copy(upb_fielddef_type(field),
+                              field_type_class(layout, field), to_memory,
+                              from_memory);
       }
     } else if (is_map_field(field)) {
       DEREF(to_memory, VALUE) =
@@ -1039,7 +1055,9 @@ void layout_deep_copy(MessageLayout* layout, void* to, void* from) {
         slot_set_hasbit(layout, to, field);
       }
 
-      native_slot_deep_copy(upb_fielddef_type(field), to_memory, from_memory);
+      native_slot_deep_copy(upb_fielddef_type(field),
+                            field_type_class(layout, field), to_memory,
+                            from_memory);
     }
   }
 }
@@ -1061,7 +1079,8 @@ VALUE layout_eq(MessageLayout* layout, void* msg1, void* msg2) {
       if (*msg1_oneof_case != *msg2_oneof_case ||
           (slot_read_oneof_case(layout, msg1, oneof) ==
                upb_fielddef_number(field) &&
-           !native_slot_eq(upb_fielddef_type(field), msg1_memory,
+           !native_slot_eq(upb_fielddef_type(field),
+                           field_type_class(layout, field), msg1_memory,
                            msg2_memory))) {
         return Qfalse;
       }
@@ -1078,7 +1097,9 @@ VALUE layout_eq(MessageLayout* layout, void* msg1, void* msg2) {
     } else {
       if (slot_is_hasbit_set(layout, msg1, field) !=
               slot_is_hasbit_set(layout, msg2, field) ||
-          !native_slot_eq(upb_fielddef_type(field), msg1_memory, msg2_memory)) {
+          !native_slot_eq(upb_fielddef_type(field),
+                          field_type_class(layout, field), msg1_memory,
+                          msg2_memory)) {
         return Qfalse;
       }
     }

+ 42 - 0
ruby/tests/basic.rb

@@ -234,6 +234,48 @@ module BasicTest
       m.map_string_int32['aaa'] = 3
     end
 
+    def test_map_wrappers
+      run_asserts = ->(m) {
+        assert_equal 2.0, m.map_double[0].value
+        assert_equal 4.0, m.map_float[0].value
+        assert_equal 3, m.map_int32[0].value
+        assert_equal 4, m.map_int64[0].value
+        assert_equal 5, m.map_uint32[0].value
+        assert_equal 6, m.map_uint64[0].value
+        assert_equal true, m.map_bool[0].value
+        assert_equal 'str', m.map_string[0].value
+        assert_equal 'fun', m.map_bytes[0].value
+      }
+
+      m = proto_module::Wrapper.new(
+        map_double: {0 => Google::Protobuf::DoubleValue.new(value: 2.0)},
+        map_float: {0 => Google::Protobuf::FloatValue.new(value: 4.0)},
+        map_int32: {0 => Google::Protobuf::Int32Value.new(value: 3)},
+        map_int64: {0 => Google::Protobuf::Int64Value.new(value: 4)},
+        map_uint32: {0 => Google::Protobuf::UInt32Value.new(value: 5)},
+        map_uint64: {0 => Google::Protobuf::UInt64Value.new(value: 6)},
+        map_bool: {0 => Google::Protobuf::BoolValue.new(value: true)},
+        map_string: {0 => Google::Protobuf::StringValue.new(value: 'str')},
+        map_bytes: {0 => Google::Protobuf::BytesValue.new(value: 'fun')},
+      )
+
+      run_asserts.call(m)
+      serialized = proto_module::Wrapper::encode(m)
+      m2 = proto_module::Wrapper::decode(serialized)
+      run_asserts.call(m2)
+
+      # Test the case where we are serializing directly from the parsed form
+      # (before anything lazy is materialized).
+      m3 = proto_module::Wrapper::decode(serialized)
+      serialized2 = proto_module::Wrapper::encode(m3)
+      m4 = proto_module::Wrapper::decode(serialized2)
+      run_asserts.call(m4)
+
+      # Test that the lazy form compares equal to the expanded form.
+      m5 = proto_module::Wrapper::decode(serialized2)
+      assert_equal m5, m
+    end
+
     def test_concurrent_decoding
       o = Outer.new
       o.items[0] = Inner.new

+ 40 - 1
ruby/tests/basic_test.proto

@@ -126,7 +126,46 @@ message Wrapper {
   google.protobuf.BytesValue bytes = 9;
   string real_string = 100;
   oneof a_oneof {
-    string oneof_string = 10;
+    string string_in_oneof = 10;
+  }
+
+  // Repeated wrappers don't make sense, but we still need to make sure they
+  // work and don't crash.
+  repeated google.protobuf.DoubleValue repeated_double = 11;
+  repeated google.protobuf.FloatValue repeated_float = 12;
+  repeated google.protobuf.Int32Value repeated_int32 = 13;
+  repeated google.protobuf.Int64Value repeated_int64 = 14;
+  repeated google.protobuf.UInt32Value repeated_uint32 = 15;
+  repeated google.protobuf.UInt64Value repeated_uint64 = 16;
+  repeated google.protobuf.BoolValue repeated_bool = 17;
+  repeated google.protobuf.StringValue repeated_string = 18;
+  repeated google.protobuf.BytesValue repeated_bytes = 19;
+
+  // Wrappers as map keys don't make sense, but we still need to make sure they
+  // work and don't crash.
+  map<int32, google.protobuf.DoubleValue> map_double = 21;
+  map<int32, google.protobuf.FloatValue> map_float = 22;
+  map<int32, google.protobuf.Int32Value> map_int32 = 23;
+  map<int32, google.protobuf.Int64Value> map_int64 = 24;
+  map<int32, google.protobuf.UInt32Value> map_uint32 = 25;
+  map<int32, google.protobuf.UInt64Value> map_uint64 = 26;
+  map<int32, google.protobuf.BoolValue> map_bool = 27;
+  map<int32, google.protobuf.StringValue> map_string = 28;
+  map<int32, google.protobuf.BytesValue> map_bytes = 29;
+
+  // Wrappers in oneofs don't make sense, but we still need to make sure they
+  // work and don't crash.
+  oneof wrapper_oneof {
+    google.protobuf.DoubleValue oneof_double = 31;
+    google.protobuf.FloatValue oneof_float = 32;
+    google.protobuf.Int32Value oneof_int32 = 33;
+    google.protobuf.Int64Value oneof_int64 = 34;
+    google.protobuf.UInt32Value oneof_uint32 = 35;
+    google.protobuf.UInt64Value oneof_uint64 = 36;
+    google.protobuf.BoolValue oneof_bool = 37;
+    google.protobuf.StringValue oneof_string = 38;
+    google.protobuf.BytesValue oneof_bytes = 39;
+    string oneof_plain_string = 101;
   }
 }
 

+ 28 - 1
ruby/tests/basic_test_proto2.proto

@@ -133,7 +133,34 @@ message Wrapper {
   optional google.protobuf.BytesValue bytes = 9;
   optional string real_string = 100;
   oneof a_oneof {
-    string oneof_string = 10;
+    string string_in_oneof = 10;
+  }
+
+  // Repeated wrappers don't really make sense, but we still need to make sure
+  // they work and don't crash.
+  repeated google.protobuf.DoubleValue repeated_double = 11;
+  repeated google.protobuf.FloatValue repeated_float = 12;
+  repeated google.protobuf.Int32Value repeated_int32 = 13;
+  repeated google.protobuf.Int64Value repeated_int64 = 14;
+  repeated google.protobuf.UInt32Value repeated_uint32 = 15;
+  repeated google.protobuf.UInt64Value repeated_uint64 = 16;
+  repeated google.protobuf.BoolValue repeated_bool = 17;
+  repeated google.protobuf.StringValue repeated_string = 18;
+  repeated google.protobuf.BytesValue repeated_bytes = 19;
+
+  // Wrappers in oneofs don't make sense, but we still need to make sure they
+  // work and don't crash.
+  oneof wrapper_oneof {
+    google.protobuf.DoubleValue oneof_double = 31;
+    google.protobuf.FloatValue oneof_float = 32;
+    google.protobuf.Int32Value oneof_int32 = 33;
+    google.protobuf.Int64Value oneof_int64 = 34;
+    google.protobuf.UInt32Value oneof_uint32 = 35;
+    google.protobuf.UInt64Value oneof_uint64 = 36;
+    google.protobuf.BoolValue oneof_bool = 37;
+    google.protobuf.StringValue oneof_string = 38;
+    google.protobuf.BytesValue oneof_bytes = 39;
+    string oneof_plain_string = 101;
   }
 }
 

+ 331 - 132
ruby/tests/common_tests.rb

@@ -1266,6 +1266,44 @@ module CommonTests
   end
 
   def test_wrapper_getters
+    run_asserts = ->(m) {
+      assert_equal 2.0, m.double_as_value
+      assert_equal 2.0, m.double.value
+      assert_equal 2.0, m.double_as_value
+
+      assert_equal 4.0, m.float_as_value
+      assert_equal 4.0, m.float.value
+      assert_equal 4.0, m.float_as_value
+
+      assert_equal 3, m.int32_as_value
+      assert_equal 3, m.int32.value
+      assert_equal 3, m.int32_as_value
+
+      assert_equal 4, m.int64_as_value
+      assert_equal 4, m.int64.value
+      assert_equal 4, m.int64_as_value
+
+      assert_equal 5, m.uint32_as_value
+      assert_equal 5, m.uint32.value
+      assert_equal 5, m.uint32_as_value
+
+      assert_equal 6, m.uint64_as_value
+      assert_equal 6, m.uint64.value
+      assert_equal 6, m.uint64_as_value
+
+      assert_equal true, m.bool_as_value
+      assert_equal true, m.bool.value
+      assert_equal true, m.bool_as_value
+
+      assert_equal "st\nr", m.string_as_value
+      assert_equal "st\nr", m.string.value
+      assert_equal "st\nr", m.string_as_value
+
+      assert_equal 'fun', m.bytes_as_value
+      assert_equal 'fun', m.bytes.value
+      assert_equal 'fun', m.bytes_as_value
+    }
+
     m = proto_module::Wrapper.new(
       double: Google::Protobuf::DoubleValue.new(value: 2.0),
       float: Google::Protobuf::FloatValue.new(value: 4.0),
@@ -1274,158 +1312,319 @@ module CommonTests
       uint32: Google::Protobuf::UInt32Value.new(value: 5),
       uint64: Google::Protobuf::UInt64Value.new(value: 6),
       bool: Google::Protobuf::BoolValue.new(value: true),
-      string: Google::Protobuf::StringValue.new(value: 'str'),
+      string: Google::Protobuf::StringValue.new(value: "st\nr"),
       bytes: Google::Protobuf::BytesValue.new(value: 'fun'),
       real_string: '100'
     )
 
-    assert_equal 2.0, m.double_as_value
-    assert_equal 2.0, m.double.value
-    assert_equal 4.0, m.float_as_value
-    assert_equal 4.0, m.float.value
-    assert_equal 3, m.int32_as_value
-    assert_equal 3, m.int32.value
-    assert_equal 4, m.int64_as_value
-    assert_equal 4, m.int64.value
-    assert_equal 5, m.uint32_as_value
-    assert_equal 5, m.uint32.value
-    assert_equal 6, m.uint64_as_value
-    assert_equal 6, m.uint64.value
-    assert_equal true, m.bool_as_value
-    assert_equal true, m.bool.value
-    assert_equal 'str', m.string_as_value
-    assert_equal 'str', m.string.value
-    assert_equal 'fun', m.bytes_as_value
-    assert_equal 'fun', m.bytes.value
+    run_asserts.call(m)
+    serialized = proto_module::Wrapper::encode(m)
+    m2 = proto_module::Wrapper::decode(serialized)
+    run_asserts.call(m2)
+
+    # Test the case where we are serializing directly from the parsed form
+    # (before anything lazy is materialized).
+    m3 = proto_module::Wrapper::decode(serialized)
+    serialized2 = proto_module::Wrapper::encode(m3)
+    m4 = proto_module::Wrapper::decode(serialized2)
+    run_asserts.call(m4)
+
+    # Test that the lazy form compares equal to the expanded form.
+    m5 = proto_module::Wrapper::decode(serialized2)
+    assert_equal m5, m
+
+    serialized_json = proto_module::Wrapper::encode_json(m)
+    m6 = proto_module::Wrapper::decode_json(serialized_json)
+    assert_equal m6, m
+  end
+
+  def test_repeated_wrappers
+    run_asserts = ->(m) {
+      assert_equal 2.0, m.repeated_double[0].value
+      assert_equal 4.0, m.repeated_float[0].value
+      assert_equal 3, m.repeated_int32[0].value
+      assert_equal 4, m.repeated_int64[0].value
+      assert_equal 5, m.repeated_uint32[0].value
+      assert_equal 6, m.repeated_uint64[0].value
+      assert_equal true, m.repeated_bool[0].value
+      assert_equal 'str', m.repeated_string[0].value
+      assert_equal 'fun', m.repeated_bytes[0].value
+    }
+
+    m = proto_module::Wrapper.new(
+      repeated_double: [Google::Protobuf::DoubleValue.new(value: 2.0)],
+      repeated_float: [Google::Protobuf::FloatValue.new(value: 4.0)],
+      repeated_int32: [Google::Protobuf::Int32Value.new(value: 3)],
+      repeated_int64: [Google::Protobuf::Int64Value.new(value: 4)],
+      repeated_uint32: [Google::Protobuf::UInt32Value.new(value: 5)],
+      repeated_uint64: [Google::Protobuf::UInt64Value.new(value: 6)],
+      repeated_bool: [Google::Protobuf::BoolValue.new(value: true)],
+      repeated_string: [Google::Protobuf::StringValue.new(value: 'str')],
+      repeated_bytes: [Google::Protobuf::BytesValue.new(value: 'fun')],
+    )
+
+    run_asserts.call(m)
+    serialized = proto_module::Wrapper::encode(m)
+    m2 = proto_module::Wrapper::decode(serialized)
+    run_asserts.call(m2)
+
+    # Test the case where we are serializing directly from the parsed form
+    # (before anything lazy is materialized).
+    m3 = proto_module::Wrapper::decode(serialized)
+    serialized2 = proto_module::Wrapper::encode(m3)
+    m4 = proto_module::Wrapper::decode(serialized2)
+    run_asserts.call(m4)
+
+    # Test that the lazy form compares equal to the expanded form.
+    m5 = proto_module::Wrapper::decode(serialized2)
+    assert_equal m5, m
+
+    # Test JSON.
+    serialized_json = proto_module::Wrapper::encode_json(m5)
+    m6 = proto_module::Wrapper::decode_json(serialized_json)
+    run_asserts.call(m6)
+    assert_equal m6, m
+  end
+
+  def test_oneof_wrappers
+    run_test = ->(m) {
+      serialized = proto_module::Wrapper::encode(m)
+      m2 = proto_module::Wrapper::decode(serialized)
+
+      # Encode directly from lazy form.
+      serialized2 = proto_module::Wrapper::encode(m2)
+
+      assert_equal m, m2
+      assert_equal serialized, serialized2
+
+      serialized_json = proto_module::Wrapper::encode_json(m)
+      m3 = proto_module::Wrapper::decode_json(serialized_json)
+      assert_equal m, m3
+    }
+
+    m = proto_module::Wrapper.new()
+
+    run_test.call(m)
+    m.oneof_double_as_value = 2.0
+    run_test.call(m)
+    m.oneof_float_as_value = 4.0
+    run_test.call(m)
+    m.oneof_int32_as_value = 3
+    run_test.call(m)
+    m.oneof_int64_as_value = 5
+    run_test.call(m)
+    m.oneof_uint32_as_value = 6
+    run_test.call(m)
+    m.oneof_uint64_as_value = 7
+    run_test.call(m)
+    m.oneof_string_as_value = 'str'
+    run_test.call(m)
+    m.oneof_bytes_as_value = 'fun'
+    run_test.call(m)
+  end
+
+  def test_top_level_wrappers
+    # We don't expect anyone to do this, but we should also make sure it does
+    # the right thing.
+    run_test = ->(klass, val) {
+      m = klass.new(value: val)
+      serialized = klass::encode(m)
+      m2 = klass::decode(serialized)
+
+      # Encode directly from lazy form.
+      serialized2 = klass::encode(m2)
+
+      assert_equal m, m2
+      assert_equal serialized, serialized2
+
+      serialized_json = klass::encode_json(m)
+
+      # This is nonsensical to do and does not work.  There is no good reason
+      # to parse a wrapper type directly.
+      assert_raise(RuntimeError) { klass::decode_json(serialized_json) }
+    }
+
+    run_test.call(Google::Protobuf::DoubleValue, 2.0)
+    run_test.call(Google::Protobuf::FloatValue, 4.0)
+    run_test.call(Google::Protobuf::Int32Value, 3)
+    run_test.call(Google::Protobuf::Int64Value, 4)
+    run_test.call(Google::Protobuf::UInt32Value, 5)
+    run_test.call(Google::Protobuf::UInt64Value, 6)
+    run_test.call(Google::Protobuf::BoolValue, true)
+    run_test.call(Google::Protobuf::StringValue, 'str')
+    run_test.call(Google::Protobuf::BytesValue, 'fun')
   end
 
   def test_wrapper_setters_as_value
+    run_asserts = ->(m) {
+      m.double_as_value = 4.8
+      assert_equal 4.8, m.double_as_value
+      assert_equal Google::Protobuf::DoubleValue.new(value: 4.8), m.double
+      m.float_as_value = 2.4
+      assert_in_delta 2.4, m.float_as_value
+      assert_in_delta Google::Protobuf::FloatValue.new(value: 2.4).value, m.float.value
+      m.int32_as_value = 5
+      assert_equal 5, m.int32_as_value
+      assert_equal Google::Protobuf::Int32Value.new(value: 5), m.int32
+      m.int64_as_value = 15
+      assert_equal 15, m.int64_as_value
+      assert_equal Google::Protobuf::Int64Value.new(value: 15), m.int64
+      m.uint32_as_value = 50
+      assert_equal 50, m.uint32_as_value
+      assert_equal Google::Protobuf::UInt32Value.new(value: 50), m.uint32
+      m.uint64_as_value = 500
+      assert_equal 500, m.uint64_as_value
+      assert_equal Google::Protobuf::UInt64Value.new(value: 500), m.uint64
+      m.bool_as_value = false
+      assert_equal false, m.bool_as_value
+      assert_equal Google::Protobuf::BoolValue.new(value: false), m.bool
+      m.string_as_value = 'xy'
+      assert_equal 'xy', m.string_as_value
+      assert_equal Google::Protobuf::StringValue.new(value: 'xy'), m.string
+      m.bytes_as_value = '123'
+      assert_equal '123', m.bytes_as_value
+      assert_equal Google::Protobuf::BytesValue.new(value: '123'), m.bytes
+
+      m.double_as_value = nil
+      assert_nil m.double
+      assert_nil m.double_as_value
+      m.float_as_value = nil
+      assert_nil m.float
+      assert_nil m.float_as_value
+      m.int32_as_value = nil
+      assert_nil m.int32
+      assert_nil m.int32_as_value
+      m.int64_as_value = nil
+      assert_nil m.int64
+      assert_nil m.int64_as_value
+      m.uint32_as_value = nil
+      assert_nil m.uint32
+      assert_nil m.uint32_as_value
+      m.uint64_as_value = nil
+      assert_nil m.uint64
+      assert_nil m.uint64_as_value
+      m.bool_as_value = nil
+      assert_nil m.bool
+      assert_nil m.bool_as_value
+      m.string_as_value = nil
+      assert_nil m.string
+      assert_nil m.string_as_value
+      m.bytes_as_value = nil
+      assert_nil m.bytes
+      assert_nil m.bytes_as_value
+    }
+
     m = proto_module::Wrapper.new
 
-    m.double_as_value = 4.8
-    assert_equal 4.8, m.double_as_value
-    assert_equal Google::Protobuf::DoubleValue.new(value: 4.8), m.double
-    m.float_as_value = 2.4
-    assert_in_delta 2.4, m.float_as_value
-    assert_in_delta Google::Protobuf::FloatValue.new(value: 2.4).value, m.float.value
-    m.int32_as_value = 5
-    assert_equal 5, m.int32_as_value
-    assert_equal Google::Protobuf::Int32Value.new(value: 5), m.int32
-    m.int64_as_value = 15
-    assert_equal 15, m.int64_as_value
-    assert_equal Google::Protobuf::Int64Value.new(value: 15), m.int64
-    m.uint32_as_value = 50
-    assert_equal 50, m.uint32_as_value
-    assert_equal Google::Protobuf::UInt32Value.new(value: 50), m.uint32
-    m.uint64_as_value = 500
-    assert_equal 500, m.uint64_as_value
-    assert_equal Google::Protobuf::UInt64Value.new(value: 500), m.uint64
-    m.bool_as_value = false
-    assert_equal false, m.bool_as_value
-    assert_equal Google::Protobuf::BoolValue.new(value: false), m.bool
-    m.string_as_value = 'xy'
-    assert_equal 'xy', m.string_as_value
-    assert_equal Google::Protobuf::StringValue.new(value: 'xy'), m.string
-    m.bytes_as_value = '123'
-    assert_equal '123', m.bytes_as_value
-    assert_equal Google::Protobuf::BytesValue.new(value: '123'), m.bytes
-
-    m.double_as_value = nil
-    assert_nil m.double
-    assert_nil m.double_as_value
-    m.float_as_value = nil
-    assert_nil m.float
-    assert_nil m.float_as_value
-    m.int32_as_value = nil
-    assert_nil m.int32
-    assert_nil m.int32_as_value
-    m.int64_as_value = nil
-    assert_nil m.int64
-    assert_nil m.int64_as_value
-    m.uint32_as_value = nil
-    assert_nil m.uint32
-    assert_nil m.uint32_as_value
-    m.uint64_as_value = nil
-    assert_nil m.uint64
-    assert_nil m.uint64_as_value
-    m.bool_as_value = nil
-    assert_nil m.bool
-    assert_nil m.bool_as_value
-    m.string_as_value = nil
-    assert_nil m.string
-    assert_nil m.string_as_value
-    m.bytes_as_value = nil
-    assert_nil m.bytes
-    assert_nil m.bytes_as_value
+    m2 = proto_module::Wrapper.new(
+      double: Google::Protobuf::DoubleValue.new(value: 2.0),
+      float: Google::Protobuf::FloatValue.new(value: 4.0),
+      int32: Google::Protobuf::Int32Value.new(value: 3),
+      int64: Google::Protobuf::Int64Value.new(value: 4),
+      uint32: Google::Protobuf::UInt32Value.new(value: 5),
+      uint64: Google::Protobuf::UInt64Value.new(value: 6),
+      bool: Google::Protobuf::BoolValue.new(value: true),
+      string: Google::Protobuf::StringValue.new(value: 'str'),
+      bytes: Google::Protobuf::BytesValue.new(value: 'fun'),
+      real_string: '100'
+    )
+
+    run_asserts.call(m2)
+
+    serialized = proto_module::Wrapper::encode(m2)
+    m3 = proto_module::Wrapper::decode(serialized)
+    run_asserts.call(m3)
   end
 
   def test_wrapper_setters
+    run_asserts = ->(m) {
+      m.double = Google::Protobuf::DoubleValue.new(value: 4.8)
+      assert_equal 4.8, m.double_as_value
+      assert_equal Google::Protobuf::DoubleValue.new(value: 4.8), m.double
+      m.float = Google::Protobuf::FloatValue.new(value: 2.4)
+      assert_in_delta 2.4, m.float_as_value
+      assert_in_delta Google::Protobuf::FloatValue.new(value: 2.4).value, m.float.value
+      m.int32 = Google::Protobuf::Int32Value.new(value: 5)
+      assert_equal 5, m.int32_as_value
+      assert_equal Google::Protobuf::Int32Value.new(value: 5), m.int32
+      m.int64 = Google::Protobuf::Int64Value.new(value: 15)
+      assert_equal 15, m.int64_as_value
+      assert_equal Google::Protobuf::Int64Value.new(value: 15), m.int64
+      m.uint32 = Google::Protobuf::UInt32Value.new(value: 50)
+      assert_equal 50, m.uint32_as_value
+      assert_equal Google::Protobuf::UInt32Value.new(value: 50), m.uint32
+      m.uint64 = Google::Protobuf::UInt64Value.new(value: 500)
+      assert_equal 500, m.uint64_as_value
+      assert_equal Google::Protobuf::UInt64Value.new(value: 500), m.uint64
+      m.bool = Google::Protobuf::BoolValue.new(value: false)
+      assert_equal false, m.bool_as_value
+      assert_equal Google::Protobuf::BoolValue.new(value: false), m.bool
+      m.string = Google::Protobuf::StringValue.new(value: 'xy')
+      assert_equal 'xy', m.string_as_value
+      assert_equal Google::Protobuf::StringValue.new(value: 'xy'), m.string
+      m.bytes = Google::Protobuf::BytesValue.new(value: '123')
+      assert_equal '123', m.bytes_as_value
+      assert_equal Google::Protobuf::BytesValue.new(value: '123'), m.bytes
+
+      m.double = nil
+      assert_nil m.double
+      assert_nil m.double_as_value
+      m.float = nil
+      assert_nil m.float
+      assert_nil m.float_as_value
+      m.int32 = nil
+      assert_nil m.int32
+      assert_nil m.int32_as_value
+      m.int64 = nil
+      assert_nil m.int64
+      assert_nil m.int64_as_value
+      m.uint32 = nil
+      assert_nil m.uint32
+      assert_nil m.uint32_as_value
+      m.uint64 = nil
+      assert_nil m.uint64
+      assert_nil m.uint64_as_value
+      m.bool = nil
+      assert_nil m.bool
+      assert_nil m.bool_as_value
+      m.string = nil
+      assert_nil m.string
+      assert_nil m.string_as_value
+      m.bytes = nil
+      assert_nil m.bytes
+      assert_nil m.bytes_as_value
+    }
+
     m = proto_module::Wrapper.new
+    run_asserts.call(m)
+
+    m2 = proto_module::Wrapper.new(
+      double: Google::Protobuf::DoubleValue.new(value: 2.0),
+      float: Google::Protobuf::FloatValue.new(value: 4.0),
+      int32: Google::Protobuf::Int32Value.new(value: 3),
+      int64: Google::Protobuf::Int64Value.new(value: 4),
+      uint32: Google::Protobuf::UInt32Value.new(value: 5),
+      uint64: Google::Protobuf::UInt64Value.new(value: 6),
+      bool: Google::Protobuf::BoolValue.new(value: true),
+      string: Google::Protobuf::StringValue.new(value: 'str'),
+      bytes: Google::Protobuf::BytesValue.new(value: 'fun'),
+      real_string: '100'
+    )
 
-    m.double = Google::Protobuf::DoubleValue.new(value: 4.8)
-    assert_equal 4.8, m.double_as_value
-    assert_equal Google::Protobuf::DoubleValue.new(value: 4.8), m.double
-    m.float = Google::Protobuf::FloatValue.new(value: 2.4)
-    assert_in_delta 2.4, m.float_as_value
-    assert_in_delta Google::Protobuf::FloatValue.new(value: 2.4).value, m.float.value
-    m.int32 = Google::Protobuf::Int32Value.new(value: 5)
-    assert_equal 5, m.int32_as_value
-    assert_equal Google::Protobuf::Int32Value.new(value: 5), m.int32
-    m.int64 = Google::Protobuf::Int64Value.new(value: 15)
-    assert_equal 15, m.int64_as_value
-    assert_equal Google::Protobuf::Int64Value.new(value: 15), m.int64
-    m.uint32 = Google::Protobuf::UInt32Value.new(value: 50)
-    assert_equal 50, m.uint32_as_value
-    assert_equal Google::Protobuf::UInt32Value.new(value: 50), m.uint32
-    m.uint64 = Google::Protobuf::UInt64Value.new(value: 500)
-    assert_equal 500, m.uint64_as_value
-    assert_equal Google::Protobuf::UInt64Value.new(value: 500), m.uint64
-    m.bool = Google::Protobuf::BoolValue.new(value: false)
-    assert_equal false, m.bool_as_value
-    assert_equal Google::Protobuf::BoolValue.new(value: false), m.bool
-    m.string = Google::Protobuf::StringValue.new(value: 'xy')
-    assert_equal 'xy', m.string_as_value
-    assert_equal Google::Protobuf::StringValue.new(value: 'xy'), m.string
-    m.bytes = Google::Protobuf::BytesValue.new(value: '123')
-    assert_equal '123', m.bytes_as_value
-    assert_equal Google::Protobuf::BytesValue.new(value: '123'), m.bytes
-
-    m.double = nil
-    assert_nil m.double
-    assert_nil m.double_as_value
-    m.float = nil
-    assert_nil m.float
-    assert_nil m.float_as_value
-    m.int32 = nil
-    assert_nil m.int32
-    assert_nil m.int32_as_value
-    m.int64 = nil
-    assert_nil m.int64
-    assert_nil m.int64_as_value
-    m.uint32 = nil
-    assert_nil m.uint32
-    assert_nil m.uint32_as_value
-    m.uint64 = nil
-    assert_nil m.uint64
-    assert_nil m.uint64_as_value
-    m.bool = nil
-    assert_nil m.bool
-    assert_nil m.bool_as_value
-    m.string = nil
-    assert_nil m.string
-    assert_nil m.string_as_value
-    m.bytes = nil
-    assert_nil m.bytes
-    assert_nil m.bytes_as_value
+    run_asserts.call(m2)
+
+    serialized = proto_module::Wrapper::encode(m2)
+    m3 = proto_module::Wrapper::decode(serialized)
+    run_asserts.call(m3)
   end
 
   def test_wrappers_only
-    m = proto_module::Wrapper.new(real_string: 'hi', oneof_string: 'there')
+    m = proto_module::Wrapper.new(real_string: 'hi', string_in_oneof: 'there')
 
     assert_raise(NoMethodError) { m.real_string_as_value }
     assert_raise(NoMethodError) { m.as_value }
     assert_raise(NoMethodError) { m._as_value }
-    assert_raise(NoMethodError) { m.oneof_string_as_value }
+    assert_raise(NoMethodError) { m.string_in_oneof_as_value }
 
     m = proto_module::Wrapper.new
     m.string_as_value = 'you'
@@ -1443,7 +1642,7 @@ module CommonTests
     assert_raise(NoMethodError) { m.string_XXXXXXXXX }
     assert_raise(NoMethodError) { m.string_XXXXXXXXXX }
   end
-  
+
   def test_converts_time
     m = proto_module::TimeMessage.new