Skip to content

[CodeCompletion] Map the result type for keypath member lookup #25717

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions include/swift/Sema/IDETypeChecking.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,14 @@ namespace swift {
/// @dynamicMemberLookup attribute on it.
bool hasDynamicMemberLookupAttribute(Type type);

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

#endif
76 changes: 72 additions & 4 deletions lib/IDE/CodeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1988,12 +1988,80 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {

Type getTypeOfMember(const ValueDecl *VD,
DynamicLookupInfo dynamicLookupInfo) {
if (dynamicLookupInfo.getKind() == DynamicLookupInfo::None) {
switch (dynamicLookupInfo.getKind()) {
case DynamicLookupInfo::None:
return getTypeOfMember(VD, this->ExprType);
} else {
// FIXME: for keypath dynamic members we should substitute the subscript
// return type; for now just avoid substituting at all by passing null.
case DynamicLookupInfo::AnyObject:
return getTypeOfMember(VD, Type());
case DynamicLookupInfo::KeyPathDynamicMember: {
auto &keyPathInfo = dynamicLookupInfo.getKeyPathDynamicMember();

// Map the result of VD to keypath member lookup results.
// Given:
// struct Wrapper<T> {
// subscript<U>(dynamicMember: KeyPath<T, U>) -> Wrapped<U> { get }
// }
// struct Circle {
// var center: Point { get }
// var radius: Length { get }
// }
//
// Consider 'Wrapper<Circle>.center'.
// 'VD' is 'Circle.center' decl.
// 'keyPathInfo.subscript' is 'Wrapper<T>.subscript' decl.
// 'keyPathInfo.baseType' is 'Wrapper<Circle>' type.

// FIXME: Handle nested keypath member lookup.
// i.e. cases where 'ExprType' != 'keyPathInfo.baseType'.

auto *SD = keyPathInfo.subscript;
auto elementTy = SD->getElementTypeLoc().getType();
if (!elementTy->hasTypeParameter())
return elementTy;

// Map is:
// { τ_0_0(T) => Circle
// τ_1_0(U) => U }
auto subs = keyPathInfo.baseType->getMemberSubstitutions(SD);

// Extract the root and result type of the KeyPath type in the parameter.
// i.e. 'T' and 'U'
auto rootAndResult =
getRootAndResultTypeOfKeypathDynamicMember(SD, CurrDeclContext);

// If the keyPath result type has type parameters, that might affect the
// subscript result type.
auto keyPathResultTy = rootAndResult->second->mapTypeOutOfContext();
if (keyPathResultTy->hasTypeParameter()) {
auto keyPathRootTy =
rootAndResult->first.subst(QueryTypeSubstitutionMap{subs},
LookUpConformanceInModule(CurrModule));

// The result type of the VD.
// i.e. 'Circle.center' => 'Point'.
auto innerResultTy = getTypeOfMember(VD, keyPathRootTy);

if (auto paramTy = keyPathResultTy->getAs<GenericTypeParamType>()) {
// Replace keyPath result type in the map with the inner result type.
// i.e. Make the map as:
// { τ_0_0(T) => Circle
// τ_1_0(U) => Point }
auto key =
paramTy->getCanonicalType()->castTo<GenericTypeParamType>();
subs[key] = innerResultTy;
} else {
// FIXME: Handle the case where the KeyPath result is generic.
// e.g. 'subscript<U>(dynamicMember: KeyPath<T, Box<U>>) -> Bag<U>'
// For now, just return the inner type.
return innerResultTy;
}
}

// Substitute the element type of the subscript using modified map.
// i.e. 'Wrapped<U>' => 'Wrapped<Point>'.
return elementTy.subst(QueryTypeSubstitutionMap{subs},
LookUpConformanceInModule(CurrModule));
}
}
}

Expand Down
12 changes: 7 additions & 5 deletions lib/Sema/LookupVisibleDecls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -973,18 +973,20 @@ static void lookupVisibleDynamicMemberLookupDecls(
if (!subscript)
continue;

auto rootType = getRootTypeOfKeypathDynamicMember(subscript, dc);
if (!rootType)
auto rootAndResult =
getRootAndResultTypeOfKeypathDynamicMember(subscript, dc);
if (!rootAndResult)
continue;
auto rootType = rootAndResult->first;

auto subs =
baseType->getMemberSubstitutionMap(dc->getParentModule(), subscript);
auto memberType = rootType->subst(subs);
auto memberType = rootType.subst(subs);
if (!memberType || !memberType->mayHaveMembers())
continue;

KeyPathDynamicMemberConsumer::SubscriptChange(consumer, subscript,
baseType);
KeyPathDynamicMemberConsumer::SubscriptChange sub(consumer, subscript,
baseType);

lookupVisibleMemberAndDynamicMemberDecls(memberType, consumer, consumer, dc,
LS, reason, typeResolver, GSB,
Expand Down
13 changes: 6 additions & 7 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1028,9 +1028,9 @@ bool swift::isValidKeyPathDynamicMemberLookup(SubscriptDecl *decl,
return false;
}

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

if (!isValidKeyPathDynamicMemberLookup(subscript, TC))
Expand All @@ -1040,11 +1040,10 @@ swift::getRootTypeOfKeypathDynamicMember(SubscriptDecl *subscript,
auto keyPathType = param->getType()->getAs<BoundGenericType>();
if (!keyPathType)
return None;

assert(!keyPathType->getGenericArgs().empty() &&
auto genericArgs = keyPathType->getGenericArgs();
assert(!genericArgs.empty() && genericArgs.size() == 2 &&
"invalid keypath dynamic member");
auto rootType = keyPathType->getGenericArgs()[0];
return rootType;
return std::pair<Type, Type>{genericArgs[0], genericArgs[1]};
}

/// The @dynamicMemberLookup attribute is only allowed on types that have at
Expand Down
50 changes: 34 additions & 16 deletions test/IDE/complete_keypath_member_lookup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testInvalid3 | %FileCheck %s -check-prefix=testInvalid3
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testInvalid4 | %FileCheck %s -check-prefix=testInvalid4
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testGenericRoot1 | %FileCheck %s -check-prefix=testGenericRoot1
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testGenericResult1 | %FileCheck %s -check-prefix=testGenericResult1
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testAnyObjectRoot1 | %FileCheck %s -check-prefix=testAnyObjectRoot1
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testNested1 | %FileCheck %s -check-prefix=testNested1
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=testNested2 | %FileCheck %s -check-prefix=testNested2
Expand Down Expand Up @@ -51,28 +52,25 @@ func testMembersPostfix1(r: Lens<Rectangle>) {
// testMembersPostfix1: Begin completions
// testMembersPostfix1-DAG: Decl[Subscript]/CurrNominal: [{#dynamicMember: WritableKeyPath<Rectangle, U>#}][#Lens<U>#];

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

func testMembersDot1(r: Lens<Rectangle>) {
r.#^testMembersDot1^#
}
// testMembersDot1: Begin completions
// FIXME: the type should be Lens<Point>
// testMembersDot1-DAG: Decl[InstanceVar]/CurrNominal: topLeft[#Point#];
// testMembersDot1-DAG: Decl[InstanceVar]/CurrNominal: bottomRight[#Point#];
// testMembersDot1-DAG: Decl[InstanceVar]/CurrNominal: topLeft[#Lens<Point>#];
// testMembersDot1-DAG: Decl[InstanceVar]/CurrNominal: bottomRight[#Lens<Point>#];
// testMembersDot1: End completions

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

// testMembersDot2: Begin completions
// FIXME: the type should be Lens<Int>
// testMembersDot2-DAG: Decl[InstanceVar]/CurrNominal: x[#Int#];
// testMembersDot2-DAG: Decl[InstanceVar]/CurrNominal: y[#Int#];
// testMembersDot2-DAG: Decl[InstanceVar]/CurrNominal: x[#Lens<Int>#];
// testMembersDot2-DAG: Decl[InstanceVar]/CurrNominal: y[#Lens<Int>#];
// testMembersDot2: End completions

@dynamicMemberLookup
Expand Down Expand Up @@ -227,7 +225,7 @@ extension Lens where T: HalfRect {
}
}
// testSelfExtension1-NOT: bottomRight
// testSelfExtension1: Decl[InstanceVar]/CurrNominal: topLeft[#Point#];
// testSelfExtension1: Decl[InstanceVar]/CurrNominal: topLeft[#Lens<Point>#];
// testSelfExtension1-NOT: bottomRight

struct Invalid1 {
Expand Down Expand Up @@ -289,8 +287,26 @@ struct GenericRoot<T> {
func testGenericRoot1(r: GenericRoot<Point>) {
r.#^testGenericRoot1^#
}
// FIXME: Type should be substituted to Int.
// testGenericRoot1: Decl[InstanceVar]/CurrNominal: foo[#T#];
// testGenericRoot1: Decl[InstanceVar]/CurrNominal: foo[#Int#];

@dynamicMemberLookup
struct GenericResult<T> {
subscript<U>(dynamicMember member: KeyPath<T, Gen1<U>>) -> GenericResult<U> {
fatalError()
}
}
struct BoxedCircle {
var center: Gen1<Point>
var radius: Gen1<Int>
}
func testGenericResult1(r: GenericResult<BoxedCircle>) {
r.#^testGenericResult1^#
}
// testGenericResult1: Begin completions
// FIXME: the type should be 'GenericResult<Point>'
// testGenericResult1-DAG: Decl[InstanceVar]/CurrNominal: center[#Gen1<Point>#]; name=center
// testGenericResult1-DAG: Decl[InstanceVar]/CurrNominal: radius[#Gen1<Int>#]; name=radius
// testGenericResult1: End completions

class C {
var someUniqueName: Int = 0
Expand All @@ -312,16 +328,18 @@ func testAnyObjectRoot1(r: AnyObjectRoot) {
func testNested1(r: Lens<Lens<Point>>) {
r.#^testNested1^#
// testNested1: Begin completions
// testNested1-DAG: Decl[InstanceVar]/CurrNominal: x[#Int#];
// testNested1-DAG: Decl[InstanceVar]/CurrNominal: y[#Int#];
// FIXME: The type should be 'Lens<Lens<Int>>'
// testNested1-DAG: Decl[InstanceVar]/CurrNominal: x[#Lens<Int>#];
// testNested1-DAG: Decl[InstanceVar]/CurrNominal: y[#Lens<Int>#];
// testNested1: End completions
}

func testNested2(r: Lens<Lens<Lens<Point>>>) {
r.#^testNested2^#
// testNested2: Begin completions
// testNested2-DAG: Decl[InstanceVar]/CurrNominal: x[#Int#];
// testNested2-DAG: Decl[InstanceVar]/CurrNominal: y[#Int#];
// FIXME: The type should be 'Lens<Lens<Lens<Int>>>'
// testNested2-DAG: Decl[InstanceVar]/CurrNominal: x[#Lens<Int>#];
// testNested2-DAG: Decl[InstanceVar]/CurrNominal: y[#Lens<Int>#];
// testNested2: End completions
}

Expand Down