//===--- 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 "SourceKit/Support/Logging.h" #include "SourceKit/Support/UIdent.h" #include "swift/Basic/Fallthrough.h" #include "swift/Frontend/Frontend.h" #include "swift/Frontend/PrintingDiagnosticConsumer.h" #include "swift/IDE/CodeCompletionCache.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) { llvm::SmallString<64> name; { llvm::raw_svector_ostream OSS(name); CodeCompletion::CompletionBuilder::getFilterName( result->getCompletionString(), 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. CodeCompletion::CompletionBuilder::getDescription( result, OSS, /*leadingPunctuation=*/false); } Completion extended(*result, name, description); return handleResult(consumer, &extended, /*leadingPunctuation=*/false, /*legacyLiteralToKeyword=*/true); } static bool handleResult(SourceKit::CodeCompletionConsumer &consumer, Completion *result, bool leadingPunctuation, bool legacyLiteralToKeyword); static void getResultSourceText(const CodeCompletionString *CCStr, raw_ostream &OS); static void getResultTypeName(const CodeCompletionString *CCStr, raw_ostream &OS); static void getResultAssociatedUSRs(ArrayRef AssocUSRs, raw_ostream &OS); }; struct SwiftCodeCompletionConsumer : public ide::SimpleCachingCodeCompletionConsumer { using HandlerFunc = std::function, SwiftCompletionInfo &)>; HandlerFunc handleResultsImpl; SwiftCompletionInfo swiftContext; SwiftCodeCompletionConsumer(HandlerFunc handleResultsImpl) : handleResultsImpl(handleResultsImpl) {} void setContext(swift::ASTContext *context, swift::CompilerInvocation *invocation, swift::ide::CodeCompletionContext *completionContext) { swiftContext.swiftASTContext = context; swiftContext.invocation = invocation; swiftContext.completionContext = completionContext; } void clearContext() { swiftContext = SwiftCompletionInfo(); } void handleResults(MutableArrayRef Results) override { assert(swiftContext.swiftASTContext); CodeCompletionContext::sortCompletionResults(Results); handleResultsImpl(Results, swiftContext); } }; } // anonymous namespace static bool swiftCodeCompleteImpl(SwiftLangSupport &Lang, llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, SwiftCodeCompletionConsumer &SwiftConsumer, ArrayRef Args, std::string &Error) { trace::TracedOperation TracedOp; if (trace::enabled()) { trace::SwiftInvocation SwiftArgs; trace::initTraceInfo(SwiftArgs, UnresolvedInputFile->getBufferIdentifier(), Args); SwiftArgs.addFile(UnresolvedInputFile->getBufferIdentifier(), UnresolvedInputFile->getBuffer()); TracedOp.start(trace::OperationKind::CodeCompletionInit, SwiftArgs, { std::make_pair("Offset", std::to_string(Offset)), std::make_pair("InputBufferSize", std::to_string(UnresolvedInputFile->getBufferSize()))}); } // Resolve symlinks for the input file; we resolve them for the input files // in the arguments as well. // FIXME: We need the Swift equivalent of Clang's FileEntry. auto InputFile = llvm::MemoryBuffer::getMemBuffer( UnresolvedInputFile->getBuffer(), Lang.resolvePathSymlinks(UnresolvedInputFile->getBufferIdentifier())); CompilerInstance CI; // Display diagnostics to stderr. PrintingDiagnosticConsumer PrintDiags; CI.addDiagnosticConsumer(&PrintDiags); CompilerInvocation Invocation; bool Failed = Lang.getASTManager().initCompilerInvocation( Invocation, Args, CI.getDiags(), InputFile->getBufferIdentifier(), Error); if (Failed) { return false; } if (Invocation.getInputFilenames().empty()) { Error = "no input filenames specified"; return false; } auto origBuffSize = InputFile->getBufferSize(); unsigned CodeCompletionOffset = Offset; if (CodeCompletionOffset > origBuffSize) { CodeCompletionOffset = origBuffSize; } const char *Position = InputFile->getBufferStart() + CodeCompletionOffset; std::unique_ptr NewBuffer = llvm::MemoryBuffer::getNewUninitMemBuffer(InputFile->getBufferSize() + 1, InputFile->getBufferIdentifier()); char *NewBuf = const_cast(NewBuffer->getBufferStart()); char *NewPos = std::copy(InputFile->getBufferStart(), Position, NewBuf); *NewPos = '\0'; std::copy(Position, InputFile->getBufferEnd(), NewPos+1); Invocation.setCodeCompletionPoint(NewBuffer.get(), CodeCompletionOffset); auto swiftCache = Lang.getCodeCompletionCache(); // Pin the cache. ide::CodeCompletionContext CompletionContext(swiftCache->getCache()); // Create a factory for code completion callbacks that will feed the // Consumer. std::unique_ptr CompletionCallbacksFactory( ide::makeCodeCompletionCallbacksFactory(CompletionContext, SwiftConsumer)); Invocation.setCodeCompletionFactory(CompletionCallbacksFactory.get()); // FIXME: We need to be passing the buffers from the open documents. // It is not a huge problem in practice because Xcode auto-saves constantly. if (CI.setup(Invocation)) { // FIXME: error? return true; } TracedOp.finish(); if (trace::enabled()) { trace::SwiftInvocation SwiftArgs; trace::initTraceInfo(SwiftArgs, InputFile->getBufferIdentifier(), Args); trace::initTraceFiles(SwiftArgs, CI); // Replace primary file with original content std::for_each(SwiftArgs.Files.begin(), SwiftArgs.Files.end(), [&] (trace::StringPairs::value_type &Pair) { if (Pair.first == InputFile->getBufferIdentifier()) { Pair.second = InputFile->getBuffer(); } }); TracedOp.start(trace::OperationKind::CodeCompletion, SwiftArgs, {std::make_pair("OriginalOffset", std::to_string(Offset)), std::make_pair("Offset", std::to_string(CodeCompletionOffset))}); } CloseClangModuleFiles scopedCloseFiles( *CI.getASTContext().getClangModuleLoader()); SwiftConsumer.setContext(&CI.getASTContext(), &Invocation, &CompletionContext); CI.performSema(); SwiftConsumer.clearContext(); return true; } void SwiftLangSupport::codeComplete(llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, SourceKit::CodeCompletionConsumer &SKConsumer, ArrayRef Args) { SwiftCodeCompletionConsumer SwiftConsumer([&]( MutableArrayRef Results, SwiftCompletionInfo &info) { bool hasExpectedType = info.completionContext->HasExpectedTypeRelation; 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 (hasExpectedType && Result->getExpectedTypeRelation() < CodeCompletionResult::Convertible) continue; break; default: continue; } } if (!SwiftToSourceKitCompletionAdapter::handleResult(SKConsumer, Result)) break; } }); std::string Error; if (!swiftCodeCompleteImpl(*this, UnresolvedInputFile, Offset, SwiftConsumer, Args, Error)) { SKConsumer.failed(Error); } } static void getResultStructure( CodeCompletion::SwiftResult *result, bool leadingPunctuation, CodeCompletionInfo::DescriptionStructure &structure, std::vector ¶meters) { 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::Whitespace)) continue; if (C.is(ChunkKind::LeftParen) || C.is(ChunkKind::LeftBracket) || C.is(ChunkKind::BraceStmtWithCursor) || C.is(ChunkKind::CallParameterBegin)) break; if (C.is(ChunkKind::Equal)) isOperator = true; 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::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)) 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) { 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); CodeCompletion::CompletionBuilder::getDescription( Result, ccOS, leadingPunctuation); } unsigned DescEnd = SS.size(); if (Result->getName().empty() || DescBegin == DescEnd) { LOG_FUNC_SECTION_WARN { llvm::SmallString<64> LogMessage; llvm::raw_svector_ostream LogMessageOs(LogMessage); LogMessageOs << "Code completion result with empty name and/or " "description was ignored: \n"; Result->print(LogMessageOs); *Log << LogMessage; } return true; } unsigned TextBegin = SS.size(); { llvm::raw_svector_ostream ccOS(SS); getResultSourceText(Result->getCompletionString(), ccOS); } unsigned TextEnd = SS.size(); unsigned TypeBegin = SS.size(); { llvm::raw_svector_ostream ccOS(SS); getResultTypeName(Result->getCompletionString(), 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; } Info.ModuleName = Result->getModuleName(); Info.DocBrief = Result->getBriefDocComment(); Info.NotRecommended = Result->isNotRecommended(); Info.NumBytesToErase = Result->getNumBytesToErase(); // Extended result values. Info.ModuleImportDepth = Result->getModuleImportDepth(); // Description structure. std::vector 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 SIL_KEYWORD(kw) #define KEYWORD(kw) \ static UIdent Keyword##kw##UID("source.lang.swift.keyword." #kw); \ if (uid == Keyword##kw##UID) { \ return CodeCompletionKeywordKind::kw_##kw; \ } #include "swift/Parse/Tokens.def" // FIXME: should warn about unexpected keyword kind. return CodeCompletionKeywordKind::None; } using ChunkKind = CodeCompletionString::Chunk::ChunkKind; /// Provide the text for the call parameter, including constructing a typed /// editor placeholder for it. static void constructTextForCallParam( ArrayRef ParamGroup, raw_ostream &OS) { assert(ParamGroup.front().is(ChunkKind::CallParameterBegin)); for (; !ParamGroup.empty(); ParamGroup = ParamGroup.slice(1)) { auto &C = ParamGroup.front(); if (C.is(ChunkKind::CallParameterInternalName) || C.is(ChunkKind::CallParameterType)) { break; } if (!C.isAnnotation() && C.hasText()) { OS << C.getText(); } } SmallString<32> DisplayString; SmallString<32> TypeString; SmallString<32> ExpansionTypeString; for (auto &C : ParamGroup) { if (C.isAnnotation() || !C.hasText()) continue; if (C.is(ChunkKind::CallParameterClosureType)) { assert(ExpansionTypeString.empty()); ExpansionTypeString = C.getText(); continue; } if (C.is(ChunkKind::CallParameterType)) { assert(TypeString.empty()); TypeString = C.getText(); } DisplayString += C.getText(); } StringRef Display = DisplayString.str(); StringRef Type = TypeString.str(); StringRef ExpansionType = ExpansionTypeString.str(); if (ExpansionType.empty()) ExpansionType = Type; OS << "<#T##" << Display; if (Display == Type && Display == ExpansionType) { // Short version, display and type are the same. } else { OS << "##" << Type; if (ExpansionType != Type) OS << "##" << ExpansionType; } OS << "#>"; } void SwiftToSourceKitCompletionAdapter::getResultSourceText( const CodeCompletionString *CCStr, raw_ostream &OS) { auto Chunks = CCStr->getChunks(); for (size_t i = 0; i < Chunks.size(); ++i) { auto &C = Chunks[i]; if (C.is(ChunkKind::BraceStmtWithCursor)) { OS << " {\n<#code#>\n}"; continue; } if (C.is(ChunkKind::CallParameterBegin)) { size_t Start = i++; for (; i < Chunks.size(); ++i) { if (Chunks[i].endsPreviousNestedGroup(C.getNestingLevel())) break; } constructTextForCallParam(Chunks.slice(Start, i-Start), OS); --i; continue; } if (!C.isAnnotation() && C.hasText()) { OS << C.getText(); } } } void SwiftToSourceKitCompletionAdapter::getResultTypeName( const CodeCompletionString *CCStr, raw_ostream &OS) { for (auto C : CCStr->getChunks()) { if (C.getKind() == CodeCompletionString::Chunk::ChunkKind::TypeAnnotation) { OS << C.getText(); } } } void SwiftToSourceKitCompletionAdapter::getResultAssociatedUSRs( ArrayRef 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 &&completions) { llvm::sys::ScopedLock L(mtx); sortedCompletions = std::move(completions); } ArrayRef CodeCompletion::SessionCache::getSortedCompletions() { llvm::sys::ScopedLock L(mtx); return sortedCompletions; } llvm::MemoryBuffer *CodeCompletion::SessionCache::getBuffer() { llvm::sys::ScopedLock L(mtx); return buffer.get(); } ArrayRef CodeCompletion::SessionCache::getCompilerArgs() { llvm::sys::ScopedLock L(mtx); return args; } CompletionKind CodeCompletion::SessionCache::getCompletionKind() { llvm::sys::ScopedLock L(mtx); return completionKind; } bool CodeCompletion::SessionCache::getCompletionHasExpectedTypes() { llvm::sys::ScopedLock L(mtx); return completionHasExpectedTypes; } 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); } 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 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"); 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(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); } /// 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 &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; } SWIFT_FALLTHROUGH; default: Result.push_back(curr); continue; } } llvm_unreachable("exit is on null byte"); } static void translateFilterRules(ArrayRef 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.hideByName[canonName] = 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 static std::vector filterInnerResults(ArrayRef results, bool includeInner, bool includeInnerOperators, bool &hasDot, bool &hasQDot, bool &hasInit, const CodeCompletion::FilterRules &rules) { std::vector topResults; for (auto *result : results) { if (!includeInnerOperators && result->isOperator()) continue; llvm::SmallString<64> name; { llvm::raw_svector_ostream OSS(name); CodeCompletion::CompletionBuilder::getFilterName( result->getCompletionString(), OSS); } if (rules.hideCompletion(result, name)) 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 chunks) { auto *completionString = CodeCompletionString::create(innerSink.allocator, chunks); CodeCompletion::SwiftResult paren( CodeCompletion::SwiftResult::ResultKind::BuiltinOperator, SemanticContextKind::ExpressionSpecific, exactMatch ? exactMatch->getNumBytesToErase() : 0, completionString); SwiftCompletionInfo info; std::vector 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->getCompletionHasExpectedTypes()); auto &rules = session->getFilterRules(); bool hasEarlyInnerResults = session->getCompletionKind() == CompletionKind::PostfixExpr; if (!hasEarlyInnerResults) { organizer.addCompletionsWithFilter(session->getSortedCompletions(), filterText, rules, 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.hideName("(")) innerResults.insert(innerResults.begin(), buildParen()); if (hasDot && !rules.hideName(".")) innerResults.insert(innerResults.begin(), buildDot()); if (hasQDot && !rules.hideName("?.")) 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 innerResults; bool hasDot = false; bool hasQDot = false; bool hasInit = false; SwiftCodeCompletionConsumer swiftConsumer([&]( MutableArrayRef results, SwiftCompletionInfo &info) { 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); { llvm::raw_string_ostream OSS(str); SwiftToSourceKitCompletionAdapter::getResultSourceText( exactMatch->getCompletionString(), OSS); } auto buffer = llvm::MemoryBuffer::getMemBuffer(str, inputBuf->getBufferIdentifier()); auto args = session->getCompilerArgs(); std::vector cargs; for (auto &arg : args) cargs.push_back(arg.c_str()); std::string error; if (!swiftCodeCompleteImpl(lang, buffer.get(), str.size(), swiftConsumer, cargs, error)) { consumer.failed(error); return; } if (options.addInnerOperators) { if (hasInit && !rules.hideName("(")) innerResults.insert(innerResults.begin(), buildParen()); if (hasDot && !rules.hideName(".")) innerResults.insert(innerResults.begin(), buildDot()); if (hasQDot && !rules.hideName("?.")) 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 rawFilterRules, GroupedCodeCompletionConsumer &consumer, ArrayRef args) { StringRef filterText; unsigned resultOffset = 0; unsigned maxResults = 0; CodeCompletion::Options CCOpts; if (options) translateCodeCompletionOptions(*options, CCOpts, filterText, resultOffset, maxResults); CodeCompletion::FilterRules filterRules; translateFilterRules(rawFilterRules, filterRules); // Set up the code completion consumer to pass results to organizer. CodeCompletion::CompletionSink sink; std::vector completions; NameToPopularityMap *nameToPopularity = nullptr; // This reference must outlive the uses of nameToPopularity. auto popularAPIRef = PopularAPI; if (popularAPIRef) { nameToPopularity = &popularAPIRef->nameToFactor; } CompletionKind completionKind = CompletionKind::None; bool hasExpectedTypes = false; SwiftCodeCompletionConsumer swiftConsumer( [&](MutableArrayRef results, SwiftCompletionInfo &info) { completionKind = info.completionContext->CodeCompletionKind; hasExpectedTypes = info.completionContext->HasExpectedTypeRelation; completions = extendCompletions(results, sink, info, nameToPopularity, CCOpts); }); // Add any codecomplete.open specific flags. std::vector extendedArgs(args.begin(), args.end()); if (CCOpts.addInitsToTopLevel) extendedArgs.push_back("-code-complete-inits-in-postfix-expr"); // Invoke completion. std::string error; if (!swiftCodeCompleteImpl(*this, inputBuf, offset, swiftConsumer, extendedArgs, 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 argsCopy(extendedArgs.begin(), extendedArgs.end()); SessionCacheRef session{new SessionCache( std::move(sink), std::move(bufferCopy), std::move(argsCopy), completionKind, hasExpectedTypes, 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 newCache(new SwiftCompletionCache); newCache->onDisk = llvm::make_unique(path); newCache->inMemory = llvm::make_unique(newCache->onDisk.get()); CCCache = newCache; // replace the old cache. } void SwiftLangSupport::codeCompleteSetPopularAPI( ArrayRef popularAPI, ArrayRef unpopularAPI) { using Factor = CodeCompletion::PopularityFactor; ThreadSafeRefCntPtr 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 completions) { ThreadSafeRefCntPtr newCustomCompletions( new SwiftCustomCompletions); newCustomCompletions->customCompletions.assign(completions.begin(), completions.end()); CustomCompletions = newCustomCompletions; // Replace the existing completions. }