diff --git a/include/swift/Frontend/Frontend.h b/include/swift/Frontend/Frontend.h index d172f66a894..f9aacf6251a 100644 --- a/include/swift/Frontend/Frontend.h +++ b/include/swift/Frontend/Frontend.h @@ -653,6 +653,9 @@ public: // for static functions in Frontend.cpp explicit ImplicitImports(CompilerInstance &compiler); }; + static void addAdditionalInitialImportsTo( + SourceFile *SF, const ImplicitImports &implicitImports); + private: void addMainFileToModule(const ImplicitImports &implicitImports); diff --git a/lib/Frontend/Frontend.cpp b/lib/Frontend/Frontend.cpp index 6504dda3246..2d4dc5607f2 100644 --- a/lib/Frontend/Frontend.cpp +++ b/lib/Frontend/Frontend.cpp @@ -642,7 +642,7 @@ ModuleDecl *CompilerInstance::getMainModule() const { return MainModule; } -static void addAdditionalInitialImportsTo( +void CompilerInstance::addAdditionalInitialImportsTo( SourceFile *SF, const CompilerInstance::ImplicitImports &implicitImports) { SmallVector additionalImports; diff --git a/lib/IDE/CompletionInstance.cpp b/lib/IDE/CompletionInstance.cpp index acfa3d6ae98..91563214e4f 100644 --- a/lib/IDE/CompletionInstance.cpp +++ b/lib/IDE/CompletionInstance.cpp @@ -189,10 +189,7 @@ bool CompletionInstance::performCachedOperaitonIfPossible( return false; auto &oldInfo = oldState.getCodeCompletionDelayedDeclState(); - - // Currently, only completions within a function body is supported. - if (oldInfo.Kind != CodeCompletionDelayedDeclKind::FunctionBody) - return false; + auto *oldSF = oldInfo.ParentContext->getParentSourceFile(); // Parse the new buffer into temporary SourceFile. SourceManager tmpSM; @@ -204,94 +201,141 @@ bool CompletionInstance::performCachedOperaitonIfPossible( TypeCheckerOptions typeckOpts; SearchPathOptions searchPathOpts; DiagnosticEngine tmpDiags(tmpSM); - std::unique_ptr Ctx( + std::unique_ptr tmpCtx( ASTContext::get(langOpts, typeckOpts, searchPathOpts, tmpSM, tmpDiags)); - registerIDERequestFunctions(Ctx->evaluator); - registerTypeCheckerRequestFunctions(Ctx->evaluator); - registerSILGenRequestFunctions(Ctx->evaluator); - ModuleDecl *M = ModuleDecl::create(Identifier(), *Ctx); + registerIDERequestFunctions(tmpCtx->evaluator); + registerTypeCheckerRequestFunctions(tmpCtx->evaluator); + registerSILGenRequestFunctions(tmpCtx->evaluator); + ModuleDecl *tmpM = ModuleDecl::create(Identifier(), *tmpCtx); PersistentParserState newState; - SourceFile *newSF = - new (*Ctx) SourceFile(*M, SourceFileKind::Library, tmpBufferID, - SourceFile::ImplicitModuleImportKind::None); - newSF->enableInterfaceHash(); + SourceFile *tmpSF = + new (*tmpCtx) SourceFile(*tmpM, oldSF->Kind, tmpBufferID, + SourceFile::ImplicitModuleImportKind::None); + tmpSF->enableInterfaceHash(); // Ensure all non-function-body tokens are hashed into the interface hash - Ctx->LangOpts.EnableTypeFingerprints = false; - parseIntoSourceFile(*newSF, tmpBufferID, &newState); + tmpCtx->LangOpts.EnableTypeFingerprints = false; + parseIntoSourceFile(*tmpSF, tmpBufferID, &newState); // Couldn't find any completion token? if (!newState.hasCodeCompletionDelayedDeclState()) return false; auto &newInfo = newState.getCodeCompletionDelayedDeclState(); + unsigned newBufferID; - // The new completion must happens in function body too. - if (newInfo.Kind != CodeCompletionDelayedDeclKind::FunctionBody) - return false; + switch (newInfo.Kind) { + case CodeCompletionDelayedDeclKind::FunctionBody: { + // If the interface has changed, AST must be refreshed. + llvm::SmallString<32> oldInterfaceHash{}; + llvm::SmallString<32> newInterfaceHash{}; + oldSF->getInterfaceHash(oldInterfaceHash); + tmpSF->getInterfaceHash(newInterfaceHash); + if (oldInterfaceHash != newInterfaceHash) + return false; - auto *oldSF = oldInfo.ParentContext->getParentSourceFile(); + DeclContext *DC = + getEquivalentDeclContextFromSourceFile(newInfo.ParentContext, oldSF); + if (!DC) + return false; - // If the interface has changed, AST must be refreshed. - llvm::SmallString<32> oldInterfaceHash{}; - llvm::SmallString<32> newInterfaceHash{}; - oldSF->getInterfaceHash(oldInterfaceHash); - newSF->getInterfaceHash(newInterfaceHash); - if (oldInterfaceHash != newInterfaceHash) - return false; + // OK, we can perform fast completion for this. Update the orignal delayed + // decl state. - DeclContext *DC = - getEquivalentDeclContextFromSourceFile(newInfo.ParentContext, oldSF); - if (!DC) - return false; + // Fast completion keeps the buffer in memory for multiple completions. + // To reduce the consumption, slice the source buffer so it only holds + // the portion that is needed for the second pass. + auto startOffset = newInfo.StartOffset; + if (newInfo.PrevOffset != ~0u) + startOffset = newInfo.PrevOffset; + auto startLoc = tmpSM.getLocForOffset(tmpBufferID, startOffset); + startLoc = Lexer::getLocForStartOfLine(tmpSM, startLoc); + startOffset = tmpSM.getLocOffsetInBuffer(startLoc, tmpBufferID); - // OK, we can perform fast completion for this. Update the orignal delayed - // decl state. + auto endOffset = newInfo.EndOffset; + auto endLoc = tmpSM.getLocForOffset(tmpBufferID, endOffset); + endLoc = Lexer::getLocForEndOfToken(tmpSM, endLoc); + endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID); - // Fast completion keeps the buffer in memory for multiple completions. - // To reduce the consumption, slice the source buffer so it only holds - // the portion that is needed for the second pass. - auto startOffset = newInfo.StartOffset; - if (newInfo.PrevOffset != ~0u) - startOffset = newInfo.PrevOffset; - auto startLoc = tmpSM.getLocForOffset(tmpBufferID, startOffset); - startLoc = Lexer::getLocForStartOfLine(tmpSM, startLoc); - startOffset = tmpSM.getLocOffsetInBuffer(startLoc, tmpBufferID); + newInfo.StartOffset -= startOffset; + newInfo.EndOffset -= startOffset; + if (newInfo.PrevOffset != ~0u) + newInfo.PrevOffset -= startOffset; - auto endOffset = newInfo.EndOffset; - auto endLoc = tmpSM.getLocForOffset(tmpBufferID, endOffset); - endLoc = Lexer::getLocForEndOfToken(tmpSM, endLoc); - endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID); + auto sourceText = + completionBuffer->getBuffer().slice(startOffset, endOffset); + auto newOffset = Offset - startOffset; - newInfo.StartOffset -= startOffset; - newInfo.EndOffset -= startOffset; - if (newInfo.PrevOffset != ~0u) - newInfo.PrevOffset -= startOffset; + newBufferID = SM.addMemBufferCopy(sourceText, + completionBuffer->getBufferIdentifier()); + SM.openVirtualFile(SM.getLocForBufferStart(newBufferID), + tmpSM.getDisplayNameForLoc(startLoc), + tmpSM.getLineAndColumn(startLoc).first - 1); + SM.setCodeCompletionPoint(newBufferID, newOffset); - auto sourceText = completionBuffer->getBuffer().slice(startOffset, endOffset); - auto newOffset = Offset - startOffset; + // Construct dummy scopes. We don't need to restore the original scope + // because they are probably not 'isResolvable()' anyway. + auto &SI = oldState.getScopeInfo(); + assert(SI.getCurrentScope() == nullptr); + Scope Top(SI, ScopeKind::TopLevel); + Scope Body(SI, ScopeKind::FunctionBody); - auto newBufferID = - SM.addMemBufferCopy(sourceText, completionBuffer->getBufferIdentifier()); - SM.openVirtualFile(SM.getLocForBufferStart(newBufferID), - tmpSM.getDisplayNameForLoc(startLoc), - tmpSM.getLineAndColumn(startLoc).first - 1); - SM.setCodeCompletionPoint(newBufferID, newOffset); + oldInfo.ParentContext = DC; + oldInfo.StartOffset = newInfo.StartOffset; + oldInfo.EndOffset = newInfo.EndOffset; + oldInfo.PrevOffset = newInfo.PrevOffset; + oldState.restoreCodeCompletionDelayedDeclState(oldInfo); - // Construct dummy scopes. We don't need to restore the original scope - // because they are probably not 'isResolvable()' anyway. - auto &SI = oldState.getScopeInfo(); - assert(SI.getCurrentScope() == nullptr); - Scope Top(SI, ScopeKind::TopLevel); - Scope Body(SI, ScopeKind::FunctionBody); + auto *AFD = cast(DC); + if (AFD->isBodySkipped()) + AFD->setBodyDelayed(AFD->getBodySourceRange()); - oldInfo.ParentContext = DC; - oldInfo.StartOffset = newInfo.StartOffset; - oldInfo.EndOffset = newInfo.EndOffset; - oldInfo.PrevOffset = newInfo.PrevOffset; - oldState.restoreCodeCompletionDelayedDeclState(oldInfo); + break; + } + case CodeCompletionDelayedDeclKind::Decl: + case CodeCompletionDelayedDeclKind::TopLevelCodeDecl: { + // Support decl/top-level code only if the completion happens in a single + // file 'main' script (e.g. playground). + auto *oldM = oldInfo.ParentContext->getParentModule(); + if (oldM->getFiles().size() != 1 || oldSF->Kind != SourceFileKind::Main) + return false; + + // Perform fast completion. + + // Prepare the new buffer in the source manager. + auto sourceText = completionBuffer->getBuffer(); + if (newInfo.Kind == CodeCompletionDelayedDeclKind::TopLevelCodeDecl) { + // We don't need the source text after the top-level code. + auto endOffset = newInfo.EndOffset; + auto endLoc = tmpSM.getLocForOffset(tmpBufferID, endOffset); + endLoc = Lexer::getLocForEndOfToken(tmpSM, endLoc); + endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID); + sourceText = sourceText.slice(0, endOffset); + } + newBufferID = SM.addMemBufferCopy(sourceText, + completionBuffer->getBufferIdentifier()); + SM.setCodeCompletionPoint(newBufferID, Offset); + + // Create a new module and a source file using the current AST context. + auto &Ctx = oldM->getASTContext(); + auto newM = ModuleDecl::create(oldM->getName(), Ctx); + CompilerInstance::ImplicitImports implicitImport(CI); + SourceFile *newSF = new (Ctx) SourceFile(*newM, SourceFileKind::Main, + newBufferID, implicitImport.kind); + newM->addFile(*newSF); + CompilerInstance::addAdditionalInitialImportsTo(newSF, implicitImport); + newSF->enableInterfaceHash(); + + // Re-parse the whole file. Still re-use imported modules. + (void)oldState.takeCodeCompletionDelayedDeclState(); + parseIntoSourceFile(*newSF, newBufferID, &oldState); + performNameBinding(*newSF); + bindExtensions(*newSF); + + assert(oldState.hasCodeCompletionDelayedDeclState() && + oldState.getCodeCompletionDelayedDeclState().Kind == newInfo.Kind); + break; + } + } - auto *AFD = cast(DC); - if (AFD->isBodySkipped()) - AFD->setBodyDelayed(AFD->getBodySourceRange()); if (DiagC) CI.addDiagnosticConsumer(DiagC); @@ -366,6 +410,9 @@ bool swift::ide::CompletionInstance::performOperation( // weaken that hash, disable them here: Invocation.getLangOptions().EnableTypeFingerprints = false; + // We don't need token list. + Invocation.getLangOptions().CollectParsedToken = false; + // FIXME: ASTScopeLookup doesn't support code completion yet. Invocation.disableASTScopeLookup(); diff --git a/test/SourceKit/CodeComplete/complete_sequence_accessor.swift b/test/SourceKit/CodeComplete/complete_sequence_accessor.swift index fc6f0417a0c..4d7da25fc43 100644 --- a/test/SourceKit/CodeComplete/complete_sequence_accessor.swift +++ b/test/SourceKit/CodeComplete/complete_sequence_accessor.swift @@ -39,18 +39,18 @@ enum S { // Enabled. // RUN: %sourcekitd-test \ // RUN: -req=track-compiles == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:9 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=15:15 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=16:15 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=23:9 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=26:15 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=27:15 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=30:9 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=33:15 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=34:15 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:1 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=23:1 %s -- %s == \ -// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=16:1 %s -- %s > %t.response +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:9 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=15:15 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=16:15 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=23:9 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=26:15 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=27:15 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=30:9 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=33:15 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=34:15 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:1 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=23:1 %s -- %s -parse-as-library == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=16:1 %s -- %s -parse-as-library > %t.response // RUN: %FileCheck --check-prefix=RESULT %s < %t.response // RUN: %FileCheck --check-prefix=TRACE %s < %t.response diff --git a/test/SourceKit/CodeComplete/complete_sequence_toplevel.swift b/test/SourceKit/CodeComplete/complete_sequence_toplevel.swift new file mode 100644 index 00000000000..aec7c0ca104 --- /dev/null +++ b/test/SourceKit/CodeComplete/complete_sequence_toplevel.swift @@ -0,0 +1,49 @@ +class Foo { + var x: Int + var y: Int + func fooMethod() {} +} +struct Bar { + var a: Int + var b: Int +} +extension Bar { + func barMethod() {} +} +func foo(arg: Foo) { + _ = arg. +} +_ = Bar(a: 12, b: 42) + +// Enabled. +// RUN: %sourcekitd-test \ +// RUN: -req=track-compiles == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=14:11 %s -- %s == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=13:15 %s -- %s == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=16:22 %s -- %s > %t.response +// RUN: %FileCheck --check-prefix=RESULT %s < %t.response +// RUN: %FileCheck --check-prefix=TRACE %s < %t.response + +// RESULT-LABEL: key.results: [ +// RESULT-DAG: key.name: "fooMethod()" +// RESULT-DAG: key.name: "self" +// RESULT-DAG: key.name: "x" +// RESULT-DAG: key.name: "y" +// RESULT: ] +// RESULT-LABEL: key.results: [ +// RESULT-DAG: key.name: "Foo" +// RESULT-DAG: key.name: "Bar" +// RESULT: ] +// RESULT-LABEL: key.results: [ +// RESULT-DAG: key.name: "barMethod()" +// RESULT-DAG: key.name: "self" +// RESULT-DAG: key.name: "a" +// RESULT-DAG: key.name: "b" +// RESULT: ] + +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE-NOT: key.description: "completion reusing previous ASTContext (benign diagnostic)" +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE: key.description: "completion reusing previous ASTContext (benign diagnostic)" +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE: key.description: "completion reusing previous ASTContext (benign diagnostic)" diff --git a/test/SourceKit/CodeComplete/complete_sequence_toplevel_edit.swift b/test/SourceKit/CodeComplete/complete_sequence_toplevel_edit.swift new file mode 100644 index 00000000000..a1ba4eef4ec --- /dev/null +++ b/test/SourceKit/CodeComplete/complete_sequence_toplevel_edit.swift @@ -0,0 +1,100 @@ +// BEGIN State1.swift +struct Foo { + var x: Int + var y: Int + func fooMethod() {} +} + +let globalFoo = Foo( + +// BEGIN State2.swift +struct Foo { + var x: Int + var y: Int + func fooMethod() {} +} + +let globalFoo = Foo(x: 12, y: 42) + +var globalX = globalFoo. + +// BEGIN State3.swift +struct Foo { + var x: Int + var y: Int + var z: Int + func fooMethod() {} +} + +let globalFoo = Foo(x: 12, y: 42, z: 68) + +guard let val = globalFoo. + +func dummy(x: Int) { + print() +} + +// BEGIN State4.swift +struct Foo { + var x: Int + var y: Int + var z: Int + func fooMethod() {} +} + +let globalFoo = Foo(x: 12, y: 42, z: 68) + +func dummy(x: ) { + print() +} + +// BEGIN DUMMY.swift + +// RUN: %empty-directory(%t) +// RUN: %{python} %utils/split_file.py -o %t %s + +// RUN: %sourcekitd-test \ +// RUN: -req=track-compiles == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=7:21 -name file.swift -text-input %t/State1.swift -- file.swift == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=9:25 -name file.swift -text-input %t/State2.swift -- file.swift == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=10:27 -name file.swift -text-input %t/State3.swift -- file.swift == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=10:15 -name file.swift -text-input %t/State4.swift -- file.swift > %t.response +// RUN: %FileCheck --check-prefix=RESULT %s < %t.response +// RUN: %FileCheck --check-prefix=TRACE %s < %t.response + +// RESULT-LABEL: key.results: [ +// RESULT-NOT: key.description: "fooMethod()" +// RESULT-NOT: key.description: "x" +// RESULT-NOT: key.description: "y" +// RESULT-DAG: key.description: "(x: Int, y: Int)", +// RESULT: ] + +// RESULT-LABEL: key.results: [ +// RESULT-NOT: key.description: "z" +// RESULT-DAG: key.description: "fooMethod()" +// RESULT-DAG: key.description: "self" +// RESULT-DAG: key.description: "x" +// RESULT-DAG: key.description: "y" +// RESULT: ] + +// RESULT-LABEL: key.results: [ +// RESULT-DAG: key.description: "fooMethod()" +// RESULT-DAG: key.description: "self" +// RESULT-DAG: key.description: "x" +// RESULT-DAG: key.description: "y" +// RESULT-DAG: key.description: "z" +// RESULT: ] + +// RESULT-LABEL: key.results: [ +// RESULT-NOT: key.description: "globalFoo" +// RESULT-DAG: key.description: "Foo" +// RESULT: ] + +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE-NOT: key.description: "completion reusing previous ASTContext (benign diagnostic)" +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE: key.description: "completion reusing previous ASTContext (benign diagnostic)" +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE: key.description: "completion reusing previous ASTContext (benign diagnostic)" +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE: key.description: "completion reusing previous ASTContext (benign diagnostic)" diff --git a/test/SourceKit/CodeComplete/complete_sequence_toplevel_edit_import.swift b/test/SourceKit/CodeComplete/complete_sequence_toplevel_edit_import.swift new file mode 100644 index 00000000000..5aeb8c6aef8 --- /dev/null +++ b/test/SourceKit/CodeComplete/complete_sequence_toplevel_edit_import.swift @@ -0,0 +1,64 @@ +// BEGIN State1.swift +import Foo + + +// BEGIN State2.swift +import Foo +import Bar + +// BEGIN State3.swift +import Bar + + +// BEGIN State4.swift + + + +// BEGIN DUMMY.swift + +// REQUIRES: objc_interop + +// RUN: %empty-directory(%t) +// RUN: %{python} %utils/split_file.py -o %t %s + +// RUN: %sourcekitd-test \ +// RUN: -req=track-compiles == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=3:1 -name main.swift -text-input %t/State1.swift -- main.swift -F %S/../Inputs/libIDE-mock-sdk == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=3:1 -name main.swift -text-input %t/State2.swift -- main.swift -F %S/../Inputs/libIDE-mock-sdk == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=3:1 -name main.swift -text-input %t/State3.swift -- main.swift -F %S/../Inputs/libIDE-mock-sdk == \ +// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=3:1 -name main.swift -text-input %t/State4.swift -- main.swift -F %S/../Inputs/libIDE-mock-sdk > %t.response +// RUN: %FileCheck --check-prefix=RESULT %s < %t.response +// RUN: %FileCheck --check-prefix=TRACE %s < %t.response + +// RESULT-LABEL: key.results: [ +// RESULT-NOT: key.description: "Bar", +// RESULT-DAG: key.description: "Foo", +// RESULT-DAG: key.description: "FooHelper", +// RESULT: ] + +// RESULT-LABEL: key.results: [ +// RESULT-DAG: key.description: "Foo", +// RESULT-DAG: key.description: "FooHelper", +// RESULT-DAG: key.description: "Bar", +// RESULT: ] + +// RESULT-LABEL: key.results: [ +// RESULT-NOT: key.description: "Foo", +// RESULT-NOT: key.description: "FooHelper", +// RESULT-DAG: key.description: "Bar", +// RESULT: ] + +// RESULT-LABEL: key.results: [ +// RESULT-NOT: key.description: "Foo", +// RESULT-NOT: key.description: "FooHelper", +// RESULT-NOT: key.description: "Bar", +// RESULT: ] + +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE-NOT: key.description: "completion reusing previous ASTContext (benign diagnostic)" +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE: key.description: "completion reusing previous ASTContext (benign diagnostic)" +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE: key.description: "completion reusing previous ASTContext (benign diagnostic)" +// TRACE-LABEL: key.notification: source.notification.compile-did-finish, +// TRACE: key.description: "completion reusing previous ASTContext (benign diagnostic)"