[CodeCompletion] Give up fast-completion if dependent files are modified

Check if dependencies are modified since the last checking.
Dependencies:

 - Other source files in the current module
 - Dependent files collected by the dependency tracker

When:

 - If the last dependency check was over N (defaults to 5) seconds ago

Invalidate if:

 - The dependency file is missing
 - The modification time of the dependecy is greater than the last check
 - If the modification time is zero, compare the content using the file
   system from the previous completion and the current completion

rdar://problem/62336432
This commit is contained in:
Rintaro Ishizaki
2020-04-28 18:34:44 -07:00
parent d9b1d8f694
commit 05a87e86c4
29 changed files with 527 additions and 18 deletions

View File

@@ -18,6 +18,7 @@
#include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringRef.h"
#include "llvm/Support/Chrono.h"
#include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/VirtualFileSystem.h"
@@ -38,13 +39,20 @@ makeCodeCompletionMemoryBuffer(const llvm::MemoryBuffer *origBuf,
/// Manages \c CompilerInstance for completion like operations. /// Manages \c CompilerInstance for completion like operations.
class CompletionInstance { class CompletionInstance {
unsigned MaxASTReuseCount = 100; unsigned MaxASTReuseCount = 100;
unsigned DependencyCheckIntervalSecond = 5;
std::mutex mtx; std::mutex mtx;
std::unique_ptr<CompilerInstance> CachedCI; std::unique_ptr<CompilerInstance> CachedCI;
llvm::hash_code CachedArgHash; llvm::hash_code CachedArgHash;
llvm::sys::TimePoint<> DependencyCheckedTimestamp;
unsigned CachedReuseCount = 0; unsigned CachedReuseCount = 0;
void cacheCompilerInstance(std::unique_ptr<CompilerInstance> CI,
llvm::hash_code ArgsHash);
bool shouldCheckDependencies() const;
/// Calls \p Callback with cached \c CompilerInstance if it's usable for the /// Calls \p Callback with cached \c CompilerInstance if it's usable for the
/// specified completion request. /// specified completion request.
/// Returns \c if the callback was called. Returns \c false if the compiler /// Returns \c if the callback was called. Returns \c false if the compiler
@@ -52,6 +60,7 @@ class CompletionInstance {
/// in function bodies, or the interface hash of the file has changed. /// in function bodies, or the interface hash of the file has changed.
bool performCachedOperationIfPossible( bool performCachedOperationIfPossible(
const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash, const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
llvm::MemoryBuffer *completionBuffer, unsigned int Offset, llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
DiagnosticConsumer *DiagC, DiagnosticConsumer *DiagC,
llvm::function_ref<void(CompilerInstance &, bool)> Callback); llvm::function_ref<void(CompilerInstance &, bool)> Callback);
@@ -69,6 +78,8 @@ class CompletionInstance {
llvm::function_ref<void(CompilerInstance &, bool)> Callback); llvm::function_ref<void(CompilerInstance &, bool)> Callback);
public: public:
void setDependencyCheckIntervalSecond(unsigned Value);
/// Calls \p Callback with a \c CompilerInstance which is prepared for the /// Calls \p Callback with a \c CompilerInstance which is prepared for the
/// second pass. \p Callback is resposible to perform the second pass on it. /// second pass. \p Callback is resposible to perform the second pass on it.
/// The \c CompilerInstance may be reused from the previous completions, /// The \c CompilerInstance may be reused from the previous completions,

View File

@@ -18,6 +18,8 @@
#include "swift/AST/Module.h" #include "swift/AST/Module.h"
#include "swift/AST/PrettyStackTrace.h" #include "swift/AST/PrettyStackTrace.h"
#include "swift/AST/SourceFile.h" #include "swift/AST/SourceFile.h"
#include "swift/Serialization/SerializedModuleLoader.h"
#include "swift/ClangImporter/ClangModule.h"
#include "swift/Basic/LangOptions.h" #include "swift/Basic/LangOptions.h"
#include "swift/Basic/PrettyStackTrace.h" #include "swift/Basic/PrettyStackTrace.h"
#include "swift/Basic/SourceManager.h" #include "swift/Basic/SourceManager.h"
@@ -28,6 +30,7 @@
#include "swift/Subsystems.h" #include "swift/Subsystems.h"
#include "llvm/ADT/Hashing.h" #include "llvm/ADT/Hashing.h"
#include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/MemoryBuffer.h"
#include "clang/AST/ASTContext.h"
using namespace swift; using namespace swift;
using namespace ide; using namespace ide;
@@ -162,10 +165,77 @@ static DeclContext *getEquivalentDeclContextFromSourceFile(DeclContext *DC,
return newDC; return newDC;
} }
/// Check if any dependent files are modified since \p timestamp.
bool areAnyDependentFilesInvalidated(CompilerInstance &CI,
llvm::vfs::FileSystem &FS,
StringRef currentFileName,
llvm::sys::TimePoint<> timestamp) {
auto isInvalidated = [&](StringRef filePath) -> bool {
auto stat = FS.status(filePath);
if (!stat)
// Missing.
return true;
auto lastModTime = stat->getLastModificationTime();
if (lastModTime > timestamp)
// Modified.
return true;
// If the last modification time is zero, this file is probably from a
// virtual file system. We need to check the content.
if (lastModTime == llvm::sys::TimePoint<>()) {
if (&CI.getFileSystem() == &FS)
return false;
auto oldContent = CI.getFileSystem().getBufferForFile(filePath);
auto newContent = FS.getBufferForFile(filePath);
if (!oldContent || !newContent)
// (unreachable?)
return true;
if (oldContent.get()->getBuffer() != newContent.get()->getBuffer())
// Different content.
return true;
}
return false;
};
// Check files in the current module.
for (FileUnit *file : CI.getMainModule()->getFiles()) {
StringRef filename;
if (auto SF = dyn_cast<SourceFile>(file))
filename = SF->getFilename();
else if (auto LF = dyn_cast<LoadedFile>(file))
filename = LF->getFilename();
else
continue;
// Ignore the current file and synthesized files.
if (filename.empty() || filename.front() == '<' ||
filename.equals(currentFileName))
continue;
if (isInvalidated(filename))
return true;
}
// Check other non-system depenencies (e.g. modules, headers).
for (auto &dep : CI.getDependencyTracker()->getDependencies()) {
if (isInvalidated(dep))
return true;
}
// All loaded module files are not modified since the timestamp.
return false;
}
} // namespace } // namespace
bool CompletionInstance::performCachedOperationIfPossible( bool CompletionInstance::performCachedOperationIfPossible(
const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash, const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
llvm::MemoryBuffer *completionBuffer, unsigned int Offset, llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
DiagnosticConsumer *DiagC, DiagnosticConsumer *DiagC,
llvm::function_ref<void(CompilerInstance &, bool)> Callback) { llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
@@ -187,10 +257,17 @@ bool CompletionInstance::performCachedOperationIfPossible(
auto &oldInfo = oldState->getCodeCompletionDelayedDeclState(); auto &oldInfo = oldState->getCodeCompletionDelayedDeclState();
auto &SM = CI.getSourceMgr(); auto &SM = CI.getSourceMgr();
if (SM.getIdentifierForBuffer(SM.getCodeCompletionBufferID()) != auto bufferName = completionBuffer->getBufferIdentifier();
completionBuffer->getBufferIdentifier()) if (SM.getIdentifierForBuffer(SM.getCodeCompletionBufferID()) != bufferName)
return false; return false;
if (shouldCheckDependencies()) {
if (areAnyDependentFilesInvalidated(CI, *FileSystem, bufferName,
DependencyCheckedTimestamp))
return false;
DependencyCheckedTimestamp = std::chrono::system_clock::now();
}
// Parse the new buffer into temporary SourceFile. // Parse the new buffer into temporary SourceFile.
SourceManager tmpSM; SourceManager tmpSM;
auto tmpBufferID = tmpSM.addMemBufferCopy(completionBuffer); auto tmpBufferID = tmpSM.addMemBufferCopy(completionBuffer);
@@ -263,8 +340,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
completionBuffer->getBuffer().slice(startOffset, endOffset); completionBuffer->getBuffer().slice(startOffset, endOffset);
auto newOffset = Offset - startOffset; auto newOffset = Offset - startOffset;
newBufferID = SM.addMemBufferCopy(sourceText, newBufferID = SM.addMemBufferCopy(sourceText, bufferName);
completionBuffer->getBufferIdentifier());
SM.openVirtualFile(SM.getLocForBufferStart(newBufferID), SM.openVirtualFile(SM.getLocForBufferStart(newBufferID),
tmpSM.getDisplayNameForLoc(startLoc), tmpSM.getDisplayNameForLoc(startLoc),
tmpSM.getLineAndColumn(startLoc).first - 1); tmpSM.getLineAndColumn(startLoc).first - 1);
@@ -310,8 +386,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID); endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID);
sourceText = sourceText.slice(0, endOffset); sourceText = sourceText.slice(0, endOffset);
} }
newBufferID = SM.addMemBufferCopy(sourceText, newBufferID = SM.addMemBufferCopy(sourceText, bufferName);
completionBuffer->getBufferIdentifier());
SM.setCodeCompletionPoint(newBufferID, Offset); SM.setCodeCompletionPoint(newBufferID, Offset);
// Create a new module and a source file using the current AST context. // Create a new module and a source file using the current AST context.
@@ -369,7 +444,15 @@ bool CompletionInstance::performNewOperation(
llvm::function_ref<void(CompilerInstance &, bool)> Callback) { llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
llvm::PrettyStackTraceString trace("While performing new completion"); llvm::PrettyStackTraceString trace("While performing new completion");
auto isCachedCompletionRequested = ArgsHash.hasValue();
auto TheInstance = std::make_unique<CompilerInstance>(); auto TheInstance = std::make_unique<CompilerInstance>();
// Track dependencies in fast-completion mode to invalidate the compiler
// instance if any dependent files are modified.
if (isCachedCompletionRequested)
TheInstance->createDependencyTracker(false);
{ {
auto &CI = *TheInstance; auto &CI = *TheInstance;
if (DiagC) if (DiagC)
@@ -407,15 +490,34 @@ bool CompletionInstance::performNewOperation(
Callback(CI, /*reusingASTContext=*/false); Callback(CI, /*reusingASTContext=*/false);
} }
if (ArgsHash.hasValue()) { // Cache the compiler instance if fast completion is enabled.
CachedCI = std::move(TheInstance); if (isCachedCompletionRequested)
CachedArgHash = *ArgsHash; cacheCompilerInstance(std::move(TheInstance), *ArgsHash);
CachedReuseCount = 0;
}
return true; return true;
} }
void CompletionInstance::cacheCompilerInstance(
std::unique_ptr<CompilerInstance> CI, llvm::hash_code ArgsHash) {
CachedCI = std::move(CI);
CachedArgHash = ArgsHash;
auto now = std::chrono::system_clock::now();
DependencyCheckedTimestamp = now;
CachedReuseCount = 0;
}
bool CompletionInstance::shouldCheckDependencies() const {
assert(CachedCI);
using namespace std::chrono;
auto now = system_clock::now();
return DependencyCheckedTimestamp + seconds(DependencyCheckIntervalSecond) < now;
}
void CompletionInstance::setDependencyCheckIntervalSecond(unsigned Value) {
std::lock_guard<std::mutex> lock(mtx);
DependencyCheckIntervalSecond = Value;
}
bool swift::ide::CompletionInstance::performOperation( bool swift::ide::CompletionInstance::performOperation(
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args, swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
@@ -451,8 +553,9 @@ bool swift::ide::CompletionInstance::performOperation(
// the cached completion instance. // the cached completion instance.
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
if (performCachedOperationIfPossible(Invocation, ArgsHash, completionBuffer, if (performCachedOperationIfPossible(Invocation, ArgsHash, FileSystem,
Offset, DiagC, Callback)) completionBuffer, Offset, DiagC,
Callback))
return true; return true;
if (performNewOperation(ArgsHash, Invocation, FileSystem, completionBuffer, if (performNewOperation(ArgsHash, Invocation, FileSystem, completionBuffer,

View File

@@ -0,0 +1,2 @@
#import <ClangFW/Funcs.h>

View File

@@ -0,0 +1,4 @@
#ifndef CLANGFW_FUNCS_H
#define CLANGFW_FUNCS_H
int clangFWFunc();
#endif

View File

@@ -0,0 +1,4 @@
framework module ClangFW {
umbrella header "ClangFW.h"
export *
}

View File

@@ -0,0 +1,5 @@
#ifndef CLANGFW_FUNCS_H
#define CLANGFW_FUNCS_H
int clangFWFunc_mod();
#endif

View File

@@ -0,0 +1 @@
#import "LocalCFunc.h"

View File

@@ -0,0 +1 @@
func localSwiftFunc() -> Int {}

View File

@@ -0,0 +1,6 @@
#ifndef MYPROJECT_LOCALCFUNC_H
#define MYPROJECT_LOCALCFUNC_H
int localClangFunc();
#endif

View File

@@ -0,0 +1 @@
func localSwiftFunc_mod() -> Int {}

View File

@@ -0,0 +1,7 @@
#ifndef MYPROJECT_LOCALCFUNC_H
#define MYPROJECT_LOCALCFUNC_H
int localClangFunc_mod();
#endif

View File

@@ -0,0 +1,2 @@
public func swiftFWFunc() -> Int { return 1 }

View File

@@ -0,0 +1 @@
public func swiftFWFunc_mod() -> Int { return 1 }

View File

@@ -0,0 +1,5 @@
import Foundation
import ClangFW // < 500 headers framework
func foo() {
/* HERE */
}

View File

@@ -0,0 +1,72 @@
import ClangFW
import SwiftFW
func foo() {
/*HERE*/
}
// RUN: %empty-directory(%t/Frameworks)
// RUN: %empty-directory(%t/MyProject)
// RUN: COMPILER_ARGS=( \
// RUN: -target %target-triple \
// RUN: -module-name MyProject \
// RUN: -F %t/Frameworks \
// RUN: -I %t/MyProject \
// RUN: -import-objc-header %t/MyProject/Bridging.h \
// RUN: %t/MyProject/Library.swift \
// RUN: %s \
// RUN: )
// RUN: INPUT_DIR=%S/Inputs/checkdeps
// RUN: DEPCHECK_INTERVAL=1
// RUN: SLEEP_TIME=2
// RUN: cp -R $INPUT_DIR/MyProject %t/
// RUN: cp -R $INPUT_DIR/ClangFW.framework %t/Frameworks/
// RUN: %empty-directory(%t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule)
// RUN: %target-swift-frontend -emit-module -module-name SwiftFW -o %t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule/%target-swiftmodule-name $INPUT_DIR/SwiftFW_src/Funcs.swift
// RUN: %sourcekitd-test \
// RUN: -req=global-config -completion-check-dependency-interval ${DEPCHECK_INTERVAL} == \
// RUN: -shell -- echo "### Initial" == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} == \
// RUN: -shell -- echo '### Modify bridging header library file' == \
// RUN: -shell -- cp -R $INPUT_DIR/MyProject_mod/LocalCFunc.h %t/MyProject/ == \
// RUN: -shell -- sleep $SLEEP_TIME == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} == \
// RUN: -shell -- echo '### Fast completion' == \
// RUN: -shell -- sleep $SLEEP_TIME == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} \
// RUN: | %FileCheck %s
// CHECK-LABEL: ### Initial
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc()"
// CHECK-DAG: key.description: "swiftFWFunc()"
// CHECK-DAG: key.description: "localClangFunc()"
// CHECK-DAG: key.description: "localSwiftFunc()"
// CHECK: ]
// CHECK-NOT: key.reusingastcontext: 1
// CHECK-LABEL: ### Modify bridging header library file
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc()"
// CHECK-DAG: key.description: "swiftFWFunc()"
// CHECK-DAG: key.description: "localClangFunc_mod()"
// CHECK-DAG: key.description: "localSwiftFunc()"
// CHECK: ]
// CHECK-NOT: key.reusingastcontext: 1
// CHECK-LABEL: ### Fast completion
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc()"
// CHECK-DAG: key.description: "swiftFWFunc()"
// CHECK-DAG: key.description: "localClangFunc_mod()"
// CHECK-DAG: key.description: "localSwiftFunc()"
// CHECK: ]
// CHECK: key.reusingastcontext: 1

View File

@@ -0,0 +1,72 @@
import ClangFW
import SwiftFW
func foo() {
/*HERE*/
}
// RUN: %empty-directory(%t/Frameworks)
// RUN: %empty-directory(%t/MyProject)
// RUN: COMPILER_ARGS=( \
// RUN: -target %target-triple \
// RUN: -module-name MyProject \
// RUN: -F %t/Frameworks \
// RUN: -I %t/MyProject \
// RUN: -import-objc-header %t/MyProject/Bridging.h \
// RUN: %t/MyProject/Library.swift \
// RUN: %s \
// RUN: )
// RUN: INPUT_DIR=%S/Inputs/checkdeps
// RUN: DEPCHECK_INTERVAL=1
// RUN: SLEEP_TIME=2
// RUN: cp -R $INPUT_DIR/MyProject %t/
// RUN: cp -R $INPUT_DIR/ClangFW.framework %t/Frameworks/
// RUN: %empty-directory(%t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule)
// RUN: %target-swift-frontend -emit-module -module-name SwiftFW -o %t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule/%target-swiftmodule-name $INPUT_DIR/SwiftFW_src/Funcs.swift
// RUN: %sourcekitd-test \
// RUN: -req=global-config -completion-check-dependency-interval ${DEPCHECK_INTERVAL} == \
// RUN: -shell -- echo "### Initial" == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} == \
// RUN: -shell -- echo '### Modify framework (c)' == \
// RUN: -shell -- cp -R $INPUT_DIR/ClangFW.framework_mod/* %t/Frameworks/ClangFW.framework/ == \
// RUN: -shell -- sleep $SLEEP_TIME == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} == \
// RUN: -shell -- echo '### Fast completion' == \
// RUN: -shell -- sleep $SLEEP_TIME == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} \
// RUN: | %FileCheck %s
// CHECK-LABEL: ### Initial
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc()"
// CHECK-DAG: key.description: "swiftFWFunc()"
// CHECK-DAG: key.description: "localClangFunc()"
// CHECK-DAG: key.description: "localSwiftFunc()"
// CHECK: ]
// CHECK-NOT: key.reusingastcontext: 1
// CHECK-LABEL: ### Modify framework (c)
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc_mod()"
// CHECK-DAG: key.description: "swiftFWFunc()"
// CHECK-DAG: key.description: "localClangFunc()"
// CHECK-DAG: key.description: "localSwiftFunc()"
// CHECK: ]
// CHECK-NOT: key.reusingastcontext: 1
// CHECK-LABEL: ### Fast completion
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc_mod()"
// CHECK-DAG: key.description: "swiftFWFunc()"
// CHECK-DAG: key.description: "localClangFunc()"
// CHECK-DAG: key.description: "localSwiftFunc()"
// CHECK: ]
// CHECK: key.reusingastcontext: 1

View File

@@ -0,0 +1,71 @@
import ClangFW
import SwiftFW
func foo() {
/*HERE*/
}
// RUN: %empty-directory(%t/Frameworks)
// RUN: %empty-directory(%t/MyProject)
// RUN: COMPILER_ARGS=( \
// RUN: -target %target-triple \
// RUN: -module-name MyProject \
// RUN: -F %t/Frameworks \
// RUN: -I %t/MyProject \
// RUN: -import-objc-header %t/MyProject/Bridging.h \
// RUN: %t/MyProject/Library.swift \
// RUN: %s \
// RUN: )
// RUN: INPUT_DIR=%S/Inputs/checkdeps
// RUN: DEPCHECK_INTERVAL=1
// RUN: SLEEP_TIME=2
// RUN: cp -R $INPUT_DIR/MyProject %t/
// RUN: cp -R $INPUT_DIR/ClangFW.framework %t/Frameworks/
// RUN: %empty-directory(%t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule)
// RUN: %target-swift-frontend -emit-module -module-name SwiftFW -o %t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule/%target-swiftmodule-name $INPUT_DIR/SwiftFW_src/Funcs.swift
// RUN: %sourcekitd-test \
// RUN: -req=global-config -completion-check-dependency-interval ${DEPCHECK_INTERVAL} == \
// RUN: -shell -- echo "### Initial" == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} == \
// RUN: -shell -- echo "### Modify local library file" == \
// RUN: -shell -- cp -R $INPUT_DIR/MyProject_mod/Library.swift %t/MyProject/ == \
// RUN: -shell -- sleep $SLEEP_TIME == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} == \
// RUN: -shell -- echo '### Fast completion' == \
// RUN: -shell -- sleep $SLEEP_TIME == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} \
// RUN: | %FileCheck %s
// CHECK-LABEL: ### Initial
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc()"
// CHECK-DAG: key.description: "swiftFWFunc()"
// CHECK-DAG: key.description: "localClangFunc()"
// CHECK-DAG: key.description: "localSwiftFunc()"
// CHECK: ]
// CHECK-NOT: key.reusingastcontext: 1
// CHECK-LABEL: ### Modify local library file
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc()"
// CHECK-DAG: key.description: "swiftFWFunc()"
// CHECK-DAG: key.description: "localClangFunc()"
// CHECK-DAG: key.description: "localSwiftFunc_mod()"
// CHECK: ]
// CHECK-NOT: key.reusingastcontext: 1
// CHECK-LABEL: ### Fast completion
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc()"
// CHECK-DAG: key.description: "swiftFWFunc()"
// CHECK-DAG: key.description: "localClangFunc()"
// CHECK-DAG: key.description: "localSwiftFunc_mod()"
// CHECK: ]
// CHECK: key.reusingastcontext: 1

View File

@@ -0,0 +1,71 @@
import ClangFW
import SwiftFW
func foo() {
/*HERE*/
}
// RUN: %empty-directory(%t/Frameworks)
// RUN: %empty-directory(%t/MyProject)
// RUN: COMPILER_ARGS=( \
// RUN: -target %target-triple \
// RUN: -module-name MyProject \
// RUN: -F %t/Frameworks \
// RUN: -I %t/MyProject \
// RUN: -import-objc-header %t/MyProject/Bridging.h \
// RUN: %t/MyProject/Library.swift \
// RUN: %s \
// RUN: )
// RUN: INPUT_DIR=%S/Inputs/checkdeps
// RUN: DEPCHECK_INTERVAL=1
// RUN: SLEEP_TIME=2
// RUN: cp -R $INPUT_DIR/MyProject %t/
// RUN: cp -R $INPUT_DIR/ClangFW.framework %t/Frameworks/
// RUN: %empty-directory(%t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule)
// RUN: %target-swift-frontend -emit-module -module-name SwiftFW -o %t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule/%target-swiftmodule-name $INPUT_DIR/SwiftFW_src/Funcs.swift
// RUN: %sourcekitd-test \
// RUN: -req=global-config -completion-check-dependency-interval ${DEPCHECK_INTERVAL} == \
// RUN: -shell -- echo "### Initial" == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} == \
// RUN: -shell -- echo '### Modify framework (s)' == \
// RUN: -shell -- %target-swift-frontend -emit-module -module-name SwiftFW -o %t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule/%target-swiftmodule-name $INPUT_DIR/SwiftFW_src_mod/Funcs.swift == \
// RUN: -shell -- sleep $SLEEP_TIME == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} == \
// RUN: -shell -- echo '### Fast completion' == \
// RUN: -shell -- sleep $SLEEP_TIME == \
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} \
// RUN: | %FileCheck %s
// CHECK-LABEL: ### Initial
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc()"
// CHECK-DAG: key.description: "swiftFWFunc()"
// CHECK-DAG: key.description: "localClangFunc()"
// CHECK-DAG: key.description: "localSwiftFunc()"
// CHECK: ]
// CHECK-NOT: key.reusingastcontext: 1
// CHECK-LABEL: ### Modify framework (s)
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc()"
// CHECK-DAG: key.description: "swiftFWFunc_mod()"
// CHECK-DAG: key.description: "localClangFunc()"
// CHECK-DAG: key.description: "localSwiftFunc()"
// CHECK: ]
// CHECK-NOT: key.reusingastcontext: 1
// CHECK-LABEL: ### Fast completion
// CHECK: key.results: [
// CHECK-DAG: key.description: "clangFWFunc()"
// CHECK-DAG: key.description: "swiftFWFunc_mod()"
// CHECK-DAG: key.description: "localClangFunc()"
// CHECK-DAG: key.description: "localSwiftFunc()"
// CHECK: ]
// CHECK: key.reusingastcontext: 1

View File

@@ -36,6 +36,9 @@ public:
/// ///
/// At the time of writing this just means ignoring .swiftsourceinfo files. /// At the time of writing this just means ignoring .swiftsourceinfo files.
bool OptimizeForIDE = false; bool OptimizeForIDE = false;
/// Interval second for checking dependencies in fast code completion.
unsigned CompletionCheckDependencyInterval = 5;
}; };
private: private:
@@ -43,8 +46,10 @@ private:
mutable llvm::sys::Mutex Mtx; mutable llvm::sys::Mutex Mtx;
public: public:
Settings update(Optional<bool> OptimizeForIDE); Settings update(Optional<bool> OptimizeForIDE,
Optional<unsigned> CompletionCheckDependencyInterval);
bool shouldOptimizeForIDE() const; bool shouldOptimizeForIDE() const;
unsigned getCompletionCheckDependencyInterval() const;
}; };
class Context { class Context {

View File

@@ -37,6 +37,7 @@ class SourceFileSyntax;
} // namespace swift } // namespace swift
namespace SourceKit { namespace SourceKit {
class GlobalConfig;
struct EntityInfo { struct EntityInfo {
UIdent Kind; UIdent Kind;
@@ -651,6 +652,8 @@ public:
virtual ~LangSupport() { } virtual ~LangSupport() { }
virtual void globalConfigurationUpdated(std::shared_ptr<GlobalConfig> Config) {};
virtual void indexSource(StringRef Filename, virtual void indexSource(StringRef Filename,
IndexingConsumer &Consumer, IndexingConsumer &Consumer,
ArrayRef<const char *> Args) = 0; ArrayRef<const char *> Args) = 0;

View File

@@ -17,10 +17,13 @@
using namespace SourceKit; using namespace SourceKit;
GlobalConfig::Settings GlobalConfig::Settings
GlobalConfig::update(Optional<bool> OptimizeForIDE) { GlobalConfig::update(Optional<bool> OptimizeForIDE,
Optional<unsigned> CompletionCheckDependencyInterval) {
llvm::sys::ScopedLock L(Mtx); llvm::sys::ScopedLock L(Mtx);
if (OptimizeForIDE.hasValue()) if (OptimizeForIDE.hasValue())
State.OptimizeForIDE = *OptimizeForIDE; State.OptimizeForIDE = *OptimizeForIDE;
if (CompletionCheckDependencyInterval.hasValue())
State.CompletionCheckDependencyInterval = *CompletionCheckDependencyInterval;
return State; return State;
}; };
@@ -28,6 +31,10 @@ bool GlobalConfig::shouldOptimizeForIDE() const {
llvm::sys::ScopedLock L(Mtx); llvm::sys::ScopedLock L(Mtx);
return State.OptimizeForIDE; return State.OptimizeForIDE;
} }
unsigned GlobalConfig::getCompletionCheckDependencyInterval() const {
llvm::sys::ScopedLock L(Mtx);
return State.CompletionCheckDependencyInterval;
}
SourceKit::Context::Context(StringRef RuntimeLibPath, SourceKit::Context::Context(StringRef RuntimeLibPath,
llvm::function_ref<std::unique_ptr<LangSupport>(Context &)> llvm::function_ref<std::unique_ptr<LangSupport>(Context &)>

View File

@@ -257,6 +257,13 @@ class InMemoryFileSystemProvider: public SourceKit::FileSystemProvider {
}; };
} }
static void
configureCompletionInstance(std::shared_ptr<CompletionInstance> CompletionInst,
std::shared_ptr<GlobalConfig> GlobalConfig) {
CompletionInst->setDependencyCheckIntervalSecond(
GlobalConfig->getCompletionCheckDependencyInterval());
}
SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx) SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx)
: NotificationCtr(SKCtx.getNotificationCenter()), : NotificationCtr(SKCtx.getNotificationCenter()),
CCCache(new SwiftCompletionCache) { CCCache(new SwiftCompletionCache) {
@@ -270,7 +277,8 @@ SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx)
SKCtx.getGlobalConfiguration(), SKCtx.getGlobalConfiguration(),
Stats, RuntimeResourcePath); Stats, RuntimeResourcePath);
CompletionInst = std::make_unique<CompletionInstance>(); CompletionInst = std::make_shared<CompletionInstance>();
configureCompletionInstance(CompletionInst, SKCtx.getGlobalConfiguration());
// By default, just use the in-memory cache. // By default, just use the in-memory cache.
CCCache->inMemory = std::make_unique<ide::CodeCompletionCache>(); CCCache->inMemory = std::make_unique<ide::CodeCompletionCache>();
@@ -282,6 +290,11 @@ SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx)
SwiftLangSupport::~SwiftLangSupport() { SwiftLangSupport::~SwiftLangSupport() {
} }
void SwiftLangSupport::globalConfigurationUpdated(
std::shared_ptr<GlobalConfig> Config) {
configureCompletionInstance(CompletionInst, Config);
}
UIdent SwiftLangSupport::getUIDForDecl(const Decl *D, bool IsRef) { UIdent SwiftLangSupport::getUIDForDecl(const Decl *D, bool IsRef) {
return UIdentVisitor(IsRef).visit(const_cast<Decl*>(D)); return UIdentVisitor(IsRef).visit(const_cast<Decl*>(D));
} }

View File

@@ -450,6 +450,8 @@ public:
// LangSupport Interface // LangSupport Interface
//==========================================================================// //==========================================================================//
void globalConfigurationUpdated(std::shared_ptr<GlobalConfig> Config) override;
void indexSource(StringRef Filename, IndexingConsumer &Consumer, void indexSource(StringRef Filename, IndexingConsumer &Consumer,
ArrayRef<const char *> Args) override; ArrayRef<const char *> Args) override;

View File

@@ -143,6 +143,11 @@ def vfs_name : Separate<["-"], "vfs-name">,
def optimize_for_ide : Joined<["-"], "for-ide=">, def optimize_for_ide : Joined<["-"], "for-ide=">,
HelpText<"Value for the OptimizeForIde global configuration setting">; HelpText<"Value for the OptimizeForIde global configuration setting">;
def completion_check_dependency_interval : Separate<["-"], "completion-check-dependency-interval">,
HelpText<"Inteval seconds for checking dependencies in fast completion">;
def completion_check_dependency_interval_EQ : Joined<["-"], "completion-check-dependency-interval=">,
Alias<completion_check_dependency_interval>;
def suppress_config_request : Flag<["-"], "suppress-config-request">, def suppress_config_request : Flag<["-"], "suppress-config-request">,
HelpText<"Suppress the default global configuration request, that is otherwise sent before any other request (except for the global-config request itself)">; HelpText<"Suppress the default global configuration request, that is otherwise sent before any other request (except for the global-config request itself)">;

View File

@@ -381,6 +381,19 @@ bool TestOptions::parseArgs(llvm::ArrayRef<const char *> Args) {
break; break;
} }
case OPT_completion_check_dependency_interval: {
int64_t Value;
if (StringRef(InputArg->getValue()).getAsInteger(10, Value)) {
llvm::errs() << "error: expected number for inteval\n";
return true;
} else if (Value < 0) {
llvm::errs() << "error: completion-check-dependency-interval must be > 0\n";
return true;
}
CompletionCheckDependencyInterval = Value;
break;
}
case OPT_suppress_config_request: case OPT_suppress_config_request:
SuppressDefaultConfigRequest = true; SuppressDefaultConfigRequest = true;
break; break;

View File

@@ -114,6 +114,7 @@ struct TestOptions {
bool timeRequest = false; bool timeRequest = false;
llvm::Optional<bool> OptimizeForIde; llvm::Optional<bool> OptimizeForIde;
bool SuppressDefaultConfigRequest = false; bool SuppressDefaultConfigRequest = false;
llvm::Optional<unsigned> CompletionCheckDependencyInterval;
unsigned repeatRequest = 1; unsigned repeatRequest = 1;
struct VFSFile { struct VFSFile {
std::string path; std::string path;

View File

@@ -548,7 +548,14 @@ static int handleTestInvocation(TestOptions Opts, TestOptions &InitOpts) {
case SourceKitRequest::GlobalConfiguration: case SourceKitRequest::GlobalConfiguration:
sourcekitd_request_dictionary_set_uid(Req, KeyRequest, RequestGlobalConfiguration); sourcekitd_request_dictionary_set_uid(Req, KeyRequest, RequestGlobalConfiguration);
if (Opts.OptimizeForIde.hasValue()) if (Opts.OptimizeForIde.hasValue())
sourcekitd_request_dictionary_set_int64(Req, KeyOptimizeForIDE, static_cast<int64_t>(Opts.OptimizeForIde.getValue())); sourcekitd_request_dictionary_set_int64(
Req, KeyOptimizeForIDE,
static_cast<int64_t>(Opts.OptimizeForIde.getValue()));
if (Opts.CompletionCheckDependencyInterval.hasValue())
sourcekitd_request_dictionary_set_int64(
Req, KeyCompletionCheckDependencyInterval,
static_cast<int64_t>(
Opts.CompletionCheckDependencyInterval.getValue()));
break; break;
case SourceKitRequest::ProtocolVersion: case SourceKitRequest::ProtocolVersion:

View File

@@ -438,9 +438,21 @@ void handleRequestImpl(sourcekitd_object_t ReqObj, ResponseReceiver Rec) {
if (!Req.getInt64(KeyOptimizeForIDE, EditorMode, true)) { if (!Req.getInt64(KeyOptimizeForIDE, EditorMode, true)) {
OptimizeForIDE = EditorMode; OptimizeForIDE = EditorMode;
} }
Optional<unsigned> CompletionCheckDependencyInterval;
int64_t IntervalValue = 0;
if (!Req.getInt64(KeyCompletionCheckDependencyInterval,
IntervalValue, /*isOptional=*/true))
CompletionCheckDependencyInterval = IntervalValue;
GlobalConfig::Settings UpdatedConfig = Config->update(
OptimizeForIDE, CompletionCheckDependencyInterval);
getGlobalContext().getSwiftLangSupport().globalConfigurationUpdated(Config);
GlobalConfig::Settings UpdatedConfig = Config->update(OptimizeForIDE);
dict.set(KeyOptimizeForIDE, UpdatedConfig.OptimizeForIDE); dict.set(KeyOptimizeForIDE, UpdatedConfig.OptimizeForIDE);
dict.set(KeyCompletionCheckDependencyInterval,
UpdatedConfig.CompletionCheckDependencyInterval);
return Rec(RB.createResponse()); return Rec(RB.createResponse());
} }
if (ReqUID == RequestProtocolVersion) { if (ReqUID == RequestProtocolVersion) {

View File

@@ -179,6 +179,8 @@ UID_KEYS = [
KEY('OptimizeForIDE', 'key.optimize_for_ide'), KEY('OptimizeForIDE', 'key.optimize_for_ide'),
KEY('RequiredBystanders', 'key.required_bystanders'), KEY('RequiredBystanders', 'key.required_bystanders'),
KEY('ReusingASTContext', 'key.reusingastcontext'), KEY('ReusingASTContext', 'key.reusingastcontext'),
KEY('CompletionCheckDependencyInterval',
'key.completion_check_dependency_interval'),
] ]