Editions: Refactor feature resolution to use an intermediate message.
This places all of the tricky reflection logic of feature resolution into a single location that outputs a portable protobuf message. With this message, feature resolution becomes drastically simpler and easy to reimplement in other languages.
Follow-up changes will provide:
- An API to specify generator features from the CodeGenerator class for C++ plugins
- A CLI for building these default mappings
- Bazel/CMake rules to help embed these mappings in target languages (i.e. for runtimes and non-C++ plugins)
PiperOrigin-RevId: 559615720
diff --git a/src/google/protobuf/BUILD.bazel b/src/google/protobuf/BUILD.bazel
index a89ef2e..4c2db3f 100644
--- a/src/google/protobuf/BUILD.bazel
+++ b/src/google/protobuf/BUILD.bazel
@@ -1109,6 +1109,9 @@
"//src/google/protobuf/compiler:importer",
"//src/google/protobuf/stubs",
"//src/google/protobuf/testing",
+ "@com_google_absl//absl/log:absl_check",
+ "@com_google_absl//absl/log:absl_log",
+ "@com_google_absl//absl/log:die_if_null",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
diff --git a/src/google/protobuf/compiler/code_generator_unittest.cc b/src/google/protobuf/compiler/code_generator_unittest.cc
index e0277f6..eea9d48 100644
--- a/src/google/protobuf/compiler/code_generator_unittest.cc
+++ b/src/google/protobuf/compiler/code_generator_unittest.cc
@@ -177,7 +177,8 @@
EXPECT_EQ(features.field_presence(), FeatureSet::EXPLICIT);
EXPECT_EQ(features.enum_type(), FeatureSet::CLOSED);
- EXPECT_TRUE(ext.has_int_message_feature());
+ // TODO(b/296638633) Flip this once generators can specify their feature sets.
+ EXPECT_FALSE(ext.has_int_message_feature());
EXPECT_EQ(ext.int_file_feature(), 8);
EXPECT_EQ(ext.string_source_feature(), "file");
}
diff --git a/src/google/protobuf/compiler/command_line_interface_unittest.cc b/src/google/protobuf/compiler/command_line_interface_unittest.cc
index 1d6d288..c133916 100644
--- a/src/google/protobuf/compiler/command_line_interface_unittest.cc
+++ b/src/google/protobuf/compiler/command_line_interface_unittest.cc
@@ -1353,7 +1353,7 @@
TEST_F(CommandLineInterfaceTest, EditionsAreNotAllowed) {
CreateTempFile("foo.proto",
- "edition = \"very-cool\";\n"
+ "edition = \"2023\";\n"
"message FooRequest {}\n");
Run("protocol_compiler --proto_path=$tmpdir --test_out=$tmpdir foo.proto");
@@ -1365,7 +1365,7 @@
TEST_F(CommandLineInterfaceTest, EditionsFlagExplicitTrue) {
CreateTempFile("foo.proto",
- "edition = \"very-cool\";\n"
+ "edition = \"2023\";\n"
"message FooRequest {}\n");
Run("protocol_compiler --proto_path=$tmpdir --test_out=$tmpdir "
@@ -1477,14 +1477,16 @@
import "features.proto";
message Foo {
int32 bar = 1;
- int32 baz = 2 [features.(pb.test).int_feature = 5];
+ int32 baz = 2 [features.(pb.test).repeated_feature = 5];
})schema");
Run("protocol_compiler --proto_path=$tmpdir --test_out=$tmpdir "
"--experimental_editions foo.proto");
- ExpectErrorSubstring(
- "Feature field pb.TestFeatures.repeated_feature is an unsupported "
- "repeated field");
+ // TODO(b/296638633) Flip this once generators can specify their feature sets.
+ ExpectNoErrors();
+ // ExpectErrorSubstring(
+ // "Feature field pb.TestFeatures.repeated_feature is an unsupported "
+ // "repeated field");
}
TEST_F(CommandLineInterfaceTest, Plugin_LegacyFeatures) {
diff --git a/src/google/protobuf/descriptor.cc b/src/google/protobuf/descriptor.cc
index 54e6b7f..100d35e 100644
--- a/src/google/protobuf/descriptor.cc
+++ b/src/google/protobuf/descriptor.cc
@@ -44,6 +44,7 @@
#include <sstream>
#include <string>
#include <type_traits>
+#include <utility>
#include <vector>
#include "google/protobuf/stubs/common.h"
@@ -57,6 +58,7 @@
#include "absl/hash/hash.h"
#include "absl/log/absl_check.h"
#include "absl/log/absl_log.h"
+#include "absl/memory/memory.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
@@ -1111,6 +1113,18 @@
allowed_proto3_extendees->end();
}
+const FeatureSetDefaults& GetCppFeatureSetDefaults() {
+ static const FeatureSetDefaults* default_spec = [] {
+ auto default_spec = FeatureResolver::CompileDefaults(
+ FeatureSet::descriptor(),
+ // TODO(b/297261063) Move this range to a central location.
+ {pb::CppFeatures::descriptor()->file()->extension(0)}, "2023", "2023");
+ ABSL_CHECK(default_spec.ok()) << default_spec.status();
+ return new FeatureSetDefaults(std::move(default_spec).value());
+ }();
+ return *default_spec;
+}
+
// Create global defaults for proto2/proto3 compatibility.
FeatureSet* CreateProto2DefaultFeatures() {
FeatureSet* features = new FeatureSet();
@@ -4577,14 +4591,7 @@
const FileDescriptor* DescriptorPool::BuildFile(
const FileDescriptorProto& proto) {
- ABSL_CHECK(fallback_database_ == nullptr)
- << "Cannot call BuildFile on a DescriptorPool that uses a "
- "DescriptorDatabase. You must instead find a way to get your file "
- "into the underlying database.";
- ABSL_CHECK(mutex_ == nullptr); // Implied by the above ABSL_CHECK.
- tables_->known_bad_symbols_.clear();
- tables_->known_bad_files_.clear();
- return DescriptorBuilder::New(this, tables_.get(), nullptr)->BuildFile(proto);
+ return BuildFileCollectingErrors(proto, nullptr);
}
const FileDescriptor* DescriptorPool::BuildFileCollectingErrors(
@@ -4596,6 +4603,7 @@
ABSL_CHECK(mutex_ == nullptr); // Implied by the above ABSL_CHECK.
tables_->known_bad_symbols_.clear();
tables_->known_bad_files_.clear();
+ build_started_ = true;
return DescriptorBuilder::New(this, tables_.get(), error_collector)
->BuildFile(proto);
}
@@ -4603,6 +4611,7 @@
const FileDescriptor* DescriptorPool::BuildFileFromDatabase(
const FileDescriptorProto& proto) const {
mutex_->AssertHeld();
+ build_started_ = true;
if (tables_->known_bad_files_.contains(proto.name())) {
return nullptr;
}
@@ -4615,6 +4624,14 @@
return result;
}
+void DescriptorPool::SetFeatureSetDefaults(FeatureSetDefaults spec) {
+ ABSL_CHECK(!build_started_)
+ << "Feature set defaults can't be changed once the pool has started "
+ "building.";
+ feature_set_defaults_spec_ =
+ absl::make_unique<FeatureSetDefaults>(std::move(spec));
+}
+
DescriptorBuilder::DescriptorBuilder(
const DescriptorPool* pool, DescriptorPool::Tables* tables,
DescriptorPool::ErrorCollector* error_collector)
@@ -5699,8 +5716,13 @@
}
ABSL_CHECK(descriptor);
+ const FeatureSetDefaults& defaults =
+ pool_->feature_set_defaults_spec_ == nullptr
+ ? GetCppFeatureSetDefaults()
+ : *pool_->feature_set_defaults_spec_;
+
absl::StatusOr<FeatureResolver> feature_resolver =
- FeatureResolver::Create(proto.edition(), descriptor);
+ FeatureResolver::Create(proto.edition(), defaults);
if (!feature_resolver.ok()) {
AddError(
proto.name(), proto, DescriptorPool::ErrorCollector::EDITIONS,
@@ -5816,16 +5838,6 @@
return nullptr;
}
- // Look for feature extensions in regular imports.
- if (feature_resolver_.has_value() && dependency != nullptr) {
- absl::Status status = feature_resolver_->RegisterExtensions(*dependency);
- if (!status.ok()) {
- AddError(dependency->name(), proto,
- DescriptorPool::ErrorCollector::EDITIONS,
- [&] { return std::string(status.message()); });
- }
- }
-
if (dependency == nullptr) {
if (!pool_->lazily_build_dependencies_) {
if (pool_->allow_unknown_ ||
diff --git a/src/google/protobuf/descriptor.h b/src/google/protobuf/descriptor.h
index 15e0057..2c6a779 100644
--- a/src/google/protobuf/descriptor.h
+++ b/src/google/protobuf/descriptor.h
@@ -122,6 +122,7 @@
class FileOptions;
class UninterpretedOption;
class FeatureSet;
+class FeatureSetDefaults;
class SourceCodeInfo;
// Defined in message_lite.h
@@ -2289,6 +2290,13 @@
// DescriptorPool will report a import not found error.
void EnforceWeakDependencies(bool enforce) { enforce_weak_ = enforce; }
+ // Sets the default feature mappings used during the build. If this function
+ // isn't called, the C++ feature set defaults are used. If this function is
+ // called, these defaults will be used instead.
+ // FeatureSetDefaults includes a minimum/maximum supported edition, which will
+ // be enforced while building proto files.
+ void SetFeatureSetDefaults(FeatureSetDefaults spec);
+
// Toggles enforcement of extension declarations.
// This enforcement is disabled by default because it requires full
// descriptors with source-retention options, which are generally not
@@ -2469,11 +2477,16 @@
bool enforce_extension_declarations_;
bool disallow_enforce_utf8_;
bool deprecated_legacy_json_field_conflicts_;
+ mutable bool build_started_ = false;
// Set of files to track for unused imports. The bool value when true means
// unused imports are treated as errors (and as warnings when false).
absl::flat_hash_map<std::string, bool> unused_import_track_files_;
+ // Specification of defaults to use for feature resolution. This defaults to
+ // just the global and C++ features, but can be overridden for other runtimes.
+ std::unique_ptr<FeatureSetDefaults> feature_set_defaults_spec_;
+
// Returns true if the field extends an option message of descriptor.proto.
bool IsExtendingDescriptor(const FieldDescriptor& field) const;
diff --git a/src/google/protobuf/descriptor_unittest.cc b/src/google/protobuf/descriptor_unittest.cc
index 10a69cb..43ef3fa 100644
--- a/src/google/protobuf/descriptor_unittest.cc
+++ b/src/google/protobuf/descriptor_unittest.cc
@@ -41,6 +41,7 @@
#include <memory>
#include <string>
#include <tuple>
+#include <utility>
#include <vector>
#include "google/protobuf/stubs/common.h"
@@ -67,6 +68,7 @@
#include "google/protobuf/descriptor_database.h"
#include "google/protobuf/descriptor_legacy.h"
#include "google/protobuf/dynamic_message.h"
+#include "google/protobuf/feature_resolver.h"
#include "google/protobuf/io/tokenizer.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "google/protobuf/test_textproto.h"
@@ -514,17 +516,17 @@
}
{
proto.set_syntax("editions");
- proto.set_edition("very-cool");
+ proto.set_edition("2023");
DescriptorPool pool;
const FileDescriptor* file = pool.BuildFile(proto);
ASSERT_TRUE(file != nullptr);
EXPECT_EQ(FileDescriptorLegacy::Syntax::SYNTAX_EDITIONS,
FileDescriptorLegacy(file).syntax());
- EXPECT_EQ("very-cool", file->edition());
+ EXPECT_EQ("2023", file->edition());
FileDescriptorProto other;
file->CopyTo(&other);
EXPECT_EQ("editions", other.syntax());
- EXPECT_EQ("very-cool", other.edition());
+ EXPECT_EQ("2023", other.edition());
}
}
@@ -552,7 +554,7 @@
EXPECT_EQ(&other.options().features(), &FeatureSet::default_instance());
{
proto.set_syntax("editions");
- proto.set_edition("very-cool");
+ proto.set_edition("2023");
DescriptorPool pool;
const FileDescriptor* file = pool.BuildFile(proto);
@@ -563,7 +565,7 @@
EXPECT_EQ(other.name(), "foo.proto");
EXPECT_EQ(other.package(), "foo.bar.baz");
EXPECT_EQ(other.syntax(), "editions");
- EXPECT_EQ(other.edition(), "very-cool");
+ EXPECT_EQ(other.edition(), "2023");
EXPECT_EQ(other.options().java_package(), "foo.bar.baz");
EXPECT_TRUE(other.message_type().empty());
EXPECT_EQ(&other.options().features(), &FeatureSet::default_instance());
@@ -7235,7 +7237,24 @@
"foo.proto: Foo.foo: EXTENDEE: \"Baz\" is not defined.\n");
}
-using FeaturesTest = ValidationErrorTest;
+using FeaturesBaseTest = ValidationErrorTest;
+
+class FeaturesTest : public FeaturesBaseTest {
+ protected:
+ void SetUp() override {
+ ValidationErrorTest::SetUp();
+
+ auto default_spec = FeatureResolver::CompileDefaults(
+ FeatureSet::descriptor(),
+ {pb::CppFeatures::descriptor()->file()->extension(0),
+ pb::TestFeatures::descriptor()->file()->extension(0),
+ pb::TestMessage::descriptor()->extension(0),
+ pb::TestMessage::Nested::descriptor()->extension(0)},
+ "2023", "2025");
+ ASSERT_OK(default_spec);
+ pool_.SetFeatureSetDefaults(std::move(default_spec).value());
+ }
+};
template <typename T>
const FeatureSet& GetFeatures(const T* descriptor) {
@@ -7544,12 +7563,36 @@
[pb.cpp] { legacy_closed_enum: false utf8_validation: VERIFY_PARSE }
)pb"));
- // Since pb::test is linked in, it should end up with defaults in our
- // FeatureSet.
+ // Since pb::test is registered in the pool, it should end up with defaults in
+ // our FeatureSet.
EXPECT_TRUE(GetFeatures(file).HasExtension(pb::test));
EXPECT_EQ(GetFeatures(file).GetExtension(pb::test).int_file_feature(), 1);
}
+TEST_F(FeaturesBaseTest, DefaultEdition2023Defaults) {
+ BuildDescriptorMessagesInTestPool();
+ BuildFileInTestPool(pb::TestFeatures::descriptor()->file());
+ const FileDescriptor* file = BuildFile(R"pb(
+ name: "foo.proto"
+ syntax: "editions"
+ edition: "2023"
+ dependency: "google/protobuf/unittest_features.proto"
+ )pb");
+ ASSERT_NE(file, nullptr);
+
+ EXPECT_THAT(file->options(), EqualsProto(""));
+ EXPECT_THAT(
+ GetFeatures(file), EqualsProto(R"pb(
+ field_presence: EXPLICIT
+ enum_type: OPEN
+ repeated_field_encoding: PACKED
+ message_encoding: LENGTH_PREFIXED
+ json_format: ALLOW
+ [pb.cpp] { legacy_closed_enum: false utf8_validation: VERIFY_PARSE }
+ )pb"));
+ EXPECT_FALSE(GetFeatures(file).HasExtension(pb::test));
+}
+
TEST_F(FeaturesTest, ClearsOptions) {
BuildDescriptorMessagesInTestPool();
const FileDescriptor* file = BuildFile(R"pb(
@@ -7842,9 +7885,8 @@
R"pb(
name: "foo.proto" syntax: "editions" edition: "2022"
)pb",
- "foo.proto: foo.proto: EDITIONS: No valid default found for edition 2022 "
- "in "
- "feature field google.protobuf.FeatureSet.field_presence\n");
+ "foo.proto: foo.proto: EDITIONS: Edition 2022 is earlier than the "
+ "minimum supported edition 2023\n");
}
TEST_F(FeaturesTest, FileFeatures) {
@@ -9110,48 +9152,6 @@
"editions.\n");
}
-TEST_F(FeaturesTest, InvalidExtensionNonMessage) {
- BuildDescriptorMessagesInTestPool();
- ASSERT_NE(BuildFile(R"pb(
- name: "unittest_invalid_features.proto"
- syntax: "proto2"
- package: "pb"
- dependency: "google/protobuf/descriptor.proto"
- message_type {
- name: "TestInvalid"
- extension {
- name: "scalar_extension"
- number: 9996
- label: LABEL_OPTIONAL
- type: TYPE_STRING
- extendee: ".google.protobuf.FeatureSet"
- }
- }
- )pb"),
- nullptr);
- BuildFileWithErrors(
- R"pb(
- name: "foo.proto"
- syntax: "editions"
- edition: "2023"
- dependency: "unittest_invalid_features.proto"
- options {
- uninterpreted_option {
- name { name_part: "features" is_extension: false }
- name {
- name_part: "pb.TestInvalid.scalar_extension"
- is_extension: true
- }
- identifier_value: "hello"
- }
- }
- )pb",
- "foo.proto: unittest_invalid_features.proto: EDITIONS: FeatureSet "
- "extension pb.TestInvalid.scalar_extension is not of message type. "
- "Feature extensions should always use messages to allow for "
- "evolution.\n");
-}
-
TEST_F(FeaturesTest, InvalidFieldImplicitDefault) {
BuildDescriptorMessagesInTestPool();
BuildFileWithErrors(
diff --git a/src/google/protobuf/feature_resolver.cc b/src/google/protobuf/feature_resolver.cc
index 46b3beb..aa4858b 100644
--- a/src/google/protobuf/feature_resolver.cc
+++ b/src/google/protobuf/feature_resolver.cc
@@ -31,6 +31,7 @@
#include "google/protobuf/feature_resolver.h"
#include <algorithm>
+#include <cstddef>
#include <iterator>
#include <memory>
#include <string>
@@ -38,13 +39,17 @@
#include <vector>
#include "absl/algorithm/container.h"
+#include "absl/container/btree_set.h"
+#include "absl/container/flat_hash_set.h"
#include "absl/log/absl_check.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
#include "google/protobuf/cpp_features.pb.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
@@ -71,24 +76,25 @@
return absl::FailedPreconditionError(absl::StrCat(args...));
}
-bool EditionsLessThan(absl::string_view a, absl::string_view b) {
- std::vector<absl::string_view> as = absl::StrSplit(a, '.');
- std::vector<absl::string_view> bs = absl::StrSplit(b, '.');
- size_t min_size = std::min(as.size(), bs.size());
- for (size_t i = 0; i < min_size; ++i) {
- if (as[i].size() != bs[i].size()) {
- return as[i].size() < bs[i].size();
- } else if (as[i] != bs[i]) {
- return as[i] < bs[i];
+struct EditionsLessThan {
+ bool operator()(absl::string_view a, absl::string_view b) const {
+ std::vector<absl::string_view> as = absl::StrSplit(a, '.');
+ std::vector<absl::string_view> bs = absl::StrSplit(b, '.');
+ size_t min_size = std::min(as.size(), bs.size());
+ for (size_t i = 0; i < min_size; ++i) {
+ if (as[i].size() != bs[i].size()) {
+ return as[i].size() < bs[i].size();
+ } else if (as[i] != bs[i]) {
+ return as[i] < bs[i];
+ }
}
+ // Both strings are equal up until an extra element, which makes that string
+ // more recent.
+ return as.size() < bs.size();
}
- // Both strings are equal up until an extra element, which makes that string
- // more recent.
- return as.size() < bs.size();
-}
+};
-absl::Status ValidateDescriptor(absl::string_view edition,
- const Descriptor& descriptor) {
+absl::Status ValidateDescriptor(const Descriptor& descriptor) {
if (descriptor.oneof_decl_count() > 0) {
return Error("Type ", descriptor.full_name(),
" contains unsupported oneof feature fields.");
@@ -113,35 +119,57 @@
return absl::OkStatus();
}
-absl::Status ValidateExtension(const FieldDescriptor& extension) {
- if (extension.message_type() == nullptr) {
- return Error("FeatureSet extension ", extension.full_name(),
+absl::Status ValidateExtension(const Descriptor& feature_set,
+ const FieldDescriptor* extension) {
+ if (extension == nullptr) {
+ return Error("Unknown extension of ", feature_set.full_name(), ".");
+ }
+
+ if (extension->containing_type() != &feature_set) {
+ return Error("Extension ", extension->full_name(),
+ " is not an extension of ", feature_set.full_name(), ".");
+ }
+
+ if (extension->message_type() == nullptr) {
+ return Error("FeatureSet extension ", extension->full_name(),
" is not of message type. Feature extensions should "
"always use messages to allow for evolution.");
}
- if (extension.is_repeated()) {
+ if (extension->is_repeated()) {
return Error(
"Only singular features extensions are supported. Found "
"repeated extension ",
- extension.full_name());
+ extension->full_name());
}
- if (extension.message_type()->extension_count() > 0 ||
- extension.message_type()->extension_range_count() > 0) {
+ if (extension->message_type()->extension_count() > 0 ||
+ extension->message_type()->extension_range_count() > 0) {
return Error("Nested extensions in feature extension ",
- extension.full_name(), " are not supported.");
+ extension->full_name(), " are not supported.");
}
return absl::OkStatus();
}
+void CollectEditions(const Descriptor& descriptor,
+ absl::string_view minimum_edition,
+ absl::string_view maximum_edition,
+ absl::btree_set<std::string, EditionsLessThan>& editions) {
+ for (int i = 0; i < descriptor.field_count(); ++i) {
+ for (const auto& def : descriptor.field(i)->options().edition_defaults()) {
+ if (EditionsLessThan()(maximum_edition, def.edition())) continue;
+ editions.insert(def.edition());
+ }
+ }
+}
+
absl::Status FillDefaults(absl::string_view edition, Message& msg) {
const Descriptor& descriptor = *msg.GetDescriptor();
auto comparator = [](const FieldOptions::EditionDefault& a,
const FieldOptions::EditionDefault& b) {
- return EditionsLessThan(a.edition(), b.edition());
+ return EditionsLessThan()(a.edition(), b.edition());
};
FieldOptions::EditionDefault edition_lookup;
edition_lookup.set_edition(edition);
@@ -206,124 +234,100 @@
return absl::OkStatus();
}
-// Forces calculation of the defaults for any features from the generated pool
-// that may be missed if the proto file doesn't import them, giving the final
-// resolved FeatureSet object maximal coverage.
-absl::StatusOr<FeatureSet> FillGeneratedDefaults(absl::string_view edition,
- const DescriptorPool* pool) {
- const Descriptor* desc =
- pool->FindMessageTypeByName(FeatureSet::descriptor()->full_name());
- // If there's no FeatureSet, there's no extensions.
- if (desc == nullptr) return FeatureSet();
-
- DynamicMessageFactory message_factory;
- auto defaults = absl::WrapUnique(message_factory.GetPrototype(desc)->New());
- std::vector<const FieldDescriptor*> extensions;
- pool->FindAllExtensions(desc, &extensions);
- for (const auto* extension : extensions) {
- RETURN_IF_ERROR(ValidateExtension(*extension));
- Message* msg =
- defaults->GetReflection()->MutableMessage(defaults.get(), extension);
- ABSL_CHECK(msg != nullptr);
- RETURN_IF_ERROR(FillDefaults(edition, *msg));
- }
-
- // Our defaults are reflectively built in a custom pool, while the
- // returned object here is in the generated pool. We parse/serialize to
- // convert from the potentially skewed FeatureSet descriptors.
- FeatureSet features;
- ABSL_CHECK(features.MergeFromString(defaults->SerializeAsString()));
- return features;
-}
-
} // namespace
-absl::StatusOr<FeatureResolver> FeatureResolver::Create(
- absl::string_view edition, const Descriptor* descriptor,
- const DescriptorPool* generated_pool) {
- if (descriptor == nullptr) {
+absl::StatusOr<FeatureSetDefaults> FeatureResolver::CompileDefaults(
+ const Descriptor* feature_set,
+ absl::Span<const FieldDescriptor* const> extensions,
+ absl::string_view minimum_edition, absl::string_view maximum_edition) {
+ // Find and validate the FeatureSet in the pool.
+ if (feature_set == nullptr) {
return Error(
"Unable to find definition of google.protobuf.FeatureSet in descriptor pool.");
}
+ RETURN_IF_ERROR(ValidateDescriptor(*feature_set));
- RETURN_IF_ERROR(ValidateDescriptor(edition, *descriptor));
+ // Collect and validate all the FeatureSet extensions.
+ for (const auto* extension : extensions) {
+ RETURN_IF_ERROR(ValidateExtension(*feature_set, extension));
+ RETURN_IF_ERROR(ValidateDescriptor(*extension->message_type()));
+ }
+ // Collect all the editions with unique defaults.
+ absl::btree_set<std::string, EditionsLessThan> editions;
+ CollectEditions(*feature_set, minimum_edition, maximum_edition, editions);
+ for (const auto* extension : extensions) {
+ CollectEditions(*extension->message_type(), minimum_edition,
+ maximum_edition, editions);
+ }
+
+ // Fill the default spec.
+ FeatureSetDefaults defaults;
+ defaults.set_minimum_edition(minimum_edition);
+ defaults.set_maximum_edition(maximum_edition);
auto message_factory = absl::make_unique<DynamicMessageFactory>();
- auto defaults =
- absl::WrapUnique(message_factory->GetPrototype(descriptor)->New());
-
- RETURN_IF_ERROR(FillDefaults(edition, *defaults));
-
- FeatureSet generated_defaults;
- if (descriptor->file()->pool() == generated_pool) {
- // If we're building the generated pool, the best we can do is load the C++
- // language features, which should always be present for code using the C++
- // runtime.
- RETURN_IF_ERROR(
- FillDefaults(edition, *generated_defaults.MutableExtension(pb::cpp)));
- } else {
- absl::StatusOr<FeatureSet> defaults =
- FillGeneratedDefaults(edition, generated_pool);
- RETURN_IF_ERROR(defaults.status());
- generated_defaults = std::move(defaults).value();
+ for (const auto& edition : editions) {
+ auto defaults_dynamic =
+ absl::WrapUnique(message_factory->GetPrototype(feature_set)->New());
+ RETURN_IF_ERROR(FillDefaults(edition, *defaults_dynamic));
+ for (const auto* extension : extensions) {
+ RETURN_IF_ERROR(FillDefaults(
+ edition, *defaults_dynamic->GetReflection()->MutableMessage(
+ defaults_dynamic.get(), extension)));
+ }
+ auto* edition_defaults = defaults.mutable_defaults()->Add();
+ edition_defaults->set_edition(edition);
+ edition_defaults->mutable_features()->MergeFromString(
+ defaults_dynamic->SerializeAsString());
}
-
- return FeatureResolver(edition, *descriptor, std::move(message_factory),
- std::move(defaults), std::move(generated_defaults));
+ return defaults;
}
-absl::Status FeatureResolver::RegisterExtension(
- const FieldDescriptor& extension) {
- if (!extension.is_extension() ||
- extension.containing_type() != &descriptor_ ||
- extensions_.contains(&extension)) {
- // These are valid but irrelevant extensions, just return ok.
- return absl::OkStatus();
+absl::StatusOr<FeatureResolver> FeatureResolver::Create(
+ absl::string_view edition, const FeatureSetDefaults& compiled_defaults) {
+ if (EditionsLessThan()(edition, compiled_defaults.minimum_edition())) {
+ return Error("Edition ", edition,
+ " is earlier than the minimum supported edition ",
+ compiled_defaults.minimum_edition());
+ }
+ if (EditionsLessThan()(compiled_defaults.maximum_edition(), edition)) {
+ return Error("Edition ", edition,
+ " is later than the maximum supported edition ",
+ compiled_defaults.maximum_edition());
}
- ABSL_CHECK(descriptor_.IsExtensionNumber(extension.number()));
-
- RETURN_IF_ERROR(ValidateExtension(extension));
-
- RETURN_IF_ERROR(ValidateDescriptor(edition_, *extension.message_type()));
-
- Message* msg =
- defaults_->GetReflection()->MutableMessage(defaults_.get(), &extension);
- ABSL_CHECK(msg != nullptr);
- RETURN_IF_ERROR(FillDefaults(edition_, *msg));
-
- extensions_.insert(&extension);
- return absl::OkStatus();
-}
-
-absl::Status FeatureResolver::RegisterExtensions(const FileDescriptor& file) {
- for (int i = 0; i < file.extension_count(); ++i) {
- RETURN_IF_ERROR(RegisterExtension(*file.extension(i)));
+ // Validate compiled defaults.
+ absl::string_view prev_edition;
+ for (const auto& edition_default : compiled_defaults.defaults()) {
+ if (!prev_edition.empty()) {
+ if (!EditionsLessThan()(prev_edition, edition_default.edition())) {
+ return Error(
+ "Feature set defaults are not strictly increasing. Edition ",
+ prev_edition, " is greater than or equal to edition ",
+ edition_default.edition(), ".");
+ }
+ }
+ prev_edition = edition_default.edition();
}
- for (int i = 0; i < file.message_type_count(); ++i) {
- RETURN_IF_ERROR(RegisterExtensions(*file.message_type(i)));
- }
- return absl::OkStatus();
-}
-absl::Status FeatureResolver::RegisterExtensions(const Descriptor& message) {
- for (int i = 0; i < message.extension_count(); ++i) {
- RETURN_IF_ERROR(RegisterExtension(*message.extension(i)));
+ // Select the matching edition defaults.
+ auto comparator = [](const auto& a, const auto& b) {
+ return EditionsLessThan()(a.edition(), b.edition());
+ };
+ FeatureSetDefaults::FeatureSetEditionDefault search;
+ search.set_edition(edition);
+ auto first_nonmatch =
+ absl::c_upper_bound(compiled_defaults.defaults(), search, comparator);
+ if (first_nonmatch == compiled_defaults.defaults().begin()) {
+ return Error("No valid default found for edition ", edition);
}
- for (int i = 0; i < message.nested_type_count(); ++i) {
- RETURN_IF_ERROR(RegisterExtensions(*message.nested_type(i)));
- }
- return absl::OkStatus();
+
+ return FeatureResolver(std::prev(first_nonmatch)->features());
}
absl::StatusOr<FeatureSet> FeatureResolver::MergeFeatures(
const FeatureSet& merged_parent, const FeatureSet& unmerged_child) const {
- FeatureSet merged = generated_defaults_;
-
- // Our defaults are a dynamic message located in the build pool, while the
- // returned object here is in the generated pool. We parse/serialize to
- // convert from the potentially skewed FeatureSet descriptors.
- ABSL_CHECK(merged.MergeFromString(defaults_->SerializeAsString()));
+ FeatureSet merged = defaults_;
merged.MergeFrom(merged_parent);
merged.MergeFrom(unmerged_child);
diff --git a/src/google/protobuf/feature_resolver.h b/src/google/protobuf/feature_resolver.h
index 0d27ee1..6b4ca27 100644
--- a/src/google/protobuf/feature_resolver.h
+++ b/src/google/protobuf/feature_resolver.h
@@ -39,6 +39,7 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/dynamic_message.h"
@@ -56,48 +57,32 @@
FeatureResolver(FeatureResolver&&) = default;
FeatureResolver& operator=(FeatureResolver&&) = delete;
- // Creates a new FeatureResolver at a specific edition. This validates the
- // built-in features for the given edition, and calculates the default feature
- // set.
- static absl::StatusOr<FeatureResolver> Create(
- absl::string_view edition, const Descriptor* descriptor,
- const DescriptorPool* generated_pool =
- DescriptorPool::internal_generated_pool());
+ // Compiles a set of FeatureSet extensions into a mapping of edition to unique
+ // defaults. This is the most complicated part of feature resolution, and by
+ // abstracting this out into an intermediate message, we can make feature
+ // resolution significantly more portable.
+ static absl::StatusOr<FeatureSetDefaults> CompileDefaults(
+ const Descriptor* feature_set,
+ absl::Span<const FieldDescriptor* const> extensions,
+ absl::string_view minimum_edition, absl::string_view maximum_edition);
- // Registers a potential extension of the FeatureSet proto. Any visible
- // extensions will be used during merging. Returns an error if the extension
- // is a feature extension but fails validation.
- absl::Status RegisterExtensions(const FileDescriptor& file);
+ // Creates a new FeatureResolver at a specific edition. This calculates the
+ // default feature set for that edition, using the output of CompileDefaults.
+ static absl::StatusOr<FeatureResolver> Create(
+ absl::string_view edition, const FeatureSetDefaults& defaults);
// Creates a new feature set using inheritance and default behavior. This is
// designed to be called recursively, and the parent feature set is expected
- // to be a fully merged one.
- // The returned FeatureSet will be fully resolved for any extensions that were
- // explicitly registered (in the custom pool) or linked into this binary (in
- // the generated pool).
+ // to be a fully merged one. The returned FeatureSet will be fully resolved
+ // for any extensions that were used to construct the defaults.
absl::StatusOr<FeatureSet> MergeFeatures(
const FeatureSet& merged_parent, const FeatureSet& unmerged_child) const;
private:
- FeatureResolver(absl::string_view edition, const Descriptor& descriptor,
- std::unique_ptr<DynamicMessageFactory> message_factory,
- std::unique_ptr<Message> defaults,
- FeatureSet generated_defaults)
- : edition_(edition),
- descriptor_(descriptor),
- message_factory_(std::move(message_factory)),
- defaults_(std::move(defaults)),
- generated_defaults_(std::move(generated_defaults)) {}
+ explicit FeatureResolver(FeatureSet defaults)
+ : defaults_(std::move(defaults)) {}
- absl::Status RegisterExtensions(const Descriptor& message);
- absl::Status RegisterExtension(const FieldDescriptor& extension);
-
- std::string edition_;
- const Descriptor& descriptor_;
- absl::flat_hash_set<const FieldDescriptor*> extensions_;
- std::unique_ptr<DynamicMessageFactory> message_factory_;
- std::unique_ptr<Message> defaults_;
- FeatureSet generated_defaults_;
+ FeatureSet defaults_;
};
} // namespace protobuf
diff --git a/src/google/protobuf/feature_resolver_test.cc b/src/google/protobuf/feature_resolver_test.cc
index d42c491..aa49bfb 100644
--- a/src/google/protobuf/feature_resolver_test.cc
+++ b/src/google/protobuf/feature_resolver_test.cc
@@ -30,10 +30,14 @@
#include "google/protobuf/feature_resolver.h"
+#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include "absl/log/absl_check.h"
+#include "absl/log/absl_log.h"
+#include "absl/log/die_if_null.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
@@ -43,6 +47,7 @@
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.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/text_format.h"
#include "google/protobuf/unittest_custom_options.pb.h"
@@ -61,7 +66,6 @@
using ::testing::HasSubstr;
// TODO(b/234474291): Use the gtest versions once that's available in OSS.
-absl::Status GetStatus(const absl::Status& s) { return s; }
template <typename T>
absl::Status GetStatus(const absl::StatusOr<T>& s) {
return s.status();
@@ -81,45 +85,28 @@
#define ASSERT_OK(x) ASSERT_THAT(x, StatusIs(absl::StatusCode::kOk))
template <typename ExtensionT>
-const FileDescriptor& GetExtensionFile(
+const FieldDescriptor* GetExtension(
const ExtensionT& ext,
const Descriptor* descriptor = FeatureSet::descriptor()) {
- return *DescriptorPool::generated_pool()
- ->FindExtensionByNumber(descriptor, ext.number())
- ->file();
+ return ABSL_DIE_IF_NULL(descriptor->file()->pool()->FindExtensionByNumber(
+ descriptor, ext.number()));
}
-const DescriptorPool* EmptyPool() {
- static DescriptorPool* empty_pool = new DescriptorPool();
- return empty_pool;
-}
-
-template <typename... Extensions>
-absl::StatusOr<FeatureResolver> SetupFeatureResolverImpl(
- absl::string_view edition, const DescriptorPool* pool,
- Extensions... extensions) {
- auto resolver =
- FeatureResolver::Create(edition, FeatureSet::descriptor(), pool);
- RETURN_IF_ERROR(resolver.status());
- std::vector<absl::Status> results{
- resolver->RegisterExtensions(GetExtensionFile(extensions))...};
- for (absl::Status result : results) {
- RETURN_IF_ERROR(result);
- }
- return resolver;
-}
template <typename... Extensions>
absl::StatusOr<FeatureResolver> SetupFeatureResolver(absl::string_view edition,
Extensions... extensions) {
- return SetupFeatureResolverImpl(edition, EmptyPool(), extensions...);
+ absl::StatusOr<FeatureSetDefaults> defaults =
+ FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
+ {GetExtension(extensions)...}, "2023",
+ "2023");
+ RETURN_IF_ERROR(defaults.status());
+ return FeatureResolver::Create(edition, *defaults);
}
-template <typename... Extensions>
-absl::StatusOr<FeatureSet> GetDefaultsImpl(absl::string_view edition,
- const DescriptorPool* pool,
- Extensions... extensions) {
+absl::StatusOr<FeatureSet> GetDefaults(absl::string_view edition,
+ const FeatureSetDefaults& defaults) {
absl::StatusOr<FeatureResolver> resolver =
- SetupFeatureResolverImpl(edition, pool, extensions...);
+ FeatureResolver::Create(edition, defaults);
RETURN_IF_ERROR(resolver.status());
FeatureSet parent, child;
return resolver->MergeFeatures(parent, child);
@@ -128,7 +115,12 @@
template <typename... Extensions>
absl::StatusOr<FeatureSet> GetDefaults(absl::string_view edition,
Extensions... extensions) {
- return GetDefaultsImpl(edition, EmptyPool(), extensions...);
+ absl::StatusOr<FeatureSetDefaults> defaults =
+ FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
+ {GetExtension(extensions)...}, "0",
+ "99999");
+ RETURN_IF_ERROR(defaults.status());
+ return GetDefaults(edition, *defaults);
}
FileDescriptorProto GetProto(const FileDescriptor* file) {
@@ -184,8 +176,10 @@
EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN);
EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED);
EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED);
+ EXPECT_FALSE(merged->HasExtension(pb::test));
- const pb::TestFeatures& ext = merged->GetExtension(pb::test);
+ const pb::TestFeatures& ext =
+ merged->GetExtension(pb::TestMessage::test_message);
EXPECT_EQ(ext.int_file_feature(), 1);
EXPECT_EQ(ext.int_extension_range_feature(), 1);
EXPECT_EQ(ext.int_message_feature(), 1);
@@ -212,8 +206,10 @@
EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN);
EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED);
EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED);
+ EXPECT_FALSE(merged->HasExtension(pb::test));
- const pb::TestFeatures& ext = merged->GetExtension(pb::test);
+ const pb::TestFeatures& ext =
+ merged->GetExtension(pb::TestMessage::Nested::test_nested);
EXPECT_EQ(ext.int_file_feature(), 1);
EXPECT_EQ(ext.int_extension_range_feature(), 1);
EXPECT_EQ(ext.int_message_feature(), 1);
@@ -238,25 +234,19 @@
nullptr);
ASSERT_NE(pool.BuildFile(GetProto(pb::TestFeatures::descriptor()->file())),
nullptr);
- absl::StatusOr<FeatureSet> merged = GetDefaultsImpl("2023", &pool);
- ASSERT_OK(merged);
+ absl::StatusOr<FeatureSetDefaults> defaults =
+ FeatureResolver::CompileDefaults(
+ pool.FindMessageTypeByName("google.protobuf.FeatureSet"),
+ {pool.FindExtensionByName("pb.test")}, "2023", "2023");
+ ASSERT_OK(defaults);
+ ASSERT_EQ(defaults->defaults().size(), 1);
+ ASSERT_EQ(defaults->defaults().at(0).edition(), "2023");
+ FeatureSet merged = defaults->defaults().at(0).features();
- EXPECT_EQ(merged->field_presence(), FeatureSet::EXPLICIT);
- EXPECT_TRUE(merged->HasExtension(pb::test));
- EXPECT_EQ(merged->GetExtension(pb::test).int_file_feature(), 1);
- EXPECT_FALSE(merged->HasExtension(pb::cpp));
-}
-
-TEST(FeatureResolverTest, DefaultsGeneratedPoolGenerated) {
- absl::StatusOr<FeatureSet> merged =
- GetDefaultsImpl("2023", DescriptorPool::generated_pool());
- ASSERT_OK(merged);
-
- EXPECT_EQ(merged->field_presence(), FeatureSet::EXPLICIT);
- EXPECT_FALSE(merged->HasExtension(pb::test));
- EXPECT_TRUE(merged->HasExtension(pb::cpp));
- EXPECT_EQ(merged->GetExtension(pb::cpp).utf8_validation(),
- pb::CppFeatures::VERIFY_PARSE);
+ EXPECT_EQ(merged.field_presence(), FeatureSet::EXPLICIT);
+ EXPECT_TRUE(merged.HasExtension(pb::test));
+ EXPECT_EQ(merged.GetExtension(pb::test).int_file_feature(), 1);
+ EXPECT_FALSE(merged.HasExtension(pb::cpp));
}
TEST(FeatureResolverTest, DefaultsTooEarly) {
@@ -328,28 +318,48 @@
}
}
-TEST(FeatureResolverTest, InitializePoolMissingDescriptor) {
- DescriptorPool pool;
- EXPECT_THAT(FeatureResolver::Create("2023", nullptr, EmptyPool()),
+TEST(FeatureResolverTest, CreateFromUnsortedDefaults) {
+ FeatureSetDefaults defaults = ParseTextOrDie(R"pb(
+ minimum_edition: "2022"
+ maximum_edition: "2024"
+ defaults {
+ edition: "2022"
+ features {}
+ }
+ defaults {
+ edition: "2024"
+ features {}
+ }
+ defaults {
+ edition: "2023"
+ features {}
+ }
+ )pb");
+ EXPECT_THAT(
+ FeatureResolver::Create("2023", defaults),
+ HasError(AllOf(
+ HasSubstr("not strictly increasing."),
+ HasSubstr("Edition 2024 is greater than or equal to edition 2023"))));
+}
+
+TEST(FeatureResolverTest, CompileDefaultsMissingDescriptor) {
+ EXPECT_THAT(FeatureResolver::CompileDefaults(nullptr, {}, "2023", "2023"),
HasError(HasSubstr("find definition of google.protobuf.FeatureSet")));
}
-TEST(FeatureResolverTest, RegisterExtensionDifferentContainingType) {
- auto resolver =
- FeatureResolver::Create("2023", FeatureSet::descriptor(), EmptyPool());
- ASSERT_OK(resolver);
-
- EXPECT_OK(resolver->RegisterExtensions(
- GetExtensionFile(protobuf_unittest::file_opt1, FileOptions::descriptor())));
+TEST(FeatureResolverTest, CompileDefaultsMissingExtension) {
+ EXPECT_THAT(FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
+ {nullptr}, "2023", "2023"),
+ HasError(HasSubstr("Unknown extension")));
}
-TEST(FeatureResolverTest, RegisterExtensionTwice) {
- auto resolver =
- FeatureResolver::Create("2023", FeatureSet::descriptor(), EmptyPool());
- ASSERT_OK(resolver);
-
- EXPECT_OK(resolver->RegisterExtensions(GetExtensionFile(pb::test)));
- EXPECT_OK(resolver->RegisterExtensions(GetExtensionFile(pb::test)));
+TEST(FeatureResolverTest, CompileDefaultsInvalidExtension) {
+ EXPECT_THAT(
+ FeatureResolver::CompileDefaults(
+ FeatureSet::descriptor(),
+ {GetExtension(protobuf_unittest::file_opt1, FileOptions::descriptor())},
+ "2023", "2023"),
+ HasError(HasSubstr("is not an extension of")));
}
TEST(FeatureResolverTest, MergeFeaturesChildOverrideCore) {
@@ -508,13 +518,17 @@
pb::TestFeatures::TEST_ENUM_FEATURE_UNKNOWN);
}
-// TODO(b/273611177) Move the following tests to descriptor_unittest.cc once
-// FeatureResolver is hooked up. These will all fail during the descriptor
-// build, invalidating the test setup.
-//
-// Note: We should make sure to keep coverage over the custom DescriptorPool
-// cases. These are the only tests covering issues there (the ones above use
-// the generated descriptor pool).
+TEST(FeatureResolverTest, MergeFeaturesDistantPast) {
+ EXPECT_THAT(SetupFeatureResolver("1000"),
+ HasError(AllOf(HasSubstr("Edition 1000"),
+ HasSubstr("minimum supported edition 2023"))));
+}
+
+TEST(FeatureResolverTest, MergeFeaturesDistantFuture) {
+ EXPECT_THAT(SetupFeatureResolver("9999"),
+ HasError(AllOf(HasSubstr("Edition 9999"),
+ HasSubstr("maximum supported edition 2023"))));
+}
class FakeErrorCollector : public io::ErrorCollector {
public:
@@ -534,6 +548,12 @@
FileDescriptorProto file;
FileDescriptorProto::GetDescriptor()->file()->CopyTo(&file);
ASSERT_NE(pool_.BuildFile(file), nullptr);
+ feature_set_ = pool_.FindMessageTypeByName("google.protobuf.FeatureSet");
+ ASSERT_NE(feature_set_, nullptr);
+ auto defaults =
+ FeatureResolver::CompileDefaults(feature_set_, {}, "2023", "2023");
+ ASSERT_OK(defaults);
+ defaults_ = std::move(defaults).value();
}
const FileDescriptor* ParseSchema(absl::string_view schema) {
@@ -552,9 +572,11 @@
DescriptorPool pool_;
FileDescriptorProto file_proto_;
+ const Descriptor* feature_set_;
+ FeatureSetDefaults defaults_;
};
-TEST_F(FeatureResolverPoolTest, RegisterExtensionNonMessage) {
+TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidNonMessage) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -567,16 +589,14 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
- ASSERT_OK(resolver);
-
- EXPECT_THAT(resolver->RegisterExtensions(*file),
- HasError(AllOf(HasSubstr("test.bar"),
- HasSubstr("is not of message type"))));
+ const FieldDescriptor* ext = file->extension(0);
+ EXPECT_THAT(
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023"),
+ HasError(
+ AllOf(HasSubstr("test.bar"), HasSubstr("is not of message type"))));
}
-TEST_F(FeatureResolverPoolTest, RegisterExtensionRepeated) {
+TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidRepeated) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -589,16 +609,13 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
- ASSERT_OK(resolver);
-
+ const FieldDescriptor* ext = file->extension(0);
EXPECT_THAT(
- resolver->RegisterExtensions(*file),
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023"),
HasError(AllOf(HasSubstr("test.bar"), HasSubstr("repeated extension"))));
}
-TEST_F(FeatureResolverPoolTest, RegisterExtensionWithExtensions) {
+TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithExtensions) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -619,16 +636,13 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
- ASSERT_OK(resolver);
-
+ const FieldDescriptor* ext = file->extension(0);
EXPECT_THAT(
- resolver->RegisterExtensions(*file),
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023"),
HasError(AllOf(HasSubstr("test.bar"), HasSubstr("Nested extensions"))));
}
-TEST_F(FeatureResolverPoolTest, RegisterExtensionWithOneof) {
+TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithOneof) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -652,16 +666,14 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
- ASSERT_OK(resolver);
-
- EXPECT_THAT(resolver->RegisterExtensions(*file),
- HasError(AllOf(HasSubstr("test.Foo"),
- HasSubstr("oneof feature fields"))));
+ const FieldDescriptor* ext = file->extension(0);
+ EXPECT_THAT(
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023"),
+ HasError(
+ AllOf(HasSubstr("test.Foo"), HasSubstr("oneof feature fields"))));
}
-TEST_F(FeatureResolverPoolTest, RegisterExtensionWithRequired) {
+TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithRequired) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -679,16 +691,14 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
- ASSERT_OK(resolver);
-
- EXPECT_THAT(resolver->RegisterExtensions(*file),
- HasError(AllOf(HasSubstr("test.Foo.required_field"),
- HasSubstr("required field"))));
+ const FieldDescriptor* ext = file->extension(0);
+ EXPECT_THAT(
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023"),
+ HasError(AllOf(HasSubstr("test.Foo.required_field"),
+ HasSubstr("required field"))));
}
-TEST_F(FeatureResolverPoolTest, RegisterExtensionWithRepeated) {
+TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithRepeated) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -706,16 +716,14 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
- ASSERT_OK(resolver);
-
- EXPECT_THAT(resolver->RegisterExtensions(*file),
- HasError(AllOf(HasSubstr("test.Foo.repeated_field"),
- HasSubstr("repeated field"))));
+ const FieldDescriptor* ext = file->extension(0);
+ EXPECT_THAT(
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023"),
+ HasError(AllOf(HasSubstr("test.Foo.repeated_field"),
+ HasSubstr("repeated field"))));
}
-TEST_F(FeatureResolverPoolTest, RegisterExtensionWithMissingTarget) {
+TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithMissingTarget) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -732,16 +740,15 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
- ASSERT_OK(resolver);
-
- EXPECT_THAT(resolver->RegisterExtensions(*file),
- HasError(AllOf(HasSubstr("test.Foo.int_field"),
- HasSubstr("no target specified"))));
+ const FieldDescriptor* ext = file->extension(0);
+ EXPECT_THAT(
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023"),
+ HasError(AllOf(HasSubstr("test.Foo.int_field"),
+ HasSubstr("no target specified"))));
}
-TEST_F(FeatureResolverPoolTest, RegisterExtensionDefaultsMessageParsingError) {
+TEST_F(FeatureResolverPoolTest,
+ CompileDefaultsInvalidDefaultsMessageParsingError) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -762,16 +769,14 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
- ASSERT_OK(resolver);
+ const FieldDescriptor* ext = file->extension(0);
EXPECT_THAT(
- resolver->RegisterExtensions(*file),
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023"),
HasError(AllOf(HasSubstr("in edition_defaults"), HasSubstr("9987"))));
}
TEST_F(FeatureResolverPoolTest,
- RegisterExtensionDefaultsMessageParsingErrorMerged) {
+ CompileDefaultsInvalidDefaultsMessageParsingErrorMerged) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -794,16 +799,14 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2024", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
- ASSERT_OK(resolver);
+ const FieldDescriptor* ext = file->extension(0);
EXPECT_THAT(
- resolver->RegisterExtensions(*file),
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2025"),
HasError(AllOf(HasSubstr("in edition_defaults"), HasSubstr("9987"))));
}
TEST_F(FeatureResolverPoolTest,
- RegisterExtensionDefaultsMessageParsingErrorSkipped) {
+ CompileDefaultsInvalidDefaultsMessageParsingErrorSkipped) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -826,15 +829,19 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2024", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
+ const FieldDescriptor* ext = file->extension(0);
+ auto defaults =
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2024");
+ ASSERT_OK(defaults);
+
+ auto resolver = FeatureResolver::Create("2023", *defaults);
ASSERT_OK(resolver);
- EXPECT_OK(resolver->RegisterExtensions(*file));
FeatureSet parent, child;
EXPECT_OK(resolver->MergeFeatures(parent, child));
}
-TEST_F(FeatureResolverPoolTest, RegisterExtensionDefaultsScalarParsingError) {
+TEST_F(FeatureResolverPoolTest,
+ CompileDefaultsInvalidDefaultsScalarParsingError) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -852,16 +859,14 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
- ASSERT_OK(resolver);
+ const FieldDescriptor* ext = file->extension(0);
EXPECT_THAT(
- resolver->RegisterExtensions(*file),
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023"),
HasError(AllOf(HasSubstr("in edition_defaults"), HasSubstr("1.23"))));
}
TEST_F(FeatureResolverPoolTest,
- RegisterExtensionDefaultsScalarParsingErrorSkipped) {
+ CompileDefaultsInvalidDefaultsScalarParsingErrorSkipped) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@@ -880,41 +885,39 @@
)schema");
ASSERT_NE(file, nullptr);
- auto resolver = FeatureResolver::Create(
- "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet"), EmptyPool());
+ const FieldDescriptor* ext = file->extension(0);
+ auto defaults =
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023");
+ ASSERT_OK(defaults);
+
+ auto resolver = FeatureResolver::Create("2023", *defaults);
ASSERT_OK(resolver);
- EXPECT_OK(resolver->RegisterExtensions(*file));
FeatureSet parent, child;
EXPECT_OK(resolver->MergeFeatures(parent, child));
}
-TEST_F(FeatureResolverPoolTest, GeneratedPoolInvalidExtension) {
+TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidDefaultsTooEarly) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
- message Foo {}
extend google.protobuf.FeatureSet {
- optional string bar = 9999;
+ optional Foo bar = 9999;
+ }
+ message Foo {
+ optional int32 int_field_feature = 12 [
+ targets = TARGET_TYPE_FIELD,
+ edition_defaults = { edition: "2022", value: "1" }
+ ];
}
)schema");
ASSERT_NE(file, nullptr);
- EXPECT_THAT(GetDefaultsImpl("2023", &pool_),
- HasError(AllOf(HasSubstr("test.bar"),
- HasSubstr("is not of message type"))));
-}
-
-TEST_F(FeatureResolverPoolTest, GeneratedPoolDefaultsFailure) {
- ASSERT_NE(
- pool_.BuildFile(GetProto(google::protobuf::DescriptorProto::descriptor()->file())),
- nullptr);
- ASSERT_NE(pool_.BuildFile(GetProto(pb::TestFeatures::descriptor()->file())),
- nullptr);
+ const FieldDescriptor* ext = file->extension(0);
EXPECT_THAT(
- GetDefaultsImpl("2022", &pool_),
- HasError(AllOf(HasSubstr("No valid default found"), HasSubstr("2022"))));
+ FeatureResolver::CompileDefaults(feature_set_, {ext}, "2023", "2023"),
+ HasError(HasSubstr("No valid default found for edition 2022")));
}
} // namespace