static bool swiftCodeCompleteImpl(SwiftLangSupport &Lang, llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, SwiftCodeCompletionConsumer &SwiftConsumer, ArrayRef<const char *> 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<llvm::MemoryBuffer> NewBuffer = llvm::MemoryBuffer::getNewUninitMemBuffer(InputFile->getBufferSize() + 1, InputFile->getBufferIdentifier()); char *NewBuf = const_cast<char*>(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<CodeCompletionCallbacksFactory> 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; }
static void resolveCursor(SwiftLangSupport &Lang, StringRef InputFile, unsigned Offset, SwiftInvocationRef Invok, bool TryExistingAST, std::function<void(const CursorInfo &)> Receiver) { assert(Invok); class CursorInfoConsumer : public SwiftASTConsumer { std::string InputFile; unsigned Offset; SwiftLangSupport ⟪ SwiftInvocationRef ASTInvok; const bool TryExistingAST; std::function<void(const CursorInfo &)> Receiver; SmallVector<ImmutableTextSnapshotRef, 4> PreviousASTSnaps; public: CursorInfoConsumer(StringRef InputFile, unsigned Offset, SwiftLangSupport &Lang, SwiftInvocationRef ASTInvok, bool TryExistingAST, std::function<void(const CursorInfo &)> Receiver) : InputFile(InputFile), Offset(Offset), Lang(Lang), ASTInvok(std::move(ASTInvok)), TryExistingAST(TryExistingAST), Receiver(std::move(Receiver)) { } bool canUseASTWithSnapshots( ArrayRef<ImmutableTextSnapshotRef> Snapshots) override { if (!TryExistingAST) { LOG_INFO_FUNC(High, "will resolve using up-to-date AST"); return false; } // If there is an existing AST and the offset can be mapped back to the // document snapshot that was used to create it, then use that AST. // The downside is that we may return stale information, but we get the // benefit of increased responsiveness, since the request will not be // blocked waiting on the AST to be fully typechecked. ImmutableTextSnapshotRef InputSnap; if (auto EditorDoc = Lang.getEditorDocuments().findByPath(InputFile)) InputSnap = EditorDoc->getLatestSnapshot(); if (!InputSnap) return false; auto mappedBackOffset = [&]()->llvm::Optional<unsigned> { for (auto &Snap : Snapshots) { if (Snap->isFromSameBuffer(InputSnap)) { if (Snap->getStamp() == InputSnap->getStamp()) return Offset; auto OptOffset = mapOffsetToOlderSnapshot(Offset, InputSnap, Snap); if (!OptOffset.hasValue()) return None; // Check that the new and old offset still point to the same token. StringRef NewTok = getSourceToken(Offset, InputSnap); if (NewTok.empty()) return None; if (NewTok == getSourceToken(OptOffset.getValue(), Snap)) return OptOffset; return None; } } return None; }; auto OldOffsetOpt = mappedBackOffset(); if (OldOffsetOpt.hasValue()) { Offset = *OldOffsetOpt; PreviousASTSnaps.append(Snapshots.begin(), Snapshots.end()); LOG_INFO_FUNC(High, "will try existing AST"); return true; } LOG_INFO_FUNC(High, "will resolve using up-to-date AST"); return false; } void handlePrimaryAST(ASTUnitRef AstUnit) override { auto &CompIns = AstUnit->getCompilerInstance(); Module *MainModule = CompIns.getMainModule(); unsigned BufferID = AstUnit->getPrimarySourceFile().getBufferID().getValue(); SourceLoc Loc = Lexer::getLocForStartOfToken(CompIns.getSourceMgr(), BufferID, Offset); if (Loc.isInvalid()) { Receiver({}); return; } trace::TracedOperation TracedOp; if (trace::enabled()) { trace::SwiftInvocation SwiftArgs; ASTInvok->raw(SwiftArgs.Args.Args, SwiftArgs.Args.PrimaryFile); trace::initTraceFiles(SwiftArgs, CompIns); TracedOp.start(trace::OperationKind::CursorInfoForSource, SwiftArgs, {std::make_pair("Offset", std::to_string(Offset))}); } SemaLocResolver Resolver(AstUnit->getPrimarySourceFile()); SemaToken SemaTok = Resolver.resolve(Loc); if (SemaTok.isInvalid()) { Receiver({}); return; } CompilerInvocation CompInvok; ASTInvok->applyTo(CompInvok); if (SemaTok.Mod) { passCursorInfoForModule(SemaTok.Mod, Lang.getIFaceGenContexts(), CompInvok, Receiver); } else { ValueDecl *VD = SemaTok.CtorTyRef ? SemaTok.CtorTyRef : SemaTok.ValueD; bool Failed = passCursorInfoForDecl(VD, MainModule, SemaTok.Ty, SemaTok.IsRef, BufferID, Lang, CompInvok, PreviousASTSnaps, Receiver); if (Failed) { if (!PreviousASTSnaps.empty()) { // Attempt again using the up-to-date AST. resolveCursor(Lang, InputFile, Offset, ASTInvok, /*TryExistingAST=*/false, Receiver); } else { Receiver({}); } } } } void cancelled() override { CursorInfo Info; Info.IsCancelled = true; Receiver(Info); } void failed(StringRef Error) override { LOG_WARN_FUNC("cursor info failed: " << Error); Receiver({}); } }; auto Consumer = std::make_shared<CursorInfoConsumer>( InputFile, Offset, Lang, Invok, TryExistingAST, Receiver); /// FIXME: When request cancellation is implemented and Xcode adopts it, /// don't use 'OncePerASTToken'. static const char OncePerASTToken = 0; Lang.getASTManager().processASTAsync(Invok, std::move(Consumer), &OncePerASTToken); }