Skip to content

Commit 3288299

Browse files
committed
[ConstraintSystem] Make sure that each index argument of keypath subscript is Hashable
Move checking for index Hashable conformance from CSApply (post-factum) to the solver and use recorded conformance records complete subscript components.
1 parent 1fa3846 commit 3288299

File tree

3 files changed

+134
-89
lines changed

3 files changed

+134
-89
lines changed

lib/Sema/CSApply.cpp

Lines changed: 54 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,15 +1561,6 @@ namespace {
15611561
LocatorPathElt::getKeyPathDynamicMember(keyPathTy->getAnyNominal()));
15621562
auto overload = solution.getOverloadChoice(componentLoc);
15631563

1564-
auto buildSubscriptComponent = [&](SourceLoc loc, Expr *indexExpr,
1565-
ArrayRef<Identifier> labels) {
1566-
// Save a reference to the component so we can do a post-pass to check
1567-
// the Hashable conformance of the indexes.
1568-
KeyPathSubscriptComponents.push_back({keyPath, 0});
1569-
return buildKeyPathSubscriptComponent(overload, loc, indexExpr, labels,
1570-
componentLoc);
1571-
};
1572-
15731564
auto getKeyPathComponentIndex =
15741565
[](ConstraintLocator *locator) -> unsigned {
15751566
for (const auto &elt : locator->getPath()) {
@@ -1583,9 +1574,10 @@ namespace {
15831574
// calls necessary to resolve a member reference.
15841575
if (overload.choice.getKind() ==
15851576
OverloadChoiceKind::KeyPathDynamicMemberLookup) {
1586-
keyPath->resolveComponents(
1587-
ctx, buildSubscriptComponent(dotLoc, /*indexExpr=*/nullptr,
1588-
ctx.Id_dynamicMember));
1577+
keyPath->resolveComponents(ctx,
1578+
buildKeyPathSubscriptComponent(
1579+
overload, dotLoc, /*indexExpr=*/nullptr,
1580+
ctx.Id_dynamicMember, componentLoc));
15891581
return keyPath;
15901582
}
15911583

@@ -1663,8 +1655,9 @@ namespace {
16631655
/*implicit=*/true, SE->getAccessSemantics());
16641656
}
16651657

1666-
component = buildSubscriptComponent(SE->getLoc(), SE->getIndex(),
1667-
SE->getArgumentLabels());
1658+
component = buildKeyPathSubscriptComponent(
1659+
overload, SE->getLoc(), SE->getIndex(), SE->getArgumentLabels(),
1660+
componentLoc);
16681661
} else {
16691662
return nullptr;
16701663
}
@@ -4298,12 +4291,7 @@ namespace {
42984291
E->setMethod(method);
42994292
return E;
43004293
}
4301-
4302-
private:
4303-
// Key path components we need to
4304-
SmallVector<std::pair<KeyPathExpr *, unsigned>, 4>
4305-
KeyPathSubscriptComponents;
4306-
public:
4294+
43074295
Expr *visitKeyPathExpr(KeyPathExpr *E) {
43084296
if (E->isObjC()) {
43094297
cs.setType(E, cs.getType(E->getObjCStringLiteralExpr()));
@@ -4445,10 +4433,6 @@ namespace {
44454433
*foundDecl, origComponent.getLoc(), origComponent.getIndexExpr(),
44464434
subscriptLabels, locator);
44474435

4448-
// Save a reference to the component so we can do a post-pass to check
4449-
// the Hashable conformance of the indexes.
4450-
KeyPathSubscriptComponents.push_back({E, resolvedComponents.size()});
4451-
44524436
baseTy = component.getComponentType();
44534437
resolvedComponents.push_back(component);
44544438

@@ -4624,10 +4608,13 @@ namespace {
46244608
// through the subscript(dynamicMember:) member, restore the
46254609
// openedType and origComponent to its full reference as if the user
46264610
// wrote out the subscript manually.
4627-
if (overload.choice.getKind() ==
4611+
bool forDynamicLookup =
4612+
overload.choice.getKind() ==
46284613
OverloadChoiceKind::DynamicMemberLookup ||
46294614
overload.choice.getKind() ==
4630-
OverloadChoiceKind::KeyPathDynamicMemberLookup) {
4615+
OverloadChoiceKind::KeyPathDynamicMemberLookup;
4616+
4617+
if (forDynamicLookup) {
46314618
overload.openedType =
46324619
overload.openedFullType->castTo<AnyFunctionType>()->getResult();
46334620

@@ -4656,8 +4643,48 @@ namespace {
46564643
/*applyExpr*/ nullptr, labels,
46574644
/*hasTrailingClosure*/ false, locator);
46584645

4659-
return KeyPathExpr::Component::forSubscriptWithPrebuiltIndexExpr(
4646+
auto component = KeyPathExpr::Component::forSubscriptWithPrebuiltIndexExpr(
46604647
ref, newIndexExpr, labels, resolvedTy, componentLoc, {});
4648+
4649+
// We need to be able to hash the captured index values in order for
4650+
// KeyPath itself to be hashable, so check that all of the subscript
4651+
// index components are hashable and collect their conformances here.
4652+
SmallVector<ProtocolConformanceRef, 4> conformances;
4653+
4654+
auto hashable =
4655+
cs.getASTContext().getProtocol(KnownProtocolKind::Hashable);
4656+
4657+
auto equatable =
4658+
cs.getASTContext().getProtocol(KnownProtocolKind::Equatable);
4659+
4660+
auto &TC = cs.getTypeChecker();
4661+
auto fnType = overload.openedType->castTo<FunctionType>();
4662+
for (const auto &param : fnType->getParams()) {
4663+
auto indexType = simplifyType(param.getPlainType());
4664+
// index conformance to the Hashable protocol has been verified
4665+
// by the solver, we just need to get it again with all of the
4666+
// generic parameters resolved.
4667+
auto hashableConformance =
4668+
TC.conformsToProtocol(indexType, hashable, cs.DC,
4669+
(ConformanceCheckFlags::Used |
4670+
ConformanceCheckFlags::InExpression));
4671+
assert(hashableConformance.hasValue());
4672+
4673+
// FIXME: Hashable implies Equatable, but we need to make sure the
4674+
// Equatable conformance is forced into existence during type
4675+
// checking so that it's available for SILGen.
4676+
auto eqConformance =
4677+
TC.conformsToProtocol(simplifyType(indexType), equatable, cs.DC,
4678+
(ConformanceCheckFlags::Used |
4679+
ConformanceCheckFlags::InExpression));
4680+
assert(eqConformance.hasValue());
4681+
(void)eqConformance;
4682+
4683+
conformances.push_back(*hashableConformance);
4684+
}
4685+
4686+
component.setSubscriptIndexHashableConformances(conformances);
4687+
return component;
46614688
}
46624689

46634690
Expr *visitKeyPathDotExpr(KeyPathDotExpr *E) {
@@ -4726,61 +4753,6 @@ namespace {
47264753
.fixItInsertAfter(cast->getEndLoc(), ")");
47274754
}
47284755

4729-
// Look at key path subscript components to verify that they're hashable.
4730-
for (auto componentRef : KeyPathSubscriptComponents) {
4731-
auto &component = componentRef.first
4732-
->getMutableComponents()[componentRef.second];
4733-
// We need to be able to hash the captured index values in order for
4734-
// KeyPath itself to be hashable, so check that all of the subscript
4735-
// index components are hashable and collect their conformances here.
4736-
SmallVector<ProtocolConformanceRef, 2> hashables;
4737-
bool allIndexesHashable = true;
4738-
ArrayRef<TupleTypeElt> indexTypes;
4739-
TupleTypeElt singleIndexTypeBuf;
4740-
if (auto tup = cs.getType(component.getIndexExpr())
4741-
->getAs<TupleType>()) {
4742-
indexTypes = tup->getElements();
4743-
} else {
4744-
singleIndexTypeBuf = cs.getType(component.getIndexExpr());
4745-
indexTypes = singleIndexTypeBuf;
4746-
}
4747-
4748-
auto hashable =
4749-
cs.getASTContext().getProtocol(KnownProtocolKind::Hashable);
4750-
auto equatable =
4751-
cs.getASTContext().getProtocol(KnownProtocolKind::Equatable);
4752-
for (auto indexType : indexTypes) {
4753-
auto conformance =
4754-
cs.TC.conformsToProtocol(indexType.getType(), hashable,
4755-
cs.DC,
4756-
(ConformanceCheckFlags::Used|
4757-
ConformanceCheckFlags::InExpression));
4758-
if (!conformance) {
4759-
cs.TC.diagnose(component.getIndexExpr()->getLoc(),
4760-
diag::expr_keypath_subscript_index_not_hashable,
4761-
indexType.getType());
4762-
allIndexesHashable = false;
4763-
continue;
4764-
}
4765-
hashables.push_back(*conformance);
4766-
4767-
// FIXME: Hashable implies Equatable, but we need to make sure the
4768-
// Equatable conformance is forced into existence during type checking
4769-
// so that it's available for SILGen.
4770-
auto eqConformance =
4771-
cs.TC.conformsToProtocol(indexType.getType(), equatable,
4772-
cs.DC,
4773-
(ConformanceCheckFlags::Used|
4774-
ConformanceCheckFlags::InExpression));
4775-
assert(eqConformance.hasValue());
4776-
(void)eqConformance;
4777-
}
4778-
4779-
if (allIndexesHashable) {
4780-
component.setSubscriptIndexHashableConformances(hashables);
4781-
}
4782-
}
4783-
47844756
// Set the final types on the expression.
47854757
cs.setExprTypes(result);
47864758
}

lib/Sema/ConstraintLocator.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,10 @@ class ConstraintLocator : public llvm::FoldingSetNode {
486486
bool isKeyPathDynamicMember() const {
487487
return getKind() == PathElementKind::KeyPathDynamicMember;
488488
}
489+
490+
bool isKeyPathComponent() const {
491+
return getKind() == PathElementKind::KeyPathComponent;
492+
}
489493
};
490494

491495
/// Return the summary flags for an entire path.

lib/Sema/ConstraintSystem.cpp

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1695,10 +1695,54 @@ isInvalidPartialApplication(ConstraintSystem &cs, const ValueDecl *member,
16951695
return {true, level};
16961696
}
16971697

1698+
static bool isResultOfKeyPathDynamicMemberLookup(ConstraintLocator *locator) {
1699+
auto path = locator->getPath();
1700+
return !path.empty() && path.back().isKeyPathDynamicMember();
1701+
}
1702+
1703+
/// If given anchor + path represents a key path with subscript
1704+
/// component at some index.
1705+
static bool isKeyPathSubscriptComponent(ConstraintLocator *locator) {
1706+
auto *anchor = locator->getAnchor();
1707+
auto *KPE = dyn_cast_or_null<KeyPathExpr>(anchor);
1708+
if (!KPE)
1709+
return false;
1710+
1711+
using ComponentKind = KeyPathExpr::Component::Kind;
1712+
auto path = locator->getPath();
1713+
while (!path.empty()) {
1714+
auto &last = path.back();
1715+
if (!last.isKeyPathComponent()) {
1716+
path = path.drop_back();
1717+
continue;
1718+
}
1719+
1720+
auto index = last.getValue();
1721+
auto &component = KPE->getComponents()[index];
1722+
return component.getKind() == ComponentKind::Subscript ||
1723+
component.getKind() == ComponentKind::UnresolvedSubscript;
1724+
}
1725+
return false;
1726+
}
1727+
16981728
void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
16991729
Type boundType,
17001730
OverloadChoice choice,
17011731
DeclContext *useDC) {
1732+
// Add a conformance constraint to make sure that given type conforms
1733+
// to Hashable protocol, which is important for key path subscript
1734+
// components.
1735+
auto verifyThatArgumentIsHashable = [&](unsigned index, Type argType,
1736+
ConstraintLocator *locator) {
1737+
if (auto *hashable = TC.getProtocol(choice.getDecl()->getLoc(),
1738+
KnownProtocolKind::Hashable)) {
1739+
addConstraint(ConstraintKind::ConformsTo, argType,
1740+
hashable->getDeclaredType(),
1741+
getConstraintLocator(
1742+
locator, LocatorPathElt::getTupleElement(index)));
1743+
}
1744+
};
1745+
17021746
// Determine the type to which we'll bind the overload set's type.
17031747
Type refType;
17041748
Type openedFullType;
@@ -1872,15 +1916,22 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
18721916
assert(refFnType->getParams().size() == 1 &&
18731917
"subscript always has one arg");
18741918
auto argType = refFnType->getParams()[0].getPlainType();
1875-
1876-
auto protoKind = KnownProtocolKind::ExpressibleByStringLiteral;
1877-
auto protocol = getTypeChecker().getProtocol(choice.getDecl()->getLoc(),
1878-
protoKind);
1879-
if (!protocol)
1919+
1920+
auto &TC = getTypeChecker();
1921+
1922+
auto stringLiteral =
1923+
TC.getProtocol(choice.getDecl()->getLoc(),
1924+
KnownProtocolKind::ExpressibleByStringLiteral);
1925+
if (!stringLiteral)
18801926
break;
1927+
18811928
addConstraint(ConstraintKind::LiteralConformsTo, argType,
1882-
protocol->getDeclaredType(),
1883-
locator);
1929+
stringLiteral->getDeclaredType(), locator);
1930+
1931+
// If this is used inside of the keypath expression, we need to make
1932+
// sure that argument is Hashable.
1933+
if (isa<KeyPathExpr>(locator->getAnchor()))
1934+
verifyThatArgumentIsHashable(0, argType, locator);
18841935
}
18851936

18861937
if (kind == OverloadChoiceKind::KeyPathDynamicMemberLookup) {
@@ -1998,6 +2049,9 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
19982049
// type into "leaf" directly.
19992050
addConstraint(ConstraintKind::Equal, memberTy, leafTy, keyPathLoc);
20002051
}
2052+
2053+
if (isa<KeyPathExpr>(locator->getAnchor()))
2054+
verifyThatArgumentIsHashable(0, keyPathTy, locator);
20012055
}
20022056
break;
20032057
}
@@ -2067,6 +2121,21 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
20672121
}
20682122
}
20692123

2124+
if (auto *SD = dyn_cast<SubscriptDecl>(decl)) {
2125+
if (isResultOfKeyPathDynamicMemberLookup(locator) ||
2126+
isKeyPathSubscriptComponent(locator)) {
2127+
// Subscript type has a format of (Self[.Type) -> (Arg...) -> Result
2128+
auto declTy = openedFullType->castTo<FunctionType>();
2129+
auto subscriptTy = declTy->getResult()->castTo<FunctionType>();
2130+
// If we have subscript, each of the arguments has to conform to
2131+
// Hashable, because it would be used as a component inside key path.
2132+
for (auto index : indices(subscriptTy->getParams())) {
2133+
const auto &param = subscriptTy->getParams()[index];
2134+
verifyThatArgumentIsHashable(index, param.getPlainType(), locator);
2135+
}
2136+
}
2137+
}
2138+
20702139
// Check whether applying this overload would result in invalid
20712140
// partial function application e.g. partial application of
20722141
// mutating method or initializer.

0 commit comments

Comments
 (0)