Implement proto2/proto3 with editions

This change migrates proto2 and proto3 to real editions, without impacting users at all.  While we already had feature defaults set, we weren't really using them outside of editions.  This change introduces InferLegacyProtoFeatures, which infers features from descriptors.  Any language that wants to follow this rollout pattern will need to implement similar transformations.

PiperOrigin-RevId: 572593956
pull/14300/head
Mike Kruskal 1 year ago committed by Copybara-Service
parent 9ad2268544
commit 3813b6622f
  1. 2
      src/google/protobuf/compiler/command_line_interface.cc
  2. 12
      src/google/protobuf/compiler/command_line_interface_unittest.cc
  3. 4
      src/google/protobuf/compiler/java/helpers.h
  4. 333
      src/google/protobuf/descriptor.cc
  5. 4
      src/google/protobuf/descriptor.h
  6. 16
      src/google/protobuf/descriptor_unittest.cc
  7. 2
      src/google/protobuf/port_def.inc
  8. 3
      src/google/protobuf/unittest_features.proto

@ -1554,7 +1554,7 @@ bool CommandLineInterface::SetupFeatureResolution(DescriptorPool& pool) {
<< " specifies a maximum edition "
<< output.generator->GetMaximumEdition()
<< " which is not the protoc maximum "
<< PROTOBUF_MINIMUM_EDITION << ".";
<< PROTOBUF_MAXIMUM_EDITION << ".";
return false;
}
for (const FieldDescriptor* ext :

@ -1489,7 +1489,7 @@ TEST_F(CommandLineInterfaceTest, InvalidMinimumEditionError) {
"--experimental_editions foo.proto");
ExpectErrorSubstring(
"generator --test_out specifies a minimum edition 1_TEST_ONLY which is "
"not the protoc minimum 2023");
"not the protoc minimum PROTO2");
}
TEST_F(CommandLineInterfaceTest, InvalidMaximumEditionError) {
@ -1780,7 +1780,7 @@ TEST_F(CommandLineInterfaceTest, EditionDefaults) {
json_format: ALLOW
}
}
minimum_edition: EDITION_2023
minimum_edition: EDITION_PROTO2
maximum_edition: EDITION_2023
)pb"));
}
@ -1829,7 +1829,7 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithMaximum) {
json_format: ALLOW
}
}
minimum_edition: EDITION_2023
minimum_edition: EDITION_PROTO2
maximum_edition: EDITION_99997_TEST_ONLY
)pb"));
}
@ -1896,7 +1896,7 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithExtension) {
ExpectNoErrors();
FeatureSetDefaults defaults = ReadEditionDefaults("defaults");
EXPECT_EQ(defaults.minimum_edition(), EDITION_2023);
EXPECT_EQ(defaults.minimum_edition(), EDITION_PROTO2);
EXPECT_EQ(defaults.maximum_edition(), EDITION_99999_TEST_ONLY);
ASSERT_EQ(defaults.defaults_size(), 5);
EXPECT_EQ(defaults.defaults(0).edition(), EDITION_PROTO2);
@ -1906,10 +1906,10 @@ TEST_F(CommandLineInterfaceTest, EditionDefaultsWithExtension) {
EXPECT_EQ(defaults.defaults(4).edition(), EDITION_99998_TEST_ONLY);
EXPECT_EQ(
defaults.defaults(0).features().GetExtension(pb::test).int_file_feature(),
0);
-2);
EXPECT_EQ(
defaults.defaults(1).features().GetExtension(pb::test).int_file_feature(),
0);
-3);
EXPECT_EQ(
defaults.defaults(2).features().GetExtension(pb::test).int_file_feature(),
1);

@ -350,7 +350,9 @@ inline bool ExposePublicParser(const FileDescriptor* descriptor) {
// ints.
inline bool SupportUnknownEnumValue(const FieldDescriptor* field) {
// TODO: Check Java legacy_enum_field_treated_as_closed feature.
return !field->legacy_enum_field_treated_as_closed();
return field->type() != FieldDescriptor::TYPE_ENUM ||
FileDescriptorLegacy(field->file()).syntax() ==
FileDescriptorLegacy::SYNTAX_PROTO3;
}
// Check whether a message has repeated fields.

@ -1103,55 +1103,6 @@ const FeatureSetDefaults& GetCppFeatureSetDefaults() {
return *default_spec;
}
// Create global defaults for proto2/proto3 compatibility.
FeatureSet* CreateProto2DefaultFeatures() {
FeatureSet* features = new FeatureSet();
features->set_field_presence(FeatureSet::EXPLICIT);
features->set_enum_type(FeatureSet::CLOSED);
features->set_repeated_field_encoding(FeatureSet::EXPANDED);
features->set_message_encoding(FeatureSet::LENGTH_PREFIXED);
features->set_json_format(FeatureSet::LEGACY_BEST_EFFORT);
features->set_utf8_validation(FeatureSet::UNVERIFIED);
features->MutableExtension(pb::cpp)->set_legacy_closed_enum(true);
return features;
}
const FeatureSet& GetProto2Features() {
static const FeatureSet* kProto2Features = CreateProto2DefaultFeatures();
return *kProto2Features;
}
const FeatureSet& GetProto2GroupFeatures() {
static const FeatureSet* kProto2GroupFeatures = [] {
FeatureSet* features = CreateProto2DefaultFeatures();
features->set_message_encoding(FeatureSet::DELIMITED);
return features;
}();
return *kProto2GroupFeatures;
}
const FeatureSet& GetProto3Features() {
static const FeatureSet* kProto3Features = [] {
FeatureSet* features = new FeatureSet();
features->set_field_presence(FeatureSet::IMPLICIT);
features->set_enum_type(FeatureSet::OPEN);
features->set_repeated_field_encoding(FeatureSet::PACKED);
features->set_utf8_validation(FeatureSet::VERIFY);
features->set_message_encoding(FeatureSet::LENGTH_PREFIXED);
features->set_json_format(FeatureSet::ALLOW);
features->MutableExtension(pb::cpp)->set_legacy_closed_enum(false);
return features;
}();
return *kProto3Features;
}
bool IsLegacyFeatureSet(const FeatureSet& features) {
return &features == &GetProto2Features() ||
&features == &GetProto2GroupFeatures() ||
&features == &GetProto3Features();
}
template <typename ProtoT>
void RestoreFeaturesToOptions(const FeatureSet* features, ProtoT* proto) {
if (features != &FeatureSet::default_instance()) {
@ -1173,14 +1124,6 @@ bool HasFeatures(const OptionsT& options) {
}
const FeatureSet& GetParentFeatures(const FileDescriptor* file) {
if (FileDescriptorLegacy(file).syntax() ==
FileDescriptorLegacy::SYNTAX_PROTO3) {
return GetProto3Features();
}
if (FileDescriptorLegacy(file).syntax() ==
FileDescriptorLegacy::SYNTAX_PROTO2) {
return GetProto2Features();
}
return FeatureSet::default_instance();
}
@ -3817,11 +3760,7 @@ bool EnumDescriptor::is_closed() const {
bool FieldDescriptor::is_packed() const {
if (!is_packable()) return false;
if (features().repeated_field_encoding() != FeatureSet::PACKED) {
return (options_ != nullptr) && options_->packed();
} else {
return options_ == nullptr || !options_->has_packed() || options_->packed();
}
return features().repeated_field_encoding() == FeatureSet::PACKED;
}
static bool IsStrictUtf8(const FieldDescriptor* field) {
@ -3839,14 +3778,14 @@ bool FieldDescriptor::has_presence() const {
features().field_presence() != FeatureSet::IMPLICIT;
}
bool FieldDescriptor::is_required() const {
return features().field_presence() == FeatureSet::LEGACY_REQUIRED;
}
bool FieldDescriptor::legacy_enum_field_treated_as_closed() const {
return type() == TYPE_ENUM &&
(features().GetExtension(pb::cpp).legacy_closed_enum() ||
enum_type()->is_closed());
return type() == TYPE_ENUM &&
(FileDescriptorLegacy(file_).syntax() ==
FileDescriptorLegacy::Syntax::SYNTAX_PROTO2 ||
enum_type()->is_closed());
}
// Location methods ===============================================
@ -4264,12 +4203,6 @@ class DescriptorBuilder {
DescriptorPool::ErrorCollector::ErrorLocation error_location,
bool force_merge = false);
// Performs descriptor-specific overrides of proto2/proto3 defaults for
// descriptors outside editions.
template <class DescriptorT>
const FeatureSet* GetLegacyFeatureOverride(const FeatureSet* parent_features,
const DescriptorT* descriptor);
void PostProcessFieldFeatures(FieldDescriptor& field);
// Allocates an array of two strings, the first one is a copy of
@ -4382,12 +4315,12 @@ class DescriptorBuilder {
// Interprets the uninterpreted options in the specified Options message.
// On error, calls AddError() on the underlying builder and returns false.
// Otherwise returns true.
bool InterpretOptions(OptionsToInterpret* options_to_interpret);
bool InterpretOptionExtensions(OptionsToInterpret* options_to_interpret);
// Interprets the uninterpreted feature options in the specified Options
// message. On error, calls AddError() on the underlying builder and returns
// false. Otherwise returns true.
bool InterpretFeatures(OptionsToInterpret* options_to_interpret);
bool InterpretNonExtensionOptions(OptionsToInterpret* options_to_interpret);
// Updates the given source code info by re-writing uninterpreted option
// locations to refer to the corresponding interpreted option.
@ -4397,7 +4330,7 @@ class DescriptorBuilder {
private:
bool InterpretOptionsImpl(OptionsToInterpret* options_to_interpret,
bool features);
bool skip_extensions);
// Interprets uninterpreted_option_ on the specified message, which
// must be the mutable copy of the original options message to which
@ -4410,7 +4343,7 @@ class DescriptorBuilder {
bool InterpretSingleOption(Message* options,
const std::vector<int>& src_path,
const std::vector<int>& options_path,
bool features);
bool skip_extensions);
// Adds the uninterpreted_option to the given options message verbatim.
// Used when AllowUnknownDependencies() is in effect and we can't find
@ -5307,6 +5240,44 @@ typename DescriptorT::OptionsType* DescriptorBuilder::AllocateOptionsImpl(
return options;
}
template <class ProtoT, class OptionsT>
static void InferLegacyProtoFeatures(const ProtoT& proto,
const OptionsT& options,
FileDescriptorLegacy::Syntax syntax,
FeatureSet& features) {}
static void InferLegacyProtoFeatures(const FieldDescriptorProto& proto,
const FieldOptions& options,
FileDescriptorLegacy::Syntax syntax,
FeatureSet& features) {
if (proto.label() == FieldDescriptorProto::LABEL_REQUIRED) {
features.set_field_presence(FeatureSet::LEGACY_REQUIRED);
}
if (proto.type() == FieldDescriptorProto::TYPE_GROUP) {
features.set_message_encoding(FeatureSet::DELIMITED);
}
if (options.packed()) {
features.set_repeated_field_encoding(FeatureSet::PACKED);
}
if (syntax == FileDescriptorLegacy::SYNTAX_PROTO3) {
if (options.has_packed() && !options.packed()) {
features.set_repeated_field_encoding(FeatureSet::EXPANDED);
}
}
}
template <class DescriptorT>
FileDescriptorLegacy::Syntax GetDescriptorSyntax(
const DescriptorT* descriptor) {
return FileDescriptorLegacy(descriptor->file()).syntax();
}
template <>
FileDescriptorLegacy::Syntax GetDescriptorSyntax(
const FileDescriptor* descriptor) {
return FileDescriptorLegacy(descriptor).syntax();
}
template <class DescriptorT>
void DescriptorBuilder::ResolveFeaturesImpl(
const typename DescriptorT::Proto& proto, DescriptorT* descriptor,
@ -5314,17 +5285,10 @@ void DescriptorBuilder::ResolveFeaturesImpl(
DescriptorPool::ErrorCollector::ErrorLocation error_location,
bool force_merge) {
const FeatureSet& parent_features = GetParentFeatures(descriptor);
descriptor->merged_features_ =
GetLegacyFeatureOverride(&parent_features, descriptor);
descriptor->proto_features_ = &FeatureSet::default_instance();
if (!feature_resolver_.has_value()) {
if (options != nullptr && options->has_features()) {
AddError(descriptor->name(), proto, error_location,
"Features are only valid under editions.");
}
return;
}
descriptor->merged_features_ = &FeatureSet::default_instance();
ABSL_CHECK(feature_resolver_.has_value());
if (options != nullptr && options->has_features()) {
// Remove the features from the child's options proto to avoid leaking
@ -5332,14 +5296,30 @@ void DescriptorBuilder::ResolveFeaturesImpl(
descriptor->proto_features_ =
tables_->InternFeatureSet(std::move(*options->mutable_features()));
options->clear_features();
} else if (!force_merge) {
}
FeatureSet base_features = *descriptor->proto_features_;
// Handle feature inference from proto2/proto3.
if (GetDescriptorSyntax(descriptor) !=
FileDescriptorLegacy::SYNTAX_EDITIONS) {
if (descriptor->proto_features_ != &FeatureSet::default_instance()) {
AddError(descriptor->name(), proto, error_location,
"Features are only valid under editions.");
}
InferLegacyProtoFeatures(proto, *options, GetDescriptorSyntax(descriptor),
base_features);
}
if (base_features.ByteSizeLong() == 0 && !force_merge) {
// Nothing to merge, and we aren't forcing it.
descriptor->merged_features_ = &parent_features;
return;
}
// Calculate the merged features for this target.
absl::StatusOr<FeatureSet> merged = feature_resolver_->MergeFeatures(
parent_features, *descriptor->proto_features_);
absl::StatusOr<FeatureSet> merged =
feature_resolver_->MergeFeatures(parent_features, base_features);
if (!merged.ok()) {
AddError(descriptor->name(), proto, error_location,
[&] { return std::string(merged.status().message()); });
@ -5369,23 +5349,6 @@ void DescriptorBuilder::ResolveFeatures(const FileDescriptorProto& proto,
/*force_merge=*/true);
}
template <typename DescriptorT>
const FeatureSet* DescriptorBuilder::GetLegacyFeatureOverride(
const FeatureSet* parent_features, const DescriptorT* descriptor) {
return parent_features;
}
template <>
const FeatureSet* DescriptorBuilder::GetLegacyFeatureOverride(
const FeatureSet* parent_features, const FieldDescriptor* descriptor) {
// Groups use delimited message encoding.
if (parent_features == &GetProto2Features() &&
descriptor->type_ == FieldDescriptor::TYPE_GROUP) {
return &GetProto2GroupFeatures();
}
return parent_features;
}
void DescriptorBuilder::PostProcessFieldFeatures(FieldDescriptor& field) {
// TODO This can be replace by a runtime check in `is_required`
// once the `label` getter is hidden.
@ -5685,21 +5648,37 @@ FileDescriptor* DescriptorBuilder::BuildFileImpl(
FileDescriptor* result = alloc.AllocateArray<FileDescriptor>(1);
file_ = result;
if (proto.has_edition()) {
const FeatureSetDefaults& defaults =
pool_->feature_set_defaults_spec_ == nullptr
? GetCppFeatureSetDefaults()
: *pool_->feature_set_defaults_spec_;
// TODO: Report error when the syntax is empty after all the protos
// have added the syntax statement.
if (proto.syntax().empty() || proto.syntax() == "proto2") {
file_->syntax_ = FileDescriptorLegacy::SYNTAX_PROTO2;
file_->edition_ = Edition::EDITION_PROTO2;
} else if (proto.syntax() == "proto3") {
file_->syntax_ = FileDescriptorLegacy::SYNTAX_PROTO3;
file_->edition_ = Edition::EDITION_PROTO3;
} else if (proto.syntax() == "editions") {
file_->syntax_ = FileDescriptorLegacy::SYNTAX_EDITIONS;
file_->edition_ = proto.edition();
} else {
file_->syntax_ = FileDescriptorLegacy::SYNTAX_UNKNOWN;
file_->edition_ = Edition::EDITION_UNKNOWN;
AddError(proto.name(), proto, DescriptorPool::ErrorCollector::OTHER, [&] {
return absl::StrCat("Unrecognized syntax: ", proto.syntax());
});
}
const FeatureSetDefaults& defaults =
pool_->feature_set_defaults_spec_ == nullptr
? GetCppFeatureSetDefaults()
: *pool_->feature_set_defaults_spec_;
absl::StatusOr<FeatureResolver> feature_resolver =
FeatureResolver::Create(proto.edition(), defaults);
if (!feature_resolver.ok()) {
AddError(
proto.name(), proto, DescriptorPool::ErrorCollector::EDITIONS,
[&] { return std::string(feature_resolver.status().message()); });
} else {
feature_resolver_.emplace(std::move(feature_resolver).value());
}
absl::StatusOr<FeatureResolver> feature_resolver =
FeatureResolver::Create(file_->edition_, defaults);
if (!feature_resolver.ok()) {
AddError(proto.name(), proto, DescriptorPool::ErrorCollector::EDITIONS,
[&] { return std::string(feature_resolver.status().message()); });
} else {
feature_resolver_.emplace(std::move(feature_resolver).value());
}
result->is_placeholder_ = false;
@ -5721,26 +5700,6 @@ FileDescriptor* DescriptorBuilder::BuildFileImpl(
"Missing field: FileDescriptorProto.name.");
}
// TODO: Report error when the syntax is empty after all the protos
// have added the syntax statement.
if (proto.syntax().empty() || proto.syntax() == "proto2") {
file_->syntax_ = FileDescriptorLegacy::SYNTAX_PROTO2;
} else if (proto.syntax() == "proto3") {
file_->syntax_ = FileDescriptorLegacy::SYNTAX_PROTO3;
} else if (proto.syntax() == "editions") {
file_->syntax_ = FileDescriptorLegacy::SYNTAX_EDITIONS;
} else {
file_->syntax_ = FileDescriptorLegacy::SYNTAX_UNKNOWN;
AddError(proto.name(), proto, DescriptorPool::ErrorCollector::OTHER, [&] {
return absl::StrCat("Unrecognized syntax: ", proto.syntax());
});
}
if (proto.has_edition()) {
file_->edition_ = proto.edition();
} else {
file_->edition_ = Edition::EDITION_UNKNOWN;
}
result->name_ = alloc.AllocateStrings(proto.name());
if (proto.has_package()) {
result->package_ = alloc.AllocateStrings(proto.package());
@ -5929,48 +5888,45 @@ FileDescriptor* DescriptorBuilder::BuildFileImpl(
SuggestFieldNumbers(result, proto);
}
// Interpret only the feature options first. This has to be done in two
// passes, since options defined in this file may have features attached
// to them.
// Interpret only the non-extension options first, including features and
// their extensions. This has to be done in two passes, since option
// extensions defined in this file may have features attached to them.
if (!had_errors_) {
OptionInterpreter option_interpreter(this);
for (std::vector<OptionsToInterpret>::iterator iter =
options_to_interpret_.begin();
iter != options_to_interpret_.end(); ++iter) {
option_interpreter.InterpretFeatures(&(*iter));
}
}
// Handle feature resolution. This must occur after option interpretation,
// but before validation.
internal::VisitDescriptors(
*result, proto, [&](const auto& descriptor, const auto& proto) {
using OptionsT =
typename std::remove_const<typename std::remove_pointer<
decltype(descriptor.options_)>::type>::type;
using DescriptorT = typename std::remove_const<
typename std::remove_reference<decltype(descriptor)>::type>::type;
ResolveFeatures(
proto, const_cast<DescriptorT*>(&descriptor),
const_cast<OptionsT*>( // NOLINT(google3-runtime-proto-const-cast)
descriptor.options_),
alloc);
});
option_interpreter.InterpretNonExtensionOptions(&(*iter));
}
// Handle feature resolution. This must occur after option interpretation,
// but before validation.
internal::VisitDescriptors(
*result, proto, [&](const auto& descriptor, const auto& proto) {
using OptionsT =
typename std::remove_const<typename std::remove_pointer<
decltype(descriptor.options_)>::type>::type;
using DescriptorT = typename std::remove_const<
typename std::remove_reference<decltype(descriptor)>::type>::type;
ResolveFeatures(
proto, const_cast<DescriptorT*>(&descriptor),
const_cast< // NOLINT(google3-runtime-proto-const-cast)
OptionsT*>(descriptor.options_),
alloc);
});
// Post-process cleanup for field features.
internal::VisitDescriptors(*result, [&](const FieldDescriptor& field) {
PostProcessFieldFeatures(const_cast<FieldDescriptor&>(field));
});
// Post-process cleanup for field features.
internal::VisitDescriptors(*result, [&](const FieldDescriptor& field) {
PostProcessFieldFeatures(const_cast<FieldDescriptor&>(field));
});
// Interpret any remaining uninterpreted options gathered into
// options_to_interpret_ during descriptor building. Cross-linking has made
// extension options known, so all interpretations should now succeed.
if (!had_errors_) {
OptionInterpreter option_interpreter(this);
// Interpret any remaining uninterpreted options gathered into
// options_to_interpret_ during descriptor building. Cross-linking has made
// extension options known, so all interpretations should now succeed.
for (std::vector<OptionsToInterpret>::iterator iter =
options_to_interpret_.begin();
iter != options_to_interpret_.end(); ++iter) {
option_interpreter.InterpretOptions(&(*iter));
option_interpreter.InterpretOptionExtensions(&(*iter));
}
options_to_interpret_.clear();
if (info != nullptr) {
@ -6369,7 +6325,7 @@ void DescriptorBuilder::BuildFieldOrExtension(const FieldDescriptorProto& proto,
result->label_ = static_cast<FieldDescriptor::Label>(
absl::implicit_cast<int>(proto.label()));
if (result->is_required()) {
if (result->label() == FieldDescriptor::LABEL_REQUIRED) {
// An extension cannot have a required field (b/13365836).
if (result->is_extension_) {
AddError(result->full_name(), proto,
@ -7930,7 +7886,10 @@ void DescriptorBuilder::ValidateFileFeatures(const FileDescriptor* file,
void DescriptorBuilder::ValidateFieldFeatures(
const FieldDescriptor* field, const FieldDescriptorProto& proto) {
// Rely on our legacy validation for proto2/proto3 files.
if (IsLegacyFeatureSet(field->features())) return;
if (FileDescriptorLegacy(field->file()).syntax() !=
FileDescriptorLegacy::SYNTAX_EDITIONS) {
return;
}
// Double check proto descriptors in editions. These would usually be caught
// by the parser, but may not be for dynamically built descriptors.
@ -8418,16 +8377,16 @@ DescriptorBuilder::OptionInterpreter::OptionInterpreter(
DescriptorBuilder::OptionInterpreter::~OptionInterpreter() {}
bool DescriptorBuilder::OptionInterpreter::InterpretOptions(
bool DescriptorBuilder::OptionInterpreter::InterpretOptionExtensions(
OptionsToInterpret* options_to_interpret) {
return InterpretOptionsImpl(options_to_interpret, /*features=*/false);
return InterpretOptionsImpl(options_to_interpret, /*skip_extensions=*/false);
}
bool DescriptorBuilder::OptionInterpreter::InterpretFeatures(
bool DescriptorBuilder::OptionInterpreter::InterpretNonExtensionOptions(
OptionsToInterpret* options_to_interpret) {
return InterpretOptionsImpl(options_to_interpret, /*features=*/true);
return InterpretOptionsImpl(options_to_interpret, /*skip_extensions=*/true);
}
bool DescriptorBuilder::OptionInterpreter::InterpretOptionsImpl(
OptionsToInterpret* options_to_interpret, bool features) {
OptionsToInterpret* options_to_interpret, bool skip_extensions) {
// Note that these may be in different pools, so we can't use the same
// descriptor and reflection objects on both.
Message* options = options_to_interpret->options;
@ -8463,7 +8422,8 @@ bool DescriptorBuilder::OptionInterpreter::InterpretOptionsImpl(
&original_options->GetReflection()->GetRepeatedMessage(
*original_options, original_uninterpreted_options_field, i));
if (!InterpretSingleOption(options, src_path,
options_to_interpret->element_path, features)) {
options_to_interpret->element_path,
skip_extensions)) {
// Error already added by InterpretSingleOption().
failed = true;
break;
@ -8513,20 +8473,29 @@ bool DescriptorBuilder::OptionInterpreter::InterpretOptionsImpl(
bool DescriptorBuilder::OptionInterpreter::InterpretSingleOption(
Message* options, const std::vector<int>& src_path,
const std::vector<int>& options_path, bool features) {
const std::vector<int>& options_path, bool skip_extensions) {
// First do some basic validation.
if (uninterpreted_option_->name_size() == 0) {
// This should never happen unless the parser has gone seriously awry or
// someone has manually created the uninterpreted option badly.
if (skip_extensions) {
// Come back to it later.
return true;
}
return AddNameError(
[]() -> std::string { return "Option must have a name."; });
}
if (uninterpreted_option_->name(0).name_part() == "uninterpreted_option") {
if (skip_extensions) {
// Come back to it later.
return true;
}
return AddNameError([]() -> std::string {
return "Option must not use reserved name \"uninterpreted_option\".";
});
}
if (features != (uninterpreted_option_->name(0).name_part() == "features")) {
if (skip_extensions == uninterpreted_option_->name(0).is_extension()) {
// Allow feature and option interpretation to occur in two phases. This is
// necessary because features *are* options and need to be interpreted
// before resolving them. However, options can also *have* features

@ -2705,10 +2705,6 @@ inline FieldDescriptor::Type FieldDescriptor::type() const {
return static_cast<Type>(type_);
}
inline bool FieldDescriptor::is_required() const {
return label() == LABEL_REQUIRED;
}
inline bool FieldDescriptor::is_optional() const {
return label() == LABEL_OPTIONAL;
}

@ -7229,7 +7229,7 @@ class FeaturesTest : public FeaturesBaseTest {
{GetExtensionReflection(pb::cpp), GetExtensionReflection(pb::test),
GetExtensionReflection(pb::TestMessage::test_message),
GetExtensionReflection(pb::TestMessage::Nested::test_nested)},
EDITION_2023, EDITION_99999_TEST_ONLY);
EDITION_PROTO2, EDITION_99999_TEST_ONLY);
ASSERT_OK(default_spec);
pool_.SetFeatureSetDefaults(std::move(default_spec).value());
}
@ -7312,7 +7312,8 @@ TEST_F(FeaturesTest, Proto2Features) {
const FieldDescriptor* field = message->field(0);
const FieldDescriptor* group = message->field(1);
EXPECT_THAT(file->options(), EqualsProto(""));
EXPECT_THAT(GetFeatures(file), EqualsProto(R"pb(
EXPECT_EQ(GetFeatures(file).GetExtension(pb::test).int_file_feature(), -2);
EXPECT_THAT(GetCoreFeatures(file), EqualsProto(R"pb(
field_presence: EXPLICIT
enum_type: CLOSED
repeated_field_encoding: EXPANDED
@ -7320,7 +7321,7 @@ TEST_F(FeaturesTest, Proto2Features) {
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
[pb.cpp] { legacy_closed_enum: true })pb"));
EXPECT_THAT(GetFeatures(field), EqualsProto(R"pb(
EXPECT_THAT(GetCoreFeatures(field), EqualsProto(R"pb(
field_presence: EXPLICIT
enum_type: CLOSED
repeated_field_encoding: EXPANDED
@ -7328,7 +7329,7 @@ TEST_F(FeaturesTest, Proto2Features) {
message_encoding: LENGTH_PREFIXED
json_format: LEGACY_BEST_EFFORT
[pb.cpp] { legacy_closed_enum: true })pb"));
EXPECT_THAT(GetFeatures(group), EqualsProto(R"pb(
EXPECT_THAT(GetCoreFeatures(group), EqualsProto(R"pb(
field_presence: EXPLICIT
enum_type: CLOSED
repeated_field_encoding: EXPANDED
@ -7385,7 +7386,8 @@ TEST_F(FeaturesTest, Proto3Features) {
const Descriptor* message = file->message_type(0);
const FieldDescriptor* field = message->field(0);
EXPECT_THAT(file->options(), EqualsProto(""));
EXPECT_THAT(GetFeatures(file), EqualsProto(R"pb(
EXPECT_EQ(GetFeatures(file).GetExtension(pb::test).int_file_feature(), -3);
EXPECT_THAT(GetCoreFeatures(file), EqualsProto(R"pb(
field_presence: IMPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
@ -7393,7 +7395,7 @@ TEST_F(FeaturesTest, Proto3Features) {
message_encoding: LENGTH_PREFIXED
json_format: ALLOW
[pb.cpp] { legacy_closed_enum: false })pb"));
EXPECT_THAT(GetFeatures(field), EqualsProto(R"pb(
EXPECT_THAT(GetCoreFeatures(field), EqualsProto(R"pb(
field_presence: IMPLICIT
enum_type: OPEN
repeated_field_encoding: PACKED
@ -7962,7 +7964,7 @@ TEST_F(FeaturesTest, InvalidEdition) {
name: "foo.proto" syntax: "editions" edition: EDITION_1_TEST_ONLY
)pb",
"foo.proto: foo.proto: EDITIONS: Edition 1_TEST_ONLY is earlier than the "
"minimum supported edition 2023\n");
"minimum supported edition PROTO2\n");
}
TEST_F(FeaturesTest, FileFeatures) {

@ -224,7 +224,7 @@ static_assert(PROTOBUF_ABSL_MIN(20230125, 3),
#ifdef PROTOBUF_MINIMUM_EDITION
#error PROTOBUF_MINIMUM_EDITION was previously defined
#endif
#define PROTOBUF_MINIMUM_EDITION EDITION_2023
#define PROTOBUF_MINIMUM_EDITION EDITION_PROTO2
#ifdef PROTOBUF_MAXIMUM_EDITION
#error PROTOBUF_MAXIMUM_EDITION was previously defined

@ -30,7 +30,8 @@ message TestFeatures {
optional int32 int_file_feature = 1 [
retention = RETENTION_RUNTIME,
targets = TARGET_TYPE_FILE,
edition_defaults = { edition: EDITION_PROTO2, value: "0" },
edition_defaults = { edition: EDITION_PROTO2, value: "-2" },
edition_defaults = { edition: EDITION_PROTO3, value: "-3" },
edition_defaults = { edition: EDITION_2023, value: "1" },
edition_defaults = { edition: EDITION_99997_TEST_ONLY, value: "2" },
edition_defaults = { edition: EDITION_99998_TEST_ONLY, value: "3" }

Loading…
Cancel
Save