mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[IDE] Implement completion-like cursor info for ValueDecls
This brings up the ability to compute cursor info results using the completion-like type checking paradigm, which an reuse ASTContexts and doesn’t need to type check the entire file. For now, the new implementation only supports cursor info on `ValueDecl`s (not on references) because they were easiest to implement. More cursor info kinds are coming soon. At the moment, we only run the new implementation in a verification mode: It is only invoked in assert toolchains and when run, we check that the results are equivalent to the old implementation. Once more cursor info kinds are implemented and if the SourceKit stress tester doesn’t find any verification issues, we can enable the new implementation, falling back to the old implementation if the new one didn’t produce any results.
This commit is contained in:
40
include/swift/IDE/CursorInfo.h
Normal file
40
include/swift/IDE/CursorInfo.h
Normal file
@@ -0,0 +1,40 @@
|
||||
//===--- CursorInfo.h --- ---------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2019 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef SWIFT_IDE_CURSORINFO_H
|
||||
#define SWIFT_IDE_CURSORINFO_H
|
||||
|
||||
#include "swift/AST/Type.h"
|
||||
#include "swift/Basic/LLVM.h"
|
||||
#include "swift/IDE/Utils.h"
|
||||
|
||||
namespace swift {
|
||||
class CodeCompletionCallbacksFactory;
|
||||
|
||||
namespace ide {
|
||||
|
||||
/// An abstract base class for consumers of context info results.
|
||||
class CursorInfoConsumer {
|
||||
public:
|
||||
virtual ~CursorInfoConsumer() {}
|
||||
virtual void handleResults(const ResolvedCursorInfo &) = 0;
|
||||
};
|
||||
|
||||
/// Create a factory for code completion callbacks.
|
||||
CodeCompletionCallbacksFactory *
|
||||
makeCursorInfoCallbacksFactory(CursorInfoConsumer &Consumer,
|
||||
SourceLoc RequestedLoc);
|
||||
|
||||
} // namespace ide
|
||||
} // namespace swift
|
||||
|
||||
#endif // SWIFT_IDE_CURSORINFO_H
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "swift/IDE/CodeCompletionResult.h"
|
||||
#include "swift/IDE/CodeCompletionResultSink.h"
|
||||
#include "swift/IDE/ConformingMethodList.h"
|
||||
#include "swift/IDE/CursorInfo.h"
|
||||
#include "swift/IDE/ImportDepth.h"
|
||||
#include "swift/IDE/SwiftCompletionInfo.h"
|
||||
#include "swift/IDE/TypeContextInfo.h"
|
||||
@@ -78,6 +79,14 @@ struct ConformingMethodListResults {
|
||||
bool DidReuseAST;
|
||||
};
|
||||
|
||||
/// The results returned from \c CompletionInstance::conformingMethodList.
|
||||
struct CursorInfoResults {
|
||||
/// The actual results. If \c nullptr, no results were found.
|
||||
const ResolvedCursorInfo *Result;
|
||||
/// Whether an AST was reused for the completion.
|
||||
bool DidReuseAST;
|
||||
};
|
||||
|
||||
/// Manages \c CompilerInstance for completion like operations.
|
||||
class CompletionInstance {
|
||||
struct Options {
|
||||
@@ -192,6 +201,14 @@ public:
|
||||
std::shared_ptr<std::atomic<bool>> CancellationFlag,
|
||||
llvm::function_ref<void(CancellableResult<ConformingMethodListResults>)>
|
||||
Callback);
|
||||
|
||||
void cursorInfo(
|
||||
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
|
||||
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
|
||||
DiagnosticConsumer *DiagC,
|
||||
std::shared_ptr<std::atomic<bool>> CancellationFlag,
|
||||
llvm::function_ref<void(CancellableResult<CursorInfoResults>)> Callback);
|
||||
};
|
||||
|
||||
} // namespace ide
|
||||
|
||||
@@ -18,6 +18,7 @@ add_swift_host_library(swiftIDE STATIC
|
||||
CompletionLookup.cpp
|
||||
CompletionOverrideLookup.cpp
|
||||
ConformingMethodList.cpp
|
||||
CursorInfo.cpp
|
||||
ExprCompletion.cpp
|
||||
ExprContextAnalysis.cpp
|
||||
Formatting.cpp
|
||||
|
||||
278
lib/IDE/CursorInfo.cpp
Normal file
278
lib/IDE/CursorInfo.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
//===--- CursorInfo.cpp ---------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2022 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "swift/IDE/CursorInfo.h"
|
||||
#include "ExprContextAnalysis.h"
|
||||
#include "swift/AST/ASTDemangler.h"
|
||||
#include "swift/AST/GenericEnvironment.h"
|
||||
#include "swift/AST/NameLookup.h"
|
||||
#include "swift/AST/USRGeneration.h"
|
||||
#include "swift/IDE/TypeCheckCompletionCallback.h"
|
||||
#include "swift/Parse/CodeCompletionCallbacks.h"
|
||||
#include "swift/Sema/ConstraintSystem.h"
|
||||
#include "swift/Sema/IDETypeChecking.h"
|
||||
#include "clang/AST/Attr.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/Basic/Module.h"
|
||||
|
||||
using namespace swift;
|
||||
using namespace swift::constraints;
|
||||
using namespace ide;
|
||||
|
||||
namespace {
|
||||
|
||||
// MARK: - Utilities
|
||||
|
||||
void typeCheckDeclAndParentClosures(ValueDecl *VD) {
|
||||
// We need to type check any parent closures because their types are
|
||||
// encoded in the USR of ParentContexts in the cursor info response.
|
||||
auto DC = VD->getDeclContext();
|
||||
while (DC->getParent()) {
|
||||
if (auto Closure = dyn_cast<AbstractClosureExpr>(DC)) {
|
||||
if (Closure->getType().isNull()) {
|
||||
typeCheckASTNodeAtLoc(
|
||||
TypeCheckASTNodeAtLocContext::declContext(DC->getParent()),
|
||||
Closure->getLoc());
|
||||
}
|
||||
}
|
||||
DC = DC->getParent();
|
||||
}
|
||||
|
||||
typeCheckASTNodeAtLoc(
|
||||
TypeCheckASTNodeAtLocContext::declContext(VD->getDeclContext()),
|
||||
VD->getLoc());
|
||||
}
|
||||
|
||||
// MARK: - NodeFinderResults
|
||||
|
||||
enum class NodeFinderResultKind { Decl };
|
||||
|
||||
class NodeFinderResult {
|
||||
NodeFinderResultKind Kind;
|
||||
|
||||
protected:
|
||||
NodeFinderResult(NodeFinderResultKind Kind) : Kind(Kind) {}
|
||||
|
||||
public:
|
||||
NodeFinderResultKind getKind() const { return Kind; }
|
||||
};
|
||||
|
||||
class NodeFinderDeclResult : public NodeFinderResult {
|
||||
ValueDecl *ValueD;
|
||||
|
||||
public:
|
||||
NodeFinderDeclResult(ValueDecl *ValueD)
|
||||
: NodeFinderResult(NodeFinderResultKind::Decl), ValueD(ValueD) {}
|
||||
|
||||
ValueDecl *getDecl() const { return ValueD; }
|
||||
|
||||
static bool classof(const NodeFinderResult *Res) {
|
||||
return Res->getKind() == NodeFinderResultKind::Decl;
|
||||
}
|
||||
};
|
||||
|
||||
// MARK: - NodeFinder
|
||||
|
||||
/// Walks the AST, looking for a node at \c LocToResolve. While walking the
|
||||
/// AST, also gathers information about shorthand shadows.
|
||||
class NodeFinder : ASTWalker {
|
||||
SourceFile &SrcFile;
|
||||
SourceLoc LocToResolve;
|
||||
|
||||
/// As we are walking the tree, this variable is updated to the last seen
|
||||
/// DeclContext.
|
||||
SmallVector<DeclContext *> DeclContextStack;
|
||||
|
||||
/// The found node.
|
||||
std::unique_ptr<NodeFinderResult> Result;
|
||||
|
||||
/// If a decl shadows another decl using shorthand syntax (`[foo]` or
|
||||
/// `if let foo {`), this maps the re-declared variable to the one that is
|
||||
/// being shadowed.
|
||||
/// The transitive closure of shorthand shadowed decls should be reported as
|
||||
/// additional results in cursor info.
|
||||
llvm::DenseMap<ValueDecl *, ValueDecl *> ShorthandShadowedDecls;
|
||||
|
||||
public:
|
||||
NodeFinder(SourceFile &SrcFile, SourceLoc LocToResolve)
|
||||
: SrcFile(SrcFile), LocToResolve(LocToResolve),
|
||||
DeclContextStack({&SrcFile}) {}
|
||||
|
||||
void resolve() { SrcFile.walk(*this); }
|
||||
|
||||
std::unique_ptr<NodeFinderResult> takeResult() { return std::move(Result); }
|
||||
|
||||
/// Get the declarations that \p ShadowingDecl shadows using shorthand shadow
|
||||
/// syntax.
|
||||
SmallVector<ValueDecl *, 2>
|
||||
getShorthandShadowedDecls(ValueDecl *ShadowingDecl) {
|
||||
SmallVector<ValueDecl *, 2> Result;
|
||||
auto ShorthandShadowedDecl = ShorthandShadowedDecls[ShadowingDecl];
|
||||
while (ShorthandShadowedDecl) {
|
||||
Result.push_back(ShorthandShadowedDecl);
|
||||
ShorthandShadowedDecl = ShorthandShadowedDecls[ShorthandShadowedDecl];
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
private:
|
||||
SourceManager &getSourceMgr() const {
|
||||
return SrcFile.getASTContext().SourceMgr;
|
||||
}
|
||||
|
||||
/// The decl context that is currently being walked.
|
||||
DeclContext *getCurrentDeclContext() { return DeclContextStack.back(); }
|
||||
|
||||
bool rangeContainsLocToResolve(SourceRange Range) const {
|
||||
return Range.contains(LocToResolve);
|
||||
}
|
||||
|
||||
PreWalkAction walkToDeclPre(Decl *D) override {
|
||||
if (!rangeContainsLocToResolve(D->getSourceRangeIncludingAttrs())) {
|
||||
return PreWalkAction::SkipChildren;
|
||||
}
|
||||
|
||||
if (auto *newDC = dyn_cast<DeclContext>(D)) {
|
||||
DeclContextStack.push_back(newDC);
|
||||
}
|
||||
|
||||
if (D->getLoc() != LocToResolve) {
|
||||
return Action::Continue();
|
||||
}
|
||||
|
||||
if (auto VD = dyn_cast<ValueDecl>(D)) {
|
||||
if (VD->hasName()) {
|
||||
assert(Result == nullptr);
|
||||
Result = std::make_unique<NodeFinderDeclResult>(VD);
|
||||
return Action::Stop();
|
||||
}
|
||||
}
|
||||
|
||||
return Action::Continue();
|
||||
}
|
||||
|
||||
PostWalkAction walkToDeclPost(Decl *D) override {
|
||||
if (auto *newDC = dyn_cast<DeclContext>(D)) {
|
||||
assert(DeclContextStack.back() == newDC);
|
||||
DeclContextStack.pop_back();
|
||||
}
|
||||
return Action::Continue();
|
||||
}
|
||||
|
||||
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
|
||||
if (auto closure = dyn_cast<ClosureExpr>(E)) {
|
||||
DeclContextStack.push_back(closure);
|
||||
}
|
||||
|
||||
if (auto CaptureList = dyn_cast<CaptureListExpr>(E)) {
|
||||
for (auto ShorthandShadows :
|
||||
getShorthandShadows(CaptureList, getCurrentDeclContext())) {
|
||||
assert(ShorthandShadowedDecls.count(ShorthandShadows.first) == 0);
|
||||
ShorthandShadowedDecls[ShorthandShadows.first] =
|
||||
ShorthandShadows.second;
|
||||
}
|
||||
}
|
||||
|
||||
return Action::Continue(E);
|
||||
}
|
||||
|
||||
PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
|
||||
if (auto *closure = dyn_cast<ClosureExpr>(E)) {
|
||||
assert(DeclContextStack.back() == closure);
|
||||
DeclContextStack.pop_back();
|
||||
}
|
||||
return Action::Continue(E);
|
||||
}
|
||||
|
||||
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
|
||||
if (auto CondStmt = dyn_cast<LabeledConditionalStmt>(S)) {
|
||||
for (auto ShorthandShadow :
|
||||
getShorthandShadows(CondStmt, getCurrentDeclContext())) {
|
||||
assert(ShorthandShadowedDecls.count(ShorthandShadow.first) == 0);
|
||||
ShorthandShadowedDecls[ShorthandShadow.first] = ShorthandShadow.second;
|
||||
}
|
||||
}
|
||||
return Action::Continue(S);
|
||||
}
|
||||
};
|
||||
|
||||
// MARK: - CursorInfoDoneParsingCallback
|
||||
|
||||
class CursorInfoDoneParsingCallback : public CodeCompletionCallbacks {
|
||||
CursorInfoConsumer &Consumer;
|
||||
SourceLoc RequestedLoc;
|
||||
|
||||
public:
|
||||
CursorInfoDoneParsingCallback(Parser &P, CursorInfoConsumer &Consumer,
|
||||
SourceLoc RequestedLoc)
|
||||
: CodeCompletionCallbacks(P), Consumer(Consumer),
|
||||
RequestedLoc(RequestedLoc) {}
|
||||
|
||||
std::unique_ptr<ResolvedCursorInfo>
|
||||
getDeclResult(NodeFinderDeclResult *DeclResult, SourceFile *SrcFile,
|
||||
NodeFinder &Finder) const {
|
||||
typeCheckDeclAndParentClosures(DeclResult->getDecl());
|
||||
auto CursorInfo = std::make_unique<ResolvedValueRefCursorInfo>(
|
||||
ResolvedCursorInfo(SrcFile), DeclResult->getDecl(),
|
||||
/*CtorTyRef=*/nullptr,
|
||||
/*ExtTyRef=*/nullptr, /*IsRef=*/false, /*Ty=*/Type(),
|
||||
/*ContainerType=*/Type());
|
||||
CursorInfo->setLoc(RequestedLoc);
|
||||
CursorInfo->setShorthandShadowedDecls(
|
||||
Finder.getShorthandShadowedDecls(DeclResult->getDecl()));
|
||||
return CursorInfo;
|
||||
}
|
||||
|
||||
void doneParsing(SourceFile *SrcFile) override {
|
||||
if (!SrcFile) {
|
||||
return;
|
||||
}
|
||||
NodeFinder Finder(*SrcFile, RequestedLoc);
|
||||
Finder.resolve();
|
||||
auto Result = Finder.takeResult();
|
||||
if (!Result) {
|
||||
return;
|
||||
}
|
||||
std::unique_ptr<ResolvedCursorInfo> CursorInfo;
|
||||
switch (Result->getKind()) {
|
||||
case NodeFinderResultKind::Decl:
|
||||
CursorInfo = getDeclResult(cast<NodeFinderDeclResult>(Result.get()),
|
||||
SrcFile, Finder);
|
||||
break;
|
||||
}
|
||||
if (Result) {
|
||||
Consumer.handleResults(*CursorInfo);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace.
|
||||
|
||||
CodeCompletionCallbacksFactory *
|
||||
swift::ide::makeCursorInfoCallbacksFactory(CursorInfoConsumer &Consumer,
|
||||
SourceLoc RequestedLoc) {
|
||||
class CursorInfoCallbacksFactoryImpl : public CodeCompletionCallbacksFactory {
|
||||
CursorInfoConsumer &Consumer;
|
||||
SourceLoc RequestedLoc;
|
||||
|
||||
public:
|
||||
CursorInfoCallbacksFactoryImpl(CursorInfoConsumer &Consumer,
|
||||
SourceLoc RequestedLoc)
|
||||
: Consumer(Consumer), RequestedLoc(RequestedLoc) {}
|
||||
|
||||
CodeCompletionCallbacks *createCodeCompletionCallbacks(Parser &P) override {
|
||||
return new CursorInfoDoneParsingCallback(P, Consumer, RequestedLoc);
|
||||
}
|
||||
};
|
||||
|
||||
return new CursorInfoCallbacksFactoryImpl(Consumer, RequestedLoc);
|
||||
}
|
||||
@@ -794,3 +794,70 @@ void swift::ide::CompletionInstance::conformingMethodList(
|
||||
Callback);
|
||||
});
|
||||
}
|
||||
|
||||
void swift::ide::CompletionInstance::cursorInfo(
|
||||
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
|
||||
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
|
||||
DiagnosticConsumer *DiagC,
|
||||
std::shared_ptr<std::atomic<bool>> CancellationFlag,
|
||||
llvm::function_ref<void(CancellableResult<CursorInfoResults>)> Callback) {
|
||||
using ResultType = CancellableResult<CursorInfoResults>;
|
||||
|
||||
struct ConsumerToCallbackAdapter : public swift::ide::CursorInfoConsumer {
|
||||
bool ReusingASTContext;
|
||||
std::shared_ptr<std::atomic<bool>> CancellationFlag;
|
||||
llvm::function_ref<void(ResultType)> Callback;
|
||||
bool HandleResultsCalled = false;
|
||||
|
||||
ConsumerToCallbackAdapter(
|
||||
bool ReusingASTContext,
|
||||
std::shared_ptr<std::atomic<bool>> CancellationFlag,
|
||||
llvm::function_ref<void(ResultType)> Callback)
|
||||
: ReusingASTContext(ReusingASTContext),
|
||||
CancellationFlag(CancellationFlag), Callback(Callback) {}
|
||||
|
||||
void handleResults(const ResolvedCursorInfo &result) override {
|
||||
HandleResultsCalled = true;
|
||||
if (CancellationFlag &&
|
||||
CancellationFlag->load(std::memory_order_relaxed)) {
|
||||
Callback(ResultType::cancelled());
|
||||
} else {
|
||||
Callback(ResultType::success({&result, ReusingASTContext}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
performOperation(
|
||||
Invocation, Args, FileSystem, completionBuffer, Offset, DiagC,
|
||||
CancellationFlag,
|
||||
[&](CancellableResult<CompletionInstanceResult> CIResult) {
|
||||
CIResult.mapAsync<CursorInfoResults>(
|
||||
[&CancellationFlag, Offset](auto &Result, auto DeliverTransformed) {
|
||||
auto &Mgr = Result.CI->getSourceMgr();
|
||||
auto RequestedLoc =
|
||||
Mgr.getLocForOffset(Mgr.getCodeCompletionBufferID(), Offset);
|
||||
ConsumerToCallbackAdapter Consumer(
|
||||
Result.DidReuseAST, CancellationFlag, DeliverTransformed);
|
||||
std::unique_ptr<CodeCompletionCallbacksFactory> callbacksFactory(
|
||||
ide::makeCursorInfoCallbacksFactory(Consumer, RequestedLoc));
|
||||
|
||||
if (!Result.DidFindCodeCompletionToken) {
|
||||
return DeliverTransformed(ResultType::success(
|
||||
{/*Results=*/nullptr, Result.DidReuseAST}));
|
||||
}
|
||||
|
||||
performCodeCompletionSecondPass(
|
||||
*Result.CI->getCodeCompletionFile(), *callbacksFactory);
|
||||
if (!Consumer.HandleResultsCalled) {
|
||||
// If we didn't receive a handleResult call from the second
|
||||
// pass, we didn't receive any results. To make sure Callback
|
||||
// gets called exactly once, call it manually with no results
|
||||
// here.
|
||||
DeliverTransformed(ResultType::success(
|
||||
{/*Results=*/nullptr, Result.DidReuseAST}));
|
||||
}
|
||||
},
|
||||
Callback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,5 +9,15 @@
|
||||
// COMPILE_1: key.notification: source.notification.compile-did-finish,
|
||||
// COMPILE_1: key.compileid: [[CID1]]
|
||||
// COMPILE_1: }
|
||||
// FIXME: Once we switch to only run solver-based cursor info, we should only receive a single compile notification
|
||||
// COMPILE_1: {
|
||||
// COMPILE_1: key.notification: source.notification.compile-will-start,
|
||||
// COMPILE_1: key.filepath: "SOURCE_DIR{{.*}}cursor-info.swift",
|
||||
// COMPILE_1: key.compileid: [[CID2:".*"]]
|
||||
// COMPILE_1: }
|
||||
// COMPILE_1: {
|
||||
// COMPILE_1: key.notification: source.notification.compile-did-finish,
|
||||
// COMPILE_1: key.compileid: [[CID2]]
|
||||
// COMPILE_1: }
|
||||
// COMPILE_1-NOT: compile-will-start
|
||||
// COMPILE_1-NOT: compile-did-finish
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// RUN: %sourcekitd-test -req=open %s -- %s == \
|
||||
// RUN: -req=edit -pos=7:16 -length=0 -replace=yyy %s == \
|
||||
// RUN: -req=cursor -pos=7:16 %s -- %s | %FileCheck %s
|
||||
|
||||
class SceneDelegate {
|
||||
func scene(_ scene: String?) {
|
||||
if let xxx = scene {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK: source.lang.swift.decl.var.local (7:16-7:22)
|
||||
// CHECK-NEXT: yyyxxx
|
||||
13
test/SourceKit/CursorInfo/cursor_in_pound_if.swift
Normal file
13
test/SourceKit/CursorInfo/cursor_in_pound_if.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
// RUN: %sourcekitd-test -req=cursor -pos=6:7 %s -- %s | %FileCheck %s --check-prefix=CHECK-INT
|
||||
// RUN: %sourcekitd-test -req=cursor -pos=8:7 %s -- %s | %FileCheck %s --check-prefix=CHECK-STR
|
||||
|
||||
func foo() {
|
||||
#if USE_INT
|
||||
let xxx = 1
|
||||
#else
|
||||
let xxx = "hello"
|
||||
#endif
|
||||
}
|
||||
// TODO: Once we switch to use the solver-based cursor info implementation, we also receive results for the int case
|
||||
// CHECK-INT: Unable to resolve cursor info
|
||||
// CHECK-STR: <Declaration>let xxx: <Type usr="s:SS">String</Type></Declaration>
|
||||
@@ -652,7 +652,8 @@ std::unique_ptr<llvm::MemoryBuffer> SwiftASTManager::getMemoryBuffer(
|
||||
StringRef Filename,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
|
||||
std::string &Error) {
|
||||
return Impl.getMemoryBuffer(Filename, FileSystem, Error);
|
||||
return Impl.getFileContent(Filename, /*IsPrimary=*/false, FileSystem, Error)
|
||||
.Buffer;
|
||||
}
|
||||
|
||||
static FrontendInputsAndOutputs
|
||||
|
||||
@@ -1854,6 +1854,32 @@ resolveRange(SwiftLangSupport &Lang, StringRef InputFile, unsigned Offset,
|
||||
llvm::vfs::getRealFileSystem());
|
||||
}
|
||||
|
||||
static void deliverCursorInfoResults(
|
||||
std::function<void(const RequestResult<CursorInfoData> &)> Receiver,
|
||||
CancellableResult<CursorInfoResults> Results, SwiftLangSupport &Lang,
|
||||
const CompilerInvocation &Invoc, bool AddRefactorings,
|
||||
bool AddSymbolGraph) {
|
||||
switch (Results.getKind()) {
|
||||
case CancellableResultKind::Success: {
|
||||
// TODO: Implement delivery of other result types as more cursor info kinds
|
||||
// are migrated to be completion-like.
|
||||
if (auto Result = dyn_cast_or_null<ResolvedValueRefCursorInfo>(
|
||||
Results.getResult().Result)) {
|
||||
std::string Diagnostic; // Unused
|
||||
passCursorInfoForDecl(*Result, AddRefactorings, AddSymbolGraph, {}, Lang,
|
||||
Invoc, Diagnostic, /*PreviousSnaps=*/{}, Receiver);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CancellableResultKind::Failure:
|
||||
Receiver(RequestResult<CursorInfoData>::fromError(Results.getError()));
|
||||
break;
|
||||
case CancellableResultKind::Cancelled:
|
||||
Receiver(RequestResult<CursorInfoData>::cancelled());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SwiftLangSupport::getCursorInfo(
|
||||
StringRef InputFile, unsigned Offset, unsigned Length, bool Actionables,
|
||||
bool SymbolGraph, bool CancelOnSubsequentRequest,
|
||||
@@ -1906,9 +1932,134 @@ void SwiftLangSupport::getCursorInfo(
|
||||
return;
|
||||
}
|
||||
|
||||
/// Counts how many symbols \p Res contains.
|
||||
auto ResultCount = [](const RequestResult<CursorInfoData> &Res) -> size_t {
|
||||
if (Res.isCancelled()) {
|
||||
return 0;
|
||||
} else if (Res.isError()) {
|
||||
return 0;
|
||||
} else {
|
||||
return Res.value().Symbols.size();
|
||||
}
|
||||
};
|
||||
|
||||
/// Serializes \c CursorInfoData into a string.
|
||||
auto ResultDescription =
|
||||
[](const RequestResult<CursorInfoData> &Res) -> std::string {
|
||||
if (Res.isCancelled()) {
|
||||
return "cancelled";
|
||||
} else if (Res.isError()) {
|
||||
return Res.getError().str();
|
||||
} else {
|
||||
std::string Description;
|
||||
llvm::raw_string_ostream OS(Description);
|
||||
Res.value().print(OS, /*Indentation=*/"");
|
||||
return OS.str();
|
||||
}
|
||||
};
|
||||
|
||||
// Currently, we only verify that the solver-based cursor implementation
|
||||
// produces the same results as the AST-based implementation. Only enable it
|
||||
// in assert builds for now.
|
||||
#ifndef NDEBUG
|
||||
bool EnableSolverBasedCursorInfo = true;
|
||||
#else
|
||||
bool EnableSolverBasedCursorInfo = false;
|
||||
#endif
|
||||
|
||||
// If solver based completion is enabled, a string description of the cursor
|
||||
// info result produced by the solver-based implementation. Once the AST-based
|
||||
// result is produced, we verify that the solver-based result matches the
|
||||
// AST-based result.
|
||||
std::string SolverBasedResultDescription;
|
||||
size_t SolverBasedResultCount = 0;
|
||||
if (EnableSolverBasedCursorInfo) {
|
||||
std::string InputFileError;
|
||||
llvm::SmallString<64> RealInputFilePath;
|
||||
fileSystem->getRealPath(InputFile, RealInputFilePath);
|
||||
std::unique_ptr<llvm::MemoryBuffer> UnresolvedInputFile =
|
||||
getASTManager()->getMemoryBuffer(RealInputFilePath, fileSystem,
|
||||
InputFileError);
|
||||
if (UnresolvedInputFile) {
|
||||
auto SolverBasedReceiver = [&](const RequestResult<CursorInfoData> &Res) {
|
||||
SolverBasedResultCount = ResultCount(Res);
|
||||
SolverBasedResultDescription = ResultDescription(Res);
|
||||
};
|
||||
|
||||
CompilerInvocation CompInvok;
|
||||
Invok->applyTo(CompInvok);
|
||||
|
||||
performWithParamsToCompletionLikeOperation(
|
||||
UnresolvedInputFile.get(), Offset,
|
||||
/*InsertCodeCompletionToken=*/false, Args, fileSystem,
|
||||
CancellationToken,
|
||||
[&](CancellableResult<CompletionLikeOperationParams> ParmsResult) {
|
||||
ParmsResult.mapAsync<CursorInfoResults>(
|
||||
[&](auto &Params, auto DeliverTransformed) {
|
||||
getCompletionInstance()->cursorInfo(
|
||||
Params.Invocation, Args, fileSystem,
|
||||
Params.completionBuffer, Offset, Params.DiagC,
|
||||
Params.CancellationFlag, DeliverTransformed);
|
||||
},
|
||||
[&](auto Result) {
|
||||
deliverCursorInfoResults(SolverBasedReceiver, Result, *this,
|
||||
CompInvok, Actionables, SymbolGraph);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// If the solver-based implementation returned a different result than the
|
||||
/// AST-based implementation, return an error message, describing the
|
||||
/// difference. Otherwise, return an empty string.
|
||||
auto VerifySolverBasedResult =
|
||||
[ResultCount, ResultDescription, SolverBasedResultCount,
|
||||
SolverBasedResultDescription](
|
||||
const RequestResult<CursorInfoData> &ASTBasedResult) -> std::string {
|
||||
if (SolverBasedResultDescription.empty()) {
|
||||
// We did not run the solver-based implementation. Nothing to check.
|
||||
return "";
|
||||
}
|
||||
auto ASTResultDescription = ResultDescription(ASTBasedResult);
|
||||
auto ASTResultCount = ResultCount(ASTBasedResult);
|
||||
if (ASTResultCount == 0 && SolverBasedResultCount > 0) {
|
||||
// The AST-based implementation did not return any results but the
|
||||
// solver-based did. That's an improvement. Success.
|
||||
return "";
|
||||
}
|
||||
if (SolverBasedResultDescription == ASTResultDescription) {
|
||||
// The solver-based and AST-based implementation produced the same
|
||||
// results. Success.
|
||||
return "";
|
||||
}
|
||||
// The solver-based implementation differed from the AST-based
|
||||
// implementation. Report a failure.
|
||||
std::string ErrorMessage;
|
||||
llvm::raw_string_ostream OS(ErrorMessage);
|
||||
OS << "The solver-based implementation returned a different result than "
|
||||
"the AST-based implementation:\n";
|
||||
OS << SolverBasedResultDescription << "\n";
|
||||
OS << "===== (solver-based vs. AST-based) =====\n";
|
||||
OS << ASTResultDescription << "\n";
|
||||
return OS.str();
|
||||
};
|
||||
|
||||
// Thunk around `Receiver` that, if solver-based cursor info is enabled,
|
||||
// verifies that the solver-based cursor info result matches the AST-based
|
||||
// result.
|
||||
auto ReceiverThunk = [Receiver, VerifySolverBasedResult](
|
||||
const RequestResult<CursorInfoData> &Res) {
|
||||
auto VerificationError = VerifySolverBasedResult(Res);
|
||||
if (VerificationError.empty()) {
|
||||
Receiver(Res);
|
||||
} else {
|
||||
Receiver(RequestResult<CursorInfoData>::fromError(VerificationError));
|
||||
}
|
||||
};
|
||||
|
||||
resolveCursor(*this, InputFile, Offset, Length, Actionables, SymbolGraph,
|
||||
Invok, /*TryExistingAST=*/true, CancelOnSubsequentRequest,
|
||||
fileSystem, CancellationToken, Receiver);
|
||||
fileSystem, CancellationToken, ReceiverThunk);
|
||||
}
|
||||
|
||||
void SwiftLangSupport::getDiagnostics(
|
||||
|
||||
Reference in New Issue
Block a user