From 7a3a0a9e23abe3626d3821cbcf522b2bdddd740b Mon Sep 17 00:00:00 2001 From: Ashley Garland Date: Fri, 27 Sep 2019 16:25:53 -0700 Subject: [PATCH] Symbol graph support Adds a tool `swift-symbolgraph-extract` that reads an existing Swift module and prints a platform- and language-agnostic JSON description of the module, primarly for documentation. Adds a small sub-library `SymbolGraphGen` which houses the core implementation for collecting relevant information about declarations. The main entry point is integrated directly into the driver as a mode: the tool is meant to be run outside of the normal edit-compile-run/test workflow to avoid impacting build times. Along with common options for other tools, unique options include `pretty-print` for debugging, and a `minimum-access-level` options for including internal documentation. A symbol graph is a directed graph where the nodes are symbols in a module and the edges are relationships between them. For example, a `struct S` may have a member `var x`. The graph would have two nodes for `S` and `x`, and one "member-of" relationship edge. Other relationship kinds include "inherits-from" or "conforms to". The data format for a symbol graph is still under development and may change without notice until a specificiation and versioning scheme is published. Various aspects about a symbol are recorded in the nodes, such as availability, documentation comments, or data needed for printing the shapes of declarations without having to understand specifics about the langauge. Implicit and public-underscored stdlib declarations are not included by default. rdar://problem/55346798 --- include/swift/AST/Decl.h | 2 + include/swift/AST/PrintOptions.h | 7 + include/swift/Driver/Driver.h | 3 +- include/swift/SymbolGraphGen/SymbolGraphGen.h | 41 ++ lib/AST/ASTPrinter.cpp | 5 +- lib/AST/Decl.cpp | 87 ++-- lib/CMakeLists.txt | 1 + lib/Driver/Driver.cpp | 2 + lib/Markup/LineList.cpp | 2 - lib/SymbolGraphGen/CMakeLists.txt | 13 + .../DeclarationFragmentPrinter.cpp | 140 +++++++ .../DeclarationFragmentPrinter.h | 123 ++++++ lib/SymbolGraphGen/Edge.cpp | 40 ++ lib/SymbolGraphGen/Edge.h | 167 ++++++++ lib/SymbolGraphGen/FormatVersion.h | 20 + lib/SymbolGraphGen/JSON.cpp | 57 +++ lib/SymbolGraphGen/JSON.h | 45 +++ lib/SymbolGraphGen/Symbol.cpp | 380 ++++++++++++++++++ lib/SymbolGraphGen/Symbol.h | 128 ++++++ lib/SymbolGraphGen/SymbolGraph.cpp | 68 ++++ lib/SymbolGraphGen/SymbolGraph.h | 64 +++ lib/SymbolGraphGen/SymbolGraphASTWalker.cpp | 364 +++++++++++++++++ lib/SymbolGraphGen/SymbolGraphASTWalker.h | 161 ++++++++ lib/SymbolGraphGen/SymbolGraphGen.cpp | 56 +++ test/IDE/comment_brief.swift | 4 +- test/SymbolGraph/Module.swift | 20 + .../Relationships/ConformsTo.swift | 16 + .../DefaultImplementationOf.swift | 18 + .../Relationships/InheritsFrom.swift | 11 + test/SymbolGraph/Relationships/MemberOf.swift | 12 + .../SymbolGraph/Relationships/Overrides.swift | 20 + .../Relationships/RequirementOf.swift | 12 + .../Relationships/TargetFallback.swift | 16 + .../AccessLevelFilter/IncludeInternal.swift | 20 + .../AccessLevelFilter/PublicDefault.swift | 15 + test/SymbolGraph/Symbols/AccessLevels.swift | 12 + test/SymbolGraph/Symbols/DocComment.swift | 54 +++ test/SymbolGraph/Symbols/Kinds.swift | 42 ++ .../Mixins/Availability/Availability.swift | 24 ++ .../UnconditionallyDeprecated.swift | 10 + .../UnconditionallyUnavailable.swift | 10 + .../Symbols/Mixins/DeclarationFragments.swift | 54 +++ .../Symbols/Mixins/FunctionSignature.swift | 37 ++ test/SymbolGraph/Symbols/Names.swift | 9 + .../Symbols/SkipsPublicUnderscore.swift | 11 + test/lit.cfg | 17 + tools/driver/CMakeLists.txt | 14 +- tools/driver/driver.cpp | 6 + .../driver/swift_symbolgraph_extract_main.cpp | 177 ++++++++ 49 files changed, 2574 insertions(+), 43 deletions(-) create mode 100644 include/swift/SymbolGraphGen/SymbolGraphGen.h create mode 100644 lib/SymbolGraphGen/CMakeLists.txt create mode 100644 lib/SymbolGraphGen/DeclarationFragmentPrinter.cpp create mode 100644 lib/SymbolGraphGen/DeclarationFragmentPrinter.h create mode 100644 lib/SymbolGraphGen/Edge.cpp create mode 100644 lib/SymbolGraphGen/Edge.h create mode 100644 lib/SymbolGraphGen/FormatVersion.h create mode 100644 lib/SymbolGraphGen/JSON.cpp create mode 100644 lib/SymbolGraphGen/JSON.h create mode 100644 lib/SymbolGraphGen/Symbol.cpp create mode 100644 lib/SymbolGraphGen/Symbol.h create mode 100644 lib/SymbolGraphGen/SymbolGraph.cpp create mode 100644 lib/SymbolGraphGen/SymbolGraph.h create mode 100644 lib/SymbolGraphGen/SymbolGraphASTWalker.cpp create mode 100644 lib/SymbolGraphGen/SymbolGraphASTWalker.h create mode 100644 lib/SymbolGraphGen/SymbolGraphGen.cpp create mode 100644 test/SymbolGraph/Module.swift create mode 100644 test/SymbolGraph/Relationships/ConformsTo.swift create mode 100644 test/SymbolGraph/Relationships/DefaultImplementationOf.swift create mode 100644 test/SymbolGraph/Relationships/InheritsFrom.swift create mode 100644 test/SymbolGraph/Relationships/MemberOf.swift create mode 100644 test/SymbolGraph/Relationships/Overrides.swift create mode 100644 test/SymbolGraph/Relationships/RequirementOf.swift create mode 100644 test/SymbolGraph/Relationships/TargetFallback.swift create mode 100644 test/SymbolGraph/Symbols/AccessLevelFilter/IncludeInternal.swift create mode 100644 test/SymbolGraph/Symbols/AccessLevelFilter/PublicDefault.swift create mode 100644 test/SymbolGraph/Symbols/AccessLevels.swift create mode 100644 test/SymbolGraph/Symbols/DocComment.swift create mode 100644 test/SymbolGraph/Symbols/Kinds.swift create mode 100644 test/SymbolGraph/Symbols/Mixins/Availability/Availability.swift create mode 100644 test/SymbolGraph/Symbols/Mixins/Availability/UnconditionallyDeprecated.swift create mode 100644 test/SymbolGraph/Symbols/Mixins/Availability/UnconditionallyUnavailable.swift create mode 100644 test/SymbolGraph/Symbols/Mixins/DeclarationFragments.swift create mode 100644 test/SymbolGraph/Symbols/Mixins/FunctionSignature.swift create mode 100644 test/SymbolGraph/Symbols/Names.swift create mode 100644 test/SymbolGraph/Symbols/SkipsPublicUnderscore.swift create mode 100644 tools/driver/swift_symbolgraph_extract_main.cpp diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index d8ba393ded6fb..57514cb26bd90 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -875,6 +875,8 @@ class alignas(1 << DeclAlignInBits) Decl { LLVM_READONLY const GenericContext *getAsGenericContext() const; + bool hasUnderscoredNaming() const; + bool isPrivateStdlibDecl(bool treatNonBuiltinProtocolsAsPublic = true) const; AvailabilityContext getAvailabilityForLinkage() const; diff --git a/include/swift/AST/PrintOptions.h b/include/swift/AST/PrintOptions.h index 6a142e02da510..55aff0da73aca 100644 --- a/include/swift/AST/PrintOptions.h +++ b/include/swift/AST/PrintOptions.h @@ -380,6 +380,13 @@ struct PrintOptions { ArgAndParamPrintingMode ArgAndParamPrinting = ArgAndParamPrintingMode::MatchSource; + /// Whether to print the default argument value string + /// representation. + bool PrintDefaultArgumentValue = true; + + /// Whether to print "_" placeholders for empty arguments. + bool PrintEmptyArgumentNames = true; + /// Whether to print documentation comments attached to declarations. /// Note that this may print documentation comments from related declarations /// (e.g. the overridden method in the superclass) if such comment is found. diff --git a/include/swift/Driver/Driver.h b/include/swift/Driver/Driver.h index b72397379bb68..d927f3908a14f 100644 --- a/include/swift/Driver/Driver.h +++ b/include/swift/Driver/Driver.h @@ -158,7 +158,8 @@ class Driver { Interactive, // swift Batch, // swiftc AutolinkExtract, // swift-autolink-extract - SwiftIndent // swift-indent + SwiftIndent, // swift-indent + SymbolGraph // swift-symbolgraph }; class InputInfoMap; diff --git a/include/swift/SymbolGraphGen/SymbolGraphGen.h b/include/swift/SymbolGraphGen/SymbolGraphGen.h new file mode 100644 index 0000000000000..324a505c45e39 --- /dev/null +++ b/include/swift/SymbolGraphGen/SymbolGraphGen.h @@ -0,0 +1,41 @@ +//===--- swift_indent_main.cpp - Swift code formatting tool ---------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/Triple.h" +#include "swift/AST/AttrKind.h" + +namespace swift { + +class ModuleDecl; + +namespace symbolgraphgen { + +struct SymbolGraphOptions { + /// The path to output the symbol graph JSON. + StringRef OutputPath; + + /// The target of the module. + llvm::Triple Target; + + /// Pretty-print the JSON with newlines and indentation. + bool PrettyPrint; + + /// The minimum access level that symbols must have in order to be + /// included in the graph. + AccessLevel MinimumAccessLevel; +}; + +/// Emit a Symbol Graph JSON file for a module. +int emitSymbolGraphForModule(ModuleDecl *M, const SymbolGraphOptions &Options); + +} // end namespace symbolgraphgen +} // end namespace swift diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 99a0ca8c982a8..992f27e7b8c19 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -2632,6 +2632,9 @@ void PrintAST::printOneParameter(const ParamDecl *param, // Else, print the argument only. LLVM_FALLTHROUGH; case PrintOptions::ArgAndParamPrintingMode::ArgumentOnly: + if (ArgName.empty() && !Options.PrintEmptyArgumentNames) { + return; + } Printer.printName(ArgName, PrintNameContext::FunctionParameterExternal); if (!ArgNameIsAPIByDefault && !ArgName.empty()) @@ -2686,7 +2689,7 @@ void PrintAST::printOneParameter(const ParamDecl *param, if (param->isVariadic()) Printer << "..."; - if (param->isDefaultArgument()) { + if (param->isDefaultArgument() && Options.PrintDefaultArgumentValue) { SmallString<128> scratch; auto defaultArgStr = param->getDefaultValueStringRepresentation(scratch); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 73b488298f153..ecc8f250d82ed 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -697,6 +697,56 @@ bool ParameterList::hasInternalParameter(StringRef Prefix) const { return false; } +bool Decl::hasUnderscoredNaming() const { + const Decl *D = this; + if (const auto AFD = dyn_cast(D)) { + // If it's a function with a parameter with leading underscore, it's a + // private function. + if (AFD->getParameters()->hasInternalParameter("_")) { + return true; + } + } + + if (const auto SubscriptD = dyn_cast(D)) { + if (SubscriptD->getIndices()->hasInternalParameter("_")) { + return true; + } + } + + if (const auto PD = dyn_cast(D)) { + if (PD->getAttrs().hasAttribute()) { + return false; + } + StringRef NameStr = PD->getNameStr(); + if (NameStr.startswith("_Builtin")) { + return true; + } + if (NameStr.startswith("_ExpressibleBy")) { + return true; + } + } + + if (const auto ImportD = dyn_cast(D)) { + if (const auto *Mod = ImportD->getModule()) { + if (Mod->isSwiftShimsModule()) { + return true; + } + } + } + + const auto VD = dyn_cast(D); + if (!VD || !VD->hasName()) { + return false; + } + + if (!VD->getBaseName().isSpecial() && + VD->getBaseName().getIdentifier().str().startswith("_")) { + return true; + } + + return false; +} + bool Decl::isPrivateStdlibDecl(bool treatNonBuiltinProtocolsAsPublic) const { const Decl *D = this; if (auto ExtD = dyn_cast(D)) { @@ -718,47 +768,12 @@ bool Decl::isPrivateStdlibDecl(bool treatNonBuiltinProtocolsAsPublic) const { FU->getKind() != FileUnitKind::SerializedAST) return false; - if (auto AFD = dyn_cast(D)) { - // If it's a function with a parameter with leading underscore, it's a - // private function. - if (AFD->getParameters()->hasInternalParameter("_")) - return true; - } - - if (auto SubscriptD = dyn_cast(D)) { - if (SubscriptD->getIndices()->hasInternalParameter("_")) - return true; - } - if (auto PD = dyn_cast(D)) { - if (PD->getAttrs().hasAttribute()) - return false; - StringRef NameStr = PD->getNameStr(); - if (NameStr.startswith("_Builtin")) - return true; - if (NameStr.startswith("_ExpressibleBy")) - return true; if (treatNonBuiltinProtocolsAsPublic) return false; } - if (auto ImportD = dyn_cast(D)) { - if (auto *Mod = ImportD->getModule()) { - if (Mod->isSwiftShimsModule()) - return true; - } - } - - auto VD = dyn_cast(D); - if (!VD || !VD->hasName()) - return false; - - // If the name has leading underscore then it's a private symbol. - if (!VD->getBaseName().isSpecial() && - VD->getBaseName().getIdentifier().str().startswith("_")) - return true; - - return false; + return hasUnderscoredNaming(); } AvailabilityContext Decl::getAvailabilityForLinkage() const { diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b06020ecb4f44..e3a21225fdb66 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -42,6 +42,7 @@ add_subdirectory(SwiftRemoteMirror) add_subdirectory(SIL) add_subdirectory(SILGen) add_subdirectory(SILOptimizer) +add_subdirectory(SymbolGraphGen) add_subdirectory(Syntax) add_subdirectory(SyntaxParse) add_subdirectory(TBDGen) diff --git a/lib/Driver/Driver.cpp b/lib/Driver/Driver.cpp index e3a6e2a3361cf..34db9ebb425d8 100644 --- a/lib/Driver/Driver.cpp +++ b/lib/Driver/Driver.cpp @@ -98,6 +98,7 @@ void Driver::parseDriverKind(ArrayRef Args) { .Case("swiftc", DriverKind::Batch) .Case("swift-autolink-extract", DriverKind::AutolinkExtract) .Case("swift-indent", DriverKind::SwiftIndent) + .Case("swift-symbolgraph-extract", DriverKind::SymbolGraph) .Default(None); if (Kind.hasValue()) @@ -3252,6 +3253,7 @@ void Driver::printHelp(bool ShowHidden) const { case DriverKind::Batch: case DriverKind::AutolinkExtract: case DriverKind::SwiftIndent: + case DriverKind::SymbolGraph: ExcludedFlagsBitmask |= options::NoBatchOption; break; } diff --git a/lib/Markup/LineList.cpp b/lib/Markup/LineList.cpp index 0afeb5eec995d..a89488b8c4213 100644 --- a/lib/Markup/LineList.cpp +++ b/lib/Markup/LineList.cpp @@ -115,8 +115,6 @@ LineList MarkupContext::getLineList(swift::RawComment RC) { // Determine if we have leading decorations in this block comment. bool HasASCIIArt = false; if (swift::startsWithNewline(Cleaned)) { - Builder.addLine(Cleaned.substr(0, 0), { C.Range.getStart(), - C.Range.getStart() }); unsigned NewlineBytes = swift::measureNewline(Cleaned); Cleaned = Cleaned.drop_front(NewlineBytes); CleanedStartLoc = CleanedStartLoc.getAdvancedLocOrInvalid(NewlineBytes); diff --git a/lib/SymbolGraphGen/CMakeLists.txt b/lib/SymbolGraphGen/CMakeLists.txt new file mode 100644 index 0000000000000..2780330587fb2 --- /dev/null +++ b/lib/SymbolGraphGen/CMakeLists.txt @@ -0,0 +1,13 @@ +add_swift_host_library(swiftSymbolGraphGen STATIC + DeclarationFragmentPrinter.cpp + Edge.cpp + JSON.cpp + Symbol.cpp + SymbolGraph.cpp + SymbolGraphGen.cpp + SymbolGraphASTWalker.cpp) + +target_link_libraries(swiftSymbolGraphGen + swiftAST + swiftFrontend + swiftMarkup) diff --git a/lib/SymbolGraphGen/DeclarationFragmentPrinter.cpp b/lib/SymbolGraphGen/DeclarationFragmentPrinter.cpp new file mode 100644 index 0000000000000..2e8db85e2d3b2 --- /dev/null +++ b/lib/SymbolGraphGen/DeclarationFragmentPrinter.cpp @@ -0,0 +1,140 @@ +//===--- DeclarationFragmentPrinter.cpp - Declaration Fragment Printer ----===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "DeclarationFragmentPrinter.h" +#include "SymbolGraphASTWalker.h" + +using namespace swift; +using namespace symbolgraphgen; + +void DeclarationFragmentPrinter::openFragment(FragmentKind Kind) { + assert(Kind != FragmentKind::None); + if (this->Kind != Kind) { + closeFragment(); + this->Kind = Kind, + Spelling.clear(); + USR.clear(); + } +} + +StringRef +DeclarationFragmentPrinter::getKindSpelling(FragmentKind Kind) const { + switch (Kind) { + case FragmentKind::Keyword: + return "keyword"; + case FragmentKind::Attribute: + return "attribute"; + case FragmentKind::NumberLiteral: + return "number"; + case FragmentKind::StringLiteral: + return "string"; + case FragmentKind::Identifier: + return "identifier"; + case FragmentKind::TypeIdentifier: + return "typeIdentifier"; + case FragmentKind::GenericParameter: + return "genericParameter"; + case FragmentKind::Text: + return "text"; + case FragmentKind::None: + llvm_unreachable("Fragment kind of 'None' has no spelling"); + } +} + +void DeclarationFragmentPrinter::closeFragment() { + if (Kind == FragmentKind::None) { + return; + } + + if (!Spelling.empty()) { + OS.object([&](){ + OS.attribute("kind", getKindSpelling(Kind)); + OS.attribute("spelling", Spelling.str()); + if (!USR.empty()) { + OS.attribute("preciseIdentifier", USR.str()); + } + }); + } + + Spelling.clear(); + USR.clear(); + Kind = FragmentKind::None; +} + +void DeclarationFragmentPrinter::printDeclLoc(const Decl *D) { + switch (D->getKind()) { + case DeclKind::Constructor: + case DeclKind::Destructor: + case DeclKind::Subscript: + openFragment(FragmentKind::Keyword); + break; + default: + openFragment(FragmentKind::Identifier); + break; + } +} + +void +DeclarationFragmentPrinter::printNamePre(PrintNameContext Context) { + switch (Context) { + case PrintNameContext::Keyword: + openFragment(FragmentKind::Keyword); + break; + case PrintNameContext::GenericParameter: + openFragment(FragmentKind::GenericParameter); + break; + case PrintNameContext::Attribute: + openFragment(FragmentKind::Attribute); + break; + case PrintNameContext::ClassDynamicSelf: + case PrintNameContext::FunctionParameterExternal: + openFragment(FragmentKind::Identifier); + break; + case PrintNameContext::FunctionParameterLocal: + openFragment(FragmentKind::Identifier); + break; + case PrintNameContext::TupleElement: + case PrintNameContext::TypeMember: + case PrintNameContext::Normal: + break; + } +} + +void DeclarationFragmentPrinter::printStructurePre(PrintStructureKind Kind, + const Decl *D) { + switch (Kind) { + case PrintStructureKind::NumberLiteral: + openFragment(FragmentKind::NumberLiteral); + break; + case PrintStructureKind::StringLiteral: + openFragment(FragmentKind::StringLiteral); + break; + default: + break; + } +} + +void DeclarationFragmentPrinter::printTypeRef(Type T, const TypeDecl *RefTo, + Identifier Name, + PrintNameContext NameContext) { + openFragment(FragmentKind::TypeIdentifier); + printText(Name.str()); + USR = Walker.getUSR(RefTo); + closeFragment(); +} + +void DeclarationFragmentPrinter::printText(StringRef Text) { + if (Kind == FragmentKind::None) { + openFragment(FragmentKind::Text); + } + Spelling.append(Text); +} diff --git a/lib/SymbolGraphGen/DeclarationFragmentPrinter.h b/lib/SymbolGraphGen/DeclarationFragmentPrinter.h new file mode 100644 index 0000000000000..2e5ec449bd34f --- /dev/null +++ b/lib/SymbolGraphGen/DeclarationFragmentPrinter.h @@ -0,0 +1,123 @@ +//===--- DeclarationFragmentPrinter.h - Declaration Fragment Printer ------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SYMBOLGRAPHGEN_DECLARATIONFRAGMENTPRINTER_H +#define SWIFT_SYMBOLGRAPHGEN_DECLARATIONFRAGMENTPRINTER_H + +#include "llvm/Support/JSON.h" +#include "swift/AST/ASTPrinter.h" +#include "swift/Basic/LLVM.h" + +namespace swift { + +class Decl; +class Type; +class TypeDecl; + +namespace symbolgraphgen { + +struct SymbolGraphASTWalker; + +/// Prints AST nodes as a stream of tagged fragments for syntax highlighting. +/// +/// These fragments are meant to display a somewhat abbreviated part of the +/// declaration for display in documentation, ignoring things like member and +/// function bodies. +/// +/// For example, a function: +/// +/// ```swift +/// func foo() { +/// print("Hello, world!") +/// } +/// ``` +/// +/// Will have fragments representing the `func foo()` part. +class DeclarationFragmentPrinter : public ASTPrinter { + enum class FragmentKind { + None, + Keyword, + Attribute, + NumberLiteral, + StringLiteral, + Identifier, + TypeIdentifier, + GenericParameter, + Text, + }; + + SymbolGraphASTWalker &Walker; + + /// The output stream to print fragment objects to. + llvm::json::OStream &OS; + + /// The current fragment being considered. + FragmentKind Kind; + + /// The actual source text of the fragment. + SmallString<256> Spelling; + + SmallString<256> USR; + + StringRef getKindSpelling(FragmentKind Kind) const; + + /// Open a new kind of fragment without committing its spelling. + void openFragment(FragmentKind Kind); + + /// Close the current fragment if there is one, and commit it for display. + void closeFragment(); + +public: + DeclarationFragmentPrinter(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS, + Optional Key = None) + : Walker(Walker), + OS(OS), + Kind(FragmentKind::None) { + if (Key) { + OS.attributeBegin(*Key); + OS.arrayBegin(); + } else { + OS.arrayBegin(); + } + } + + void printDeclLoc(const Decl *D) override; + + void printDeclNameEndLoc(const Decl *D) override { + closeFragment(); + } + + void printNamePre(PrintNameContext Context) override; + + void printStructurePre(PrintStructureKind Kind, const Decl *D) override; + + void printNamePost(PrintNameContext Context) override { + closeFragment(); + } + + void printTypeRef(Type T, const TypeDecl *RefTo, Identifier Name, + PrintNameContext NameContext) override; + + void printText(StringRef Text) override; + + ~DeclarationFragmentPrinter() { + closeFragment(); + OS.arrayEnd(); + OS.attributeEnd(); + } +}; + +} // end namespace symbolgraphgen +} // end namespace swift + +#endif // SWIFT_SYMBOLGRAPHGEN_DECLARATIONFRAGMENTPRINTER_H diff --git a/lib/SymbolGraphGen/Edge.cpp b/lib/SymbolGraphGen/Edge.cpp new file mode 100644 index 0000000000000..296cc0960fee2 --- /dev/null +++ b/lib/SymbolGraphGen/Edge.cpp @@ -0,0 +1,40 @@ +//===--- Edge.cpp - Symbol Graph Edge -------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "swift/AST/Module.h" +#include "Edge.h" +#include "SymbolGraphASTWalker.h" + +using namespace swift; +using namespace symbolgraphgen; + +void Edge::serialize(llvm::json::OStream &OS) const { + OS.object([&](){ + OS.attribute("kind", Kind.Name); + OS.attribute("source", Walker->getUSR(Source)); + OS.attribute("target", Walker->getUSR(Target)); + + // In case a dependent module isn't available, serialize a fallback name. + auto TargetModuleName = Target->getModuleContext()->getName().str(); + if (TargetModuleName != Walker->M.getName().str()) { + auto TargetSymbolIdentifier = Walker->getSymbolIdentifier(Target); + auto TargetComponents = TargetSymbolIdentifier.SimpleComponents; + SmallString<128> Scratch(TargetModuleName); + for (auto it = TargetComponents.begin(); + it != TargetComponents.end(); ++it) { + Scratch.push_back('.'); + Scratch.append(*it); + } + OS.attribute("targetFallback", Scratch.str()); + } + }); +} diff --git a/lib/SymbolGraphGen/Edge.h b/lib/SymbolGraphGen/Edge.h new file mode 100644 index 0000000000000..e22d2880151a7 --- /dev/null +++ b/lib/SymbolGraphGen/Edge.h @@ -0,0 +1,167 @@ +//===--- Edge.h - Symbol Graph Edge ---------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SYMBOLGRAPHGEN_EDGE_H +#define SWIFT_SYMBOLGRAPHGEN_EDGE_H + +#include "llvm/Support/JSON.h" +#include "swift/AST/Decl.h" +#include "swift/Basic/LLVM.h" + +#include "JSON.h" +#include "Symbol.h" + +namespace swift { +namespace symbolgraphgen { + +/// The kind of relationship, tagging an edge in the graph. +struct RelationshipKind { + StringRef Name; + + RelationshipKind(llvm::StringRef Name) : Name(Name) {} + + /** + A symbol A is a member of another symbol B. + + For example, a method or field of a class would be a member of that class. + + The implied inverse of this relationship is a symbol B is the owner + of a member symbol A. + */ + static inline RelationshipKind MemberOf() { + return RelationshipKind { "memberOf" }; + } + + /** + A symbol A conforms to an interface/protocol symbol B. + + For example, a class `C` that conforms to protocol `P` in Swift would use + this relationship. + + The implied inverse of this relationship is a symbol B that has + a conformer A. + */ + static inline RelationshipKind ConformsTo() { + return RelationshipKind { "conformsTo" }; + } + /** + A symbol A inherits from another symbol B. + + For example, a derived class inherits from a base class, or a protocol + that refines another protocol would use this relationship. + + The implied inverse of this relationship is a symbol B is a base + of another symbol A. + */ + static inline RelationshipKind InheritsFrom() { + return RelationshipKind { "inheritsFrom" }; + } + /** + A symbol A serves as a default implementation of an interface requirement B. + + The implied inverse of this relationship is an interface requirement B + has a default implementation of A. + */ + static inline RelationshipKind DefaultImplementationOf() { + return RelationshipKind { "defaultImplementationOf" }; + } + /** + A symbol A overrides another symbol B, such as through inheritance. + + The implied inverse of this relationship is a symbol A is the base + of symbol B. + */ + static inline RelationshipKind Overrides() { + return RelationshipKind { "overrides" }; + } + /** + A symbol A is a requirement of interface B. + + The implied inverse of this relationship is an interface B + has a requirement of A. + */ + static inline RelationshipKind RequirementOf() { + return RelationshipKind { "requirementOf" }; + } + /** + A symbol A is an optional requirement of interface B. + + The implied inverse of this relationship is an interface B + has an optional requirement of A. + */ + static inline RelationshipKind OptionalRequirementOf() { + return RelationshipKind { "optionalRequirementOf" }; + } + + bool operator==(const RelationshipKind &Other) const { + return Name == Other.Name; + } + + bool operator<(const RelationshipKind &Other) const { + return Name < Other.Name; + } +}; + +/// A relationship between two symbols: an edge in a directed graph. +struct Edge { + SymbolGraphASTWalker *Walker; + + /// The kind of relationship this edge represents. + RelationshipKind Kind; + + /// The precise identifier of the source symbol node. + const ValueDecl *Source; + + /// The precise identifier of the target symbol node. + const ValueDecl *Target; + + void serialize(llvm::json::OStream &OS) const; +}; + +} // end namespace symbolgraphgen +} // end namespace swift + +namespace llvm { +using Edge = swift::symbolgraphgen::Edge; +template <> struct DenseMapInfo { + static inline Edge getEmptyKey() { + return { + nullptr, + { "Empty" }, + nullptr, + nullptr, + }; + } + static inline Edge getTombstoneKey() { + return { + nullptr, + { "Tombstone" }, + nullptr, + nullptr, + }; + } + static unsigned getHashValue(const Edge E) { + unsigned H = 0; + H ^= DenseMapInfo::getHashValue(E.Kind.Name); + H ^= DenseMapInfo::getHashValue(reinterpret_cast(E.Source)); + H ^= DenseMapInfo::getHashValue(reinterpret_cast(E.Target)); + return H; + } + static bool isEqual(const Edge LHS, const Edge RHS) { + return LHS.Kind == RHS.Kind && + LHS.Source == RHS.Source && + LHS.Target == RHS.Target; + } +}; +} // end namespace llvm + +#endif // SWIFT_SYMBOLGRAPHGEN_EDGE_H diff --git a/lib/SymbolGraphGen/FormatVersion.h b/lib/SymbolGraphGen/FormatVersion.h new file mode 100644 index 0000000000000..74ac1c196c5b4 --- /dev/null +++ b/lib/SymbolGraphGen/FormatVersion.h @@ -0,0 +1,20 @@ +//===--- FormatVersion.h - Symbol Graph Format Version 00------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SYMBOLGRAPHGEN_FORMATVERSION_H +#define SWIFT_SYMBOLGRAPHGEN_FORMATVERSION_H + +#define SWIFT_SYMBOLGRAPH_FORMAT_MAJOR 0 +#define SWIFT_SYMBOLGRAPH_FORMAT_MINOR 1 +#define SWIFT_SYMBOLGRAPH_FORMAT_PATCH 0 + +#endif // SWIFT_SYMBOLGRAPHGEN_FORMATVERSION_H diff --git a/lib/SymbolGraphGen/JSON.cpp b/lib/SymbolGraphGen/JSON.cpp new file mode 100644 index 0000000000000..6bb117daaeeb4 --- /dev/null +++ b/lib/SymbolGraphGen/JSON.cpp @@ -0,0 +1,57 @@ +//===--- JSON.cpp - Symbol Graph JSON Helpers -----------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// Adds Symbol Graph JSON serialization to other types. +//===----------------------------------------------------------------------===// + +#include "JSON.h" + +void swift::symbolgraphgen::serialize(const llvm::VersionTuple &VT, + llvm::json::OStream &OS) { + OS.object([&](){ + OS.attribute("major", VT.getMajor()); + if (VT.getMinor()) { + OS.attribute("minor", *VT.getMinor()); + } + if (VT.getSubminor()) { + OS.attribute("patch", *VT.getSubminor()); + } + // Despite the name, + // this is not Semantic Versioning "build metadata" + if (VT.getBuild()) { + OS.attribute("prerelease", *VT.getBuild()); + } + }); +} + +void swift::symbolgraphgen::serialize(const llvm::Triple &T, + llvm::json::OStream &OS) { + OS.object([&](){ + OS.attribute("architecture", T.getArchName()); + if (!T.getEnvironmentName().empty()) { + OS.attribute("environment", T.getEnvironmentName()); + } + OS.attribute("vendor", T.getVendorName()); + OS.attributeObject("operatingSystem", [&](){ + OS.attribute("name", T.getOSTypeName(T.getOS())); + + unsigned Major; + unsigned Minor; + unsigned Patch; + T.getOSVersion(Major, Minor, Patch); + llvm::VersionTuple OSVersion(Major, Minor, Patch); + + OS.attributeBegin("minimumVersion"); + serialize(OSVersion, OS); + OS.attributeEnd(); + }); + }); +} diff --git a/lib/SymbolGraphGen/JSON.h b/lib/SymbolGraphGen/JSON.h new file mode 100644 index 0000000000000..0c0018da724cb --- /dev/null +++ b/lib/SymbolGraphGen/JSON.h @@ -0,0 +1,45 @@ +//===--- JSON.h - Symbol Graph JSON Helpers -------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// Adds Symbol Graph JSON serialization to other types. +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SYMBOLGRAPHGEN_JSON_H +#define SWIFT_SYMBOLGRAPHGEN_JSON_H + +#include "llvm/ADT/Triple.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/VersionTuple.h" +#include "swift/AST/GenericSignature.h" + +namespace swift { +namespace symbolgraphgen { + +struct AttributeRAII { + StringRef Key; + llvm::json::OStream &OS; + AttributeRAII(StringRef Key, llvm::json::OStream &OS) + : Key(Key), OS(OS) { + OS.attributeBegin(Key); + } + + ~AttributeRAII() { + OS.attributeEnd(); + } +}; + +void serialize(const llvm::VersionTuple &VT, llvm::json::OStream &OS); +void serialize(const llvm::Triple &T, llvm::json::OStream &OS); + +} // end namespace symbolgraphgen +} // end namespace swift + +#endif // SWIFT_SYMBOLGRAPHGEN_JSON_H diff --git a/lib/SymbolGraphGen/Symbol.cpp b/lib/SymbolGraphGen/Symbol.cpp new file mode 100644 index 0000000000000..441a636454051 --- /dev/null +++ b/lib/SymbolGraphGen/Symbol.cpp @@ -0,0 +1,380 @@ +//===--- Symbol.cpp - Symbol Graph Node -----------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "swift/AST/ASTContext.h" +#include "swift/AST/Module.h" +#include "swift/AST/ParameterList.h" +#include "swift/Basic/SourceManager.h" +#include "JSON.h" +#include "Symbol.h" +#include "SymbolGraph.h" +#include "SymbolGraphASTWalker.h" + +using namespace swift; +using namespace symbolgraphgen; + +void Symbol::serializeKind(StringRef Identifier, StringRef DisplayName, + llvm::json::OStream &OS) const { + OS.object([&](){ + OS.attribute("identifier", Identifier); + OS.attribute("displayName", DisplayName); + }); +} + +void Symbol::serializeKind(llvm::json::OStream &OS) const { + AttributeRAII A("kind", OS); + switch (VD->getKind()) { + case swift::DeclKind::Class: + serializeKind("swift.class", "Class", OS); + break; + case swift::DeclKind::Struct: + serializeKind("swift.struct", "Structure", OS); + break; + case swift::DeclKind::Enum: + serializeKind("swift.enum", "Enumeration", OS); + break; + case swift::DeclKind::EnumElement: + serializeKind("swift.enum.case", "Case", OS); + break; + case swift::DeclKind::Protocol: + serializeKind("swift.protocol", "Protocol", OS); + break; + case swift::DeclKind::Constructor: + serializeKind("swift.initializer", "Initializer", OS); + break; + case swift::DeclKind::Func: + serializeKind("swift.function", "Function", OS); + break; + case swift::DeclKind::Var: + serializeKind("swift.variable", "Variable", OS); + break; + case swift::DeclKind::TypeAlias: + serializeKind("swift.typealias", "Type Alias", OS); + break; + case swift::DeclKind::InfixOperator: + serializeKind("swift.infixOperator", "Infix Operator", OS); + break; + case swift::DeclKind::PrefixOperator: + serializeKind("swift.prefixOperator", "Prefix Operator", OS); + break; + case swift::DeclKind::PostfixOperator: + serializeKind("swift.postfixOperator", "Postfix Operator", OS); + break; + default: + llvm_unreachable("Unsupported declaration kind for symbol graph"); + } +} + +void Symbol::serializeIdentifier(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const { + AttributeRAII A("identifier", OS); + Walker.getSymbolIdentifier(VD).serialize(OS); +} + +void Symbol::serializeNames(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const { + OS.attributeObject("names", [&](){ + auto Identifier = Walker.getSymbolIdentifier(VD); + OS.attribute("title", Identifier.SimpleComponents.back()); + // "navigator": null + Walker.serializeSubheadingDeclarationFragments("subheading", VD, OS); + // "prose": null + }); +} + +void Symbol::serializePosition(StringRef Key, + unsigned Line, unsigned ByteOffset, + llvm::json::OStream &OS) const { + OS.attributeObject(Key, [&](){ + OS.attribute("line", Line); + OS.attribute("character", ByteOffset); + }); +} + +void Symbol::serializeRange(size_t InitialIndentation, + SourceRange Range, SourceManager &SourceMgr, + llvm::json::OStream &OS) const { + OS.attributeObject("range", [&](){ + auto StartLineAndColumn = SourceMgr.getLineAndColumn(Range.Start); + auto StartLine = StartLineAndColumn.first; + auto StartColumn = StartLineAndColumn.second + InitialIndentation; + serializePosition("start", StartLine, StartColumn, OS); + + auto EndLineAndColumn = SourceMgr.getLineAndColumn(Range.End); + auto EndLine = EndLineAndColumn.first; + auto EndColumn = EndLineAndColumn.second + InitialIndentation; + serializePosition("end", EndLine, EndColumn, OS); + }); +} + +void Symbol::serializeDocComment(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const { + OS.attributeObject("docComment", [&](){ + auto LL = Walker.Ctx.getLineList(VD->getRawComment()); + size_t InitialIndentation = LL.getLines().empty() + ? 0 + : markup::measureIndentation(LL.getLines().front().Text); + OS.attributeArray("lines", [&](){ + for (const auto &Line : LL.getLines()) { + // Line object + OS.object([&](){ + // Trim off any initial indentation from the line's + // text and start of its source range, if it has one. + if (Line.Range.isValid()) { + serializeRange(InitialIndentation, + Line.Range, Walker.M.getASTContext().SourceMgr, OS); + } + auto TrimmedLine = Line.Text.drop_front(std::min(InitialIndentation, + Line.FirstNonspaceOffset)); + OS.attribute("text", TrimmedLine); + }); + } + }); // end lines: [] + }); // end docComment: +} + +void Symbol::serializeFunctionSignature(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const { + if (const auto *FD = dyn_cast_or_null(VD)) { + OS.attributeObject("functionSignature", [&](){ + + // Parameters + if (const auto *ParamList = FD->getParameters()) { + if (ParamList->size()) { + OS.attributeArray("parameters", [&](){ + for (const auto *Param : *ParamList) { + auto ExternalName = Param->getArgumentName().str(); + auto InternalName = Param->getParameterName().str(); + + OS.object([&](){ + if (ExternalName.empty()) { + OS.attribute("name", InternalName); + } else { + OS.attribute("name", ExternalName); + if (ExternalName != InternalName && + !InternalName.empty()) { + OS.attribute("internalName", InternalName); + } + } + Walker.serializeDeclarationFragments("declarationFragments", + Param, OS); + }); // end parameter object + } + }); // end parameters: + } + } + + // Returns + if (const auto ReturnType = FD->getResultInterfaceType()) { + Walker.serializeDeclarationFragments("returns", ReturnType, OS); + } + }); + } +} + +void Symbol::serializeGenericParam(const swift::GenericTypeParamType &Param, + llvm::json::OStream &OS) const { + OS.object([&](){ + OS.attribute("name", Param.getName().str()); + OS.attribute("index", Param.getIndex()); + OS.attribute("depth", Param.getDepth()); + }); +} + +void Symbol::serializeGenericRequirement(const swift::Requirement &Req, + llvm::json::OStream &OS) const { + OS.object([&](){ + switch (Req.getKind()) { + case swift::RequirementKind::Conformance: + OS.attribute("kind", "conformance"); + break; + case swift::RequirementKind::Superclass: + OS.attribute("kind", "superclass"); + break; + case swift::RequirementKind::SameType: + OS.attribute("kind", "sameType"); + break; + case swift::RequirementKind::Layout: + return; + } + OS.attribute("lhs", Req.getFirstType()->getString()); + OS.attribute("rhs", Req.getSecondType()->getString()); + }); +} + +void Symbol::serializeSwiftGenericMixin(llvm::json::OStream &OS) const { + if (const auto *GC = VD->getAsGenericContext()) { + if (const auto Generics = GC->getGenericSignature()) { + + OS.attributeObject("swiftGenerics", [&](){ + if (!Generics->getGenericParams().empty()) { + OS.attributeArray("parameters", [&](){ + for (const auto Param : Generics->getGenericParams()) { + if (const auto *D = Param->getDecl()) { + if (D->isImplicit()) { + continue; + } + } + serializeGenericParam(*Param, OS); + } + }); // end parameters: + } + + if (!Generics->getRequirements().empty()) { + OS.attributeArray("constraints", [&](){ + for (const auto &Requirement : Generics->getRequirements()) { + serializeGenericRequirement(Requirement, OS); + } + }); // end constraints: + } + + }); // end swiftGenerics: + } + } +} + +void Symbol::serializeSwiftExtensionMixin(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const { + if (const auto *Extension + = dyn_cast_or_null(VD->getInnermostDeclContext())) { + OS.attributeObject("swiftExtension", [&](){ + OS.attribute("definedInModule", Walker.M.getNameStr()); + auto Generics = Extension->getGenericSignature(); + if (Generics && !Generics->getRequirements().empty()) { + OS.attributeArray("constraints", [&](){ + for (const auto &Requirement : Generics->getRequirements()) { + serializeGenericRequirement(Requirement, OS); + } + }); // end constraints: + } + }); // end swiftExtension: + } +} + +void Symbol::serializeDeclarationFragmentMixin(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const { + Walker.serializeDeclarationFragments("declarationFragments", VD, OS); +} + +void Symbol::serializeAccessLevelMixin(llvm::json::OStream &OS) const { + OS.attribute("accessLevel", getAccessLevelSpelling(VD->getFormalAccess())); +} + +llvm::Optional +Symbol::getDomain(PlatformAgnosticAvailabilityKind AgnosticKind, + PlatformKind Kind) const { + switch (AgnosticKind) { + // SPM- and Swift-specific availability. + case PlatformAgnosticAvailabilityKind::PackageDescriptionVersionSpecific: + return { "SwiftPM" }; + case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific: + case PlatformAgnosticAvailabilityKind::UnavailableInSwift: + return { "Swift" }; + // Although these are in the agnostic kinds, they are actually a signal + // that there is either platform-specific or completely platform-agnostic. + // They'll be handled below. + case PlatformAgnosticAvailabilityKind::Deprecated: + case PlatformAgnosticAvailabilityKind::Unavailable: + case PlatformAgnosticAvailabilityKind::None: + break; + } + + // Platform-specific availability. + switch (Kind) { + case swift::PlatformKind::iOS: + return { "iOS" }; + case swift::PlatformKind::OSX: + return { "macOS" }; + case swift::PlatformKind::tvOS: + return { "tvOS" }; + case swift::PlatformKind::watchOS: + return { "watchOS" }; + case swift::PlatformKind::iOSApplicationExtension: + return { "iOSAppExtension" }; + case swift::PlatformKind::OSXApplicationExtension: + return { "macOSAppExtension" }; + case swift::PlatformKind::tvOSApplicationExtension: + return { "tvOSAppExtension" }; + case swift::PlatformKind::watchOSApplicationExtension: + return { "watchOSAppExtension" }; + // Platform-agnostic availability, such as "unconditionally deprecated" + // or "unconditionally obsoleted". + case swift::PlatformKind::none: + return None; + } +} + +void Symbol::serializeAvailabilityMixin(llvm::json::OStream &OS) const { + SmallVector Availabilities; + for (const auto *Attr : VD->getAttrs()) { + if (const auto *AvAttr = dyn_cast(Attr)) { + Availabilities.push_back(AvAttr); + } + } + if (Availabilities.empty()) { + return; + } + + OS.attributeArray("availability", [&](){ + for (const auto *AvAttr : Availabilities) { + OS.object([&](){ + auto Domain = getDomain(AvAttr->getPlatformAgnosticAvailability(), + AvAttr->Platform); + if (Domain) { + OS.attribute("domain", *Domain); + } + if (AvAttr->Introduced) { + AttributeRAII Introduced("introduced", OS); + symbolgraphgen::serialize(*AvAttr->Introduced, OS); + } + if (AvAttr->Deprecated) { + AttributeRAII Deprecated("deprecated", OS); + symbolgraphgen::serialize(*AvAttr->Deprecated, OS); + } + if (AvAttr->Obsoleted) { + AttributeRAII Obsoleted("obsoleted", OS); + symbolgraphgen::serialize(*AvAttr->Obsoleted, OS); + } + if (!AvAttr->Message.empty()) { + OS.attribute("message", AvAttr->Message); + } + if (!AvAttr->Rename.empty()) { + OS.attribute("renamed", AvAttr->Rename); + } + if (AvAttr->isUnconditionallyDeprecated()) { + OS.attribute("isUnconditionallyDeprecated", true); + } + if (AvAttr->isUnconditionallyUnavailable()) { + OS.attribute("isUnconditionallyUnavailable", true); + } + }); // end availability object + } + }); // end availability: [] +} + +void Symbol::serialize(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const { + OS.object([&](){ + serializeKind(OS); + serializeIdentifier(Walker, OS); + serializeNames(Walker, OS); + serializeDocComment(Walker, OS); + + // "Mixins" + serializeFunctionSignature(Walker, OS); + serializeSwiftGenericMixin(OS); + serializeSwiftExtensionMixin(Walker, OS); + serializeDeclarationFragmentMixin(Walker, OS); + serializeAccessLevelMixin(OS); + serializeAvailabilityMixin(OS); + }); +} diff --git a/lib/SymbolGraphGen/Symbol.h b/lib/SymbolGraphGen/Symbol.h new file mode 100644 index 0000000000000..9cdbca5757164 --- /dev/null +++ b/lib/SymbolGraphGen/Symbol.h @@ -0,0 +1,128 @@ +//===--- Symbol.h- Symbol Graph Node --------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SYMBOLGRAPHGEN_SYMBOL_H +#define SWIFT_SYMBOLGRAPHGEN_SYMBOL_H + +#include "llvm/Support/JSON.h" +#include "swift/AST/Attr.h" +#include "swift/Basic/LLVM.h" +#include "swift/Markup/Markup.h" + +namespace swift { +namespace symbolgraphgen { + +struct AvailabilityDomain; +struct SymbolGraphASTWalker; + +/** + An identifier for a symbol that provides a globally unique identifier suitable for + internal lookups and a locally unique path for human use, such as a URL. + */ +struct SymbolIdentifier { + /** + A string that uniquely identifies a symbol within a module in the event of + ambiguities. A precise identifier need not be human readable. + */ + StringRef PreciseIdentifier; + + /** + The components for a "fully qualified" identifier. + */ + ArrayRef SimpleComponents; + + SymbolIdentifier(llvm::StringRef PreciseIdentifier, + ArrayRef SimpleComponents) + : PreciseIdentifier(PreciseIdentifier), + SimpleComponents(SimpleComponents) { + assert(!PreciseIdentifier.empty()); + } + + void serialize(llvm::json::OStream &OS) const { + OS.object([&](){ + OS.attribute("precise", PreciseIdentifier); + OS.attributeArray("simpleComponents", [&](){ + for (auto Component : SimpleComponents) { + OS.value(Component); + } + }); + }); + } + + bool operator==(const SymbolIdentifier &Other) const { + return PreciseIdentifier == Other.PreciseIdentifier && + SimpleComponents == Other.SimpleComponents; + } +}; + +/// A symbol from a module: a node in a graph. +struct Symbol { + const ValueDecl *VD; + + void serializeKind(StringRef Identifier, StringRef DisplayName, + llvm::json::OStream &OS) const; + + void serializeKind(llvm::json::OStream &OS) const; + + void serializeIdentifier(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const; + + void serializeNames(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const; + + void serializePosition(StringRef Key, unsigned Line, unsigned ByteOffset, + llvm::json::OStream &OS) const; + + void serializeRange(size_t InitialIdentation, + SourceRange Range, SourceManager &SourceMgr, + llvm::json::OStream &OS) const; + + void serializeDocComment(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const; + + void serializeFunctionSignature(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const; + + void serializeGenericParam(const swift::GenericTypeParamType &Param, + llvm::json::OStream &OS) const; + + void serializeGenericRequirement(const swift::Requirement &Req, + llvm::json::OStream &OS) const; + + void serializeSwiftGenericMixin(llvm::json::OStream &OS) const; + + void serializeSwiftExtensionMixin(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const; + + void serializeDeclarationFragmentMixin(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const; + + void serializeAccessLevelMixin(llvm::json::OStream &OS) const; + + llvm::Optional + getDomain(PlatformAgnosticAvailabilityKind AgnosticKind, + PlatformKind Kind) const; + + void serializeAvailabilityMixin(llvm::json::OStream &OS) const; + + void serialize(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const; + + bool operator==(const Symbol &Other) const { + return VD == Other.VD; + } +}; + +} // end namespace symbolgraphgen +} // end namespace swift + +#endif // SWIFT_SYMBOLGRAPHGEN_SYMBOL_H diff --git a/lib/SymbolGraphGen/SymbolGraph.cpp b/lib/SymbolGraphGen/SymbolGraph.cpp new file mode 100644 index 0000000000000..0d41bdcf4a638 --- /dev/null +++ b/lib/SymbolGraphGen/SymbolGraph.cpp @@ -0,0 +1,68 @@ +//===--- SymbolGraph.cpp - Symbol Graph Data Structure -------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "swift/AST/Module.h" +#include "swift/Basic/Version.h" + +#include "FormatVersion.h" +#include "SymbolGraph.h" + +using namespace swift; +using namespace symbolgraphgen; + +SymbolGraph::SymbolGraph(ModuleDecl &M, llvm::Triple Target, + Optional ModuleVersion) +: M(M), Target(Target), ModuleVersion(ModuleVersion) {} + +void SymbolGraph::serialize(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const { + OS.object([&](){ + OS.attributeObject("metadata", [&](){ + { + AttributeRAII FV("formatVersion", OS); + llvm::VersionTuple FormatVersion(SWIFT_SYMBOLGRAPH_FORMAT_MAJOR, + SWIFT_SYMBOLGRAPH_FORMAT_MINOR, + SWIFT_SYMBOLGRAPH_FORMAT_PATCH); + symbolgraphgen::serialize(FormatVersion, OS); + } // end formatVersion: + + auto VersionString = version::getSwiftFullVersion(); + StringRef VersionStringRef(VersionString.c_str(), VersionString.size()); + OS.attribute("generator", VersionStringRef); + }); // end metadata: + + OS.attributeObject("module", [&](){ + OS.attribute("name", M.getNameStr()); + AttributeRAII Platform("platform", OS); + symbolgraphgen::serialize(Target, OS); + }); + + if (ModuleVersion) { + AttributeRAII MV("moduleVersion", OS); + symbolgraphgen::serialize(*ModuleVersion, OS); + } + + OS.attributeArray("symbols", [&](){ + for (const auto *VD: Nodes) { + Symbol S { VD }; + S.serialize(Walker, OS); + } + }); + + OS.attributeArray("relationships", [&](){ + for (const auto Relationship : Edges) { + Relationship.serialize(OS); + } + }); + + }); +} diff --git a/lib/SymbolGraphGen/SymbolGraph.h b/lib/SymbolGraphGen/SymbolGraph.h new file mode 100644 index 0000000000000..1e893780e35f3 --- /dev/null +++ b/lib/SymbolGraphGen/SymbolGraph.h @@ -0,0 +1,64 @@ +//===--- SymbolGraph.h - Symbol Graph Data Structure ----------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SYMBOLGRAPHGEN_SYMBOLGRAPH_H +#define SWIFT_SYMBOLGRAPHGEN_SYMBOLGRAPH_H + +#include "llvm/ADT/SmallSet.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/VersionTuple.h" +#include "swift/Basic/LLVM.h" +#include "Edge.h" +#include "JSON.h" + +namespace swift { +namespace symbolgraphgen { + +/// A graph of symbols and the relationships between them. +struct SymbolGraph { + /** + The module this symbol graph represents. + */ + ModuleDecl &M; + + /** + The module's target triple. + */ + llvm::Triple Target; + + /** + The semantic version of the module that this symbol graph describes, + if known. + */ + Optional ModuleVersion; + + /** + The symbols in a module: the nodes in the graph. + */ + llvm::SmallPtrSet Nodes; + + /** + The relationships between symbols: the edges in the graph. + */ + llvm::DenseSet Edges; + + SymbolGraph(ModuleDecl &M, llvm::Triple Target, + Optional ModuleVersion = None); + + void serialize(SymbolGraphASTWalker &Walker, + llvm::json::OStream &OS) const; +}; + +} // end namespace symbolgraphgen +} // end namespace swift + +#endif // SWIFT_SYMBOLGRAPHGEN_SYMBOLGRAPH_H diff --git a/lib/SymbolGraphGen/SymbolGraphASTWalker.cpp b/lib/SymbolGraphGen/SymbolGraphASTWalker.cpp new file mode 100644 index 0000000000000..2ce1337d65468 --- /dev/null +++ b/lib/SymbolGraphGen/SymbolGraphASTWalker.cpp @@ -0,0 +1,364 @@ +//===--- SymbolGraphASTWalker.cpp - Symbol Graph AST Walker ---------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/DeclObjC.h" +#include "llvm/ADT/StringSwitch.h" +#include "swift/AST/ASTContext.h" +#include "swift/AST/ASTPrinter.h" +#include "swift/AST/Decl.h" +#include "swift/AST/GenericSignature.h" +#include "swift/AST/Module.h" +#include "swift/AST/ParameterList.h" +#include "swift/AST/ProtocolConformance.h" +#include "swift/AST/USRGeneration.h" +#include "swift/Basic/PrimitiveParsing.h" +#include "swift/Markup/Markup.h" +#include "swift/SymbolGraphGen/SymbolGraphGen.h" + +#include "DeclarationFragmentPrinter.h" +#include "SymbolGraphASTWalker.h" + +using namespace swift; +using namespace symbolgraphgen; + +SymbolGraphASTWalker::SymbolGraphASTWalker(ModuleDecl &M, + const SymbolGraphOptions &Options) + : Options(Options), + M(M), + Graph(M, Options.Target) {} + +/// Returns `true` if the symbol should be included as a node in the graph. +bool SymbolGraphASTWalker::shouldIncludeNode(const Decl *D) const { + // If this decl isn't in this module, don't record it, + // as it will appear elsewhere in its module's symbol graph. + if (D->getModuleContext()->getName() != M.getName()) { + return false; + } + + // Implicit declarations are probably not going to have documentation, + // so don't record it in the symbol graph. + if (D->isImplicit()) { + return false; + } + + // At this point, the declaration must be a ValueDecl. + auto VD = cast(D); + + // Don't record unconditionally private declarations + if (VD->isPrivateStdlibDecl(/*treatNonBuiltinProtocolsAsPublic=*/false)) { + return false; + } + + // Don't record effectively internal declarations if specified + if (Options.MinimumAccessLevel > AccessLevel::Internal && + VD->hasUnderscoredNaming()) { + return false; + } + + // Symbols must meet the minimum access level to be included in the graph. + if (VD->getFormalAccess() < Options.MinimumAccessLevel) { + return false; + } + + // Special cases + + auto BaseName = VD->getBaseName().userFacingName(); + + // ${MODULE}Version{Number,String} in ${Module}.h + SmallString<32> VersionNameIdentPrefix { M.getName().str() }; + VersionNameIdentPrefix.append("Version"); + + if (BaseName.startswith(VersionNameIdentPrefix.str())) { + return false; + } + + // Automatically mapped SIMD types + bool ShouldInclude = llvm::StringSwitch(BaseName) +#define MAP_SIMD_TYPE(C_TYPE, _, __) \ + .Case("swift_" #C_TYPE "2", false) \ + .Case("swift_" #C_TYPE "3", false) \ + .Case("swift_" #C_TYPE "4", false) +#include "swift/ClangImporter/SIMDMappedTypes.def" + .Case("SWIFT_TYPEDEFS", false) + .Case("char16_t", false) + .Case("char32_t", false) + .Default(true); + + return ShouldInclude; +} + +bool SymbolGraphASTWalker::walkToDeclPre(Decl *D, CharSourceRange Range) { + + switch (D->getKind()) { + // We'll record nodes for the following kinds of declarations. + case swift::DeclKind::Class: + case swift::DeclKind::Struct: + case swift::DeclKind::Enum: + case swift::DeclKind::EnumElement: + case swift::DeclKind::Protocol: + case swift::DeclKind::Constructor: + case swift::DeclKind::Func: + case swift::DeclKind::Var: + case swift::DeclKind::TypeAlias: + break; + + // We'll descend into everything else. + default: + return true; + } + + if (!shouldIncludeNode(D)) { + return false; + } + + auto *VD = cast(D); + Graph.Nodes.insert(VD); + + // Record all of the possible relationships (edges) originating + // with this declaration. + recordMemberRelationship(VD); + recordConformanceRelationships(VD); + recordInheritanceRelationships(VD); + recordDefaultImplementationRelationships(VD); + recordOverrideRelationship(VD); + recordRequirementRelationships(VD); + recordOptionalRequirementRelationships(VD); + + return true; +} + +StringRef SymbolGraphASTWalker::getUSR(const ValueDecl *VD) { + auto Found = USRCache.find(VD); + if (Found != USRCache.end()) { + return Found->second; + } + llvm::SmallString<32> Scratch; + llvm::raw_svector_ostream OS(Scratch); + ide::printDeclUSR(VD, OS); + auto USR = Ctx.allocateCopy(Scratch.str()); + USRCache.insert({VD, USR}); + return USR; +} + +SymbolIdentifier +SymbolGraphASTWalker::getSymbolIdentifier(const ValueDecl *VD) { + // Look in the symbol identifier cache for this declartion. + auto Found = SymbolIdentifierCache.find(VD); + if (Found != SymbolIdentifierCache.end()) { + return Found->getSecond(); + } + + // Not found; need to build a symbol identifier and add it to the cache. + auto PreciseIdentifier = getUSR(VD); + llvm::SmallVector SimpleIdentifierChain; + + // Collect the spellings of the fully qualified identifier components. + auto Decl = VD; + while (Decl && !isa(Decl)) { + SmallString<32> Scratch; + Decl->getFullName().getString(Scratch); + SimpleIdentifierChain.push_back(Ctx.allocateCopy(Scratch.str())); + if (const auto *DC = Decl->getDeclContext()) { + if (const auto *Proto = DC->getExtendedProtocolDecl()) { + Decl = Proto; + } else if (const auto *Ext = dyn_cast_or_null(DC->getAsDecl())) { + Decl = Ext->getExtendedNominal(); + } else { + Decl = dyn_cast_or_null(DC->getAsDecl()); + } + } else { + Decl = nullptr; + } + } + + // The list is leaf-to-root, but our list is root-to-leaf, so reverse it. + std::reverse(SimpleIdentifierChain.begin(), SimpleIdentifierChain.end()); + + SymbolIdentifier Identifier { + PreciseIdentifier, + Ctx.allocateCopy(llvm::makeArrayRef(SimpleIdentifierChain)) + }; + + SymbolIdentifierCache.insert({VD, Identifier}); + return Identifier; +} + +PrintOptions SymbolGraphASTWalker::getDeclarationFragmentsPrintOptions() const { + PrintOptions Opts; + Opts.FunctionDefinitions = false; + Opts.ArgAndParamPrinting = + PrintOptions::ArgAndParamPrintingMode::ArgumentOnly; + Opts.PrintGetSetOnRWProperties = false; + Opts.PrintPropertyAccessors = false; + Opts.PrintSubscriptAccessors = false; + Opts.SkipUnderscoredKeywords = true; + Opts.SkipAttributes = true; + Opts.PrintOverrideKeyword = true; + Opts.PrintImplicitAttrs = false; + Opts.PrintFunctionRepresentationAttrs = + PrintOptions::FunctionRepresentationMode::None; + Opts.PrintUserInaccessibleAttrs = false; + Opts.SkipPrivateStdlibDecls = true; + Opts.SkipUnderscoredStdlibProtocols = true; + + Opts.ExclusiveAttrList.clear(); + +#define DECL_ATTR(SPELLING, CLASS, OPTIONS, CODE) Opts.ExcludeAttrList.push_back(DAK_##CLASS); +#define TYPE_ATTR(X) Opts.ExcludeAttrList.push_back(TAK_##X); +#include "swift/AST/Attr.def" + + return Opts; +} + +void +SymbolGraphASTWalker::serializeDeclarationFragments(StringRef Key, + const ValueDecl *VD, + llvm::json::OStream &OS) { + DeclarationFragmentPrinter Printer(*this, OS, Key); + VD->print(Printer, getDeclarationFragmentsPrintOptions()); +} + +void +SymbolGraphASTWalker::serializeSubheadingDeclarationFragments(StringRef Key, + const ValueDecl *VD, + llvm::json::OStream &OS) { + DeclarationFragmentPrinter Printer(*this, OS, Key); + auto Options = getDeclarationFragmentsPrintOptions(); + Options.VarInitializers = false; + Options.PrintDefaultArgumentValue = false; + Options.PrintEmptyArgumentNames = false; + Options.PrintOverrideKeyword = false; + VD->print(Printer, Options); +} + +void +SymbolGraphASTWalker::serializeDeclarationFragments(StringRef Key, Type T, + llvm::json::OStream &OS) { + DeclarationFragmentPrinter Printer(*this, OS, Key); + T->print(Printer, getDeclarationFragmentsPrintOptions()); +} + +void SymbolGraphASTWalker::recordEdge(const ValueDecl *Source, + const ValueDecl *Target, + RelationshipKind Kind) { + if (Target->isPrivateStdlibDecl( + /*treatNonBuiltinProtocolsAsPublic = */false)) { + return; + } + + // There might be relationships on implicit declarations, + // such as overriding implicit @objc init(). + if (Target->isImplicit()) { + return; + } + + Graph.Nodes.insert(Source); + Graph.Nodes.insert(Target); + + Graph.Edges.insert({this, Kind, Source, Target}); +} + +void SymbolGraphASTWalker::recordMemberRelationship(const ValueDecl *VD) { + auto *DC = VD->getDeclContext(); + switch (DC->getContextKind()) { + case DeclContextKind::GenericTypeDecl: + case DeclContextKind::ExtensionDecl: + case swift::DeclContextKind::EnumElementDecl: + return recordEdge(VD, VD->getDeclContext()->getSelfNominalTypeDecl(), + RelationshipKind::MemberOf()); + case swift::DeclContextKind::AbstractClosureExpr: + case swift::DeclContextKind::Initializer: + case swift::DeclContextKind::TopLevelCodeDecl: + case swift::DeclContextKind::SubscriptDecl: + case swift::DeclContextKind::AbstractFunctionDecl: + case swift::DeclContextKind::SerializedLocal: + case swift::DeclContextKind::Module: + case swift::DeclContextKind::FileUnit: + break; + } +} + +void +SymbolGraphASTWalker::recordInheritanceRelationships(const ValueDecl *VD) { + if (const auto *NTD = dyn_cast(VD)) { + for (const auto &InheritanceLoc : NTD->getInherited()) { + auto Ty = InheritanceLoc.getType(); + if (!Ty) { + continue; + } + auto *InheritedTypeDecl = + dyn_cast_or_null(Ty->getAnyNominal()); + if (!InheritedTypeDecl) { + continue; + } + + recordEdge(VD, InheritedTypeDecl, RelationshipKind::InheritsFrom()); + } + } +} + +void SymbolGraphASTWalker::recordDefaultImplementationRelationships( + const ValueDecl *VD) { + if (const auto *Extension = dyn_cast(VD->getDeclContext())) { + if (const auto *Protocol = Extension->getExtendedProtocolDecl()) { + for (const auto *Member : Protocol->getMembers()) { + if (const auto *MemberVD = dyn_cast(Member)) { + if (MemberVD->getFullName().compare(VD->getFullName()) == 0) { + recordEdge(VD, MemberVD, + RelationshipKind::DefaultImplementationOf()); + } + } + } + } + } +} + +void +SymbolGraphASTWalker::recordRequirementRelationships(const ValueDecl *VD) { + if (const auto *Protocol = dyn_cast(VD->getDeclContext())) { + if (VD->isProtocolRequirement()) { + recordEdge(VD, Protocol, RelationshipKind::RequirementOf()); + } + } +} + +void SymbolGraphASTWalker::recordOptionalRequirementRelationships( + const ValueDecl *VD) { + if (const auto *Protocol = dyn_cast(VD->getDeclContext())) { + if (VD->isProtocolRequirement()) { + if (const auto *ClangDecl = VD->getClangDecl()) { + if (const auto *Method = dyn_cast(ClangDecl)) { + if (Method->isOptional()) { + recordEdge(VD, Protocol, + RelationshipKind::OptionalRequirementOf()); + } + } + } + } + } +} + +void +SymbolGraphASTWalker::recordConformanceRelationships(const ValueDecl *VD) { + if (const auto *NTD = dyn_cast(VD)) { + for (const auto *Conformance : NTD->getAllConformances()) { + recordEdge(VD, Conformance->getProtocol(), + RelationshipKind::ConformsTo()); + } + } +} + +void SymbolGraphASTWalker::recordOverrideRelationship(const ValueDecl *VD) { + if (const auto *Override = VD->getOverriddenDecl()) { + recordEdge(VD, Override, RelationshipKind::Overrides()); + } +} diff --git a/lib/SymbolGraphGen/SymbolGraphASTWalker.h b/lib/SymbolGraphGen/SymbolGraphASTWalker.h new file mode 100644 index 0000000000000..fb3e3dc9b6d0d --- /dev/null +++ b/lib/SymbolGraphGen/SymbolGraphASTWalker.h @@ -0,0 +1,161 @@ +//===--- SymbolGraphASTWalker.h - Symbol Graph AST Walker -----------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SYMBOLGRAPHGEN_SYMBOLGRAPHASTWALKER_H +#define SWIFT_SYMBOLGRAPHGEN_SYMBOLGRAPHASTWALKER_H + +#include "swift/Basic/LLVM.h" +#include "swift/IDE/SourceEntityWalker.h" +#include "swift/Markup/Markup.h" + +#include "SymbolGraph.h" + +namespace swift { + +class Decl; +class Type; +class ValueDecl; + +namespace symbolgraphgen { + +struct SymbolIdentifier; +struct SymbolGraph; +struct SymbolGraphOptions; + +/** + The `SymbolGraphASTWalker` is the core implementation that builds a + symbol graph. It walks a module for declarations, recording facts about + symbols and relationships between them. + */ +struct SymbolGraphASTWalker : public SourceEntityWalker { + /// Options for collecting and serialization. + const SymbolGraphOptions &Options; + + /// The module that this symbol graph will represent. + const ModuleDecl &M; + + /// The symbol graph. + SymbolGraph Graph; + + /// A context for allocations. + markup::MarkupContext Ctx; + + /// A cache of identifiers for declarations that may be seen more than once. + llvm::DenseMap SymbolIdentifierCache; + + /// A cache of USRs for declarations. + llvm::DenseMap USRCache; + + // MARK: - + + SymbolGraphASTWalker(ModuleDecl &M, const SymbolGraphOptions &Options); + virtual ~SymbolGraphASTWalker() {} + + // MARK: - + + /// Returns `true` if the symbol should be included as a node in the graph. + bool shouldIncludeNode(const Decl *D) const; + + virtual bool walkToDeclPre(Decl *D, CharSourceRange Range); + + // MARK: - Utilities and Conversions + + /// Get the USR of a declaration and add it to the local allocator. + StringRef getUSR(const ValueDecl *VD); + + /// Returns a `SymbolIdentifier` for a given declaration. + SymbolIdentifier getSymbolIdentifier(const ValueDecl *VD); + + // MARK: - Declaration Fragments + + /// Get the base print options for declaration fragments. + PrintOptions getDeclarationFragmentsPrintOptions() const; + + /// Serialize the overall declaration fragments for a `ValueDecl`. + void + serializeDeclarationFragments(StringRef Key, const ValueDecl *VD, + llvm::json::OStream &OS); + + /// Get the overall declaration fragments for a `ValueDecl` when it is viewed + /// as a subheading and/or part of a larger group of symbol listings. + void + serializeSubheadingDeclarationFragments(StringRef Key, const ValueDecl *VD, + llvm::json::OStream &OS); + + /// Get the overall declaration for a type declaration. + void + serializeDeclarationFragments(StringRef Key, Type T, + llvm::json::OStream &OS); + + // MARK: - Relationships (Edges) + + /** + Record a relationship between two declarations as an edge in the graph. + + \param Source The declaration serving as the source of the edge in the + directed graph. + \param Target The declaration serving as the target of the edge in the + directed graph. + \param Kind The kind of relationship the edge represents. + */ + void recordEdge(const ValueDecl *Source, const ValueDecl *Target, + RelationshipKind Kind); + + /** + Record a MemberOf relationship, if the given declaration is nested + in another. + */ + void recordMemberRelationship(const ValueDecl *VD); + + /** + Record InheritsFrom relationships for every class from which the + declaration inherits. + */ + void recordInheritanceRelationships(const ValueDecl *VD); + + /** + If the declaration is a default implementation in a protocol extension, + record a DefaultImplementationOf relationship between the declaration and + the requirement. + */ + void recordDefaultImplementationRelationships(const ValueDecl *VD); + + /** + Record a RequirementOf relationship if the declaration is a requirement + of a protocol. + */ + void recordRequirementRelationships(const ValueDecl *VD); + + /** + If the declaration is an Objective-C-based optional protocol requirement, + record an OptionalRequirementOf relationship between the declaration + and its containing protocol. + */ + void recordOptionalRequirementRelationships(const ValueDecl *VD); + + /** + Record ConformsTo relationships for each protocol conformance of + the declaration. + */ + void recordConformanceRelationships(const ValueDecl *VD); + + /** + Records an Overrides relationship if the given declaration + overrides another. + */ + void recordOverrideRelationship(const ValueDecl *VD); +}; + +} // end namespace symbolgraphgen +} // end namespace swift + +#endif // SWIFT_SYMBOLGRAPHGEN_SYMBOLGRAPHASTWALKER_H diff --git a/lib/SymbolGraphGen/SymbolGraphGen.cpp b/lib/SymbolGraphGen/SymbolGraphGen.cpp new file mode 100644 index 0000000000000..667d7b3e328c4 --- /dev/null +++ b/lib/SymbolGraphGen/SymbolGraphGen.cpp @@ -0,0 +1,56 @@ +//===--- SymbolGraphGen.cpp - Symbol Graph Generator Entry Point ----------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/JSON.h" +#include "llvm/Support/Path.h" +#include "swift/AST/Module.h" +#include "swift/SymbolGraphGen/SymbolGraphGen.h" + +#include "SymbolGraphASTWalker.h" + +using namespace swift; +using namespace symbolgraphgen; + +// MARK: - Main Entry Point + +/// Emit a symbol graph JSON file for a `ModuleDecl`. +int +symbolgraphgen::emitSymbolGraphForModule(ModuleDecl *M, + const SymbolGraphOptions &Options) { + SymbolGraphASTWalker Walker(*M, Options); + SmallVector ModuleDecls; + M->getDisplayDecls(ModuleDecls); + + llvm::errs() << ModuleDecls.size() + << " top-level declarations in this module.\n"; + + for (auto *Decl : ModuleDecls) { + Walker.walk(Decl); + } + + llvm::errs() + << "Found " << Walker.Graph.Nodes.size() << " symbols and " + << Walker.Graph.Edges.size() << " relationships.\n"; + + std::error_code Error; + llvm::raw_fd_ostream OS(Options.OutputPath, Error, llvm::sys::fs::FA_Write); + if (Error) { + llvm::errs() << "Couldn't open output file for writing: " + << Error.message() << "\n"; + return EXIT_FAILURE; + } + + llvm::json::OStream J(OS, Options.PrettyPrint ? 2 : 0); + Walker.Graph.serialize(Walker, J); + + return EXIT_SUCCESS; +} diff --git a/test/IDE/comment_brief.swift b/test/IDE/comment_brief.swift index 1409f3e9fd36f..db447aaca1807 100644 --- a/test/IDE/comment_brief.swift +++ b/test/IDE/comment_brief.swift @@ -145,8 +145,8 @@ struct Indentation { // CHECK-NEXT: Func/briefBlockWithASCIIArt5 {{.*}} BriefComment=[Aaa. Bbb. *Ccc.] // CHECK-NEXT: Func/briefBlockWithASCIIArt6 {{.*}} BriefComment=[Aaa.] // CHECK-NEXT: Func/briefMixed1 {{.*}} BriefComment=[Aaa. Bbb.] -// CHECK-NEXT: Func/briefMixed2 {{.*}} BriefComment=[Aaa.] -// CHECK-NEXT: Func/briefMixed3 {{.*}} BriefComment=[Aaa.] +// CHECK-NEXT: Func/briefMixed2 {{.*}} BriefComment=[Aaa. Bbb.] +// CHECK-NEXT: Func/briefMixed3 {{.*}} BriefComment=[Aaa. Bbb.] // CHECK-NEXT: Struct/Indentation RawComment=none // CHECK-NEXT: Func/Indentation.briefBlockWithASCIIArt1 {{.*}} BriefComment=[Aaa.] diff --git a/test/SymbolGraph/Module.swift b/test/SymbolGraph/Module.swift new file mode 100644 index 0000000000000..dcfba01e9a43b --- /dev/null +++ b/test/SymbolGraph/Module.swift @@ -0,0 +1,20 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name SymbolGraphModule -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name SymbolGraphModule -I %t -pretty-print -o %t/SymbolGraphModule.symbols.json +// RUN: %FileCheck %s --input-file %t/SymbolGraphModule.symbols.json + +public struct S { + public var x: Int +} + +// CHECK: module +// CHECK-NEXT: "name": "SymbolGraphModule" +// CHECK-NEXT: platform +// CHECK-NEXT: architecture +// CHECK: vendor +// CHECK-NEXT: operatingSystem +// CHECK-NEXT: name +// CHECK-NEXT: minimumVersion +// CHECK-NEXT: major +// CHECK-NEXT: minor +// CHECK-NEXT: patch diff --git a/test/SymbolGraph/Relationships/ConformsTo.swift b/test/SymbolGraph/Relationships/ConformsTo.swift new file mode 100644 index 0000000000000..c895fd5f7bfc6 --- /dev/null +++ b/test/SymbolGraph/Relationships/ConformsTo.swift @@ -0,0 +1,16 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name ConformsTo -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name ConformsTo -I %t -pretty-print -o %t/ConformsTo.symbols.json +// RUN: %FileCheck %s --input-file %t/ConformsTo.symbols.json + +public protocol P { + var x: Int { get } +} + +public struct S: P { + public var x: Int +} + +// CHECK: "kind": "conformsTo" +// CHECK-NEXT: "source": "s:10ConformsTo1SV" +// CHECK-NEXT: "target": "s:10ConformsTo1PP" diff --git a/test/SymbolGraph/Relationships/DefaultImplementationOf.swift b/test/SymbolGraph/Relationships/DefaultImplementationOf.swift new file mode 100644 index 0000000000000..3193cfdf15200 --- /dev/null +++ b/test/SymbolGraph/Relationships/DefaultImplementationOf.swift @@ -0,0 +1,18 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name DefaultImplementationOf -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name DefaultImplementationOf -I %t -pretty-print -o %t/DefaultImplementationOf.symbols.json +// RUN: %FileCheck %s --input-file %t/DefaultImplementationOf.symbols.json + +public protocol P { + var x: Int { get } +} + +extension P { + public var x: Int { + return 2 + } +} + +// CHECK: "kind": "defaultImplementationOf", +// CHECK-NEXT: "source": "s:23DefaultImplementationOf1PPAAE1xSivp", +// CHECK-NEXT: "target": "s:23DefaultImplementationOf1PP1xSivp" diff --git a/test/SymbolGraph/Relationships/InheritsFrom.swift b/test/SymbolGraph/Relationships/InheritsFrom.swift new file mode 100644 index 0000000000000..feb9c4f9ec61b --- /dev/null +++ b/test/SymbolGraph/Relationships/InheritsFrom.swift @@ -0,0 +1,11 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name InheritsFrom -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name InheritsFrom -I %t -pretty-print -o %t/InheritsFrom.symbols.json +// RUN: %FileCheck %s --input-file %t/InheritsFrom.symbols.json + +public class Base {} +public class Derived: Base {} + +// CHECK: "kind": "inheritsFrom" +// CHECK-NEXT: "source": "s:12InheritsFrom7DerivedC" +// CHECK-NEXT: "target": "s:12InheritsFrom4BaseC" diff --git a/test/SymbolGraph/Relationships/MemberOf.swift b/test/SymbolGraph/Relationships/MemberOf.swift new file mode 100644 index 0000000000000..f338e74aff62f --- /dev/null +++ b/test/SymbolGraph/Relationships/MemberOf.swift @@ -0,0 +1,12 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name MemberOf -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name MemberOf -I %t -pretty-print -o %t/MemberOf.symbols.json +// RUN: %FileCheck %s --input-file %t/MemberOf.symbols.json + +public struct S { + public var x: Int +} + +// CHECK: "kind": "memberOf" +// CHECK-NEXT: "source": "s:8MemberOf1SV1xSivp" +// CHECK-NEXT: "target": "s:8MemberOf1SV" diff --git a/test/SymbolGraph/Relationships/Overrides.swift b/test/SymbolGraph/Relationships/Overrides.swift new file mode 100644 index 0000000000000..85d2f7afb57f2 --- /dev/null +++ b/test/SymbolGraph/Relationships/Overrides.swift @@ -0,0 +1,20 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name Overrides -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name Overrides -I %t -pretty-print -o %t/Overrides.symbols.json +// RUN: %FileCheck %s --input-file %t/Overrides.symbols.json + +public class Base { + public var x: Int { + return 1 + } +} + +public class Derived: Base { + public override var x: Int { + return 2 + } +} + +// CHECK: "kind": "overrides" +// CHECK-NEXT: "source": "s:9Overrides7DerivedC1xSivp" +// CHECK-NEXT: "target": "s:9Overrides4BaseC1xSivp" diff --git a/test/SymbolGraph/Relationships/RequirementOf.swift b/test/SymbolGraph/Relationships/RequirementOf.swift new file mode 100644 index 0000000000000..a06507b523b7e --- /dev/null +++ b/test/SymbolGraph/Relationships/RequirementOf.swift @@ -0,0 +1,12 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name ConformsTo -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name ConformsTo -I %t -pretty-print -o %t/ConformsTo.symbols.json +// RUN: %FileCheck %s --input-file %t/ConformsTo.symbols.json + +public protocol P { + var x: Int { get } +} + +// CHECK: "kind": "requirementOf" +// CHECK-NEXT: "source": "s:10ConformsTo1PP1xSivp" +// CHECK-NEXT: "target": "s:10ConformsTo1PP" diff --git a/test/SymbolGraph/Relationships/TargetFallback.swift b/test/SymbolGraph/Relationships/TargetFallback.swift new file mode 100644 index 0000000000000..274c423d0f748 --- /dev/null +++ b/test/SymbolGraph/Relationships/TargetFallback.swift @@ -0,0 +1,16 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name TargetFallback -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name TargetFallback -I %t -pretty-print -o %t/TargetFallback.symbols.json +// RUN: %FileCheck %s --input-file %t/TargetFallback.symbols.json + +public struct S: CustomStringConvertible { + public var x: Int + public var description: String { + return x.description + } +} + +// CHECK: "kind": "conformsTo", +// CHECK-NEXT: "source": "s:14TargetFallback1SV", +// CHECK-NEXT: "target": "s:s23CustomStringConvertibleP", +// CHECK-NEXT: "targetFallback": "Swift.CustomStringConvertible" diff --git a/test/SymbolGraph/Symbols/AccessLevelFilter/IncludeInternal.swift b/test/SymbolGraph/Symbols/AccessLevelFilter/IncludeInternal.swift new file mode 100644 index 0000000000000..eb6e3bb15633f --- /dev/null +++ b/test/SymbolGraph/Symbols/AccessLevelFilter/IncludeInternal.swift @@ -0,0 +1,20 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name IncludeInternal -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name IncludeInternal -I %t -pretty-print -o %t/IncludeInternal.symbols.json -minimum-access-level internal +// RUN: %FileCheck %s --input-file %t/IncludeInternal.symbols.json + +public struct ShouldAppear { + public var x: Int +} + +internal struct ShouldAlsoAppear { + internal var x: Int +} + +private struct ShouldntAppear { + var x: Int +} + +// CHECK: ShouldAppear +// CHECK: ShouldAlsoAppear +// CHECK-NOT: ShouldntAppear diff --git a/test/SymbolGraph/Symbols/AccessLevelFilter/PublicDefault.swift b/test/SymbolGraph/Symbols/AccessLevelFilter/PublicDefault.swift new file mode 100644 index 0000000000000..258b29058f1c1 --- /dev/null +++ b/test/SymbolGraph/Symbols/AccessLevelFilter/PublicDefault.swift @@ -0,0 +1,15 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name PublicDefault -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name PublicDefault -I %t -pretty-print -o %t/PublicDefault.symbols.json +// RUN: %FileCheck %s --input-file %t/PublicDefault.symbols.json + +public struct ShouldAppear { + public var x: Int +} + +internal struct ShouldntAppear { + internal var x: Int +} + +// CHECK: ShouldAppear +// CHECK-NOT: ShouldntAppear diff --git a/test/SymbolGraph/Symbols/AccessLevels.swift b/test/SymbolGraph/Symbols/AccessLevels.swift new file mode 100644 index 0000000000000..e7d3daec46132 --- /dev/null +++ b/test/SymbolGraph/Symbols/AccessLevels.swift @@ -0,0 +1,12 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name AccessLevels -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name AccessLevels -I %t -pretty-print -o %t/AccessLevels.symbols.json +// RUN: %FileCheck %s --input-file %t/AccessLevels.symbols.json + +// CHECK: "accessLevel": "public" + +public struct PublicStruct { + public var x: Int +} + +// CHECK-NOT: "accessLevel": "private" diff --git a/test/SymbolGraph/Symbols/DocComment.swift b/test/SymbolGraph/Symbols/DocComment.swift new file mode 100644 index 0000000000000..b9c07d2df1548 --- /dev/null +++ b/test/SymbolGraph/Symbols/DocComment.swift @@ -0,0 +1,54 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name DocComment -emit-module-path %t/DocComment.swiftmodule +// RUN: %target-swift-symbolgraph-extract -module-name DocComment -I %t -pretty-print -o %t/DocComment.symbols.json +// RUN: %FileCheck %s --input-file %t/DocComment.symbols.json + +// CHECK: "text": "Single line." + +/// Single line. +public struct S1 {} + +// CHECK: "text": "Two" +// CHECK: "text": "lines." + +/// Two +/// lines. +public struct S2 {} + +// CHECK: "text": "There are" +// CHECK: "text": "three lines" +// CHECK: "text": "here." + +/// There are +/// three lines +/// here. +public struct S3 {} + +// CHECK: "text": "Comment" +// CHECK: "text": "block." + +/** + Comment + block. +*/ +public struct S4 {} + +// CHECK: "text": "Comment block" +// CHECK: "text": "with more indentation." + +/** + Comment block + with more indentation. + */ +public struct S5 {} + +// CHECK: "text": "With" +// CHECK: "text": "ASCII" +// CHECK: "text": "art." + +/** + * With + * ASCII + * art. + */ +public struct S6 {} diff --git a/test/SymbolGraph/Symbols/Kinds.swift b/test/SymbolGraph/Symbols/Kinds.swift new file mode 100644 index 0000000000000..ec79235016659 --- /dev/null +++ b/test/SymbolGraph/Symbols/Kinds.swift @@ -0,0 +1,42 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name Kinds -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name Kinds -I %t -pretty-print -o %t/Kinds.symbols.json +// RUN: %FileCheck %s --input-file %t/Kinds.symbols.json + +// CHECK: "identifier": "swift.class" +// CHECK-NEXT: "displayName": "Class" +public class C {} + +// CHECK: "identifier": "swift.struct" +// CHECK-NEXT: "displayName": "Structure" +public struct S { + // CHECK: "identifier": "swift.variable" + // CHECK-NEXT: "displayName": "Variable" + public var x: Int + + // CHECK: "identifier": "swift.initializer" + // CHECK-NEXT: "displayName": "Initializer" + public init(x: Int) { + self.x = x + } + + // CHECK: "identifier": "swift.function" + // CHECK-NEXT: "displayName": "Function" + public func foo() {} +} + +// CHECK: "identifier": "swift.enum" +// CHECK-NEXT: "displayName": "Enumeration" +public enum E { + // CHECK: "identifier": "swift.enum.case" + // CHECK-NEXT: "displayName": "Case" + case oneCase +} + +// CHECK: "identifier": "swift.protocol" +// CHECK-NEXT: "displayName": "Protocol" +public protocol P {} + +// CHECK: "identifier": "swift.typealias" +// CHECK-NEXT: "displayName": "Type Alias" +public typealias Alias = S diff --git a/test/SymbolGraph/Symbols/Mixins/Availability/Availability.swift b/test/SymbolGraph/Symbols/Mixins/Availability/Availability.swift new file mode 100644 index 0000000000000..d380b84e5d766 --- /dev/null +++ b/test/SymbolGraph/Symbols/Mixins/Availability/Availability.swift @@ -0,0 +1,24 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name Availability -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name Availability -I %t -pretty-print -o %t/Availability.symbols.json +// RUN: %FileCheck %s --input-file %t/Availability.symbols.json + +@available(macOS, introduced: 10.9, deprecated: 10.10, obsoleted: 10.11, message: "Everyone makes mistakes", renamed: "S2") +public struct S {} + +// CHECK: "domain": "macOS" + +// CHECK: introduced +// CHECK-NEXT: "major": 10 +// CHECK-NEXT: "minor": 9 + +// CHECK: deprecated +// CHECK-NEXT: "major": 10 +// CHECK-NEXT: "minor": 10 + +// CHECK: obsoleted +// CHECK-NEXT: "major": 10 +// CHECK-NEXT: "minor": 11 + +// CHECK: "message": "Everyone makes mistakes" +// CHECK: "renamed": "S2" diff --git a/test/SymbolGraph/Symbols/Mixins/Availability/UnconditionallyDeprecated.swift b/test/SymbolGraph/Symbols/Mixins/Availability/UnconditionallyDeprecated.swift new file mode 100644 index 0000000000000..96488cbc63d20 --- /dev/null +++ b/test/SymbolGraph/Symbols/Mixins/Availability/UnconditionallyDeprecated.swift @@ -0,0 +1,10 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name UnconditionallyDeprecated -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name UnconditionallyDeprecated -I %t -pretty-print -o %t/UnconditionallyDeprecated.symbols.json +// RUN: %FileCheck %s --input-file %t/UnconditionallyDeprecated.symbols.json + +@available(*, deprecated) +public struct UnconditionallyDeprecated {} + +// CHECK-NOT: domain +// CHECK: "isUnconditionallyDeprecated": true diff --git a/test/SymbolGraph/Symbols/Mixins/Availability/UnconditionallyUnavailable.swift b/test/SymbolGraph/Symbols/Mixins/Availability/UnconditionallyUnavailable.swift new file mode 100644 index 0000000000000..9fdcb6fa779f9 --- /dev/null +++ b/test/SymbolGraph/Symbols/Mixins/Availability/UnconditionallyUnavailable.swift @@ -0,0 +1,10 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name UnconditionallyUnavailable -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name UnconditionallyUnavailable -I %t -pretty-print -o %t/UnconditionallyUnavailable.symbols.json +// RUN: %FileCheck %s --input-file %t/UnconditionallyUnavailable.symbols.json + +@available(*, unavailable) +public struct UnconditionallyUnavailable {} + +// CHECK-NOT: domain +// CHECK: "isUnconditionallyUnavailable": true diff --git a/test/SymbolGraph/Symbols/Mixins/DeclarationFragments.swift b/test/SymbolGraph/Symbols/Mixins/DeclarationFragments.swift new file mode 100644 index 0000000000000..9f35f6fde8b5e --- /dev/null +++ b/test/SymbolGraph/Symbols/Mixins/DeclarationFragments.swift @@ -0,0 +1,54 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name DeclarationFragments -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name DeclarationFragments -I %t -pretty-print -o %t/DeclarationFragments.symbols.json +// RUN: %FileCheck %s --input-file %t/DeclarationFragments.symbols.json + +public func foo(f: @escaping () -> (), x: Int = 2, s: S) {} + +// CHECK: declarationFragments + +// CHECK: "kind": "keyword", +// CHECK-NEXT: "spelling": "func" + +// CHECK: "kind": "text", +// CHECK-NEXT: "spelling": " " + +// CHECK: "kind": "identifier", +// CHECK-NEXT: "spelling": "foo" + +// CHECK: "kind": "text", +// CHECK-NEXT: "spelling": "<" + +// CHECK: "kind": "genericParameter", +// CHECK-NEXT: "spelling": "S" + +// CHECK: "kind": "text", +// CHECK-NEXT: "spelling": ">(" + +// CHECK: "kind": "identifier", +// CHECK-NEXT: "spelling": "f" + +// CHECK: "kind": "text", +// CHECK-NEXT: "spelling": ": () -> (), " + +// CHECK: "kind": "identifier", +// CHECK-NEXT: "spelling": "x" + +// CHECK: "kind": "text", +// CHECK-NEXT: "spelling": ": " + +// CHECK: "kind": "typeIdentifier", +// CHECK-NEXT: "spelling": "Int", +// CHECK-NEXT: "preciseIdentifier": "s:Si" + +// CHECK: "kind": "text", +// CHECK-NEXT: "spelling": " = 2, " + +// CHECK: "kind": "identifier", +// CHECK-NEXT: "spelling": "s" + +// CHECK: "kind": "text", +// CHECK-NEXT: "spelling": ": S" + +// CHECK: "kind": "text", +// CHECK-NEXT: "spelling": ")" diff --git a/test/SymbolGraph/Symbols/Mixins/FunctionSignature.swift b/test/SymbolGraph/Symbols/Mixins/FunctionSignature.swift new file mode 100644 index 0000000000000..ae2a4bd6c441a --- /dev/null +++ b/test/SymbolGraph/Symbols/Mixins/FunctionSignature.swift @@ -0,0 +1,37 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name FunctionSignature -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name FunctionSignature -I %t -pretty-print -o %t/FunctionSignature.symbols.json +// RUN: %FileCheck %s --input-file %t/FunctionSignature.symbols.json + +public func foo(_ noext: Int, ext int: Int) -> String { + return "OK" +} + +// CHECK: "name": "noext" +// CHECK-NOT: "internalName": "noext" +// CHECK-NEXT: declarationFragments + +// CHECK: "kind": "identifier" +// CHECK-NEXT: "spelling": "noext" +// CHECK: "kind": "text" +// CHECK-NEXT: "spelling": ": " +// CHECK: "kind": "typeIdentifier" +// CHECK-NEXT: "spelling": "Int" +// CHECK-NEXT: "preciseIdentifier": "s:Si" + +// CHECK: "name": "ext" +// CHECK-NEXT: "internalName": "int" +// CHECK-NEXT: declarationFragments + +// CHECK: "kind": "identifier" +// CHECK-NEXT: "spelling": "int" +// CHECK: "kind": "text" +// CHECK-NEXT: "spelling": ": " +// CHECK: "kind": "typeIdentifier" +// CHECK-NEXT: "spelling": "Int" +// CHECK-NEXT: "preciseIdentifier": "s:Si" + +// CHECK: returns +// CHECK: "kind": "typeIdentifier" +// CHECK-NEXT: "spelling": "String" +// CHECK-NEXT: "preciseIdentifier": "s:SS" diff --git a/test/SymbolGraph/Symbols/Names.swift b/test/SymbolGraph/Symbols/Names.swift new file mode 100644 index 0000000000000..4ea658167d919 --- /dev/null +++ b/test/SymbolGraph/Symbols/Names.swift @@ -0,0 +1,9 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name Names -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name Names -I %t -pretty-print -o %t/Names.symbols.json +// RUN: %FileCheck %s --input-file %t/Names.symbols.json + +public struct MyStruct {} + +// CHECK: names +// CHECK-NEXT: "title": "MyStruct" diff --git a/test/SymbolGraph/Symbols/SkipsPublicUnderscore.swift b/test/SymbolGraph/Symbols/SkipsPublicUnderscore.swift new file mode 100644 index 0000000000000..e62567f069c4c --- /dev/null +++ b/test/SymbolGraph/Symbols/SkipsPublicUnderscore.swift @@ -0,0 +1,11 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -module-name SkipsPublicUnderscore -emit-module -emit-module-path %t/ +// RUN: %target-swift-symbolgraph-extract -module-name SkipsPublicUnderscore -I %t -pretty-print -o %t/SymbolGraphModule.symbols.json +// RUN: %FileCheck %s --input-file %t/SymbolGraphModule.symbols.json + +public struct _ShouldntAppear { + public var shouldntAppear: Int +} + +// CHECK-NOT: _ShouldntAppear +// CHECK-NOT: shouldntAppear diff --git a/test/lit.cfg b/test/lit.cfg index 1b43328020d69..9ea2cb9851a07 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -275,6 +275,7 @@ if 'syntax_parser_lib' in config.available_features: config.swift_reflection_dump = inferSwiftBinary('swift-reflection-dump') config.swift_remoteast_test = inferSwiftBinary('swift-remoteast-test') config.swift_indent = inferSwiftBinary('swift-indent') +config.swift_symbolgraph_extract = inferSwiftBinary('swift-symbolgraph-extract') config.clang = inferSwiftBinary('clang') config.llvm_link = inferSwiftBinary('llvm-link') config.swift_llvm_opt = inferSwiftBinary('swift-llvm-opt') @@ -904,6 +905,9 @@ if run_vendor == 'apple': config.target_sil_opt = ( "%s %s %s %s" % (xcrun_prefix, config.sil_opt, target_options, config.sil_test_options)) + config.target_swift_symbolgraph_extract = ( + "%s %s %s" % + (xcrun_prefix, config.swift_symbolgraph_extract, target_options)) config.target_swift_ide_test = ( "%s %s %s %s" % (xcrun_prefix, config.swift_ide_test, target_options, ccp_opt)) @@ -975,6 +979,10 @@ elif run_os in ['windows-msvc']: ('%r -target %s %s %s %s' % (config.sil_opt, config.variant_triple, \ resource_dir_opt, mcp_opt, \ config.sil_test_options)) + config.target_swift_symbol_graph_extract = \ + ('%r -target %s %s' % (config.swift_symbolgraph_extract, \ + config.variant_triple, \ + mcp_opt)) config.target_swift_ide_test = \ ('%r -target %s %s %s %s' % (config.swift_ide_test, \ config.variant_triple, \ @@ -1068,6 +1076,9 @@ elif (run_os in ['linux-gnu', 'linux-gnueabihf', 'freebsd', 'windows-cygnus', 'w config.target_sil_opt = ( '%s -target %s %s %s %s' % (config.sil_opt, config.variant_triple, resource_dir_opt, mcp_opt, config.sil_test_options)) + config.target_swift_symbolgraph_extract = ( + '%s -target %s %s' % + (config.swift_symbolgraph_extract, config.variant_triple, mcp_opt)) config.target_swift_ide_test = ( '%s -target %s %s %s %s' % (config.swift_ide_test, config.variant_triple, resource_dir_opt, @@ -1185,6 +1196,10 @@ elif run_os == 'linux-androideabi' or run_os == 'linux-android': '-target', config.variant_triple, android_include_paths_opt, resource_dir_opt, mcp_opt, config.sil_test_options]) + config.target_swift_symbolgraph_extract = ' '.join([ + config.swift_symbolgraph_extract, + '-target', config.variant_triple, + mcp_opt]) config.target_swift_ide_test = ' '.join([ config.swift_ide_test, '-target', config.variant_triple, @@ -1613,6 +1628,8 @@ config.substitutions.append(('%target-swift-ide-test\(mock-sdk:([^)]+)\)', swift_version)))) config.substitutions.append(('%target-swift-ide-test', "%s -swift-version %s" % (config.target_swift_ide_test, swift_version))) +config.substitutions.append(('%target-swift-symbolgraph-extract', config.target_swift_symbolgraph_extract)) + if not hasattr(config, 'target_swift_reflection_test'): config.target_swift_reflection_test = inferSwiftBinary(swift_reflection_test_name) diff --git a/tools/driver/CMakeLists.txt b/tools/driver/CMakeLists.txt index 39aeebb29bafd..2b5eb14dc0d7a 100644 --- a/tools/driver/CMakeLists.txt +++ b/tools/driver/CMakeLists.txt @@ -3,12 +3,14 @@ add_swift_host_tool(swift autolink_extract_main.cpp modulewrap_main.cpp swift_indent_main.cpp + swift_symbolgraph_extract_main.cpp SWIFT_COMPONENT compiler ) target_link_libraries(swift PRIVATE swiftDriver - swiftFrontendTool) + swiftFrontendTool + swiftSymbolGraphGen) swift_create_post_build_symlink(swift SOURCE "swift${CMAKE_EXECUTABLE_SUFFIX}" @@ -20,6 +22,11 @@ swift_create_post_build_symlink(swift DESTINATION "swift-indent${CMAKE_EXECUTABLE_SUFFIX}" WORKING_DIRECTORY "${SWIFT_RUNTIME_OUTPUT_INTDIR}") +swift_create_post_build_symlink(swift + SOURCE "swift${CMAKE_EXECUTABLE_SUFFIX}" + DESTINATION "swift-symbolgraph-extract${CMAKE_EXECUTABLE_SUFFIX}" + WORKING_DIRECTORY "${SWIFT_RUNTIME_OUTPUT_INTDIR}") + swift_create_post_build_symlink(swift SOURCE "swift${CMAKE_EXECUTABLE_SUFFIX}" DESTINATION "swift-autolink-extract${CMAKE_EXECUTABLE_SUFFIX}" @@ -28,6 +35,7 @@ swift_create_post_build_symlink(swift add_swift_tool_symlink(swiftc swift compiler) add_swift_tool_symlink(swift-autolink-extract swift autolink-driver) add_swift_tool_symlink(swift-indent swift editor-integration) +add_swift_tool_symlink(swift-symbolgraph-extract swift toolchain-tools) # If building as part of clang, make sure the headers are installed. if(NOT SWIFT_BUILT_STANDALONE) @@ -46,3 +54,7 @@ add_dependencies(editor-integration swift) swift_install_in_component(FILES "${SWIFT_RUNTIME_OUTPUT_INTDIR}/swift-indent${CMAKE_EXECUTABLE_SUFFIX}" DESTINATION "bin" COMPONENT editor-integration) +add_dependencies(toolchain-tools swift) +swift_install_in_component(FILES "${SWIFT_RUNTIME_OUTPUT_INTDIR}/swift-symbolgraph-extract${CMAKE_EXECUTABLE_SUFFIX}" + DESTINATION "bin" + COMPONENT toolchain-tools) diff --git a/tools/driver/driver.cpp b/tools/driver/driver.cpp index 0424977e4012a..17a451c76a051 100644 --- a/tools/driver/driver.cpp +++ b/tools/driver/driver.cpp @@ -70,6 +70,10 @@ extern int modulewrap_main(ArrayRef Args, const char *Argv0, extern int swift_indent_main(ArrayRef Args, const char *Argv0, void *MainAddr); +/// Run 'swift-symbolgraph-extract' +extern int swift_symbolgraph_extract_main(ArrayRef Args, const char *Argv0, +void *MainAddr); + /// Determine if the given invocation should run as a subcommand. /// /// \param ExecName The name of the argv[0] we were invoked as. @@ -152,6 +156,8 @@ static int run_driver(StringRef ExecName, return swift_indent_main( TheDriver.getArgsWithoutProgramNameAndDriverMode(argv), argv[0], (void *)(intptr_t)getExecutablePath); + case Driver::DriverKind::SymbolGraph: + return swift_symbolgraph_extract_main(TheDriver.getArgsWithoutProgramNameAndDriverMode(argv), argv[0], (void *)(intptr_t)getExecutablePath); default: break; } diff --git a/tools/driver/swift_symbolgraph_extract_main.cpp b/tools/driver/swift_symbolgraph_extract_main.cpp new file mode 100644 index 0000000000000..ef01acf5c3e58 --- /dev/null +++ b/tools/driver/swift_symbolgraph_extract_main.cpp @@ -0,0 +1,177 @@ +//===--- swift_indent_main.cpp - Swift code formatting tool ---------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// Extracts a Symbol Graph from a .swiftmodule file. +// +//===----------------------------------------------------------------------===// + +#include "swift/AST/DiagnosticsFrontend.h" +#include "swift/Basic/LLVM.h" +#include "swift/Basic/LLVMInitialize.h" +#include "swift/Basic/Version.h" +#include "swift/Frontend/Frontend.h" +#include "swift/Frontend/PrintingDiagnosticConsumer.h" +#include "swift/SymbolGraphGen/SymbolGraphGen.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/raw_ostream.h" + +using namespace swift; +using namespace llvm::opt; + +namespace options { +static llvm::cl::OptionCategory Category("swift-symbolgraph-extract Options"); + +static llvm::cl::opt +ModuleName("module-name", llvm::cl::desc("Name of the module to extract"), llvm::cl::cat(Category)); + +static llvm::cl::list +FrameworkSearchPaths("F", llvm::cl::desc("add a directory to the framework search paths"), llvm::cl::ZeroOrMore, + llvm::cl::cat(Category)); + +static llvm::cl::list +LibrarySearchPaths("L", llvm::cl::desc("Add a directory to the library search paths"), llvm::cl::ZeroOrMore, + llvm::cl::cat(Category)); + +static llvm::cl::list +ImportSearchPaths("I", llvm::cl::desc("Add directory to the import search paths"), llvm::cl::ZeroOrMore, + llvm::cl::cat(Category)); + +static llvm::cl::opt +ModuleCachePath("module-cache-path", llvm::cl::desc("Specifies a path to cache modules"), llvm::cl::cat(Category)); + +static llvm::cl::opt +SDK("sdk", llvm::cl::desc("Path to the SDK"), + llvm::cl::cat(Category)); + +static llvm::cl::opt +Target("target", llvm::cl::desc("Target triple"), + llvm::cl::cat(Category)); + +static llvm::cl::opt +SwiftVersion("swift-version", llvm::cl::desc("Interpret input according to a specific Swift language version number"), llvm::cl::cat(Category)); + +static llvm::cl::opt +PrettyPrint("pretty-print", llvm::cl::desc("Pretty-print the resulting Symbol Graph JSON"), llvm::cl::cat(Category)); + +static llvm::cl::opt +MinimumAccessLevel("minimum-access-level", llvm::cl::desc("Include symbols with this access level or more"), llvm::cl::cat(Category)); + +static llvm::cl::opt +OutputPath("o", llvm::cl::desc("Symbol Graph JSON Output Path"), llvm::cl::cat(Category)); +} // end namespace options + +int swift_symbolgraph_extract_main(ArrayRef Args, const char *Argv0, void *MainAddr) { + INITIALIZE_LLVM(); + + llvm::cl::HideUnrelatedOptions(options::Category); + + // LLVM Command Line expects to trim off argv[0]. + SmallVector ArgsWithArgv0 { Argv0 }; + ArgsWithArgv0.append(Args.begin(), Args.end()); + + llvm::cl::ParseCommandLineOptions(ArgsWithArgv0.size(), + llvm::makeArrayRef(ArgsWithArgv0).data(), "Swift Symbol Graph Extractor\n"); + + CompilerInvocation Invocation; + + Invocation.setMainExecutablePath( + llvm::sys::fs::getMainExecutable(Argv0, MainAddr)); + Invocation.setModuleName("swift_symbolgraph_extract"); + Invocation.setSDKPath(options::SDK); + Invocation.setTargetTriple(options::Target); + + std::vector FrameworkSearchPaths; + for (const auto &Path : options::FrameworkSearchPaths) { + FrameworkSearchPaths.push_back({ Path, /*isSystem*/ false}); + } + Invocation.setFrameworkSearchPaths(FrameworkSearchPaths); + Invocation.getSearchPathOptions().LibrarySearchPaths = options::LibrarySearchPaths; + Invocation.setImportSearchPaths(options::ImportSearchPaths); + + Invocation.getLangOptions().EnableObjCInterop = llvm::Triple(options::Target).isOSDarwin(); + Invocation.getLangOptions().DebuggerSupport = true; + + Invocation.getFrontendOptions().EnableLibraryEvolution = true; + + Invocation.setClangModuleCachePath(options::ModuleCachePath); + Invocation.getClangImporterOptions().ModuleCachePath = options::ModuleCachePath; + + if (!options::SwiftVersion.empty()) { + using version::Version; + bool isValid = false; + if (auto Version = Version::parseVersionString(options::SwiftVersion, + SourceLoc(), nullptr)) { + if (auto Effective = Version.getValue().getEffectiveLanguageVersion()) { + Invocation.getLangOptions().EffectiveLanguageVersion = *Effective; + isValid = true; + } + } + if (!isValid) { + llvm::errs() << "Unsupported Swift Version.\n"; + return EXIT_FAILURE; + } + } + + symbolgraphgen::SymbolGraphOptions Options { + options::OutputPath, + llvm::Triple(options::Target), + options::PrettyPrint, + AccessLevel::Public, + }; + + if (!options::MinimumAccessLevel.empty()) { + Options.MinimumAccessLevel = + llvm::StringSwitch(options::MinimumAccessLevel) + .Case("open", AccessLevel::Open) + .Case("public", AccessLevel::Public) + .Case("internal", AccessLevel::Internal) + .Case("fileprivate", AccessLevel::FilePrivate) + .Case("private", AccessLevel::Private) + .Default(AccessLevel::Public); + } + + PrintingDiagnosticConsumer DiagPrinter; + + CompilerInstance CI; + CI.getDiags().addConsumer(DiagPrinter); + + if (CI.setup(Invocation)) { + llvm::outs() << "Failed to setup compiler instance\n"; + return EXIT_FAILURE; + } + + auto M = CI.getASTContext().getModuleByName(options::ModuleName); + if (!M) { + llvm::errs() + << "Couldn't load module '" << options::ModuleName << '\'' + << " in the current SDK and search paths.\n"; + SmallVector VisibleModuleNames; + CI.getASTContext().getVisibleTopLevelModuleNames(VisibleModuleNames); + + if (VisibleModuleNames.empty()) { + llvm::errs() << "Could not find any modules.\n"; + } else { + std::sort(VisibleModuleNames.begin(), VisibleModuleNames.end(), + [](const Identifier &A, const Identifier &B) -> bool { + return A.str() < B.str(); + }); + llvm::errs() << "Current visible modules:\n"; + for (const auto &ModuleName : VisibleModuleNames) { + llvm::errs() << ModuleName.str() << "\n"; + } + } + return EXIT_FAILURE; + } + + return symbolgraphgen::emitSymbolGraphForModule(M, + Options); +}