Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/compilation/cpp_gen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ class CppGenerator::Impl {
static std::string GenerateMessageBaseClass(const ir::MessageDef& msgDef);
static std::string GenerateEnumProtobufName(const ir::EnumDef& enumDef);
static std::string GenerateWithNamespaceName(const std::string& ns, const std::string& name);
static std::string GetNestedLocalName(const std::string& parentName, const std::string& childName);

void GenerateHeader();
void GenerateIncludes(const ir::SchemaDef& schema);
Expand All @@ -271,6 +272,8 @@ class CppGenerator::Impl {
void GenerateEnumIsValidFunc(const ir::EnumDef& enumDef);
void GenerateEnumNameFunc(const ir::EnumDef& enumDef);

void GenerateNestedEnumAliases(const ir::MessageDef& msgDef, const ir::EnumDef& enumDef);

void GenerateMessageParseToProtobuf(const ir::MessageDef& msgDef);
void GenerateMessageProtobufSerializer(const ir::MessageDef& msgDef, const bool deferred = false);
void GenerateMessageAliasSerializeFunc(const ir::MessageDef& msgDef);
Expand Down Expand Up @@ -454,6 +457,44 @@ void CppGenerator::Impl::GenerateEnumNameFunc(const ir::EnumDef& enumDef) {
Writer_ |= "}\n";
}

void CppGenerator::Impl::GenerateNestedEnumAliases(const ir::MessageDef& msgDef, const ir::EnumDef& enumDef) {
const std::string localName = GetNestedLocalName(msgDef.Name, enumDef.Name);
if (localName.empty()) {
return;
}

const auto& flatName = enumDef.Name;
Writer_ >= "using " + localName + " = " + flatName + ";";
ForValues(enumDef, [&](const auto& enumVal) {
const std::string valueName = GenerateEscapedName(enumVal.Name);
Writer_ >= "static constexpr " + localName + " " + valueName + " = " + flatName + "::" + valueName + ";";
});

Writer_.IncrementIdentLevel();
Writer_ |= "static constexpr bool " + localName + "_IsValid(const int value) {";
Writer_.IncrementIdentLevel();
Writer_ |= "return " + flatName + "_IsValid(value);";
Writer_.DecrementIdentLevel();
Writer_ |= "}";

Writer_ |= "static constexpr std::string_view " + localName + "_Name(" + localName + " value) {";
Writer_.IncrementIdentLevel();
Writer_ |= "return " + flatName + "_Name(value);";
Writer_.DecrementIdentLevel();
Writer_ |= "}";

Writer_ |= "static constexpr bool " + localName + "_Parse(std::string_view name, " + localName + "* value) {";
Writer_.IncrementIdentLevel();
Writer_ |= "return " + flatName + "_Parse(name, value);";
Writer_.DecrementIdentLevel();
Writer_ |= "}";
Writer_.DecrementIdentLevel();

Writer_ >= "static constexpr " + localName + " " + localName + "_MIN = " + flatName + "_MIN;";
Writer_ >= "static constexpr " + localName + " " + localName + "_MAX = " + flatName + "_MAX;";
Writer_ >= "static constexpr int " + localName + "_ARRAYSIZE = " + flatName + "_ARRAYSIZE;";
}

void CppGenerator::Impl::GenerateEnumPre(const ir::EnumDef& enumDef) {
Writer_ |= "enum class " + enumDef.Name + " : int32_t;";

Expand Down Expand Up @@ -484,6 +525,17 @@ void CppGenerator::Impl::GenerateMessage(const ir::MessageDef& msgDef) {
if (Opts_.GenerateProtobufApi) {
Writer_ >= "using ProtobufType = " + GenerateMessageProtobufType(msgDef) + ";";
}

// Generate type aliases for nested types
for (auto&& child : msgDef.NestedMessages) {
if (const std::string localName = GetNestedLocalName(msgDef.Name, child->Name); !localName.empty()) {
Writer_ >= "using " + localName + " = " + child->Name + ";";
}
}
for (auto&& child : msgDef.NestedEnums) {
GenerateNestedEnumAliases(msgDef, *child);
}

if (Writer_.TextWritten()) {
Writer_ |= "";
}
Expand Down Expand Up @@ -1764,6 +1816,14 @@ std::string CppGenerator::Impl::GenerateWithNamespaceName(const std::string& ns,
return ns + "::" + name;
}

std::string CppGenerator::Impl::GetNestedLocalName(const std::string& parentName, const std::string& childName) {
const std::string prefix = parentName + "_";
if (childName.size() > prefix.size() && childName.compare(0, prefix.size(), prefix) == 0) {
return childName.substr(prefix.size());
}
return {};
}

CppGenerator::Impl::MessageMeta CppGenerator::Impl::GenerateMessageMeta(const ir::MessageDef& msg) {
MessageMeta meta;
const size_t maxId = ir::MaxFieldId(msg);
Expand Down
2 changes: 1 addition & 1 deletion src/compilation/ir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ std::string EnumDef::ToString() const {
return Schema->Namespace + "::" + Name;
}

MessageDef::MessageDef(std::string name, const SchemaDef* schema, MessageLayout layout)
MessageDef::MessageDef(std::string name, const SchemaDef* schema, const MessageLayout layout)
: BaseDef(std::move(name)), Schema(schema), Layout(layout) {
}

Expand Down
7 changes: 6 additions & 1 deletion src/compilation/ir.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ struct MessageDef : public BaseDef {

const SchemaDef* Schema = nullptr;

// These fields are filled during traversal, right after this MessageDef is registered.
std::vector<const EnumDef*> NestedEnums;
std::vector<const MessageDef*> NestedMessages;

MessageLayout Layout = MessageLayout::MESSAGE_LAYOUT_UNKNOWN;
std::vector<FieldDef> Fields;

Expand All @@ -112,7 +116,8 @@ struct MessageDef : public BaseDef {
bool AssociativePair = false;
std::map<std::string, OneOfDef> OneOfs;

MessageDef(std::string name, const SchemaDef* schema, MessageLayout layout = MessageLayout::MESSAGE_LAYOUT_UNKNOWN);
MessageDef(std::string name, const SchemaDef* schema,
MessageLayout layout = MessageLayout::MESSAGE_LAYOUT_UNKNOWN);
std::string ToString() const;
};

Expand Down
15 changes: 12 additions & 3 deletions src/compilation/proto_front.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,20 @@ class TProtobufBuilder {
};

const ir::MessageDef* TProtobufBuilder::TraverseMessage(const google::protobuf::Descriptor& message) {
std::vector<const ir::MessageDef*> nestedMessages;
std::vector<const ir::EnumDef*> nestedEnums;

for (int i = 0; i < message.nested_type_count(); ++i) {
if (const auto& nested = *message.nested_type(i); !IsMap(nested)) {
TraverseMessage(nested);
if (const auto* next = TraverseMessage(nested); next) {
nestedMessages.push_back(next);
}
}
}
for (int i = 0; i < message.enum_type_count(); ++i) {
RegisterEnum(*message.enum_type(i));
if (const auto* next = RegisterEnum(*message.enum_type(i)); next) {
nestedEnums.push_back(next);
}
}

const auto fields = CollectFields(message);
Expand All @@ -297,10 +304,12 @@ const ir::MessageDef* TProtobufBuilder::TraverseMessage(const google::protobuf::
auto* schemaDef = RegisterFile(*file);

const std::string messageName = GetTypeDefName(AdaptString(file->package()), AdaptString(message.full_name()));
auto [messageDef, emplaced] = Ir_.Messages.TryEmplace(ir::MessageDef(std::move(messageName), schemaDef));
auto [messageDef, emplaced] = Ir_.Messages.TryEmplace(ir::MessageDef(messageName, schemaDef));
if (!emplaced) {
return messageDef;
}
messageDef->NestedEnums = std::move(nestedEnums);
messageDef->NestedMessages = std::move(nestedMessages);
schemaDef->Messages.emplace_back(messageDef);
if (!IsTargetFile(*file)) {
return messageDef;
Expand Down
57 changes: 57 additions & 0 deletions tests/proto_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ TEST(ProtoAPI, TemplateTest) {

const auto& restoredProto = Restore(yaff);

static_assert(std::is_same_v<protoyaff::test::UniversalMessage::EmbeddedEnumeration,
protoyaff::test::UniversalMessage_EmbeddedEnumeration>,
"Nested alias for enumeration is not generated");
static_assert(std::is_same_v<protoyaff::test::UniversalMessage::EmbeddedMessage,
protoyaff::test::UniversalMessage_EmbeddedMessage>,
"Nested alias for message is not generated");

CheckExplicitStringFields(originalProto);
CheckExplicitStringFields(yaff);
CheckExplicitStringFields(restoredProto);
Expand Down Expand Up @@ -201,3 +208,53 @@ TEST(ProtoAPI, Enumerations) {
EXPECT_EQ(protoyaff::test::Enumeration_IsValid(2), test::Enumeration_IsValid(2));
EXPECT_EQ(protoyaff::test::Enumeration_IsValid(9000), test::Enumeration_IsValid(9000));
}

TEST(ProtoAPI, NestedTypeAliasDepth) {
// Depth >= 2: the alias inside Level2 must be named "Level3", not "Level2_Level3".
static_assert(std::is_same_v<protoyaff::test::DeepNesting::Level2, protoyaff::test::DeepNesting_Level2>,
"Nested alias for depth-1 message is not generated");
static_assert(
std::is_same_v<protoyaff::test::DeepNesting::Level2::Level3, protoyaff::test::DeepNesting_Level2_Level3>,
"Nested alias for depth-2 message is not generated, or was generated with the wrong local name");

// Enclosing message's own flattened name contains an underscore: the alias inside it must still be
// named "Inner", not "Message_Inner".
static_assert(
std::is_same_v<protoyaff::test::My_UnderscoredMessage::Inner, protoyaff::test::My_UnderscoredMessage_Inner>,
"Nested alias is wrong when the enclosing message name contains an underscore");
}

TEST(ProtoAPI, NestedEnumFullParity) {
using Alias = protoyaff::test::UniversalMessage::EmbeddedEnumeration;
using Flat = protoyaff::test::UniversalMessage_EmbeddedEnumeration;

static_assert(std::is_same_v<Alias, Flat>, "Nested alias for enumeration is not generated");
static_assert(protoyaff::test::UniversalMessage::EMBEDDED_ENUMERATION_UNSPECIFIED ==
Flat::EMBEDDED_ENUMERATION_UNSPECIFIED);
static_assert(protoyaff::test::UniversalMessage::EMBEDDED_ENUMERATION_SPECIFIED ==
Flat::EMBEDDED_ENUMERATION_SPECIFIED);
static_assert(protoyaff::test::UniversalMessage::EmbeddedEnumeration_MIN ==
protoyaff::test::UniversalMessage_EmbeddedEnumeration_MIN);
static_assert(protoyaff::test::UniversalMessage::EmbeddedEnumeration_MAX ==
protoyaff::test::UniversalMessage_EmbeddedEnumeration_MAX);
static_assert(protoyaff::test::UniversalMessage::EmbeddedEnumeration_ARRAYSIZE ==
protoyaff::test::UniversalMessage_EmbeddedEnumeration_ARRAYSIZE);

for (const int value : {-1, 0, 1, 2}) {
EXPECT_EQ(protoyaff::test::UniversalMessage::EmbeddedEnumeration_IsValid(value),
protoyaff::test::UniversalMessage_EmbeddedEnumeration_IsValid(value));
}

EXPECT_EQ(protoyaff::test::UniversalMessage::EmbeddedEnumeration_Name(
protoyaff::test::UniversalMessage::EMBEDDED_ENUMERATION_SPECIFIED),
protoyaff::test::UniversalMessage_EmbeddedEnumeration_Name(Flat::EMBEDDED_ENUMERATION_SPECIFIED));

Alias parsedViaAlias{};
Flat parsedViaFlat{};
const bool aliasParsed =
protoyaff::test::UniversalMessage::EmbeddedEnumeration_Parse("EMBEDDED_ENUMERATION_SPECIFIED", &parsedViaAlias);
const bool flatParsed = protoyaff::test::UniversalMessage_EmbeddedEnumeration_Parse(
"EMBEDDED_ENUMERATION_SPECIFIED", &parsedViaFlat);
EXPECT_EQ(aliasParsed, flatParsed);
EXPECT_EQ(parsedViaAlias, parsedViaFlat);
}
16 changes: 16 additions & 0 deletions tests/protoyaff/proto_api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ enum Enumeration {
}

message UniversalMessage {
enum EmbeddedEnumeration {
EMBEDDED_ENUMERATION_UNSPECIFIED = 0;
EMBEDDED_ENUMERATION_SPECIFIED = 1;
}
message EmbeddedMessage { }

int32 implicit_numeric_field = 1;
Expand Down Expand Up @@ -50,3 +54,15 @@ message UniversalMessage {
EmbeddedMessage oneof_embedded_message_field = 26;
}
}

// Covers nested-type alias generation at nesting depth >= 2.
message DeepNesting {
message Level2 {
message Level3 { }
}
}

// Covers nested-type alias generation when the enclosing message's own flattened name contains an underscore.
message My_UnderscoredMessage {
message Inner { }
}
Loading