blob: 8e8e9bf8bbb95de8b712d8f2718f317185a9a2fb [file] [log] [blame]
//===--- SwiftCompletion.cpp ----------------------------------------------===//
//
// 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 "CodeCompletionOrganizer.h"
#include "SwiftASTManager.h"
#include "SwiftLangSupport.h"
#include "SwiftEditorDiagConsumer.h"
#include "SourceKit/Support/FileSystemProvider.h"
#include "SourceKit/Support/Logging.h"
#include "SourceKit/Support/UIdent.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/Frontend/Frontend.h"
#include "swift/Frontend/PrintingDiagnosticConsumer.h"
#include "swift/IDE/CodeCompletionCache.h"
#include "swift/IDE/CodeCompletionResultPrinter.h"
#include "swift/IDE/CompletionInstance.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/MemoryBuffer.h"
using namespace SourceKit;
using namespace swift;
using namespace ide;
using CodeCompletion::SwiftCompletionInfo;
using CodeCompletion::Completion;
using CodeCompletion::CodeCompletionView;
using CodeCompletion::CodeCompletionViewRef;
using CodeCompletion::NameToPopularityMap;
static_assert(swift::ide::CodeCompletionResult::MaxNumBytesToErase == 127,
"custom array implementation for code completion results "
"has this limit hardcoded");
namespace {
struct SwiftToSourceKitCompletionAdapter {
static bool handleResult(SourceKit::CodeCompletionConsumer &consumer,
CodeCompletionResult *result,
bool annotatedDescription) {
llvm::SmallString<64> name;
{
llvm::raw_svector_ostream OSS(name);
ide::printCodeCompletionResultFilterName(*result, OSS);
}
llvm::SmallString<64> description;
{
llvm::raw_svector_ostream OSS(description);
// FIXME: The leading punctuation (e.g. "?." in an optional completion)
// should really be part of a structured result description and clients
// can
// decide whether to display it or not. For now just include it in the
// description only in the new code path.
ide::printCodeCompletionResultDescription(*result, OSS,
/*leadingPunctuation=*/false);
}
Completion extended(*result, name, description);
return handleResult(consumer, &extended, /*leadingPunctuation=*/false,
/*legacyLiteralToKeyword=*/true, annotatedDescription);
}
static bool handleResult(SourceKit::CodeCompletionConsumer &consumer,
Completion *result, bool leadingPunctuation,
bool legacyLiteralToKeyword,
bool annotatedDescription);
static void getResultAssociatedUSRs(ArrayRef<StringRef> AssocUSRs,
raw_ostream &OS);
};
struct SwiftCodeCompletionConsumer
: public ide::SimpleCachingCodeCompletionConsumer {
using HandlerFunc = std::function<void(
MutableArrayRef<CodeCompletionResult *>, SwiftCompletionInfo &)>;
HandlerFunc handleResultsImpl;
SwiftCompletionInfo swiftContext;
SwiftCodeCompletionConsumer(HandlerFunc handleResultsImpl)
: handleResultsImpl(handleResultsImpl) {}
void setContext(swift::ASTContext *context,
const swift::CompilerInvocation *invocation,
swift::ide::CodeCompletionContext *completionContext) {
swiftContext.swiftASTContext = context;
swiftContext.invocation = invocation;
swiftContext.completionContext = completionContext;
}
void clearContext() { swiftContext = SwiftCompletionInfo(); }
void handleResults(MutableArrayRef<CodeCompletionResult *> Results) override {
assert(swiftContext.swiftASTContext);
handleResultsImpl(Results, swiftContext);
}
};
} // anonymous namespace
/// Returns completion context kind \c UIdent to report to the client.
/// For now, only returns "unresolved member" kind because others are unstable.
/// Returns invalid \c UIents other cases.
static UIdent getUIDForCodeCompletionKindToReport(CompletionKind kind) {
switch (kind) {
case CompletionKind::UnresolvedMember:
return UIdent("source.lang.swift.completion.unresolvedmember");
default:
return UIdent();
}
}
static bool swiftCodeCompleteImpl(
SwiftLangSupport &Lang, llvm::MemoryBuffer *UnresolvedInputFile,
unsigned Offset, SwiftCodeCompletionConsumer &SwiftConsumer,
ArrayRef<const char *> Args,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
bool annotateDescription, std::string &Error) {
return Lang.performCompletionLikeOperation(
UnresolvedInputFile, Offset, Args, FileSystem, Error,
[&](CompilerInstance &CI, bool reusingASTContext) {
// Create a factory for code completion callbacks that will feed the
// Consumer.
auto swiftCache = Lang.getCodeCompletionCache(); // Pin the cache.
ide::CodeCompletionContext CompletionContext(swiftCache->getCache());
CompletionContext.ReusingASTContext = reusingASTContext;
CompletionContext.setAnnotateResult(annotateDescription);
std::unique_ptr<CodeCompletionCallbacksFactory> callbacksFactory(
ide::makeCodeCompletionCallbacksFactory(CompletionContext,
SwiftConsumer));
SwiftConsumer.setContext(&CI.getASTContext(), &CI.getInvocation(),
&CompletionContext);
auto *SF = CI.getCodeCompletionFile();
performCodeCompletionSecondPass(*SF, *callbacksFactory);
SwiftConsumer.clearContext();
});
}
static void translateCodeCompletionOptions(OptionsDictionary &from,
CodeCompletion::Options &to,
StringRef &filterText,
unsigned &resultOffset,
unsigned &maxResults);
void SwiftLangSupport::codeComplete(
llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset,
OptionsDictionary *options,
SourceKit::CodeCompletionConsumer &SKConsumer, ArrayRef<const char *> Args,
Optional<VFSOptions> vfsOptions) {
CodeCompletion::Options CCOpts;
if (options) {
StringRef filterText;
unsigned resultOffset = 0;
unsigned maxResults = 0;
translateCodeCompletionOptions(*options, CCOpts, filterText, resultOffset,
maxResults);
}
std::string error;
// FIXME: the use of None as primary file is to match the fact we do not read
// the document contents using the editor documents infrastructure.
auto fileSystem = getFileSystem(vfsOptions, /*primaryFile=*/None, error);
if (!fileSystem)
return SKConsumer.failed(error);
SwiftCodeCompletionConsumer SwiftConsumer([&](
MutableArrayRef<CodeCompletionResult *> Results,
SwiftCompletionInfo &info) {
auto kind = getUIDForCodeCompletionKindToReport(
info.completionContext->CodeCompletionKind);
if (kind.isValid())
SKConsumer.setCompletionKind(kind);
bool hasRequiredType = info.completionContext->typeContextKind == TypeContextKind::Required;
if (CCOpts.sortByName)
CodeCompletionContext::sortCompletionResults(Results);
// FIXME: this adhoc filtering should be configurable like it is in the
// codeCompleteOpen path.
for (auto *Result : Results) {
if (Result->getKind() == CodeCompletionResult::Literal) {
switch (Result->getLiteralKind()) {
case CodeCompletionLiteralKind::NilLiteral:
case CodeCompletionLiteralKind::BooleanLiteral:
break;
case CodeCompletionLiteralKind::ImageLiteral:
case CodeCompletionLiteralKind::ColorLiteral:
if (hasRequiredType &&
Result->getExpectedTypeRelation() <
CodeCompletionResult::Convertible)
continue;
break;
default:
continue;
}
}
if (!SwiftToSourceKitCompletionAdapter::handleResult(
SKConsumer, Result, CCOpts.annotatedDescription))
break;
}
SKConsumer.setReusingASTContext(info.completionContext->ReusingASTContext);
SKConsumer.setAnnotatedTypename(info.completionContext->getAnnotateResult());
});
std::string Error;
if (!swiftCodeCompleteImpl(*this, UnresolvedInputFile, Offset, SwiftConsumer,
Args, fileSystem,
CCOpts.annotatedDescription, Error)) {
SKConsumer.failed(Error);
}
}
static void getResultStructure(
CodeCompletion::SwiftResult *result, bool leadingPunctuation,
CodeCompletionInfo::DescriptionStructure &structure,
std::vector<CodeCompletionInfo::ParameterStructure> &parameters) {
auto *CCStr = result->getCompletionString();
auto FirstTextChunk = CCStr->getFirstTextChunkIndex(leadingPunctuation);
if (!FirstTextChunk.hasValue())
return;
bool isOperator = result->isOperator();
auto chunks = CCStr->getChunks();
using ChunkKind = CodeCompletionString::Chunk::ChunkKind;
unsigned i = *FirstTextChunk;
unsigned textSize = 0;
// The result name.
for (; i < chunks.size(); ++i) {
auto C = chunks[i];
if (C.is(ChunkKind::TypeAnnotation) ||
C.is(ChunkKind::CallParameterClosureType) ||
C.is(ChunkKind::CallParameterClosureExpr) ||
C.is(ChunkKind::Whitespace))
continue;
if (C.is(ChunkKind::LeftParen) || C.is(ChunkKind::LeftBracket) ||
C.is(ChunkKind::BraceStmtWithCursor) ||
C.is(ChunkKind::CallParameterBegin))
break;
if (C.hasText())
textSize += C.getText().size();
}
structure.baseName.begin = 0;
structure.baseName.end = textSize;
// The parameters.
for (; i < chunks.size(); ++i) {
auto &C = chunks[i];
if (C.is(ChunkKind::TypeAnnotation) ||
C.is(ChunkKind::CallParameterClosureType) ||
C.is(ChunkKind::CallParameterClosureExpr) ||
C.is(ChunkKind::Whitespace))
continue;
if (C.is(ChunkKind::BraceStmtWithCursor))
break;
if (C.is(ChunkKind::ThrowsKeyword) ||
C.is(ChunkKind::RethrowsKeyword)) {
structure.throwsRange.begin = textSize;
structure.throwsRange.end = textSize + C.getText().size();
}
if (C.is(ChunkKind::CallParameterBegin)) {
CodeCompletionInfo::ParameterStructure param;
++i;
bool inName = false;
bool inAfterColon = false;
for (; i < chunks.size(); ++i) {
if (chunks[i].endsPreviousNestedGroup(C.getNestingLevel()))
break;
if (chunks[i].is(ChunkKind::CallParameterClosureType) ||
chunks[i].is(ChunkKind::CallParameterClosureExpr))
continue;
if (isOperator && chunks[i].is(ChunkKind::CallParameterType))
continue;
// Parameter name
if (chunks[i].is(ChunkKind::CallParameterName) ||
chunks[i].is(ChunkKind::CallParameterInternalName)) {
param.name.begin = textSize;
param.isLocalName =
chunks[i].is(ChunkKind::CallParameterInternalName);
inName = true;
}
// Parameter type
if (chunks[i].is(ChunkKind::CallParameterType)) {
unsigned start = textSize;
unsigned prev = i - 1; // if i == 0, prev = ~0u.
// Combine & for inout into the type name.
if (prev != ~0u && chunks[prev].is(ChunkKind::Ampersand)) {
start -= chunks[prev].getText().size();
prev -= 1;
}
// Combine the whitespace after ':' into the type name.
if (prev != ~0u && chunks[prev].is(ChunkKind::CallParameterColon))
start -= 1;
param.afterColon.begin = start;
inAfterColon = true;
if (inName) {
param.name.end = start;
inName = false;
}
}
if (chunks[i].hasText())
textSize += chunks[i].getText().size();
}
// If we had a name with no type following it, finish it now.
if (inName)
param.name.end = textSize;
// Finish the type name.
if (inAfterColon)
param.afterColon.end = textSize;
if (!param.range().empty())
parameters.push_back(std::move(param));
if (i < chunks.size() && chunks[i].hasText())
textSize += chunks[i].getText().size();
}
if (C.hasText())
textSize += C.getText().size();
}
if (!parameters.empty()) {
structure.parameterRange.begin = parameters.front().range().begin;
structure.parameterRange.end = parameters.back().range().end;
}
}
static UIdent KindLiteralArray("source.lang.swift.literal.array");
static UIdent KindLiteralBoolean("source.lang.swift.literal.boolean");
static UIdent KindLiteralColor("source.lang.swift.literal.color");
static UIdent KindLiteralImage("source.lang.swift.literal.image");
static UIdent KindLiteralDictionary("source.lang.swift.literal.dictionary");
static UIdent KindLiteralInteger("source.lang.swift.literal.integer");
static UIdent KindLiteralNil("source.lang.swift.literal.nil");
static UIdent KindLiteralString("source.lang.swift.literal.string");
static UIdent KindLiteralTuple("source.lang.swift.literal.tuple");
static UIdent
getUIDForCodeCompletionLiteralKind(CodeCompletionLiteralKind kind) {
switch (kind) {
case CodeCompletionLiteralKind::ArrayLiteral:
return KindLiteralArray;
case CodeCompletionLiteralKind::BooleanLiteral:
return KindLiteralBoolean;
case CodeCompletionLiteralKind::ColorLiteral:
return KindLiteralColor;
case CodeCompletionLiteralKind::ImageLiteral:
return KindLiteralImage;
case CodeCompletionLiteralKind::DictionaryLiteral:
return KindLiteralDictionary;
case CodeCompletionLiteralKind::IntegerLiteral:
return KindLiteralInteger;
case CodeCompletionLiteralKind::NilLiteral:
return KindLiteralNil;
case CodeCompletionLiteralKind::StringLiteral:
return KindLiteralString;
case CodeCompletionLiteralKind::Tuple:
return KindLiteralTuple;
}
}
static UIdent KeywordUID("source.lang.swift.keyword");
static UIdent KeywordLetUID("source.lang.swift.keyword.let");
static UIdent KeywordVarUID("source.lang.swift.keyword.var");
static UIdent KeywordIfUID("source.lang.swift.keyword.if");
static UIdent KeywordForUID("source.lang.swift.keyword.for");
static UIdent KeywordWhileUID("source.lang.swift.keyword.while");
static UIdent KeywordFuncUID("source.lang.swift.keyword.func");
bool SwiftToSourceKitCompletionAdapter::handleResult(
SourceKit::CodeCompletionConsumer &Consumer, Completion *Result,
bool leadingPunctuation, bool legacyLiteralToKeyword,
bool annotatedDescription) {
static UIdent KeywordUID("source.lang.swift.keyword");
static UIdent PatternUID("source.lang.swift.pattern");
CodeCompletionInfo Info;
if (Result->hasCustomKind()) {
Info.CustomKind = Result->getCustomKind();
} else if (Result->getKind() == CodeCompletionResult::Keyword) {
Info.Kind = KeywordUID;
} else if (Result->getKind() == CodeCompletionResult::Pattern) {
Info.Kind = PatternUID;
} else if (Result->getKind() == CodeCompletionResult::BuiltinOperator) {
Info.Kind = PatternUID; // FIXME: add a UID for operators
} else if (Result->getKind() == CodeCompletionResult::Declaration) {
Info.Kind = SwiftLangSupport::getUIDForCodeCompletionDeclKind(
Result->getAssociatedDeclKind());
} else if (Result->getKind() == CodeCompletionResult::Literal) {
auto literalKind = Result->getLiteralKind();
if (legacyLiteralToKeyword &&
(literalKind == CodeCompletionLiteralKind::BooleanLiteral ||
literalKind == CodeCompletionLiteralKind::NilLiteral)) {
// Fallback to keywords as appropriate.
Info.Kind = KeywordUID;
} else {
Info.Kind = getUIDForCodeCompletionLiteralKind(literalKind);
}
}
if (Info.Kind.isInvalid() && !Info.CustomKind)
return true;
llvm::SmallString<512> SS;
unsigned DescBegin = SS.size();
{
llvm::raw_svector_ostream ccOS(SS);
if (annotatedDescription)
ide::printCodeCompletionResultDescriptionAnnotated(*Result, ccOS,
leadingPunctuation);
else
ide::printCodeCompletionResultDescription(*Result, ccOS,
leadingPunctuation);
}
unsigned DescEnd = SS.size();
if (DescBegin == DescEnd) {
LOG_FUNC_SECTION_WARN {
llvm::SmallString<64> LogMessage;
llvm::raw_svector_ostream LogMessageOs(LogMessage);
LogMessageOs << "Code completion result with empty description "
"was ignored: \n";
Result->printPrefix(LogMessageOs);
Result->getCompletionString()->print(LogMessageOs);
*Log << LogMessage;
}
return true;
}
unsigned TextBegin = SS.size();
{
llvm::raw_svector_ostream ccOS(SS);
ide::printCodeCompletionResultSourceText(*Result, ccOS);
}
unsigned TextEnd = SS.size();
unsigned TypeBegin = SS.size();
{
llvm::raw_svector_ostream ccOS(SS);
if (annotatedDescription)
ide::printCodeCompletionResultTypeNameAnnotated(*Result, ccOS);
else
ide::printCodeCompletionResultTypeName(*Result, ccOS);
}
unsigned TypeEnd = SS.size();
unsigned USRsBegin = SS.size();
{
llvm::raw_svector_ostream ccOS(SS);
getResultAssociatedUSRs(Result->getAssociatedUSRs(), ccOS);
}
unsigned USRsEnd = SS.size();
Info.Name = Result->getName();
Info.Description = StringRef(SS.begin() + DescBegin, DescEnd - DescBegin);
Info.SourceText = StringRef(SS.begin() + TextBegin, TextEnd - TextBegin);
Info.TypeName = StringRef(SS.begin() + TypeBegin, TypeEnd - TypeBegin);
Info.AssocUSRs = StringRef(SS.begin() + USRsBegin, USRsEnd - USRsBegin);
static UIdent CCCtxNone("source.codecompletion.context.none");
static UIdent CCCtxExpressionSpecific(
"source.codecompletion.context.exprspecific");
static UIdent CCCtxLocal("source.codecompletion.context.local");
static UIdent CCCtxCurrentNominal("source.codecompletion.context.thisclass");
static UIdent CCCtxSuper("source.codecompletion.context.superclass");
static UIdent CCCtxOutsideNominal("source.codecompletion.context.otherclass");
static UIdent CCCtxCurrentModule("source.codecompletion.context.thismodule");
static UIdent CCCtxOtherModule("source.codecompletion.context.othermodule");
switch (Result->getSemanticContext()) {
case SemanticContextKind::None:
Info.SemanticContext = CCCtxNone; break;
case SemanticContextKind::ExpressionSpecific:
Info.SemanticContext = CCCtxExpressionSpecific; break;
case SemanticContextKind::Local:
Info.SemanticContext = CCCtxLocal; break;
case SemanticContextKind::CurrentNominal:
Info.SemanticContext = CCCtxCurrentNominal; break;
case SemanticContextKind::Super:
Info.SemanticContext = CCCtxSuper; break;
case SemanticContextKind::OutsideNominal:
Info.SemanticContext = CCCtxOutsideNominal; break;
case SemanticContextKind::CurrentModule:
Info.SemanticContext = CCCtxCurrentModule; break;
case SemanticContextKind::OtherModule:
Info.SemanticContext = CCCtxOtherModule; break;
}
static UIdent CCTypeRelNotApplicable("source.codecompletion.typerelation.notapplicable");
static UIdent CCTypeRelUnknown("source.codecompletion.typerelation.unknown");
static UIdent CCTypeRelUnrelated("source.codecompletion.typerelation.unrelated");
static UIdent CCTypeRelInvalid("source.codecompletion.typerelation.invalid");
static UIdent CCTypeRelConvertible("source.codecompletion.typerelation.convertible");
static UIdent CCTypeRelIdentical("source.codecompletion.typerelation.identical");
switch (Result->getExpectedTypeRelation()) {
case CodeCompletionResult::NotApplicable:
Info.TypeRelation = CCTypeRelNotApplicable; break;
case CodeCompletionResult::Unknown:
Info.TypeRelation = CCTypeRelUnknown; break;
case CodeCompletionResult::Unrelated:
Info.TypeRelation = CCTypeRelUnrelated; break;
case CodeCompletionResult::Invalid:
Info.TypeRelation = CCTypeRelInvalid; break;
case CodeCompletionResult::Convertible:
Info.TypeRelation = CCTypeRelConvertible; break;
case CodeCompletionResult::Identical:
Info.TypeRelation = CCTypeRelIdentical; break;
}
Info.ModuleName = Result->getModuleName();
Info.DocBrief = Result->getBriefDocComment();
Info.NotRecommended = Result->isNotRecommended();
Info.IsSystem = Result->isSystem();
Info.NumBytesToErase = Result->getNumBytesToErase();
// Extended result values.
Info.ModuleImportDepth = Result->getModuleImportDepth();
// Description structure.
std::vector<CodeCompletionInfo::ParameterStructure> parameters;
CodeCompletionInfo::DescriptionStructure structure;
getResultStructure(Result, leadingPunctuation, structure, parameters);
Info.descriptionStructure = structure;
if (!parameters.empty())
Info.parametersStructure = parameters;
return Consumer.handleResult(Info);
}
static CodeCompletionLiteralKind
getCodeCompletionLiteralKindForUID(UIdent uid) {
if (uid == KindLiteralArray) {
return CodeCompletionLiteralKind::ArrayLiteral;
} else if (uid == KindLiteralBoolean) {
return CodeCompletionLiteralKind::BooleanLiteral;
} else if (uid == KindLiteralColor) {
return CodeCompletionLiteralKind::ColorLiteral;
} else if (uid == KindLiteralImage) {
return CodeCompletionLiteralKind::ImageLiteral;
} else if (uid == KindLiteralDictionary) {
return CodeCompletionLiteralKind::DictionaryLiteral;
} else if (uid == KindLiteralInteger) {
return CodeCompletionLiteralKind::IntegerLiteral;
} else if (uid == KindLiteralNil) {
return CodeCompletionLiteralKind::NilLiteral;
} else if (uid == KindLiteralString) {
return CodeCompletionLiteralKind::StringLiteral;
} else if (uid == KindLiteralTuple) {
return CodeCompletionLiteralKind::Tuple;
} else {
llvm_unreachable("unexpected literal kind");
}
}
static CodeCompletionKeywordKind
getCodeCompletionKeywordKindForUID(UIdent uid) {
#define SWIFT_KEYWORD(kw) \
static UIdent Keyword##kw##UID("source.lang.swift.keyword." #kw); \
if (uid == Keyword##kw##UID) { \
return CodeCompletionKeywordKind::kw_##kw; \
}
#include "swift/Syntax/TokenKinds.def"
// FIXME: should warn about unexpected keyword kind.
return CodeCompletionKeywordKind::None;
}
void SwiftToSourceKitCompletionAdapter::getResultAssociatedUSRs(
ArrayRef<StringRef> AssocUSRs, raw_ostream &OS) {
bool First = true;
for (auto USR : AssocUSRs) {
if (!First)
OS << " ";
First = false;
OS << USR;
}
}
//===----------------------------------------------------------------------===//
// CodeCompletion::SessionCache
//===----------------------------------------------------------------------===//
void CodeCompletion::SessionCache::setSortedCompletions(
std::vector<Completion *> &&completions) {
llvm::sys::ScopedLock L(mtx);
sortedCompletions = std::move(completions);
}
ArrayRef<Completion *> CodeCompletion::SessionCache::getSortedCompletions() {
llvm::sys::ScopedLock L(mtx);
return sortedCompletions;
}
llvm::MemoryBuffer *CodeCompletion::SessionCache::getBuffer() {
llvm::sys::ScopedLock L(mtx);
return buffer.get();
}
ArrayRef<std::string> CodeCompletion::SessionCache::getCompilerArgs() {
llvm::sys::ScopedLock L(mtx);
return args;
}
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> CodeCompletion::SessionCache::getFileSystem() {
llvm::sys::ScopedLock L(mtx);
return fileSystem;
}
CompletionKind CodeCompletion::SessionCache::getCompletionKind() {
llvm::sys::ScopedLock L(mtx);
return completionKind;
}
TypeContextKind CodeCompletion::SessionCache::getCompletionTypeContextKind() {
llvm::sys::ScopedLock L(mtx);
return typeContextKind;
}
bool CodeCompletion::SessionCache::getCompletionMayUseImplicitMemberExpr() {
llvm::sys::ScopedLock L(mtx);
return completionMayUseImplicitMemberExpr;
}
const CodeCompletion::FilterRules &
CodeCompletion::SessionCache::getFilterRules() {
llvm::sys::ScopedLock L(mtx);
return filterRules;
}
//===----------------------------------------------------------------------===//
// CodeCompletion::SessionCacheMap
//===----------------------------------------------------------------------===//
unsigned CodeCompletion::SessionCacheMap::getBufferID(StringRef name) const {
auto pair = nameToBufferMap.insert(std::make_pair(name, nextBufferID));
if (pair.second)
++nextBufferID;
return pair.first->getValue();
}
CodeCompletion::SessionCacheRef
CodeCompletion::SessionCacheMap::get(StringRef name, unsigned offset) const {
llvm::sys::ScopedLock L(mtx);
auto key = std::make_pair(getBufferID(name), offset);
auto I = sessions.find(key);
if (I == sessions.end())
return nullptr;
return I->second;
}
bool CodeCompletion::SessionCacheMap::set(StringRef name, unsigned offset,
CodeCompletion::SessionCacheRef s) {
llvm::sys::ScopedLock L(mtx);
auto key = std::make_pair(getBufferID(name), offset);
return sessions.insert(std::make_pair(key, s)).second;
}
bool CodeCompletion::SessionCacheMap::remove(StringRef name, unsigned offset) {
llvm::sys::ScopedLock L(mtx);
auto key = std::make_pair(getBufferID(name), offset);
return sessions.erase(key);
}
//===----------------------------------------------------------------------===//
// (New) Code completion interface
//===----------------------------------------------------------------------===//
namespace {
class SwiftGroupedCodeCompletionConsumer : public CodeCompletionView::Walker {
GroupedCodeCompletionConsumer &consumer;
public:
SwiftGroupedCodeCompletionConsumer(GroupedCodeCompletionConsumer &consumer)
: consumer(consumer) {}
bool handleResult(Completion *result) override {
return SwiftToSourceKitCompletionAdapter::handleResult(
consumer, result, /*leadingPunctuation=*/true,
/*legacyLiteralToKeyword=*/false, /*annotatedDescription=*/false);
}
void startGroup(StringRef name) override {
static UIdent GroupUID("source.lang.swift.codecomplete.group");
consumer.startGroup(GroupUID, name);
}
void endGroup() override { consumer.endGroup(); }
};
} // end anonymous namespace
static void translateCodeCompletionOptions(OptionsDictionary &from,
CodeCompletion::Options &to,
StringRef &filterText,
unsigned &resultOffset,
unsigned &maxResults) {
static UIdent KeySortByName("key.codecomplete.sort.byname");
static UIdent KeyUseImportDepth("key.codecomplete.sort.useimportdepth");
static UIdent KeyGroupOverloads("key.codecomplete.group.overloads");
static UIdent KeyGroupStems("key.codecomplete.group.stems");
static UIdent KeyFilterText("key.codecomplete.filtertext");
static UIdent KeyRequestLimit("key.codecomplete.requestlimit");
static UIdent KeyRequestStart("key.codecomplete.requeststart");
static UIdent KeyHideUnderscores("key.codecomplete.hideunderscores");
static UIdent KeyHideLowPriority("key.codecomplete.hidelowpriority");
static UIdent KeyHideByName("key.codecomplete.hidebyname");
static UIdent KeyIncludeExactMatch("key.codecomplete.includeexactmatch");
static UIdent KeyAddInnerResults("key.codecomplete.addinnerresults");
static UIdent KeyAddInnerOperators("key.codecomplete.addinneroperators");
static UIdent KeyAddInitsToTopLevel("key.codecomplete.addinitstotoplevel");
static UIdent KeyCallPatternHeuristics("key.codecomplete.callpatternheuristics");
static UIdent KeyFuzzyMatching("key.codecomplete.fuzzymatching");
static UIdent KeyTopNonLiteral("key.codecomplete.showtopnonliteralresults");
static UIdent KeyContextWeight("key.codecomplete.sort.contextweight");
static UIdent KeyFuzzyWeight("key.codecomplete.sort.fuzzyweight");
static UIdent KeyPopularityBonus("key.codecomplete.sort.popularitybonus");
static UIdent KeyAnnotatedDescription("key.codecomplete.annotateddescription");
from.valueForOption(KeySortByName, to.sortByName);
from.valueForOption(KeyUseImportDepth, to.useImportDepth);
from.valueForOption(KeyGroupOverloads, to.groupOverloads);
from.valueForOption(KeyGroupStems, to.groupStems);
from.valueForOption(KeyFilterText, filterText);
from.valueForOption(KeyRequestLimit, maxResults);
from.valueForOption(KeyRequestStart, resultOffset);
unsigned howMuchHiding = 1;
from.valueForOption(KeyHideUnderscores, howMuchHiding);
to.hideUnderscores = howMuchHiding;
to.reallyHideAllUnderscores = howMuchHiding > 1;
from.valueForOption(KeyHideLowPriority, to.hideLowPriority);
from.valueForOption(KeyIncludeExactMatch, to.includeExactMatch);
from.valueForOption(KeyAddInnerResults, to.addInnerResults);
from.valueForOption(KeyAddInnerOperators, to.addInnerOperators);
from.valueForOption(KeyAddInitsToTopLevel, to.addInitsToTopLevel);
from.valueForOption(KeyCallPatternHeuristics, to.callPatternHeuristics);
from.valueForOption(KeyFuzzyMatching, to.fuzzyMatching);
from.valueForOption(KeyContextWeight, to.semanticContextWeight);
from.valueForOption(KeyFuzzyWeight, to.fuzzyMatchWeight);
from.valueForOption(KeyPopularityBonus, to.popularityBonus);
from.valueForOption(KeyHideByName, to.hideByNameStyle);
from.valueForOption(KeyTopNonLiteral, to.showTopNonLiteralResults);
from.valueForOption(KeyAnnotatedDescription, to.annotatedDescription);
}
/// Canonicalize a name that is in the format of a reference to a function into
/// the name format used internally for filtering.
///
/// Returns true if the name is invalid.
static bool canonicalizeFilterName(const char *origName,
SmallVectorImpl<char> &Result) {
assert(origName);
const char *p = origName;
char curr = '\0';
char prev;
// FIXME: disallow unnamed parameters without underscores `foo(::)`.
while (true) {
prev = curr;
curr = *p++;
switch (curr) {
case '\0':
return false; // Done.
case '_':
// Remove the _ underscore for an unnamed parameter.
if (prev == ':' || prev == '(') {
char next = *p;
if (next == ':' || next == ')')
continue;
}
LLVM_FALLTHROUGH;
default:
Result.push_back(curr);
continue;
}
}
}
static void translateFilterRules(ArrayRef<FilterRule> rawFilterRules,
CodeCompletion::FilterRules &filterRules) {
for (auto &rule : rawFilterRules) {
switch (rule.kind) {
case FilterRule::Everything:
filterRules.hideAll = rule.hide;
break;
case FilterRule::Identifier:
for (auto name : rule.names) {
SmallString<128> canonName;
// Note: name is null-terminated.
if (canonicalizeFilterName(name.data(), canonName))
continue;
filterRules.hideByFilterName[canonName] = rule.hide;
}
break;
case FilterRule::Description:
for (auto name : rule.names) {
filterRules.hideByDescription[name] = rule.hide;
}
break;
case FilterRule::Module:
for (auto name : rule.names) {
filterRules.hideModule[name] = rule.hide;
}
break;
case FilterRule::Keyword:
if (rule.uids.empty())
filterRules.hideAllKeywords = rule.hide;
for (auto uid : rule.uids) {
auto kind = getCodeCompletionKeywordKindForUID(uid);
filterRules.hideKeyword[kind] = rule.hide;
}
break;
case FilterRule::Literal:
if (rule.uids.empty())
filterRules.hideAllValueLiterals = rule.hide;
for (auto uid : rule.uids) {
auto kind = getCodeCompletionLiteralKindForUID(uid);
filterRules.hideValueLiteral[kind] = rule.hide;
}
break;
case FilterRule::CustomCompletion:
if (rule.uids.empty())
filterRules.hideCustomCompletions = rule.hide;
// FIXME: hide individual custom completions
break;
}
}
}
static bool checkInnerResult(CodeCompletionResult *result, bool &hasDot,
bool &hasQDot, bool &hasInit) {
auto chunks = result->getCompletionString()->getChunks();
if (!chunks.empty() &&
chunks[0].is(CodeCompletionString::Chunk::ChunkKind::Dot)) {
hasDot = true;
return true;
} else if (chunks.size() >= 2 &&
chunks[0].is(
CodeCompletionString::Chunk::ChunkKind::QuestionMark) &&
chunks[1].is(CodeCompletionString::Chunk::ChunkKind::Dot)) {
hasQDot = true;
return true;
} else if (result->getKind() ==
CodeCompletion::SwiftResult::ResultKind::Declaration &&
result->getAssociatedDeclKind() ==
CodeCompletionDeclKind::Constructor) {
hasInit = true;
return true;
} else {
return false;
}
}
template <typename Result>
static std::vector<Result *>
filterInnerResults(ArrayRef<Result *> results, bool includeInner,
bool includeInnerOperators,
bool &hasDot, bool &hasQDot, bool &hasInit,
const CodeCompletion::FilterRules &rules) {
std::vector<Result *> topResults;
for (auto *result : results) {
if (!includeInnerOperators && result->isOperator())
continue;
llvm::SmallString<64> filterName;
{
llvm::raw_svector_ostream OSS(filterName);
ide::printCodeCompletionResultFilterName(*result, OSS);
}
llvm::SmallString<64> description;
{
llvm::raw_svector_ostream OSS(description);
ide::printCodeCompletionResultDescription(*result, OSS,
/*leadingPunctuation=*/false);
}
if (rules.hideCompletion(result, filterName, description))
continue;
bool inner = checkInnerResult(result, hasDot, hasQDot, hasInit);
if (!inner ||
(includeInner &&
result->getSemanticContext() <= SemanticContextKind::CurrentNominal))
topResults.push_back(result);
}
return topResults;
}
static void transformAndForwardResults(
GroupedCodeCompletionConsumer &consumer, SwiftLangSupport &lang,
CodeCompletion::SessionCacheRef session,
const NameToPopularityMap *nameToPopularity,
CodeCompletion::Options options, unsigned offset, StringRef filterText,
unsigned resultOffset, unsigned maxResults) {
CodeCompletion::CompletionSink innerSink;
Completion *exactMatch = nullptr;
auto buildInnerResult = [&](ArrayRef<CodeCompletionString::Chunk> chunks) {
auto *completionString =
CodeCompletionString::create(innerSink.allocator, chunks);
CodeCompletion::SwiftResult paren(
CodeCompletion::SwiftResult::ResultKind::BuiltinOperator,
SemanticContextKind::ExpressionSpecific,
exactMatch ? exactMatch->getNumBytesToErase() : 0, completionString,
CodeCompletionResult::ExpectedTypeRelation::NotApplicable);
SwiftCompletionInfo info;
std::vector<Completion *> extended =
extendCompletions(&paren, innerSink, info, nameToPopularity, options,
exactMatch, SemanticContextKind::ExpressionSpecific);
assert(extended.size() == 1);
return extended.front();
};
auto buildParen = [&]() {
return buildInnerResult(CodeCompletionString::Chunk::createWithText(
CodeCompletionString::Chunk::ChunkKind::LeftParen, 0, "("));
};
auto buildDot = [&]() {
return buildInnerResult(CodeCompletionString::Chunk::createWithText(
CodeCompletionString::Chunk::ChunkKind::Dot, 0, "."));
};
auto buildQDot = [&]() {
CodeCompletionString::Chunk chunks[2] = {
CodeCompletionString::Chunk::createWithText(
CodeCompletionString::Chunk::ChunkKind::QuestionMark, 0, "?"),
CodeCompletionString::Chunk::createWithText(
CodeCompletionString::Chunk::ChunkKind::Dot, 0, ".")};
return buildInnerResult(chunks);
};
CodeCompletion::CodeCompletionOrganizer organizer(
options, session->getCompletionKind(),
session->getCompletionTypeContextKind());
auto &rules = session->getFilterRules();
bool hasEarlyInnerResults =
session->getCompletionKind() == CompletionKind::PostfixExpr;
if (!hasEarlyInnerResults) {
organizer.addCompletionsWithFilter(session->getSortedCompletions(),
filterText, rules, exactMatch);
// Add leading dot?
if (options.addInnerOperators && !rules.hideFilterName(".") &&
session->getCompletionMayUseImplicitMemberExpr()) {
organizer.addCompletionsWithFilter(
buildDot(), filterText, CodeCompletion::FilterRules(), exactMatch);
}
}
if (hasEarlyInnerResults &&
(options.addInnerResults || options.addInnerOperators)) {
bool hasInit = false;
bool hasDot = false;
bool hasQDot = false;
auto completions = session->getSortedCompletions();
auto innerResults =
filterInnerResults(completions, options.addInnerResults,
options.addInnerOperators, hasDot, hasQDot, hasInit,
rules);
if (options.addInnerOperators) {
if (hasInit && !rules.hideFilterName("("))
innerResults.insert(innerResults.begin(), buildParen());
if (hasDot && !rules.hideFilterName("."))
innerResults.insert(innerResults.begin(), buildDot());
if (hasQDot && !rules.hideFilterName("?."))
innerResults.insert(innerResults.begin(), buildQDot());
}
organizer.addCompletionsWithFilter(innerResults, filterText,
CodeCompletion::FilterRules(), exactMatch);
}
organizer.groupAndSort(options);
if ((options.addInnerResults || options.addInnerOperators) &&
exactMatch && exactMatch->getKind() == Completion::Declaration) {
std::vector<Completion *> innerResults;
bool hasDot = false;
bool hasQDot = false;
bool hasInit = false;
SwiftCodeCompletionConsumer swiftConsumer([&](
MutableArrayRef<CodeCompletionResult *> results,
SwiftCompletionInfo &info) {
auto *context = info.completionContext;
if (!context || context->CodeCompletionKind != CompletionKind::PostfixExpr)
return;
auto topResults = filterInnerResults(results, options.addInnerResults,
options.addInnerOperators, hasDot,
hasQDot, hasInit, rules);
// FIXME: Overriding the default to context "None" is a hack so that they
// won't overwhelm other results that also match the filter text.
innerResults = extendCompletions(
topResults, innerSink, info, nameToPopularity, options, exactMatch,
SemanticContextKind::None, SemanticContextKind::None);
});
auto *inputBuf = session->getBuffer();
std::string str = inputBuf->getBuffer().slice(0, offset).str();
{
llvm::raw_string_ostream OSS(str);
ide::printCodeCompletionResultSourceText(*exactMatch, OSS);
}
auto buffer =
llvm::MemoryBuffer::getMemBuffer(str, inputBuf->getBufferIdentifier());
auto args = session->getCompilerArgs();
std::vector<const char *> cargs;
for (auto &arg : args)
cargs.push_back(arg.c_str());
std::string error;
if (!swiftCodeCompleteImpl(lang, buffer.get(), str.size(), swiftConsumer,
cargs, session->getFileSystem(),
options.annotatedDescription, error)) {
consumer.failed(error);
return;
}
if (options.addInnerOperators) {
if (hasInit && !rules.hideFilterName("("))
innerResults.insert(innerResults.begin(), buildParen());
if (hasDot && !rules.hideFilterName("."))
innerResults.insert(innerResults.begin(), buildDot());
if (hasQDot && !rules.hideFilterName("?."))
innerResults.insert(innerResults.begin(), buildQDot());
}
// Add the inner results (and don't filter them).
exactMatch = nullptr; // No longer needed.
organizer.addCompletionsWithFilter(innerResults, filterText,
CodeCompletion::FilterRules(), exactMatch);
CodeCompletion::Options noGroupOpts = options;
noGroupOpts.groupStems = false;
noGroupOpts.groupOverloads = false;
organizer.groupAndSort(noGroupOpts);
}
// Build the final results view.
auto view = organizer.takeResultsView();
CodeCompletion::LimitedResultView limitedResults(*view, resultOffset,
maxResults);
// Forward results to the SourceKit consumer.
SwiftGroupedCodeCompletionConsumer groupedConsumer(consumer);
limitedResults.walk(groupedConsumer);
consumer.setNextRequestStart(limitedResults.getNextOffset());
}
void SwiftLangSupport::codeCompleteOpen(
StringRef name, llvm::MemoryBuffer *inputBuf, unsigned offset,
OptionsDictionary *options, ArrayRef<FilterRule> rawFilterRules,
GroupedCodeCompletionConsumer &consumer, ArrayRef<const char *> args,
Optional<VFSOptions> vfsOptions) {
StringRef filterText;
unsigned resultOffset = 0;
unsigned maxResults = 0;
CodeCompletion::Options CCOpts;
if (options)
translateCodeCompletionOptions(*options, CCOpts, filterText, resultOffset,
maxResults);
std::string error;
// FIXME: the use of None as primary file is to match the fact we do not read
// the document contents using the editor documents infrastructure.
auto fileSystem = getFileSystem(vfsOptions, /*primaryFile=*/None, error);
if (!fileSystem)
return consumer.failed(error);
CodeCompletion::FilterRules filterRules;
translateFilterRules(rawFilterRules, filterRules);
// Set up the code completion consumer to pass results to organizer.
CodeCompletion::CompletionSink sink;
std::vector<Completion *> completions;
NameToPopularityMap *nameToPopularity = nullptr;
// This reference must outlive the uses of nameToPopularity.
auto popularAPIRef = PopularAPI;
if (popularAPIRef) {
nameToPopularity = &popularAPIRef->nameToFactor;
}
CompletionKind completionKind = CompletionKind::None;
TypeContextKind typeContextKind = TypeContextKind::None;
bool mayUseImplicitMemberExpr = false;
SwiftCodeCompletionConsumer swiftConsumer(
[&](MutableArrayRef<CodeCompletionResult *> results,
SwiftCompletionInfo &info) {
auto &completionCtx = *info.completionContext;
completionKind = completionCtx.CodeCompletionKind;
typeContextKind = completionCtx.typeContextKind;
mayUseImplicitMemberExpr = completionCtx.MayUseImplicitMemberExpr;
consumer.setReusingASTContext(completionCtx.ReusingASTContext);
consumer.setAnnotatedTypename(completionCtx.getAnnotateResult());
completions =
extendCompletions(results, sink, info, nameToPopularity, CCOpts);
});
// Add any codecomplete.open specific flags.
std::vector<const char *> extendedArgs(args.begin(), args.end());
if (CCOpts.addInitsToTopLevel) {
extendedArgs.push_back("-Xfrontend");
extendedArgs.push_back("-code-complete-inits-in-postfix-expr");
}
if (CCOpts.callPatternHeuristics) {
extendedArgs.push_back("-Xfrontend");
extendedArgs.push_back("-code-complete-call-pattern-heuristics");
}
// Invoke completion.
if (!swiftCodeCompleteImpl(*this, inputBuf, offset, swiftConsumer,
extendedArgs, fileSystem,
CCOpts.annotatedDescription, error)) {
consumer.failed(error);
return;
}
// Add any relevant custom completions.
if (auto custom = CustomCompletions)
addCustomCompletions(sink, completions, custom->customCompletions,
completionKind);
// Pre-sort the completions.
CodeCompletion::CodeCompletionOrganizer::preSortCompletions(completions);
// Store in the session.
using CodeCompletion::SessionCache;
using CodeCompletion::SessionCacheRef;
auto bufferCopy = llvm::MemoryBuffer::getMemBufferCopy(
inputBuf->getBuffer(), inputBuf->getBufferIdentifier());
std::vector<std::string> argsCopy(extendedArgs.begin(), extendedArgs.end());
SessionCacheRef session{new SessionCache(
std::move(sink), std::move(bufferCopy), std::move(argsCopy), fileSystem,
completionKind, typeContextKind, mayUseImplicitMemberExpr,
std::move(filterRules))};
session->setSortedCompletions(std::move(completions));
if (!CCSessions.set(name, offset, session)) {
std::string err;
llvm::raw_string_ostream OS(err);
OS << "codecomplete.open: code completion session for '" << name << "', "
<< offset << " already exists";
consumer.failed(OS.str());
}
transformAndForwardResults(consumer, *this, session, nameToPopularity, CCOpts,
offset, filterText, resultOffset, maxResults);
}
void SwiftLangSupport::codeCompleteClose(
StringRef name, unsigned offset, GroupedCodeCompletionConsumer &consumer) {
if (!CCSessions.remove(name, offset)) {
std::string err;
llvm::raw_string_ostream OS(err);
OS << "codecomplete.close: no code completion session for '" << name
<< "', " << offset;
consumer.failed(OS.str());
}
}
void SwiftLangSupport::codeCompleteUpdate(
StringRef name, unsigned offset, OptionsDictionary *options,
GroupedCodeCompletionConsumer &consumer) {
auto session = CCSessions.get(name, offset);
if (!session) {
std::string err;
llvm::raw_string_ostream OS(err);
OS << "codecomplete.update: no code completion session for '" << name
<< "', " << offset;
consumer.failed(OS.str());
return;
}
StringRef filterText;
unsigned resultOffset = 0;
unsigned maxResults = 0;
// FIXME: consider whether CCOpts has changed since the 'open'.
CodeCompletion::Options CCOpts;
if (options)
translateCodeCompletionOptions(*options, CCOpts, filterText, resultOffset,
maxResults);
NameToPopularityMap *nameToPopularity = nullptr;
// This reference must outlive the uses of nameToPopularity.
auto popularAPIRef = PopularAPI;
if (popularAPIRef) {
nameToPopularity = &popularAPIRef->nameToFactor;
}
transformAndForwardResults(consumer, *this, session, nameToPopularity, CCOpts,
offset, filterText, resultOffset, maxResults);
}
swift::ide::CodeCompletionCache &SwiftCompletionCache::getCache() {
return *inMemory;
}
SwiftCompletionCache::~SwiftCompletionCache() {}
void SwiftLangSupport::codeCompleteCacheOnDisk(StringRef path) {
ThreadSafeRefCntPtr<SwiftCompletionCache> newCache(new SwiftCompletionCache);
newCache->onDisk = std::make_unique<ide::OnDiskCodeCompletionCache>(path);
newCache->inMemory =
std::make_unique<ide::CodeCompletionCache>(newCache->onDisk.get());
CCCache = newCache; // replace the old cache.
}
void SwiftLangSupport::codeCompleteSetPopularAPI(
ArrayRef<const char *> popularAPI, ArrayRef<const char *> unpopularAPI) {
using Factor = CodeCompletion::PopularityFactor;
ThreadSafeRefCntPtr<SwiftPopularAPI> newPopularAPI(new SwiftPopularAPI);
auto &nameToFactor = newPopularAPI->nameToFactor;
for (unsigned i = 0, n = popularAPI.size(); i < n; ++i) {
SmallString<64> name;
if (canonicalizeFilterName(popularAPI[i], name))
continue;
nameToFactor[name] = Factor(double(n - i) / n);
}
for (unsigned i = 0, n = unpopularAPI.size(); i < n; ++i) {
SmallString<64> name;
if (canonicalizeFilterName(unpopularAPI[i], name))
continue;
nameToFactor[name] = Factor(-double(n - i) / n);
}
PopularAPI = newPopularAPI; // replace the old popular API.
}
void SwiftLangSupport::codeCompleteSetCustom(
ArrayRef<CustomCompletionInfo> completions) {
ThreadSafeRefCntPtr<SwiftCustomCompletions> newCustomCompletions(
new SwiftCustomCompletions);
newCustomCompletions->customCompletions.assign(completions.begin(),
completions.end());
CustomCompletions = newCustomCompletions; // Replace the existing completions.
}