Skip to content

Commit 6cec68e

Browse files
committed
[IDE] Ignore score kinds that represent implicit conversions when solving for code completion
Ignore conversion score increases during code completion to make sure we don't filter solutions that might start receiving the best score based on a choice of the code completion token.
1 parent 77bd8ad commit 6cec68e

17 files changed

+423
-142
lines changed

include/swift/IDE/PostfixCompletion.h

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,32 @@ namespace ide {
2525
/// type-checking.
2626
class PostfixCompletionCallback : public TypeCheckCompletionCallback {
2727
struct Result {
28+
/// The type that we are completing on. Will never be null.
2829
Type BaseTy;
30+
31+
/// The decl that we are completing on. Is \c nullptr if we are completing
32+
/// on an expression.
2933
ValueDecl *BaseDecl;
34+
35+
/// If the expression we are completing on statically refers to a metatype,
36+
/// that is if it's something like 'MyType'. In such cases we want to offer
37+
/// constructor call pattern completions and don't want to suggeste
38+
/// operators that work on metatypes.
39+
bool BaseIsStaticMetaType;
40+
41+
/// The types that the completion is expected to produce.
3042
SmallVector<Type, 4> ExpectedTypes;
43+
44+
/// Whether results that produce 'Void' should be disfavored. This happens
45+
/// if the context is requiring a value. Once a completion produces 'Void',
46+
/// we know that we can't retrieve a value from it anymore.
3147
bool ExpectsNonVoid;
32-
bool BaseIsStaticMetaType;
48+
49+
/// If the code completion expression occurs as a single statement in a
50+
/// single-expression closure. In such cases we don't want to disfavor
51+
/// results that produce 'Void' because the user might intend to make the
52+
/// closure a multi-statment closure, in which case this expression is no
53+
/// longer implicitly returned.
3354
bool IsImplicitSingleExpressionReturn;
3455

3556
/// Whether the surrounding context is async and thus calling async
@@ -40,13 +61,24 @@ class PostfixCompletionCallback : public TypeCheckCompletionCallback {
4061
/// haven't been saved to the AST.
4162
llvm::DenseMap<AbstractClosureExpr *, ClosureActorIsolation>
4263
ClosureActorIsolations;
64+
65+
/// Checks whether this result has the same \c BaseTy and \c BaseDecl as
66+
/// \p Other and if the two can thus be merged to be one value lookup in
67+
/// \c deliverResults.
68+
bool canBeMergedWith(const Result &Other, DeclContext &DC) const;
69+
70+
/// Merge this result with \p Other. Assumes that they can be merged.
71+
void merge(const Result &Other, DeclContext &DC);
4372
};
4473

4574
CodeCompletionExpr *CompletionExpr;
4675
DeclContext *DC;
4776

4877
SmallVector<Result, 4> Results;
49-
llvm::DenseMap<std::pair<Type, Decl *>, size_t> BaseToSolutionIdx;
78+
79+
/// Add a result to \c Results, merging it with an existing result, if
80+
/// possible.
81+
void addResult(const Result &Res);
5082

5183
void sawSolutionImpl(const constraints::Solution &solution) override;
5284

include/swift/IDE/UnresolvedMemberCompletion.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ class UnresolvedMemberTypeCheckCompletionCallback
3232
/// Whether the surrounding context is async and thus calling async
3333
/// functions is supported.
3434
bool IsInAsyncContext;
35+
36+
/// Checks whether this result has the same \c BaseTy and \c BaseDecl as
37+
/// \p Other and if the two can thus be merged to be one value lookup in
38+
/// \c deliverResults.
39+
bool canBeMergedWith(const Result &Other, DeclContext &DC) const;
40+
41+
/// Merge this result with \p Other. Assumes that they can be merged.
42+
void merge(const Result &Other, DeclContext &DC);
3543
};
3644

3745
CodeCompletionExpr *CompletionExpr;
@@ -40,6 +48,10 @@ class UnresolvedMemberTypeCheckCompletionCallback
4048
SmallVector<Result, 4> ExprResults;
4149
SmallVector<Result, 1> EnumPatternTypes;
4250

51+
/// Add a result to \c Results, merging it with an existing result, if
52+
/// possible.
53+
void addExprResult(const Result &Res);
54+
4355
void sawSolutionImpl(const constraints::Solution &solution) override;
4456

4557
public:

include/swift/Sema/ConstraintSystem.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,9 @@ enum ScoreKind: unsigned int {
938938
/// A reference to an @unavailable declaration.
939939
SK_Unavailable,
940940
/// A reference to an async function in a synchronous context.
941+
///
942+
/// \note Any score kind after this is considered a conversion that doesn't
943+
/// require fixing the source and will be ignored during code completion.
941944
SK_AsyncInSyncMismatch,
942945
/// Synchronous function in an asynchronous context or a conversion of
943946
/// a synchronous function to an asynchronous one.
@@ -5228,7 +5231,8 @@ class ConstraintSystem {
52285231
public:
52295232
/// Increase the score of the given kind for the current (partial) solution
52305233
/// along the.
5231-
void increaseScore(ScoreKind kind, unsigned value = 1);
5234+
void increaseScore(ScoreKind kind, ConstraintLocatorBuilder Locator,
5235+
unsigned value = 1);
52325236

52335237
/// Determine whether this solution is guaranteed to be worse than the best
52345238
/// solution found so far.

lib/IDE/PostfixCompletion.cpp

Lines changed: 91 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,64 @@ using namespace swift;
2121
using namespace swift::constraints;
2222
using namespace swift::ide;
2323

24+
bool PostfixCompletionCallback::Result::canBeMergedWith(const Result &Other,
25+
DeclContext &DC) const {
26+
if (BaseDecl != Other.BaseDecl) {
27+
return false;
28+
}
29+
if (!BaseTy->isEqual(Other.BaseTy) &&
30+
!isConvertibleTo(BaseTy, Other.BaseTy, /*openArchetypes=*/true, DC) &&
31+
!isConvertibleTo(Other.BaseTy, BaseTy, /*openArchetypes=*/true, DC)) {
32+
return false;
33+
}
34+
return true;
35+
}
36+
37+
void PostfixCompletionCallback::Result::merge(const Result &Other,
38+
DeclContext &DC) {
39+
assert(canBeMergedWith(Other, DC));
40+
// These properties should match if we are talking about the same BaseDecl.
41+
assert(BaseIsStaticMetaType == Other.BaseIsStaticMetaType);
42+
43+
if (!BaseTy->isEqual(Other.BaseTy) &&
44+
isConvertibleTo(Other.BaseTy, BaseTy, /*openArchetypes=*/true, DC)) {
45+
// Pick the more specific base type as it will produce more solutions.
46+
BaseTy = Other.BaseTy;
47+
}
48+
49+
// There could be multiple results that have different actor isolations if the
50+
// closure is an argument to a function that has multiple overloads with
51+
// different isolations for the closure. Producing multiple results for these
52+
// is usually not very enlightning. For now, we just pick the first actor
53+
// isolation that we find. This is good enough in practice.
54+
// What we should really do is probably merge these two actor isolations and
55+
// pick the weakest isolation for each closure.
56+
57+
for (auto &OtherExpectedTy : Other.ExpectedTypes) {
58+
auto IsEqual = [&](Type Ty) { return Ty->isEqual(OtherExpectedTy); };
59+
if (llvm::any_of(ExpectedTypes, IsEqual)) {
60+
// We already know if this expected type
61+
continue;
62+
}
63+
ExpectedTypes.push_back(OtherExpectedTy);
64+
}
65+
ExpectsNonVoid &= Other.ExpectsNonVoid;
66+
IsImplicitSingleExpressionReturn |= Other.IsImplicitSingleExpressionReturn;
67+
IsInAsyncContext |= Other.IsInAsyncContext;
68+
}
69+
70+
void PostfixCompletionCallback::addResult(const Result &Res) {
71+
auto ExistingRes =
72+
llvm::find_if(Results, [&Res, DC = DC](const Result &ExistingResult) {
73+
return ExistingResult.canBeMergedWith(Res, *DC);
74+
});
75+
if (ExistingRes != Results.end()) {
76+
ExistingRes->merge(Res, *DC);
77+
} else {
78+
Results.push_back(Res);
79+
}
80+
}
81+
2482
void PostfixCompletionCallback::fallbackTypeCheck(DeclContext *DC) {
2583
assert(!gotCallback());
2684

@@ -101,39 +159,43 @@ void PostfixCompletionCallback::sawSolutionImpl(
101159
}
102160
}
103161

104-
auto Key = std::make_pair(BaseTy, ReferencedDecl);
105-
auto Ret = BaseToSolutionIdx.insert({Key, Results.size()});
106-
if (Ret.second) {
107-
bool ISDMT = S.isStaticallyDerivedMetatype(ParsedExpr);
108-
bool ImplicitReturn = isImplicitSingleExpressionReturn(CS, CompletionExpr);
109-
bool DisallowVoid = false;
110-
DisallowVoid |= ExpectedTy && !ExpectedTy->isVoid();
111-
DisallowVoid |= !ParentExpr &&
112-
CS.getContextualTypePurpose(CompletionExpr) != CTP_Unused;
113-
for (auto SAT : S.targets) {
114-
if (DisallowVoid) {
115-
// DisallowVoid is already set. No need to iterate further.
116-
break;
117-
}
118-
if (SAT.second.getAsExpr() == CompletionExpr) {
119-
DisallowVoid |= SAT.second.getExprContextualTypePurpose() != CTP_Unused;
120-
}
121-
}
162+
bool BaseIsStaticMetaType = S.isStaticallyDerivedMetatype(ParsedExpr);
163+
164+
SmallVector<Type, 4> ExpectedTypes;
165+
if (ExpectedTy) {
166+
ExpectedTypes.push_back(ExpectedTy);
167+
}
122168

123-
Results.push_back({BaseTy, ReferencedDecl,
124-
/*ExpectedTypes=*/{}, DisallowVoid, ISDMT,
125-
ImplicitReturn, IsAsync, ClosureActorIsolations});
126-
if (ExpectedTy) {
127-
Results.back().ExpectedTypes.push_back(ExpectedTy);
169+
bool ExpectsNonVoid = false;
170+
ExpectsNonVoid |= ExpectedTy && !ExpectedTy->isVoid();
171+
ExpectsNonVoid |=
172+
!ParentExpr && CS.getContextualTypePurpose(CompletionExpr) != CTP_Unused;
173+
174+
for (auto SAT : S.targets) {
175+
if (ExpectsNonVoid) {
176+
// ExpectsNonVoid is already set. No need to iterate further.
177+
break;
128178
}
129-
} else if (ExpectedTy) {
130-
auto &ExistingResult = Results[Ret.first->getSecond()];
131-
ExistingResult.IsInAsyncContext |= IsAsync;
132-
auto IsEqual = [&](Type Ty) { return ExpectedTy->isEqual(Ty); };
133-
if (!llvm::any_of(ExistingResult.ExpectedTypes, IsEqual)) {
134-
ExistingResult.ExpectedTypes.push_back(ExpectedTy);
179+
if (SAT.second.getAsExpr() == CompletionExpr) {
180+
ExpectsNonVoid |= SAT.second.getExprContextualTypePurpose() != CTP_Unused;
135181
}
136182
}
183+
184+
bool IsImplicitSingleExpressionReturn =
185+
isImplicitSingleExpressionReturn(CS, CompletionExpr);
186+
187+
Result Res = {
188+
BaseTy,
189+
ReferencedDecl,
190+
BaseIsStaticMetaType,
191+
ExpectedTypes,
192+
ExpectsNonVoid,
193+
IsImplicitSingleExpressionReturn,
194+
IsAsync,
195+
ClosureActorIsolations
196+
};
197+
198+
addResult(Res);
137199
}
138200

139201
void PostfixCompletionCallback::deliverResults(

lib/IDE/UnresolvedMemberCompletion.cpp

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,45 @@ using namespace swift;
2121
using namespace swift::constraints;
2222
using namespace swift::ide;
2323

24+
bool UnresolvedMemberTypeCheckCompletionCallback::Result::canBeMergedWith(
25+
const Result &Other, DeclContext &DC) const {
26+
if (!isConvertibleTo(ExpectedTy, Other.ExpectedTy, /*openArchetypes=*/true,
27+
DC) &&
28+
!isConvertibleTo(Other.ExpectedTy, ExpectedTy, /*openArchetypes=*/true,
29+
DC)) {
30+
return false;
31+
}
32+
return true;
33+
}
34+
35+
void UnresolvedMemberTypeCheckCompletionCallback::Result::merge(
36+
const Result &Other, DeclContext &DC) {
37+
assert(canBeMergedWith(Other, DC));
38+
if (!ExpectedTy->isEqual(Other.ExpectedTy) &&
39+
isConvertibleTo(ExpectedTy, Other.ExpectedTy, /*openArchetypes=*/true,
40+
DC)) {
41+
// ExpectedTy is more general than Other.ExpectedTy. Complete based on the
42+
// more general type because it offers more completion options.
43+
ExpectedTy = Other.ExpectedTy;
44+
}
45+
46+
IsImplicitSingleExpressionReturn |= Other.IsImplicitSingleExpressionReturn;
47+
IsInAsyncContext |= Other.IsInAsyncContext;
48+
}
49+
50+
void UnresolvedMemberTypeCheckCompletionCallback::addExprResult(
51+
const Result &Res) {
52+
auto ExistingRes =
53+
llvm::find_if(ExprResults, [&Res, DC = DC](const Result &ExistingResult) {
54+
return ExistingResult.canBeMergedWith(Res, *DC);
55+
});
56+
if (ExistingRes != ExprResults.end()) {
57+
ExistingRes->merge(Res, *DC);
58+
} else {
59+
ExprResults.push_back(Res);
60+
}
61+
}
62+
2463
void UnresolvedMemberTypeCheckCompletionCallback::sawSolutionImpl(
2564
const constraints::Solution &S) {
2665
auto &CS = S.getConstraintSystem();
@@ -32,15 +71,9 @@ void UnresolvedMemberTypeCheckCompletionCallback::sawSolutionImpl(
3271
// to derive it from), let's not attempt to do a lookup since it wouldn't
3372
// produce any useful results anyway.
3473
if (ExpectedTy) {
35-
// If ExpectedTy is a duplicate of any other result, ignore this solution.
36-
auto IsEqual = [&](const Result &R) {
37-
return R.ExpectedTy->isEqual(ExpectedTy);
38-
};
39-
if (!llvm::any_of(ExprResults, IsEqual)) {
40-
bool SingleExprBody =
41-
isImplicitSingleExpressionReturn(CS, CompletionExpr);
42-
ExprResults.push_back({ExpectedTy, SingleExprBody, IsAsync});
43-
}
74+
bool SingleExprBody = isImplicitSingleExpressionReturn(CS, CompletionExpr);
75+
Result Res = {ExpectedTy, SingleExprBody, IsAsync};
76+
addExprResult(Res);
4477
}
4578

4679
if (auto PatternType = getPatternMatchType(S, CompletionExpr)) {

lib/Sema/CSBindings.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2315,7 +2315,7 @@ bool TypeVariableBinding::attempt(ConstraintSystem &cs) const {
23152315
}
23162316
// Reflect in the score that this type variable couldn't be
23172317
// resolved and had to be bound to a placeholder "hole" type.
2318-
cs.increaseScore(SK_Hole);
2318+
cs.increaseScore(SK_Hole, srcLocator);
23192319

23202320
if (auto fix = fixForHole(cs)) {
23212321
if (cs.recordFix(/*fix=*/fix->first, /*impact=*/fix->second))

0 commit comments

Comments
 (0)