From b2eb9291ee4a2170c650c67cf133894a65df7c11 Mon Sep 17 00:00:00 2001 From: gigel773 Date: Mon, 22 Jun 2026 18:18:44 +0300 Subject: [PATCH 1/2] [feature] Add aliases generation support for nested types --- src/compilation/cpp_gen.cpp | 9 +++++++++ src/compilation/ir.cpp | 5 +++-- src/compilation/ir.h | 5 ++++- src/compilation/proto_front.cpp | 13 ++++++++++--- tests/proto_api.cpp | 7 +++++++ tests/protoyaff/proto_api.proto | 4 ++++ 6 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/compilation/cpp_gen.cpp b/src/compilation/cpp_gen.cpp index ae5ab0d..833a8d3 100644 --- a/src/compilation/cpp_gen.cpp +++ b/src/compilation/cpp_gen.cpp @@ -484,6 +484,15 @@ 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.NestedTypes) { + const auto& fullName = child->Name; + if (const auto pos = fullName.find('_'); pos != std::string::npos) { + Writer_ >= "using " + fullName.substr(pos + 1, fullName.size()) + " = " + fullName + ";"; + } + } + if (Writer_.TextWritten()) { Writer_ |= ""; } diff --git a/src/compilation/ir.cpp b/src/compilation/ir.cpp index 5b9a5a4..cf35da6 100644 --- a/src/compilation/ir.cpp +++ b/src/compilation/ir.cpp @@ -93,8 +93,9 @@ std::string EnumDef::ToString() const { return Schema->Namespace + "::" + Name; } -MessageDef::MessageDef(std::string name, const SchemaDef* schema, MessageLayout layout) - : BaseDef(std::move(name)), Schema(schema), Layout(layout) { +MessageDef::MessageDef(std::string name, const SchemaDef* schema, std::vector nestedTypes, + const MessageLayout layout) + : BaseDef(std::move(name)), Schema(schema), NestedTypes(std::move(nestedTypes)), Layout(layout) { } std::string MessageDef::ToString() const { diff --git a/src/compilation/ir.h b/src/compilation/ir.h index e2f5fa7..161b88a 100644 --- a/src/compilation/ir.h +++ b/src/compilation/ir.h @@ -104,6 +104,8 @@ struct MessageDef : public BaseDef { const SchemaDef* Schema = nullptr; + std::vector NestedTypes; + MessageLayout Layout = MessageLayout::MESSAGE_LAYOUT_UNKNOWN; std::vector Fields; @@ -112,7 +114,8 @@ struct MessageDef : public BaseDef { bool AssociativePair = false; std::map OneOfs; - MessageDef(std::string name, const SchemaDef* schema, MessageLayout layout = MessageLayout::MESSAGE_LAYOUT_UNKNOWN); + MessageDef(std::string name, const SchemaDef* schema, std::vector nestedTypes = {}, + MessageLayout layout = MessageLayout::MESSAGE_LAYOUT_UNKNOWN); std::string ToString() const; }; diff --git a/src/compilation/proto_front.cpp b/src/compilation/proto_front.cpp index d7d742e..6fce0f3 100644 --- a/src/compilation/proto_front.cpp +++ b/src/compilation/proto_front.cpp @@ -274,13 +274,19 @@ class TProtobufBuilder { }; const ir::MessageDef* TProtobufBuilder::TraverseMessage(const google::protobuf::Descriptor& message) { + std::vector nestedTypes; + 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) { + nestedTypes.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) { + nestedTypes.push_back(next); + } } const auto fields = CollectFields(message); @@ -297,7 +303,8 @@ 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, std::move(nestedTypes))); if (!emplaced) { return messageDef; } diff --git a/tests/proto_api.cpp b/tests/proto_api.cpp index fc83cbc..cf3fc5f 100644 --- a/tests/proto_api.cpp +++ b/tests/proto_api.cpp @@ -130,6 +130,13 @@ TEST(ProtoAPI, TemplateTest) { const auto& restoredProto = Restore(yaff); + static_assert(std::is_same_v, + "Nested alias for enumeration is not generated"); + static_assert(std::is_same_v, + "Nested alias for message is not generated"); + CheckExplicitStringFields(originalProto); CheckExplicitStringFields(yaff); CheckExplicitStringFields(restoredProto); diff --git a/tests/protoyaff/proto_api.proto b/tests/protoyaff/proto_api.proto index a8351b5..eb164ee 100644 --- a/tests/protoyaff/proto_api.proto +++ b/tests/protoyaff/proto_api.proto @@ -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; From 2299f3eccfc5117d09db9a3641056b3091e425d0 Mon Sep 17 00:00:00 2001 From: gigel773 Date: Wed, 12 Aug 2026 11:09:22 +0300 Subject: [PATCH 2/2] [fix] Fixed review issues + additional tests --- src/compilation/cpp_gen.cpp | 59 ++++++++++++++++++++++++++++++--- src/compilation/ir.cpp | 5 ++- src/compilation/ir.h | 6 ++-- src/compilation/proto_front.cpp | 12 ++++--- tests/proto_api.cpp | 50 ++++++++++++++++++++++++++++ tests/protoyaff/proto_api.proto | 12 +++++++ 6 files changed, 130 insertions(+), 14 deletions(-) diff --git a/src/compilation/cpp_gen.cpp b/src/compilation/cpp_gen.cpp index 833a8d3..6a3695f 100644 --- a/src/compilation/cpp_gen.cpp +++ b/src/compilation/cpp_gen.cpp @@ -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); @@ -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); @@ -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;"; @@ -486,12 +527,14 @@ void CppGenerator::Impl::GenerateMessage(const ir::MessageDef& msgDef) { } // Generate type aliases for nested types - for (auto&& child : msgDef.NestedTypes) { - const auto& fullName = child->Name; - if (const auto pos = fullName.find('_'); pos != std::string::npos) { - Writer_ >= "using " + fullName.substr(pos + 1, fullName.size()) + " = " + fullName + ";"; + 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_ |= ""; @@ -1773,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); diff --git a/src/compilation/ir.cpp b/src/compilation/ir.cpp index cf35da6..e053dae 100644 --- a/src/compilation/ir.cpp +++ b/src/compilation/ir.cpp @@ -93,9 +93,8 @@ std::string EnumDef::ToString() const { return Schema->Namespace + "::" + Name; } -MessageDef::MessageDef(std::string name, const SchemaDef* schema, std::vector nestedTypes, - const MessageLayout layout) - : BaseDef(std::move(name)), Schema(schema), NestedTypes(std::move(nestedTypes)), Layout(layout) { +MessageDef::MessageDef(std::string name, const SchemaDef* schema, const MessageLayout layout) + : BaseDef(std::move(name)), Schema(schema), Layout(layout) { } std::string MessageDef::ToString() const { diff --git a/src/compilation/ir.h b/src/compilation/ir.h index 161b88a..76a1c8d 100644 --- a/src/compilation/ir.h +++ b/src/compilation/ir.h @@ -104,7 +104,9 @@ struct MessageDef : public BaseDef { const SchemaDef* Schema = nullptr; - std::vector NestedTypes; + // These fields are filled during traversal, right after this MessageDef is registered. + std::vector NestedEnums; + std::vector NestedMessages; MessageLayout Layout = MessageLayout::MESSAGE_LAYOUT_UNKNOWN; std::vector Fields; @@ -114,7 +116,7 @@ struct MessageDef : public BaseDef { bool AssociativePair = false; std::map OneOfs; - MessageDef(std::string name, const SchemaDef* schema, std::vector nestedTypes = {}, + MessageDef(std::string name, const SchemaDef* schema, MessageLayout layout = MessageLayout::MESSAGE_LAYOUT_UNKNOWN); std::string ToString() const; }; diff --git a/src/compilation/proto_front.cpp b/src/compilation/proto_front.cpp index 6fce0f3..7d79222 100644 --- a/src/compilation/proto_front.cpp +++ b/src/compilation/proto_front.cpp @@ -274,18 +274,19 @@ class TProtobufBuilder { }; const ir::MessageDef* TProtobufBuilder::TraverseMessage(const google::protobuf::Descriptor& message) { - std::vector nestedTypes; + std::vector nestedMessages; + std::vector nestedEnums; for (int i = 0; i < message.nested_type_count(); ++i) { if (const auto& nested = *message.nested_type(i); !IsMap(nested)) { if (const auto* next = TraverseMessage(nested); next) { - nestedTypes.push_back(next); + nestedMessages.push_back(next); } } } for (int i = 0; i < message.enum_type_count(); ++i) { if (const auto* next = RegisterEnum(*message.enum_type(i)); next) { - nestedTypes.push_back(next); + nestedEnums.push_back(next); } } @@ -303,11 +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(messageName, schemaDef, std::move(nestedTypes))); + 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; diff --git a/tests/proto_api.cpp b/tests/proto_api.cpp index cf3fc5f..557cb28 100644 --- a/tests/proto_api.cpp +++ b/tests/proto_api.cpp @@ -208,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, + "Nested alias for depth-1 message is not generated"); + static_assert( + std::is_same_v, + "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, + "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, "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); +} diff --git a/tests/protoyaff/proto_api.proto b/tests/protoyaff/proto_api.proto index eb164ee..4c099c6 100644 --- a/tests/protoyaff/proto_api.proto +++ b/tests/protoyaff/proto_api.proto @@ -54,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 { } +}