Files
clang-p2996/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
QuietMisdreavus 1be4a67454 [ExtractAPI] reorder the module names in extension symbol graph file names (#119925)
Resolves rdar://140298287

ExtractAPI's support for printing Objective-C category extensions from
other modules emits symbol graphs with an
`ExtendedModule@HostModule.symbols.json`. However, this is backwards
from existing symbol graph practices, causing issues when these symbol
graphs are consumed alongside symbol graphs generated with other tools
like Swift. This PR flips the naming scheme to be in line with existing
symbol graph tooling.
2024-12-16 13:36:19 -07:00

1132 lines
37 KiB
C++

//===- ExtractAPI/Serialization/SymbolGraphSerializer.cpp -------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file implements the SymbolGraphSerializer.
///
//===----------------------------------------------------------------------===//
#include "clang/ExtractAPI/Serialization/SymbolGraphSerializer.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Version.h"
#include "clang/ExtractAPI/API.h"
#include "clang/ExtractAPI/DeclarationFragments.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/Path.h"
#include "llvm/Support/VersionTuple.h"
#include "llvm/Support/raw_ostream.h"
#include <iterator>
#include <optional>
#include <type_traits>
using namespace clang;
using namespace clang::extractapi;
using namespace llvm;
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,
std::optional<Object> &&Obj) {
if (Obj)
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,
std::optional<Array> &&Array) {
if (Array)
Paren[Key] = std::move(*Array);
}
/// Helper function to inject a JSON array composed of the values in \p C into
/// object \p Paren at position \p Key.
template <typename ContainerTy>
void serializeArray(Object &Paren, StringRef Key, ContainerTy &&C) {
Paren[Key] = Array(C);
}
/// Serialize a \c VersionTuple \p V with the Symbol Graph semantic version
/// format.
///
/// A semantic version object contains three numeric fields, representing the
/// \c major, \c minor, and \c patch parts of the version tuple.
/// For example version tuple 1.0.3 is serialized as:
/// \code
/// {
/// "major" : 1,
/// "minor" : 0,
/// "patch" : 3
/// }
/// \endcode
///
/// \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 std::nullopt;
Object Version;
Version["major"] = V.getMajor();
Version["minor"] = V.getMinor().value_or(0);
Version["patch"] = V.getSubminor().value_or(0);
return Version;
}
/// Serialize the OS information in the Symbol Graph platform property.
///
/// The OS information in Symbol Graph contains the \c name of the OS, and an
/// optional \c minimumVersion semantic version field.
Object serializeOperatingSystem(const Triple &T) {
Object OS;
OS["name"] = T.getOSTypeName(T.getOS());
serializeObject(OS, "minimumVersion",
serializeSemanticVersion(T.getMinimumSupportedOSVersion()));
return OS;
}
/// Serialize the platform information in the Symbol Graph module section.
///
/// The platform object describes a target platform triple in corresponding
/// three fields: \c architecture, \c vendor, and \c operatingSystem.
Object serializePlatform(const Triple &T) {
Object Platform;
Platform["architecture"] = T.getArchName();
Platform["vendor"] = T.getVendorName();
if (!T.getEnvironmentName().empty())
Platform["environment"] = T.getEnvironmentName();
Platform["operatingSystem"] = serializeOperatingSystem(T);
return Platform;
}
/// Serialize a source position.
Object serializeSourcePosition(const PresumedLoc &Loc) {
assert(Loc.isValid() && "invalid source position");
Object SourcePosition;
SourcePosition["line"] = Loc.getLine() - 1;
SourcePosition["character"] = Loc.getColumn() - 1;
return SourcePosition;
}
/// Serialize a source location in file.
///
/// \param Loc The presumed location to serialize.
/// \param IncludeFileURI If true, include the file path of \p Loc as a URI.
/// Defaults to false.
Object serializeSourceLocation(const PresumedLoc &Loc,
bool IncludeFileURI = false) {
Object SourceLocation;
serializeObject(SourceLocation, "position", serializeSourcePosition(Loc));
if (IncludeFileURI) {
std::string FileURI = "file://";
// Normalize file path to use forward slashes for the URI.
FileURI += sys::path::convert_to_slash(Loc.getFilename());
SourceLocation["uri"] = FileURI;
}
return SourceLocation;
}
/// Serialize a source range with begin and end locations.
Object serializeSourceRange(const PresumedLoc &BeginLoc,
const PresumedLoc &EndLoc) {
Object SourceRange;
serializeObject(SourceRange, "start", serializeSourcePosition(BeginLoc));
serializeObject(SourceRange, "end", serializeSourcePosition(EndLoc));
return SourceRange;
}
/// 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)).
///
/// \returns \c std::nullopt if the symbol has default availability attributes,
/// or an \c Array containing an object with the formatted availability
/// information.
std::optional<Array> serializeAvailability(const AvailabilityInfo &Avail) {
if (Avail.isDefault())
return std::nullopt;
Array AvailabilityArray;
if (Avail.isUnconditionallyDeprecated()) {
Object UnconditionallyDeprecated;
UnconditionallyDeprecated["domain"] = "*";
UnconditionallyDeprecated["isUnconditionallyDeprecated"] = true;
AvailabilityArray.emplace_back(std::move(UnconditionallyDeprecated));
}
if (Avail.Domain.str() != "") {
Object Availability;
Availability["domain"] = Avail.Domain;
if (Avail.isUnavailable()) {
Availability["isUnconditionallyUnavailable"] = true;
} else {
serializeObject(Availability, "introduced",
serializeSemanticVersion(Avail.Introduced));
serializeObject(Availability, "deprecated",
serializeSemanticVersion(Avail.Deprecated));
serializeObject(Availability, "obsoleted",
serializeSemanticVersion(Avail.Obsoleted));
}
AvailabilityArray.emplace_back(std::move(Availability));
}
return AvailabilityArray;
}
/// Get the language name string for interface language references.
StringRef getLanguageName(Language Lang) {
switch (Lang) {
case Language::C:
return "c";
case Language::ObjC:
return "objective-c";
case Language::CXX:
return "c++";
case Language::ObjCXX:
return "objective-c++";
// Unsupported language currently
case Language::OpenCL:
case Language::OpenCLCXX:
case Language::CUDA:
case Language::HIP:
case Language::HLSL:
// Languages that the frontend cannot parse and compile
case Language::Unknown:
case Language::Asm:
case Language::LLVM_IR:
case Language::CIR:
llvm_unreachable("Unsupported language kind");
}
llvm_unreachable("Unhandled language kind");
}
/// Serialize the identifier object as specified by the Symbol Graph format.
///
/// The identifier property of a symbol contains the USR for precise and unique
/// references, and the interface language name.
Object serializeIdentifier(const APIRecord &Record, Language Lang) {
Object Identifier;
Identifier["precise"] = Record.USR;
Identifier["interfaceLanguage"] = getLanguageName(Lang);
return Identifier;
}
/// Serialize the documentation comments attached to a symbol, as specified by
/// the Symbol Graph format.
///
/// The Symbol Graph \c docComment object contains an array of lines. Each line
/// represents one line of striped documentation comment, with source range
/// information.
/// e.g.
/// \code
/// /// This is a documentation comment
/// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' First line.
/// /// with multiple lines.
/// ^~~~~~~~~~~~~~~~~~~~~~~' Second line.
/// \endcode
///
/// \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 std::nullopt;
Object DocComment;
Array LinesArray;
for (const auto &CommentLine : Comment) {
Object Line;
Line["text"] = CommentLine.Text;
serializeObject(Line, "range",
serializeSourceRange(CommentLine.Begin, CommentLine.End));
LinesArray.emplace_back(std::move(Line));
}
serializeArray(DocComment, "lines", std::move(LinesArray));
return DocComment;
}
/// Serialize the declaration fragments of a symbol.
///
/// The Symbol Graph declaration fragments is an array of tagged important
/// parts of a symbol's declaration. The fragments sequence can be joined to
/// form spans of declaration text, with attached information useful for
/// purposes like syntax-highlighting etc. For example:
/// \code
/// const int pi; -> "declarationFragments" : [
/// {
/// "kind" : "keyword",
/// "spelling" : "const"
/// },
/// {
/// "kind" : "text",
/// "spelling" : " "
/// },
/// {
/// "kind" : "typeIdentifier",
/// "preciseIdentifier" : "c:I",
/// "spelling" : "int"
/// },
/// {
/// "kind" : "text",
/// "spelling" : " "
/// },
/// {
/// "kind" : "identifier",
/// "spelling" : "pi"
/// }
/// ]
/// \endcode
///
/// \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 std::nullopt;
Array Fragments;
for (const auto &F : DF.getFragments()) {
Object Fragment;
Fragment["spelling"] = F.Spelling;
Fragment["kind"] = DeclarationFragments::getFragmentKindString(F.Kind);
if (!F.PreciseIdentifier.empty())
Fragment["preciseIdentifier"] = F.PreciseIdentifier;
Fragments.emplace_back(std::move(Fragment));
}
return Fragments;
}
/// Serialize the \c names field of a symbol as specified by the Symbol Graph
/// format.
///
/// The Symbol Graph names field contains multiple representations of a symbol
/// that can be used for different applications:
/// - \c title : The simple declared name of the symbol;
/// - \c subHeading : An array of declaration fragments that provides tags,
/// and potentially more tokens (for example the \c +/- symbol for
/// Objective-C methods). Can be used as sub-headings for documentation.
Object serializeNames(const APIRecord *Record) {
Object Names;
Names["title"] = Record->Name;
serializeArray(Names, "subHeading",
serializeDeclarationFragments(Record->SubHeading));
DeclarationFragments NavigatorFragments;
NavigatorFragments.append(Record->Name,
DeclarationFragments::FragmentKind::Identifier,
/*PreciseIdentifier*/ "");
serializeArray(Names, "navigator",
serializeDeclarationFragments(NavigatorFragments));
return Names;
}
Object serializeSymbolKind(APIRecord::RecordKind RK, Language Lang) {
auto AddLangPrefix = [&Lang](StringRef S) -> std::string {
return (getLanguageName(Lang) + "." + S).str();
};
Object Kind;
switch (RK) {
case APIRecord::RK_Unknown:
Kind["identifier"] = AddLangPrefix("unknown");
Kind["displayName"] = "Unknown";
break;
case APIRecord::RK_Namespace:
Kind["identifier"] = AddLangPrefix("namespace");
Kind["displayName"] = "Namespace";
break;
case APIRecord::RK_GlobalFunction:
Kind["identifier"] = AddLangPrefix("func");
Kind["displayName"] = "Function";
break;
case APIRecord::RK_GlobalFunctionTemplate:
Kind["identifier"] = AddLangPrefix("func");
Kind["displayName"] = "Function Template";
break;
case APIRecord::RK_GlobalFunctionTemplateSpecialization:
Kind["identifier"] = AddLangPrefix("func");
Kind["displayName"] = "Function Template Specialization";
break;
case APIRecord::RK_GlobalVariableTemplate:
Kind["identifier"] = AddLangPrefix("var");
Kind["displayName"] = "Global Variable Template";
break;
case APIRecord::RK_GlobalVariableTemplateSpecialization:
Kind["identifier"] = AddLangPrefix("var");
Kind["displayName"] = "Global Variable Template Specialization";
break;
case APIRecord::RK_GlobalVariableTemplatePartialSpecialization:
Kind["identifier"] = AddLangPrefix("var");
Kind["displayName"] = "Global Variable Template Partial Specialization";
break;
case APIRecord::RK_GlobalVariable:
Kind["identifier"] = AddLangPrefix("var");
Kind["displayName"] = "Global Variable";
break;
case APIRecord::RK_EnumConstant:
Kind["identifier"] = AddLangPrefix("enum.case");
Kind["displayName"] = "Enumeration Case";
break;
case APIRecord::RK_Enum:
Kind["identifier"] = AddLangPrefix("enum");
Kind["displayName"] = "Enumeration";
break;
case APIRecord::RK_StructField:
Kind["identifier"] = AddLangPrefix("property");
Kind["displayName"] = "Instance Property";
break;
case APIRecord::RK_Struct:
Kind["identifier"] = AddLangPrefix("struct");
Kind["displayName"] = "Structure";
break;
case APIRecord::RK_UnionField:
Kind["identifier"] = AddLangPrefix("property");
Kind["displayName"] = "Instance Property";
break;
case APIRecord::RK_Union:
Kind["identifier"] = AddLangPrefix("union");
Kind["displayName"] = "Union";
break;
case APIRecord::RK_CXXField:
Kind["identifier"] = AddLangPrefix("property");
Kind["displayName"] = "Instance Property";
break;
case APIRecord::RK_StaticField:
Kind["identifier"] = AddLangPrefix("type.property");
Kind["displayName"] = "Type Property";
break;
case APIRecord::RK_ClassTemplate:
case APIRecord::RK_ClassTemplateSpecialization:
case APIRecord::RK_ClassTemplatePartialSpecialization:
case APIRecord::RK_CXXClass:
Kind["identifier"] = AddLangPrefix("class");
Kind["displayName"] = "Class";
break;
case APIRecord::RK_CXXMethodTemplate:
Kind["identifier"] = AddLangPrefix("method");
Kind["displayName"] = "Method Template";
break;
case APIRecord::RK_CXXMethodTemplateSpecialization:
Kind["identifier"] = AddLangPrefix("method");
Kind["displayName"] = "Method Template Specialization";
break;
case APIRecord::RK_CXXFieldTemplate:
Kind["identifier"] = AddLangPrefix("property");
Kind["displayName"] = "Template Property";
break;
case APIRecord::RK_Concept:
Kind["identifier"] = AddLangPrefix("concept");
Kind["displayName"] = "Concept";
break;
case APIRecord::RK_CXXStaticMethod:
Kind["identifier"] = AddLangPrefix("type.method");
Kind["displayName"] = "Static Method";
break;
case APIRecord::RK_CXXInstanceMethod:
Kind["identifier"] = AddLangPrefix("method");
Kind["displayName"] = "Instance Method";
break;
case APIRecord::RK_CXXConstructorMethod:
Kind["identifier"] = AddLangPrefix("method");
Kind["displayName"] = "Constructor";
break;
case APIRecord::RK_CXXDestructorMethod:
Kind["identifier"] = AddLangPrefix("method");
Kind["displayName"] = "Destructor";
break;
case APIRecord::RK_ObjCIvar:
Kind["identifier"] = AddLangPrefix("ivar");
Kind["displayName"] = "Instance Variable";
break;
case APIRecord::RK_ObjCInstanceMethod:
Kind["identifier"] = AddLangPrefix("method");
Kind["displayName"] = "Instance Method";
break;
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";
break;
case APIRecord::RK_ObjCCategory:
Kind["identifier"] = AddLangPrefix("class.extension");
Kind["displayName"] = "Class Extension";
break;
case APIRecord::RK_ObjCProtocol:
Kind["identifier"] = AddLangPrefix("protocol");
Kind["displayName"] = "Protocol";
break;
case APIRecord::RK_MacroDefinition:
Kind["identifier"] = AddLangPrefix("macro");
Kind["displayName"] = "Macro";
break;
case APIRecord::RK_Typedef:
Kind["identifier"] = AddLangPrefix("typealias");
Kind["displayName"] = "Type Alias";
break;
default:
llvm_unreachable("API Record with uninstantiable kind");
}
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.KindForDisplay, Lang);
}
/// Serialize the function signature field, as specified by the
/// Symbol Graph format.
///
/// The Symbol Graph function signature property contains two arrays.
/// - The \c returns array is the declaration fragments of the return type;
/// - The \c parameters array contains names and declaration fragments of the
/// parameters.
template <typename RecordTy>
void serializeFunctionSignatureMixin(Object &Paren, const RecordTy &Record) {
const auto &FS = Record.Signature;
if (FS.empty())
return;
Object Signature;
serializeArray(Signature, "returns",
serializeDeclarationFragments(FS.getReturnType()));
Array Parameters;
for (const auto &P : FS.getParameters()) {
Object Parameter;
Parameter["name"] = P.Name;
serializeArray(Parameter, "declarationFragments",
serializeDeclarationFragments(P.Fragments));
Parameters.emplace_back(std::move(Parameter));
}
if (!Parameters.empty())
Signature["parameters"] = std::move(Parameters);
serializeObject(Paren, "functionSignature", std::move(Signature));
}
template <typename RecordTy>
void serializeTemplateMixin(Object &Paren, const RecordTy &Record) {
const auto &Template = Record.Templ;
if (Template.empty())
return;
Object Generics;
Array GenericParameters;
for (const auto &Param : Template.getParameters()) {
Object Parameter;
Parameter["name"] = Param.Name;
Parameter["index"] = Param.Index;
Parameter["depth"] = Param.Depth;
GenericParameters.emplace_back(std::move(Parameter));
}
if (!GenericParameters.empty())
Generics["parameters"] = std::move(GenericParameters);
Array GenericConstraints;
for (const auto &Constr : Template.getConstraints()) {
Object Constraint;
Constraint["kind"] = Constr.Kind;
Constraint["lhs"] = Constr.LHS;
Constraint["rhs"] = Constr.RHS;
GenericConstraints.emplace_back(std::move(Constraint));
}
if (!GenericConstraints.empty())
Generics["constraints"] = std::move(GenericConstraints);
serializeObject(Paren, "swiftGenerics", Generics);
}
Array generateParentContexts(const SmallVectorImpl<SymbolReference> &Parents,
Language Lang) {
Array ParentContexts;
for (const auto &Parent : Parents) {
Object Elem;
Elem["usr"] = Parent.USR;
Elem["name"] = Parent.Name;
if (Parent.Record)
Elem["kind"] = serializeSymbolKind(Parent.Record->KindForDisplay,
Lang)["identifier"];
else
Elem["kind"] =
serializeSymbolKind(APIRecord::RK_Unknown, Lang)["identifier"];
ParentContexts.emplace_back(std::move(Elem));
}
return ParentContexts;
}
/// Walk the records parent information in reverse to generate a hierarchy
/// suitable for serialization.
SmallVector<SymbolReference, 8>
generateHierarchyFromRecord(const APIRecord *Record) {
SmallVector<SymbolReference, 8> ReverseHierarchy;
for (const auto *Current = Record; Current != nullptr;
Current = Current->Parent.Record)
ReverseHierarchy.emplace_back(Current);
return SmallVector<SymbolReference, 8>(
std::make_move_iterator(ReverseHierarchy.rbegin()),
std::make_move_iterator(ReverseHierarchy.rend()));
}
SymbolReference getHierarchyReference(const APIRecord *Record,
const APISet &API) {
// If the parent is a category extended from internal module then we need to
// pretend this belongs to the associated interface.
if (auto *CategoryRecord = dyn_cast_or_null<ObjCCategoryRecord>(Record)) {
return CategoryRecord->Interface;
// FIXME: TODO generate path components correctly for categories extending
// an external module.
}
return SymbolReference(Record);
}
} // namespace
Object *ExtendedModule::addSymbol(Object &&Symbol) {
Symbols.emplace_back(std::move(Symbol));
return Symbols.back().getAsObject();
}
void ExtendedModule::addRelationship(Object &&Relationship) {
Relationships.emplace_back(std::move(Relationship));
}
/// Defines the format version emitted by SymbolGraphSerializer.
const VersionTuple SymbolGraphSerializer::FormatVersion{0, 5, 3};
Object SymbolGraphSerializer::serializeMetadata() const {
Object Metadata;
serializeObject(Metadata, "formatVersion",
serializeSemanticVersion(FormatVersion));
Metadata["generator"] = clang::getClangFullVersion();
return Metadata;
}
Object
SymbolGraphSerializer::serializeModuleObject(StringRef ModuleName) const {
Object Module;
Module["name"] = ModuleName;
serializeObject(Module, "platform", serializePlatform(API.getTarget()));
return Module;
}
bool SymbolGraphSerializer::shouldSkip(const APIRecord *Record) const {
if (!Record)
return true;
// Skip unconditionally unavailable symbols
if (Record->Availability.isUnconditionallyUnavailable())
return true;
// Filter out symbols prefixed with an underscored as they are understood to
// be symbols clients should not use.
if (Record->Name.starts_with("_"))
return true;
// Skip explicitly ignored symbols.
if (IgnoresList.shouldIgnore(Record->Name))
return true;
return false;
}
ExtendedModule &SymbolGraphSerializer::getModuleForCurrentSymbol() {
if (!ForceEmitToMainModule && ModuleForCurrentSymbol)
return *ModuleForCurrentSymbol;
return MainModule;
}
Array SymbolGraphSerializer::serializePathComponents(
const APIRecord *Record) const {
return Array(map_range(Hierarchy, [](auto Elt) { return Elt.Name; }));
}
StringRef SymbolGraphSerializer::getRelationshipString(RelationshipKind Kind) {
switch (Kind) {
case RelationshipKind::MemberOf:
return "memberOf";
case RelationshipKind::InheritsFrom:
return "inheritsFrom";
case RelationshipKind::ConformsTo:
return "conformsTo";
case RelationshipKind::ExtensionTo:
return "extensionTo";
}
llvm_unreachable("Unhandled relationship kind");
}
void SymbolGraphSerializer::serializeRelationship(RelationshipKind Kind,
const SymbolReference &Source,
const SymbolReference &Target,
ExtendedModule &Into) {
Object Relationship;
SmallString<64> TestRelLabel;
if (EmitSymbolLabelsForTesting) {
llvm::raw_svector_ostream OS(TestRelLabel);
OS << SymbolGraphSerializer::getRelationshipString(Kind) << " $ "
<< Source.USR << " $ ";
if (Target.USR.empty())
OS << Target.Name;
else
OS << Target.USR;
Relationship["!testRelLabel"] = TestRelLabel;
}
Relationship["source"] = Source.USR;
Relationship["target"] = Target.USR;
Relationship["targetFallback"] = Target.Name;
Relationship["kind"] = SymbolGraphSerializer::getRelationshipString(Kind);
if (ForceEmitToMainModule)
MainModule.addRelationship(std::move(Relationship));
else
Into.addRelationship(std::move(Relationship));
}
StringRef SymbolGraphSerializer::getConstraintString(ConstraintKind Kind) {
switch (Kind) {
case ConstraintKind::Conformance:
return "conformance";
case ConstraintKind::ConditionalConformance:
return "conditionalConformance";
}
llvm_unreachable("Unhandled constraint kind");
}
void SymbolGraphSerializer::serializeAPIRecord(const APIRecord *Record) {
Object Obj;
// If we need symbol labels for testing emit the USR as the value and the key
// starts with '!'' to ensure it ends up at the top of the object.
if (EmitSymbolLabelsForTesting)
Obj["!testLabel"] = Record->USR;
serializeObject(Obj, "identifier",
serializeIdentifier(*Record, API.getLanguage()));
serializeObject(Obj, "kind", serializeSymbolKind(*Record, API.getLanguage()));
serializeObject(Obj, "names", serializeNames(Record));
serializeObject(
Obj, "location",
serializeSourceLocation(Record->Location, /*IncludeFileURI=*/true));
serializeArray(Obj, "availability",
serializeAvailability(Record->Availability));
serializeObject(Obj, "docComment", serializeDocComment(Record->Comment));
serializeArray(Obj, "declarationFragments",
serializeDeclarationFragments(Record->Declaration));
Obj["pathComponents"] = serializePathComponents(Record);
Obj["accessLevel"] = Record->Access.getAccess();
ExtendedModule &Module = getModuleForCurrentSymbol();
// If the hierarchy has at least one parent and child.
if (Hierarchy.size() >= 2)
serializeRelationship(MemberOf, Hierarchy.back(),
Hierarchy[Hierarchy.size() - 2], Module);
CurrentSymbol = Module.addSymbol(std::move(Obj));
}
bool SymbolGraphSerializer::traverseAPIRecord(const APIRecord *Record) {
if (!Record)
return true;
if (shouldSkip(Record))
return true;
Hierarchy.push_back(getHierarchyReference(Record, API));
// Defer traversal mechanics to APISetVisitor base implementation
auto RetVal = Base::traverseAPIRecord(Record);
Hierarchy.pop_back();
return RetVal;
}
bool SymbolGraphSerializer::visitAPIRecord(const APIRecord *Record) {
serializeAPIRecord(Record);
return true;
}
bool SymbolGraphSerializer::visitGlobalFunctionRecord(
const GlobalFunctionRecord *Record) {
if (!CurrentSymbol)
return true;
serializeFunctionSignatureMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::visitCXXClassRecord(const CXXClassRecord *Record) {
if (!CurrentSymbol)
return true;
for (const auto &Base : Record->Bases)
serializeRelationship(RelationshipKind::InheritsFrom, Record, Base,
getModuleForCurrentSymbol());
return true;
}
bool SymbolGraphSerializer::visitClassTemplateRecord(
const ClassTemplateRecord *Record) {
if (!CurrentSymbol)
return true;
serializeTemplateMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::visitClassTemplatePartialSpecializationRecord(
const ClassTemplatePartialSpecializationRecord *Record) {
if (!CurrentSymbol)
return true;
serializeTemplateMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::visitCXXMethodRecord(
const CXXMethodRecord *Record) {
if (!CurrentSymbol)
return true;
serializeFunctionSignatureMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::visitCXXMethodTemplateRecord(
const CXXMethodTemplateRecord *Record) {
if (!CurrentSymbol)
return true;
serializeTemplateMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::visitCXXFieldTemplateRecord(
const CXXFieldTemplateRecord *Record) {
if (!CurrentSymbol)
return true;
serializeTemplateMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::visitConceptRecord(const ConceptRecord *Record) {
if (!CurrentSymbol)
return true;
serializeTemplateMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::visitGlobalVariableTemplateRecord(
const GlobalVariableTemplateRecord *Record) {
if (!CurrentSymbol)
return true;
serializeTemplateMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::
visitGlobalVariableTemplatePartialSpecializationRecord(
const GlobalVariableTemplatePartialSpecializationRecord *Record) {
if (!CurrentSymbol)
return true;
serializeTemplateMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::visitGlobalFunctionTemplateRecord(
const GlobalFunctionTemplateRecord *Record) {
if (!CurrentSymbol)
return true;
serializeTemplateMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::visitObjCContainerRecord(
const ObjCContainerRecord *Record) {
if (!CurrentSymbol)
return true;
for (const auto &Protocol : Record->Protocols)
serializeRelationship(ConformsTo, Record, Protocol,
getModuleForCurrentSymbol());
return true;
}
bool SymbolGraphSerializer::visitObjCInterfaceRecord(
const ObjCInterfaceRecord *Record) {
if (!CurrentSymbol)
return true;
if (!Record->SuperClass.empty())
serializeRelationship(InheritsFrom, Record, Record->SuperClass,
getModuleForCurrentSymbol());
return true;
}
bool SymbolGraphSerializer::traverseObjCCategoryRecord(
const ObjCCategoryRecord *Record) {
if (SkipSymbolsInCategoriesToExternalTypes &&
!API.findRecordForUSR(Record->Interface.USR))
return true;
auto *CurrentModule = ModuleForCurrentSymbol;
if (auto ModuleExtendedByRecord = Record->getExtendedExternalModule())
ModuleForCurrentSymbol = &ExtendedModules[*ModuleExtendedByRecord];
if (!walkUpFromObjCCategoryRecord(Record))
return false;
bool RetVal = traverseRecordContext(Record);
ModuleForCurrentSymbol = CurrentModule;
return RetVal;
}
bool SymbolGraphSerializer::walkUpFromObjCCategoryRecord(
const ObjCCategoryRecord *Record) {
return visitObjCCategoryRecord(Record);
}
bool SymbolGraphSerializer::visitObjCCategoryRecord(
const ObjCCategoryRecord *Record) {
// If we need to create a record for the category in the future do so here,
// otherwise everything is set up to pretend that the category is in fact the
// interface it extends.
for (const auto &Protocol : Record->Protocols)
serializeRelationship(ConformsTo, Record->Interface, Protocol,
getModuleForCurrentSymbol());
return true;
}
bool SymbolGraphSerializer::visitObjCMethodRecord(
const ObjCMethodRecord *Record) {
if (!CurrentSymbol)
return true;
serializeFunctionSignatureMixin(*CurrentSymbol, *Record);
return true;
}
bool SymbolGraphSerializer::visitObjCInstanceVariableRecord(
const ObjCInstanceVariableRecord *Record) {
// FIXME: serialize ivar access control here.
return true;
}
bool SymbolGraphSerializer::walkUpFromTypedefRecord(
const TypedefRecord *Record) {
// Short-circuit walking up the class hierarchy and handle creating typedef
// symbol objects manually as there are additional symbol dropping rules to
// respect.
return visitTypedefRecord(Record);
}
bool SymbolGraphSerializer::visitTypedefRecord(const TypedefRecord *Record) {
// Typedefs of anonymous types have their entries unified with the underlying
// type.
bool ShouldDrop = Record->UnderlyingType.Name.empty();
// enums declared with `NS_OPTION` have a named enum and a named typedef, with
// the same name
ShouldDrop |= (Record->UnderlyingType.Name == Record->Name);
if (ShouldDrop)
return true;
// Create the symbol record if the other symbol droppping rules permit it.
serializeAPIRecord(Record);
if (!CurrentSymbol)
return true;
(*CurrentSymbol)["type"] = Record->UnderlyingType.USR;
return true;
}
void SymbolGraphSerializer::serializeSingleRecord(const APIRecord *Record) {
switch (Record->getKind()) {
// dispatch to the relevant walkUpFromMethod
#define CONCRETE_RECORD(CLASS, BASE, KIND) \
case APIRecord::KIND: { \
walkUpFrom##CLASS(static_cast<const CLASS *>(Record)); \
break; \
}
#include "clang/ExtractAPI/APIRecords.inc"
// otherwise fallback on the only behavior we can implement safely.
case APIRecord::RK_Unknown:
visitAPIRecord(Record);
break;
default:
llvm_unreachable("API Record with uninstantiable kind");
}
}
Object SymbolGraphSerializer::serializeGraph(StringRef ModuleName,
ExtendedModule &&EM) {
Object Root;
serializeObject(Root, "metadata", serializeMetadata());
serializeObject(Root, "module", serializeModuleObject(ModuleName));
Root["symbols"] = std::move(EM.Symbols);
Root["relationships"] = std::move(EM.Relationships);
return Root;
}
void SymbolGraphSerializer::serializeGraphToStream(
raw_ostream &OS, SymbolGraphSerializerOption Options, StringRef ModuleName,
ExtendedModule &&EM) {
Object Root = serializeGraph(ModuleName, std::move(EM));
if (Options.Compact)
OS << formatv("{0}", json::Value(std::move(Root))) << "\n";
else
OS << formatv("{0:2}", json::Value(std::move(Root))) << "\n";
}
void SymbolGraphSerializer::serializeMainSymbolGraph(
raw_ostream &OS, const APISet &API, const APIIgnoresList &IgnoresList,
SymbolGraphSerializerOption Options) {
SymbolGraphSerializer Serializer(
API, IgnoresList, Options.EmitSymbolLabelsForTesting,
/*ForceEmitToMainModule=*/true,
/*SkipSymbolsInCategoriesToExternalTypes=*/true);
Serializer.traverseAPISet();
Serializer.serializeGraphToStream(OS, Options, API.ProductName,
std::move(Serializer.MainModule));
// FIXME: TODO handle extended modules here
}
void SymbolGraphSerializer::serializeWithExtensionGraphs(
raw_ostream &MainOutput, const APISet &API,
const APIIgnoresList &IgnoresList,
llvm::function_ref<std::unique_ptr<llvm::raw_pwrite_stream>(Twine BaseName)>
CreateOutputStream,
SymbolGraphSerializerOption Options) {
SymbolGraphSerializer Serializer(API, IgnoresList,
Options.EmitSymbolLabelsForTesting);
Serializer.traverseAPISet();
Serializer.serializeGraphToStream(MainOutput, Options, API.ProductName,
std::move(Serializer.MainModule));
for (auto &ExtensionSGF : Serializer.ExtendedModules) {
if (auto ExtensionOS =
CreateOutputStream(API.ProductName + "@" + ExtensionSGF.getKey()))
Serializer.serializeGraphToStream(*ExtensionOS, Options, API.ProductName,
std::move(ExtensionSGF.getValue()));
}
}
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,
/*EmitSymbolLabelsForTesting*/ false,
/*ForceEmitToMainModule*/ true);
// Set up serializer parent chain
Serializer.Hierarchy = generateHierarchyFromRecord(Record);
Serializer.serializeSingleRecord(Record);
serializeObject(Root, "symbolGraph",
Serializer.serializeGraph(API.ProductName,
std::move(Serializer.MainModule)));
Language Lang = API.getLanguage();
serializeArray(Root, "parentContexts",
generateParentContexts(Serializer.Hierarchy, 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);
RelatedSymbol["accessLevel"] = RelatedRecord->Access.getAccess();
RelatedSymbol["filePath"] = RelatedRecord->Location.getFilename();
RelatedSymbol["moduleName"] = API.ProductName;
RelatedSymbol["isSystem"] = RelatedRecord->IsFromSystemHeader;
serializeArray(RelatedSymbol, "parentContexts",
generateParentContexts(
generateHierarchyFromRecord(RelatedRecord), Lang));
RelatedSymbols.push_back(std::move(RelatedSymbol));
}
serializeArray(Root, "relatedSymbols", RelatedSymbols);
return Root;
}