Skip to content

Commit 0a0cde9

Browse files
committed
[CodeCompletion] Fast completion for top-level code in single file script
e.g. Playground. A single file script is like a single function body; the interface of the file does not affect any other files. So when a completion happens in a single file script, re-parse the whole file. But we are still be able to reuse imported modules. rdar://problem/58378157
1 parent e35ae37 commit 0a0cde9

File tree

7 files changed

+359
-96
lines changed

7 files changed

+359
-96
lines changed

include/swift/Frontend/Frontend.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,9 @@ class CompilerInstance {
653653
explicit ImplicitImports(CompilerInstance &compiler);
654654
};
655655

656+
static void addAdditionalInitialImportsTo(
657+
SourceFile *SF, const ImplicitImports &implicitImports);
658+
656659
private:
657660
void addMainFileToModule(const ImplicitImports &implicitImports);
658661

lib/Frontend/Frontend.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ ModuleDecl *CompilerInstance::getMainModule() const {
640640
return MainModule;
641641
}
642642

643-
static void addAdditionalInitialImportsTo(
643+
void CompilerInstance::addAdditionalInitialImportsTo(
644644
SourceFile *SF, const CompilerInstance::ImplicitImports &implicitImports) {
645645
SmallVector<SourceFile::ImportedModuleDesc, 4> additionalImports;
646646

lib/IDE/CompletionInstance.cpp

Lines changed: 130 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,7 @@ bool CompletionInstance::performCachedOperaitonIfPossible(
189189
return false;
190190

191191
auto &oldInfo = oldState.getCodeCompletionDelayedDeclState();
192-
193-
// Currently, only completions within a function body is supported.
194-
if (oldInfo.Kind != CodeCompletionDelayedDeclKind::FunctionBody)
195-
return false;
192+
auto *oldSF = oldInfo.ParentContext->getParentSourceFile();
196193

197194
// Parse the new buffer into temporary SourceFile.
198195
SourceManager tmpSM;
@@ -204,94 +201,141 @@ bool CompletionInstance::performCachedOperaitonIfPossible(
204201
TypeCheckerOptions typeckOpts;
205202
SearchPathOptions searchPathOpts;
206203
DiagnosticEngine tmpDiags(tmpSM);
207-
std::unique_ptr<ASTContext> Ctx(
204+
std::unique_ptr<ASTContext> tmpCtx(
208205
ASTContext::get(langOpts, typeckOpts, searchPathOpts, tmpSM, tmpDiags));
209-
registerIDERequestFunctions(Ctx->evaluator);
210-
registerTypeCheckerRequestFunctions(Ctx->evaluator);
211-
registerSILGenRequestFunctions(Ctx->evaluator);
212-
ModuleDecl *M = ModuleDecl::create(Identifier(), *Ctx);
206+
registerIDERequestFunctions(tmpCtx->evaluator);
207+
registerTypeCheckerRequestFunctions(tmpCtx->evaluator);
208+
registerSILGenRequestFunctions(tmpCtx->evaluator);
209+
ModuleDecl *tmpM = ModuleDecl::create(Identifier(), *tmpCtx);
213210
PersistentParserState newState;
214-
SourceFile *newSF =
215-
new (*Ctx) SourceFile(*M, SourceFileKind::Library, tmpBufferID,
216-
SourceFile::ImplicitModuleImportKind::None);
217-
newSF->enableInterfaceHash();
211+
SourceFile *tmpSF =
212+
new (*tmpCtx) SourceFile(*tmpM, oldSF->Kind, tmpBufferID,
213+
SourceFile::ImplicitModuleImportKind::None);
214+
tmpSF->enableInterfaceHash();
218215
// Ensure all non-function-body tokens are hashed into the interface hash
219-
Ctx->LangOpts.EnableTypeFingerprints = false;
220-
parseIntoSourceFile(*newSF, tmpBufferID, &newState);
216+
tmpCtx->LangOpts.EnableTypeFingerprints = false;
217+
parseIntoSourceFile(*tmpSF, tmpBufferID, &newState);
221218
// Couldn't find any completion token?
222219
if (!newState.hasCodeCompletionDelayedDeclState())
223220
return false;
224221

225222
auto &newInfo = newState.getCodeCompletionDelayedDeclState();
223+
unsigned newBufferID;
224+
225+
switch (newInfo.Kind) {
226+
case CodeCompletionDelayedDeclKind::FunctionBody: {
227+
// If the interface has changed, AST must be refreshed.
228+
llvm::SmallString<32> oldInterfaceHash{};
229+
llvm::SmallString<32> newInterfaceHash{};
230+
oldSF->getInterfaceHash(oldInterfaceHash);
231+
tmpSF->getInterfaceHash(newInterfaceHash);
232+
if (oldInterfaceHash != newInterfaceHash)
233+
return false;
234+
235+
DeclContext *DC =
236+
getEquivalentDeclContextFromSourceFile(newInfo.ParentContext, oldSF);
237+
if (!DC)
238+
return false;
239+
240+
// OK, we can perform fast completion for this. Update the orignal delayed
241+
// decl state.
242+
243+
// Fast completion keeps the buffer in memory for multiple completions.
244+
// To reduce the consumption, slice the source buffer so it only holds
245+
// the portion that is needed for the second pass.
246+
auto startOffset = newInfo.StartOffset;
247+
if (newInfo.PrevOffset != ~0u)
248+
startOffset = newInfo.PrevOffset;
249+
auto startLoc = tmpSM.getLocForOffset(tmpBufferID, startOffset);
250+
startLoc = Lexer::getLocForStartOfLine(tmpSM, startLoc);
251+
startOffset = tmpSM.getLocOffsetInBuffer(startLoc, tmpBufferID);
252+
253+
auto endOffset = newInfo.EndOffset;
254+
auto endLoc = tmpSM.getLocForOffset(tmpBufferID, endOffset);
255+
endLoc = Lexer::getLocForEndOfToken(tmpSM, endLoc);
256+
endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID);
257+
258+
newInfo.StartOffset -= startOffset;
259+
newInfo.EndOffset -= startOffset;
260+
if (newInfo.PrevOffset != ~0u)
261+
newInfo.PrevOffset -= startOffset;
262+
263+
auto sourceText =
264+
completionBuffer->getBuffer().slice(startOffset, endOffset);
265+
auto newOffset = Offset - startOffset;
266+
267+
newBufferID = SM.addMemBufferCopy(sourceText,
268+
completionBuffer->getBufferIdentifier());
269+
SM.openVirtualFile(SM.getLocForBufferStart(newBufferID),
270+
tmpSM.getDisplayNameForLoc(startLoc),
271+
tmpSM.getLineAndColumn(startLoc).first - 1);
272+
SM.setCodeCompletionPoint(newBufferID, newOffset);
273+
274+
// Construct dummy scopes. We don't need to restore the original scope
275+
// because they are probably not 'isResolvable()' anyway.
276+
auto &SI = oldState.getScopeInfo();
277+
assert(SI.getCurrentScope() == nullptr);
278+
Scope Top(SI, ScopeKind::TopLevel);
279+
Scope Body(SI, ScopeKind::FunctionBody);
280+
281+
oldInfo.ParentContext = DC;
282+
oldInfo.StartOffset = newInfo.StartOffset;
283+
oldInfo.EndOffset = newInfo.EndOffset;
284+
oldInfo.PrevOffset = newInfo.PrevOffset;
285+
oldState.restoreCodeCompletionDelayedDeclState(oldInfo);
286+
287+
auto *AFD = cast<AbstractFunctionDecl>(DC);
288+
if (AFD->isBodySkipped())
289+
AFD->setBodyDelayed(AFD->getBodySourceRange());
290+
291+
break;
292+
}
293+
case CodeCompletionDelayedDeclKind::Decl:
294+
case CodeCompletionDelayedDeclKind::TopLevelCodeDecl: {
295+
// Support decl/top-level code only if the completion happens in a single
296+
// file 'main' script (e.g. playground).
297+
auto *oldM = oldInfo.ParentContext->getParentModule();
298+
if (oldM->getFiles().size() != 1 || oldSF->Kind != SourceFileKind::Main)
299+
return false;
300+
301+
// Perform fast completion.
302+
303+
// Prepare the new buffer in the source manager.
304+
auto sourceText = completionBuffer->getBuffer();
305+
if (newInfo.Kind == CodeCompletionDelayedDeclKind::TopLevelCodeDecl) {
306+
// We don't need the source text after the top-level code.
307+
auto endOffset = newInfo.EndOffset;
308+
auto endLoc = tmpSM.getLocForOffset(tmpBufferID, endOffset);
309+
endLoc = Lexer::getLocForEndOfToken(tmpSM, endLoc);
310+
endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID);
311+
sourceText = sourceText.slice(0, endOffset);
312+
}
313+
newBufferID = SM.addMemBufferCopy(sourceText,
314+
completionBuffer->getBufferIdentifier());
315+
SM.setCodeCompletionPoint(newBufferID, Offset);
316+
317+
// Create a new module and a source file using the current AST context.
318+
auto &Ctx = oldM->getASTContext();
319+
auto newM = ModuleDecl::create(oldM->getName(), Ctx);
320+
CompilerInstance::ImplicitImports implicitImport(CI);
321+
SourceFile *newSF = new (Ctx) SourceFile(*newM, SourceFileKind::Main,
322+
newBufferID, implicitImport.kind);
323+
newM->addFile(*newSF);
324+
CompilerInstance::addAdditionalInitialImportsTo(newSF, implicitImport);
325+
newSF->enableInterfaceHash();
326+
327+
// Re-parse the whole file. Still re-use imported modules.
328+
(void)oldState.takeCodeCompletionDelayedDeclState();
329+
parseIntoSourceFile(*newSF, newBufferID, &oldState);
330+
performNameBinding(*newSF);
331+
bindExtensions(*newSF);
332+
333+
assert(oldState.hasCodeCompletionDelayedDeclState() &&
334+
oldState.getCodeCompletionDelayedDeclState().Kind == newInfo.Kind);
335+
break;
336+
}
337+
}
226338

227-
// The new completion must happens in function body too.
228-
if (newInfo.Kind != CodeCompletionDelayedDeclKind::FunctionBody)
229-
return false;
230-
231-
auto *oldSF = oldInfo.ParentContext->getParentSourceFile();
232-
233-
// If the interface has changed, AST must be refreshed.
234-
llvm::SmallString<32> oldInterfaceHash{};
235-
llvm::SmallString<32> newInterfaceHash{};
236-
oldSF->getInterfaceHash(oldInterfaceHash);
237-
newSF->getInterfaceHash(newInterfaceHash);
238-
if (oldInterfaceHash != newInterfaceHash)
239-
return false;
240-
241-
DeclContext *DC =
242-
getEquivalentDeclContextFromSourceFile(newInfo.ParentContext, oldSF);
243-
if (!DC)
244-
return false;
245-
246-
// OK, we can perform fast completion for this. Update the orignal delayed
247-
// decl state.
248-
249-
// Fast completion keeps the buffer in memory for multiple completions.
250-
// To reduce the consumption, slice the source buffer so it only holds
251-
// the portion that is needed for the second pass.
252-
auto startOffset = newInfo.StartOffset;
253-
if (newInfo.PrevOffset != ~0u)
254-
startOffset = newInfo.PrevOffset;
255-
auto startLoc = tmpSM.getLocForOffset(tmpBufferID, startOffset);
256-
startLoc = Lexer::getLocForStartOfLine(tmpSM, startLoc);
257-
startOffset = tmpSM.getLocOffsetInBuffer(startLoc, tmpBufferID);
258-
259-
auto endOffset = newInfo.EndOffset;
260-
auto endLoc = tmpSM.getLocForOffset(tmpBufferID, endOffset);
261-
endLoc = Lexer::getLocForEndOfToken(tmpSM, endLoc);
262-
endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID);
263-
264-
newInfo.StartOffset -= startOffset;
265-
newInfo.EndOffset -= startOffset;
266-
if (newInfo.PrevOffset != ~0u)
267-
newInfo.PrevOffset -= startOffset;
268-
269-
auto sourceText = completionBuffer->getBuffer().slice(startOffset, endOffset);
270-
auto newOffset = Offset - startOffset;
271-
272-
auto newBufferID =
273-
SM.addMemBufferCopy(sourceText, completionBuffer->getBufferIdentifier());
274-
SM.openVirtualFile(SM.getLocForBufferStart(newBufferID),
275-
tmpSM.getDisplayNameForLoc(startLoc),
276-
tmpSM.getLineAndColumn(startLoc).first - 1);
277-
SM.setCodeCompletionPoint(newBufferID, newOffset);
278-
279-
// Construct dummy scopes. We don't need to restore the original scope
280-
// because they are probably not 'isResolvable()' anyway.
281-
auto &SI = oldState.getScopeInfo();
282-
assert(SI.getCurrentScope() == nullptr);
283-
Scope Top(SI, ScopeKind::TopLevel);
284-
Scope Body(SI, ScopeKind::FunctionBody);
285-
286-
oldInfo.ParentContext = DC;
287-
oldInfo.StartOffset = newInfo.StartOffset;
288-
oldInfo.EndOffset = newInfo.EndOffset;
289-
oldInfo.PrevOffset = newInfo.PrevOffset;
290-
oldState.restoreCodeCompletionDelayedDeclState(oldInfo);
291-
292-
auto *AFD = cast<AbstractFunctionDecl>(DC);
293-
if (AFD->isBodySkipped())
294-
AFD->setBodyDelayed(AFD->getBodySourceRange());
295339
if (DiagC)
296340
CI.addDiagnosticConsumer(DiagC);
297341

@@ -366,6 +410,9 @@ bool swift::ide::CompletionInstance::performOperation(
366410
// weaken that hash, disable them here:
367411
Invocation.getLangOptions().EnableTypeFingerprints = false;
368412

413+
// We don't need token list.
414+
Invocation.getLangOptions().CollectParsedToken = false;
415+
369416
// FIXME: ASTScopeLookup doesn't support code completion yet.
370417
Invocation.disableASTScopeLookup();
371418

test/SourceKit/CodeComplete/complete_sequence_accessor.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,18 @@ enum S {
3939
// Enabled.
4040
// RUN: %sourcekitd-test \
4141
// RUN: -req=track-compiles == \
42-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:9 %s -- %s == \
43-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=15:15 %s -- %s == \
44-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=16:15 %s -- %s == \
45-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=23:9 %s -- %s == \
46-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=26:15 %s -- %s == \
47-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=27:15 %s -- %s == \
48-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=30:9 %s -- %s == \
49-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=33:15 %s -- %s == \
50-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=34:15 %s -- %s == \
51-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:1 %s -- %s == \
52-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=23:1 %s -- %s == \
53-
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=16:1 %s -- %s > %t.response
42+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:9 %s -- %s -parse-as-library == \
43+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=15:15 %s -- %s -parse-as-library == \
44+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=16:15 %s -- %s -parse-as-library == \
45+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=23:9 %s -- %s -parse-as-library == \
46+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=26:15 %s -- %s -parse-as-library == \
47+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=27:15 %s -- %s -parse-as-library == \
48+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=30:9 %s -- %s -parse-as-library == \
49+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=33:15 %s -- %s -parse-as-library == \
50+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=34:15 %s -- %s -parse-as-library == \
51+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:1 %s -- %s -parse-as-library == \
52+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=23:1 %s -- %s -parse-as-library == \
53+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=16:1 %s -- %s -parse-as-library > %t.response
5454
// RUN: %FileCheck --check-prefix=RESULT %s < %t.response
5555
// RUN: %FileCheck --check-prefix=TRACE %s < %t.response
5656

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
class Foo {
2+
var x: Int
3+
var y: Int
4+
func fooMethod() {}
5+
}
6+
struct Bar {
7+
var a: Int
8+
var b: Int
9+
}
10+
extension Bar {
11+
func barMethod() {}
12+
}
13+
func foo(arg: Foo) {
14+
_ = arg.
15+
}
16+
_ = Bar(a: 12, b: 42)
17+
18+
// Enabled.
19+
// RUN: %sourcekitd-test \
20+
// RUN: -req=track-compiles == \
21+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=14:11 %s -- %s == \
22+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=13:15 %s -- %s == \
23+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=16:22 %s -- %s > %t.response
24+
// RUN: %FileCheck --check-prefix=RESULT %s < %t.response
25+
// RUN: %FileCheck --check-prefix=TRACE %s < %t.response
26+
27+
// RESULT-LABEL: key.results: [
28+
// RESULT-DAG: key.name: "fooMethod()"
29+
// RESULT-DAG: key.name: "self"
30+
// RESULT-DAG: key.name: "x"
31+
// RESULT-DAG: key.name: "y"
32+
// RESULT: ]
33+
// RESULT-LABEL: key.results: [
34+
// RESULT-DAG: key.name: "Foo"
35+
// RESULT-DAG: key.name: "Bar"
36+
// RESULT: ]
37+
// RESULT-LABEL: key.results: [
38+
// RESULT-DAG: key.name: "barMethod()"
39+
// RESULT-DAG: key.name: "self"
40+
// RESULT-DAG: key.name: "a"
41+
// RESULT-DAG: key.name: "b"
42+
// RESULT: ]
43+
44+
// TRACE-LABEL: key.notification: source.notification.compile-did-finish,
45+
// TRACE-NOT: key.description: "completion reusing previous ASTContext (benign diagnostic)"
46+
// TRACE-LABEL: key.notification: source.notification.compile-did-finish,
47+
// TRACE: key.description: "completion reusing previous ASTContext (benign diagnostic)"
48+
// TRACE-LABEL: key.notification: source.notification.compile-did-finish,
49+
// TRACE: key.description: "completion reusing previous ASTContext (benign diagnostic)"

0 commit comments

Comments
 (0)