diff options
Diffstat (limited to 'clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp')
-rw-r--r-- | clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp | 379 |
1 files changed, 290 insertions, 89 deletions
diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp index 709b781968bf..01e9b37d2680 100644 --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -12,12 +12,21 @@ //===----------------------------------------------------------------------===// #include "clang/ExtractAPI/Serialization/SymbolGraphSerializer.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/Version.h" #include "clang/ExtractAPI/API.h" +#include "clang/ExtractAPI/APIIgnoresList.h" #include "clang/ExtractAPI/DeclarationFragments.h" +#include "clang/ExtractAPI/Serialization/SerializerBase.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/STLFunctionalExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" #include "llvm/Support/VersionTuple.h" +#include <optional> #include <type_traits> using namespace clang; @@ -29,16 +38,16 @@ namespace { /// Helper function to inject a JSON object \p Obj into another object \p Paren /// at position \p Key. -void serializeObject(Object &Paren, StringRef Key, Optional<Object> Obj) { +void serializeObject(Object &Paren, StringRef Key, std::optional<Object> Obj) { if (Obj) - Paren[Key] = std::move(Obj.value()); + Paren[Key] = std::move(*Obj); } /// Helper function to inject a JSON array \p Array into object \p Paren at /// position \p Key. -void serializeArray(Object &Paren, StringRef Key, Optional<Array> Array) { +void serializeArray(Object &Paren, StringRef Key, std::optional<Array> Array) { if (Array) - Paren[Key] = std::move(Array.value()); + Paren[Key] = std::move(*Array); } /// Serialize a \c VersionTuple \p V with the Symbol Graph semantic version @@ -55,11 +64,11 @@ void serializeArray(Object &Paren, StringRef Key, Optional<Array> Array) { /// } /// \endcode /// -/// \returns \c None if the version \p V is empty, or an \c Object containing -/// the semantic version representation of \p V. -Optional<Object> serializeSemanticVersion(const VersionTuple &V) { +/// \returns \c std::nullopt if the version \p V is empty, or an \c Object +/// containing the semantic version representation of \p V. +std::optional<Object> serializeSemanticVersion(const VersionTuple &V) { if (V.empty()) - return None; + return std::nullopt; Object Version; Version["major"] = V.getMajor(); @@ -135,30 +144,43 @@ Object serializeSourceRange(const PresumedLoc &BeginLoc, /// Serialize the availability attributes of a symbol. /// /// Availability information contains the introduced, deprecated, and obsoleted -/// versions of the symbol as semantic versions, if not default. -/// Availability information also contains flags to indicate if the symbol is -/// unconditionally unavailable or deprecated, -/// i.e. \c __attribute__((unavailable)) and \c __attribute__((deprecated)). +/// versions of the symbol for a given domain (roughly corresponds to a +/// platform) as semantic versions, if not default. Availability information +/// also contains flags to indicate if the symbol is unconditionally unavailable +/// or deprecated, i.e. \c __attribute__((unavailable)) and \c +/// __attribute__((deprecated)). /// -/// \returns \c None if the symbol has default availability attributes, or -/// an \c Object containing the formatted availability information. -Optional<Object> serializeAvailability(const AvailabilityInfo &Avail) { - if (Avail.isDefault()) - return None; - - Object Availbility; - serializeObject(Availbility, "introducedVersion", - serializeSemanticVersion(Avail.Introduced)); - serializeObject(Availbility, "deprecatedVersion", - serializeSemanticVersion(Avail.Deprecated)); - serializeObject(Availbility, "obsoletedVersion", - serializeSemanticVersion(Avail.Obsoleted)); - if (Avail.isUnavailable()) - Availbility["isUnconditionallyUnavailable"] = true; - if (Avail.isUnconditionallyDeprecated()) - Availbility["isUnconditionallyDeprecated"] = true; - - return Availbility; +/// \returns \c std::nullopt if the symbol has default availability attributes, +/// or an \c Array containing the formatted availability information. +std::optional<Array> +serializeAvailability(const AvailabilitySet &Availabilities) { + if (Availabilities.isDefault()) + return std::nullopt; + + Array AvailabilityArray; + + if (Availabilities.isUnconditionallyDeprecated()) { + Object UnconditionallyDeprecated; + UnconditionallyDeprecated["domain"] = "*"; + UnconditionallyDeprecated["isUnconditionallyDeprecated"] = true; + AvailabilityArray.emplace_back(std::move(UnconditionallyDeprecated)); + } + + // Note unconditionally unavailable records are skipped. + + for (const auto &AvailInfo : Availabilities) { + Object Availability; + Availability["domain"] = AvailInfo.Domain; + serializeObject(Availability, "introducedVersion", + serializeSemanticVersion(AvailInfo.Introduced)); + serializeObject(Availability, "deprecatedVersion", + serializeSemanticVersion(AvailInfo.Deprecated)); + serializeObject(Availability, "obsoletedVersion", + serializeSemanticVersion(AvailInfo.Obsoleted)); + AvailabilityArray.emplace_back(std::move(Availability)); + } + + return AvailabilityArray; } /// Get the language name string for interface language references. @@ -215,11 +237,11 @@ Object serializeIdentifier(const APIRecord &Record, Language Lang) { /// ^~~~~~~~~~~~~~~~~~~~~~~' Second line. /// \endcode /// -/// \returns \c None if \p Comment is empty, or an \c Object containing the -/// formatted lines. -Optional<Object> serializeDocComment(const DocComment &Comment) { +/// \returns \c std::nullopt if \p Comment is empty, or an \c Object containing +/// the formatted lines. +std::optional<Object> serializeDocComment(const DocComment &Comment) { if (Comment.empty()) - return None; + return std::nullopt; Object DocComment; Array LinesArray; @@ -267,11 +289,12 @@ Optional<Object> serializeDocComment(const DocComment &Comment) { /// ] /// \endcode /// -/// \returns \c None if \p DF is empty, or an \c Array containing the formatted -/// declaration fragments array. -Optional<Array> serializeDeclarationFragments(const DeclarationFragments &DF) { +/// \returns \c std::nullopt if \p DF is empty, or an \c Array containing the +/// formatted declaration fragments array. +std::optional<Array> +serializeDeclarationFragments(const DeclarationFragments &DF) { if (DF.getFragments().empty()) - return None; + return std::nullopt; Array Fragments; for (const auto &F : DF.getFragments()) { @@ -310,18 +333,16 @@ Object serializeNames(const APIRecord &Record) { return Names; } -/// Serialize the symbol kind information. -/// -/// The Symbol Graph symbol kind property contains a shorthand \c identifier -/// which is prefixed by the source language name, useful for tooling to parse -/// the kind, and a \c displayName for rendering human-readable names. -Object serializeSymbolKind(const APIRecord &Record, Language Lang) { +Object serializeSymbolKind(APIRecord::RecordKind RK, Language Lang) { auto AddLangPrefix = [&Lang](StringRef S) -> std::string { return (getLanguageName(Lang) + "." + S).str(); }; Object Kind; - switch (Record.getKind()) { + switch (RK) { + case APIRecord::RK_Unknown: + llvm_unreachable("Records should have an explicit kind"); + break; case APIRecord::RK_GlobalFunction: Kind["identifier"] = AddLangPrefix("func"); Kind["displayName"] = "Function"; @@ -350,19 +371,22 @@ Object serializeSymbolKind(const APIRecord &Record, Language Lang) { Kind["identifier"] = AddLangPrefix("ivar"); Kind["displayName"] = "Instance Variable"; break; - case APIRecord::RK_ObjCMethod: - if (dyn_cast<ObjCMethodRecord>(&Record)->IsInstanceMethod) { - Kind["identifier"] = AddLangPrefix("method"); - Kind["displayName"] = "Instance Method"; - } else { - Kind["identifier"] = AddLangPrefix("type.method"); - Kind["displayName"] = "Type Method"; - } + case APIRecord::RK_ObjCInstanceMethod: + Kind["identifier"] = AddLangPrefix("method"); + Kind["displayName"] = "Instance Method"; break; - case APIRecord::RK_ObjCProperty: + case APIRecord::RK_ObjCClassMethod: + Kind["identifier"] = AddLangPrefix("type.method"); + Kind["displayName"] = "Type Method"; + break; + case APIRecord::RK_ObjCInstanceProperty: Kind["identifier"] = AddLangPrefix("property"); Kind["displayName"] = "Instance Property"; break; + case APIRecord::RK_ObjCClassProperty: + Kind["identifier"] = AddLangPrefix("type.property"); + Kind["displayName"] = "Type Property"; + break; case APIRecord::RK_ObjCInterface: Kind["identifier"] = AddLangPrefix("class"); Kind["displayName"] = "Class"; @@ -389,12 +413,21 @@ Object serializeSymbolKind(const APIRecord &Record, Language Lang) { return Kind; } +/// Serialize the symbol kind information. +/// +/// The Symbol Graph symbol kind property contains a shorthand \c identifier +/// which is prefixed by the source language name, useful for tooling to parse +/// the kind, and a \c displayName for rendering human-readable names. +Object serializeSymbolKind(const APIRecord &Record, Language Lang) { + return serializeSymbolKind(Record.getKind(), Lang); +} + template <typename RecordTy> -Optional<Object> serializeFunctionSignatureMixinImpl(const RecordTy &Record, - std::true_type) { +std::optional<Object> +serializeFunctionSignatureMixinImpl(const RecordTy &Record, std::true_type) { const auto &FS = Record.Signature; if (FS.empty()) - return None; + return std::nullopt; Object Signature; serializeArray(Signature, "returns", @@ -416,9 +449,9 @@ Optional<Object> serializeFunctionSignatureMixinImpl(const RecordTy &Record, } template <typename RecordTy> -Optional<Object> serializeFunctionSignatureMixinImpl(const RecordTy &Record, - std::false_type) { - return None; +std::optional<Object> +serializeFunctionSignatureMixinImpl(const RecordTy &Record, std::false_type) { + return std::nullopt; } /// Serialize the function signature field, as specified by the @@ -429,7 +462,7 @@ Optional<Object> serializeFunctionSignatureMixinImpl(const RecordTy &Record, /// - The \c parameters array contains names and declaration fragments of the /// parameters. /// -/// \returns \c None if \p FS is empty, or an \c Object containing the +/// \returns \c std::nullopt if \p FS is empty, or an \c Object containing the /// formatted function signature. template <typename RecordTy> void serializeFunctionSignatureMixin(Object &Paren, const RecordTy &Record) { @@ -438,6 +471,78 @@ void serializeFunctionSignatureMixin(Object &Paren, const RecordTy &Record) { Record, has_function_signature<RecordTy>())); } +struct PathComponent { + StringRef USR; + StringRef Name; + APIRecord::RecordKind Kind; + + PathComponent(StringRef USR, StringRef Name, APIRecord::RecordKind Kind) + : USR(USR), Name(Name), Kind(Kind) {} +}; + +template <typename RecordTy> +bool generatePathComponents( + const RecordTy &Record, const APISet &API, + function_ref<void(const PathComponent &)> ComponentTransformer) { + SmallVector<PathComponent, 4> ReverseComponenents; + ReverseComponenents.emplace_back(Record.USR, Record.Name, Record.getKind()); + const auto *CurrentParent = &Record.ParentInformation; + while (CurrentParent && !CurrentParent->empty()) { + PathComponent CurrentParentComponent(CurrentParent->ParentUSR, + CurrentParent->ParentName, + CurrentParent->ParentKind); + + auto *ParentRecord = CurrentParent->ParentRecord; + // Slow path if we don't have a direct reference to the ParentRecord + if (!ParentRecord) + ParentRecord = API.findRecordForUSR(CurrentParent->ParentUSR); + + // If the parent is a category then we need to pretend this belongs to the + // associated interface. + if (auto *CategoryRecord = + dyn_cast_or_null<ObjCCategoryRecord>(ParentRecord)) { + ParentRecord = API.findRecordForUSR(CategoryRecord->Interface.USR); + CurrentParentComponent = PathComponent(CategoryRecord->Interface.USR, + CategoryRecord->Interface.Name, + APIRecord::RK_ObjCInterface); + } + + // The parent record doesn't exist which means the symbol shouldn't be + // treated as part of the current product. + if (!ParentRecord) + return true; + + ReverseComponenents.push_back(std::move(CurrentParentComponent)); + CurrentParent = &ParentRecord->ParentInformation; + } + + for (const auto &PC : reverse(ReverseComponenents)) + ComponentTransformer(PC); + + return false; +} +Object serializeParentContext(const PathComponent &PC, Language Lang) { + Object ParentContextElem; + ParentContextElem["usr"] = PC.USR; + ParentContextElem["name"] = PC.Name; + ParentContextElem["kind"] = serializeSymbolKind(PC.Kind, Lang)["identifier"]; + return ParentContextElem; +} + +template <typename RecordTy> +Array generateParentContexts(const RecordTy &Record, const APISet &API, + Language Lang) { + Array ParentContexts; + if (generatePathComponents( + Record, API, [Lang, &ParentContexts](const PathComponent &PC) { + ParentContexts.push_back(serializeParentContext(PC, Lang)); + })) + ParentContexts.clear(); + ParentContexts.pop_back(); + + return ParentContexts; +} + } // namespace void SymbolGraphSerializer::anchor() {} @@ -457,14 +562,18 @@ Object SymbolGraphSerializer::serializeModule() const { Object Module; // The user is expected to always pass `--product-name=` on the command line // to populate this field. - Module["name"] = ProductName; + Module["name"] = API.ProductName; serializeObject(Module, "platform", serializePlatform(API.getTarget())); return Module; } bool SymbolGraphSerializer::shouldSkip(const APIRecord &Record) const { + // Skip explicitly ignored symbols. + if (IgnoresList.shouldIgnore(Record.Name)) + return true; + // Skip unconditionally unavailable symbols - if (Record.Availability.isUnconditionallyUnavailable()) + if (Record.Availabilities.isUnconditionallyUnavailable()) return true; // Filter out symbols prefixed with an underscored as they are understood to @@ -476,10 +585,10 @@ bool SymbolGraphSerializer::shouldSkip(const APIRecord &Record) const { } template <typename RecordTy> -Optional<Object> +std::optional<Object> SymbolGraphSerializer::serializeAPIRecord(const RecordTy &Record) const { if (shouldSkip(Record)) - return None; + return std::nullopt; Object Obj; serializeObject(Obj, "identifier", @@ -489,15 +598,24 @@ SymbolGraphSerializer::serializeAPIRecord(const RecordTy &Record) const { serializeObject( Obj, "location", serializeSourceLocation(Record.Location, /*IncludeFileURI=*/true)); - serializeObject(Obj, "availbility", - serializeAvailability(Record.Availability)); + serializeArray(Obj, "availability", + serializeAvailability(Record.Availabilities)); serializeObject(Obj, "docComment", serializeDocComment(Record.Comment)); serializeArray(Obj, "declarationFragments", serializeDeclarationFragments(Record.Declaration)); // TODO: Once we keep track of symbol access information serialize it // correctly here. Obj["accessLevel"] = "public"; - serializeArray(Obj, "pathComponents", Array(PathComponents)); + SmallVector<StringRef, 4> PathComponentsNames; + // If this returns true it indicates that we couldn't find a symbol in the + // hierarchy. + if (generatePathComponents(Record, API, + [&PathComponentsNames](const PathComponent &PC) { + PathComponentsNames.push_back(PC.Name); + })) + return {}; + + serializeArray(Obj, "pathComponents", Array(PathComponentsNames)); serializeFunctionSignatureMixin(Obj, Record); @@ -508,8 +626,10 @@ template <typename MemberTy> void SymbolGraphSerializer::serializeMembers( const APIRecord &Record, const SmallVector<std::unique_ptr<MemberTy>> &Members) { + // Members should not be serialized if we aren't recursing. + if (!ShouldRecurse) + return; for (const auto &Member : Members) { - auto MemberPathComponentGuard = makePathComponentGuard(Member->Name); auto MemberRecord = serializeAPIRecord(*Member); if (!MemberRecord) continue; @@ -537,6 +657,7 @@ void SymbolGraphSerializer::serializeRelationship(RelationshipKind Kind, Object Relationship; Relationship["source"] = Source.USR; Relationship["target"] = Target.USR; + Relationship["targetFallback"] = Target.Name; Relationship["kind"] = getRelationshipString(Kind); Relationships.emplace_back(std::move(Relationship)); @@ -544,8 +665,6 @@ void SymbolGraphSerializer::serializeRelationship(RelationshipKind Kind, void SymbolGraphSerializer::serializeGlobalFunctionRecord( const GlobalFunctionRecord &Record) { - auto GlobalPathComponentGuard = makePathComponentGuard(Record.Name); - auto Obj = serializeAPIRecord(Record); if (!Obj) return; @@ -555,8 +674,6 @@ void SymbolGraphSerializer::serializeGlobalFunctionRecord( void SymbolGraphSerializer::serializeGlobalVariableRecord( const GlobalVariableRecord &Record) { - auto GlobalPathComponentGuard = makePathComponentGuard(Record.Name); - auto Obj = serializeAPIRecord(Record); if (!Obj) return; @@ -565,7 +682,6 @@ void SymbolGraphSerializer::serializeGlobalVariableRecord( } void SymbolGraphSerializer::serializeEnumRecord(const EnumRecord &Record) { - auto EnumPathComponentGuard = makePathComponentGuard(Record.Name); auto Enum = serializeAPIRecord(Record); if (!Enum) return; @@ -575,7 +691,6 @@ void SymbolGraphSerializer::serializeEnumRecord(const EnumRecord &Record) { } void SymbolGraphSerializer::serializeStructRecord(const StructRecord &Record) { - auto StructPathComponentGuard = makePathComponentGuard(Record.Name); auto Struct = serializeAPIRecord(Record); if (!Struct) return; @@ -586,7 +701,6 @@ void SymbolGraphSerializer::serializeStructRecord(const StructRecord &Record) { void SymbolGraphSerializer::serializeObjCContainerRecord( const ObjCContainerRecord &Record) { - auto ObjCContainerPathComponentGuard = makePathComponentGuard(Record.Name); auto ObjCContainer = serializeAPIRecord(Record); if (!ObjCContainer) return; @@ -615,7 +729,7 @@ void SymbolGraphSerializer::serializeObjCContainerRecord( serializeMembers(Record, Category->Methods); serializeMembers(Record, Category->Properties); - // Surface the protocols of the the category to the interface. + // Surface the protocols of the category to the interface. for (const auto &Protocol : Category->Protocols) serializeRelationship(RelationshipKind::ConformsTo, Record, Protocol); } @@ -624,7 +738,6 @@ void SymbolGraphSerializer::serializeObjCContainerRecord( void SymbolGraphSerializer::serializeMacroDefinitionRecord( const MacroDefinitionRecord &Record) { - auto MacroPathComponentGuard = makePathComponentGuard(Record.Name); auto Macro = serializeAPIRecord(Record); if (!Macro) @@ -633,6 +746,46 @@ void SymbolGraphSerializer::serializeMacroDefinitionRecord( Symbols.emplace_back(std::move(*Macro)); } +void SymbolGraphSerializer::serializeSingleRecord(const APIRecord *Record) { + switch (Record->getKind()) { + case APIRecord::RK_Unknown: + llvm_unreachable("Records should have a known kind!"); + case APIRecord::RK_GlobalFunction: + serializeGlobalFunctionRecord(*cast<GlobalFunctionRecord>(Record)); + break; + case APIRecord::RK_GlobalVariable: + serializeGlobalVariableRecord(*cast<GlobalVariableRecord>(Record)); + break; + case APIRecord::RK_Enum: + serializeEnumRecord(*cast<EnumRecord>(Record)); + break; + case APIRecord::RK_Struct: + serializeStructRecord(*cast<StructRecord>(Record)); + break; + case APIRecord::RK_ObjCInterface: + serializeObjCContainerRecord(*cast<ObjCInterfaceRecord>(Record)); + break; + case APIRecord::RK_ObjCProtocol: + serializeObjCContainerRecord(*cast<ObjCProtocolRecord>(Record)); + break; + case APIRecord::RK_MacroDefinition: + serializeMacroDefinitionRecord(*cast<MacroDefinitionRecord>(Record)); + break; + case APIRecord::RK_Typedef: + serializeTypedefRecord(*cast<TypedefRecord>(Record)); + break; + default: + if (auto Obj = serializeAPIRecord(*Record)) { + Symbols.emplace_back(std::move(*Obj)); + auto &ParentInformation = Record->ParentInformation; + if (!ParentInformation.empty()) + serializeRelationship(RelationshipKind::MemberOf, *Record, + *ParentInformation.ParentRecord); + } + break; + } +} + void SymbolGraphSerializer::serializeTypedefRecord( const TypedefRecord &Record) { // Typedefs of anonymous types have their entries unified with the underlying @@ -644,7 +797,6 @@ void SymbolGraphSerializer::serializeTypedefRecord( if (ShouldDrop) return; - auto TypedefPathComponentGuard = makePathComponentGuard(Record.Name); auto Typedef = serializeAPIRecord(Record); if (!Typedef) return; @@ -654,16 +806,7 @@ void SymbolGraphSerializer::serializeTypedefRecord( Symbols.emplace_back(std::move(*Typedef)); } -SymbolGraphSerializer::PathComponentGuard -SymbolGraphSerializer::makePathComponentGuard(StringRef Component) { - return PathComponentGuard(PathComponents, Component); -} - Object SymbolGraphSerializer::serialize() { - Object Root; - serializeObject(Root, "metadata", serializeMetadata()); - serializeObject(Root, "module", serializeModule()); - // Serialize global variables in the API set. for (const auto &GlobalVar : API.getGlobalVariables()) serializeGlobalVariableRecord(*GlobalVar.second); @@ -693,6 +836,14 @@ Object SymbolGraphSerializer::serialize() { for (const auto &Typedef : API.getTypedefs()) serializeTypedefRecord(*Typedef.second); + return serializeCurrentGraph(); +} + +Object SymbolGraphSerializer::serializeCurrentGraph() { + Object Root; + serializeObject(Root, "metadata", serializeMetadata()); + serializeObject(Root, "module", serializeModule()); + Root["symbols"] = std::move(Symbols); Root["relationships"] = std::move(Relationships); @@ -706,3 +857,53 @@ void SymbolGraphSerializer::serialize(raw_ostream &os) { else os << formatv("{0:2}", Value(std::move(root))) << "\n"; } + +std::optional<Object> +SymbolGraphSerializer::serializeSingleSymbolSGF(StringRef USR, + const APISet &API) { + APIRecord *Record = API.findRecordForUSR(USR); + if (!Record) + return {}; + + Object Root; + APIIgnoresList EmptyIgnores; + SymbolGraphSerializer Serializer(API, EmptyIgnores, + /*Options.Compact*/ {true}, + /*ShouldRecurse*/ false); + Serializer.serializeSingleRecord(Record); + serializeObject(Root, "symbolGraph", Serializer.serializeCurrentGraph()); + + Language Lang = API.getLanguage(); + serializeArray(Root, "parentContexts", + generateParentContexts(*Record, API, Lang)); + + Array RelatedSymbols; + + for (const auto &Fragment : Record->Declaration.getFragments()) { + // If we don't have a USR there isn't much we can do. + if (Fragment.PreciseIdentifier.empty()) + continue; + + APIRecord *RelatedRecord = API.findRecordForUSR(Fragment.PreciseIdentifier); + + // If we can't find the record let's skip. + if (!RelatedRecord) + continue; + + Object RelatedSymbol; + RelatedSymbol["usr"] = RelatedRecord->USR; + RelatedSymbol["declarationLanguage"] = getLanguageName(Lang); + // TODO: once we record this properly let's serialize it right. + RelatedSymbol["accessLevel"] = "public"; + RelatedSymbol["filePath"] = RelatedRecord->Location.getFilename(); + RelatedSymbol["moduleName"] = API.ProductName; + RelatedSymbol["isSystem"] = RelatedRecord->IsFromSystemHeader; + + serializeArray(RelatedSymbol, "parentContexts", + generateParentContexts(*RelatedRecord, API, Lang)); + RelatedSymbols.push_back(std::move(RelatedSymbol)); + } + + serializeArray(Root, "relatedSymbols", RelatedSymbols); + return Root; +} |