Skip to content

Commit e7c8e22

Browse files
authored
Merge pull request #25717 from rintaro/ide-completion-keypathlookup-rdar50073837
[CodeCompletion] Map the result type for keypath member lookup
2 parents f54ff7f + 9f02fa7 commit e7c8e22

File tree

5 files changed

+124
-36
lines changed

5 files changed

+124
-36
lines changed

include/swift/Sema/IDETypeChecking.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,14 @@ namespace swift {
232232
/// @dynamicMemberLookup attribute on it.
233233
bool hasDynamicMemberLookupAttribute(Type type);
234234

235-
/// Returns the root type of the keypath type in a keypath dynamic member
236-
/// lookup subscript, or \c None if it cannot be determined.
235+
/// Returns the root type and result type of the keypath type in a keypath
236+
/// dynamic member lookup subscript, or \c None if it cannot be determined.
237237
///
238238
/// \param subscript The potential keypath dynamic member lookup subscript.
239239
/// \param DC The DeclContext from which the subscript is being referenced.
240-
Optional<Type> getRootTypeOfKeypathDynamicMember(SubscriptDecl *subscript,
241-
const DeclContext *DC);
240+
Optional<std::pair<Type, Type>>
241+
getRootAndResultTypeOfKeypathDynamicMember(SubscriptDecl *subscript,
242+
const DeclContext *DC);
242243
}
243244

244245
#endif

lib/IDE/CodeCompletion.cpp

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,12 +1994,80 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
19941994

19951995
Type getTypeOfMember(const ValueDecl *VD,
19961996
DynamicLookupInfo dynamicLookupInfo) {
1997-
if (dynamicLookupInfo.getKind() == DynamicLookupInfo::None) {
1997+
switch (dynamicLookupInfo.getKind()) {
1998+
case DynamicLookupInfo::None:
19981999
return getTypeOfMember(VD, this->ExprType);
1999-
} else {
2000-
// FIXME: for keypath dynamic members we should substitute the subscript
2001-
// return type; for now just avoid substituting at all by passing null.
2000+
case DynamicLookupInfo::AnyObject:
20022001
return getTypeOfMember(VD, Type());
2002+
case DynamicLookupInfo::KeyPathDynamicMember: {
2003+
auto &keyPathInfo = dynamicLookupInfo.getKeyPathDynamicMember();
2004+
2005+
// Map the result of VD to keypath member lookup results.
2006+
// Given:
2007+
// struct Wrapper<T> {
2008+
// subscript<U>(dynamicMember: KeyPath<T, U>) -> Wrapped<U> { get }
2009+
// }
2010+
// struct Circle {
2011+
// var center: Point { get }
2012+
// var radius: Length { get }
2013+
// }
2014+
//
2015+
// Consider 'Wrapper<Circle>.center'.
2016+
// 'VD' is 'Circle.center' decl.
2017+
// 'keyPathInfo.subscript' is 'Wrapper<T>.subscript' decl.
2018+
// 'keyPathInfo.baseType' is 'Wrapper<Circle>' type.
2019+
2020+
// FIXME: Handle nested keypath member lookup.
2021+
// i.e. cases where 'ExprType' != 'keyPathInfo.baseType'.
2022+
2023+
auto *SD = keyPathInfo.subscript;
2024+
auto elementTy = SD->getElementTypeLoc().getType();
2025+
if (!elementTy->hasTypeParameter())
2026+
return elementTy;
2027+
2028+
// Map is:
2029+
// { τ_0_0(T) => Circle
2030+
// τ_1_0(U) => U }
2031+
auto subs = keyPathInfo.baseType->getMemberSubstitutions(SD);
2032+
2033+
// Extract the root and result type of the KeyPath type in the parameter.
2034+
// i.e. 'T' and 'U'
2035+
auto rootAndResult =
2036+
getRootAndResultTypeOfKeypathDynamicMember(SD, CurrDeclContext);
2037+
2038+
// If the keyPath result type has type parameters, that might affect the
2039+
// subscript result type.
2040+
auto keyPathResultTy = rootAndResult->second->mapTypeOutOfContext();
2041+
if (keyPathResultTy->hasTypeParameter()) {
2042+
auto keyPathRootTy =
2043+
rootAndResult->first.subst(QueryTypeSubstitutionMap{subs},
2044+
LookUpConformanceInModule(CurrModule));
2045+
2046+
// The result type of the VD.
2047+
// i.e. 'Circle.center' => 'Point'.
2048+
auto innerResultTy = getTypeOfMember(VD, keyPathRootTy);
2049+
2050+
if (auto paramTy = keyPathResultTy->getAs<GenericTypeParamType>()) {
2051+
// Replace keyPath result type in the map with the inner result type.
2052+
// i.e. Make the map as:
2053+
// { τ_0_0(T) => Circle
2054+
// τ_1_0(U) => Point }
2055+
auto key =
2056+
paramTy->getCanonicalType()->castTo<GenericTypeParamType>();
2057+
subs[key] = innerResultTy;
2058+
} else {
2059+
// FIXME: Handle the case where the KeyPath result is generic.
2060+
// e.g. 'subscript<U>(dynamicMember: KeyPath<T, Box<U>>) -> Bag<U>'
2061+
// For now, just return the inner type.
2062+
return innerResultTy;
2063+
}
2064+
}
2065+
2066+
// Substitute the element type of the subscript using modified map.
2067+
// i.e. 'Wrapped<U>' => 'Wrapped<Point>'.
2068+
return elementTy.subst(QueryTypeSubstitutionMap{subs},
2069+
LookUpConformanceInModule(CurrModule));
2070+
}
20032071
}
20042072
}
20052073

lib/Sema/LookupVisibleDecls.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -973,18 +973,20 @@ static void lookupVisibleDynamicMemberLookupDecls(
973973
if (!subscript)
974974
continue;
975975

976-
auto rootType = getRootTypeOfKeypathDynamicMember(subscript, dc);
977-
if (!rootType)
976+
auto rootAndResult =
977+
getRootAndResultTypeOfKeypathDynamicMember(subscript, dc);
978+
if (!rootAndResult)
978979
continue;
980+
auto rootType = rootAndResult->first;
979981

980982
auto subs =
981983
baseType->getMemberSubstitutionMap(dc->getParentModule(), subscript);
982-
auto memberType = rootType->subst(subs);
984+
auto memberType = rootType.subst(subs);
983985
if (!memberType || !memberType->mayHaveMembers())
984986
continue;
985987

986-
KeyPathDynamicMemberConsumer::SubscriptChange(consumer, subscript,
987-
baseType);
988+
KeyPathDynamicMemberConsumer::SubscriptChange sub(consumer, subscript,
989+
baseType);
988990

989991
lookupVisibleMemberAndDynamicMemberDecls(memberType, consumer, consumer, dc,
990992
LS, reason, typeResolver, GSB,

lib/Sema/TypeCheckAttr.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,9 +1028,9 @@ bool swift::isValidKeyPathDynamicMemberLookup(SubscriptDecl *decl,
10281028
return false;
10291029
}
10301030

1031-
Optional<Type>
1032-
swift::getRootTypeOfKeypathDynamicMember(SubscriptDecl *subscript,
1033-
const DeclContext *DC) {
1031+
Optional<std::pair<Type, Type>>
1032+
swift::getRootAndResultTypeOfKeypathDynamicMember(SubscriptDecl *subscript,
1033+
const DeclContext *DC) {
10341034
auto &TC = TypeChecker::createForContext(DC->getASTContext());
10351035

10361036
if (!isValidKeyPathDynamicMemberLookup(subscript, TC))
@@ -1040,11 +1040,10 @@ swift::getRootTypeOfKeypathDynamicMember(SubscriptDecl *subscript,
10401040
auto keyPathType = param->getType()->getAs<BoundGenericType>();
10411041
if (!keyPathType)
10421042
return None;
1043-
1044-
assert(!keyPathType->getGenericArgs().empty() &&
1043+
auto genericArgs = keyPathType->getGenericArgs();
1044+
assert(!genericArgs.empty() && genericArgs.size() == 2 &&
10451045
"invalid keypath dynamic member");
1046-
auto rootType = keyPathType->getGenericArgs()[0];
1047-
return rootType;
1046+
return std::pair<Type, Type>{genericArgs[0], genericArgs[1]};
10481047
}
10491048

10501049
/// The @dynamicMemberLookup attribute is only allowed on types that have at

test/IDE/complete_keypath_member_lookup.swift

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testInvalid3 | %FileCheck %s -check-prefix=testInvalid3
1818
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testInvalid4 | %FileCheck %s -check-prefix=testInvalid4
1919
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testGenericRoot1 | %FileCheck %s -check-prefix=testGenericRoot1
20+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testGenericResult1 | %FileCheck %s -check-prefix=testGenericResult1
2021
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testAnyObjectRoot1 | %FileCheck %s -check-prefix=testAnyObjectRoot1
2122
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testNested1 | %FileCheck %s -check-prefix=testNested1
2223
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testNested2 | %FileCheck %s -check-prefix=testNested2
@@ -51,28 +52,25 @@ func testMembersPostfix1(r: Lens<Rectangle>) {
5152
// testMembersPostfix1: Begin completions
5253
// testMembersPostfix1-DAG: Decl[Subscript]/CurrNominal: [{#dynamicMember: WritableKeyPath<Rectangle, U>#}][#Lens<U>#];
5354

54-
// FIXME: the type should be Lens<Point>
55-
// testMembersPostfix1-DAG: Decl[InstanceVar]/CurrNominal: .topLeft[#Point#];
56-
// testMembersPostfix1-DAG: Decl[InstanceVar]/CurrNominal: .bottomRight[#Point#];
55+
// testMembersPostfix1-DAG: Decl[InstanceVar]/CurrNominal: .topLeft[#Lens<Point>#];
56+
// testMembersPostfix1-DAG: Decl[InstanceVar]/CurrNominal: .bottomRight[#Lens<Point>#];
5757
// testMembersPostfix1: End completions
5858

5959
func testMembersDot1(r: Lens<Rectangle>) {
6060
r.#^testMembersDot1^#
6161
}
6262
// testMembersDot1: Begin completions
63-
// FIXME: the type should be Lens<Point>
64-
// testMembersDot1-DAG: Decl[InstanceVar]/CurrNominal: topLeft[#Point#];
65-
// testMembersDot1-DAG: Decl[InstanceVar]/CurrNominal: bottomRight[#Point#];
63+
// testMembersDot1-DAG: Decl[InstanceVar]/CurrNominal: topLeft[#Lens<Point>#];
64+
// testMembersDot1-DAG: Decl[InstanceVar]/CurrNominal: bottomRight[#Lens<Point>#];
6665
// testMembersDot1: End completions
6766

6867
func testMembersDot2(r: Lens<Rectangle>) {
6968
r.topLeft.#^testMembersDot2^#
7069
}
7170

7271
// testMembersDot2: Begin completions
73-
// FIXME: the type should be Lens<Int>
74-
// testMembersDot2-DAG: Decl[InstanceVar]/CurrNominal: x[#Int#];
75-
// testMembersDot2-DAG: Decl[InstanceVar]/CurrNominal: y[#Int#];
72+
// testMembersDot2-DAG: Decl[InstanceVar]/CurrNominal: x[#Lens<Int>#];
73+
// testMembersDot2-DAG: Decl[InstanceVar]/CurrNominal: y[#Lens<Int>#];
7674
// testMembersDot2: End completions
7775

7876
@dynamicMemberLookup
@@ -227,7 +225,7 @@ extension Lens where T: HalfRect {
227225
}
228226
}
229227
// testSelfExtension1-NOT: bottomRight
230-
// testSelfExtension1: Decl[InstanceVar]/CurrNominal: topLeft[#Point#];
228+
// testSelfExtension1: Decl[InstanceVar]/CurrNominal: topLeft[#Lens<Point>#];
231229
// testSelfExtension1-NOT: bottomRight
232230

233231
struct Invalid1 {
@@ -289,8 +287,26 @@ struct GenericRoot<T> {
289287
func testGenericRoot1(r: GenericRoot<Point>) {
290288
r.#^testGenericRoot1^#
291289
}
292-
// FIXME: Type should be substituted to Int.
293-
// testGenericRoot1: Decl[InstanceVar]/CurrNominal: foo[#T#];
290+
// testGenericRoot1: Decl[InstanceVar]/CurrNominal: foo[#Int#];
291+
292+
@dynamicMemberLookup
293+
struct GenericResult<T> {
294+
subscript<U>(dynamicMember member: KeyPath<T, Gen1<U>>) -> GenericResult<U> {
295+
fatalError()
296+
}
297+
}
298+
struct BoxedCircle {
299+
var center: Gen1<Point>
300+
var radius: Gen1<Int>
301+
}
302+
func testGenericResult1(r: GenericResult<BoxedCircle>) {
303+
r.#^testGenericResult1^#
304+
}
305+
// testGenericResult1: Begin completions
306+
// FIXME: the type should be 'GenericResult<Point>'
307+
// testGenericResult1-DAG: Decl[InstanceVar]/CurrNominal: center[#Gen1<Point>#]; name=center
308+
// testGenericResult1-DAG: Decl[InstanceVar]/CurrNominal: radius[#Gen1<Int>#]; name=radius
309+
// testGenericResult1: End completions
294310

295311
class C {
296312
var someUniqueName: Int = 0
@@ -312,16 +328,18 @@ func testAnyObjectRoot1(r: AnyObjectRoot) {
312328
func testNested1(r: Lens<Lens<Point>>) {
313329
r.#^testNested1^#
314330
// testNested1: Begin completions
315-
// testNested1-DAG: Decl[InstanceVar]/CurrNominal: x[#Int#];
316-
// testNested1-DAG: Decl[InstanceVar]/CurrNominal: y[#Int#];
331+
// FIXME: The type should be 'Lens<Lens<Int>>'
332+
// testNested1-DAG: Decl[InstanceVar]/CurrNominal: x[#Lens<Int>#];
333+
// testNested1-DAG: Decl[InstanceVar]/CurrNominal: y[#Lens<Int>#];
317334
// testNested1: End completions
318335
}
319336

320337
func testNested2(r: Lens<Lens<Lens<Point>>>) {
321338
r.#^testNested2^#
322339
// testNested2: Begin completions
323-
// testNested2-DAG: Decl[InstanceVar]/CurrNominal: x[#Int#];
324-
// testNested2-DAG: Decl[InstanceVar]/CurrNominal: y[#Int#];
340+
// FIXME: The type should be 'Lens<Lens<Lens<Int>>>'
341+
// testNested2-DAG: Decl[InstanceVar]/CurrNominal: x[#Lens<Int>#];
342+
// testNested2-DAG: Decl[InstanceVar]/CurrNominal: y[#Lens<Int>#];
325343
// testNested2: End completions
326344
}
327345

0 commit comments

Comments
 (0)