Moved promotion-related accessors to a separate file.

Since promotion is a more complicated operation than the simple accessors, and since promotion logic will likely be changing before long, it helps to put promotion-related logic in a separate place and rule.

PiperOrigin-RevId: 525519707
diff --git a/BUILD b/BUILD
index 045ded0..89d9e3c 100644
--- a/BUILD
+++ b/BUILD
@@ -286,6 +286,30 @@
 )
 
 cc_library(
+    name = "message_promote",
+    srcs = [
+        "upb/message/promote.c",
+    ],
+    hdrs = [
+        "upb/message/promote.h",
+    ],
+    copts = UPB_DEFAULT_COPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":collections_internal",
+        ":eps_copy_input_stream",
+        ":hash",
+        ":message_accessors",
+        ":message_internal",
+        ":mini_table_internal",
+        ":port",
+        ":upb",
+        ":wire",
+        ":wire_reader",
+    ],
+)
+
+cc_library(
     name = "message_copy",
     srcs = [
         "upb/message/copy.c",
@@ -342,6 +366,25 @@
 )
 
 cc_test(
+    name = "message_promote_test",
+    srcs = ["upb/message/promote_test.cc"],
+    deps = [
+        ":collections",
+        ":message_accessors",
+        ":message_promote",
+        ":mini_table_internal",
+        ":port",
+        ":upb",
+        "//upb/test:test_messages_proto2_upb_proto",
+        "//upb/test:test_messages_proto3_upb_proto",
+        "//upb/test:test_upb_proto",
+        "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_test(
     name = "message_copy_test",
     srcs = ["upb/message/copy_test.cc"],
     deps = [
diff --git a/upb/message/accessors.c b/upb/message/accessors.c
index 2f1dd7c..d2c74df 100644
--- a/upb/message/accessors.c
+++ b/upb/message/accessors.c
@@ -41,246 +41,6 @@
 // Must be last.
 #include "upb/port/def.inc"
 
-// Parses unknown data by merging into existing base_message or creating a
-// new message usingg mini_table.
-static upb_UnknownToMessageRet upb_MiniTable_ParseUnknownMessage(
-    const char* unknown_data, size_t unknown_size,
-    const upb_MiniTable* mini_table, upb_Message* base_message,
-    int decode_options, upb_Arena* arena) {
-  upb_UnknownToMessageRet ret;
-  ret.message =
-      base_message ? base_message : _upb_Message_New(mini_table, arena);
-  if (!ret.message) {
-    ret.status = kUpb_UnknownToMessage_OutOfMemory;
-    return ret;
-  }
-  // Decode sub message using unknown field contents.
-  const char* data = unknown_data;
-  uint32_t tag;
-  uint64_t message_len = 0;
-  data = upb_WireReader_ReadTag(data, &tag);
-  data = upb_WireReader_ReadVarint(data, &message_len);
-  upb_DecodeStatus status = upb_Decode(data, message_len, ret.message,
-                                       mini_table, NULL, decode_options, arena);
-  if (status == kUpb_DecodeStatus_OutOfMemory) {
-    ret.status = kUpb_UnknownToMessage_OutOfMemory;
-  } else if (status == kUpb_DecodeStatus_Ok) {
-    ret.status = kUpb_UnknownToMessage_Ok;
-  } else {
-    ret.status = kUpb_UnknownToMessage_ParseError;
-  }
-  return ret;
-}
-
-upb_GetExtension_Status upb_MiniTable_GetOrPromoteExtension(
-    upb_Message* msg, const upb_MiniTableExtension* ext_table,
-    int decode_options, upb_Arena* arena,
-    const upb_Message_Extension** extension) {
-  UPB_ASSERT(upb_MiniTableField_CType(&ext_table->field) == kUpb_CType_Message);
-  *extension = _upb_Message_Getext(msg, ext_table);
-  if (*extension) {
-    return kUpb_GetExtension_Ok;
-  }
-
-  // Check unknown fields, if available promote.
-  int field_number = ext_table->field.number;
-  upb_FindUnknownRet result = upb_MiniTable_FindUnknown(
-      msg, field_number, kUpb_WireFormat_DefaultDepthLimit);
-  if (result.status != kUpb_FindUnknown_Ok) {
-    return kUpb_GetExtension_NotPresent;
-  }
-  size_t len;
-  size_t ofs = result.ptr - upb_Message_GetUnknown(msg, &len);
-  // Decode and promote from unknown.
-  const upb_MiniTable* extension_table = ext_table->sub.submsg;
-  upb_UnknownToMessageRet parse_result = upb_MiniTable_ParseUnknownMessage(
-      result.ptr, result.len, extension_table,
-      /* base_message= */ NULL, decode_options, arena);
-  switch (parse_result.status) {
-    case kUpb_UnknownToMessage_OutOfMemory:
-      return kUpb_GetExtension_OutOfMemory;
-    case kUpb_UnknownToMessage_ParseError:
-      return kUpb_GetExtension_ParseError;
-    case kUpb_UnknownToMessage_NotFound:
-      return kUpb_GetExtension_NotPresent;
-    case kUpb_UnknownToMessage_Ok:
-      break;
-  }
-  upb_Message* extension_msg = parse_result.message;
-  // Add to extensions.
-  upb_Message_Extension* ext =
-      _upb_Message_GetOrCreateExtension(msg, ext_table, arena);
-  if (!ext) {
-    return kUpb_GetExtension_OutOfMemory;
-  }
-  memcpy(&ext->data, &extension_msg, sizeof(extension_msg));
-  *extension = ext;
-  const char* delete_ptr = upb_Message_GetUnknown(msg, &len) + ofs;
-  upb_Message_DeleteUnknown(msg, delete_ptr, result.len);
-  return kUpb_GetExtension_Ok;
-}
-
-upb_GetExtensionAsBytes_Status upb_MiniTable_GetExtensionAsBytes(
-    const upb_Message* msg, const upb_MiniTableExtension* ext_table,
-    int encode_options, upb_Arena* arena, const char** extension_data,
-    size_t* len) {
-  const upb_Message_Extension* msg_ext = _upb_Message_Getext(msg, ext_table);
-  UPB_ASSERT(upb_MiniTableField_CType(&ext_table->field) == kUpb_CType_Message);
-  if (msg_ext) {
-    upb_EncodeStatus status =
-        upb_Encode(msg_ext->data.ptr, msg_ext->ext->sub.submsg, encode_options,
-                   arena, (char**)extension_data, len);
-    if (status != kUpb_EncodeStatus_Ok) {
-      return kUpb_GetExtensionAsBytes_EncodeError;
-    }
-    return kUpb_GetExtensionAsBytes_Ok;
-  }
-  int field_number = ext_table->field.number;
-  upb_FindUnknownRet result = upb_MiniTable_FindUnknown(
-      msg, field_number, upb_DecodeOptions_GetMaxDepth(encode_options));
-  if (result.status != kUpb_FindUnknown_Ok) {
-    return kUpb_GetExtensionAsBytes_NotPresent;
-  }
-  const char* data = result.ptr;
-  uint32_t tag;
-  uint64_t message_len = 0;
-  data = upb_WireReader_ReadTag(data, &tag);
-  data = upb_WireReader_ReadVarint(data, &message_len);
-  *extension_data = data;
-  *len = message_len;
-  return kUpb_GetExtensionAsBytes_Ok;
-}
-
-static upb_FindUnknownRet upb_FindUnknownRet_ParseError(void) {
-  return (upb_FindUnknownRet){.status = kUpb_FindUnknown_ParseError};
-}
-
-upb_FindUnknownRet upb_MiniTable_FindUnknown(const upb_Message* msg,
-                                             uint32_t field_number,
-                                             int depth_limit) {
-  size_t size;
-  upb_FindUnknownRet ret;
-
-  const char* ptr = upb_Message_GetUnknown(msg, &size);
-  upb_EpsCopyInputStream stream;
-  upb_EpsCopyInputStream_Init(&stream, &ptr, size, true);
-
-  while (!upb_EpsCopyInputStream_IsDone(&stream, &ptr)) {
-    uint32_t tag;
-    const char* unknown_begin = ptr;
-    ptr = upb_WireReader_ReadTag(ptr, &tag);
-    if (!ptr) return upb_FindUnknownRet_ParseError();
-    if (field_number == upb_WireReader_GetFieldNumber(tag)) {
-      ret.status = kUpb_FindUnknown_Ok;
-      ret.ptr = upb_EpsCopyInputStream_GetAliasedPtr(&stream, unknown_begin);
-      ptr = _upb_WireReader_SkipValue(ptr, tag, depth_limit, &stream);
-      // Because we know that the input is a flat buffer, it is safe to perform
-      // pointer arithmetic on aliased pointers.
-      ret.len = upb_EpsCopyInputStream_GetAliasedPtr(&stream, ptr) - ret.ptr;
-      return ret;
-    }
-
-    ptr = _upb_WireReader_SkipValue(ptr, tag, depth_limit, &stream);
-    if (!ptr) return upb_FindUnknownRet_ParseError();
-  }
-  ret.status = kUpb_FindUnknown_NotPresent;
-  ret.ptr = NULL;
-  ret.len = 0;
-  return ret;
-}
-
-// Warning: See TODO(b/267655898)
-upb_UnknownToMessageRet upb_MiniTable_PromoteUnknownToMessage(
-    upb_Message* msg, const upb_MiniTable* mini_table,
-    const upb_MiniTableField* field, const upb_MiniTable* sub_mini_table,
-    int decode_options, upb_Arena* arena) {
-  upb_FindUnknownRet unknown;
-  // We need to loop and merge unknowns that have matching tag field->number.
-  upb_Message* message = NULL;
-  // Callers should check that message is not set first before calling
-  // PromotoUnknownToMessage.
-  UPB_ASSERT(upb_MiniTable_GetSubMessageTable(mini_table, field) ==
-             sub_mini_table);
-  bool is_oneof = _upb_MiniTableField_InOneOf(field);
-  if (!is_oneof || _upb_getoneofcase_field(msg, field) == field->number) {
-    UPB_ASSERT(upb_Message_GetMessage(msg, field, NULL) == NULL);
-  }
-  upb_UnknownToMessageRet ret;
-  ret.status = kUpb_UnknownToMessage_Ok;
-  do {
-    unknown = upb_MiniTable_FindUnknown(
-        msg, field->number, upb_DecodeOptions_GetMaxDepth(decode_options));
-    switch (unknown.status) {
-      case kUpb_FindUnknown_Ok: {
-        const char* unknown_data = unknown.ptr;
-        size_t unknown_size = unknown.len;
-        ret = upb_MiniTable_ParseUnknownMessage(unknown_data, unknown_size,
-                                                sub_mini_table, message,
-                                                decode_options, arena);
-        if (ret.status == kUpb_UnknownToMessage_Ok) {
-          message = ret.message;
-          upb_Message_DeleteUnknown(msg, unknown_data, unknown_size);
-        }
-      } break;
-      case kUpb_FindUnknown_ParseError:
-        ret.status = kUpb_UnknownToMessage_ParseError;
-        break;
-      case kUpb_FindUnknown_NotPresent:
-        // If we parsed at least one unknown, we are done.
-        ret.status =
-            message ? kUpb_UnknownToMessage_Ok : kUpb_UnknownToMessage_NotFound;
-        break;
-    }
-  } while (unknown.status == kUpb_FindUnknown_Ok);
-  if (message) {
-    if (is_oneof) {
-      *_upb_oneofcase_field(msg, field) = field->number;
-    }
-    upb_Message_SetMessage(msg, mini_table, field, message);
-    ret.message = message;
-  }
-  return ret;
-}
-
-// Moves repeated messages in unknowns to a upb_Array.
-//
-// Since the repeated field is not a scalar type we don't check for
-// kUpb_LabelFlags_IsPacked.
-// TODO(b/251007554): Optimize. Instead of converting messages one at a time,
-// scan all unknown data once and compact.
-upb_UnknownToMessage_Status upb_MiniTable_PromoteUnknownToMessageArray(
-    upb_Message* msg, const upb_MiniTableField* field,
-    const upb_MiniTable* mini_table, int decode_options, upb_Arena* arena) {
-  upb_Array* repeated_messages = upb_Message_GetMutableArray(msg, field);
-  // Find all unknowns with given field number and parse.
-  upb_FindUnknownRet unknown;
-  do {
-    unknown = upb_MiniTable_FindUnknown(
-        msg, field->number, upb_DecodeOptions_GetMaxDepth(decode_options));
-    if (unknown.status == kUpb_FindUnknown_Ok) {
-      upb_UnknownToMessageRet ret = upb_MiniTable_ParseUnknownMessage(
-          unknown.ptr, unknown.len, mini_table,
-          /* base_message= */ NULL, decode_options, arena);
-      if (ret.status == kUpb_UnknownToMessage_Ok) {
-        upb_MessageValue value;
-        value.msg_val = ret.message;
-        // Allocate array on demand before append.
-        if (!repeated_messages) {
-          upb_Message_ResizeArray(msg, field, 0, arena);
-          repeated_messages = upb_Message_GetMutableArray(msg, field);
-        }
-        if (!upb_Array_Append(repeated_messages, value, arena)) {
-          return kUpb_UnknownToMessage_OutOfMemory;
-        }
-        upb_Message_DeleteUnknown(msg, unknown.ptr, unknown.len);
-      } else {
-        return ret.status;
-      }
-    }
-  } while (unknown.status == kUpb_FindUnknown_Ok);
-  return kUpb_UnknownToMessage_Ok;
-}
-
 upb_MapInsertStatus upb_Message_InsertMapEntry(upb_Map* map,
                                                const upb_MiniTable* mini_table,
                                                const upb_MiniTableField* field,
@@ -306,39 +66,3 @@
                         &map_entry_value);
   return upb_Map_Insert(map, map_entry_key, map_entry_value, arena);
 }
-
-// Moves repeated messages in unknowns to a upb_Map.
-upb_UnknownToMessage_Status upb_MiniTable_PromoteUnknownToMap(
-    upb_Message* msg, const upb_MiniTable* mini_table,
-    const upb_MiniTableField* field, int decode_options, upb_Arena* arena) {
-  const upb_MiniTable* map_entry_mini_table =
-      mini_table->subs[field->UPB_PRIVATE(submsg_index)].submsg;
-  UPB_ASSERT(map_entry_mini_table);
-  UPB_ASSERT(map_entry_mini_table);
-  UPB_ASSERT(map_entry_mini_table->field_count == 2);
-  UPB_ASSERT(upb_FieldMode_Get(field) == kUpb_FieldMode_Map);
-  // Find all unknowns with given field number and parse.
-  upb_FindUnknownRet unknown;
-  while (1) {
-    unknown = upb_MiniTable_FindUnknown(
-        msg, field->number, upb_DecodeOptions_GetMaxDepth(decode_options));
-    if (unknown.status != kUpb_FindUnknown_Ok) break;
-    upb_UnknownToMessageRet ret = upb_MiniTable_ParseUnknownMessage(
-        unknown.ptr, unknown.len, map_entry_mini_table,
-        /* base_message= */ NULL, decode_options, arena);
-    if (ret.status != kUpb_UnknownToMessage_Ok) return ret.status;
-    // Allocate map on demand before append.
-    upb_Map* map = upb_Message_GetOrCreateMutableMap(msg, map_entry_mini_table,
-                                                     field, arena);
-    upb_Message* map_entry_message = ret.message;
-    upb_MapInsertStatus insert_status = upb_Message_InsertMapEntry(
-        map, mini_table, field, map_entry_message, arena);
-    if (insert_status == kUpb_MapInsertStatus_OutOfMemory) {
-      return kUpb_UnknownToMessage_OutOfMemory;
-    }
-    UPB_ASSUME(insert_status == kUpb_MapInsertStatus_Inserted ||
-               insert_status == kUpb_MapInsertStatus_Replaced);
-    upb_Message_DeleteUnknown(msg, unknown.ptr, unknown.len);
-  }
-  return kUpb_UnknownToMessage_Ok;
-}
diff --git a/upb/message/accessors.h b/upb/message/accessors.h
index 6d42d88..0432d9e 100644
--- a/upb/message/accessors.h
+++ b/upb/message/accessors.h
@@ -369,100 +369,6 @@
                                                upb_Message* map_entry_message,
                                                upb_Arena* arena);
 
-typedef enum {
-  kUpb_GetExtension_Ok,
-  kUpb_GetExtension_NotPresent,
-  kUpb_GetExtension_ParseError,
-  kUpb_GetExtension_OutOfMemory,
-} upb_GetExtension_Status;
-
-typedef enum {
-  kUpb_GetExtensionAsBytes_Ok,
-  kUpb_GetExtensionAsBytes_NotPresent,
-  kUpb_GetExtensionAsBytes_EncodeError,
-} upb_GetExtensionAsBytes_Status;
-
-// Returns a message extension or promotes an unknown field to
-// an extension.
-//
-// TODO(ferhat): Only supports extension fields that are messages,
-// expand support to include non-message types.
-upb_GetExtension_Status upb_MiniTable_GetOrPromoteExtension(
-    upb_Message* msg, const upb_MiniTableExtension* ext_table,
-    int decode_options, upb_Arena* arena,
-    const upb_Message_Extension** extension);
-
-// Returns a message extension or unknown field matching the extension
-// data as bytes.
-//
-// If an extension has already been decoded it will be re-encoded
-// to bytes.
-upb_GetExtensionAsBytes_Status upb_MiniTable_GetExtensionAsBytes(
-    const upb_Message* msg, const upb_MiniTableExtension* ext_table,
-    int encode_options, upb_Arena* arena, const char** extension_data,
-    size_t* len);
-
-typedef enum {
-  kUpb_FindUnknown_Ok,
-  kUpb_FindUnknown_NotPresent,
-  kUpb_FindUnknown_ParseError,
-} upb_FindUnknown_Status;
-
-typedef struct {
-  upb_FindUnknown_Status status;
-  // Start of unknown field data in message arena.
-  const char* ptr;
-  // Size of unknown field data.
-  size_t len;
-} upb_FindUnknownRet;
-
-// Finds first occurrence of unknown data by tag id in message.
-upb_FindUnknownRet upb_MiniTable_FindUnknown(const upb_Message* msg,
-                                             uint32_t field_number,
-                                             int depth_limit);
-
-typedef enum {
-  kUpb_UnknownToMessage_Ok,
-  kUpb_UnknownToMessage_ParseError,
-  kUpb_UnknownToMessage_OutOfMemory,
-  kUpb_UnknownToMessage_NotFound,
-} upb_UnknownToMessage_Status;
-
-typedef struct {
-  upb_UnknownToMessage_Status status;
-  upb_Message* message;
-} upb_UnknownToMessageRet;
-
-// Promotes unknown data inside message to a upb_Message parsing the unknown.
-//
-// The unknown data is removed from message after field value is set
-// using upb_Message_SetMessage.
-//
-// WARNING!: See b/267655898
-upb_UnknownToMessageRet upb_MiniTable_PromoteUnknownToMessage(
-    upb_Message* msg, const upb_MiniTable* mini_table,
-    const upb_MiniTableField* field, const upb_MiniTable* sub_mini_table,
-    int decode_options, upb_Arena* arena);
-
-// Promotes all unknown data that matches field tag id to repeated messages
-// in upb_Array.
-//
-// The unknown data is removed from message after upb_Array is populated.
-// Since repeated messages can't be packed we remove each unknown that
-// contains the target tag id.
-upb_UnknownToMessage_Status upb_MiniTable_PromoteUnknownToMessageArray(
-    upb_Message* msg, const upb_MiniTableField* field,
-    const upb_MiniTable* mini_table, int decode_options, upb_Arena* arena);
-
-// Promotes all unknown data that matches field tag id to upb_Map.
-//
-// The unknown data is removed from message after upb_Map is populated.
-// Since repeated messages can't be packed we remove each unknown that
-// contains the target tag id.
-upb_UnknownToMessage_Status upb_MiniTable_PromoteUnknownToMap(
-    upb_Message* msg, const upb_MiniTable* mini_table,
-    const upb_MiniTableField* field, int decode_options, upb_Arena* arena);
-
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
diff --git a/upb/message/accessors_test.cc b/upb/message/accessors_test.cc
index 910a700..a46f602 100644
--- a/upb/message/accessors_test.cc
+++ b/upb/message/accessors_test.cc
@@ -392,395 +392,6 @@
   upb_Arena_Free(arena);
 }
 
-TEST(GeneratedCode, FindUnknown) {
-  upb_Arena* arena = upb_Arena_New();
-  upb_test_ModelWithExtensions* msg = upb_test_ModelWithExtensions_new(arena);
-  upb_test_ModelWithExtensions_set_random_int32(msg, 10);
-  upb_test_ModelWithExtensions_set_random_name(
-      msg, upb_StringView_FromString("Hello"));
-
-  upb_test_ModelExtension1* extension1 = upb_test_ModelExtension1_new(arena);
-  upb_test_ModelExtension1_set_str(extension1,
-                                   upb_StringView_FromString("World"));
-
-  upb_test_ModelExtension1_set_model_ext(msg, extension1, arena);
-
-  size_t serialized_size;
-  char* serialized =
-      upb_test_ModelWithExtensions_serialize(msg, arena, &serialized_size);
-
-  upb_test_EmptyMessageWithExtensions* base_msg =
-      upb_test_EmptyMessageWithExtensions_parse(serialized, serialized_size,
-                                                arena);
-
-  upb_FindUnknownRet result = upb_MiniTable_FindUnknown(
-      base_msg, upb_test_ModelExtension1_model_ext_ext.field.number,
-      kUpb_WireFormat_DefaultDepthLimit);
-  EXPECT_EQ(kUpb_FindUnknown_Ok, result.status);
-
-  result = upb_MiniTable_FindUnknown(
-      base_msg, upb_test_ModelExtension2_model_ext_ext.field.number,
-      kUpb_WireFormat_DefaultDepthLimit);
-  EXPECT_EQ(kUpb_FindUnknown_NotPresent, result.status);
-
-  upb_Arena_Free(arena);
-}
-
-TEST(GeneratedCode, Extensions) {
-  upb_Arena* arena = upb_Arena_New();
-  upb_test_ModelWithExtensions* msg = upb_test_ModelWithExtensions_new(arena);
-  upb_test_ModelWithExtensions_set_random_int32(msg, 10);
-  upb_test_ModelWithExtensions_set_random_name(
-      msg, upb_StringView_FromString("Hello"));
-
-  upb_test_ModelExtension1* extension1 = upb_test_ModelExtension1_new(arena);
-  upb_test_ModelExtension1_set_str(extension1,
-                                   upb_StringView_FromString("World"));
-
-  upb_test_ModelExtension2* extension2 = upb_test_ModelExtension2_new(arena);
-  upb_test_ModelExtension2_set_i(extension2, 5);
-
-  upb_test_ModelExtension2* extension3 = upb_test_ModelExtension2_new(arena);
-  upb_test_ModelExtension2_set_i(extension3, 6);
-
-  upb_test_ModelExtension2* extension4 = upb_test_ModelExtension2_new(arena);
-  upb_test_ModelExtension2_set_i(extension4, 7);
-
-  upb_test_ModelExtension2* extension5 = upb_test_ModelExtension2_new(arena);
-  upb_test_ModelExtension2_set_i(extension5, 8);
-
-  upb_test_ModelExtension2* extension6 = upb_test_ModelExtension2_new(arena);
-  upb_test_ModelExtension2_set_i(extension6, 9);
-
-  // Set many extensions, to exercise code paths that involve reallocating the
-  // extensions and unknown fields array.
-  upb_test_ModelExtension1_set_model_ext(msg, extension1, arena);
-  upb_test_ModelExtension2_set_model_ext(msg, extension2, arena);
-  upb_test_ModelExtension2_set_model_ext_2(msg, extension3, arena);
-  upb_test_ModelExtension2_set_model_ext_3(msg, extension4, arena);
-  upb_test_ModelExtension2_set_model_ext_4(msg, extension5, arena);
-  upb_test_ModelExtension2_set_model_ext_5(msg, extension6, arena);
-
-  size_t serialized_size;
-  char* serialized =
-      upb_test_ModelWithExtensions_serialize(msg, arena, &serialized_size);
-
-  const upb_Message_Extension* upb_ext2;
-  upb_test_ModelExtension1* ext1;
-  upb_test_ModelExtension2* ext2;
-  upb_GetExtension_Status promote_status;
-
-  // Test known GetExtension 1
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      msg, &upb_test_ModelExtension1_model_ext_ext, 0, arena, &upb_ext2);
-  ext1 = (upb_test_ModelExtension1*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_TRUE(upb_StringView_IsEqual(upb_StringView_FromString("World"),
-                                     upb_test_ModelExtension1_str(ext1)));
-
-  // Test known GetExtension 2
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      msg, &upb_test_ModelExtension2_model_ext_ext, 0, arena, &upb_ext2);
-  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_EQ(5, upb_test_ModelExtension2_i(ext2));
-
-  // Test known GetExtension 3
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      msg, &upb_test_ModelExtension2_model_ext_2_ext, 0, arena, &upb_ext2);
-  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_EQ(6, upb_test_ModelExtension2_i(ext2));
-
-  // Test known GetExtension 4
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      msg, &upb_test_ModelExtension2_model_ext_3_ext, 0, arena, &upb_ext2);
-  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_EQ(7, upb_test_ModelExtension2_i(ext2));
-
-  // Test known GetExtension 5
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      msg, &upb_test_ModelExtension2_model_ext_4_ext, 0, arena, &upb_ext2);
-  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_EQ(8, upb_test_ModelExtension2_i(ext2));
-
-  // Test known GetExtension 6
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      msg, &upb_test_ModelExtension2_model_ext_5_ext, 0, arena, &upb_ext2);
-  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_EQ(9, upb_test_ModelExtension2_i(ext2));
-
-  upb_test_EmptyMessageWithExtensions* base_msg =
-      upb_test_EmptyMessageWithExtensions_parse(serialized, serialized_size,
-                                                arena);
-
-  // Get unknown extension bytes before promotion.
-  const char* extension_data;
-  size_t len;
-  upb_GetExtensionAsBytes_Status status = upb_MiniTable_GetExtensionAsBytes(
-      base_msg, &upb_test_ModelExtension2_model_ext_ext, 0, arena,
-      &extension_data, &len);
-  EXPECT_EQ(kUpb_GetExtensionAsBytes_Ok, status);
-  EXPECT_EQ(0x48, extension_data[0]);
-  EXPECT_EQ(5, extension_data[1]);
-
-  // Test unknown GetExtension.
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      base_msg, &upb_test_ModelExtension1_model_ext_ext, 0, arena, &upb_ext2);
-  ext1 = (upb_test_ModelExtension1*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_TRUE(upb_StringView_IsEqual(upb_StringView_FromString("World"),
-                                     upb_test_ModelExtension1_str(ext1)));
-
-  // Test unknown GetExtension.
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      base_msg, &upb_test_ModelExtension2_model_ext_ext, 0, arena, &upb_ext2);
-  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_EQ(5, upb_test_ModelExtension2_i(ext2));
-
-  // Test unknown GetExtension.
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      base_msg, &upb_test_ModelExtension2_model_ext_2_ext, 0, arena, &upb_ext2);
-  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_EQ(6, upb_test_ModelExtension2_i(ext2));
-
-  // Test unknown GetExtension.
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      base_msg, &upb_test_ModelExtension2_model_ext_3_ext, 0, arena, &upb_ext2);
-  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_EQ(7, upb_test_ModelExtension2_i(ext2));
-
-  // Test unknown GetExtension.
-  promote_status = upb_MiniTable_GetOrPromoteExtension(
-      base_msg, &upb_test_ModelExtension2_model_ext_4_ext, 0, arena, &upb_ext2);
-  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
-  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
-  EXPECT_EQ(8, upb_test_ModelExtension2_i(ext2));
-
-  upb_Arena_Free(arena);
-}
-
-// Create a minitable to mimic ModelWithSubMessages with unlinked subs
-// to lazily promote unknowns after parsing.
-upb_MiniTable* CreateMiniTableWithEmptySubTables(upb_Arena* arena) {
-  upb::MtDataEncoder e;
-  e.StartMessage(0);
-  e.PutField(kUpb_FieldType_Int32, 4, 0);
-  e.PutField(kUpb_FieldType_Message, 5, 0);
-  e.PutField(kUpb_FieldType_Message, 6, kUpb_FieldModifier_IsRepeated);
-
-  upb_Status status;
-  upb_Status_Clear(&status);
-  upb_MiniTable* table =
-      upb_MiniTable_Build(e.data().data(), e.data().size(), arena, &status);
-  EXPECT_EQ(status.ok, true);
-  // Initialize sub table to null. Not using upb_MiniTable_SetSubMessage
-  // since it checks ->ext on parameter.
-  upb_MiniTableSub* sub = const_cast<upb_MiniTableSub*>(
-      &table->subs[table->fields[1].UPB_PRIVATE(submsg_index)]);
-  sub->submsg = nullptr;
-  sub = const_cast<upb_MiniTableSub*>(
-      &table->subs[table->fields[2].UPB_PRIVATE(submsg_index)]);
-  sub->submsg = nullptr;
-  return table;
-}
-
-// Create a minitable to mimic ModelWithMaps with unlinked subs
-// to lazily promote unknowns after parsing.
-upb_MiniTable* CreateMiniTableWithEmptySubTablesForMaps(upb_Arena* arena) {
-  upb::MtDataEncoder e;
-  e.StartMessage(0);
-  e.PutField(kUpb_FieldType_Int32, 1, 0);
-  e.PutField(kUpb_FieldType_Message, 3, kUpb_FieldModifier_IsRepeated);
-  e.PutField(kUpb_FieldType_Message, 4, kUpb_FieldModifier_IsRepeated);
-
-  upb_Status status;
-  upb_Status_Clear(&status);
-  upb_MiniTable* table =
-      upb_MiniTable_Build(e.data().data(), e.data().size(), arena, &status);
-  EXPECT_EQ(status.ok, true);
-  // Initialize sub table to null. Not using upb_MiniTable_SetSubMessage
-  // since it checks ->ext on parameter.
-  upb_MiniTableSub* sub = const_cast<upb_MiniTableSub*>(
-      &table->subs[table->fields[1].UPB_PRIVATE(submsg_index)]);
-  sub->submsg = nullptr;
-  sub = const_cast<upb_MiniTableSub*>(
-      &table->subs[table->fields[2].UPB_PRIVATE(submsg_index)]);
-  sub->submsg = nullptr;
-  return table;
-}
-
-upb_MiniTable* CreateMapEntryMiniTable(upb_Arena* arena) {
-  upb::MtDataEncoder e;
-  e.EncodeMap(kUpb_FieldType_String, kUpb_FieldType_String, 0, 0);
-  upb_Status status;
-  upb_Status_Clear(&status);
-  upb_MiniTable* table =
-      upb_MiniTable_Build(e.data().data(), e.data().size(), arena, &status);
-  EXPECT_EQ(status.ok, true);
-  return table;
-}
-
-TEST(GeneratedCode, PromoteUnknownMessage) {
-  upb_Arena* arena = upb_Arena_New();
-  upb_test_ModelWithSubMessages* input_msg =
-      upb_test_ModelWithSubMessages_new(arena);
-  upb_test_ModelWithExtensions* sub_message =
-      upb_test_ModelWithExtensions_new(arena);
-  upb_test_ModelWithSubMessages_set_id(input_msg, 11);
-  upb_test_ModelWithExtensions_set_random_int32(sub_message, 12);
-  upb_test_ModelWithSubMessages_set_optional_child(input_msg, sub_message);
-  size_t serialized_size;
-  char* serialized = upb_test_ModelWithSubMessages_serialize(input_msg, arena,
-                                                             &serialized_size);
-
-  upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTables(arena);
-  upb_Message* msg = _upb_Message_New(mini_table, arena);
-  upb_DecodeStatus decode_status = upb_Decode(serialized, serialized_size, msg,
-                                              mini_table, nullptr, 0, arena);
-  EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
-  int32_t val = upb_Message_GetInt32(
-      msg, upb_MiniTable_FindFieldByNumber(mini_table, 4), 0);
-  EXPECT_EQ(val, 11);
-  upb_FindUnknownRet unknown =
-      upb_MiniTable_FindUnknown(msg, 5, kUpb_WireFormat_DefaultDepthLimit);
-  EXPECT_EQ(unknown.status, kUpb_FindUnknown_Ok);
-  // Update mini table and promote unknown to a message.
-  EXPECT_TRUE(upb_MiniTable_SetSubMessage(
-      mini_table, (upb_MiniTableField*)&mini_table->fields[1],
-      &upb_test_ModelWithExtensions_msg_init));
-  const int decode_options = upb_DecodeOptions_MaxDepth(
-      kUpb_WireFormat_DefaultDepthLimit);  // UPB_DECODE_ALIAS disabled.
-  upb_UnknownToMessageRet promote_result =
-      upb_MiniTable_PromoteUnknownToMessage(
-          msg, mini_table, &mini_table->fields[1],
-          &upb_test_ModelWithExtensions_msg_init, decode_options, arena);
-  EXPECT_EQ(promote_result.status, kUpb_UnknownToMessage_Ok);
-  const upb_Message* promoted_message =
-      upb_Message_GetMessage(msg, &mini_table->fields[1], nullptr);
-  EXPECT_EQ(upb_test_ModelWithExtensions_random_int32(
-                (upb_test_ModelWithExtensions*)promoted_message),
-            12);
-  upb_Arena_Free(arena);
-}
-
-TEST(GeneratedCode, PromoteUnknownRepeatedMessage) {
-  upb_Arena* arena = upb_Arena_New();
-  upb_test_ModelWithSubMessages* input_msg =
-      upb_test_ModelWithSubMessages_new(arena);
-  upb_test_ModelWithSubMessages_set_id(input_msg, 123);
-
-  // Add 2 repeated messages to input_msg.
-  upb_test_ModelWithExtensions* item =
-      upb_test_ModelWithSubMessages_add_items(input_msg, arena);
-  upb_test_ModelWithExtensions_set_random_int32(item, 5);
-  item = upb_test_ModelWithSubMessages_add_items(input_msg, arena);
-  upb_test_ModelWithExtensions_set_random_int32(item, 6);
-
-  size_t serialized_size;
-  char* serialized = upb_test_ModelWithSubMessages_serialize(input_msg, arena,
-                                                             &serialized_size);
-
-  upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTables(arena);
-  upb_Message* msg = _upb_Message_New(mini_table, arena);
-  upb_DecodeStatus decode_status = upb_Decode(serialized, serialized_size, msg,
-                                              mini_table, nullptr, 0, arena);
-  EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
-  int32_t val = upb_Message_GetInt32(
-      msg, upb_MiniTable_FindFieldByNumber(mini_table, 4), 0);
-  EXPECT_EQ(val, 123);
-
-  // Check that we have repeated field data in an unknown.
-  upb_FindUnknownRet unknown =
-      upb_MiniTable_FindUnknown(msg, 6, kUpb_WireFormat_DefaultDepthLimit);
-  EXPECT_EQ(unknown.status, kUpb_FindUnknown_Ok);
-
-  // Update mini table and promote unknown to a message.
-  EXPECT_TRUE(upb_MiniTable_SetSubMessage(
-      mini_table, (upb_MiniTableField*)&mini_table->fields[2],
-      &upb_test_ModelWithExtensions_msg_init));
-  const int decode_options = upb_DecodeOptions_MaxDepth(
-      kUpb_WireFormat_DefaultDepthLimit);  // UPB_DECODE_ALIAS disabled.
-  upb_UnknownToMessage_Status promote_result =
-      upb_MiniTable_PromoteUnknownToMessageArray(
-          msg, &mini_table->fields[2], &upb_test_ModelWithExtensions_msg_init,
-          decode_options, arena);
-  EXPECT_EQ(promote_result, kUpb_UnknownToMessage_Ok);
-
-  upb_Array* array = upb_Message_GetMutableArray(msg, &mini_table->fields[2]);
-  const upb_Message* promoted_message = upb_Array_Get(array, 0).msg_val;
-  EXPECT_EQ(upb_test_ModelWithExtensions_random_int32(
-                (upb_test_ModelWithExtensions*)promoted_message),
-            5);
-  promoted_message = upb_Array_Get(array, 1).msg_val;
-  EXPECT_EQ(upb_test_ModelWithExtensions_random_int32(
-                (upb_test_ModelWithExtensions*)promoted_message),
-            6);
-  upb_Arena_Free(arena);
-}
-
-TEST(GeneratedCode, PromoteUnknownToMap) {
-  upb_Arena* arena = upb_Arena_New();
-  upb_test_ModelWithMaps* input_msg = upb_test_ModelWithMaps_new(arena);
-  upb_test_ModelWithMaps_set_id(input_msg, 123);
-
-  // Add 2 map entries.
-  upb_test_ModelWithMaps_map_ss_set(input_msg,
-                                    upb_StringView_FromString("key1"),
-                                    upb_StringView_FromString("value1"), arena);
-  upb_test_ModelWithMaps_map_ss_set(input_msg,
-                                    upb_StringView_FromString("key2"),
-                                    upb_StringView_FromString("value2"), arena);
-
-  size_t serialized_size;
-  char* serialized =
-      upb_test_ModelWithMaps_serialize(input_msg, arena, &serialized_size);
-
-  upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTablesForMaps(arena);
-  upb_MiniTable* map_entry_mini_table = CreateMapEntryMiniTable(arena);
-  upb_Message* msg = _upb_Message_New(mini_table, arena);
-  const int decode_options =
-      upb_DecodeOptions_MaxDepth(kUpb_WireFormat_DefaultDepthLimit);
-  upb_DecodeStatus decode_status =
-      upb_Decode(serialized, serialized_size, msg, mini_table, nullptr,
-                 decode_options, arena);
-  EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
-  int32_t val = upb_Message_GetInt32(
-      msg, upb_MiniTable_FindFieldByNumber(mini_table, 1), 0);
-  EXPECT_EQ(val, 123);
-
-  // Check that we have map data in an unknown.
-  upb_FindUnknownRet unknown =
-      upb_MiniTable_FindUnknown(msg, 3, kUpb_WireFormat_DefaultDepthLimit);
-  EXPECT_EQ(unknown.status, kUpb_FindUnknown_Ok);
-
-  // Update mini table and promote unknown to a message.
-  EXPECT_TRUE(upb_MiniTable_SetSubMessage(
-      mini_table, (upb_MiniTableField*)&mini_table->fields[1],
-      map_entry_mini_table));
-  upb_UnknownToMessage_Status promote_result =
-      upb_MiniTable_PromoteUnknownToMap(msg, mini_table, &mini_table->fields[1],
-                                        decode_options, arena);
-  EXPECT_EQ(promote_result, kUpb_UnknownToMessage_Ok);
-
-  upb_Map* map = upb_Message_GetOrCreateMutableMap(
-      msg, map_entry_mini_table, &mini_table->fields[1], arena);
-  EXPECT_NE(map, nullptr);
-  // Lookup in map.
-  upb_MessageValue key;
-  key.str_val = upb_StringView_FromString("key2");
-  upb_MessageValue value;
-  EXPECT_TRUE(upb_Map_Get(map, key, &value));
-  EXPECT_EQ(0, strncmp(value.str_val.data, "value2", 5));
-  upb_Arena_Free(arena);
-}
-
 TEST(GeneratedCode, EnumClosedCheck) {
   upb_Arena* arena = upb_Arena_New();
 
diff --git a/upb/message/message.h b/upb/message/message.h
index 1cf7249..dbf0e13 100644
--- a/upb/message/message.h
+++ b/upb/message/message.h
@@ -25,11 +25,9 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-// Public APIs for message operations that do not require descriptors.
-// These functions can be used even in build that does not want to depend on
-// reflection or descriptors.
+// Public APIs for message operations that do not depend on the schema.
 //
-// Descriptor-based reflection functionality lives in reflection.h.
+// MiniTable-based accessors live in accessors.h.
 
 #ifndef UPB_MESSAGE_MESSAGE_H_
 #define UPB_MESSAGE_MESSAGE_H_
diff --git a/upb/message/promote.c b/upb/message/promote.c
new file mode 100644
index 0000000..3f2a2db
--- /dev/null
+++ b/upb/message/promote.c
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "upb/message/promote.h"
+
+#include "upb/collections/array.h"
+#include "upb/collections/array_internal.h"
+#include "upb/collections/map.h"
+#include "upb/message/accessors.h"
+#include "upb/message/message.h"
+#include "upb/mini_table/field_internal.h"
+#include "upb/wire/common.h"
+#include "upb/wire/decode.h"
+#include "upb/wire/encode.h"
+#include "upb/wire/eps_copy_input_stream.h"
+#include "upb/wire/reader.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+// Parses unknown data by merging into existing base_message or creating a
+// new message usingg mini_table.
+static upb_UnknownToMessageRet upb_MiniTable_ParseUnknownMessage(
+    const char* unknown_data, size_t unknown_size,
+    const upb_MiniTable* mini_table, upb_Message* base_message,
+    int decode_options, upb_Arena* arena) {
+  upb_UnknownToMessageRet ret;
+  ret.message =
+      base_message ? base_message : _upb_Message_New(mini_table, arena);
+  if (!ret.message) {
+    ret.status = kUpb_UnknownToMessage_OutOfMemory;
+    return ret;
+  }
+  // Decode sub message using unknown field contents.
+  const char* data = unknown_data;
+  uint32_t tag;
+  uint64_t message_len = 0;
+  data = upb_WireReader_ReadTag(data, &tag);
+  data = upb_WireReader_ReadVarint(data, &message_len);
+  upb_DecodeStatus status = upb_Decode(data, message_len, ret.message,
+                                       mini_table, NULL, decode_options, arena);
+  if (status == kUpb_DecodeStatus_OutOfMemory) {
+    ret.status = kUpb_UnknownToMessage_OutOfMemory;
+  } else if (status == kUpb_DecodeStatus_Ok) {
+    ret.status = kUpb_UnknownToMessage_Ok;
+  } else {
+    ret.status = kUpb_UnknownToMessage_ParseError;
+  }
+  return ret;
+}
+
+upb_GetExtension_Status upb_MiniTable_GetOrPromoteExtension(
+    upb_Message* msg, const upb_MiniTableExtension* ext_table,
+    int decode_options, upb_Arena* arena,
+    const upb_Message_Extension** extension) {
+  UPB_ASSERT(upb_MiniTableField_CType(&ext_table->field) == kUpb_CType_Message);
+  *extension = _upb_Message_Getext(msg, ext_table);
+  if (*extension) {
+    return kUpb_GetExtension_Ok;
+  }
+
+  // Check unknown fields, if available promote.
+  int field_number = ext_table->field.number;
+  upb_FindUnknownRet result = upb_MiniTable_FindUnknown(
+      msg, field_number, kUpb_WireFormat_DefaultDepthLimit);
+  if (result.status != kUpb_FindUnknown_Ok) {
+    return kUpb_GetExtension_NotPresent;
+  }
+  size_t len;
+  size_t ofs = result.ptr - upb_Message_GetUnknown(msg, &len);
+  // Decode and promote from unknown.
+  const upb_MiniTable* extension_table = ext_table->sub.submsg;
+  upb_UnknownToMessageRet parse_result = upb_MiniTable_ParseUnknownMessage(
+      result.ptr, result.len, extension_table,
+      /* base_message= */ NULL, decode_options, arena);
+  switch (parse_result.status) {
+    case kUpb_UnknownToMessage_OutOfMemory:
+      return kUpb_GetExtension_OutOfMemory;
+    case kUpb_UnknownToMessage_ParseError:
+      return kUpb_GetExtension_ParseError;
+    case kUpb_UnknownToMessage_NotFound:
+      return kUpb_GetExtension_NotPresent;
+    case kUpb_UnknownToMessage_Ok:
+      break;
+  }
+  upb_Message* extension_msg = parse_result.message;
+  // Add to extensions.
+  upb_Message_Extension* ext =
+      _upb_Message_GetOrCreateExtension(msg, ext_table, arena);
+  if (!ext) {
+    return kUpb_GetExtension_OutOfMemory;
+  }
+  memcpy(&ext->data, &extension_msg, sizeof(extension_msg));
+  *extension = ext;
+  const char* delete_ptr = upb_Message_GetUnknown(msg, &len) + ofs;
+  upb_Message_DeleteUnknown(msg, delete_ptr, result.len);
+  return kUpb_GetExtension_Ok;
+}
+
+upb_GetExtensionAsBytes_Status upb_MiniTable_GetExtensionAsBytes(
+    const upb_Message* msg, const upb_MiniTableExtension* ext_table,
+    int encode_options, upb_Arena* arena, const char** extension_data,
+    size_t* len) {
+  const upb_Message_Extension* msg_ext = _upb_Message_Getext(msg, ext_table);
+  UPB_ASSERT(upb_MiniTableField_CType(&ext_table->field) == kUpb_CType_Message);
+  if (msg_ext) {
+    upb_EncodeStatus status =
+        upb_Encode(msg_ext->data.ptr, msg_ext->ext->sub.submsg, encode_options,
+                   arena, (char**)extension_data, len);
+    if (status != kUpb_EncodeStatus_Ok) {
+      return kUpb_GetExtensionAsBytes_EncodeError;
+    }
+    return kUpb_GetExtensionAsBytes_Ok;
+  }
+  int field_number = ext_table->field.number;
+  upb_FindUnknownRet result = upb_MiniTable_FindUnknown(
+      msg, field_number, upb_DecodeOptions_GetMaxDepth(encode_options));
+  if (result.status != kUpb_FindUnknown_Ok) {
+    return kUpb_GetExtensionAsBytes_NotPresent;
+  }
+  const char* data = result.ptr;
+  uint32_t tag;
+  uint64_t message_len = 0;
+  data = upb_WireReader_ReadTag(data, &tag);
+  data = upb_WireReader_ReadVarint(data, &message_len);
+  *extension_data = data;
+  *len = message_len;
+  return kUpb_GetExtensionAsBytes_Ok;
+}
+
+static upb_FindUnknownRet upb_FindUnknownRet_ParseError(void) {
+  return (upb_FindUnknownRet){.status = kUpb_FindUnknown_ParseError};
+}
+
+upb_FindUnknownRet upb_MiniTable_FindUnknown(const upb_Message* msg,
+                                             uint32_t field_number,
+                                             int depth_limit) {
+  size_t size;
+  upb_FindUnknownRet ret;
+
+  const char* ptr = upb_Message_GetUnknown(msg, &size);
+  upb_EpsCopyInputStream stream;
+  upb_EpsCopyInputStream_Init(&stream, &ptr, size, true);
+
+  while (!upb_EpsCopyInputStream_IsDone(&stream, &ptr)) {
+    uint32_t tag;
+    const char* unknown_begin = ptr;
+    ptr = upb_WireReader_ReadTag(ptr, &tag);
+    if (!ptr) return upb_FindUnknownRet_ParseError();
+    if (field_number == upb_WireReader_GetFieldNumber(tag)) {
+      ret.status = kUpb_FindUnknown_Ok;
+      ret.ptr = upb_EpsCopyInputStream_GetAliasedPtr(&stream, unknown_begin);
+      ptr = _upb_WireReader_SkipValue(ptr, tag, depth_limit, &stream);
+      // Because we know that the input is a flat buffer, it is safe to perform
+      // pointer arithmetic on aliased pointers.
+      ret.len = upb_EpsCopyInputStream_GetAliasedPtr(&stream, ptr) - ret.ptr;
+      return ret;
+    }
+
+    ptr = _upb_WireReader_SkipValue(ptr, tag, depth_limit, &stream);
+    if (!ptr) return upb_FindUnknownRet_ParseError();
+  }
+  ret.status = kUpb_FindUnknown_NotPresent;
+  ret.ptr = NULL;
+  ret.len = 0;
+  return ret;
+}
+
+// Warning: See TODO(b/267655898)
+upb_UnknownToMessageRet upb_MiniTable_PromoteUnknownToMessage(
+    upb_Message* msg, const upb_MiniTable* mini_table,
+    const upb_MiniTableField* field, const upb_MiniTable* sub_mini_table,
+    int decode_options, upb_Arena* arena) {
+  upb_FindUnknownRet unknown;
+  // We need to loop and merge unknowns that have matching tag field->number.
+  upb_Message* message = NULL;
+  // Callers should check that message is not set first before calling
+  // PromotoUnknownToMessage.
+  UPB_ASSERT(upb_MiniTable_GetSubMessageTable(mini_table, field) ==
+             sub_mini_table);
+  bool is_oneof = _upb_MiniTableField_InOneOf(field);
+  if (!is_oneof || _upb_getoneofcase_field(msg, field) == field->number) {
+    UPB_ASSERT(upb_Message_GetMessage(msg, field, NULL) == NULL);
+  }
+  upb_UnknownToMessageRet ret;
+  ret.status = kUpb_UnknownToMessage_Ok;
+  do {
+    unknown = upb_MiniTable_FindUnknown(
+        msg, field->number, upb_DecodeOptions_GetMaxDepth(decode_options));
+    switch (unknown.status) {
+      case kUpb_FindUnknown_Ok: {
+        const char* unknown_data = unknown.ptr;
+        size_t unknown_size = unknown.len;
+        ret = upb_MiniTable_ParseUnknownMessage(unknown_data, unknown_size,
+                                                sub_mini_table, message,
+                                                decode_options, arena);
+        if (ret.status == kUpb_UnknownToMessage_Ok) {
+          message = ret.message;
+          upb_Message_DeleteUnknown(msg, unknown_data, unknown_size);
+        }
+      } break;
+      case kUpb_FindUnknown_ParseError:
+        ret.status = kUpb_UnknownToMessage_ParseError;
+        break;
+      case kUpb_FindUnknown_NotPresent:
+        // If we parsed at least one unknown, we are done.
+        ret.status =
+            message ? kUpb_UnknownToMessage_Ok : kUpb_UnknownToMessage_NotFound;
+        break;
+    }
+  } while (unknown.status == kUpb_FindUnknown_Ok);
+  if (message) {
+    if (is_oneof) {
+      *_upb_oneofcase_field(msg, field) = field->number;
+    }
+    upb_Message_SetMessage(msg, mini_table, field, message);
+    ret.message = message;
+  }
+  return ret;
+}
+
+// Moves repeated messages in unknowns to a upb_Array.
+//
+// Since the repeated field is not a scalar type we don't check for
+// kUpb_LabelFlags_IsPacked.
+// TODO(b/251007554): Optimize. Instead of converting messages one at a time,
+// scan all unknown data once and compact.
+upb_UnknownToMessage_Status upb_MiniTable_PromoteUnknownToMessageArray(
+    upb_Message* msg, const upb_MiniTableField* field,
+    const upb_MiniTable* mini_table, int decode_options, upb_Arena* arena) {
+  upb_Array* repeated_messages = upb_Message_GetMutableArray(msg, field);
+  // Find all unknowns with given field number and parse.
+  upb_FindUnknownRet unknown;
+  do {
+    unknown = upb_MiniTable_FindUnknown(
+        msg, field->number, upb_DecodeOptions_GetMaxDepth(decode_options));
+    if (unknown.status == kUpb_FindUnknown_Ok) {
+      upb_UnknownToMessageRet ret = upb_MiniTable_ParseUnknownMessage(
+          unknown.ptr, unknown.len, mini_table,
+          /* base_message= */ NULL, decode_options, arena);
+      if (ret.status == kUpb_UnknownToMessage_Ok) {
+        upb_MessageValue value;
+        value.msg_val = ret.message;
+        // Allocate array on demand before append.
+        if (!repeated_messages) {
+          upb_Message_ResizeArray(msg, field, 0, arena);
+          repeated_messages = upb_Message_GetMutableArray(msg, field);
+        }
+        if (!upb_Array_Append(repeated_messages, value, arena)) {
+          return kUpb_UnknownToMessage_OutOfMemory;
+        }
+        upb_Message_DeleteUnknown(msg, unknown.ptr, unknown.len);
+      } else {
+        return ret.status;
+      }
+    }
+  } while (unknown.status == kUpb_FindUnknown_Ok);
+  return kUpb_UnknownToMessage_Ok;
+}
+
+// Moves repeated messages in unknowns to a upb_Map.
+upb_UnknownToMessage_Status upb_MiniTable_PromoteUnknownToMap(
+    upb_Message* msg, const upb_MiniTable* mini_table,
+    const upb_MiniTableField* field, int decode_options, upb_Arena* arena) {
+  const upb_MiniTable* map_entry_mini_table =
+      mini_table->subs[field->UPB_PRIVATE(submsg_index)].submsg;
+  UPB_ASSERT(map_entry_mini_table);
+  UPB_ASSERT(map_entry_mini_table);
+  UPB_ASSERT(map_entry_mini_table->field_count == 2);
+  UPB_ASSERT(upb_FieldMode_Get(field) == kUpb_FieldMode_Map);
+  // Find all unknowns with given field number and parse.
+  upb_FindUnknownRet unknown;
+  while (1) {
+    unknown = upb_MiniTable_FindUnknown(
+        msg, field->number, upb_DecodeOptions_GetMaxDepth(decode_options));
+    if (unknown.status != kUpb_FindUnknown_Ok) break;
+    upb_UnknownToMessageRet ret = upb_MiniTable_ParseUnknownMessage(
+        unknown.ptr, unknown.len, map_entry_mini_table,
+        /* base_message= */ NULL, decode_options, arena);
+    if (ret.status != kUpb_UnknownToMessage_Ok) return ret.status;
+    // Allocate map on demand before append.
+    upb_Map* map = upb_Message_GetOrCreateMutableMap(msg, map_entry_mini_table,
+                                                     field, arena);
+    upb_Message* map_entry_message = ret.message;
+    upb_MapInsertStatus insert_status = upb_Message_InsertMapEntry(
+        map, mini_table, field, map_entry_message, arena);
+    if (insert_status == kUpb_MapInsertStatus_OutOfMemory) {
+      return kUpb_UnknownToMessage_OutOfMemory;
+    }
+    UPB_ASSUME(insert_status == kUpb_MapInsertStatus_Inserted ||
+               insert_status == kUpb_MapInsertStatus_Replaced);
+    upb_Message_DeleteUnknown(msg, unknown.ptr, unknown.len);
+  }
+  return kUpb_UnknownToMessage_Ok;
+}
diff --git a/upb/message/promote.h b/upb/message/promote.h
new file mode 100644
index 0000000..36b45aa
--- /dev/null
+++ b/upb/message/promote.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009-2022, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UPB_MESSAGE_PROMOTE_H_
+#define UPB_MESSAGE_PROMOTE_H_
+
+#include "upb/message/extension_internal.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+  kUpb_GetExtension_Ok,
+  kUpb_GetExtension_NotPresent,
+  kUpb_GetExtension_ParseError,
+  kUpb_GetExtension_OutOfMemory,
+} upb_GetExtension_Status;
+
+typedef enum {
+  kUpb_GetExtensionAsBytes_Ok,
+  kUpb_GetExtensionAsBytes_NotPresent,
+  kUpb_GetExtensionAsBytes_EncodeError,
+} upb_GetExtensionAsBytes_Status;
+
+// Returns a message extension or promotes an unknown field to
+// an extension.
+//
+// TODO(ferhat): Only supports extension fields that are messages,
+// expand support to include non-message types.
+upb_GetExtension_Status upb_MiniTable_GetOrPromoteExtension(
+    upb_Message* msg, const upb_MiniTableExtension* ext_table,
+    int decode_options, upb_Arena* arena,
+    const upb_Message_Extension** extension);
+
+typedef enum {
+  kUpb_FindUnknown_Ok,
+  kUpb_FindUnknown_NotPresent,
+  kUpb_FindUnknown_ParseError,
+} upb_FindUnknown_Status;
+
+typedef struct {
+  upb_FindUnknown_Status status;
+  // Start of unknown field data in message arena.
+  const char* ptr;
+  // Size of unknown field data.
+  size_t len;
+} upb_FindUnknownRet;
+
+// Finds first occurrence of unknown data by tag id in message.
+upb_FindUnknownRet upb_MiniTable_FindUnknown(const upb_Message* msg,
+                                             uint32_t field_number,
+                                             int depth_limit);
+
+typedef enum {
+  kUpb_UnknownToMessage_Ok,
+  kUpb_UnknownToMessage_ParseError,
+  kUpb_UnknownToMessage_OutOfMemory,
+  kUpb_UnknownToMessage_NotFound,
+} upb_UnknownToMessage_Status;
+
+typedef struct {
+  upb_UnknownToMessage_Status status;
+  upb_Message* message;
+} upb_UnknownToMessageRet;
+
+// Promotes unknown data inside message to a upb_Message parsing the unknown.
+//
+// The unknown data is removed from message after field value is set
+// using upb_Message_SetMessage.
+//
+// WARNING!: See b/267655898
+upb_UnknownToMessageRet upb_MiniTable_PromoteUnknownToMessage(
+    upb_Message* msg, const upb_MiniTable* mini_table,
+    const upb_MiniTableField* field, const upb_MiniTable* sub_mini_table,
+    int decode_options, upb_Arena* arena);
+
+// Promotes all unknown data that matches field tag id to repeated messages
+// in upb_Array.
+//
+// The unknown data is removed from message after upb_Array is populated.
+// Since repeated messages can't be packed we remove each unknown that
+// contains the target tag id.
+upb_UnknownToMessage_Status upb_MiniTable_PromoteUnknownToMessageArray(
+    upb_Message* msg, const upb_MiniTableField* field,
+    const upb_MiniTable* mini_table, int decode_options, upb_Arena* arena);
+
+// Promotes all unknown data that matches field tag id to upb_Map.
+//
+// The unknown data is removed from message after upb_Map is populated.
+// Since repeated messages can't be packed we remove each unknown that
+// contains the target tag id.
+upb_UnknownToMessage_Status upb_MiniTable_PromoteUnknownToMap(
+    upb_Message* msg, const upb_MiniTable* mini_table,
+    const upb_MiniTableField* field, int decode_options, upb_Arena* arena);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include "upb/port/undef.inc"
+
+#endif  // UPB_MESSAGE_PROMOTE_H_
diff --git a/upb/message/promote_test.cc b/upb/message/promote_test.cc
new file mode 100644
index 0000000..98d98c5
--- /dev/null
+++ b/upb/message/promote_test.cc
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2009-2021, Google LLC
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google LLC nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* Test of mini table accessors.
+ *
+ * Messages are created and mutated using generated code, and then
+ * accessed through reflective APIs exposed through mini table accessors.
+ */
+
+#include "upb/message/promote.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "google/protobuf/test_messages_proto2.upb.h"
+#include "google/protobuf/test_messages_proto3.upb.h"
+#include "upb/base/string_view.h"
+#include "upb/collections/array.h"
+#include "upb/message/accessors.h"
+#include "upb/mini_table/common.h"
+#include "upb/mini_table/decode.h"
+#include "upb/mini_table/encode_internal.hpp"
+#include "upb/mini_table/field_internal.h"
+#include "upb/test/test.upb.h"
+#include "upb/upb.h"
+#include "upb/wire/common.h"
+#include "upb/wire/decode.h"
+
+// Must be last
+#include "upb/port/def.inc"
+
+namespace {
+
+// Proto2 test messages field numbers used for reflective access.
+const uint32_t kFieldOptionalInt32 = 1;
+const uint32_t kFieldOptionalUInt32 = 3;
+const uint32_t kFieldOptionalBool = 13;
+const uint32_t kFieldOptionalString = 14;
+const uint32_t kFieldOptionalNestedMessage = 18;
+const uint32_t kFieldOptionalRepeatedInt32 = 31;
+const uint32_t kFieldOptionalNestedMessageA = 1;
+const uint32_t kFieldOptionalOneOfUInt32 = 111;
+const uint32_t kFieldOptionalOneOfString = 113;
+
+const uint32_t kFieldProto3OptionalInt64 = 2;
+const uint32_t kFieldProto3OptionalUInt64 = 4;
+
+const char kTestStr1[] = "Hello1";
+const char kTestStr2[] = "Hello2";
+const int32_t kTestInt32 = 567;
+const int32_t kTestUInt32 = 0xF1234567;
+const uint64_t kTestUInt64 = 0xFEDCBAFF87654321;
+
+TEST(GeneratedCode, FindUnknown) {
+  upb_Arena* arena = upb_Arena_New();
+  upb_test_ModelWithExtensions* msg = upb_test_ModelWithExtensions_new(arena);
+  upb_test_ModelWithExtensions_set_random_int32(msg, 10);
+  upb_test_ModelWithExtensions_set_random_name(
+      msg, upb_StringView_FromString("Hello"));
+
+  upb_test_ModelExtension1* extension1 = upb_test_ModelExtension1_new(arena);
+  upb_test_ModelExtension1_set_str(extension1,
+                                   upb_StringView_FromString("World"));
+
+  upb_test_ModelExtension1_set_model_ext(msg, extension1, arena);
+
+  size_t serialized_size;
+  char* serialized =
+      upb_test_ModelWithExtensions_serialize(msg, arena, &serialized_size);
+
+  upb_test_EmptyMessageWithExtensions* base_msg =
+      upb_test_EmptyMessageWithExtensions_parse(serialized, serialized_size,
+                                                arena);
+
+  upb_FindUnknownRet result = upb_MiniTable_FindUnknown(
+      base_msg, upb_test_ModelExtension1_model_ext_ext.field.number,
+      kUpb_WireFormat_DefaultDepthLimit);
+  EXPECT_EQ(kUpb_FindUnknown_Ok, result.status);
+
+  result = upb_MiniTable_FindUnknown(
+      base_msg, upb_test_ModelExtension2_model_ext_ext.field.number,
+      kUpb_WireFormat_DefaultDepthLimit);
+  EXPECT_EQ(kUpb_FindUnknown_NotPresent, result.status);
+
+  upb_Arena_Free(arena);
+}
+
+TEST(GeneratedCode, Extensions) {
+  upb_Arena* arena = upb_Arena_New();
+  upb_test_ModelWithExtensions* msg = upb_test_ModelWithExtensions_new(arena);
+  upb_test_ModelWithExtensions_set_random_int32(msg, 10);
+  upb_test_ModelWithExtensions_set_random_name(
+      msg, upb_StringView_FromString("Hello"));
+
+  upb_test_ModelExtension1* extension1 = upb_test_ModelExtension1_new(arena);
+  upb_test_ModelExtension1_set_str(extension1,
+                                   upb_StringView_FromString("World"));
+
+  upb_test_ModelExtension2* extension2 = upb_test_ModelExtension2_new(arena);
+  upb_test_ModelExtension2_set_i(extension2, 5);
+
+  upb_test_ModelExtension2* extension3 = upb_test_ModelExtension2_new(arena);
+  upb_test_ModelExtension2_set_i(extension3, 6);
+
+  upb_test_ModelExtension2* extension4 = upb_test_ModelExtension2_new(arena);
+  upb_test_ModelExtension2_set_i(extension4, 7);
+
+  upb_test_ModelExtension2* extension5 = upb_test_ModelExtension2_new(arena);
+  upb_test_ModelExtension2_set_i(extension5, 8);
+
+  upb_test_ModelExtension2* extension6 = upb_test_ModelExtension2_new(arena);
+  upb_test_ModelExtension2_set_i(extension6, 9);
+
+  // Set many extensions, to exercise code paths that involve reallocating the
+  // extensions and unknown fields array.
+  upb_test_ModelExtension1_set_model_ext(msg, extension1, arena);
+  upb_test_ModelExtension2_set_model_ext(msg, extension2, arena);
+  upb_test_ModelExtension2_set_model_ext_2(msg, extension3, arena);
+  upb_test_ModelExtension2_set_model_ext_3(msg, extension4, arena);
+  upb_test_ModelExtension2_set_model_ext_4(msg, extension5, arena);
+  upb_test_ModelExtension2_set_model_ext_5(msg, extension6, arena);
+
+  size_t serialized_size;
+  char* serialized =
+      upb_test_ModelWithExtensions_serialize(msg, arena, &serialized_size);
+
+  const upb_Message_Extension* upb_ext2;
+  upb_test_ModelExtension1* ext1;
+  upb_test_ModelExtension2* ext2;
+  upb_GetExtension_Status promote_status;
+
+  // Test known GetExtension 1
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      msg, &upb_test_ModelExtension1_model_ext_ext, 0, arena, &upb_ext2);
+  ext1 = (upb_test_ModelExtension1*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_TRUE(upb_StringView_IsEqual(upb_StringView_FromString("World"),
+                                     upb_test_ModelExtension1_str(ext1)));
+
+  // Test known GetExtension 2
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      msg, &upb_test_ModelExtension2_model_ext_ext, 0, arena, &upb_ext2);
+  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_EQ(5, upb_test_ModelExtension2_i(ext2));
+
+  // Test known GetExtension 3
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      msg, &upb_test_ModelExtension2_model_ext_2_ext, 0, arena, &upb_ext2);
+  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_EQ(6, upb_test_ModelExtension2_i(ext2));
+
+  // Test known GetExtension 4
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      msg, &upb_test_ModelExtension2_model_ext_3_ext, 0, arena, &upb_ext2);
+  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_EQ(7, upb_test_ModelExtension2_i(ext2));
+
+  // Test known GetExtension 5
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      msg, &upb_test_ModelExtension2_model_ext_4_ext, 0, arena, &upb_ext2);
+  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_EQ(8, upb_test_ModelExtension2_i(ext2));
+
+  // Test known GetExtension 6
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      msg, &upb_test_ModelExtension2_model_ext_5_ext, 0, arena, &upb_ext2);
+  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_EQ(9, upb_test_ModelExtension2_i(ext2));
+
+  upb_test_EmptyMessageWithExtensions* base_msg =
+      upb_test_EmptyMessageWithExtensions_parse(serialized, serialized_size,
+                                                arena);
+
+  // Get unknown extension bytes before promotion.
+  size_t start_len;
+  upb_Message_GetUnknown(base_msg, &start_len);
+  EXPECT_GT(start_len, 0);
+  EXPECT_EQ(0, upb_Message_ExtensionCount(base_msg));
+
+  // Test unknown GetExtension.
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      base_msg, &upb_test_ModelExtension1_model_ext_ext, 0, arena, &upb_ext2);
+  ext1 = (upb_test_ModelExtension1*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_TRUE(upb_StringView_IsEqual(upb_StringView_FromString("World"),
+                                     upb_test_ModelExtension1_str(ext1)));
+
+  // Test unknown GetExtension.
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      base_msg, &upb_test_ModelExtension2_model_ext_ext, 0, arena, &upb_ext2);
+  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_EQ(5, upb_test_ModelExtension2_i(ext2));
+
+  // Test unknown GetExtension.
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      base_msg, &upb_test_ModelExtension2_model_ext_2_ext, 0, arena, &upb_ext2);
+  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_EQ(6, upb_test_ModelExtension2_i(ext2));
+
+  // Test unknown GetExtension.
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      base_msg, &upb_test_ModelExtension2_model_ext_3_ext, 0, arena, &upb_ext2);
+  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_EQ(7, upb_test_ModelExtension2_i(ext2));
+
+  // Test unknown GetExtension.
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      base_msg, &upb_test_ModelExtension2_model_ext_4_ext, 0, arena, &upb_ext2);
+  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_EQ(8, upb_test_ModelExtension2_i(ext2));
+
+  // Test unknown GetExtension.
+  promote_status = upb_MiniTable_GetOrPromoteExtension(
+      base_msg, &upb_test_ModelExtension2_model_ext_5_ext, 0, arena, &upb_ext2);
+  ext2 = (upb_test_ModelExtension2*)upb_ext2->data.ptr;
+  EXPECT_EQ(kUpb_GetExtension_Ok, promote_status);
+  EXPECT_EQ(9, upb_test_ModelExtension2_i(ext2));
+
+  size_t end_len;
+  upb_Message_GetUnknown(base_msg, &end_len);
+  EXPECT_LT(end_len, start_len);
+  EXPECT_EQ(6, upb_Message_ExtensionCount(base_msg));
+
+  upb_Arena_Free(arena);
+}
+
+// Create a minitable to mimic ModelWithSubMessages with unlinked subs
+// to lazily promote unknowns after parsing.
+upb_MiniTable* CreateMiniTableWithEmptySubTables(upb_Arena* arena) {
+  upb::MtDataEncoder e;
+  e.StartMessage(0);
+  e.PutField(kUpb_FieldType_Int32, 4, 0);
+  e.PutField(kUpb_FieldType_Message, 5, 0);
+  e.PutField(kUpb_FieldType_Message, 6, kUpb_FieldModifier_IsRepeated);
+
+  upb_Status status;
+  upb_Status_Clear(&status);
+  upb_MiniTable* table =
+      upb_MiniTable_Build(e.data().data(), e.data().size(), arena, &status);
+  EXPECT_EQ(status.ok, true);
+  // Initialize sub table to null. Not using upb_MiniTable_SetSubMessage
+  // since it checks ->ext on parameter.
+  upb_MiniTableSub* sub = const_cast<upb_MiniTableSub*>(
+      &table->subs[table->fields[1].UPB_PRIVATE(submsg_index)]);
+  sub->submsg = nullptr;
+  sub = const_cast<upb_MiniTableSub*>(
+      &table->subs[table->fields[2].UPB_PRIVATE(submsg_index)]);
+  sub->submsg = nullptr;
+  return table;
+}
+
+// Create a minitable to mimic ModelWithMaps with unlinked subs
+// to lazily promote unknowns after parsing.
+upb_MiniTable* CreateMiniTableWithEmptySubTablesForMaps(upb_Arena* arena) {
+  upb::MtDataEncoder e;
+  e.StartMessage(0);
+  e.PutField(kUpb_FieldType_Int32, 1, 0);
+  e.PutField(kUpb_FieldType_Message, 3, kUpb_FieldModifier_IsRepeated);
+  e.PutField(kUpb_FieldType_Message, 4, kUpb_FieldModifier_IsRepeated);
+
+  upb_Status status;
+  upb_Status_Clear(&status);
+  upb_MiniTable* table =
+      upb_MiniTable_Build(e.data().data(), e.data().size(), arena, &status);
+  EXPECT_EQ(status.ok, true);
+  // Initialize sub table to null. Not using upb_MiniTable_SetSubMessage
+  // since it checks ->ext on parameter.
+  upb_MiniTableSub* sub = const_cast<upb_MiniTableSub*>(
+      &table->subs[table->fields[1].UPB_PRIVATE(submsg_index)]);
+  sub->submsg = nullptr;
+  sub = const_cast<upb_MiniTableSub*>(
+      &table->subs[table->fields[2].UPB_PRIVATE(submsg_index)]);
+  sub->submsg = nullptr;
+  return table;
+}
+
+upb_MiniTable* CreateMapEntryMiniTable(upb_Arena* arena) {
+  upb::MtDataEncoder e;
+  e.EncodeMap(kUpb_FieldType_String, kUpb_FieldType_String, 0, 0);
+  upb_Status status;
+  upb_Status_Clear(&status);
+  upb_MiniTable* table =
+      upb_MiniTable_Build(e.data().data(), e.data().size(), arena, &status);
+  EXPECT_EQ(status.ok, true);
+  return table;
+}
+
+TEST(GeneratedCode, PromoteUnknownMessage) {
+  upb_Arena* arena = upb_Arena_New();
+  upb_test_ModelWithSubMessages* input_msg =
+      upb_test_ModelWithSubMessages_new(arena);
+  upb_test_ModelWithExtensions* sub_message =
+      upb_test_ModelWithExtensions_new(arena);
+  upb_test_ModelWithSubMessages_set_id(input_msg, 11);
+  upb_test_ModelWithExtensions_set_random_int32(sub_message, 12);
+  upb_test_ModelWithSubMessages_set_optional_child(input_msg, sub_message);
+  size_t serialized_size;
+  char* serialized = upb_test_ModelWithSubMessages_serialize(input_msg, arena,
+                                                             &serialized_size);
+
+  upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTables(arena);
+  upb_Message* msg = _upb_Message_New(mini_table, arena);
+  upb_DecodeStatus decode_status = upb_Decode(serialized, serialized_size, msg,
+                                              mini_table, nullptr, 0, arena);
+  EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
+  int32_t val = upb_Message_GetInt32(
+      msg, upb_MiniTable_FindFieldByNumber(mini_table, 4), 0);
+  EXPECT_EQ(val, 11);
+  upb_FindUnknownRet unknown =
+      upb_MiniTable_FindUnknown(msg, 5, kUpb_WireFormat_DefaultDepthLimit);
+  EXPECT_EQ(unknown.status, kUpb_FindUnknown_Ok);
+  // Update mini table and promote unknown to a message.
+  EXPECT_TRUE(upb_MiniTable_SetSubMessage(
+      mini_table, (upb_MiniTableField*)&mini_table->fields[1],
+      &upb_test_ModelWithExtensions_msg_init));
+  const int decode_options = upb_DecodeOptions_MaxDepth(
+      kUpb_WireFormat_DefaultDepthLimit);  // UPB_DECODE_ALIAS disabled.
+  upb_UnknownToMessageRet promote_result =
+      upb_MiniTable_PromoteUnknownToMessage(
+          msg, mini_table, &mini_table->fields[1],
+          &upb_test_ModelWithExtensions_msg_init, decode_options, arena);
+  EXPECT_EQ(promote_result.status, kUpb_UnknownToMessage_Ok);
+  const upb_Message* promoted_message =
+      upb_Message_GetMessage(msg, &mini_table->fields[1], nullptr);
+  EXPECT_EQ(upb_test_ModelWithExtensions_random_int32(
+                (upb_test_ModelWithExtensions*)promoted_message),
+            12);
+  upb_Arena_Free(arena);
+}
+
+TEST(GeneratedCode, PromoteUnknownRepeatedMessage) {
+  upb_Arena* arena = upb_Arena_New();
+  upb_test_ModelWithSubMessages* input_msg =
+      upb_test_ModelWithSubMessages_new(arena);
+  upb_test_ModelWithSubMessages_set_id(input_msg, 123);
+
+  // Add 2 repeated messages to input_msg.
+  upb_test_ModelWithExtensions* item =
+      upb_test_ModelWithSubMessages_add_items(input_msg, arena);
+  upb_test_ModelWithExtensions_set_random_int32(item, 5);
+  item = upb_test_ModelWithSubMessages_add_items(input_msg, arena);
+  upb_test_ModelWithExtensions_set_random_int32(item, 6);
+
+  size_t serialized_size;
+  char* serialized = upb_test_ModelWithSubMessages_serialize(input_msg, arena,
+                                                             &serialized_size);
+
+  upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTables(arena);
+  upb_Message* msg = _upb_Message_New(mini_table, arena);
+  upb_DecodeStatus decode_status = upb_Decode(serialized, serialized_size, msg,
+                                              mini_table, nullptr, 0, arena);
+  EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
+  int32_t val = upb_Message_GetInt32(
+      msg, upb_MiniTable_FindFieldByNumber(mini_table, 4), 0);
+  EXPECT_EQ(val, 123);
+
+  // Check that we have repeated field data in an unknown.
+  upb_FindUnknownRet unknown =
+      upb_MiniTable_FindUnknown(msg, 6, kUpb_WireFormat_DefaultDepthLimit);
+  EXPECT_EQ(unknown.status, kUpb_FindUnknown_Ok);
+
+  // Update mini table and promote unknown to a message.
+  EXPECT_TRUE(upb_MiniTable_SetSubMessage(
+      mini_table, (upb_MiniTableField*)&mini_table->fields[2],
+      &upb_test_ModelWithExtensions_msg_init));
+  const int decode_options = upb_DecodeOptions_MaxDepth(
+      kUpb_WireFormat_DefaultDepthLimit);  // UPB_DECODE_ALIAS disabled.
+  upb_UnknownToMessage_Status promote_result =
+      upb_MiniTable_PromoteUnknownToMessageArray(
+          msg, &mini_table->fields[2], &upb_test_ModelWithExtensions_msg_init,
+          decode_options, arena);
+  EXPECT_EQ(promote_result, kUpb_UnknownToMessage_Ok);
+
+  upb_Array* array = upb_Message_GetMutableArray(msg, &mini_table->fields[2]);
+  const upb_Message* promoted_message = upb_Array_Get(array, 0).msg_val;
+  EXPECT_EQ(upb_test_ModelWithExtensions_random_int32(
+                (upb_test_ModelWithExtensions*)promoted_message),
+            5);
+  promoted_message = upb_Array_Get(array, 1).msg_val;
+  EXPECT_EQ(upb_test_ModelWithExtensions_random_int32(
+                (upb_test_ModelWithExtensions*)promoted_message),
+            6);
+  upb_Arena_Free(arena);
+}
+
+TEST(GeneratedCode, PromoteUnknownToMap) {
+  upb_Arena* arena = upb_Arena_New();
+  upb_test_ModelWithMaps* input_msg = upb_test_ModelWithMaps_new(arena);
+  upb_test_ModelWithMaps_set_id(input_msg, 123);
+
+  // Add 2 map entries.
+  upb_test_ModelWithMaps_map_ss_set(input_msg,
+                                    upb_StringView_FromString("key1"),
+                                    upb_StringView_FromString("value1"), arena);
+  upb_test_ModelWithMaps_map_ss_set(input_msg,
+                                    upb_StringView_FromString("key2"),
+                                    upb_StringView_FromString("value2"), arena);
+
+  size_t serialized_size;
+  char* serialized =
+      upb_test_ModelWithMaps_serialize(input_msg, arena, &serialized_size);
+
+  upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTablesForMaps(arena);
+  upb_MiniTable* map_entry_mini_table = CreateMapEntryMiniTable(arena);
+  upb_Message* msg = _upb_Message_New(mini_table, arena);
+  const int decode_options =
+      upb_DecodeOptions_MaxDepth(kUpb_WireFormat_DefaultDepthLimit);
+  upb_DecodeStatus decode_status =
+      upb_Decode(serialized, serialized_size, msg, mini_table, nullptr,
+                 decode_options, arena);
+  EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
+  int32_t val = upb_Message_GetInt32(
+      msg, upb_MiniTable_FindFieldByNumber(mini_table, 1), 0);
+  EXPECT_EQ(val, 123);
+
+  // Check that we have map data in an unknown.
+  upb_FindUnknownRet unknown =
+      upb_MiniTable_FindUnknown(msg, 3, kUpb_WireFormat_DefaultDepthLimit);
+  EXPECT_EQ(unknown.status, kUpb_FindUnknown_Ok);
+
+  // Update mini table and promote unknown to a message.
+  EXPECT_TRUE(upb_MiniTable_SetSubMessage(
+      mini_table, (upb_MiniTableField*)&mini_table->fields[1],
+      map_entry_mini_table));
+  upb_UnknownToMessage_Status promote_result =
+      upb_MiniTable_PromoteUnknownToMap(msg, mini_table, &mini_table->fields[1],
+                                        decode_options, arena);
+  EXPECT_EQ(promote_result, kUpb_UnknownToMessage_Ok);
+
+  upb_Map* map = upb_Message_GetOrCreateMutableMap(
+      msg, map_entry_mini_table, &mini_table->fields[1], arena);
+  EXPECT_NE(map, nullptr);
+  // Lookup in map.
+  upb_MessageValue key;
+  key.str_val = upb_StringView_FromString("key2");
+  upb_MessageValue value;
+  EXPECT_TRUE(upb_Map_Get(map, key, &value));
+  EXPECT_EQ(0, strncmp(value.str_val.data, "value2", 5));
+  upb_Arena_Free(arena);
+}
+
+}  // namespace