diff --git a/src/google/protobuf/compiler/BUILD.bazel b/src/google/protobuf/compiler/BUILD.bazel index 5898938000..58b8802ff4 100644 --- a/src/google/protobuf/compiler/BUILD.bazel +++ b/src/google/protobuf/compiler/BUILD.bazel @@ -69,6 +69,7 @@ cc_library( visibility = ["//visibility:public"], deps = [ "//src/google/protobuf:protobuf_nowkt", + "//src/google/protobuf/compiler:retention", "//src/google/protobuf/io:io_win32", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/log:absl_check", @@ -248,6 +249,24 @@ cc_binary( ], ) +cc_test( + name = "code_generator_unittest", + srcs = ["code_generator_unittest.cc"], + copts = COPTS, + deps = [ + ":code_generator", + ":importer", + "//src/google/protobuf:cc_test_protos", + "//src/google/protobuf:test_textproto", + "//src/google/protobuf/io", + "//src/google/protobuf/testing", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "command_line_interface_unittest", srcs = ["command_line_interface_unittest.cc"], diff --git a/src/google/protobuf/compiler/code_generator.h b/src/google/protobuf/compiler/code_generator.h index cff469753b..17674c90f2 100644 --- a/src/google/protobuf/compiler/code_generator.h +++ b/src/google/protobuf/compiler/code_generator.h @@ -43,7 +43,9 @@ #include #include "absl/strings/string_view.h" +#include "google/protobuf/compiler/retention.h" #include "google/protobuf/descriptor.h" +#include "google/protobuf/descriptor.pb.h" #include "google/protobuf/port.h" // Must be included last. @@ -143,6 +145,16 @@ class PROTOC_EXPORT CodeGenerator { static const FeatureSet& GetSourceRawFeatures(const DescriptorT& desc) { return ::google::protobuf::internal::InternalFeatureHelper::GetRawFeatures(desc); } + + // Converts a FileDescriptor to a FileDescriptorProto suitable for passing off + // to a runtime. Notably, this strips all source-retention options and + // includes both raw and resolved features. + static FileDescriptorProto GetRuntimeProto(const FileDescriptor& file) { + FileDescriptorProto proto = + ::google::protobuf::internal::InternalFeatureHelper::GetGeneratorProto(file); + StripSourceRetentionOptions(*file.pool(), proto); + return proto; + } #endif // PROTOBUF_FUTURE_EDITIONS }; diff --git a/src/google/protobuf/compiler/code_generator_unittest.cc b/src/google/protobuf/compiler/code_generator_unittest.cc new file mode 100644 index 0000000000..626342d3fe --- /dev/null +++ b/src/google/protobuf/compiler/code_generator_unittest.cc @@ -0,0 +1,299 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 Inc. 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 THE COPYRIGHT +// OWNER OR CONTRIBUTORS 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 "google/protobuf/compiler/code_generator.h" + +#include + +#include "google/protobuf/descriptor.pb.h" +#include +#include +#include "absl/log/absl_log.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "google/protobuf/compiler/parser.h" +#include "google/protobuf/io/tokenizer.h" +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" +#include "google/protobuf/test_textproto.h" +#include "google/protobuf/unittest_features.pb.h" + +namespace google { +namespace protobuf { +namespace compiler { +namespace { + +using ::testing::NotNull; + +class TestGenerator : public CodeGenerator { + public: + bool Generate(const FileDescriptor* file, const std::string& parameter, + GeneratorContext* generator_context, + std::string* error) const override { + return true; + } + + // Expose the protected methods for testing. + using CodeGenerator::GetRuntimeProto; + using CodeGenerator::GetSourceFeatures; + using CodeGenerator::GetSourceRawFeatures; +}; + +class SimpleErrorCollector : public io::ErrorCollector { + public: + void RecordError(int line, int column, absl::string_view message) override { + ABSL_LOG(ERROR) << absl::StrFormat("%d:%d:%s", line, column, message); + } +}; + +class CodeGeneratorTest : public ::testing::Test { + protected: + void SetUp() override { + ASSERT_THAT(BuildFile(DescriptorProto::descriptor()->file()), NotNull()); + ASSERT_THAT(BuildFile(pb::TestMessage::descriptor()->file()), NotNull()); + } + + const FileDescriptor* BuildFile(absl::string_view schema) { + io::ArrayInputStream input_stream(schema.data(), + static_cast(schema.size())); + SimpleErrorCollector error_collector; + io::Tokenizer tokenizer(&input_stream, &error_collector); + Parser parser; + parser.RecordErrorsTo(&error_collector); + FileDescriptorProto proto; + ABSL_CHECK(parser.Parse(&tokenizer, &proto)) << schema; + proto.set_name("test.proto"); + return pool_.BuildFile(proto); + } + + const FileDescriptor* BuildFile(const FileDescriptor* file) { + FileDescriptorProto proto; + file->CopyTo(&proto); + return pool_.BuildFile(proto); + } + + DescriptorPool pool_; +}; + +TEST_F(CodeGeneratorTest, GetSourceRawFeaturesRoot) { + auto file = BuildFile(R"schema( + edition = "2023"; + package protobuf_unittest; + + import "google/protobuf/unittest_features.proto"; + + option features.field_presence = EXPLICIT; // 2023 default + option features.enum_type = CLOSED; // override + option features.(pb.test).int_file_feature = 8; + option features.(pb.test).string_source_feature = "file"; + )schema"); + ASSERT_THAT(file, NotNull()); + + EXPECT_THAT(TestGenerator::GetSourceRawFeatures(*file), + google::protobuf::EqualsProto(R"pb( + field_presence: EXPLICIT + enum_type: CLOSED + [pb.test] { int_file_feature: 8 string_source_feature: "file" } + )pb")); +} + +TEST_F(CodeGeneratorTest, GetSourceRawFeaturesInherited) { + auto file = BuildFile(R"schema( + edition = "2023"; + package protobuf_unittest; + + import "google/protobuf/unittest_features.proto"; + + option features.enum_type = OPEN; + option features.(pb.test).int_file_feature = 6; + message EditionsMessage { + option features.(pb.test).int_message_feature = 7; + option features.(pb.test).int_multiple_feature = 8; + + string field = 1 [ + features.field_presence = EXPLICIT, + features.(pb.test).int_multiple_feature = 9, + features.(pb.test).string_source_feature = "field" + ]; + } + )schema"); + ASSERT_THAT(file, NotNull()); + + const FieldDescriptor* field = + file->FindMessageTypeByName("EditionsMessage")->FindFieldByName("field"); + ASSERT_THAT(field, NotNull()); + + EXPECT_THAT( + TestGenerator::GetSourceRawFeatures(*field), google::protobuf::EqualsProto(R"pb( + field_presence: EXPLICIT + [pb.test] { int_multiple_feature: 9 string_source_feature: "field" } + )pb")); +} + +TEST_F(CodeGeneratorTest, GetSourceFeaturesRoot) { + auto file = BuildFile(R"schema( + edition = "2023"; + package protobuf_unittest; + + import "google/protobuf/unittest_features.proto"; + + option features.field_presence = EXPLICIT; // 2023 default + option features.enum_type = CLOSED; // override + option features.(pb.test).int_file_feature = 8; + option features.(pb.test).string_source_feature = "file"; + )schema"); + ASSERT_THAT(file, NotNull()); + + const FeatureSet& features = TestGenerator::GetSourceFeatures(*file); + const pb::TestFeatures& ext = features.GetExtension(pb::test); + + EXPECT_TRUE(features.has_repeated_field_encoding()); + EXPECT_TRUE(features.field_presence()); + EXPECT_EQ(features.field_presence(), FeatureSet::EXPLICIT); + EXPECT_EQ(features.enum_type(), FeatureSet::CLOSED); + + EXPECT_TRUE(ext.has_int_message_feature()); + EXPECT_EQ(ext.int_file_feature(), 8); + EXPECT_EQ(ext.string_source_feature(), "file"); +} + +TEST_F(CodeGeneratorTest, GetSourceFeaturesInherited) { + auto file = BuildFile(R"schema( + edition = "2023"; + package protobuf_unittest; + + import "google/protobuf/unittest_features.proto"; + + option features.enum_type = CLOSED; + option features.(pb.test).int_source_feature = 5; + option features.(pb.test).int_file_feature = 6; + message EditionsMessage { + option features.(pb.test).int_message_feature = 7; + option features.(pb.test).int_multiple_feature = 8; + option features.(pb.test).string_source_feature = "message"; + + string field = 1 [ + features.field_presence = IMPLICIT, + features.(pb.test).int_multiple_feature = 9, + features.(pb.test).string_source_feature = "field" + ]; + } + )schema"); + ASSERT_THAT(file, NotNull()); + + const FieldDescriptor* field = + file->FindMessageTypeByName("EditionsMessage")->FindFieldByName("field"); + ASSERT_THAT(field, NotNull()); + const FeatureSet& features = TestGenerator::GetSourceFeatures(*field); + const pb::TestFeatures& ext = features.GetExtension(pb::test); + + EXPECT_EQ(features.enum_type(), FeatureSet::CLOSED); + EXPECT_EQ(features.field_presence(), FeatureSet::IMPLICIT); + + EXPECT_EQ(ext.int_message_feature(), 7); + EXPECT_EQ(ext.int_file_feature(), 6); + EXPECT_EQ(ext.int_multiple_feature(), 9); + EXPECT_EQ(ext.int_source_feature(), 5); + EXPECT_EQ(ext.string_source_feature(), "field"); +} + +TEST_F(CodeGeneratorTest, GetRuntimeProtoRoot) { + auto file = BuildFile(R"schema( + edition = "2023"; + package protobuf_unittest; + + import "google/protobuf/unittest_features.proto"; + + option features.enum_type = CLOSED; + option features.(pb.test).int_source_feature = 5; + option features.(pb.test).int_file_feature = 6; + )schema"); + ASSERT_THAT(file, NotNull()); + + FileDescriptorProto proto = TestGenerator::GetRuntimeProto(*file); + const FeatureSet& features = proto.options().features(); + const pb::TestFeatures& ext = features.GetExtension(pb::test); + + EXPECT_THAT(features.raw_features(), + EqualsProto(R"pb(enum_type: CLOSED + [pb.test] { int_file_feature: 6 })pb")); + EXPECT_EQ(features.enum_type(), FeatureSet::CLOSED); + EXPECT_TRUE(features.has_field_presence()); + EXPECT_EQ(features.field_presence(), FeatureSet::EXPLICIT); + + EXPECT_FALSE(ext.has_int_source_feature()); + EXPECT_EQ(ext.int_file_feature(), 6); +} + +TEST_F(CodeGeneratorTest, GetRuntimeProtoInherited) { + auto file = BuildFile(R"schema( + edition = "2023"; + package protobuf_unittest; + + import "google/protobuf/unittest_features.proto"; + + option features.enum_type = CLOSED; + option features.(pb.test).int_source_feature = 5; + option features.(pb.test).int_file_feature = 6; + message EditionsMessage { + option features.(pb.test).int_message_feature = 7; + option features.(pb.test).int_multiple_feature = 8; + + string field = 1 [ + features.field_presence = IMPLICIT, + features.(pb.test).int_multiple_feature = 9, + features.(pb.test).string_source_feature = "field" + ]; + } + )schema"); + ASSERT_THAT(file, NotNull()); + + FileDescriptorProto proto = TestGenerator::GetRuntimeProto(*file); + const FieldDescriptorProto& field = proto.message_type(0).field(0); + const FeatureSet& features = field.options().features(); + const pb::TestFeatures& ext = features.GetExtension(pb::test); + + EXPECT_THAT(features.raw_features(), google::protobuf::EqualsProto(R"pb( + field_presence: IMPLICIT + [pb.test] { int_multiple_feature: 9 } + )pb")); + EXPECT_EQ(features.enum_type(), FeatureSet::CLOSED); + EXPECT_EQ(features.field_presence(), FeatureSet::IMPLICIT); + EXPECT_EQ(ext.int_multiple_feature(), 9); + EXPECT_EQ(ext.int_message_feature(), 7); + EXPECT_EQ(ext.int_file_feature(), 6); + EXPECT_FALSE(ext.has_int_source_feature()); + EXPECT_FALSE(ext.has_string_source_feature()); +} + +} // namespace +} // namespace compiler +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/compiler/retention.cc b/src/google/protobuf/compiler/retention.cc index 822df6096f..38df6210d1 100644 --- a/src/google/protobuf/compiler/retention.cc +++ b/src/google/protobuf/compiler/retention.cc @@ -248,16 +248,22 @@ FileDescriptorProto StripSourceRetentionOptions(const FileDescriptor& file, bool include_source_code_info) { FileDescriptorProto file_proto; file.CopyTo(&file_proto); - std::vector> stripped_paths; - ConvertToDynamicMessageAndStripOptions(file_proto, *file.pool(), - &stripped_paths); if (include_source_code_info) { file.CopySourceCodeInfoTo(&file_proto); - StripSourceCodeInfo(stripped_paths, *file_proto.mutable_source_code_info()); } + StripSourceRetentionOptions(*file.pool(), file_proto); return file_proto; } +void StripSourceRetentionOptions(const DescriptorPool& pool, + FileDescriptorProto& file_proto) { + std::vector> stripped_paths; + ConvertToDynamicMessageAndStripOptions(file_proto, pool, &stripped_paths); + if (file_proto.has_source_code_info()) { + StripSourceCodeInfo(stripped_paths, *file_proto.mutable_source_code_info()); + } +} + DescriptorProto StripSourceRetentionOptions(const Descriptor& message) { DescriptorProto message_proto; message.CopyTo(&message_proto); diff --git a/src/google/protobuf/compiler/retention.h b/src/google/protobuf/compiler/retention.h index bf7fe0e1d6..63353b2c7f 100644 --- a/src/google/protobuf/compiler/retention.h +++ b/src/google/protobuf/compiler/retention.h @@ -47,6 +47,8 @@ namespace compiler { // corresponding to source-retention options. PROTOC_EXPORT FileDescriptorProto StripSourceRetentionOptions( const FileDescriptor& file, bool include_source_code_info = false); +PROTOC_EXPORT void StripSourceRetentionOptions(const DescriptorPool& pool, + FileDescriptorProto& file_proto); PROTOC_EXPORT DescriptorProto StripSourceRetentionOptions(const Descriptor& message); PROTOC_EXPORT DescriptorProto::ExtensionRange StripSourceRetentionOptions( diff --git a/src/google/protobuf/compiler/retention_unittest.cc b/src/google/protobuf/compiler/retention_unittest.cc index fa14e597ae..b0629e01a0 100644 --- a/src/google/protobuf/compiler/retention_unittest.cc +++ b/src/google/protobuf/compiler/retention_unittest.cc @@ -115,16 +115,7 @@ class RetentionStripTest : public testing::Test { DynamicMessageFactory factory; std::unique_ptr dynamic_message( factory.GetPrototype(file_options_descriptor)->New()); - ABSL_CHECK(TextFormat::ParseFromString( - R"([google.protobuf.internal.options] { - i2: 456 - c {} - rc {} - } - [google.protobuf.internal.repeated_options] { - i2: 222 - })", - dynamic_message.get())); + ABSL_CHECK(TextFormat::ParseFromString(data, dynamic_message.get())); ProtoType ret; ABSL_CHECK(ret.ParseFromString(dynamic_message->SerializeAsString())); return ret; @@ -179,6 +170,60 @@ TEST_F(RetentionStripTest, StripSourceRetentionFileOptions) { EqualsProto(expected_options)); } +TEST_F(RetentionStripTest, StripSourceRetentionProtoFileOptions) { + const FileDescriptor* file = ParseSchema(R"schema( + option (source_retention_option) = 123; + option (options) = { + i1: 123 + i2: 456 + c { s: "abc" } + rc { s: "abc" } + }; + option (repeated_options) = { + i1: 111 i2: 222 + }; + + message Options { + optional int32 i1 = 1 [retention = RETENTION_SOURCE]; + optional int32 i2 = 2; + message ChildMessage { + optional string s = 1 [retention = RETENTION_SOURCE]; + } + optional ChildMessage c = 3; + repeated ChildMessage rc = 4; + } + + extend google.protobuf.FileOptions { + optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE]; + optional Options options = 50001; + repeated Options repeated_options = 50002; + } + )schema"); + + FileDescriptorProto proto; + file->CopyTo(&proto); + + ASSERT_THAT(proto.options(), EqualsProto(BuildDynamicProto(R"pb( + [google.protobuf.internal.source_retention_option]: 123 + [google.protobuf.internal.options] { + i1: 123 + i2: 456 + c { s: "abc" } + rc { s: "abc" } + } + [google.protobuf.internal.repeated_options] { i1: 111 i2: 222 })pb"))); + + StripSourceRetentionOptions(*file->pool(), proto); + + EXPECT_THAT(proto.options(), EqualsProto(BuildDynamicProto(R"pb( + [google.protobuf.internal.options] { + i2: 456 + c {} + rc {} + } + [google.protobuf.internal.repeated_options] { i2: 222 })pb"))); +} + TEST_F(RetentionStripTest, StripSourceRetentionMessageOptions) { const FileDescriptor* file = ParseSchema(R"schema( message TestMessage { diff --git a/src/google/protobuf/descriptor.cc b/src/google/protobuf/descriptor.cc index 1f15e59c88..4531801c6b 100644 --- a/src/google/protobuf/descriptor.cc +++ b/src/google/protobuf/descriptor.cc @@ -2759,6 +2759,32 @@ FileDescriptor::FileDescriptor() {} // CopyTo methods ==================================================== +#ifdef PROTOBUF_FUTURE_EDITIONS +namespace internal { +FileDescriptorProto InternalFeatureHelper::GetGeneratorProto( + const FileDescriptor& file) { + FileDescriptorProto file_proto; + file.CopyTo(&file_proto); + + // Insert all the raw features back into the proto. + internal::VisitDescriptors( + file, file_proto, [](const auto& desc, auto& proto) { + const auto& features = GetFeatures(desc); + if (&features != &FeatureSet::default_instance() && + !IsLegacyFeatureSet(features)) { + *proto.mutable_options()->mutable_features() = features; + } + const auto& raw_features = GetRawFeatures(desc); + if (&raw_features != &FeatureSet::default_instance()) { + *proto.mutable_options()->mutable_features()->mutable_raw_features() = + GetRawFeatures(desc); + } + }); + return file_proto; +} +} // namespace internal +#endif // PROTOBUF_FUTURE_EDITIONS + void FileDescriptor::CopyTo(FileDescriptorProto* proto) const { CopyHeadingTo(proto); @@ -5407,7 +5433,14 @@ void DescriptorBuilder::ResolveFeaturesImpl( // internal details. FeatureSet* mutable_features = alloc.AllocateArray(1); descriptor->proto_features_ = mutable_features; - options->mutable_features()->Swap(mutable_features); + if (options->features().has_raw_features()) { + // If the raw features are specified, use those and recalculate the + // resolved features. + options->mutable_features()->mutable_raw_features()->Swap( + mutable_features); + } else { + options->mutable_features()->Swap(mutable_features); + } options->clear_features(); } else if (!force_merge) { // Nothing to merge, and we aren't forcing it. diff --git a/src/google/protobuf/descriptor.h b/src/google/protobuf/descriptor.h index 1bd40c8aff..0c0eccbf29 100644 --- a/src/google/protobuf/descriptor.h +++ b/src/google/protobuf/descriptor.h @@ -262,7 +262,7 @@ class PROTOBUF_EXPORT SymbolBaseN : public SymbolBase {}; // This class is for internal use only and provides access to the FeatureSets // defined on descriptors. These features are not designed to be stable, and // depending directly on them (vs the public descriptor APIs) is not safe. -class InternalFeatureHelper { +class PROTOBUF_EXPORT InternalFeatureHelper { public: template static const FeatureSet& GetFeatures(const DescriptorT& desc) { @@ -271,10 +271,21 @@ class InternalFeatureHelper { private: friend class ::google::protobuf::compiler::CodeGenerator; + + // Provides a restricted view exclusively to code generators. Raw features + // haven't been resolved, and are virtually meaningless to everyone else. Code + // generators will need them to validate their own features, and runtimes may + // need them internally to be able to properly represent the original proto + // files from generated code. template static const FeatureSet& GetRawFeatures(const DescriptorT& desc) { return *desc.proto_features_; } + + // Provides the full descriptor tree including both resolved features (in the + // `features` fields) and unresolved features (in the `raw_features` fields) + // for every descriptor. + static FileDescriptorProto GetGeneratorProto(const FileDescriptor& file); }; #endif // PROTOBUF_FUTURE_EDITIONS diff --git a/src/google/protobuf/descriptor_unittest.cc b/src/google/protobuf/descriptor_unittest.cc index bef2eb1846..cb59fa1645 100644 --- a/src/google/protobuf/descriptor_unittest.cc +++ b/src/google/protobuf/descriptor_unittest.cc @@ -9538,6 +9538,47 @@ TEST_F(FeaturesTest, UninterpretedOptionsMergeExtension) { 9); } +TEST_F(FeaturesTest, RawFeatures) { + BuildDescriptorMessagesInTestPool(); + const FileDescriptor* file = BuildFile(R"pb( + name: "foo.proto" + syntax: "editions" + edition: "2023" + options { features { raw_features { field_presence: IMPLICIT } } } + )pb"); + EXPECT_THAT(file->options(), EqualsProto("")); + EXPECT_THAT(GetFeatures(file), EqualsProto(R"pb( + field_presence: IMPLICIT + enum_type: OPEN + repeated_field_encoding: PACKED + string_field_validation: MANDATORY + message_encoding: LENGTH_PREFIXED + json_format: ALLOW)pb")); +} + +TEST_F(FeaturesTest, RawFeaturesConflict) { + BuildDescriptorMessagesInTestPool(); + const FileDescriptor* file = BuildFile(R"pb( + name: "foo.proto" + syntax: "editions" + edition: "2023" + options { + features { + enum_type: CLOSED + raw_features { field_presence: IMPLICIT } + } + } + )pb"); + EXPECT_THAT(file->options(), EqualsProto("")); + EXPECT_THAT(GetFeatures(file), EqualsProto(R"pb( + field_presence: IMPLICIT + enum_type: OPEN + repeated_field_encoding: PACKED + string_field_validation: MANDATORY + message_encoding: LENGTH_PREFIXED + json_format: ALLOW)pb")); +} + TEST_F(FeaturesTest, InvalidJsonUniquenessDefaultWarning) { BuildFileWithWarnings( R"pb( diff --git a/src/google/protobuf/unittest_features.proto b/src/google/protobuf/unittest_features.proto index 79ac22e951..7ba44b19b2 100644 --- a/src/google/protobuf/unittest_features.proto +++ b/src/google/protobuf/unittest_features.proto @@ -164,4 +164,32 @@ message TestFeatures { edition_defaults = { edition: "2023", value: "ENUM_VALUE1" }, edition_defaults = { edition: "2024", value: "ENUM_VALUE4" } ]; + + optional int32 int_source_feature = 15 [ + retention = RETENTION_SOURCE, + targets = TARGET_TYPE_FILE, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_ENUM_ENTRY, + targets = TARGET_TYPE_SERVICE, + targets = TARGET_TYPE_METHOD, + targets = TARGET_TYPE_ONEOF, + targets = TARGET_TYPE_EXTENSION_RANGE, + edition_defaults = { edition: "2023", value: "1" } + ]; + + optional string string_source_feature = 16 [ + retention = RETENTION_SOURCE, + targets = TARGET_TYPE_FILE, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_ENUM_ENTRY, + targets = TARGET_TYPE_SERVICE, + targets = TARGET_TYPE_METHOD, + targets = TARGET_TYPE_ONEOF, + targets = TARGET_TYPE_EXTENSION_RANGE, + edition_defaults = { edition: "2023", value: "'2023'" } + ]; }