Skip to content

Commit c84a346

Browse files
committed
[AST] Special handling for existentials with superclass and marker protocols
Even if protocol is not self-conforming it should be okay to produce a conformance based on superclass if protocol bounds of the existential are all marker protocols. Superclass concrete conformance is wrapped into an inherited conformance in such cases to reference the existential.
1 parent ed745a5 commit c84a346

File tree

2 files changed

+71
-15
lines changed

2 files changed

+71
-15
lines changed

lib/AST/ConformanceLookup.cpp

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,46 @@ ModuleDecl::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {
7070

7171
assert(type->isExistentialType());
7272

73+
auto getConstraintType = [&type]() {
74+
if (auto *existentialTy = type->getAs<ExistentialType>())
75+
return existentialTy->getConstraintType();
76+
return type;
77+
};
78+
79+
auto lookupSuperclassConformance = [&](ExistentialLayout &layout) {
80+
if (auto superclass = layout.explicitSuperclass) {
81+
if (auto result =
82+
lookupConformance(superclass, protocol, /*allowMissing=*/false)) {
83+
if (protocol->isSpecificProtocol(KnownProtocolKind::Sendable) &&
84+
result.hasUnavailableConformance())
85+
return ProtocolConformanceRef::forInvalid();
86+
return result;
87+
}
88+
}
89+
return ProtocolConformanceRef::forInvalid();
90+
};
91+
7392
// If the existential type cannot be represented or the protocol does not
7493
// conform to itself, there's no point in looking further.
75-
if (!protocol->existentialConformsToSelf())
94+
if (!protocol->existentialConformsToSelf()) {
95+
// If type is a protocol composition with marker protocols
96+
// check whether superclass conforms, and if it does form
97+
// an inherited conformance. This means that types like:
98+
// `KeyPath<String, Int> & Sendable` don't have to be "opened"
99+
// to satisfy conformance to i.e. `Equatable`.
100+
if (getConstraintType()->is<ProtocolCompositionType>()) {
101+
auto layout = type->getExistentialLayout();
102+
if (llvm::all_of(layout.getProtocols(),
103+
[](const auto *P) { return P->isMarkerProtocol(); })) {
104+
if (auto conformance = lookupSuperclassConformance(layout)) {
105+
return ProtocolConformanceRef(
106+
ctx.getInheritedConformance(type, conformance.getConcrete()));
107+
}
108+
}
109+
}
110+
76111
return ProtocolConformanceRef::forInvalid();
112+
}
77113

78114
if (protocol->isSpecificProtocol(KnownProtocolKind::Copyable)
79115
&& !ctx.LangOpts.hasFeature(Feature::NoncopyableGenerics)) {
@@ -89,10 +125,7 @@ ModuleDecl::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {
89125
// existential to an archetype parameter, so for now we restrict this to
90126
// @objc protocols and marker protocols.
91127
if (!layout.isObjC() && !protocol->isMarkerProtocol()) {
92-
auto constraint = type;
93-
if (auto existential = constraint->getAs<ExistentialType>())
94-
constraint = existential->getConstraintType();
95-
128+
auto constraint = getConstraintType();
96129
// There's a specific exception for protocols with self-conforming
97130
// witness tables, but the existential has to be *exactly* that type.
98131
// TODO: synthesize witness tables on-demand for protocol compositions
@@ -107,16 +140,8 @@ ModuleDecl::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {
107140

108141
// If the existential is class-constrained, the class might conform
109142
// concretely.
110-
if (auto superclass = layout.explicitSuperclass) {
111-
if (auto result = lookupConformance(
112-
superclass, protocol, /*allowMissing=*/false)) {
113-
if (protocol->isSpecificProtocol(KnownProtocolKind::Sendable) &&
114-
result.hasUnavailableConformance())
115-
result = ProtocolConformanceRef::forInvalid();
116-
117-
return result;
118-
}
119-
}
143+
if (auto conformance = lookupSuperclassConformance(layout))
144+
return conformance;
120145

121146
// Otherwise, the existential might conform abstractly.
122147
for (auto protoDecl : layout.getProtocols()) {

test/Constraints/protocols.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,3 +546,34 @@ _ = P_61517(false) // expected-error{{type 'any P_61517' cannot be instantiated}
546546

547547
_ = P_61517.init() // expected-error{{type 'any P_61517' cannot be instantiated}}
548548
_ = P_61517.init(false) // expected-error{{type 'any P_61517' cannot be instantiated}}
549+
550+
@_marker
551+
protocol Marker {}
552+
553+
do {
554+
class C : Fooable, Error {
555+
func foo() {}
556+
}
557+
558+
struct Other {}
559+
560+
func overloaded() -> C & Marker {
561+
fatalError()
562+
}
563+
564+
func overloaded() -> Other {
565+
fatalError()
566+
}
567+
568+
func isFooable<T: Fooable>(_: T) {}
569+
570+
isFooable(overloaded()) // Ok
571+
572+
func isError<T: Error>(_: T) {}
573+
574+
isError(overloaded()) // Ok
575+
576+
func isFooableError<T: Fooable & Error>(_: T) {}
577+
578+
isFooableError(overloaded()) // Ok
579+
}

0 commit comments

Comments
 (0)