Skip to content

Commit 0dbd99a

Browse files
mkruskal-googlecopybara-github
authored andcommitted
Clarify map behaviors in editions.
Map fields should remain length-prefixed for now, even if DELIMITED is inherited. Field presence will remain unchanged, but unit-tests are added to make sure proto2/proto3 behaviors stay consistent. Closes #16549 PiperOrigin-RevId: 630191163
1 parent 9340eec commit 0dbd99a

File tree

2 files changed

+143
-39
lines changed

2 files changed

+143
-39
lines changed

src/google/protobuf/descriptor.cc

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4362,7 +4362,8 @@ class DescriptorBuilder {
43624362
DescriptorPool::ErrorCollector::ErrorLocation error_location,
43634363
bool force_merge = false);
43644364

4365-
void PostProcessFieldFeatures(FieldDescriptor& field);
4365+
void PostProcessFieldFeatures(FieldDescriptor& field,
4366+
const FieldDescriptorProto& proto);
43664367

43674368
// Allocates an array of two strings, the first one is a copy of
43684369
// `proto_name`, and the second one is the full name. Full proto name is
@@ -5542,7 +5543,8 @@ void DescriptorBuilder::ResolveFeatures(const FileDescriptorProto& proto,
55425543
/*force_merge=*/true);
55435544
}
55445545

5545-
void DescriptorBuilder::PostProcessFieldFeatures(FieldDescriptor& field) {
5546+
void DescriptorBuilder::PostProcessFieldFeatures(
5547+
FieldDescriptor& field, const FieldDescriptorProto& proto) {
55465548
// TODO This can be replace by a runtime check in `is_required`
55475549
// once the `label` getter is hidden.
55485550
if (field.features().field_presence() == FeatureSet::LEGACY_REQUIRED &&
@@ -5552,8 +5554,15 @@ void DescriptorBuilder::PostProcessFieldFeatures(FieldDescriptor& field) {
55525554
// TODO This can be replace by a runtime check of `is_delimited`
55535555
// once the `TYPE_GROUP` value is removed.
55545556
if (field.type_ == FieldDescriptor::TYPE_MESSAGE &&
5557+
!field.containing_type()->options().map_entry() &&
55555558
field.features().message_encoding() == FeatureSet::DELIMITED) {
5556-
field.type_ = FieldDescriptor::TYPE_GROUP;
5559+
Symbol type =
5560+
LookupSymbol(proto.type_name(), field.full_name(),
5561+
DescriptorPool::PLACEHOLDER_MESSAGE, LOOKUP_TYPES, false);
5562+
if (type.descriptor() == nullptr ||
5563+
!type.descriptor()->options().map_entry()) {
5564+
field.type_ = FieldDescriptor::TYPE_GROUP;
5565+
}
55575566
}
55585567
}
55595568

@@ -6099,9 +6108,11 @@ FileDescriptor* DescriptorBuilder::BuildFileImpl(
60996108
});
61006109

61016110
// Post-process cleanup for field features.
6102-
internal::VisitDescriptors(*result, [&](const FieldDescriptor& field) {
6103-
PostProcessFieldFeatures(const_cast<FieldDescriptor&>(field));
6104-
});
6111+
internal::VisitDescriptors(
6112+
*result, proto,
6113+
[&](const FieldDescriptor& field, const FieldDescriptorProto& proto) {
6114+
PostProcessFieldFeatures(const_cast<FieldDescriptor&>(field), proto);
6115+
});
61056116

61066117
// Interpret any remaining uninterpreted options gathered into
61076118
// options_to_interpret_ during descriptor building. Cross-linking has made

src/google/protobuf/descriptor_unittest.cc

Lines changed: 126 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4059,6 +4059,22 @@ class ValidationErrorTest : public testing::Test {
40594059
return ABSL_DIE_IF_NULL(pool_.BuildFile(file_proto));
40604060
}
40614061

4062+
const FileDescriptor* ParseAndBuildFile(absl::string_view file_name,
4063+
absl::string_view file_text) {
4064+
io::ArrayInputStream input_stream(file_text.data(), file_text.size());
4065+
SimpleErrorCollector error_collector;
4066+
io::Tokenizer tokenizer(&input_stream, &error_collector);
4067+
compiler::Parser parser;
4068+
parser.RecordErrorsTo(&error_collector);
4069+
FileDescriptorProto proto;
4070+
ABSL_CHECK(parser.Parse(&tokenizer, &proto))
4071+
<< error_collector.last_error() << "\n"
4072+
<< file_text;
4073+
ABSL_CHECK_EQ("", error_collector.last_error());
4074+
proto.set_name(file_name);
4075+
return pool_.BuildFile(proto);
4076+
}
4077+
40624078

40634079
// Add file_proto to the DescriptorPool. Expect errors to be produced which
40644080
// match the given error text.
@@ -8684,7 +8700,9 @@ TEST_F(FeaturesTest, OneofFieldFeaturesOverride) {
86848700
}
86858701

86868702
TEST_F(FeaturesTest, MapFieldFeaturesOverride) {
8687-
constexpr absl::string_view kProtoFile = R"schema(
8703+
BuildDescriptorMessagesInTestPool();
8704+
BuildFileInTestPool(pb::TestFeatures::descriptor()->file());
8705+
const FileDescriptor* file = ParseAndBuildFile("foo.proto", R"schema(
86888706
edition = "2023";
86898707
86908708
import "google/protobuf/unittest_features.proto";
@@ -8701,22 +8719,7 @@ TEST_F(FeaturesTest, MapFieldFeaturesOverride) {
87018719
features.(pb.test).multiple_feature = VALUE3
87028720
];
87038721
}
8704-
)schema";
8705-
io::ArrayInputStream input_stream(kProtoFile.data(), kProtoFile.size());
8706-
SimpleErrorCollector error_collector;
8707-
io::Tokenizer tokenizer(&input_stream, &error_collector);
8708-
compiler::Parser parser;
8709-
parser.RecordErrorsTo(&error_collector);
8710-
FileDescriptorProto proto;
8711-
ASSERT_TRUE(parser.Parse(&tokenizer, &proto))
8712-
<< error_collector.last_error() << "\n"
8713-
<< kProtoFile;
8714-
ASSERT_EQ("", error_collector.last_error());
8715-
proto.set_name("foo.proto");
8716-
8717-
BuildDescriptorMessagesInTestPool();
8718-
BuildFileInTestPool(pb::TestFeatures::descriptor()->file());
8719-
const FileDescriptor* file = pool_.BuildFile(proto);
8722+
)schema");
87208723
ASSERT_THAT(file, NotNull());
87218724

87228725
const FieldDescriptor* map_field = file->message_type(0)->field(0);
@@ -8744,7 +8747,8 @@ TEST_F(FeaturesTest, MapFieldFeaturesOverride) {
87448747
}
87458748

87468749
TEST_F(FeaturesTest, MapFieldFeaturesStringValidation) {
8747-
constexpr absl::string_view kProtoFile = R"schema(
8750+
BuildDescriptorMessagesInTestPool();
8751+
const FileDescriptor* file = ParseAndBuildFile("foo.proto", R"schema(
87488752
edition = "2023";
87498753
87508754
message Foo {
@@ -8758,21 +8762,7 @@ TEST_F(FeaturesTest, MapFieldFeaturesStringValidation) {
87588762
features.utf8_validation = NONE
87598763
];
87608764
}
8761-
)schema";
8762-
io::ArrayInputStream input_stream(kProtoFile.data(), kProtoFile.size());
8763-
SimpleErrorCollector error_collector;
8764-
io::Tokenizer tokenizer(&input_stream, &error_collector);
8765-
compiler::Parser parser;
8766-
parser.RecordErrorsTo(&error_collector);
8767-
FileDescriptorProto proto;
8768-
ASSERT_TRUE(parser.Parse(&tokenizer, &proto))
8769-
<< error_collector.last_error() << "\n"
8770-
<< kProtoFile;
8771-
ASSERT_EQ("", error_collector.last_error());
8772-
proto.set_name("foo.proto");
8773-
8774-
BuildDescriptorMessagesInTestPool();
8775-
const FileDescriptor* file = pool_.BuildFile(proto);
8765+
)schema");
87768766
ASSERT_THAT(file, NotNull());
87778767

87788768
auto validate_map_field = [](const FieldDescriptor* field) {
@@ -8789,6 +8779,109 @@ TEST_F(FeaturesTest, MapFieldFeaturesStringValidation) {
87898779
validate_map_field(file->message_type(0)->field(2));
87908780
}
87918781

8782+
TEST_F(FeaturesTest, MapFieldFeaturesImplicitPresence) {
8783+
BuildDescriptorMessagesInTestPool();
8784+
const FileDescriptor* editions = ParseAndBuildFile("editions.proto", R"schema(
8785+
edition = "2023";
8786+
8787+
option features.field_presence = IMPLICIT;
8788+
8789+
message Foo {
8790+
map<string, Foo> message_map = 1;
8791+
map<string, string> string_map = 2;
8792+
}
8793+
)schema");
8794+
ASSERT_THAT(editions, NotNull());
8795+
const FileDescriptor* proto3 = ParseAndBuildFile("proto3.proto", R"schema(
8796+
syntax = "proto3";
8797+
8798+
message Bar {
8799+
map<string, Bar> message_map = 1;
8800+
map<string, string> string_map = 2;
8801+
}
8802+
)schema");
8803+
ASSERT_THAT(proto3, NotNull());
8804+
8805+
auto validate_maps = [](const FileDescriptor* file) {
8806+
const FieldDescriptor* message_map = file->message_type(0)->field(0);
8807+
EXPECT_FALSE(message_map->has_presence());
8808+
EXPECT_FALSE(message_map->message_type()->field(0)->has_presence());
8809+
EXPECT_TRUE(message_map->message_type()->field(1)->has_presence());
8810+
8811+
const FieldDescriptor* string_map = file->message_type(0)->field(1);
8812+
EXPECT_FALSE(string_map->has_presence());
8813+
EXPECT_FALSE(string_map->message_type()->field(0)->has_presence());
8814+
EXPECT_FALSE(string_map->message_type()->field(1)->has_presence());
8815+
};
8816+
validate_maps(editions);
8817+
validate_maps(proto3);
8818+
}
8819+
8820+
TEST_F(FeaturesTest, MapFieldFeaturesExplicitPresence) {
8821+
BuildDescriptorMessagesInTestPool();
8822+
const FileDescriptor* editions = ParseAndBuildFile("editions.proto", R"schema(
8823+
edition = "2023";
8824+
8825+
message Foo {
8826+
map<string, Foo> message_map = 1;
8827+
map<string, string> string_map = 2;
8828+
}
8829+
)schema");
8830+
ASSERT_THAT(editions, NotNull());
8831+
const FileDescriptor* proto2 = ParseAndBuildFile("google.protobuf.proto", R"schema(
8832+
syntax = "proto2";
8833+
8834+
message Bar {
8835+
map<string, Bar> message_map = 1;
8836+
map<string, string> string_map = 2;
8837+
}
8838+
)schema");
8839+
ASSERT_THAT(proto2, NotNull());
8840+
8841+
auto validate_maps = [](const FileDescriptor* file) {
8842+
const FieldDescriptor* message_map = file->message_type(0)->field(0);
8843+
EXPECT_FALSE(message_map->has_presence());
8844+
EXPECT_TRUE(message_map->message_type()->field(0)->has_presence());
8845+
EXPECT_TRUE(message_map->message_type()->field(1)->has_presence());
8846+
8847+
const FieldDescriptor* string_map = file->message_type(0)->field(1);
8848+
EXPECT_FALSE(string_map->has_presence());
8849+
EXPECT_TRUE(string_map->message_type()->field(0)->has_presence());
8850+
EXPECT_TRUE(string_map->message_type()->field(1)->has_presence());
8851+
};
8852+
validate_maps(editions);
8853+
validate_maps(proto2);
8854+
}
8855+
8856+
TEST_F(FeaturesTest, MapFieldFeaturesInheritedMessageEncoding) {
8857+
BuildDescriptorMessagesInTestPool();
8858+
const FileDescriptor* file = ParseAndBuildFile("foo.proto", R"schema(
8859+
edition = "2023";
8860+
8861+
option features.message_encoding = DELIMITED;
8862+
8863+
message Foo {
8864+
map<int32, Foo> message_map = 1;
8865+
map<string, string> string_map = 2;
8866+
}
8867+
)schema");
8868+
ASSERT_THAT(file, NotNull());
8869+
8870+
const FieldDescriptor* message_map = file->message_type(0)->field(0);
8871+
EXPECT_EQ(message_map->type(), FieldDescriptor::TYPE_MESSAGE);
8872+
EXPECT_EQ(message_map->message_type()->field(0)->type(),
8873+
FieldDescriptor::TYPE_INT32);
8874+
EXPECT_EQ(message_map->message_type()->field(1)->type(),
8875+
FieldDescriptor::TYPE_MESSAGE);
8876+
8877+
const FieldDescriptor* string_map = file->message_type(0)->field(1);
8878+
EXPECT_EQ(string_map->type(), FieldDescriptor::TYPE_MESSAGE);
8879+
EXPECT_EQ(string_map->message_type()->field(0)->type(),
8880+
FieldDescriptor::TYPE_STRING);
8881+
EXPECT_EQ(string_map->message_type()->field(1)->type(),
8882+
FieldDescriptor::TYPE_STRING);
8883+
}
8884+
87928885
TEST_F(FeaturesTest, RootExtensionFeaturesOverride) {
87938886
BuildDescriptorMessagesInTestPool();
87948887
BuildFileInTestPool(pb::TestFeatures::descriptor()->file());

0 commit comments

Comments
 (0)