Skip to content

Commit a8dd681

Browse files
committed
[IDE] Implement completion-like cursor info for ValueDecls
This brings up the ability to compute cursor info results using the completion-like type checking paradigm, which an reuse ASTContexts and doesn’t need to type check the entire file. For now, the new implementation only supports cursor info on `ValueDecl`s (not on references) because they were easiest to implement. More cursor info kinds are coming soon. At the moment, we only run the new implementation in a verification mode: It is only invoked in assert toolchains and when run, we check that the results are equivalent to the old implementation. Once more cursor info kinds are implemented and if the SourceKit stress tester doesn’t find any verification issues, we can enable the new implementation, falling back to the old implementation if the new one didn’t produce any results.
1 parent 226c7c8 commit a8dd681

File tree

10 files changed

+593
-2
lines changed

10 files changed

+593
-2
lines changed

include/swift/IDE/CursorInfo.h

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//===--- CursorInfo.h --- ---------------------------------------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef SWIFT_IDE_CURSORINFO_H
14+
#define SWIFT_IDE_CURSORINFO_H
15+
16+
#include "swift/AST/Type.h"
17+
#include "swift/Basic/LLVM.h"
18+
#include "swift/IDE/Utils.h"
19+
20+
namespace swift {
21+
class CodeCompletionCallbacksFactory;
22+
23+
namespace ide {
24+
25+
/// An abstract base class for consumers of context info results.
26+
class CursorInfoConsumer {
27+
public:
28+
virtual ~CursorInfoConsumer() {}
29+
virtual void handleResults(const ResolvedCursorInfo &) = 0;
30+
};
31+
32+
/// Create a factory for code completion callbacks.
33+
CodeCompletionCallbacksFactory *
34+
makeCursorInfoCallbacksFactory(CursorInfoConsumer &Consumer,
35+
SourceLoc RequestedLoc);
36+
37+
} // namespace ide
38+
} // namespace swift
39+
40+
#endif // SWIFT_IDE_CURSORINFO_H

include/swift/IDETool/CompletionInstance.h

+17
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "swift/IDE/CodeCompletionResult.h"
2020
#include "swift/IDE/CodeCompletionResultSink.h"
2121
#include "swift/IDE/ConformingMethodList.h"
22+
#include "swift/IDE/CursorInfo.h"
2223
#include "swift/IDE/ImportDepth.h"
2324
#include "swift/IDE/SwiftCompletionInfo.h"
2425
#include "swift/IDE/TypeContextInfo.h"
@@ -78,6 +79,14 @@ struct ConformingMethodListResults {
7879
bool DidReuseAST;
7980
};
8081

82+
/// The results returned from \c CompletionInstance::conformingMethodList.
83+
struct CursorInfoResults {
84+
/// The actual results. If \c nullptr, no results were found.
85+
const ResolvedCursorInfo *Result;
86+
/// Whether an AST was reused for the completion.
87+
bool DidReuseAST;
88+
};
89+
8190
/// Manages \c CompilerInstance for completion like operations.
8291
class CompletionInstance {
8392
struct Options {
@@ -192,6 +201,14 @@ class CompletionInstance {
192201
std::shared_ptr<std::atomic<bool>> CancellationFlag,
193202
llvm::function_ref<void(CancellableResult<ConformingMethodListResults>)>
194203
Callback);
204+
205+
void cursorInfo(
206+
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
207+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
208+
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
209+
DiagnosticConsumer *DiagC,
210+
std::shared_ptr<std::atomic<bool>> CancellationFlag,
211+
llvm::function_ref<void(CancellableResult<CursorInfoResults>)> Callback);
195212
};
196213

197214
} // namespace ide

lib/IDE/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ add_swift_host_library(swiftIDE STATIC
1818
CompletionLookup.cpp
1919
CompletionOverrideLookup.cpp
2020
ConformingMethodList.cpp
21+
CursorInfo.cpp
2122
ExprCompletion.cpp
2223
ExprContextAnalysis.cpp
2324
Formatting.cpp

lib/IDE/CursorInfo.cpp

+278
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
//===--- CursorInfo.cpp ---------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "swift/IDE/CursorInfo.h"
14+
#include "ExprContextAnalysis.h"
15+
#include "swift/AST/ASTDemangler.h"
16+
#include "swift/AST/GenericEnvironment.h"
17+
#include "swift/AST/NameLookup.h"
18+
#include "swift/AST/USRGeneration.h"
19+
#include "swift/IDE/TypeCheckCompletionCallback.h"
20+
#include "swift/Parse/CodeCompletionCallbacks.h"
21+
#include "swift/Sema/ConstraintSystem.h"
22+
#include "swift/Sema/IDETypeChecking.h"
23+
#include "clang/AST/Attr.h"
24+
#include "clang/AST/Decl.h"
25+
#include "clang/Basic/Module.h"
26+
27+
using namespace swift;
28+
using namespace swift::constraints;
29+
using namespace ide;
30+
31+
namespace {
32+
33+
// MARK: - Utilities
34+
35+
void typeCheckDeclAndParentClosures(ValueDecl *VD) {
36+
// We need to type check any parent closures because their types are
37+
// encoded in the USR of ParentContexts in the cursor info response.
38+
auto DC = VD->getDeclContext();
39+
while (DC->getParent()) {
40+
if (auto Closure = dyn_cast<AbstractClosureExpr>(DC)) {
41+
if (Closure->getType().isNull()) {
42+
typeCheckASTNodeAtLoc(
43+
TypeCheckASTNodeAtLocContext::declContext(DC->getParent()),
44+
Closure->getLoc());
45+
}
46+
}
47+
DC = DC->getParent();
48+
}
49+
50+
typeCheckASTNodeAtLoc(
51+
TypeCheckASTNodeAtLocContext::declContext(VD->getDeclContext()),
52+
VD->getLoc());
53+
}
54+
55+
// MARK: - NodeFinderResults
56+
57+
enum class NodeFinderResultKind { Decl };
58+
59+
class NodeFinderResult {
60+
NodeFinderResultKind Kind;
61+
62+
protected:
63+
NodeFinderResult(NodeFinderResultKind Kind) : Kind(Kind) {}
64+
65+
public:
66+
NodeFinderResultKind getKind() const { return Kind; }
67+
};
68+
69+
class NodeFinderDeclResult : public NodeFinderResult {
70+
ValueDecl *ValueD;
71+
72+
public:
73+
NodeFinderDeclResult(ValueDecl *ValueD)
74+
: NodeFinderResult(NodeFinderResultKind::Decl), ValueD(ValueD) {}
75+
76+
ValueDecl *getDecl() const { return ValueD; }
77+
78+
static bool classof(const NodeFinderResult *Res) {
79+
return Res->getKind() == NodeFinderResultKind::Decl;
80+
}
81+
};
82+
83+
// MARK: - NodeFinder
84+
85+
/// Walks the AST, looking for a node at \c LocToResolve. While walking the
86+
/// AST, also gathers information about shorthand shadows.
87+
class NodeFinder : ASTWalker {
88+
SourceFile &SrcFile;
89+
SourceLoc LocToResolve;
90+
91+
/// As we are walking the tree, this variable is updated to the last seen
92+
/// DeclContext.
93+
SmallVector<DeclContext *> DeclContextStack;
94+
95+
/// The found node.
96+
std::unique_ptr<NodeFinderResult> Result;
97+
98+
/// If a decl shadows another decl using shorthand syntax (`[foo]` or
99+
/// `if let foo {`), this maps the re-declared variable to the one that is
100+
/// being shadowed.
101+
/// The transitive closure of shorthand shadowed decls should be reported as
102+
/// additional results in cursor info.
103+
llvm::DenseMap<ValueDecl *, ValueDecl *> ShorthandShadowedDecls;
104+
105+
public:
106+
NodeFinder(SourceFile &SrcFile, SourceLoc LocToResolve)
107+
: SrcFile(SrcFile), LocToResolve(LocToResolve),
108+
DeclContextStack({&SrcFile}) {}
109+
110+
void resolve() { SrcFile.walk(*this); }
111+
112+
std::unique_ptr<NodeFinderResult> takeResult() { return std::move(Result); }
113+
114+
/// Get the declarations that \p ShadowingDecl shadows using shorthand shadow
115+
/// syntax.
116+
SmallVector<ValueDecl *, 2>
117+
getShorthandShadowedDecls(ValueDecl *ShadowingDecl) {
118+
SmallVector<ValueDecl *, 2> Result;
119+
auto ShorthandShadowedDecl = ShorthandShadowedDecls[ShadowingDecl];
120+
while (ShorthandShadowedDecl) {
121+
Result.push_back(ShorthandShadowedDecl);
122+
ShorthandShadowedDecl = ShorthandShadowedDecls[ShorthandShadowedDecl];
123+
}
124+
return Result;
125+
}
126+
127+
private:
128+
SourceManager &getSourceMgr() const {
129+
return SrcFile.getASTContext().SourceMgr;
130+
}
131+
132+
/// The decl context that is currently being walked.
133+
DeclContext *getCurrentDeclContext() { return DeclContextStack.back(); }
134+
135+
bool rangeContainsLocToResolve(SourceRange Range) const {
136+
return Range.contains(LocToResolve);
137+
}
138+
139+
PreWalkAction walkToDeclPre(Decl *D) override {
140+
if (!rangeContainsLocToResolve(D->getSourceRangeIncludingAttrs())) {
141+
return PreWalkAction::SkipChildren;
142+
}
143+
144+
if (auto *newDC = dyn_cast<DeclContext>(D)) {
145+
DeclContextStack.push_back(newDC);
146+
}
147+
148+
if (D->getLoc() != LocToResolve) {
149+
return Action::Continue();
150+
}
151+
152+
if (auto VD = dyn_cast<ValueDecl>(D)) {
153+
if (VD->hasName()) {
154+
assert(Result == nullptr);
155+
Result = std::make_unique<NodeFinderDeclResult>(VD);
156+
return Action::Stop();
157+
}
158+
}
159+
160+
return Action::Continue();
161+
}
162+
163+
PostWalkAction walkToDeclPost(Decl *D) override {
164+
if (auto *newDC = dyn_cast<DeclContext>(D)) {
165+
assert(DeclContextStack.back() == newDC);
166+
DeclContextStack.pop_back();
167+
}
168+
return Action::Continue();
169+
}
170+
171+
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
172+
if (auto closure = dyn_cast<ClosureExpr>(E)) {
173+
DeclContextStack.push_back(closure);
174+
}
175+
176+
if (auto CaptureList = dyn_cast<CaptureListExpr>(E)) {
177+
for (auto ShorthandShadows :
178+
getShorthandShadows(CaptureList, getCurrentDeclContext())) {
179+
assert(ShorthandShadowedDecls.count(ShorthandShadows.first) == 0);
180+
ShorthandShadowedDecls[ShorthandShadows.first] =
181+
ShorthandShadows.second;
182+
}
183+
}
184+
185+
return Action::Continue(E);
186+
}
187+
188+
PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
189+
if (auto *closure = dyn_cast<ClosureExpr>(E)) {
190+
assert(DeclContextStack.back() == closure);
191+
DeclContextStack.pop_back();
192+
}
193+
return Action::Continue(E);
194+
}
195+
196+
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
197+
if (auto CondStmt = dyn_cast<LabeledConditionalStmt>(S)) {
198+
for (auto ShorthandShadow :
199+
getShorthandShadows(CondStmt, getCurrentDeclContext())) {
200+
assert(ShorthandShadowedDecls.count(ShorthandShadow.first) == 0);
201+
ShorthandShadowedDecls[ShorthandShadow.first] = ShorthandShadow.second;
202+
}
203+
}
204+
return Action::Continue(S);
205+
}
206+
};
207+
208+
// MARK: - CursorInfoDoneParsingCallback
209+
210+
class CursorInfoDoneParsingCallback : public CodeCompletionCallbacks {
211+
CursorInfoConsumer &Consumer;
212+
SourceLoc RequestedLoc;
213+
214+
public:
215+
CursorInfoDoneParsingCallback(Parser &P, CursorInfoConsumer &Consumer,
216+
SourceLoc RequestedLoc)
217+
: CodeCompletionCallbacks(P), Consumer(Consumer),
218+
RequestedLoc(RequestedLoc) {}
219+
220+
std::unique_ptr<ResolvedCursorInfo>
221+
getDeclResult(NodeFinderDeclResult *DeclResult, SourceFile *SrcFile,
222+
NodeFinder &Finder) const {
223+
typeCheckDeclAndParentClosures(DeclResult->getDecl());
224+
auto CursorInfo = std::make_unique<ResolvedValueRefCursorInfo>(
225+
ResolvedCursorInfo(SrcFile), DeclResult->getDecl(),
226+
/*CtorTyRef=*/nullptr,
227+
/*ExtTyRef=*/nullptr, /*IsRef=*/false, /*Ty=*/Type(),
228+
/*ContainerType=*/Type());
229+
CursorInfo->setLoc(RequestedLoc);
230+
CursorInfo->setShorthandShadowedDecls(
231+
Finder.getShorthandShadowedDecls(DeclResult->getDecl()));
232+
return CursorInfo;
233+
}
234+
235+
void doneParsing(SourceFile *SrcFile) override {
236+
if (!SrcFile) {
237+
return;
238+
}
239+
NodeFinder Finder(*SrcFile, RequestedLoc);
240+
Finder.resolve();
241+
auto Result = Finder.takeResult();
242+
if (!Result) {
243+
return;
244+
}
245+
std::unique_ptr<ResolvedCursorInfo> CursorInfo;
246+
switch (Result->getKind()) {
247+
case NodeFinderResultKind::Decl:
248+
CursorInfo = getDeclResult(cast<NodeFinderDeclResult>(Result.get()),
249+
SrcFile, Finder);
250+
break;
251+
}
252+
if (Result) {
253+
Consumer.handleResults(*CursorInfo);
254+
}
255+
}
256+
};
257+
258+
} // anonymous namespace.
259+
260+
CodeCompletionCallbacksFactory *
261+
swift::ide::makeCursorInfoCallbacksFactory(CursorInfoConsumer &Consumer,
262+
SourceLoc RequestedLoc) {
263+
class CursorInfoCallbacksFactoryImpl : public CodeCompletionCallbacksFactory {
264+
CursorInfoConsumer &Consumer;
265+
SourceLoc RequestedLoc;
266+
267+
public:
268+
CursorInfoCallbacksFactoryImpl(CursorInfoConsumer &Consumer,
269+
SourceLoc RequestedLoc)
270+
: Consumer(Consumer), RequestedLoc(RequestedLoc) {}
271+
272+
CodeCompletionCallbacks *createCodeCompletionCallbacks(Parser &P) override {
273+
return new CursorInfoDoneParsingCallback(P, Consumer, RequestedLoc);
274+
}
275+
};
276+
277+
return new CursorInfoCallbacksFactoryImpl(Consumer, RequestedLoc);
278+
}

0 commit comments

Comments
 (0)