Skip to content

Commit 2108d01

Browse files
authored
Merge pull request #63613 from ahoppen/ahoppen/ambiguous-cursor-info-results
[IDE] Report ambiguous cursor info results
2 parents f5e8fc2 + e5c521c commit 2108d01

File tree

12 files changed

+234
-110
lines changed

12 files changed

+234
-110
lines changed

include/swift/IDE/CursorInfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace ide {
2626
class CursorInfoConsumer {
2727
public:
2828
virtual ~CursorInfoConsumer() {}
29-
virtual void handleResults(ResolvedCursorInfoPtr) = 0;
29+
virtual void handleResults(std::vector<ResolvedCursorInfoPtr>) = 0;
3030
};
3131

3232
/// Create a factory for code completion callbacks.

include/swift/IDETool/IDEInspectionInstance.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ struct ConformingMethodListResults {
8181

8282
/// The results returned from \c IDEInspectionInstance::cursorInfo.
8383
struct CursorInfoResults {
84-
/// The actual results. If \c nullptr, no results were found.
85-
ResolvedCursorInfoPtr Result;
84+
/// The actual results.
85+
std::vector<ResolvedCursorInfoPtr> ResolvedCursorInfos;
8686
/// Whether an AST was reused to produce the results.
8787
bool DidReuseAST;
8888
};

lib/IDE/CursorInfo.cpp

Lines changed: 77 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class NodeFinderExprResult : public NodeFinderResult {
121121
/// Walks the AST, looking for a node at \c LocToResolve. While walking the
122122
/// AST, also gathers information about shorthand shadows.
123123
class NodeFinder : ASTWalker {
124-
SourceFile &SrcFile;
124+
DeclContext &DC;
125125
SourceLoc LocToResolve;
126126

127127
/// As we are walking the tree, this variable is updated to the last seen
@@ -139,11 +139,10 @@ class NodeFinder : ASTWalker {
139139
llvm::DenseMap<ValueDecl *, ValueDecl *> ShorthandShadowedDecls;
140140

141141
public:
142-
NodeFinder(SourceFile &SrcFile, SourceLoc LocToResolve)
143-
: SrcFile(SrcFile), LocToResolve(LocToResolve),
144-
DeclContextStack({&SrcFile}) {}
142+
NodeFinder(DeclContext &DC, SourceLoc LocToResolve)
143+
: DC(DC), LocToResolve(LocToResolve), DeclContextStack({&DC}) {}
145144

146-
void resolve() { SrcFile.walk(*this); }
145+
void resolve() { DC.walkContext(*this); }
147146

148147
std::unique_ptr<NodeFinderResult> takeResult() { return std::move(Result); }
149148

@@ -161,9 +160,7 @@ class NodeFinder : ASTWalker {
161160
}
162161

163162
private:
164-
SourceManager &getSourceMgr() const {
165-
return SrcFile.getASTContext().SourceMgr;
166-
}
163+
SourceManager &getSourceMgr() const { return DC.getASTContext().SourceMgr; }
167164

168165
/// The decl context that is currently being walked.
169166
DeclContext *getCurrentDeclContext() { return DeclContextStack.back(); }
@@ -219,7 +216,9 @@ class NodeFinder : ASTWalker {
219216
if (auto CaptureList = dyn_cast<CaptureListExpr>(E)) {
220217
for (auto ShorthandShadows :
221218
getShorthandShadows(CaptureList, getCurrentDeclContext())) {
222-
assert(ShorthandShadowedDecls.count(ShorthandShadows.first) == 0);
219+
assert(ShorthandShadowedDecls.count(ShorthandShadows.first) == 0 ||
220+
ShorthandShadowedDecls[ShorthandShadows.first] ==
221+
ShorthandShadows.second);
223222
ShorthandShadowedDecls[ShorthandShadows.first] =
224223
ShorthandShadows.second;
225224
}
@@ -232,7 +231,8 @@ class NodeFinder : ASTWalker {
232231
switch (E->getKind()) {
233232
case ExprKind::DeclRef:
234233
case ExprKind::UnresolvedDot:
235-
case ExprKind::UnresolvedDeclRef: {
234+
case ExprKind::UnresolvedDeclRef:
235+
case ExprKind::OverloadedDeclRef: {
236236
assert(Result == nullptr);
237237
Result =
238238
std::make_unique<NodeFinderExprResult>(E, getCurrentDeclContext());
@@ -280,13 +280,33 @@ class CursorInfoTypeCheckSolutionCallback : public TypeCheckCompletionCallback {
280280
};
281281

282282
private:
283-
/// The expression for which we want to provide cursor info results.
284-
Expr *ResolveExpr;
283+
/// The location to resolve and the \c DeclContext to resolve it in.
284+
/// Note that we cannot store the expression to resolve directly because an
285+
/// \c UnresolvedDeclRefExpr might be replaced by an \c OverloadedDeclRefExpr
286+
/// and thus the constraint system solution doesn't know about the
287+
/// \c UnresolvedDeclRefExpr. Instead, we find the expression to resolve in
288+
/// the source file again after expression pre-check has run.
289+
DeclContext &DC;
290+
SourceLoc ResolveLoc;
285291

286292
SmallVector<CursorInfoDeclReference, 1> Results;
287293

294+
Expr *getExprToResolve() {
295+
NodeFinder Finder(DC, ResolveLoc);
296+
Finder.resolve();
297+
auto Result = Finder.takeResult();
298+
if (!Result || Result->getKind() != NodeFinderResultKind::Expr) {
299+
return nullptr;
300+
}
301+
return cast<NodeFinderExprResult>(Result.get())->getExpr();
302+
}
303+
288304
void sawSolutionImpl(const Solution &S) override {
289305
auto &CS = S.getConstraintSystem();
306+
auto ResolveExpr = getExprToResolve();
307+
if (!ResolveExpr) {
308+
return;
309+
}
290310

291311
auto Locator = CS.getConstraintLocator(ResolveExpr);
292312
auto CalleeLocator = S.getCalleeLocator(Locator);
@@ -310,8 +330,8 @@ class CursorInfoTypeCheckSolutionCallback : public TypeCheckCompletionCallback {
310330
}
311331

312332
public:
313-
CursorInfoTypeCheckSolutionCallback(Expr *ResolveExpr)
314-
: ResolveExpr(ResolveExpr) {}
333+
CursorInfoTypeCheckSolutionCallback(DeclContext &DC, SourceLoc ResolveLoc)
334+
: DC(DC), ResolveLoc(ResolveLoc) {}
315335

316336
ArrayRef<CursorInfoDeclReference> getResults() const { return Results; }
317337
};
@@ -327,11 +347,11 @@ class CursorInfoDoneParsingCallback : public DoneParsingCallback {
327347
SourceLoc RequestedLoc)
328348
: DoneParsingCallback(), Consumer(Consumer), RequestedLoc(RequestedLoc) {}
329349

330-
ResolvedCursorInfoPtr getDeclResult(NodeFinderDeclResult *DeclResult,
331-
SourceFile *SrcFile,
332-
NodeFinder &Finder) const {
350+
std::vector<ResolvedCursorInfoPtr>
351+
getDeclResult(NodeFinderDeclResult *DeclResult, SourceFile *SrcFile,
352+
NodeFinder &Finder) const {
333353
typeCheckDeclAndParentClosures(DeclResult->getDecl());
334-
return new ResolvedValueRefCursorInfo(
354+
auto CursorInfo = new ResolvedValueRefCursorInfo(
335355
SrcFile, RequestedLoc, DeclResult->getDecl(),
336356
/*CtorTyRef=*/nullptr,
337357
/*ExtTyRef=*/nullptr, /*IsRef=*/false, /*Ty=*/Type(),
@@ -341,24 +361,27 @@ class CursorInfoDoneParsingCallback : public DoneParsingCallback {
341361
/*IsDynamic=*/false,
342362
/*ReceiverTypes=*/{},
343363
Finder.getShorthandShadowedDecls(DeclResult->getDecl()));
364+
return {CursorInfo};
344365
}
345366

346-
ResolvedCursorInfoPtr getExprResult(NodeFinderExprResult *ExprResult,
347-
SourceFile *SrcFile,
348-
NodeFinder &Finder) const {
367+
std::vector<ResolvedCursorInfoPtr>
368+
getExprResult(NodeFinderExprResult *ExprResult, SourceFile *SrcFile,
369+
NodeFinder &Finder) const {
349370
Expr *E = ExprResult->getExpr();
350371
DeclContext *DC = ExprResult->getDeclContext();
351372

352373
// Type check the statemnt containing E and listen for solutions.
353-
CursorInfoTypeCheckSolutionCallback Callback(E);
354-
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
355-
DC->getASTContext().SolutionCallback, &Callback);
356-
typeCheckASTNodeAtLoc(TypeCheckASTNodeAtLocContext::declContext(DC),
357-
E->getLoc());
374+
CursorInfoTypeCheckSolutionCallback Callback(*DC, RequestedLoc);
375+
{
376+
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
377+
DC->getASTContext().SolutionCallback, &Callback);
378+
typeCheckASTNodeAtLoc(TypeCheckASTNodeAtLocContext::declContext(DC),
379+
E->getLoc());
380+
}
358381

359382
if (Callback.getResults().empty()) {
360383
// No results.
361-
return nullptr;
384+
return {};
362385
}
363386

364387
for (auto Info : Callback.getResults()) {
@@ -367,34 +390,38 @@ class CursorInfoDoneParsingCallback : public DoneParsingCallback {
367390
typeCheckDeclAndParentClosures(Info.ReferencedDecl);
368391
}
369392

370-
if (Callback.getResults().size() != 1) {
371-
// FIXME: We need to be able to report multiple results.
372-
return nullptr;
373-
}
374-
375393
// Deliver results
376394

377-
auto Res = Callback.getResults()[0];
378-
SmallVector<NominalTypeDecl *> ReceiverTypes;
379-
if (Res.IsDynamicRef && Res.BaseType) {
380-
if (auto ReceiverType = Res.BaseType->getAnyNominal()) {
381-
ReceiverTypes = {ReceiverType};
382-
} else if (auto MT = Res.BaseType->getAs<AnyMetatypeType>()) {
383-
// Look through metatypes to get the nominal type decl.
384-
if (auto ReceiverType = MT->getInstanceType()->getAnyNominal()) {
395+
std::vector<ResolvedCursorInfoPtr> Results;
396+
for (auto Res : Callback.getResults()) {
397+
SmallVector<NominalTypeDecl *> ReceiverTypes;
398+
if (isa<ModuleDecl>(Res.ReferencedDecl)) {
399+
// ResolvedModuleRefCursorInfo is not supported by solver-based cursor
400+
// info yet.
401+
continue;
402+
}
403+
if (Res.IsDynamicRef && Res.BaseType) {
404+
if (auto ReceiverType = Res.BaseType->getAnyNominal()) {
385405
ReceiverTypes = {ReceiverType};
406+
} else if (auto MT = Res.BaseType->getAs<AnyMetatypeType>()) {
407+
// Look through metatypes to get the nominal type decl.
408+
if (auto ReceiverType = MT->getInstanceType()->getAnyNominal()) {
409+
ReceiverTypes = {ReceiverType};
410+
}
386411
}
387412
}
388-
}
389413

390-
return new ResolvedValueRefCursorInfo(
391-
SrcFile, RequestedLoc, Res.ReferencedDecl,
392-
/*CtorTyRef=*/nullptr,
393-
/*ExtTyRef=*/nullptr, /*IsRef=*/true, /*Ty=*/Type(),
394-
/*ContainerType=*/Res.BaseType,
395-
/*CustomAttrRef=*/None,
396-
/*IsKeywordArgument=*/false, Res.IsDynamicRef, ReceiverTypes,
397-
Finder.getShorthandShadowedDecls(Res.ReferencedDecl));
414+
auto CursorInfo = new ResolvedValueRefCursorInfo(
415+
SrcFile, RequestedLoc, Res.ReferencedDecl,
416+
/*CtorTyRef=*/nullptr,
417+
/*ExtTyRef=*/nullptr, /*IsRef=*/true, /*Ty=*/Type(),
418+
/*ContainerType=*/Res.BaseType,
419+
/*CustomAttrRef=*/None,
420+
/*IsKeywordArgument=*/false, Res.IsDynamicRef, ReceiverTypes,
421+
Finder.getShorthandShadowedDecls(Res.ReferencedDecl));
422+
Results.push_back(CursorInfo);
423+
}
424+
return Results;
398425
}
399426

400427
void doneParsing(SourceFile *SrcFile) override {
@@ -407,7 +434,7 @@ class CursorInfoDoneParsingCallback : public DoneParsingCallback {
407434
if (!Result) {
408435
return;
409436
}
410-
ResolvedCursorInfoPtr CursorInfo;
437+
std::vector<ResolvedCursorInfoPtr> CursorInfo;
411438
switch (Result->getKind()) {
412439
case NodeFinderResultKind::Decl:
413440
CursorInfo = getDeclResult(cast<NodeFinderDeclResult>(Result.get()),

lib/IDETool/IDEInspectionInstance.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ void swift::ide::IDEInspectionInstance::cursorInfo(
827827
: ReusingASTContext(ReusingASTContext),
828828
CancellationFlag(CancellationFlag), Callback(Callback) {}
829829

830-
void handleResults(ResolvedCursorInfoPtr result) override {
830+
void handleResults(std::vector<ResolvedCursorInfoPtr> result) override {
831831
HandleResultsCalled = true;
832832
if (CancellationFlag &&
833833
CancellationFlag->load(std::memory_order_relaxed)) {
@@ -852,8 +852,8 @@ void swift::ide::IDEInspectionInstance::cursorInfo(
852852
ide::makeCursorInfoCallbacksFactory(Consumer, RequestedLoc));
853853

854854
if (!Result.DidFindIDEInspectionTarget) {
855-
return DeliverTransformed(ResultType::success(
856-
{/*Results=*/nullptr, Result.DidReuseAST}));
855+
return DeliverTransformed(
856+
ResultType::success({/*Results=*/{}, Result.DidReuseAST}));
857857
}
858858

859859
performIDEInspectionSecondPass(
@@ -863,8 +863,8 @@ void swift::ide::IDEInspectionInstance::cursorInfo(
863863
// pass, we didn't receive any results. To make sure Callback
864864
// gets called exactly once, call it manually with no results
865865
// here.
866-
DeliverTransformed(ResultType::success(
867-
{/*Results=*/nullptr, Result.DidReuseAST}));
866+
DeliverTransformed(
867+
ResultType::success({/*Results=*/{}, Result.DidReuseAST}));
868868
}
869869
},
870870
Callback);

lib/Sema/CSSolver.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,7 +1430,10 @@ ConstraintSystem::solve(SyntacticElementTarget &target,
14301430
case SolutionResult::Ambiguous:
14311431
// If salvaging produced an ambiguous result, it has already been
14321432
// diagnosed.
1433-
if (stage == 1) {
1433+
// If we have found an ambiguous solution in the first stage, salvaging
1434+
// won't produce more solutions, so we can inform the solution callback
1435+
// about the current ambiguous solutions straight away.
1436+
if (stage == 1 || Context.SolutionCallback) {
14341437
reportSolutionsToSolutionCallback(solution);
14351438
solution.markAsDiagnosed();
14361439
return None;
@@ -1449,8 +1452,10 @@ ConstraintSystem::solve(SyntacticElementTarget &target,
14491452
LLVM_FALLTHROUGH;
14501453

14511454
case SolutionResult::UndiagnosedError:
1452-
if (shouldSuppressDiagnostics()) {
1453-
reportSolutionsToSolutionCallback(solution);
1455+
/// If we have a SolutionCallback, we are inspecting constraint system
1456+
/// solutions directly and thus also want to receive ambiguous solutions.
1457+
/// Hence always run the second (salvaging) stage.
1458+
if (shouldSuppressDiagnostics() && !Context.SolutionCallback) {
14541459
solution.markAsDiagnosed();
14551460
return None;
14561461
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
func testAmbiguousFunctionReference() {
2+
func foo(a: Int) {}
3+
func foo(a: String) {}
4+
5+
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 1):7 %s -- %s | %FileCheck %s
6+
_ = foo
7+
8+
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 1):7 %s -- %s | %FileCheck %s
9+
_ = foo(a: UInt(1))
10+
}
11+
12+
// CHECK: source.lang.swift.ref.function.free (2:8-2:19)
13+
// CHECK: <Declaration>func foo(a: <Type usr="s:Si">Int</Type>)</Declaration>
14+
// CHECK: SECONDARY SYMBOLS BEGIN
15+
// CHECK: source.lang.swift.ref.function.free (3:8-3:22)
16+
// CHECK: <Declaration>func foo(a: <Type usr="s:SS">String</Type>)</Declaration>
17+
// CHECK: SECONDARY SYMBOLS END
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
struct GorEach<Data, Content> {
2+
public init(_ data: Data, content: (Data) -> Void) {}
3+
}
4+
5+
struct MavigationLink<Label, Destination> {
6+
init(destination: PeopleDetail) {}
7+
}
8+
9+
struct PeopleDetail {
10+
init(peopleId: Int) {}
11+
}
12+
13+
func test(peoples: [Int]) {
14+
GorEach(peoples) { people in
15+
// Should not crash
16+
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 1):56 %s -- %s
17+
MavigationLink(destination: PeopleDetail(peopleId: people))
18+
}
19+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Items {
2+
func test() {
3+
// Should not crash
4+
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 1):9 %s -- %s
5+
_ = Invalid.sink { [weak self] items in
6+
}
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
func test() {
2+
// RUN: %sourcekitd-test -req=cursor -pos=%(line + 1):3 %s -- %s | %FileCheck %s
3+
Swift.min
4+
}
5+
6+
// CHECK: source.lang.swift.ref.module ()
7+
// CHECK-NEXT: Swift

test/SourceKit/Refactoring/basic.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ func hasCallToAsyncAlternative(c: ConvertAsync) {
170170
// RUN: %sourcekitd-test -req=cursor -pos=117:16 -cursor-action %s -- %s | %FileCheck %s -check-prefix=CHECK-GLOBAL
171171
// RUN: %sourcekitd-test -req=cursor -pos=117:17 -cursor-action %s -- %s | %FileCheck %s -check-prefix=CHECK-GLOBAL
172172

173-
// RUN: %sourcekitd-test -req=cursor -pos=35:10 -end-pos=35:16 -cursor-action %s -- %s | %FileCheck %s -check-prefix=CHECK-RENAME-EXTRACT
173+
// RUN: %sourcekitd-test -req=cursor -pos=35:10 -end-pos=35:16 -cursor-action %s -- %s | %FileCheck %s -check-prefix=CHECK-RENAME
174174

175175
// RUN: %sourcekitd-test -req=cursor -pos=54:10 -end-pos=54:22 -cursor-action %s -- %s | %FileCheck %s -check-prefix=CHECK-LOCAL
176176
// RUN: %sourcekitd-test -req=cursor -pos=54:12 -end-pos=54:22 -cursor-action %s -- %s | %FileCheck %s -check-prefix=CHECK-SELF-RENAME1
@@ -231,6 +231,8 @@ func hasCallToAsyncAlternative(c: ConvertAsync) {
231231
// CHECK-LOCAL-NOT: Global Rename
232232
// CHECK-LOCAL: ACTIONS END
233233

234+
// CHECK-RENAME: Global Rename
235+
234236
// CHECK-RENAME-EXTRACT: Global Rename
235237
// CHECK-RENAME-EXTRACT: Extract Method
236238

tools/SourceKit/include/SourceKit/Core/LangSupport.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,11 +586,14 @@ struct CursorInfoData {
586586
// will be empty). Clients can potentially use this to show a diagnostic
587587
// message to the user in lieu of using the empty response.
588588
StringRef InternalDiagnostic;
589-
llvm::ArrayRef<CursorSymbolInfo> Symbols;
589+
llvm::SmallVector<CursorSymbolInfo, 1> Symbols;
590590
/// All available actions on the code under cursor.
591-
llvm::ArrayRef<RefactoringInfo> AvailableActions;
591+
llvm::SmallVector<RefactoringInfo, 8> AvailableActions;
592592
/// Whether the ASTContext was reused for this cursor info.
593593
bool DidReuseAST = false;
594+
/// An allocator that can be used to allocate data that is referenced by this
595+
/// \c CursorInfoData.
596+
llvm::BumpPtrAllocator Allocator;
594597

595598
void print(llvm::raw_ostream &OS, std::string Indentation) const {
596599
OS << Indentation << "CursorInfoData" << '\n';

0 commit comments

Comments
 (0)