Skip to content

Commit 2fcb24e

Browse files
committed
[CodeCompletion] Refactor how code completion results are returned to support cancellation
This refactors a bunch of code-completion methods around `performOperation` to return their results via a callback only instead of the current mixed approach of indicating failure via a return value, returning an error string as an inout parameter and success results via a callback. The new guarantee should be that the callback is always called exactly once on control flow graph. Other than a support for passing the (currently unused) cancelled state through the different instance, there should be no functionality change.
1 parent c209f52 commit 2fcb24e

File tree

13 files changed

+695
-188
lines changed

13 files changed

+695
-188
lines changed

include/swift/IDE/CancellableResult.h

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//===--- CancellableResult.h ------------------------------------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef SWIFT_IDE_CANCELLABLE_RESULT_H
14+
#define SWIFT_IDE_CANCELLABLE_RESULT_H
15+
16+
#include <string>
17+
18+
namespace swift {
19+
20+
namespace ide {
21+
22+
enum class CancellableResultKind { Success, Failure, Cancelled };
23+
24+
/// A result type that can carry one be in one of the following states:
25+
/// - Success and carry a value of \c ResultType
26+
/// - Failure and carry an error description
27+
/// - Cancelled in case the operation that produced the result was cancelled
28+
///
29+
/// Essentially this emulates an enum with associated values as follows
30+
/// \code
31+
/// enum CancellableResult<ResultType> {
32+
/// case success(ResultType)
33+
/// case failure(String)
34+
/// case cancelled
35+
/// }
36+
/// \endcode
37+
///
38+
/// The implementation is inspired by llvm::optional_detail::OptionalStorage
39+
template <typename ResultType>
40+
class CancellableResult {
41+
CancellableResultKind Kind;
42+
union {
43+
/// If \c Kind == Success, carries the result.
44+
ResultType Result;
45+
/// If \c Kind == Error, carries the error description.
46+
std::string Error;
47+
/// If \c Kind == Cancelled, this union is not initialized.
48+
char Empty;
49+
};
50+
51+
CancellableResult(ResultType Result)
52+
: Kind(CancellableResultKind::Success), Result(Result) {}
53+
54+
CancellableResult(std::string Error)
55+
: Kind(CancellableResultKind::Failure), Error(Error) {}
56+
57+
explicit CancellableResult()
58+
: Kind(CancellableResultKind::Cancelled), Empty() {}
59+
60+
public:
61+
CancellableResult(const CancellableResult &Other) : Kind(Other.Kind), Empty() {
62+
switch (Kind) {
63+
case CancellableResultKind::Success:
64+
::new ((void *)std::addressof(Result)) ResultType(Other.Result);
65+
break;
66+
case CancellableResultKind::Failure:
67+
::new ((void *)std::addressof(Error)) std::string(Other.Error);
68+
break;
69+
case CancellableResultKind::Cancelled:
70+
break;
71+
}
72+
}
73+
74+
CancellableResult(CancellableResult &&Other) : Kind(Other.Kind), Empty() {
75+
switch (Kind) {
76+
case CancellableResultKind::Success:
77+
::new ((void *)std::addressof(Result))
78+
ResultType(std::move(Other.Result));
79+
break;
80+
case CancellableResultKind::Failure:
81+
Error = std::move(Other.Error);
82+
break;
83+
case CancellableResultKind::Cancelled:
84+
break;
85+
}
86+
}
87+
88+
~CancellableResult() {
89+
using std::string;
90+
switch (Kind) {
91+
case CancellableResultKind::Success:
92+
Result.~ResultType();
93+
break;
94+
case CancellableResultKind::Failure:
95+
Error.~string();
96+
break;
97+
case CancellableResultKind::Cancelled:
98+
break;
99+
}
100+
}
101+
102+
/// Construct a \c CancellableResult that carries a successful result.
103+
static CancellableResult success(ResultType Result) {
104+
return std::move(CancellableResult(ResultType(Result)));
105+
}
106+
107+
/// Construct a \c CancellableResult that carries the error message of a
108+
/// failure.
109+
static CancellableResult failure(std::string Error) {
110+
return std::move(CancellableResult(Error));
111+
}
112+
113+
/// Construct a \c CancellableResult representing that the producing operation
114+
/// was cancelled.
115+
static CancellableResult cancelled() {
116+
return std::move(CancellableResult());
117+
}
118+
119+
/// Return the result kind this \c CancellableResult represents: success,
120+
/// failure or cancelled.
121+
CancellableResultKind getKind() { return Kind; }
122+
123+
/// Assuming that the result represents success, return the underlying result
124+
/// value.
125+
ResultType &getResult() {
126+
assert(getKind() == CancellableResultKind::Success);
127+
return Result;
128+
}
129+
130+
/// Assuming that the result represents success, retrieve members of the
131+
/// underlying result value.
132+
ResultType *operator->() { return &getResult(); }
133+
134+
/// Assuming that the result represents success, return the underlying result
135+
/// value.
136+
ResultType &operator*() { return getResult(); }
137+
138+
/// Assuming that the result represents a failure, return the error message.
139+
std::string getError() {
140+
assert(getKind() == CancellableResultKind::Failure);
141+
return Error;
142+
}
143+
};
144+
145+
} // namespace ide
146+
} // namespace swift
147+
148+
#endif // SWIFT_IDE_CANCELLABLE_RESULT_H

include/swift/IDE/CompletionInstance.h

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define SWIFT_IDE_COMPLETIONINSTANCE_H
1515

1616
#include "swift/Frontend/Frontend.h"
17+
#include "swift/IDE/CancellableResult.h"
1718
#include "llvm/ADT/Hashing.h"
1819
#include "llvm/ADT/IntrusiveRefCntPtr.h"
1920
#include "llvm/ADT/StringRef.h"
@@ -35,6 +36,14 @@ makeCodeCompletionMemoryBuffer(const llvm::MemoryBuffer *origBuf,
3536
unsigned &Offset,
3637
llvm::StringRef bufferIdentifier);
3738

39+
/// The result returned via the callback from the perform*Operation methods.
40+
struct CompletionInstanceResult {
41+
/// The compiler instance that is prepared for the second pass.
42+
CompilerInstance &CI;
43+
/// Whether an AST was reused.
44+
bool DidReuseAST;
45+
};
46+
3847
/// Manages \c CompilerInstance for completion like operations.
3948
class CompletionInstance {
4049
struct Options {
@@ -58,27 +67,31 @@ class CompletionInstance {
5867

5968
/// Calls \p Callback with cached \c CompilerInstance if it's usable for the
6069
/// specified completion request.
61-
/// Returns \c if the callback was called. Returns \c false if the compiler
62-
/// argument has changed, primary file is not the same, the \c Offset is not
63-
/// in function bodies, or the interface hash of the file has changed.
70+
/// Returns \c true if performing the cached operation was possible. Returns
71+
/// \c false if the compiler argument has changed, primary file is not the
72+
/// same, the \c Offset is not in function bodies, or the interface hash of
73+
/// the file has changed.
74+
/// \p Callback will be called if and only if this function returns \c true.
6475
bool performCachedOperationIfPossible(
6576
llvm::hash_code ArgsHash,
6677
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
6778
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
6879
DiagnosticConsumer *DiagC,
69-
llvm::function_ref<void(CompilerInstance &, bool)> Callback);
80+
llvm::function_ref<void(CancellableResult<CompletionInstanceResult>)>
81+
Callback);
7082

7183
/// Calls \p Callback with new \c CompilerInstance for the completion
7284
/// request. The \c CompilerInstace passed to the callback already performed
7385
/// the first pass.
7486
/// Returns \c false if it fails to setup the \c CompilerInstance.
75-
bool performNewOperation(
87+
void performNewOperation(
7688
llvm::Optional<llvm::hash_code> ArgsHash,
7789
swift::CompilerInvocation &Invocation,
7890
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
7991
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
80-
std::string &Error, DiagnosticConsumer *DiagC,
81-
llvm::function_ref<void(CompilerInstance &, bool)> Callback);
92+
DiagnosticConsumer *DiagC,
93+
llvm::function_ref<void(CancellableResult<CompletionInstanceResult>)>
94+
Callback);
8295

8396
public:
8497
CompletionInstance() : CachedCIShouldBeInvalidated(false) {}
@@ -94,18 +107,19 @@ class CompletionInstance {
94107
/// second pass. \p Callback is resposible to perform the second pass on it.
95108
/// The \c CompilerInstance may be reused from the previous completions,
96109
/// and may be cached for the next completion.
97-
/// Return \c true if \p is successfully called, \c it fails. In failure
98-
/// cases \p Error is populated with an error message.
110+
/// In case of failure or cancellation, the callback receives the
111+
/// corresponding failed or cancelled result.
99112
///
100113
/// NOTE: \p Args is only used for checking the equaity of the invocation.
101114
/// Since this function assumes that it is already normalized, exact the same
102115
/// arguments including their order is considered as the same invocation.
103-
bool performOperation(
116+
void performOperation(
104117
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
105118
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
106119
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
107-
std::string &Error, DiagnosticConsumer *DiagC,
108-
llvm::function_ref<void(CompilerInstance &, bool)> Callback);
120+
DiagnosticConsumer *DiagC,
121+
llvm::function_ref<void(CancellableResult<CompletionInstanceResult>)>
122+
Callback);
109123
};
110124

111125
} // namespace ide

include/swift/IDE/ConformingMethodList.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ class ConformingMethodListConsumer {
4242
public:
4343
virtual ~ConformingMethodListConsumer() {}
4444
virtual void handleResult(const ConformingMethodListResult &result) = 0;
45-
virtual void setReusingASTContext(bool flag) = 0;
4645
};
4746

4847
/// Printing consumer
@@ -54,7 +53,6 @@ class PrintingConformingMethodListConsumer
5453
PrintingConformingMethodListConsumer(llvm::raw_ostream &OS) : OS(OS) {}
5554

5655
void handleResult(const ConformingMethodListResult &result) override;
57-
void setReusingASTContext(bool flag) override {}
5856
};
5957

6058
/// Create a factory for code completion callbacks.

include/swift/IDE/TypeContextInfo.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ class TypeContextInfoConsumer {
3838
public:
3939
virtual ~TypeContextInfoConsumer() {}
4040
virtual void handleResults(ArrayRef<TypeContextInfoItem>) = 0;
41-
virtual void setReusingASTContext(bool flag) = 0;
4241
};
4342

4443
/// Printing consumer
@@ -49,7 +48,6 @@ class PrintingTypeContextInfoConsumer : public TypeContextInfoConsumer {
4948
PrintingTypeContextInfoConsumer(llvm::raw_ostream &OS) : OS(OS) {}
5049

5150
void handleResults(ArrayRef<TypeContextInfoItem>) override;
52-
void setReusingASTContext(bool flag) override {}
5351
};
5452

5553
/// Create a factory for code completion callbacks.

lib/IDE/CompletionInstance.cpp

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,12 @@ static bool areAnyDependentFilesInvalidated(
281281
} // namespace
282282

283283
bool CompletionInstance::performCachedOperationIfPossible(
284-
llvm::hash_code ArgsHash,
284+
llvm::hash_code ArgsHash,
285285
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
286286
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
287287
DiagnosticConsumer *DiagC,
288-
llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
288+
llvm::function_ref<void(CancellableResult<CompletionInstanceResult>)>
289+
Callback) {
289290
llvm::PrettyStackTraceString trace(
290291
"While performing cached completion if possible");
291292

@@ -504,7 +505,8 @@ bool CompletionInstance::performCachedOperationIfPossible(
504505
if (DiagC)
505506
CI.addDiagnosticConsumer(DiagC);
506507

507-
Callback(CI, /*reusingASTContext=*/true);
508+
Callback(CancellableResult<CompletionInstanceResult>::success(
509+
{CI, /*reusingASTContext=*/true}));
508510

509511
if (DiagC)
510512
CI.removeDiagnosticConsumer(DiagC);
@@ -515,12 +517,13 @@ bool CompletionInstance::performCachedOperationIfPossible(
515517
return true;
516518
}
517519

518-
bool CompletionInstance::performNewOperation(
520+
void CompletionInstance::performNewOperation(
519521
Optional<llvm::hash_code> ArgsHash, swift::CompilerInvocation &Invocation,
520522
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
521523
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
522-
std::string &Error, DiagnosticConsumer *DiagC,
523-
llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
524+
DiagnosticConsumer *DiagC,
525+
llvm::function_ref<void(CancellableResult<CompletionInstanceResult>)>
526+
Callback) {
524527
llvm::PrettyStackTraceString trace("While performing new completion");
525528

526529
auto isCachedCompletionRequested = ArgsHash.hasValue();
@@ -548,32 +551,35 @@ bool CompletionInstance::performNewOperation(
548551
Invocation.setCodeCompletionPoint(completionBuffer, Offset);
549552

550553
if (CI.setup(Invocation)) {
551-
Error = "failed to setup compiler instance";
552-
return false;
554+
Callback(CancellableResult<CompletionInstanceResult>::failure(
555+
"failed to setup compiler instance"));
556+
return;
553557
}
554558
registerIDERequestFunctions(CI.getASTContext().evaluator);
555559

556560
// If we're expecting a standard library, but there either isn't one, or it
557561
// failed to load, let's bail early and hand back an empty completion
558562
// result to avoid any downstream crashes.
559-
if (CI.loadStdlibIfNeeded())
560-
return true;
563+
if (CI.loadStdlibIfNeeded()) {
564+
/// FIXME: Callback is not being called on this path.
565+
return;
566+
}
561567

562568
CI.performParseAndResolveImportsOnly();
563569

564570
// If we didn't find a code completion token, bail.
565571
auto *state = CI.getCodeCompletionFile()->getDelayedParserState();
566572
if (!state->hasCodeCompletionDelayedDeclState())
567-
return true;
573+
/// FIXME: Callback is not being called on this path.
574+
return;
568575

569-
Callback(CI, /*reusingASTContext=*/false);
576+
Callback(CancellableResult<CompletionInstanceResult>::success(
577+
{CI, /*ReuisingASTContext=*/false}));
570578
}
571579

572580
// Cache the compiler instance if fast completion is enabled.
573581
if (isCachedCompletionRequested)
574582
cacheCompilerInstance(std::move(TheInstance), *ArgsHash);
575-
576-
return true;
577583
}
578584

579585
void CompletionInstance::cacheCompilerInstance(
@@ -608,12 +614,13 @@ void CompletionInstance::setOptions(CompletionInstance::Options NewOpts) {
608614
Opts = NewOpts;
609615
}
610616

611-
bool swift::ide::CompletionInstance::performOperation(
617+
void swift::ide::CompletionInstance::performOperation(
612618
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
613619
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
614620
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
615-
std::string &Error, DiagnosticConsumer *DiagC,
616-
llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
621+
DiagnosticConsumer *DiagC,
622+
llvm::function_ref<void(CancellableResult<CompletionInstanceResult>)>
623+
Callback) {
617624

618625
// Always disable source location resolutions from .swiftsourceinfo file
619626
// because they're somewhat heavy operations and aren't needed for completion.
@@ -637,14 +644,11 @@ bool swift::ide::CompletionInstance::performOperation(
637644

638645
if (performCachedOperationIfPossible(ArgsHash, FileSystem, completionBuffer,
639646
Offset, DiagC, Callback)) {
640-
return true;
647+
// We were able to reuse a cached AST. Callback has already been invoked
648+
// and we don't need to build a new AST. We are done.
649+
return;
641650
}
642651

643-
if(performNewOperation(ArgsHash, Invocation, FileSystem, completionBuffer,
644-
Offset, Error, DiagC, Callback)) {
645-
return true;
646-
}
647-
648-
assert(!Error.empty());
649-
return false;
652+
performNewOperation(ArgsHash, Invocation, FileSystem, completionBuffer,
653+
Offset, DiagC, Callback);
650654
}

0 commit comments

Comments
 (0)