mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
The goal for https://bugs.swift.org/browse/SR-710 is to automatically generate a list of tests to run for XCTest on Linux. The prevailing approach is to generate this list using SourceKit, which is to be ported to Linux. SourceKit uses libIndex's concept of `isTestCandidate` to determine what constitutes a test. `isTestCandidate` is tested via `test/SourceKit/Indexing/index.swift`. On Linux, however, the list of tests to be run will be generated by some tool and placed in a file that is separate from the source file that defines the test method. Therefore, the test method must not be "private", since it needs to be accessed from a separate file. This commit adds two test files: one that verifies the behavior on Linux, and one that verifies the behavior on platforms with Objective-C interop. It also (1) simplifies the `isTestCandidate` function, and (2) adds the interop-specific logic. This commit does not remove the existing `isTestCandidate` tests in `test/SourceKit/Indexing/index.swift`; that is left for a future commit.
1081 lines
32 KiB
C++
1081 lines
32 KiB
C++
//===--- Index.cpp --------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/Index/Index.h"
|
|
|
|
#include "swift/AST/AST.h"
|
|
#include "swift/AST/SourceEntityWalker.h"
|
|
#include "swift/AST/USRGeneration.h"
|
|
#include "swift/Basic/SourceManager.h"
|
|
#include "swift/Basic/StringExtras.h"
|
|
#include "llvm/ADT/APInt.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::index;
|
|
|
|
static bool printDisplayName(const swift::ValueDecl *D, llvm::raw_ostream &OS) {
|
|
if (!D->hasName())
|
|
return true;
|
|
|
|
OS << D->getFullName();
|
|
return false;
|
|
}
|
|
|
|
namespace {
|
|
// Adapter providing a common interface for a SourceFile/Module.
|
|
class SourceFileOrModule {
|
|
llvm::PointerUnion<SourceFile *, Module *> SFOrMod;
|
|
|
|
public:
|
|
SourceFileOrModule(SourceFile &SF) : SFOrMod(&SF) {}
|
|
SourceFileOrModule(Module &Mod) : SFOrMod(&Mod) {}
|
|
|
|
SourceFile *getAsSourceFile() const {
|
|
return SFOrMod.dyn_cast<SourceFile *>();
|
|
}
|
|
|
|
Module *getAsModule() const { return SFOrMod.dyn_cast<Module *>(); }
|
|
|
|
Module &getModule() const {
|
|
if (auto SF = SFOrMod.dyn_cast<SourceFile *>())
|
|
return *SF->getParentModule();
|
|
return *SFOrMod.get<Module *>();
|
|
}
|
|
|
|
ArrayRef<FileUnit *> getFiles() const {
|
|
return SFOrMod.is<SourceFile *>() ? *SFOrMod.getAddrOfPtr1()
|
|
: SFOrMod.get<Module *>()->getFiles();
|
|
}
|
|
|
|
StringRef getFilename() const {
|
|
if (SourceFile *SF = SFOrMod.dyn_cast<SourceFile *>())
|
|
return SF->getFilename();
|
|
return SFOrMod.get<Module *>()->getModuleFilename();
|
|
}
|
|
|
|
void
|
|
getImportedModules(SmallVectorImpl<Module::ImportedModule> &Modules) const {
|
|
if (SourceFile *SF = SFOrMod.dyn_cast<SourceFile *>()) {
|
|
SF->getImportedModules(Modules, Module::ImportFilter::All);
|
|
} else {
|
|
SFOrMod.get<Module *>()->getImportedModules(Modules,
|
|
Module::ImportFilter::All);
|
|
}
|
|
}
|
|
};
|
|
|
|
class IndexSwiftASTWalker : public SourceEntityWalker {
|
|
IndexDataConsumer &IdxConsumer;
|
|
SourceManager &SrcMgr;
|
|
unsigned BufferID;
|
|
bool enableWarnings;
|
|
|
|
bool IsModuleFile = false;
|
|
bool isSystemModule = false;
|
|
struct Entity {
|
|
Decl *D;
|
|
SymbolKind Kind;
|
|
SymbolSubKindSet SubKinds;
|
|
SymbolRoleSet Roles;
|
|
};
|
|
SmallVector<Entity, 6> EntitiesStack;
|
|
SmallVector<Expr *, 8> ExprStack;
|
|
bool Cancelled = false;
|
|
|
|
struct NameAndUSR {
|
|
StringRef USR;
|
|
StringRef name;
|
|
};
|
|
typedef llvm::PointerIntPair<Decl *, 3> DeclAccessorPair;
|
|
llvm::DenseMap<Decl *, NameAndUSR> nameAndUSRCache;
|
|
llvm::DenseMap<DeclAccessorPair, StringRef> accessorUSRCache;
|
|
StringScratchSpace stringStorage;
|
|
|
|
bool getNameAndUSR(ValueDecl *D, StringRef &name, StringRef &USR) {
|
|
auto &result = nameAndUSRCache[D];
|
|
if (result.USR.empty()) {
|
|
SmallString<128> storage;
|
|
{
|
|
llvm::raw_svector_ostream OS(storage);
|
|
if (ide::printDeclUSR(D, OS))
|
|
return true;
|
|
result.USR = stringStorage.copyString(OS.str());
|
|
}
|
|
|
|
storage.clear();
|
|
{
|
|
llvm::raw_svector_ostream OS(storage);
|
|
printDisplayName(D, OS);
|
|
result.name = stringStorage.copyString(OS.str());
|
|
}
|
|
}
|
|
|
|
name = result.name;
|
|
USR = result.USR;
|
|
return false;
|
|
}
|
|
|
|
StringRef getAccessorUSR(AbstractStorageDecl *D, AccessorKind AK) {
|
|
assert(AK != AccessorKind::NotAccessor);
|
|
assert(static_cast<int>(AK) < 0x111 && "AccessorKind too big for pair");
|
|
DeclAccessorPair key(D, static_cast<int>(AK));
|
|
auto &result = accessorUSRCache[key];
|
|
if (result.empty()) {
|
|
SmallString<128> storage;
|
|
llvm::raw_svector_ostream OS(storage);
|
|
ide::printAccessorUSR(D, AK, OS);
|
|
result = stringStorage.copyString(OS.str());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public:
|
|
IndexSwiftASTWalker(IndexDataConsumer &IdxConsumer, ASTContext &Ctx,
|
|
unsigned BufferID)
|
|
: IdxConsumer(IdxConsumer), SrcMgr(Ctx.SourceMgr), BufferID(BufferID),
|
|
enableWarnings(IdxConsumer.enableWarnings()) {}
|
|
~IndexSwiftASTWalker() { assert(Cancelled || EntitiesStack.empty()); }
|
|
|
|
void visitModule(Module &Mod, StringRef Hash);
|
|
|
|
private:
|
|
bool visitImports(SourceFileOrModule Mod,
|
|
llvm::SmallPtrSet<Module *, 16> &Visited);
|
|
|
|
bool handleSourceOrModuleFile(SourceFileOrModule SFOrMod, StringRef KnownHash,
|
|
bool &HashIsKnown);
|
|
|
|
bool walkToDeclPre(Decl *D, CharSourceRange Range) override {
|
|
// Do not handle unavailable decls.
|
|
if (AvailableAttr::isUnavailable(D))
|
|
return false;
|
|
if (FuncDecl *FD = dyn_cast<FuncDecl>(D)) {
|
|
if (FD->isAccessor() && getParentDecl() != FD->getAccessorStorageDecl())
|
|
return false; // already handled as part of the var decl.
|
|
}
|
|
if (ValueDecl *VD = dyn_cast<ValueDecl>(D)) {
|
|
if (!report(VD))
|
|
return false;
|
|
if (SubscriptDecl *SD = dyn_cast<SubscriptDecl>(VD)) {
|
|
// Avoid indexing the indices, only walk the getter/setter.
|
|
if (SD->getGetter())
|
|
if (SourceEntityWalker::walk(cast<Decl>(SD->getGetter())))
|
|
return false;
|
|
if (SD->getSetter())
|
|
if (SourceEntityWalker::walk(cast<Decl>(SD->getSetter())))
|
|
return false;
|
|
if (SD->hasAddressors()) {
|
|
if (auto FD = SD->getAddressor())
|
|
SourceEntityWalker::walk(cast<Decl>(FD));
|
|
if (Cancelled)
|
|
return false;
|
|
if (auto FD = SD->getMutableAddressor())
|
|
SourceEntityWalker::walk(cast<Decl>(FD));
|
|
}
|
|
walkToDeclPost(D);
|
|
return false; // already walked what we needed.
|
|
}
|
|
}
|
|
if (ExtensionDecl *ED = dyn_cast<ExtensionDecl>(D))
|
|
return reportExtension(ED);
|
|
return true;
|
|
}
|
|
|
|
bool walkToDeclPost(Decl *D) override {
|
|
if (Cancelled)
|
|
return false;
|
|
|
|
if (getParentDecl() == D)
|
|
return finishCurrentEntity();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool walkToExprPre(Expr *E) override {
|
|
if (Cancelled)
|
|
return false;
|
|
ExprStack.push_back(E);
|
|
return true;
|
|
}
|
|
|
|
bool walkToExprPost(Expr *E) override {
|
|
if (Cancelled)
|
|
return false;
|
|
assert(ExprStack.back() == E);
|
|
ExprStack.pop_back();
|
|
return true;
|
|
}
|
|
|
|
bool visitDeclReference(ValueDecl *D, CharSourceRange Range,
|
|
TypeDecl *CtorTyRef, Type T) override {
|
|
SourceLoc Loc = Range.getStart();
|
|
if (CtorTyRef)
|
|
if (!reportRef(CtorTyRef, Loc))
|
|
return false;
|
|
if (!reportRef(D, Loc))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
Decl *getParentDecl() {
|
|
if (!EntitiesStack.empty())
|
|
return EntitiesStack.back().D;
|
|
return nullptr;
|
|
}
|
|
|
|
Expr *getParentExpr() {
|
|
if (ExprStack.size() >= 2)
|
|
return ExprStack.end()[-2];
|
|
return nullptr;
|
|
}
|
|
|
|
bool reportExtension(ExtensionDecl *D);
|
|
|
|
bool report(ValueDecl *D);
|
|
bool reportRef(ValueDecl *D, SourceLoc Loc);
|
|
|
|
bool startEntityDecl(ValueDecl *D);
|
|
bool startEntityRef(ValueDecl *D, SourceLoc Loc);
|
|
bool startEntity(ValueDecl *D, const IndexSymbol &Info);
|
|
|
|
bool passRelated(ValueDecl *D, SourceLoc Loc);
|
|
bool passInheritedTypes(ArrayRef<TypeLoc> Inherited);
|
|
bool passRelatedType(const TypeLoc &Ty);
|
|
NominalTypeDecl *getTypeLocAsNominalTypeDecl(const TypeLoc &Ty);
|
|
|
|
bool reportPseudoGetterDecl(VarDecl *D) {
|
|
return reportPseudoAccessor(D, AccessorKind::IsGetter, /*IsRef=*/false,
|
|
D->getLoc());
|
|
}
|
|
bool reportPseudoSetterDecl(VarDecl *D) {
|
|
return reportPseudoAccessor(D, AccessorKind::IsSetter, /*IsRef=*/false,
|
|
D->getLoc());
|
|
}
|
|
bool reportPseudoAccessor(AbstractStorageDecl *D, AccessorKind AccKind,
|
|
bool IsRef, SourceLoc Loc);
|
|
|
|
bool finishCurrentEntity() {
|
|
Entity CurrEnt = EntitiesStack.pop_back_val();
|
|
assert(CurrEnt.Kind != SymbolKind::Unknown);
|
|
if (!IdxConsumer.finishSourceEntity(CurrEnt.Kind, CurrEnt.SubKinds,
|
|
CurrEnt.Roles)) {
|
|
Cancelled = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool initIndexSymbol(ValueDecl *D, SourceLoc Loc, bool IsRef,
|
|
IndexSymbol &Info);
|
|
bool initFuncDeclIndexSymbol(ValueDecl *D, IndexSymbol &Info);
|
|
bool initCallRefIndexSymbol(Expr *CurrentE, Expr *ParentE, ValueDecl *D,
|
|
SourceLoc Loc, IndexSymbol &Info);
|
|
|
|
std::pair<unsigned, unsigned> getLineCol(SourceLoc Loc) {
|
|
if (Loc.isInvalid())
|
|
return std::make_pair(0, 0);
|
|
return SrcMgr.getLineAndColumn(Loc, BufferID);
|
|
}
|
|
|
|
bool shouldIndex(ValueDecl *D) const {
|
|
if (D->isImplicit())
|
|
return false;
|
|
if (isLocal(D))
|
|
return false;
|
|
if (D->isPrivateStdlibDecl())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool isLocal(ValueDecl *D) const {
|
|
return D->getDeclContext()->getLocalContext();
|
|
}
|
|
|
|
void getModuleHash(SourceFileOrModule SFOrMod, llvm::raw_ostream &OS);
|
|
llvm::hash_code hashModule(llvm::hash_code code, SourceFileOrModule SFOrMod);
|
|
llvm::hash_code hashFileReference(llvm::hash_code code,
|
|
SourceFileOrModule SFOrMod);
|
|
void getRecursiveModuleImports(Module &Mod,
|
|
SmallVectorImpl<Module *> &Imports);
|
|
void collectRecursiveModuleImports(Module &Mod,
|
|
llvm::SmallPtrSet<Module *, 16> &Visited);
|
|
|
|
template <typename F>
|
|
void warn(F log) {
|
|
if (!enableWarnings)
|
|
return;
|
|
|
|
SmallString<128> warning;
|
|
llvm::raw_svector_ostream OS(warning);
|
|
log(OS);
|
|
}
|
|
|
|
// This maps a module to all its imports, recursively.
|
|
llvm::DenseMap<Module *, llvm::SmallVector<Module *, 4>> ImportsMap;
|
|
};
|
|
} // anonymous namespace
|
|
|
|
void IndexSwiftASTWalker::visitModule(Module &Mod, StringRef KnownHash) {
|
|
SourceFile *SrcFile = nullptr;
|
|
for (auto File : Mod.getFiles()) {
|
|
if (auto SF = dyn_cast<SourceFile>(File)) {
|
|
auto BufID = SF->getBufferID();
|
|
if (BufID.hasValue() && *BufID == BufferID) {
|
|
SrcFile = SF;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HashIsKnown;
|
|
if (SrcFile != nullptr) {
|
|
IsModuleFile = false;
|
|
if (!handleSourceOrModuleFile(*SrcFile, KnownHash, HashIsKnown))
|
|
return;
|
|
if (HashIsKnown)
|
|
return; // No need to report symbols.
|
|
walk(*SrcFile);
|
|
} else {
|
|
IsModuleFile = true;
|
|
isSystemModule = Mod.isSystemModule();
|
|
if (!handleSourceOrModuleFile(Mod, KnownHash, HashIsKnown))
|
|
return;
|
|
if (HashIsKnown)
|
|
return; // No need to report symbols.
|
|
walk(Mod);
|
|
}
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::handleSourceOrModuleFile(SourceFileOrModule SFOrMod,
|
|
StringRef KnownHash,
|
|
bool &HashIsKnown) {
|
|
// Common reporting for TU/module file.
|
|
|
|
SmallString<32> HashBuf;
|
|
{
|
|
llvm::raw_svector_ostream HashOS(HashBuf);
|
|
getModuleHash(SFOrMod, HashOS);
|
|
StringRef Hash = HashOS.str();
|
|
HashIsKnown = Hash == KnownHash;
|
|
if (!IdxConsumer.recordHash(Hash, HashIsKnown))
|
|
return false;
|
|
}
|
|
|
|
// We always report the dependencies, even if the hash is known.
|
|
llvm::SmallPtrSet<Module *, 16> Visited;
|
|
return visitImports(SFOrMod, Visited);
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::visitImports(
|
|
SourceFileOrModule TopMod, llvm::SmallPtrSet<Module *, 16> &Visited) {
|
|
// Dependencies of the stdlib module (like SwiftShims module) are
|
|
// implementation details.
|
|
if (TopMod.getModule().isStdlibModule())
|
|
return true;
|
|
|
|
bool IsNew = Visited.insert(&TopMod.getModule()).second;
|
|
if (!IsNew)
|
|
return true;
|
|
|
|
SmallVector<Module::ImportedModule, 8> Imports;
|
|
TopMod.getImportedModules(Imports);
|
|
|
|
llvm::SmallPtrSet<Module *, 8> Reported;
|
|
for (auto Import : Imports) {
|
|
Module *Mod = Import.second;
|
|
bool NewReport = Reported.insert(Mod).second;
|
|
if (!NewReport)
|
|
continue;
|
|
|
|
// FIXME: Handle modules with multiple source files; these will fail on
|
|
// getModuleFilename() (by returning an empty path). Note that such modules
|
|
// may be heterogeneous.
|
|
StringRef Path = Mod->getModuleFilename();
|
|
if (Path.empty() || Path == TopMod.getFilename())
|
|
continue; // this is a submodule.
|
|
|
|
SymbolKind ImportKind = SymbolKind::Unknown;
|
|
for (auto File : Mod->getFiles()) {
|
|
switch (File->getKind()) {
|
|
case FileUnitKind::Source:
|
|
case FileUnitKind::Builtin:
|
|
case FileUnitKind::Derived:
|
|
break;
|
|
case FileUnitKind::SerializedAST:
|
|
assert(ImportKind == SymbolKind::Unknown &&
|
|
"cannot handle multi-file modules");
|
|
ImportKind = SymbolKind::Module;
|
|
break;
|
|
case FileUnitKind::ClangModule:
|
|
assert(ImportKind == SymbolKind::Unknown &&
|
|
"cannot handle multi-file modules");
|
|
ImportKind = SymbolKind::ClangModule;
|
|
break;
|
|
}
|
|
}
|
|
if (ImportKind == SymbolKind::Unknown)
|
|
continue;
|
|
|
|
StringRef Hash;
|
|
SmallString<32> HashBuf;
|
|
if (ImportKind != SymbolKind::ClangModule) {
|
|
llvm::raw_svector_ostream HashOS(HashBuf);
|
|
getModuleHash(*Mod, HashOS);
|
|
Hash = HashOS.str();
|
|
}
|
|
|
|
if (!IdxConsumer.startDependency(ImportKind, Mod->getName().str(), Path,
|
|
Mod->isSystemModule(), Hash))
|
|
return false;
|
|
if (ImportKind != SymbolKind::ClangModule)
|
|
if (!visitImports(*Mod, Visited))
|
|
return false;
|
|
if (!IdxConsumer.finishDependency(ImportKind))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::startEntityDecl(ValueDecl *D) {
|
|
if (!shouldIndex(D))
|
|
return false;
|
|
|
|
SourceLoc Loc = D->getLoc();
|
|
if (Loc.isInvalid() && !IsModuleFile)
|
|
return false;
|
|
|
|
if (isa<FuncDecl>(D)) {
|
|
IndexSymbol Info;
|
|
if (initFuncDeclIndexSymbol(D, Info))
|
|
return false;
|
|
|
|
return startEntity(D, Info);
|
|
|
|
} else {
|
|
IndexSymbol Info;
|
|
if (initIndexSymbol(D, Loc, /*isRef=*/false, Info))
|
|
return false;
|
|
|
|
return startEntity(D, Info);
|
|
}
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::startEntityRef(ValueDecl *D, SourceLoc Loc) {
|
|
if (!shouldIndex(D))
|
|
return false;
|
|
|
|
if (Loc.isInvalid())
|
|
return false;
|
|
|
|
if (isa<AbstractFunctionDecl>(D)) {
|
|
IndexSymbol Info;
|
|
if (initCallRefIndexSymbol(ExprStack.back(), getParentExpr(), D, Loc, Info))
|
|
return false;
|
|
|
|
return startEntity(D, Info);
|
|
|
|
} else {
|
|
IndexSymbol Info;
|
|
if (initIndexSymbol(D, Loc, /*isRef=*/true, Info))
|
|
return false;
|
|
|
|
return startEntity(D, Info);
|
|
}
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::startEntity(ValueDecl *D, const IndexSymbol &Info) {
|
|
if (!IdxConsumer.startSourceEntity(Info)) {
|
|
Cancelled = true;
|
|
return false;
|
|
}
|
|
|
|
EntitiesStack.push_back({D, Info.kind, Info.subKinds, Info.roles});
|
|
return true;
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::passRelated(ValueDecl *D, SourceLoc Loc) {
|
|
if (!shouldIndex(D))
|
|
return false;
|
|
|
|
IndexSymbol Info;
|
|
if (initIndexSymbol(D, Loc, /*isRef=*/true, Info))
|
|
return false;
|
|
|
|
if (!IdxConsumer.recordRelatedEntity(Info)) {
|
|
Cancelled = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::passInheritedTypes(ArrayRef<TypeLoc> Inherited) {
|
|
for (auto Base : Inherited) {
|
|
passRelatedType(Base);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::passRelatedType(const TypeLoc &Ty) {
|
|
if (IdentTypeRepr *T = dyn_cast_or_null<IdentTypeRepr>(Ty.getTypeRepr())) {
|
|
auto Comps = T->getComponentRange();
|
|
if (auto NTD =
|
|
dyn_cast_or_null<NominalTypeDecl>(Comps.back()->getBoundDecl())) {
|
|
if (!passRelated(NTD, Comps.back()->getIdLoc()))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (Ty.getType()) {
|
|
if (auto nominal = dyn_cast_or_null<NominalTypeDecl>(
|
|
Ty.getType()->getDirectlyReferencedTypeDecl()))
|
|
if (!passRelated(nominal, Ty.getLoc()))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static SymbolSubKind getSubKindForAccessor(AccessorKind AK) {
|
|
switch (AK) {
|
|
case AccessorKind::NotAccessor: return SymbolSubKind::None;
|
|
case AccessorKind::IsGetter: return SymbolSubKind::AccessorGetter;
|
|
case AccessorKind::IsSetter: return SymbolSubKind::AccessorSetter;
|
|
case AccessorKind::IsWillSet: return SymbolSubKind::AccessorWillSet;
|
|
case AccessorKind::IsDidSet: return SymbolSubKind::AccessorDidSet;
|
|
case AccessorKind::IsAddressor: return SymbolSubKind::AccessorAddressor;
|
|
case AccessorKind::IsMutableAddressor:
|
|
return SymbolSubKind::AccessorMutableAddressor;
|
|
case AccessorKind::IsMaterializeForSet:
|
|
llvm_unreachable("unexpected MaterializeForSet");
|
|
}
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::reportPseudoAccessor(AbstractStorageDecl *D,
|
|
AccessorKind AccKind, bool IsRef,
|
|
SourceLoc Loc) {
|
|
if (!shouldIndex(D))
|
|
return true; // continue walking.
|
|
|
|
auto handleInfo = [this, D, AccKind](IndexSymbol &Info) {
|
|
Info.kind = SymbolKind::Accessor;
|
|
Info.subKinds |= getSubKindForAccessor(AccKind);
|
|
Info.name = "";
|
|
Info.USR = getAccessorUSR(D, AccKind);
|
|
Info.group = "";
|
|
|
|
if (!IdxConsumer.startSourceEntity(Info)) {
|
|
Cancelled = true;
|
|
return false;
|
|
}
|
|
if (!IdxConsumer.finishSourceEntity(Info.kind, Info.subKinds, Info.roles)) {
|
|
Cancelled = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (IsRef) {
|
|
IndexSymbol Info;
|
|
if (initCallRefIndexSymbol(ExprStack.back(), getParentExpr(), D, Loc, Info))
|
|
return true; // continue walking.
|
|
|
|
return handleInfo(Info);
|
|
|
|
} else {
|
|
IndexSymbol Info;
|
|
if (initIndexSymbol(D, Loc, IsRef, Info))
|
|
return true; // continue walking.
|
|
|
|
return handleInfo(Info);
|
|
}
|
|
}
|
|
|
|
NominalTypeDecl *
|
|
IndexSwiftASTWalker::getTypeLocAsNominalTypeDecl(const TypeLoc &Ty) {
|
|
if (Type T = Ty.getType())
|
|
return dyn_cast_or_null<NominalTypeDecl>(
|
|
T->getDirectlyReferencedTypeDecl());
|
|
if (IdentTypeRepr *T = dyn_cast_or_null<IdentTypeRepr>(Ty.getTypeRepr())) {
|
|
auto Comp = T->getComponentRange().back();
|
|
if (auto NTD = dyn_cast_or_null<NominalTypeDecl>(Comp->getBoundDecl()))
|
|
return NTD;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::reportExtension(ExtensionDecl *D) {
|
|
SourceLoc Loc = D->getExtendedTypeLoc().getSourceRange().Start;
|
|
if (!D->getExtendedType())
|
|
return true;
|
|
NominalTypeDecl *NTD = D->getExtendedType()->getAnyNominal();
|
|
if (!NTD)
|
|
return true;
|
|
if (!shouldIndex(NTD))
|
|
return true;
|
|
|
|
IndexSymbol Info;
|
|
if (initIndexSymbol(NTD, Loc, /*isRef=*/false, Info))
|
|
return true;
|
|
|
|
Info.kind = getSymbolKindForDecl(D);
|
|
if (isa<StructDecl>(NTD))
|
|
Info.subKinds |= SymbolSubKind::ExtensionOfStruct;
|
|
else if (isa<ClassDecl>(NTD))
|
|
Info.subKinds |= SymbolSubKind::ExtensionOfClass;
|
|
else if (isa<EnumDecl>(NTD))
|
|
Info.subKinds |= SymbolSubKind::ExtensionOfEnum;
|
|
else if (isa<ProtocolDecl>(NTD))
|
|
Info.subKinds |= SymbolSubKind::ExtensionOfProtocol;
|
|
|
|
assert(Info.subKinds != 0);
|
|
|
|
if (!IdxConsumer.startSourceEntity(Info)) {
|
|
Cancelled = true;
|
|
return false;
|
|
}
|
|
|
|
passInheritedTypes(D->getInherited());
|
|
if (Cancelled)
|
|
return false;
|
|
|
|
EntitiesStack.push_back({D, Info.kind, Info.subKinds, Info.roles});
|
|
return true;
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::report(ValueDecl *D) {
|
|
if (startEntityDecl(D)) {
|
|
if (TypeDecl *TD = dyn_cast<NominalTypeDecl>(D))
|
|
passInheritedTypes(TD->getInherited());
|
|
if (Cancelled)
|
|
return false;
|
|
if (auto Overridden = D->getOverriddenDecl()) {
|
|
passRelated(Overridden, SourceLoc());
|
|
if (Cancelled)
|
|
return false;
|
|
}
|
|
for (auto Conf : D->getSatisfiedProtocolRequirements()) {
|
|
passRelated(Conf, SourceLoc());
|
|
if (Cancelled)
|
|
return false;
|
|
}
|
|
|
|
// Pass accessors.
|
|
if (auto VarD = dyn_cast<VarDecl>(D)) {
|
|
if (!VarD->getGetter() && !VarD->getSetter()) {
|
|
// No actual getter or setter, pass 'pseudo' accessors.
|
|
// We create accessor entities so we can implement the functionality
|
|
// of libclang, which reports implicit method property accessor
|
|
// declarations, invocations, and overrides for properties.
|
|
// Note that an ObjC class subclassing from a Swift class, may still
|
|
// be able to override its non-computed-property-accessors via a
|
|
// method.
|
|
if (!reportPseudoGetterDecl(VarD))
|
|
return false;
|
|
if (!reportPseudoSetterDecl(VarD))
|
|
return false;
|
|
} else {
|
|
if (auto FD = VarD->getGetter())
|
|
SourceEntityWalker::walk(cast<Decl>(FD));
|
|
if (Cancelled)
|
|
return false;
|
|
if (auto FD = VarD->getSetter())
|
|
SourceEntityWalker::walk(cast<Decl>(FD));
|
|
if (Cancelled)
|
|
return false;
|
|
if (VarD->hasObservers()) {
|
|
if (auto FD = VarD->getWillSetFunc())
|
|
SourceEntityWalker::walk(cast<Decl>(FD));
|
|
if (Cancelled)
|
|
return false;
|
|
if (auto FD = VarD->getDidSetFunc())
|
|
SourceEntityWalker::walk(cast<Decl>(FD));
|
|
if (Cancelled)
|
|
return false;
|
|
}
|
|
if (VarD->hasAddressors()) {
|
|
if (auto FD = VarD->getAddressor())
|
|
SourceEntityWalker::walk(cast<Decl>(FD));
|
|
if (Cancelled)
|
|
return false;
|
|
if (auto FD = VarD->getMutableAddressor())
|
|
SourceEntityWalker::walk(cast<Decl>(FD));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return !Cancelled;
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::reportRef(ValueDecl *D, SourceLoc Loc) {
|
|
if (startEntityRef(D, Loc)) {
|
|
// Report the accessors that were utilized.
|
|
if (isa<AbstractStorageDecl>(D) && getParentExpr()) {
|
|
bool UsesGetter = false;
|
|
bool UsesSetter = false;
|
|
Expr *CurrE = ExprStack.back();
|
|
Expr *Parent = getParentExpr();
|
|
bool isLValue =
|
|
!CurrE->getType().isNull() && CurrE->getType()->is<LValueType>();
|
|
if (isa<LoadExpr>(Parent) || !isLValue) {
|
|
UsesGetter = true;
|
|
} else if (isa<AssignExpr>(Parent)) {
|
|
UsesSetter = true;
|
|
} else {
|
|
UsesGetter = UsesSetter = true;
|
|
}
|
|
|
|
AbstractStorageDecl *ASD = cast<AbstractStorageDecl>(D);
|
|
if (UsesGetter)
|
|
if (!reportPseudoAccessor(ASD, AccessorKind::IsGetter, /*IsRef=*/true,
|
|
Loc))
|
|
return false;
|
|
if (UsesSetter)
|
|
if (!reportPseudoAccessor(ASD, AccessorKind::IsSetter, /*IsRef=*/true,
|
|
Loc))
|
|
return false;
|
|
}
|
|
|
|
assert(EntitiesStack.back().D == D);
|
|
return finishCurrentEntity();
|
|
}
|
|
|
|
return !Cancelled;
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::initIndexSymbol(ValueDecl *D, SourceLoc Loc,
|
|
bool IsRef, IndexSymbol &Info) {
|
|
assert(D);
|
|
Info.decl = D;
|
|
Info.kind = getSymbolKindForDecl(D);
|
|
if (Info.kind == SymbolKind::Unknown)
|
|
return true;
|
|
|
|
if (Info.kind == SymbolKind::Accessor)
|
|
Info.subKinds |= getSubKindForAccessor(cast<FuncDecl>(D)->getAccessorKind());
|
|
// Cannot be extension, which is not a ValueDecl.
|
|
|
|
if (IsRef)
|
|
Info.roles |= (unsigned)SymbolRole::Reference;
|
|
else
|
|
Info.roles |= (unsigned)SymbolRole::Definition;
|
|
|
|
if (getNameAndUSR(D, Info.name, Info.USR))
|
|
return true;
|
|
|
|
std::tie(Info.line, Info.column) = getLineCol(Loc);
|
|
if (!IsRef) {
|
|
if (auto Group = D->getGroupName())
|
|
Info.group = Group.getValue();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static NominalTypeDecl *getNominalParent(ValueDecl *D) {
|
|
Type Ty = D->getDeclContext()->getDeclaredTypeOfContext();
|
|
if (!Ty)
|
|
return nullptr;
|
|
return Ty->getAnyNominal();
|
|
}
|
|
|
|
static bool isTestCandidate(ValueDecl *D) {
|
|
if (!D->hasName())
|
|
return false;
|
|
|
|
// A 'test candidate' is:
|
|
// 1. An instance method...
|
|
auto FD = dyn_cast<FuncDecl>(D);
|
|
if (!FD)
|
|
return false;
|
|
if (!D->isInstanceMember())
|
|
return false;
|
|
|
|
// 2. ...on a class or extension (not a struct)...
|
|
auto NTD = getNominalParent(D);
|
|
if (!NTD)
|
|
return false;
|
|
if (!isa<ClassDecl>(NTD))
|
|
return false;
|
|
|
|
// 3. ...that returns void...
|
|
Type RetTy = FD->getResultType();
|
|
if (RetTy && !RetTy->isVoid())
|
|
return false;
|
|
|
|
// 4. ...takes no parameters...
|
|
if (FD->getParameterLists().size() != 2)
|
|
return false;
|
|
if (FD->getParameterList(1)->size() != 0)
|
|
return false;
|
|
|
|
// 5. ...is of at least 'internal' accessibility (unless we can use
|
|
// Objective-C reflection)...
|
|
#if SWIFT_OBJC_INTEROP
|
|
if (D->getFormalAccess() < Accessibility::Internal)
|
|
return false;
|
|
#endif
|
|
|
|
// 6. ...and starts with "test".
|
|
if (FD->getName().str().startswith("test"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::initFuncDeclIndexSymbol(ValueDecl *D,
|
|
IndexSymbol &Info) {
|
|
if (initIndexSymbol(D, D->getLoc(), /*IsRef=*/false, Info))
|
|
return true;
|
|
|
|
if (isTestCandidate(D))
|
|
Info.subKinds |= SymbolSubKind::UnitTest;
|
|
|
|
if (auto Group = D->getGroupName())
|
|
Info.group = Group.getValue();
|
|
return false;
|
|
}
|
|
|
|
static bool isSuperRefExpr(Expr *E) {
|
|
if (!E)
|
|
return false;
|
|
if (isa<SuperRefExpr>(E))
|
|
return true;
|
|
if (auto LoadE = dyn_cast<LoadExpr>(E))
|
|
return isSuperRefExpr(LoadE->getSubExpr());
|
|
return false;
|
|
}
|
|
|
|
static bool isDynamicCall(Expr *BaseE, ValueDecl *D) {
|
|
// The call is 'dynamic' if the method is not of a struct and the
|
|
// receiver is not 'super'. Note that if the receiver is 'super' that
|
|
// does not mean that the call is statically determined (an extension
|
|
// method may have injected itself in the super hierarchy).
|
|
// For our purposes 'dynamic' means that the method call cannot invoke
|
|
// a method in a subclass.
|
|
auto TyD = getNominalParent(D);
|
|
if (!TyD)
|
|
return false;
|
|
if (isa<StructDecl>(TyD))
|
|
return false;
|
|
if (isSuperRefExpr(BaseE))
|
|
return false;
|
|
if (BaseE->getType()->is<MetatypeType>())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IndexSwiftASTWalker::initCallRefIndexSymbol(Expr *CurrentE, Expr *ParentE,
|
|
ValueDecl *D, SourceLoc Loc,
|
|
IndexSymbol &Info) {
|
|
if (!ParentE)
|
|
return true;
|
|
|
|
if (initIndexSymbol(D, Loc, /*IsRef=*/true, Info))
|
|
return true;
|
|
|
|
Info.roles |= (unsigned)SymbolRole::Call;
|
|
|
|
Expr *BaseE = nullptr;
|
|
if (auto DotE = dyn_cast<DotSyntaxCallExpr>(ParentE))
|
|
BaseE = DotE->getBase();
|
|
else if (auto MembE = dyn_cast<MemberRefExpr>(CurrentE))
|
|
BaseE = MembE->getBase();
|
|
else if (auto SubsE = dyn_cast<SubscriptExpr>(CurrentE))
|
|
BaseE = SubsE->getBase();
|
|
|
|
if (!BaseE || BaseE == CurrentE)
|
|
return false;
|
|
|
|
if (Type ReceiverTy = BaseE->getType()) {
|
|
if (auto LVT = ReceiverTy->getAs<LValueType>())
|
|
ReceiverTy = LVT->getObjectType();
|
|
else if (auto MetaT = ReceiverTy->getAs<MetatypeType>())
|
|
ReceiverTy = MetaT->getInstanceType();
|
|
|
|
if (auto TyD = ReceiverTy->getAnyNominal()) {
|
|
StringRef unused;
|
|
if (getNameAndUSR(TyD, unused, Info.receiverUSR))
|
|
return true;
|
|
if (isDynamicCall(BaseE, D))
|
|
Info.roles |= (unsigned)SymbolRole::Dynamic;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
llvm::hash_code
|
|
IndexSwiftASTWalker::hashFileReference(llvm::hash_code code,
|
|
SourceFileOrModule SFOrMod) {
|
|
StringRef Filename = SFOrMod.getFilename();
|
|
if (Filename.empty())
|
|
return code;
|
|
|
|
// FIXME: FileManager for swift ?
|
|
|
|
llvm::sys::fs::file_status Status;
|
|
if (std::error_code Ret = llvm::sys::fs::status(Filename, Status)) {
|
|
// Failure to read the file, just use filename to recover.
|
|
warn([&](llvm::raw_ostream &OS) {
|
|
OS << "failed to stat file: " << Filename << " (" << Ret.message() << ')';
|
|
});
|
|
return hash_combine(code, Filename);
|
|
}
|
|
|
|
// Don't use inode because it can easily change when you update the repository
|
|
// even though the file is supposed to be the same (same size/time).
|
|
code = hash_combine(code, Filename);
|
|
return hash_combine(code, Status.getSize(),
|
|
Status.getLastModificationTime().toEpochTime());
|
|
}
|
|
|
|
llvm::hash_code IndexSwiftASTWalker::hashModule(llvm::hash_code code,
|
|
SourceFileOrModule SFOrMod) {
|
|
code = hashFileReference(code, SFOrMod);
|
|
|
|
SmallVector<Module *, 16> Imports;
|
|
getRecursiveModuleImports(SFOrMod.getModule(), Imports);
|
|
for (auto Import : Imports)
|
|
code = hashFileReference(code, *Import);
|
|
|
|
return code;
|
|
}
|
|
|
|
void IndexSwiftASTWalker::getRecursiveModuleImports(
|
|
Module &Mod, SmallVectorImpl<Module *> &Imports) {
|
|
auto It = ImportsMap.find(&Mod);
|
|
if (It != ImportsMap.end()) {
|
|
Imports.append(It->second.begin(), It->second.end());
|
|
return;
|
|
}
|
|
|
|
llvm::SmallPtrSet<Module *, 16> Visited;
|
|
collectRecursiveModuleImports(Mod, Visited);
|
|
Visited.erase(&Mod);
|
|
|
|
warn([&Imports](llvm::raw_ostream &OS) {
|
|
std::for_each(Imports.begin(), Imports.end(), [&OS](Module *M) {
|
|
if (M->getModuleFilename().empty()) {
|
|
std::string Info = "swift::Module with empty file name!! \nDetails: \n";
|
|
Info += " name: ";
|
|
Info += M->getName().get();
|
|
Info += "\n";
|
|
|
|
auto Files = M->getFiles();
|
|
std::for_each(Files.begin(), Files.end(), [&](FileUnit *FU) {
|
|
Info += " file unit: ";
|
|
|
|
switch (FU->getKind()) {
|
|
case FileUnitKind::Builtin:
|
|
Info += "builtin";
|
|
break;
|
|
case FileUnitKind::Derived:
|
|
Info += "derived";
|
|
break;
|
|
case FileUnitKind::Source:
|
|
Info += "source, file=\"";
|
|
Info += cast<SourceFile>(FU)->getFilename();
|
|
Info += "\"";
|
|
break;
|
|
case FileUnitKind::SerializedAST:
|
|
Info += "serialized ast, file=\"";
|
|
Info += cast<LoadedFile>(FU)->getFilename();
|
|
Info += "\"";
|
|
break;
|
|
case FileUnitKind::ClangModule:
|
|
Info += "clang module, file=\"";
|
|
Info += cast<LoadedFile>(FU)->getFilename();
|
|
Info += "\"";
|
|
}
|
|
|
|
Info += "\n";
|
|
});
|
|
|
|
OS << "swift::Module with empty file name! " << Info << "\n";
|
|
}
|
|
});
|
|
});
|
|
|
|
Imports.append(Visited.begin(), Visited.end());
|
|
std::sort(Imports.begin(), Imports.end(), [](Module *LHS, Module *RHS) {
|
|
return LHS->getModuleFilename() < RHS->getModuleFilename();
|
|
});
|
|
|
|
// Cache it.
|
|
ImportsMap[&Mod].append(Imports.begin(), Imports.end());
|
|
}
|
|
|
|
void IndexSwiftASTWalker::collectRecursiveModuleImports(
|
|
Module &TopMod, llvm::SmallPtrSet<Module *, 16> &Visited) {
|
|
|
|
bool IsNew = Visited.insert(&TopMod).second;
|
|
if (!IsNew)
|
|
return;
|
|
|
|
// Pure Clang modules are tied to their dependencies, no need to look into its
|
|
// imports.
|
|
// FIXME: What happens if the clang module imports a swift module ? So far
|
|
// the assumption is that the path to the swift module will be fixed, so no
|
|
// need to hash the clang module.
|
|
// FIXME: This is a bit of a hack.
|
|
if (TopMod.getFiles().size() == 1)
|
|
if (TopMod.getFiles().front()->getKind() == FileUnitKind::ClangModule)
|
|
return;
|
|
|
|
auto It = ImportsMap.find(&TopMod);
|
|
if (It != ImportsMap.end()) {
|
|
Visited.insert(It->second.begin(), It->second.end());
|
|
return;
|
|
}
|
|
|
|
SmallVector<Module::ImportedModule, 8> Imports;
|
|
TopMod.getImportedModules(Imports, Module::ImportFilter::All);
|
|
|
|
for (auto Import : Imports) {
|
|
collectRecursiveModuleImports(*Import.second, Visited);
|
|
}
|
|
}
|
|
|
|
void IndexSwiftASTWalker::getModuleHash(SourceFileOrModule Mod,
|
|
llvm::raw_ostream &OS) {
|
|
// FIXME: Use a longer hash string to minimize possibility for conflicts.
|
|
llvm::hash_code code = hashModule(0, Mod);
|
|
OS << llvm::APInt(64, code).toString(36, /*Signed=*/false);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Indexing entry points
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void index::indexSourceFile(SourceFile *SF, StringRef hash,
|
|
IndexDataConsumer &consumer) {
|
|
assert(SF);
|
|
unsigned bufferID = SF->getBufferID().getValue();
|
|
IndexSwiftASTWalker walker(consumer, SF->getASTContext(), bufferID);
|
|
walker.visitModule(*SF->getParentModule(), hash);
|
|
}
|
|
|
|
void index::indexModule(ModuleDecl *module, StringRef hash,
|
|
IndexDataConsumer &consumer) {
|
|
assert(module);
|
|
IndexSwiftASTWalker walker(consumer, module->getASTContext(),
|
|
/*bufferID*/ -1);
|
|
walker.visitModule(*module, hash);
|
|
}
|