Skip to content

Commit 30af99e

Browse files
committed
[Completion] Better handle merging of lookup base types
For unresolved member completion, we were preferring the more general type, when we ought to be preferring the more specific type. Additionally, for both unresolved member and postfix completion we were opening archetypes, which doesn't work as expected since we don't compare requirements. Factor out the logic that deals with merging base types for lookup, and have it prefer either the subtype, or the optional type in the case of optional promotion. rdar://126168123
1 parent f17d3d3 commit 30af99e

11 files changed

+189
-70
lines changed

include/swift/IDE/PostfixCompletion.h

+3-7
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,9 @@ class PostfixCompletionCallback : public TypeCheckCompletionCallback {
6666
llvm::DenseMap<AbstractClosureExpr *, ActorIsolation>
6767
ClosureActorIsolations;
6868

69-
/// Checks whether this result has the same \c BaseTy and \c BaseDecl as
70-
/// \p Other and if the two can thus be merged to be one value lookup in
71-
/// \c deliverResults.
72-
bool canBeMergedWith(const Result &Other, DeclContext &DC) const;
73-
74-
/// Merge this result with \p Other. Assumes that they can be merged.
75-
void merge(const Result &Other, DeclContext &DC);
69+
/// Merge this result with \p Other, returning \c true if
70+
/// successful, else \c false.
71+
bool tryMerge(const Result &Other, DeclContext *DC);
7672
};
7773

7874
CodeCompletionExpr *CompletionExpr;

include/swift/IDE/UnresolvedMemberCompletion.h

+3-7
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,9 @@ class UnresolvedMemberTypeCheckCompletionCallback
3333
/// functions is supported.
3434
bool IsInAsyncContext;
3535

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);
36+
/// Attempts to merge this result with \p Other, returning \c true if
37+
/// successful, else \c false.
38+
bool tryMerge(const Result &Other, DeclContext *DC);
4339
};
4440

4541
CodeCompletionExpr *CompletionExpr;

include/swift/Sema/IDETypeChecking.h

+9
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,20 @@ namespace swift {
6060
/// Typecheck binding initializer at \p bindingIndex.
6161
void typeCheckPatternBinding(PatternBindingDecl *PBD, unsigned bindingIndex);
6262

63+
/// Attempt to merge two types for the purposes of completion lookup. In
64+
/// general this means preferring a subtype over a supertype, but can also e.g
65+
/// prefer an optional over a non-optional. If the two types are incompatible,
66+
/// null is returned.
67+
Type tryMergeBaseTypeForCompletionLookup(Type ty1, Type ty2, DeclContext *dc);
68+
6369
/// Check if T1 is convertible to T2.
6470
///
6571
/// \returns true on convertible, false on not.
6672
bool isConvertibleTo(Type T1, Type T2, bool openArchetypes, DeclContext &DC);
6773

74+
/// Check whether \p T1 is a subtype of \p T2.
75+
bool isSubtypeOf(Type T1, Type T2, DeclContext *DC);
76+
6877
void collectDefaultImplementationForProtocolMembers(ProtocolDecl *PD,
6978
llvm::SmallDenseMap<ValueDecl*, ValueDecl*> &DefaultMap);
7079

include/swift/Sema/IDETypeCheckingRequests.h

+2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class IsDeclApplicableRequest:
8686
//----------------------------------------------------------------------------//
8787
enum class TypeRelation: uint8_t {
8888
ConvertTo,
89+
SubtypeOf
8990
};
9091

9192
struct TypePair {
@@ -153,6 +154,7 @@ struct TypeRelationCheckInput {
153154
switch(owner.Relation) {
154155
#define CASE(NAME) case TypeRelation::NAME: out << #NAME << " "; break;
155156
CASE(ConvertTo)
157+
CASE(SubtypeOf)
156158
#undef CASE
157159
}
158160
}

lib/IDE/IDETypeChecking.cpp

+45
Original file line numberDiff line numberDiff line change
@@ -941,13 +941,58 @@ bool swift::isMemberDeclApplied(const DeclContext *DC, Type BaseTy,
941941
IsDeclApplicableRequest(DeclApplicabilityOwner(DC, BaseTy, VD)), false);
942942
}
943943

944+
Type swift::tryMergeBaseTypeForCompletionLookup(Type ty1, Type ty2,
945+
DeclContext *dc) {
946+
// Easy case, equivalent so just pick one.
947+
if (ty1->isEqual(ty2))
948+
return ty1;
949+
950+
// Check to see if one is an optional of another. In that case, prefer the
951+
// optional since we can unwrap a single level when doing a lookup.
952+
{
953+
SmallVector<Type, 4> ty1Optionals;
954+
SmallVector<Type, 4> ty2Optionals;
955+
auto ty1Unwrapped = ty1->lookThroughAllOptionalTypes(ty1Optionals);
956+
auto ty2Unwrapped = ty2->lookThroughAllOptionalTypes(ty2Optionals);
957+
958+
if (ty1Unwrapped->isEqual(ty2Unwrapped)) {
959+
// We currently only unwrap a single level of optional, so if the
960+
// difference is greater, don't merge.
961+
if (ty1Optionals.size() == 1 && ty2Optionals.empty())
962+
return ty1;
963+
if (ty2Optionals.size() == 1 && ty1Optionals.empty())
964+
return ty2;
965+
}
966+
// We don't want to consider subtyping for optional mismatches since
967+
// optional promotion is modelled as a subtype, which isn't useful for us
968+
// (i.e if we have T? and U, preferring U would miss members on T?).
969+
if (ty1Optionals.size() != ty2Optionals.size())
970+
return Type();
971+
}
972+
973+
// In general we want to prefer a subtype over a supertype.
974+
if (isSubtypeOf(ty1, ty2, dc))
975+
return ty1;
976+
if (isSubtypeOf(ty2, ty1, dc))
977+
return ty2;
978+
979+
// Incomparable, return null.
980+
return Type();
981+
}
982+
944983
bool swift::isConvertibleTo(Type T1, Type T2, bool openArchetypes,
945984
DeclContext &DC) {
946985
return evaluateOrDefault(DC.getASTContext().evaluator,
947986
TypeRelationCheckRequest(TypeRelationCheckInput(&DC, T1, T2,
948987
TypeRelation::ConvertTo, openArchetypes)), false);
949988
}
950989

990+
bool swift::isSubtypeOf(Type T1, Type T2, DeclContext *DC) {
991+
return evaluateOrDefault(DC->getASTContext().evaluator,
992+
TypeRelationCheckRequest(TypeRelationCheckInput(DC, T1, T2,
993+
TypeRelation::SubtypeOf, /*openArchetypes*/ false)), false);
994+
}
995+
951996
Type swift::getRootTypeOfKeypathDynamicMember(SubscriptDecl *SD) {
952997
return evaluateOrDefault(SD->getASTContext().evaluator,
953998
RootTypeOfKeypathDynamicMemberRequest{SD}, Type());

lib/IDE/PostfixCompletion.cpp

+13-27
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,20 @@ 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) {
24+
bool PostfixCompletionCallback::Result::tryMerge(const Result &Other,
25+
DeclContext *DC) {
26+
if (BaseDecl != Other.BaseDecl)
2727
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-
}
3628

37-
void PostfixCompletionCallback::Result::merge(const Result &Other,
38-
DeclContext &DC) {
39-
assert(canBeMergedWith(Other, DC));
4029
// These properties should match if we are talking about the same BaseDecl.
4130
assert(IsBaseDeclUnapplied == Other.IsBaseDeclUnapplied);
4231
assert(BaseIsStaticMetaType == Other.BaseIsStaticMetaType);
4332

44-
if (!BaseTy->isEqual(Other.BaseTy) &&
45-
isConvertibleTo(Other.BaseTy, BaseTy, /*openArchetypes=*/true, DC)) {
46-
// Pick the more specific base type as it will produce more solutions.
47-
BaseTy = Other.BaseTy;
48-
}
33+
auto baseTy = tryMergeBaseTypeForCompletionLookup(BaseTy, Other.BaseTy, DC);
34+
if (!baseTy)
35+
return false;
36+
37+
BaseTy = baseTy;
4938

5039
// There could be multiple results that have different actor isolations if the
5140
// closure is an argument to a function that has multiple overloads with
@@ -66,18 +55,15 @@ void PostfixCompletionCallback::Result::merge(const Result &Other,
6655
ExpectsNonVoid &= Other.ExpectsNonVoid;
6756
IsImpliedResult |= Other.IsImpliedResult;
6857
IsInAsyncContext |= Other.IsInAsyncContext;
58+
return true;
6959
}
7060

7161
void PostfixCompletionCallback::addResult(const Result &Res) {
72-
auto ExistingRes =
73-
llvm::find_if(Results, [&Res, DC = DC](const Result &ExistingResult) {
74-
return ExistingResult.canBeMergedWith(Res, *DC);
75-
});
76-
if (ExistingRes != Results.end()) {
77-
ExistingRes->merge(Res, *DC);
78-
} else {
79-
Results.push_back(Res);
62+
for (auto idx : indices(Results)) {
63+
if (Results[idx].tryMerge(Res, DC))
64+
return;
8065
}
66+
Results.push_back(Res);
8167
}
8268

8369
void PostfixCompletionCallback::fallbackTypeCheck(DeclContext *DC) {

lib/IDE/UnresolvedMemberCompletion.cpp

+11-27
Original file line numberDiff line numberDiff line change
@@ -21,43 +21,27 @@ 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)) {
24+
bool UnresolvedMemberTypeCheckCompletionCallback::Result::tryMerge(
25+
const Result &Other, DeclContext *DC) {
26+
auto expectedTy = tryMergeBaseTypeForCompletionLookup(ExpectedTy,
27+
Other.ExpectedTy, DC);
28+
if (!expectedTy)
3029
return false;
31-
}
32-
return true;
33-
}
3430

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-
}
31+
ExpectedTy = expectedTy;
4532

4633
IsImpliedResult |= Other.IsImpliedResult;
4734
IsInAsyncContext |= Other.IsInAsyncContext;
35+
return true;
4836
}
4937

5038
void UnresolvedMemberTypeCheckCompletionCallback::addExprResult(
5139
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);
40+
for (auto idx : indices(ExprResults)) {
41+
if (ExprResults[idx].tryMerge(Res, DC))
42+
return;
6043
}
44+
ExprResults.push_back(Res);
6145
}
6246

6347
void UnresolvedMemberTypeCheckCompletionCallback::sawSolutionImpl(

lib/Sema/IDETypeCheckingRequests.cpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,14 @@ IsDeclApplicableRequest::evaluate(Evaluator &evaluator,
246246
bool
247247
TypeRelationCheckRequest::evaluate(Evaluator &evaluator,
248248
TypeRelationCheckInput Owner) const {
249-
std::optional<constraints::ConstraintKind> CKind;
249+
using namespace constraints;
250+
std::optional<ConstraintKind> CKind;
250251
switch (Owner.Relation) {
251252
case TypeRelation::ConvertTo:
252-
CKind = constraints::ConstraintKind::Conversion;
253+
CKind = ConstraintKind::Conversion;
254+
break;
255+
case TypeRelation::SubtypeOf:
256+
CKind = ConstraintKind::Subtype;
253257
break;
254258
}
255259
assert(CKind.has_value());
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t
3+
4+
// REQUIRES: objc_interop
5+
6+
import Foundation
7+
8+
func foo(_ x: CGFloat) {}
9+
func foo(_ x: Double) {}
10+
11+
// Make sure we suggest completions for both CGFloat and Double.
12+
foo(.#^FOO^#)
13+
// FOO-DAG: Decl[Constructor]/CurrNominal/TypeRelation[Convertible]: init()[#CGFloat#]; name=init()
14+
// FOO-DAG: Decl[Constructor]/CurrNominal/IsSystem/TypeRelation[Convertible]: init()[#Double#]; name=init()

test/IDE/complete_rdar126168123.swift

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t
3+
4+
// rdar://126168123
5+
6+
protocol MyProto {}
7+
protocol MyProto2 {}
8+
9+
struct MyStruct : MyProto {}
10+
11+
extension MyProto where Self == MyStruct {
12+
static var automatic: MyStruct { fatalError() }
13+
}
14+
15+
func use<T: MyProto>(_ someT: T) {}
16+
func use<T: MyProto2>(_ someT: T) {}
17+
18+
func test() {
19+
use(.#^COMPLETE^#)
20+
}
21+
22+
// COMPLETE: Decl[StaticVar]/CurrNominal/TypeRelation[Convertible]: automatic[#MyStruct#]; name=automatic
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t
3+
4+
class C {
5+
static func cMethod() -> C {}
6+
}
7+
class D : C {
8+
static func dMethod() -> D {}
9+
}
10+
11+
func test1(_ x: C) {}
12+
func test1(_ x: D) {}
13+
14+
// We prefer the subtype here, so we show completions for D.
15+
test1(.#^TEST1^#)
16+
// TEST1-DAG: Decl[StaticMethod]/Super: cMethod()[#C#]; name=cMethod()
17+
// TEST1-DAG: Decl[StaticMethod]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: dMethod()[#D#]; name=dMethod()
18+
// TEST1-DAG: Decl[Constructor]/CurrNominal/TypeRelation[Convertible]: init()[#D#]; name=init()
19+
20+
func test2(_ x: C?) {}
21+
func test2(_ x: D?) {}
22+
23+
test2(.#^TEST2^#)
24+
// TEST2-DAG: Decl[StaticMethod]/Super: cMethod()[#C#]; name=cMethod()
25+
// TEST2-DAG: Decl[StaticMethod]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: dMethod()[#D#]; name=dMethod()
26+
// TEST2-DAG: Decl[Constructor]/CurrNominal/TypeRelation[Convertible]: init()[#D#]; name=init()
27+
// TEST2-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Convertible]: none[#Optional<D>#]; name=none
28+
// TEST2-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Convertible]: some({#D#})[#Optional<D>#]; name=some()
29+
30+
func test3(_ x: C?) {}
31+
func test3(_ x: D) {}
32+
33+
// We can still provide both C and D completions here.
34+
test3(.#^TEST3^#)
35+
// TEST3-DAG: Decl[StaticMethod]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: cMethod()[#C#]; name=cMethod()
36+
// TEST3-DAG: Decl[StaticMethod]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: dMethod()[#D#]; name=dMethod()
37+
// TEST3-DAG: Decl[Constructor]/CurrNominal/TypeRelation[Convertible]: init()[#D#]; name=init()
38+
// TEST3-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Convertible]: none[#Optional<C>#]; name=none
39+
// TEST3-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Convertible]: some({#C#})[#Optional<C>#]; name=some()
40+
41+
func test4(_ x: Int) {}
42+
func test4(_ x: AnyHashable) {}
43+
44+
// Make sure we show Int completions.
45+
test4(.#^TEST4^#)
46+
// TEST4: Decl[StaticVar]/Super/Flair[ExprSpecific]/IsSystem/TypeRelation[Convertible]: zero[#Int#]; name=zero
47+
48+
protocol P {}
49+
extension P {
50+
func pMethod() {}
51+
}
52+
struct S : P {
53+
func sMethod() {}
54+
}
55+
56+
func test5() -> any P {}
57+
func test5() -> S {}
58+
59+
test5().#^TEST5^#
60+
// TEST5-DAG: Decl[InstanceMethod]/CurrNominal: pMethod()[#Void#]; name=pMethod()
61+
// TEST5-DAG: Decl[InstanceMethod]/CurrNominal: sMethod()[#Void#]; name=sMethod()

0 commit comments

Comments
 (0)