mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Merge pull request #23436 from xedin/keypath-dynamic-lookup
[SE-0252][TypeChecker] Keypath Dynamic Member Lookup
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user