Skip to content

Commit 95d3984

Browse files
committed
[CodeCompletion] Don't record solution results twice in ExprCompletion
This fixes a performance problem where we were sometimes reporting >1000 solutions but all the relevant parameters of the result were equal.
1 parent 7f082e1 commit 95d3984

File tree

5 files changed

+88
-23
lines changed

5 files changed

+88
-23
lines changed

include/swift/IDE/ExprCompletion.h

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ namespace ide {
2323
class ExprTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
2424
public:
2525
struct Result {
26-
/// The contextual type that the code completion expression should produce.
27-
Type ExpectedType;
28-
2926
/// If the code completion expression is an implicit return in a
3027
/// single-expression closure.
3128
bool IsImplicitSingleExpressionReturn;
@@ -38,14 +35,37 @@ class ExprTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
3835
/// this result. This in particular includes parameters of closures that
3936
/// were type-checked with the code completion expression.
4037
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes;
38+
39+
bool operator==(const Result &Other) const;
4140
};
4241

4342
private:
4443
CodeCompletionExpr *CompletionExpr;
4544
DeclContext *DC;
4645

46+
/// The contextual types to which the code completion results should be
47+
/// convertible.
48+
/// Technically, each result should have its own expected type because some
49+
/// expected types may only be available e.g. for certain
50+
/// \c SolutionSpecificVarTypes. But that means that we need to do a separate
51+
/// completion lookup for each expected type and de-duplicate the results,
52+
/// which can have huge performance implications (>5mins instead of <2secs).
53+
/// In practice sharing ExpectedTypes between results yields identical results
54+
/// in almost all cases and acceptable results in the other cases.
55+
SmallVector<Type, 4> ExpectedTypes;
56+
4757
SmallVector<Result, 4> Results;
4858

59+
/// Adds the given type to \c ExpectedTypes unless \c ExpectedTypes already
60+
/// contains the type.
61+
void addExpectedType(Type ExpectedType);
62+
63+
/// Adds the result with the given parameters to \c Results unless \c Results
64+
/// already contains an entry with exactly the same values.
65+
void addResult(
66+
bool IsImplicitSingleExpressionReturn, bool IsInAsyncContext,
67+
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes);
68+
4969
void sawSolutionImpl(const constraints::Solution &solution) override;
5070

5171
public:

include/swift/IDE/TypeCheckCompletionCallback.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ bool isImplicitSingleExpressionReturn(constraints::ConstraintSystem &CS,
9090
/// Returns \c true iff the decl context \p DC allows calling async functions.
9191
bool isContextAsync(const constraints::Solution &S, DeclContext *DC);
9292

93+
/// Returns true if both types are null or if they are equal.
94+
bool nullableTypesEqual(Type LHS, Type RHS);
95+
9396
} // namespace ide
9497
} // namespace swift
9598

lib/IDE/ArgumentCompletion.cpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,6 @@ using namespace swift;
2020
using namespace swift::ide;
2121
using namespace swift::constraints;
2222

23-
/// Returns true if both types are null or if they are equal.
24-
static bool nullableTypesEqual(Type LHS, Type RHS) {
25-
if (LHS.isNull() && RHS.isNull()) {
26-
return true;
27-
} else if (LHS.isNull() || RHS.isNull()) {
28-
// One type is null but the other is not.
29-
return false;
30-
} else {
31-
return LHS->isEqual(RHS);
32-
}
33-
}
34-
3523
bool ArgumentTypeCheckCompletionCallback::addPossibleParams(
3624
const ArgumentTypeCheckCompletionCallback::Result &Res,
3725
SmallVectorImpl<PossibleParamInfo> &Params, SmallVectorImpl<Type> &Types) {

lib/IDE/ExprCompletion.cpp

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,54 @@ using namespace swift;
1919
using namespace swift::ide;
2020
using namespace swift::constraints;
2121

22+
static bool solutionSpecificVarTypesEqual(
23+
const llvm::SmallDenseMap<const VarDecl *, Type> &LHS,
24+
const llvm::SmallDenseMap<const VarDecl *, Type> &RHS) {
25+
if (LHS.size() != RHS.size()) {
26+
return false;
27+
}
28+
for (auto LHSEntry : LHS) {
29+
auto RHSEntry = RHS.find(LHSEntry.first);
30+
if (RHSEntry == RHS.end()) {
31+
// Entry of the LHS doesn't exist in RHS
32+
return false;
33+
} else if (!nullableTypesEqual(LHSEntry.second, RHSEntry->second)) {
34+
return false;
35+
}
36+
}
37+
return true;
38+
}
39+
40+
bool ExprTypeCheckCompletionCallback::Result::operator==(
41+
const Result &Other) const {
42+
return IsImplicitSingleExpressionReturn ==
43+
Other.IsImplicitSingleExpressionReturn &&
44+
IsInAsyncContext == Other.IsInAsyncContext &&
45+
solutionSpecificVarTypesEqual(SolutionSpecificVarTypes,
46+
Other.SolutionSpecificVarTypes);
47+
}
48+
49+
void ExprTypeCheckCompletionCallback::addExpectedType(Type ExpectedType) {
50+
auto IsEqual = [&ExpectedType](Type Other) {
51+
return nullableTypesEqual(ExpectedType, Other);
52+
};
53+
if (llvm::any_of(ExpectedTypes, IsEqual)) {
54+
return;
55+
}
56+
ExpectedTypes.push_back(ExpectedType);
57+
}
58+
59+
void ExprTypeCheckCompletionCallback::addResult(
60+
bool IsImplicitSingleExpressionReturn, bool IsInAsyncContext,
61+
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes) {
62+
Result NewResult = {IsImplicitSingleExpressionReturn, IsInAsyncContext,
63+
SolutionSpecificVarTypes};
64+
if (llvm::is_contained(Results, NewResult)) {
65+
return;
66+
}
67+
Results.push_back(NewResult);
68+
}
69+
2270
void ExprTypeCheckCompletionCallback::sawSolutionImpl(
2371
const constraints::Solution &S) {
2472
auto &CS = S.getConstraintSystem();
@@ -36,12 +84,11 @@ void ExprTypeCheckCompletionCallback::sawSolutionImpl(
3684
}
3785
}
3886

39-
Results.push_back(
40-
{ExpectedTy, ImplicitReturn, IsAsync, SolutionSpecificVarTypes});
87+
addResult(ImplicitReturn, IsAsync, SolutionSpecificVarTypes);
88+
addExpectedType(ExpectedTy);
4189

4290
if (auto PatternMatchType = getPatternMatchType(S, CompletionExpr)) {
43-
Results.push_back(
44-
{PatternMatchType, ImplicitReturn, IsAsync, SolutionSpecificVarTypes});
91+
addExpectedType(PatternMatchType);
4592
}
4693
}
4794

@@ -53,10 +100,6 @@ void ExprTypeCheckCompletionCallback::deliverResults(
53100
&CompletionCtx);
54101
Lookup.shouldCheckForDuplicates(Results.size() > 1);
55102

56-
SmallVector<Type, 2> ExpectedTypes;
57-
for (auto &Result : Results) {
58-
ExpectedTypes.push_back(Result.ExpectedType);
59-
}
60103
for (auto &Result : Results) {
61104
Lookup.setExpectedTypes(ExpectedTypes,
62105
Result.IsImplicitSingleExpressionReturn);

lib/IDE/TypeCheckCompletionCallback.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,14 @@ bool swift::ide::isContextAsync(const constraints::Solution &S,
208208
// async.
209209
return canDeclContextHandleAsync(DC);
210210
}
211+
212+
bool swift::ide::nullableTypesEqual(Type LHS, Type RHS) {
213+
if (LHS.isNull() && RHS.isNull()) {
214+
return true;
215+
} else if (LHS.isNull() || RHS.isNull()) {
216+
// One type is null but the other is not.
217+
return false;
218+
} else {
219+
return LHS->isEqual(RHS);
220+
}
221+
}

0 commit comments

Comments
 (0)