mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[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:
@@ -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
|
||||
@@ -4480,29 +4521,31 @@ 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,
|
||||
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,7 +4555,8 @@ 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));
|
||||
result.addUnviable(choice, subscripts.UnviableReasons[index]);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user