[ConstraintSystem] Don't let conditional conformances shadow members accessible through dynamic lookup

Currently if there is a conditional conformance to a type marked
as `@dynamicMemberLookup` with member that shadows one accessible
through dynamic lookup we'd report an error if that conformance
has not been satisfied.

Better behavior would be to consider dynamic member lookup and
if that fits let the expression type-check.

Resolves: rdar://problem/52779809
This commit is contained in:
Pavel Yaskevich
2019-07-10 14:49:25 -07:00
parent 9e4c054d6a
commit 22fb079373
2 changed files with 107 additions and 20 deletions

View File

@@ -4019,6 +4019,47 @@ static bool isForKeyPathSubscript(ConstraintSystem &cs,
return false;
}
/// Determine whether all of the given candidate overloads
/// found through conditional conformances of a given base type.
/// This is useful to figure out whether it makes sense to
/// perform dynamic member lookup or not.
static bool
allFromConditionalConformances(DeclContext *DC, Type baseTy,
ArrayRef<OverloadChoice> candidates) {
auto *NTD = baseTy->getAnyNominal();
if (!NTD)
return false;
return llvm::all_of(candidates, [&](const OverloadChoice &choice) {
auto *decl = choice.getDeclOrNull();
if (!decl)
return false;
auto *candidateDC = decl->getDeclContext();
if (auto *extension = dyn_cast<ExtensionDecl>(candidateDC)) {
if (extension->isConstrainedExtension())
return true;
}
if (auto *protocol = candidateDC->getSelfProtocolDecl()) {
SmallVector<ProtocolConformance *, 4> conformances;
if (!NTD->lookupConformance(DC->getParentModule(), protocol,
conformances))
return false;
// This is opportunistic, there should be a way to narrow the
// list down to a particular declaration member comes from.
return llvm::any_of(
conformances, [](const ProtocolConformance *conformance) {
return !conformance->getConditionalRequirements().empty();
});
}
return false;
});
}
/// Given a ValueMember, UnresolvedValueMember, or TypeMember constraint,
/// perform a lookup into the specified base type to find a candidate list.
/// The list returned includes the viable candidates as well as the unviable
@@ -4479,30 +4520,32 @@ retry_after_fail:
}
}
}
// If we're about to fail lookup, but we are looking for members in a type
// with the @dynamicMemberLookup attribute, then we resolve a reference
// to a `subscript(dynamicMember:)` method and pass the member name as a
// string parameter.
if (result.ViableCandidates.empty() &&
constraintKind == ConstraintKind::ValueMember &&
memberName.isSimpleName() && !memberName.isSpecial()) {
auto name = memberName.getBaseIdentifier();
if (::hasDynamicMemberLookupAttribute(instanceTy,
DynamicMemberLookupCache)) {
// If we're about to fail lookup because there are no viable candidates
// or if all of the candidates come from conditional conformances (which
// might not be applicable), and we are looking for members in a type with
// the @dynamicMemberLookup attribute, then we resolve a reference to a
// `subscript(dynamicMember:)` method and pass the member name as a string
// parameter.
if (constraintKind == ConstraintKind::ValueMember &&
memberName.isSimpleName() && !memberName.isSpecial() &&
::hasDynamicMemberLookupAttribute(instanceTy, DynamicMemberLookupCache)) {
const auto &candidates = result.ViableCandidates;
if (candidates.empty() ||
allFromConditionalConformances(DC, instanceTy, candidates)) {
auto &ctx = getASTContext();
// Recursively look up `subscript(dynamicMember:)` methods in this type.
auto subscriptName =
DeclName(ctx, DeclBaseName::createSubscript(), ctx.Id_dynamicMember);
auto subscripts = performMemberLookup(constraintKind,
subscriptName,
baseTy, functionRefKind,
memberLocator,
includeInaccessibleMembers);
DeclName(ctx, DeclBaseName::createSubscript(), ctx.Id_dynamicMember);
auto subscripts = performMemberLookup(
constraintKind, subscriptName, baseTy, functionRefKind, memberLocator,
includeInaccessibleMembers);
// Reflect the candidates found as `DynamicMemberLookup` results.
for (auto candidate : subscripts.ViableCandidates) {
auto name = memberName.getBaseIdentifier();
for (const auto &candidate : subscripts.ViableCandidates) {
auto *SD = cast<SubscriptDecl>(candidate.getDecl());
bool isKeyPathBased = isValidKeyPathDynamicMemberLookup(SD, TC);
@@ -4512,9 +4555,10 @@ retry_after_fail:
}
for (auto index : indices(subscripts.UnviableCandidates)) {
auto *SD = cast<SubscriptDecl>(subscripts.UnviableCandidates[index].getDecl());
auto *SD =
cast<SubscriptDecl>(subscripts.UnviableCandidates[index].getDecl());
auto choice = OverloadChoice::getDynamicMemberLookup(
baseTy, SD, name, isValidKeyPathDynamicMemberLookup(SD, TC));
baseTy, SD, name, isValidKeyPathDynamicMemberLookup(SD, TC));
result.addUnviable(choice, subscripts.UnviableReasons[index]);
}
}

View File

@@ -285,3 +285,46 @@ func prefer_readonly_keypath_over_reference_writable() {
// CHECK-NEXT: function_ref @$s29keypath_dynamic_member_lookup14RefWritableBoxV0B6Memberqd__s7KeyPathCyxqd__G_tcluig
_ = box.foo
}
// rdar://problem/52779809 - condiitional conformance shadows names of members reachable through dynamic lookup
protocol P {
var foo: Int { get }
}
@dynamicMemberLookup struct Ref<T> {
var value: T
subscript<U>(dynamicMember member: KeyPath<T, U>) -> U {
get { return value[keyPath: member] }
}
}
extension P {
var foo: Int { return 42 }
}
struct S {
var foo: Int { return 0 }
var baz: Int { return 1 }
}
struct Q {
var bar: Int { return 1 }
}
extension Ref : P where T == Q {
var baz: String { return "hello" }
}
func rdar52779809(_ ref1: Ref<S>, _ ref2: Ref<Q>) {
// CHECK: function_ref @$s29keypath_dynamic_member_lookup3RefV0B6Memberqd__s7KeyPathCyxqd__G_tcluig
_ = ref1.foo // Ok
// CHECK: function_ref @$s29keypath_dynamic_member_lookup3RefV0B6Memberqd__s7KeyPathCyxqd__G_tcluig
_ = ref1.baz // Ok
// CHECK: function_ref @$s29keypath_dynamic_member_lookup1PPAAE3fooSivg
_ = ref2.foo // Ok
// CHECK: function_ref @$s29keypath_dynamic_member_lookup3RefV0B6Memberqd__s7KeyPathCyxqd__G_tcluig
_ = ref2.bar // Ok
}