mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[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.
This commit is contained in:
@@ -78,7 +78,7 @@ public:
|
|||||||
ResultType(std::move(Other.Result));
|
ResultType(std::move(Other.Result));
|
||||||
break;
|
break;
|
||||||
case CancellableResultKind::Failure:
|
case CancellableResultKind::Failure:
|
||||||
Error = std::move(Other.Error);
|
::new ((void *)std::addressof(Error)) std::string(std::move(Other.Error));
|
||||||
break;
|
break;
|
||||||
case CancellableResultKind::Cancelled:
|
case CancellableResultKind::Cancelled:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ struct CompletionInstanceResult {
|
|||||||
CompilerInstance &CI;
|
CompilerInstance &CI;
|
||||||
/// Whether an AST was reused.
|
/// Whether an AST was reused.
|
||||||
bool DidReuseAST;
|
bool DidReuseAST;
|
||||||
|
/// Whether the CompletionInstance found a code completion token in the source
|
||||||
|
/// file. If this is \c false, the user will most likely want to return empty
|
||||||
|
/// results.
|
||||||
|
bool DidFindCodeCompletionToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Manages \c CompilerInstance for completion like operations.
|
/// Manages \c CompilerInstance for completion like operations.
|
||||||
|
|||||||
@@ -506,7 +506,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
|
|||||||
CI.addDiagnosticConsumer(DiagC);
|
CI.addDiagnosticConsumer(DiagC);
|
||||||
|
|
||||||
Callback(CancellableResult<CompletionInstanceResult>::success(
|
Callback(CancellableResult<CompletionInstanceResult>::success(
|
||||||
{CI, /*reusingASTContext=*/true}));
|
{CI, /*reusingASTContext=*/true, /*DidFindCodeCompletionToken=*/true}));
|
||||||
|
|
||||||
if (DiagC)
|
if (DiagC)
|
||||||
CI.removeDiagnosticConsumer(DiagC);
|
CI.removeDiagnosticConsumer(DiagC);
|
||||||
@@ -535,6 +535,7 @@ void CompletionInstance::performNewOperation(
|
|||||||
Invocation.getFrontendOptions().IntermoduleDependencyTracking =
|
Invocation.getFrontendOptions().IntermoduleDependencyTracking =
|
||||||
IntermoduleDepTrackingMode::ExcludeSystem;
|
IntermoduleDepTrackingMode::ExcludeSystem;
|
||||||
|
|
||||||
|
bool DidFindCodeCompletionToken = false;
|
||||||
{
|
{
|
||||||
auto &CI = *TheInstance;
|
auto &CI = *TheInstance;
|
||||||
if (DiagC)
|
if (DiagC)
|
||||||
@@ -561,24 +562,26 @@ void CompletionInstance::performNewOperation(
|
|||||||
// failed to load, let's bail early and hand back an empty completion
|
// failed to load, let's bail early and hand back an empty completion
|
||||||
// result to avoid any downstream crashes.
|
// result to avoid any downstream crashes.
|
||||||
if (CI.loadStdlibIfNeeded()) {
|
if (CI.loadStdlibIfNeeded()) {
|
||||||
/// FIXME: Callback is not being called on this path.
|
Callback(CancellableResult<CompletionInstanceResult>::failure(
|
||||||
|
"failed to load the standard library"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CI.performParseAndResolveImportsOnly();
|
CI.performParseAndResolveImportsOnly();
|
||||||
|
|
||||||
// If we didn't find a code completion token, bail.
|
DidFindCodeCompletionToken = CI.getCodeCompletionFile()
|
||||||
auto *state = CI.getCodeCompletionFile()->getDelayedParserState();
|
->getDelayedParserState()
|
||||||
if (!state->hasCodeCompletionDelayedDeclState())
|
->hasCodeCompletionDelayedDeclState();
|
||||||
/// FIXME: Callback is not being called on this path.
|
|
||||||
return;
|
|
||||||
|
|
||||||
Callback(CancellableResult<CompletionInstanceResult>::success(
|
Callback(CancellableResult<CompletionInstanceResult>::success(
|
||||||
{CI, /*ReuisingASTContext=*/false}));
|
{CI, /*ReuisingASTContext=*/false, DidFindCodeCompletionToken}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the compiler instance if fast completion is enabled.
|
// Cache the compiler instance if fast completion is enabled.
|
||||||
if (isCachedCompletionRequested)
|
// If we didn't find a code compleiton token, we can't cache the instance
|
||||||
|
// because performCachedOperationIfPossible wouldn't have an old code
|
||||||
|
// completion state to compare the new one to.
|
||||||
|
if (isCachedCompletionRequested && DidFindCodeCompletionToken)
|
||||||
cacheCompilerInstance(std::move(TheInstance), *ArgsHash);
|
cacheCompilerInstance(std::move(TheInstance), *ArgsHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ class Str {
|
|||||||
// RUN: %empty-directory(%t/rsrc)
|
// RUN: %empty-directory(%t/rsrc)
|
||||||
// RUN: %empty-directory(%t/sdk)
|
// RUN: %empty-directory(%t/sdk)
|
||||||
|
|
||||||
// RUN: %sourcekitd-test \
|
// RUN: not %sourcekitd-test \
|
||||||
// RUN: -req=global-config -req-opts=completion_max_astcontext_reuse_count=0 \
|
// RUN: -req=global-config -req-opts=completion_max_astcontext_reuse_count=0 \
|
||||||
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk | %FileCheck %s
|
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk 2>&1 | %FileCheck %s
|
||||||
// RUN: %sourcekitd-test \
|
// RUN: not %sourcekitd-test \
|
||||||
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk == \
|
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk == \
|
||||||
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk | %FileCheck %s
|
// RUN: -req=complete -pos=4:1 %s -- %s -resource-dir %t/rsrc -sdk %t/sdk 2>&1 | %FileCheck %s
|
||||||
|
|
||||||
// CHECK: key.results: [
|
// CHECK: error response (Request Failed): failed to load the standard library
|
||||||
// CHECK-NOT: key.description:
|
|
||||||
|
|||||||
@@ -190,8 +190,15 @@ static void swiftCodeCompleteImpl(
|
|||||||
ide::makeCodeCompletionCallbacksFactory(CompletionContext,
|
ide::makeCodeCompletionCallbacksFactory(CompletionContext,
|
||||||
Consumer));
|
Consumer));
|
||||||
|
|
||||||
auto *SF = CI.getCodeCompletionFile();
|
if (!Result->DidFindCodeCompletionToken) {
|
||||||
performCodeCompletionSecondPass(*SF, *callbacksFactory);
|
Callback(ResultType::success(
|
||||||
|
{/*HasResults=*/false, &CI.getASTContext(), &CI.getInvocation(),
|
||||||
|
&CompletionContext, /*RequestedModules=*/{}, /*DC=*/nullptr}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
performCodeCompletionSecondPass(*CI.getCodeCompletionFile(),
|
||||||
|
*callbacksFactory);
|
||||||
if (!Consumer.HandleResultWasCalled) {
|
if (!Consumer.HandleResultWasCalled) {
|
||||||
// If we didn't receive a handleResult call from the second pass,
|
// If we didn't receive a handleResult call from the second pass,
|
||||||
// we didn't receive any results. To make sure Callback gets called
|
// we didn't receive any results. To make sure Callback gets called
|
||||||
|
|||||||
@@ -81,6 +81,12 @@ static void swiftConformingMethodListImpl(
|
|||||||
ide::makeConformingMethodListCallbacksFactory(ExpectedTypeNames,
|
ide::makeConformingMethodListCallbacksFactory(ExpectedTypeNames,
|
||||||
Consumer));
|
Consumer));
|
||||||
|
|
||||||
|
if (!Result->DidFindCodeCompletionToken) {
|
||||||
|
Callback(ResultType::success(
|
||||||
|
{/*Results=*/nullptr, Result->DidReuseAST}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(),
|
performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(),
|
||||||
*callbacksFactory);
|
*callbacksFactory);
|
||||||
if (!Consumer.HandleResultWasCalled) {
|
if (!Consumer.HandleResultWasCalled) {
|
||||||
|
|||||||
@@ -75,6 +75,11 @@ static void swiftTypeContextInfoImpl(
|
|||||||
std::unique_ptr<CodeCompletionCallbacksFactory> callbacksFactory(
|
std::unique_ptr<CodeCompletionCallbacksFactory> callbacksFactory(
|
||||||
ide::makeTypeContextInfoCallbacksFactory(Consumer));
|
ide::makeTypeContextInfoCallbacksFactory(Consumer));
|
||||||
|
|
||||||
|
if (!Result->DidFindCodeCompletionToken) {
|
||||||
|
Callback(
|
||||||
|
ResultType::success({/*Results=*/{}, Result->DidReuseAST}));
|
||||||
|
}
|
||||||
|
|
||||||
performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(),
|
performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(),
|
||||||
*callbacksFactory);
|
*callbacksFactory);
|
||||||
if (!Consumer.HandleResultsCalled) {
|
if (!Consumer.HandleResultsCalled) {
|
||||||
|
|||||||
@@ -866,28 +866,48 @@ static bool doCodeCompletionImpl(
|
|||||||
|
|
||||||
PrintingDiagnosticConsumer PrintDiags;
|
PrintingDiagnosticConsumer PrintDiags;
|
||||||
CompletionInstance CompletionInst;
|
CompletionInstance CompletionInst;
|
||||||
// FIXME: Should we count it as an error if we didn't receive a callback?
|
std::string CompletionError;
|
||||||
bool HadError = false;
|
bool CallbackCalled = false;
|
||||||
CompletionInst.performOperation(
|
CompletionInst.performOperation(
|
||||||
Invocation, /*Args=*/{}, llvm::vfs::getRealFileSystem(), CleanFile.get(),
|
Invocation, /*Args=*/{}, llvm::vfs::getRealFileSystem(), CleanFile.get(),
|
||||||
Offset, CodeCompletionDiagnostics ? &PrintDiags : nullptr,
|
Offset, CodeCompletionDiagnostics ? &PrintDiags : nullptr,
|
||||||
[&](CancellableResult<CompletionInstanceResult> Result) {
|
[&](CancellableResult<CompletionInstanceResult> Result) {
|
||||||
|
CallbackCalled = true;
|
||||||
switch (Result.getKind()) {
|
switch (Result.getKind()) {
|
||||||
case CancellableResultKind::Success: {
|
case CancellableResultKind::Success: {
|
||||||
HadError = false;
|
|
||||||
assert(!Result->DidReuseAST &&
|
assert(!Result->DidReuseAST &&
|
||||||
"reusing AST context without enabling it");
|
"reusing AST context without enabling it");
|
||||||
|
|
||||||
|
if (!Result->DidFindCodeCompletionToken) {
|
||||||
|
// Return empty results by not performing the second pass and never
|
||||||
|
// calling the consumer of the callback factory.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(),
|
performCodeCompletionSecondPass(*Result->CI.getCodeCompletionFile(),
|
||||||
*callbacksFactory);
|
*callbacksFactory);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CancellableResultKind::Failure:
|
case CancellableResultKind::Failure:
|
||||||
|
CompletionError = "error: " + Result.getError();
|
||||||
|
break;
|
||||||
case CancellableResultKind::Cancelled:
|
case CancellableResultKind::Cancelled:
|
||||||
HadError = true;
|
CompletionError = "request cancelled";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return HadError;
|
assert(CallbackCalled &&
|
||||||
|
"We should always receive a callback call for code completion");
|
||||||
|
if (CompletionError == "error: did not find code completion token") {
|
||||||
|
// Do not consider failure to find the code completion token as a critical
|
||||||
|
// failure that returns a non-zero exit code. Instead, allow the caller to
|
||||||
|
// match the error message.
|
||||||
|
llvm::outs() << CompletionError << '\n';
|
||||||
|
} else if (!CompletionError.empty()) {
|
||||||
|
llvm::errs() << CompletionError << '\n';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int doTypeContextInfo(const CompilerInvocation &InitInvok,
|
static int doTypeContextInfo(const CompilerInvocation &InitInvok,
|
||||||
@@ -1279,16 +1299,20 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok,
|
|||||||
PrintingDiagnosticConsumer PrintDiags;
|
PrintingDiagnosticConsumer PrintDiags;
|
||||||
auto completionStart = std::chrono::high_resolution_clock::now();
|
auto completionStart = std::chrono::high_resolution_clock::now();
|
||||||
bool wasASTContextReused = false;
|
bool wasASTContextReused = false;
|
||||||
// FIXME: Should we count it as an error if we didn't receive a callback?
|
std::string completionError = "";
|
||||||
bool completionDidFail = false;
|
bool CallbackCalled = false;
|
||||||
CompletionInst.performOperation(
|
CompletionInst.performOperation(
|
||||||
Invocation, /*Args=*/{}, FileSystem, completionBuffer.get(), Offset,
|
Invocation, /*Args=*/{}, FileSystem, completionBuffer.get(), Offset,
|
||||||
CodeCompletionDiagnostics ? &PrintDiags : nullptr,
|
CodeCompletionDiagnostics ? &PrintDiags : nullptr,
|
||||||
[&](CancellableResult<CompletionInstanceResult> Result) {
|
[&](CancellableResult<CompletionInstanceResult> Result) {
|
||||||
|
CallbackCalled = true;
|
||||||
switch (Result.getKind()) {
|
switch (Result.getKind()) {
|
||||||
case CancellableResultKind::Success: {
|
case CancellableResultKind::Success: {
|
||||||
completionDidFail = false;
|
if (!Result->DidFindCodeCompletionToken) {
|
||||||
|
// Return empty results by not performing the second pass and
|
||||||
|
// never calling the consumer of the callback factory.
|
||||||
|
return;
|
||||||
|
}
|
||||||
wasASTContextReused = Result->DidReuseAST;
|
wasASTContextReused = Result->DidReuseAST;
|
||||||
|
|
||||||
// Create a CodeCompletionConsumer.
|
// Create a CodeCompletionConsumer.
|
||||||
@@ -1314,15 +1338,15 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CancellableResultKind::Failure:
|
case CancellableResultKind::Failure:
|
||||||
completionDidFail = true;
|
completionError = "error: " + Result.getError();
|
||||||
llvm::errs() << "error: " << Result.getError() << "\n";
|
|
||||||
break;
|
break;
|
||||||
case CancellableResultKind::Cancelled:
|
case CancellableResultKind::Cancelled:
|
||||||
completionDidFail = true;
|
completionError = "request cancelled";
|
||||||
llvm::errs() << "request cancelled\n";
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
assert(CallbackCalled &&
|
||||||
|
"We should always receive a callback call for code completion");
|
||||||
auto completionEnd = std::chrono::high_resolution_clock::now();
|
auto completionEnd = std::chrono::high_resolution_clock::now();
|
||||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
completionEnd - completionStart);
|
completionEnd - completionStart);
|
||||||
@@ -1350,13 +1374,18 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok,
|
|||||||
}
|
}
|
||||||
|
|
||||||
llvm::raw_fd_ostream fileOut(resultFD, /*shouldClose=*/true);
|
llvm::raw_fd_ostream fileOut(resultFD, /*shouldClose=*/true);
|
||||||
fileOut << ResultStr;
|
if (completionError == "error: did not find code completion token") {
|
||||||
fileOut.close();
|
// Do not consider failure to find the code completion token as a critical
|
||||||
|
// failure that returns a non-zero exit code. Instead, allow the caller to
|
||||||
if (completionDidFail) {
|
// match the error message.
|
||||||
// error message was already emitted above.
|
fileOut << completionError;
|
||||||
|
} else if (!completionError.empty()) {
|
||||||
|
llvm::errs() << completionError << '\n';
|
||||||
return 1;
|
return 1;
|
||||||
|
} else {
|
||||||
|
fileOut << ResultStr;
|
||||||
}
|
}
|
||||||
|
fileOut.close();
|
||||||
|
|
||||||
if (options::SkipFileCheck)
|
if (options::SkipFileCheck)
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user