Skip to content

Commit b6e03e3

Browse files
committed
[CodeCompletion] Make sure callback is always called from performOperation
We had some situations left that neither returned an error, nor called the callback with results in `performOperation`. Return an error in these and adjust the tests to correctly match the error.
1 parent 2fcb24e commit b6e03e3

File tree

8 files changed

+89
-36
lines changed

8 files changed

+89
-36
lines changed

include/swift/IDE/CancellableResult.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class CancellableResult {
7878
ResultType(std::move(Other.Result));
7979
break;
8080
case CancellableResultKind::Failure:
81-
Error = std::move(Other.Error);
81+
::new ((void *)std::addressof(Error)) std::string(std::move(Other.Error));
8282
break;
8383
case CancellableResultKind::Cancelled:
8484
break;

include/swift/IDE/CompletionInstance.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ struct CompletionInstanceResult {
4242
CompilerInstance &CI;
4343
/// Whether an AST was reused.
4444
bool DidReuseAST;
45+
/// Whether the CompletionInstance found a code completion token in the source
46+
/// file. If this is \c false, the user will most likely want to return empty
47+
/// results.
48+
bool DidFindCodeCompletionToken;
4549
};
4650

4751
/// Manages \c CompilerInstance for completion like operations.

lib/IDE/CompletionInstance.cpp

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
506506
CI.addDiagnosticConsumer(DiagC);
507507

508508
Callback(CancellableResult<CompletionInstanceResult>::success(
509-
{CI, /*reusingASTContext=*/true}));
509+
{CI, /*reusingASTContext=*/true, /*DidFindCodeCompletionToken=*/true}));
510510

511511
if (DiagC)
512512
CI.removeDiagnosticConsumer(DiagC);
@@ -535,6 +535,7 @@ void CompletionInstance::performNewOperation(
535535
Invocation.getFrontendOptions().IntermoduleDependencyTracking =
536536
IntermoduleDepTrackingMode::ExcludeSystem;
537537

538+
bool DidFindCodeCompletionToken = false;
538539
{
539540
auto &CI = *TheInstance;
540541
if (DiagC)
@@ -561,24 +562,26 @@ void CompletionInstance::performNewOperation(
561562
// failed to load, let's bail early and hand back an empty completion
562563
// result to avoid any downstream crashes.
563564
if (CI.loadStdlibIfNeeded()) {
564-
/// FIXME: Callback is not being called on this path.
565+
Callback(CancellableResult<CompletionInstanceResult>::failure(
566+
"failed to load the standard library"));
565567
return;
566568
}
567569

568570
CI.performParseAndResolveImportsOnly();
569571

570-
// If we didn't find a code completion token, bail.
571-
auto *state = CI.getCodeCompletionFile()->getDelayedParserState();
572-
if (!state->hasCodeCompletionDelayedDeclState())
573-
/// FIXME: Callback is not being called on this path.
574-
return;
572+
DidFindCodeCompletionToken = CI.getCodeCompletionFile()
573+
->getDelayedParserState()
574+
->hasCodeCompletionDelayedDeclState();
575575

576576
Callback(CancellableResult<CompletionInstanceResult>::success(
577-
{CI, /*ReuisingASTContext=*/false}));
577+
{CI, /*ReuisingASTContext=*/false, DidFindCodeCompletionToken}));
578578
}
579579

580580
// Cache the compiler instance if fast completion is enabled.
581-
if (isCachedCompletionRequested)
581+
// If we didn't find a code compleiton token, we can't cache the instance
582+
// because performCachedOperationIfPossible wouldn't have an old code
583+
// completion state to compare the new one to.
584+
if (isCachedCompletionRequested && DidFindCodeCompletionToken)
582585
cacheCompilerInstance(std::move(TheInstance), *ArgsHash);
583586
}
584587

test/SourceKit/CodeComplete/complete_without_stdlib.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ class Str {
99
// RUN: %empty-directory(%t/rsrc)
1010
// RUN: %empty-directory(%t/sdk)
1111

12-
// RUN: %sourcekitd-test \
12+
// RUN: not %sourcekitd-test \
1313
// RUN: -req=global-config -req-opts=completion_max_astcontext_reuse_count=0 \
14-
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk | %FileCheck %s
15-
// RUN: %sourcekitd-test \
14+
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk 2>&1 | %FileCheck %s
15+
// RUN: not %sourcekitd-test \
1616
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk == \
17-
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk | %FileCheck %s
17+
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk 2>&1 | %FileCheck %s
1818

19-
// CHECK: key.results: [
20-
// CHECK-NOT: key.description:
19+
// CHECK: error response (Request Failed): failed to load the standard library

tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,15 @@ static void swiftCodeCompleteImpl(
190190
ide::makeCodeCompletionCallbacksFactory(CompletionContext,
191191
Consumer));
192192

193-
auto *SF = CI.getCodeCompletionFile();
194-
performCodeCompletionSecondPass(*SF, *callbacksFactory);
193+
if (!Result->DidFindCodeCompletionToken) {
194+
Callback(ResultType::success(
195+
{/*HasResults=*/false, &CI.getASTContext(), &CI.getInvocation(),
196+
&CompletionContext, /*RequestedModules=*/{}, /*DC=*/nullptr}));
197+
return;
198+
}
199+
200+
performCodeCompletionSecondPass(*CI.getCodeCompletionFile(),
201+
*callbacksFactory);
195202
if (!Consumer.HandleResultWasCalled) {
196203
// If we didn't receive a handleResult call from the second pass,
197204
// we didn't receive any results. To make sure Callback gets called

tools/SourceKit/lib/SwiftLang/SwiftConformingMethodList.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ static void swiftConformingMethodListImpl(
8181
ide::makeConformingMethodListCallbacksFactory(ExpectedTypeNames,
8282
Consumer));
8383

84+
if (!Result->DidFindCodeCompletionToken) {
85+
Callback(ResultType::success(
86+
{/*Results=*/nullptr, Result->DidReuseAST}));
87+
return;
88+
}
89+
8490
performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(),
8591
*callbacksFactory);
8692
if (!Consumer.HandleResultWasCalled) {

tools/SourceKit/lib/SwiftLang/SwiftTypeContextInfo.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ static void swiftTypeContextInfoImpl(
7575
std::unique_ptr<CodeCompletionCallbacksFactory> callbacksFactory(
7676
ide::makeTypeContextInfoCallbacksFactory(Consumer));
7777

78+
if (!Result->DidFindCodeCompletionToken) {
79+
Callback(
80+
ResultType::success({/*Results=*/{}, Result->DidReuseAST}));
81+
}
82+
7883
performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(),
7984
*callbacksFactory);
8085
if (!Consumer.HandleResultsCalled) {

tools/swift-ide-test/swift-ide-test.cpp

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -866,28 +866,48 @@ static bool doCodeCompletionImpl(
866866

867867
PrintingDiagnosticConsumer PrintDiags;
868868
CompletionInstance CompletionInst;
869-
// FIXME: Should we count it as an error if we didn't receive a callback?
870-
bool HadError = false;
869+
std::string CompletionError;
870+
bool CallbackCalled = false;
871871
CompletionInst.performOperation(
872872
Invocation, /*Args=*/{}, llvm::vfs::getRealFileSystem(), CleanFile.get(),
873873
Offset, CodeCompletionDiagnostics ? &PrintDiags : nullptr,
874874
[&](CancellableResult<CompletionInstanceResult> Result) {
875+
CallbackCalled = true;
875876
switch (Result.getKind()) {
876877
case CancellableResultKind::Success: {
877-
HadError = false;
878878
assert(!Result->DidReuseAST &&
879879
"reusing AST context without enabling it");
880+
881+
if (!Result->DidFindCodeCompletionToken) {
882+
// Return empty results by not performing the second pass and never
883+
// calling the consumer of the callback factory.
884+
return;
885+
}
886+
880887
performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(),
881888
*callbacksFactory);
882889
break;
883890
}
884891
case CancellableResultKind::Failure:
892+
CompletionError = "error: " + Result.getError();
893+
break;
885894
case CancellableResultKind::Cancelled:
886-
HadError = true;
895+
CompletionError = "request cancelled";
887896
break;
888897
}
889898
});
890-
return HadError;
899+
assert(CallbackCalled &&
900+
"We should always receive a callback call for code completion");
901+
if (CompletionError == "error: did not find code completion token") {
902+
// Do not consider failure to find the code completion token as a critical
903+
// failure that returns a non-zero exit code. Instead, allow the caller to
904+
// match the error message.
905+
llvm::outs() << CompletionError << '\n';
906+
} else if (!CompletionError.empty()) {
907+
llvm::errs() << CompletionError << '\n';
908+
return true;
909+
}
910+
return false;
891911
}
892912

893913
static int doTypeContextInfo(const CompilerInvocation &InitInvok,
@@ -1279,16 +1299,20 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok,
12791299
PrintingDiagnosticConsumer PrintDiags;
12801300
auto completionStart = std::chrono::high_resolution_clock::now();
12811301
bool wasASTContextReused = false;
1282-
// FIXME: Should we count it as an error if we didn't receive a callback?
1283-
bool completionDidFail = false;
1302+
std::string completionError = "";
1303+
bool CallbackCalled = false;
12841304
CompletionInst.performOperation(
12851305
Invocation, /*Args=*/{}, FileSystem, completionBuffer.get(), Offset,
12861306
CodeCompletionDiagnostics ? &PrintDiags : nullptr,
12871307
[&](CancellableResult<CompletionInstanceResult> Result) {
1308+
CallbackCalled = true;
12881309
switch (Result.getKind()) {
12891310
case CancellableResultKind::Success: {
1290-
completionDidFail = false;
1291-
1311+
if (!Result->DidFindCodeCompletionToken) {
1312+
// Return empty results by not performing the second pass and
1313+
// never calling the consumer of the callback factory.
1314+
return;
1315+
}
12921316
wasASTContextReused = Result->DidReuseAST;
12931317

12941318
// Create a CodeCompletionConsumer.
@@ -1314,15 +1338,15 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok,
13141338
break;
13151339
}
13161340
case CancellableResultKind::Failure:
1317-
completionDidFail = true;
1318-
llvm::errs() << "error: " << Result.getError() << "\n";
1341+
completionError = "error: " + Result.getError();
13191342
break;
13201343
case CancellableResultKind::Cancelled:
1321-
completionDidFail = true;
1322-
llvm::errs() << "request cancelled\n";
1344+
completionError = "request cancelled";
13231345
break;
13241346
}
13251347
});
1348+
assert(CallbackCalled &&
1349+
"We should always receive a callback call for code completion");
13261350
auto completionEnd = std::chrono::high_resolution_clock::now();
13271351
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
13281352
completionEnd - completionStart);
@@ -1350,13 +1374,18 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok,
13501374
}
13511375

13521376
llvm::raw_fd_ostream fileOut(resultFD, /*shouldClose=*/true);
1353-
fileOut << ResultStr;
1354-
fileOut.close();
1355-
1356-
if (completionDidFail) {
1357-
// error message was already emitted above.
1377+
if (completionError == "error: did not find code completion token") {
1378+
// Do not consider failure to find the code completion token as a critical
1379+
// failure that returns a non-zero exit code. Instead, allow the caller to
1380+
// match the error message.
1381+
fileOut << completionError;
1382+
} else if (!completionError.empty()) {
1383+
llvm::errs() << completionError << '\n';
13581384
return 1;
1385+
} else {
1386+
fileOut << ResultStr;
13591387
}
1388+
fileOut.close();
13601389

13611390
if (options::SkipFileCheck)
13621391
continue;

0 commit comments

Comments
 (0)