Merge pull request #23436 from xedin/keypath-dynamic-lookup

[SE-0252][TypeChecker] Keypath Dynamic Member Lookup
This commit is contained in:
Pavel Yaskevich
2019-04-05 00:10:53 -07:00
committed by GitHub
21 changed files with 1454 additions and 260 deletions

View File

@@ -117,6 +117,7 @@ bool constraints::areConservativelyCompatibleArgumentLabels(
case OverloadChoiceKind::BaseType:
case OverloadChoiceKind::DynamicMemberLookup:
case OverloadChoiceKind::KeyPathDynamicMemberLookup:
case OverloadChoiceKind::TupleIndex:
return true;
}
@@ -1869,7 +1870,8 @@ ConstraintSystem::matchTypesBindTypeVar(
// lvalues either.
type.visit([&](Type t) {
if (auto *tvt = dyn_cast<TypeVariableType>(t.getPointer()))
typeVar->getImpl().setCannotBindToLValue(getSavedBindings());
typeVar->getImpl().setCanBindToLValue(getSavedBindings(),
/*enabled=*/false);
});
}
@@ -3534,6 +3536,10 @@ static bool hasDynamicMemberLookupAttribute(Type type,
return result;
}
static bool isKeyPathDynamicMemberLookup(ConstraintLocator *locator) {
auto path = locator ? locator->getPath() : None;
return !path.empty() && path.back().isKeyPathDynamicMember();
}
/// Given a ValueMember, UnresolvedValueMember, or TypeMember constraint,
/// perform a lookup into the specified base type to find a candidate list.
@@ -3565,10 +3571,15 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
// Okay, start building up the result list.
MemberLookupResult result;
result.OverallResult = MemberLookupResult::HasResults;
// If we're looking for a subscript, consider key path operations.
//
// TODO: This logic needs to be refactored to make sure that implicit
// keypath result is only introduced when it makes sense e.g. if there
// is a single argument with `keypath:` label or `\.` syntax is used.
if (memberName.isSimpleName() &&
memberName.getBaseName().getKind() == DeclBaseName::Kind::Subscript) {
memberName.getBaseName().getKind() == DeclBaseName::Kind::Subscript &&
!isKeyPathDynamicMemberLookup(memberLocator)) {
result.ViableCandidates.push_back(
OverloadChoice(baseTy, OverloadChoiceKind::KeyPathApplication));
}
@@ -3824,6 +3835,37 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
}
}
// Check whether this is overload choice found via keypath
// based dynamic member lookup. Since it's unknown upfront
// what kind of declaration lookup is going to find, let's
// double check here that given keypath is appropriate for it.
if (isKeyPathDynamicMemberLookup(memberLocator)) {
auto path = memberLocator->getPath();
auto *keyPath = path.back().getKeyPath();
if (auto *storage = dyn_cast<AbstractStorageDecl>(decl)) {
// If this is an attempt to access read-only member via
// writable key path, let's fail this choice early.
if (isReadOnlyKeyPathComponent(storage) &&
keyPath == getASTContext().getWritableKeyPathDecl()) {
result.addUnviable(
candidate,
MemberLookupResult::UR_WritableKeyPathOnReadOnlyMember);
return;
}
// A nonmutating setter indicates a reference-writable base,
// on the other hand if setter is mutating there is no point
// of attempting `ReferenceWritableKeyPath` overload.
if (storage->isSetterMutating() &&
keyPath == getASTContext().getReferenceWritableKeyPathDecl()) {
result.addUnviable(
candidate,
MemberLookupResult::UR_ReferenceWritableKeyPathOnMutatingMember);
return;
}
}
}
// Otherwise, we're good, add the candidate to the list.
result.addViable(candidate);
};
@@ -3856,6 +3898,27 @@ performMemberLookup(ConstraintKind constraintKind, DeclName memberName,
functionRefKind);
}
// While looking for subscript choices it's possible to find
// `subscript(dynamicMember: {Writable}KeyPath)` on types
// marked as `@dynamicMemberLookup`, let's mark this candidate
// as representing "dynamic lookup" unless it's a direct call
// to such subscript (in that case label is expected to match).
if (auto *subscript = dyn_cast<SubscriptDecl>(cand)) {
if (hasDynamicMemberLookupAttribute(instanceTy,
DynamicMemberLookupCache) &&
isValidKeyPathDynamicMemberLookup(subscript, TC)) {
auto info =
getArgumentLabels(*this, ConstraintLocatorBuilder(memberLocator));
if (!(info && info->Labels.size() == 1 &&
info->Labels[0] == getASTContext().Id_dynamicMember)) {
return OverloadChoice::getDynamicMemberLookup(
baseTy, subscript, TC.Context.getIdentifier("subscript"),
/*isKeyPathBased=*/true);
}
}
}
return OverloadChoice(baseTy, cand, functionRefKind);
};
@@ -3945,17 +4008,21 @@ retry_after_fail:
baseTy, functionRefKind,
memberLocator,
includeInaccessibleMembers);
// Reflect the candidates found as `DynamicMemberLookup` results.
for (auto candidate : subscripts.ViableCandidates) {
auto decl = cast<SubscriptDecl>(candidate.getDecl());
if (isValidDynamicMemberLookupSubscript(decl, DC, TC))
result.addViable(
OverloadChoice::getDynamicMemberLookup(baseTy, decl, name));
auto *SD = cast<SubscriptDecl>(candidate.getDecl());
bool isKeyPathBased = isValidKeyPathDynamicMemberLookup(SD, TC);
if (isValidStringDynamicMemberLookup(SD, DC, TC) || isKeyPathBased)
result.addViable(OverloadChoice::getDynamicMemberLookup(
baseTy, SD, name, isKeyPathBased));
}
for (auto index : indices(subscripts.UnviableCandidates)) {
auto decl = subscripts.UnviableCandidates[index].getDecl();
auto choice = OverloadChoice::getDynamicMemberLookup(baseTy, decl,name);
auto *SD = cast<SubscriptDecl>(subscripts.UnviableCandidates[index].getDecl());
auto choice = OverloadChoice::getDynamicMemberLookup(
baseTy, SD, name, isValidKeyPathDynamicMemberLookup(SD, TC));
result.addUnviable(choice, subscripts.UnviableReasons[index]);
}
}
@@ -4187,6 +4254,12 @@ fixMemberRef(ConstraintSystem &cs, Type baseTy,
case MemberLookupResult::UR_MutatingGetterOnRValue:
case MemberLookupResult::UR_LabelMismatch:
case MemberLookupResult::UR_UnavailableInExistential:
// TODO(diagnostics): Add a new fix that is suggests to
// add `subscript(dynamicMember: {Writable}KeyPath<T, U>)`
// overload here, that would help if such subscript has
// not been provided.
case MemberLookupResult::UR_WritableKeyPathOnReadOnlyMember:
case MemberLookupResult::UR_ReferenceWritableKeyPathOnMutatingMember:
break;
}
}
@@ -4910,34 +4983,18 @@ ConstraintSystem::simplifyKeyPathConstraint(Type keyPathTy,
if (!storage) {
return SolutionKind::Error;
}
// See whether key paths can store to this component. (Key paths don't
// get any special power from being formed in certain contexts, such
// as the ability to assign to `let`s in initialization contexts, so
// we pass null for the DC to `isSettable` here.)
if (!getASTContext().isSwiftVersionAtLeast(5)) {
// As a source-compatibility measure, continue to allow
// WritableKeyPaths to be formed in the same conditions we did
// in previous releases even if we should not be able to set
// the value in this context.
if (!storage->isSettable(DC)) {
// A non-settable component makes the key path read-only, unless
// a reference-writable component shows up later.
capability = ReadOnly;
continue;
}
} else if (!storage->isSettable(nullptr)
|| !storage->isSetterAccessibleFrom(DC)) {
// A non-settable component makes the key path read-only, unless
// a reference-writable component shows up later.
if (isReadOnlyKeyPathComponent(storage)) {
capability = ReadOnly;
continue;
}
// A nonmutating setter indicates a reference-writable base.
if (!storage->isSetterMutating()) {
capability = ReferenceWritable;
continue;
}
// Otherwise, the key path maintains its current capability.
break;
}
@@ -4985,8 +5042,9 @@ done:
if (keyPathBGT) {
if (keyPathBGT->getDecl() == getASTContext().getKeyPathDecl())
kpDecl = getASTContext().getKeyPathDecl();
else if (keyPathBGT->getDecl() == getASTContext().getWritableKeyPathDecl()
&& capability >= Writable)
else if (keyPathBGT->getDecl() ==
getASTContext().getWritableKeyPathDecl() &&
capability >= Writable)
kpDecl = getASTContext().getWritableKeyPathDecl();
}