1818#include " swift/AST/Module.h"
1919#include " swift/AST/PrettyStackTrace.h"
2020#include " swift/AST/SourceFile.h"
21+ #include " swift/Serialization/SerializedModuleLoader.h"
22+ #include " swift/ClangImporter/ClangModule.h"
2123#include " swift/Basic/LangOptions.h"
2224#include " swift/Basic/PrettyStackTrace.h"
2325#include " swift/Basic/SourceManager.h"
2830#include " swift/Subsystems.h"
2931#include " llvm/ADT/Hashing.h"
3032#include " llvm/Support/MemoryBuffer.h"
33+ #include " clang/AST/ASTContext.h"
3134
3235using namespace swift ;
3336using namespace ide ;
@@ -162,10 +165,77 @@ static DeclContext *getEquivalentDeclContextFromSourceFile(DeclContext *DC,
162165 return newDC;
163166}
164167
168+ // / Check if any dependent files are modified since \p timestamp.
169+ bool areAnyDependentFilesInvalidated (CompilerInstance &CI,
170+ llvm::vfs::FileSystem &FS,
171+ StringRef currentFileName,
172+ llvm::sys::TimePoint<> timestamp) {
173+
174+ auto isInvalidated = [&](StringRef filePath) -> bool {
175+ auto stat = FS.status (filePath);
176+ if (!stat)
177+ // Missing.
178+ return true ;
179+
180+ auto lastModTime = stat->getLastModificationTime ();
181+ if (lastModTime > timestamp)
182+ // Modified.
183+ return true ;
184+
185+ // If the last modification time is zero, this file is probably from a
186+ // virtual file system. We need to check the content.
187+ if (lastModTime == llvm::sys::TimePoint<>()) {
188+ if (&CI.getFileSystem () == &FS)
189+ return false ;
190+
191+ auto oldContent = CI.getFileSystem ().getBufferForFile (filePath);
192+ auto newContent = FS.getBufferForFile (filePath);
193+ if (!oldContent || !newContent)
194+ // (unreachable?)
195+ return true ;
196+
197+ if (oldContent.get ()->getBuffer () != newContent.get ()->getBuffer ())
198+ // Different content.
199+ return true ;
200+ }
201+
202+ return false ;
203+ };
204+
205+ // Check files in the current module.
206+ for (FileUnit *file : CI.getMainModule ()->getFiles ()) {
207+ StringRef filename;
208+ if (auto SF = dyn_cast<SourceFile>(file))
209+ filename = SF->getFilename ();
210+ else if (auto LF = dyn_cast<LoadedFile>(file))
211+ filename = LF->getFilename ();
212+ else
213+ continue ;
214+
215+ // Ignore the current file and synthesized files.
216+ if (filename.empty () || filename.front () == ' <' ||
217+ filename.equals (currentFileName))
218+ continue ;
219+
220+ if (isInvalidated (filename))
221+ return true ;
222+ }
223+
224+ // Check other non-system depenencies (e.g. modules, headers).
225+ for (auto &dep : CI.getDependencyTracker ()->getDependencies ()) {
226+ if (isInvalidated (dep))
227+ return true ;
228+ }
229+
230+ // All loaded module files are not modified since the timestamp.
231+ return false ;
232+ }
233+
165234} // namespace
166235
167236bool CompletionInstance::performCachedOperationIfPossible (
168237 const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
238+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
169239 llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
170240 DiagnosticConsumer *DiagC,
171241 llvm::function_ref<void (CompilerInstance &, bool )> Callback) {
@@ -187,10 +257,17 @@ bool CompletionInstance::performCachedOperationIfPossible(
187257 auto &oldInfo = oldState->getCodeCompletionDelayedDeclState ();
188258
189259 auto &SM = CI.getSourceMgr ();
190- if (SM. getIdentifierForBuffer (SM. getCodeCompletionBufferID ()) !=
191- completionBuffer-> getBufferIdentifier () )
260+ auto bufferName = completionBuffer-> getBufferIdentifier ();
261+ if (SM. getIdentifierForBuffer (SM. getCodeCompletionBufferID ()) != bufferName )
192262 return false ;
193263
264+ if (shouldCheckDependencies ()) {
265+ if (areAnyDependentFilesInvalidated (CI, *FileSystem, bufferName,
266+ DependencyCheckedTimestamp))
267+ return false ;
268+ DependencyCheckedTimestamp = std::chrono::system_clock::now ();
269+ }
270+
194271 // Parse the new buffer into temporary SourceFile.
195272 SourceManager tmpSM;
196273 auto tmpBufferID = tmpSM.addMemBufferCopy (completionBuffer);
@@ -263,8 +340,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
263340 completionBuffer->getBuffer ().slice (startOffset, endOffset);
264341 auto newOffset = Offset - startOffset;
265342
266- newBufferID = SM.addMemBufferCopy (sourceText,
267- completionBuffer->getBufferIdentifier ());
343+ newBufferID = SM.addMemBufferCopy (sourceText, bufferName);
268344 SM.openVirtualFile (SM.getLocForBufferStart (newBufferID),
269345 tmpSM.getDisplayNameForLoc (startLoc),
270346 tmpSM.getLineAndColumn (startLoc).first - 1 );
@@ -310,8 +386,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
310386 endOffset = tmpSM.getLocOffsetInBuffer (endLoc, tmpBufferID);
311387 sourceText = sourceText.slice (0 , endOffset);
312388 }
313- newBufferID = SM.addMemBufferCopy (sourceText,
314- completionBuffer->getBufferIdentifier ());
389+ newBufferID = SM.addMemBufferCopy (sourceText, bufferName);
315390 SM.setCodeCompletionPoint (newBufferID, Offset);
316391
317392 // Create a new module and a source file using the current AST context.
@@ -369,7 +444,15 @@ bool CompletionInstance::performNewOperation(
369444 llvm::function_ref<void (CompilerInstance &, bool )> Callback) {
370445 llvm::PrettyStackTraceString trace (" While performing new completion" );
371446
447+ auto isCachedCompletionRequested = ArgsHash.hasValue ();
448+
372449 auto TheInstance = std::make_unique<CompilerInstance>();
450+
451+ // Track dependencies in fast-completion mode to invalidate the compiler
452+ // instance if any dependent files are modified.
453+ if (isCachedCompletionRequested)
454+ TheInstance->createDependencyTracker (false );
455+
373456 {
374457 auto &CI = *TheInstance;
375458 if (DiagC)
@@ -407,15 +490,34 @@ bool CompletionInstance::performNewOperation(
407490 Callback (CI, /* reusingASTContext=*/ false );
408491 }
409492
410- if (ArgsHash.hasValue ()) {
411- CachedCI = std::move (TheInstance);
412- CachedArgHash = *ArgsHash;
413- CachedReuseCount = 0 ;
414- }
493+ // Cache the compiler instance if fast completion is enabled.
494+ if (isCachedCompletionRequested)
495+ cacheCompilerInstance (std::move (TheInstance), *ArgsHash);
415496
416497 return true ;
417498}
418499
500+ void CompletionInstance::cacheCompilerInstance (
501+ std::unique_ptr<CompilerInstance> CI, llvm::hash_code ArgsHash) {
502+ CachedCI = std::move (CI);
503+ CachedArgHash = ArgsHash;
504+ auto now = std::chrono::system_clock::now ();
505+ DependencyCheckedTimestamp = now;
506+ CachedReuseCount = 0 ;
507+ }
508+
509+ bool CompletionInstance::shouldCheckDependencies () const {
510+ assert (CachedCI);
511+ using namespace std ::chrono;
512+ auto now = system_clock::now ();
513+ return DependencyCheckedTimestamp + seconds (DependencyCheckIntervalSecond) < now;
514+ }
515+
516+ void CompletionInstance::setDependencyCheckIntervalSecond (unsigned Value) {
517+ std::lock_guard<std::mutex> lock (mtx);
518+ DependencyCheckIntervalSecond = Value;
519+ }
520+
419521bool swift::ide::CompletionInstance::performOperation (
420522 swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
421523 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
@@ -451,8 +553,9 @@ bool swift::ide::CompletionInstance::performOperation(
451553 // the cached completion instance.
452554 std::lock_guard<std::mutex> lock (mtx);
453555
454- if (performCachedOperationIfPossible (Invocation, ArgsHash, completionBuffer,
455- Offset, DiagC, Callback))
556+ if (performCachedOperationIfPossible (Invocation, ArgsHash, FileSystem,
557+ completionBuffer, Offset, DiagC,
558+ Callback))
456559 return true ;
457560
458561 if (performNewOperation (ArgsHash, Invocation, FileSystem, completionBuffer,
0 commit comments