Skip to content

Commit b388d83

Browse files
committed
[ConstraintSystem] Make sure that keypath dynamic member lookup could be used recursively
For example if there a structure which requires multiple implicit dynamic member calls to get the members: ```swift struct Point { var x, y: Int } @dynamicMemberLookup struct Lens<T> { var obj: T subscript<U>(dynamicMember member: KeyPath<T, U>) -> U { get { return obj[keyPath: member] } } } func foo(_ lens: Lens<Lens<Point>>) { _ = lens.x _ = lens.obj.x _ = lens.obj.obj.x } _ = \Lens<Lens<Point>>.x ```
1 parent 2e8d163 commit b388d83

File tree

3 files changed

+92
-25
lines changed

3 files changed

+92
-25
lines changed

lib/Sema/CSApply.cpp

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,12 +1545,44 @@ namespace {
15451545
/*parsedRoot=*/nullptr,
15461546
/*parsedPath=*/anchor,
15471547
/*isImplicit=*/true);
1548+
// Type of the keypath expression we are forming is known
1549+
// in advance, so let's set it right away.
1550+
keyPath->setType(keyPathTy);
1551+
cs.cacheType(keyPath);
15481552

15491553
auto *componentLoc = cs.getConstraintLocator(
15501554
memberLoc,
15511555
LocatorPathElt::getKeyPathDynamicMember(keyPathTy->getAnyNominal()));
15521556
auto overload = solution.getOverloadChoice(componentLoc);
15531557

1558+
auto buildSubscriptComponent = [&](SourceLoc loc, Expr *indexExpr,
1559+
ArrayRef<Identifier> labels) {
1560+
// Save a reference to the component so we can do a post-pass to check
1561+
// the Hashable conformance of the indexes.
1562+
KeyPathSubscriptComponents.push_back({keyPath, 0});
1563+
return buildKeyPathSubscriptComponent(overload, loc, indexExpr, labels,
1564+
componentLoc);
1565+
};
1566+
1567+
auto getKeyPathComponentIndex =
1568+
[](ConstraintLocator *locator) -> unsigned {
1569+
for (const auto &elt : locator->getPath()) {
1570+
if (elt.getKind() == ConstraintLocator::KeyPathComponent)
1571+
return elt.getValue();
1572+
}
1573+
llvm_unreachable("no keypath component node");
1574+
};
1575+
1576+
// Looks like there is a chain of implicit `subscript(dynamicMember:)`
1577+
// calls necessary to resolve a member reference.
1578+
if (overload.choice.getKind() ==
1579+
OverloadChoiceKind::KeyPathDynamicMemberLookup) {
1580+
keyPath->resolveComponents(
1581+
ctx, buildSubscriptComponent(dotLoc, /*indexExpr=*/nullptr,
1582+
ctx.Id_dynamicMember));
1583+
return keyPath;
1584+
}
1585+
15541586
// We can't reuse existing expression because type-check
15551587
// based diagnostics could hold the reference to original AST.
15561588
Expr *componentExpr = nullptr;
@@ -1564,13 +1596,8 @@ namespace {
15641596
// of a keypath expression e.g. `\Lens<[Int]>.count` where
15651597
// `count` is referenced using dynamic lookup.
15661598
if (auto *KPE = dyn_cast<KeyPathExpr>(anchor)) {
1567-
auto path = memberLoc->getPath();
1568-
if (memberLoc->isSubscriptMemberRef())
1569-
path = path.drop_back();
1570-
1571-
auto &componentIdx = path.back();
1572-
assert(componentIdx.getKind() == ConstraintLocator::KeyPathComponent);
1573-
auto &origComponent = KPE->getComponents()[componentIdx.getValue()];
1599+
auto componentIdx = getKeyPathComponentIndex(memberLoc);
1600+
auto &origComponent = KPE->getComponents()[componentIdx];
15741601

15751602
using ComponentKind = KeyPathExpr::Component::Kind;
15761603
if (origComponent.getKind() == ComponentKind::UnresolvedProperty) {
@@ -1630,12 +1657,8 @@ namespace {
16301657
/*implicit=*/true, SE->getAccessSemantics());
16311658
}
16321659

1633-
component = buildKeyPathSubscriptComponent(
1634-
overload, SE->getLoc(), SE->getIndex(), SE->getArgumentLabels(),
1635-
componentLoc);
1636-
// Save a reference to the component so we can do a post-pass to check
1637-
// the Hashable conformance of the indexes.
1638-
KeyPathSubscriptComponents.push_back({keyPath, 0});
1660+
component = buildSubscriptComponent(SE->getLoc(), SE->getIndex(),
1661+
SE->getArgumentLabels());
16391662
} else {
16401663
return nullptr;
16411664
}
@@ -1646,8 +1669,6 @@ namespace {
16461669

16471670
keyPath->setParsedPath(componentExpr);
16481671
keyPath->resolveComponents(ctx, {component});
1649-
keyPath->setType(keyPathTy);
1650-
cs.cacheType(keyPath);
16511672
return keyPath;
16521673
}
16531674

@@ -2815,12 +2836,7 @@ namespace {
28152836
// Figure out the expected type of the lookup parameter. We know the
28162837
// openedFullType will be "xType -> indexType -> resultType". Dig out
28172838
// its index type.
2818-
auto declTy = solution.simplifyType(overload.openedFullType);
2819-
auto subscriptTy = declTy->castTo<FunctionType>()->getResult();
2820-
auto refFnType = subscriptTy->castTo<FunctionType>();
2821-
assert(refFnType->getParams().size() == 1 &&
2822-
"subscript always has one arg");
2823-
auto paramTy = refFnType->getParams()[0].getPlainType();
2839+
auto paramTy = getTypeOfDynamicMemberIndex(overload);
28242840

28252841
Expr *argExpr = nullptr;
28262842
if (overload.choice.getKind() ==
@@ -2855,6 +2871,20 @@ namespace {
28552871
/*isImplicit*/ false, AccessSemantics::Ordinary, overload);
28562872
}
28572873

2874+
Type getTypeOfDynamicMemberIndex(const SelectedOverload &overload) {
2875+
assert(overload.choice.getKind() ==
2876+
OverloadChoiceKind::DynamicMemberLookup ||
2877+
overload.choice.getKind() ==
2878+
OverloadChoiceKind::KeyPathDynamicMemberLookup);
2879+
2880+
auto declTy = solution.simplifyType(overload.openedFullType);
2881+
auto subscriptTy = declTy->castTo<FunctionType>()->getResult();
2882+
auto refFnType = subscriptTy->castTo<FunctionType>();
2883+
assert(refFnType->getParams().size() == 1 &&
2884+
"subscript always has one arg");
2885+
return refFnType->getParams()[0].getPlainType();
2886+
}
2887+
28582888
public:
28592889
Expr *visitUnresolvedDotExpr(UnresolvedDotExpr *expr) {
28602890
return applyMemberRefExpr(expr, expr->getBase(), expr->getDotLoc(),
@@ -4599,10 +4629,9 @@ namespace {
45994629

46004630
if (overload.choice.getKind() ==
46014631
OverloadChoiceKind::KeyPathDynamicMemberLookup) {
4602-
auto fnType = overload.openedType->castTo<FunctionType>();
4603-
auto keyPathTy = simplifyType(fnType->getParams()[0].getPlainType());
4632+
auto indexType = getTypeOfDynamicMemberIndex(overload);
46044633
indexExpr = buildKeyPathDynamicMemberIndexExpr(
4605-
keyPathTy->castTo<BoundGenericType>(), componentLoc, locator);
4634+
indexType->castTo<BoundGenericType>(), componentLoc, locator);
46064635
} else {
46074636
auto fieldName = overload.choice.getName().getBaseIdentifier().str();
46084637
indexExpr = buildDynamicMemberLookupIndexExpr(fieldName, componentLoc,

test/Constraints/keypath_dynamic_member_lookup.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,27 @@ func test_keypath_dynamic_lookup_inside_keypath() {
226226
// CHECK-NEXT: keypath $KeyPath<Lens<Array<Array<Int>>>, Lens<Int>>, (root $Lens<Array<Array<Int>>>; settable_property $Lens<Array<Int>>, id @$s29keypath_dynamic_member_lookup4LensV0B6MemberACyqd__Gs15WritableKeyPathCyxqd__G_tcluig : {{.*}})
227227
_ = \Lens<[[Int]]>.[0].count
228228
}
229+
230+
func test_recursive_dynamic_lookup(_ lens: Lens<Lens<Point>>) {
231+
// CHECK: keypath $KeyPath<Point, Int>, (root $Point; stored_property #Point.x : $Int)
232+
// CHECK-NEXT: keypath $KeyPath<Lens<Point>, Lens<Int>>, (root $Lens<Point>; gettable_property $Lens<Int>, id @$s29keypath_dynamic_member_lookup4LensV0B6MemberACyqd__Gs7KeyPathCyxqd__G_tcluig : {{.*}})
233+
// CHECK: function_ref @$s29keypath_dynamic_member_lookup4LensV0B6MemberACyqd__Gs7KeyPathCyxqd__G_tcluig
234+
_ = lens.x
235+
// CHECK: keypath $KeyPath<Point, Int>, (root $Point; stored_property #Point.x : $Int)
236+
// CHECK: function_ref @$s29keypath_dynamic_member_lookup4LensV0B6MemberACyqd__Gs7KeyPathCyxqd__G_tcluig
237+
_ = lens.obj.x
238+
// CHECK: [[FIRST_OBJ:%.*]] = struct_extract {{.*}} : $Lens<Lens<Point>>, #Lens.obj
239+
// CHECK-NEXT: [[SECOND_OBJ:%.*]] = struct_extract [[FIRST_OBJ]] : $Lens<Point>, #Lens.obj
240+
// CHECK-NEXT: struct_extract [[SECOND_OBJ]] : $Point, #Point.y
241+
_ = lens.obj.obj.y
242+
// CHECK: keypath $KeyPath<Point, Int>, (root $Point; stored_property #Point.x : $Int)
243+
// CHECK-NEXT: keypath $KeyPath<Lens<Point>, Lens<Int>>, (root $Lens<Point>; gettable_property $Lens<Int>, id @$s29keypath_dynamic_member_lookup4LensV0B6MemberACyqd__Gs7KeyPathCyxqd__G_tcluig : {{.*}})
244+
// CHECK-NEXT: keypath $KeyPath<Lens<Lens<Point>>, Lens<Lens<Int>>>, (root $Lens<Lens<Point>>; gettable_property $Lens<Lens<Int>>, id @$s29keypath_dynamic_member_lookup4LensV0B6MemberACyqd__Gs7KeyPathCyxqd__G_tcluig : {{.*}})
245+
_ = \Lens<Lens<Point>>.x
246+
// CHECK: keypath $WritableKeyPath<Rectangle, Point>, (root $Rectangle; stored_property #Rectangle.topLeft : $Point)
247+
// CHECK-NEXT: keypath $WritableKeyPath<Lens<Rectangle>, Lens<Point>>, (root $Lens<Rectangle>; settable_property $Lens<Point>, id @$s29keypath_dynamic_member_lookup4LensV0B6MemberACyqd__Gs15WritableKeyPathCyxqd__G_tcluig : {{.*}})
248+
// CHECK-NEXT: keypath $KeyPath<Point, Int>, (root $Point; stored_property #Point.x : $Int)
249+
// CHECK-NEXT: keypath $KeyPath<Lens<Point>, Lens<Int>>, (root $Lens<Point>; gettable_property $Lens<Int>, id @$s29keypath_dynamic_member_lookup4LensV0B6MemberACyqd__Gs7KeyPathCyxqd__G_tcluig : {{.*}})
250+
// CHECK-NEXT: keypath $KeyPath<Lens<Lens<Rectangle>>, Lens<Lens<Int>>>, (root $Lens<Lens<Rectangle>>; settable_property $Lens<Lens<Point>>, id @$s29keypath_dynamic_member_lookup4LensV0B6MemberACyqd__Gs15WritableKeyPathCyxqd__G_tcluig : {{.*}})
251+
_ = \Lens<Lens<Rectangle>>.topLeft.x
252+
}

test/attr/attr_dynamic_member_lookup.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ struct Point {
404404
var x: Int
405405
let y: Int // expected-note 2 {{change 'let' to 'var' to make it mutable}}
406406

407-
private let z: Int = 0 // expected-note 9 {{declared here}}
407+
private let z: Int = 0 // expected-note 10 {{declared here}}
408408
}
409409

410410
struct Rectangle {
@@ -654,3 +654,17 @@ struct SingleChoiceLens<T> {
654654
func test_lens_with_a_single_choice(a: inout SingleChoiceLens<[Int]>) {
655655
a[0] = 1 // Ok
656656
}
657+
658+
func test_chain_of_recursive_lookups(_ lens: Lens<Lens<Lens<Point>>>) {
659+
_ = lens.x
660+
_ = lens.y
661+
_ = lens.z // expected-error {{'z' is inaccessible due to 'private' protection level}}
662+
// Make sure that 'obj' field could be retrieved at any level
663+
_ = lens.obj
664+
_ = lens.obj.obj
665+
_ = lens.obj.x
666+
_ = lens.obj.obj.x
667+
668+
_ = \Lens<Lens<Point>>.x
669+
_ = \Lens<Lens<Point>>.obj.x
670+
}

0 commit comments

Comments
 (0)