Skip to content

Commit ff7d03f

Browse files
authored
[ParseableInterfaces] Handle unsatisfiable conditional conformances (#20433)
...by printing them with a dummy, unsatisfiable condition. This happens when a public type conforms to a public protocol with non-public conditions; the conformance can't be used in inlinable code, but neither is it okay for a client to declare their own conformance (constrained or unconstrained). rdar://problem/45657450
1 parent c83f638 commit ff7d03f

File tree

2 files changed

+105
-3
lines changed

2 files changed

+105
-3
lines changed

lib/Frontend/ParseableInterfaceSupport.cpp

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -466,10 +466,15 @@ namespace {
466466
/// spelling of the protocol type, as well as the locality in the file), but it
467467
/// does work.
468468
class InheritedProtocolCollector {
469+
static const StringLiteral DummyProtocolName;
470+
469471
/// Protocols that will be included by the ASTPrinter without any extra work.
470472
SmallVector<ProtocolDecl *, 8> IncludedProtocols;
471473
/// Protocols that will not be printed by the ASTPrinter.
472474
SmallVector<ProtocolDecl *, 8> ExtraProtocols;
475+
/// Protocols that can be printed, but whose conformances are constrained with
476+
/// something that \e can't be printed.
477+
SmallVector<const ProtocolType *, 8> ConditionalConformanceProtocols;
473478

474479
/// For each type in \p directlyInherited, classify the protocols it refers to
475480
/// as included for printing or not, and record them in the appropriate
@@ -492,6 +497,23 @@ class InheritedProtocolCollector {
492497
}
493498
}
494499

500+
/// For each type in \p directlyInherited, record any protocols that we would
501+
/// have printed in ConditionalConformanceProtocols.
502+
void recordConditionalConformances(ArrayRef<TypeLoc> directlyInherited) {
503+
for (TypeLoc inherited : directlyInherited) {
504+
Type inheritedTy = inherited.getType();
505+
if (!inheritedTy || !inheritedTy->isExistentialType())
506+
continue;
507+
508+
ExistentialLayout layout = inheritedTy->getExistentialLayout();
509+
for (ProtocolType *protoTy : layout.getProtocols())
510+
if (isPublicOrUsableFromInline(protoTy))
511+
ConditionalConformanceProtocols.push_back(protoTy);
512+
// FIXME: This ignores layout constraints, but currently we don't support
513+
// any of those besides 'AnyObject'.
514+
}
515+
}
516+
495517
public:
496518
using PerTypeMap = llvm::MapVector<const NominalTypeDecl *,
497519
InheritedProtocolCollector>;
@@ -530,6 +552,21 @@ class InheritedProtocolCollector {
530552
collectProtocols(map, member);
531553
}
532554

555+
/// If \p D is an extension providing conditional conformances, record those
556+
/// in \p map.
557+
///
558+
/// \sa recordConditionalConformances
559+
static void collectSkippedConditionalConformances(PerTypeMap &map,
560+
const Decl *D) {
561+
auto *extension = dyn_cast<ExtensionDecl>(D);
562+
if (!extension || !extension->isConstrainedExtension())
563+
return;
564+
565+
const NominalTypeDecl *nominal = extension->getExtendedNominal();
566+
map[nominal].recordConditionalConformances(extension->getInherited());
567+
// No recursion here because extensions are never nested.
568+
}
569+
533570
/// If there were any public protocols that need to be printed (i.e. they
534571
/// weren't conformed to explicitly or inherited by another printed protocol),
535572
/// do so now by printing a dummy extension on \p nominal to \p out.
@@ -580,7 +617,40 @@ class InheritedProtocolCollector {
580617
}, [&out] { out << ", "; });
581618
out << " {}\n";
582619
}
620+
621+
/// If there were any conditional conformances that couldn't be printed,
622+
/// make a dummy extension that conforms to all of them, constrained by a
623+
/// fake protocol.
624+
bool printInaccessibleConformanceExtensionIfNeeded(
625+
raw_ostream &out, const PrintOptions &printOptions,
626+
const NominalTypeDecl *nominal) const {
627+
if (ConditionalConformanceProtocols.empty())
628+
return false;
629+
assert(nominal->isGenericContext());
630+
631+
out << "extension ";
632+
nominal->getDeclaredType().print(out, printOptions);
633+
out << " : ";
634+
swift::interleave(ConditionalConformanceProtocols,
635+
[&out, &printOptions](const ProtocolType *protoTy) {
636+
protoTy->print(out, printOptions);
637+
}, [&out] { out << ", "; });
638+
out << " where "
639+
<< nominal->getGenericParamsOfContext()->getParams().front()->getName()
640+
<< " : " << DummyProtocolName << " {}\n";
641+
return true;
642+
}
643+
644+
/// Print a fake protocol declaration for use by
645+
/// #printInaccessibleConformanceExtensionIfNeeded.
646+
static void printDummyProtocolDeclaration(raw_ostream &out) {
647+
out << "\n@usableFromInline\ninternal protocol " << DummyProtocolName
648+
<< " {}\n";
649+
}
583650
};
651+
652+
const StringLiteral InheritedProtocolCollector::DummyProtocolName =
653+
"_ConstraintThatIsNotPartOfTheAPIOfThisLibrary";
584654
} // end anonymous namespace
585655

586656
bool swift::emitParseableInterface(raw_ostream &out,
@@ -597,8 +667,12 @@ bool swift::emitParseableInterface(raw_ostream &out,
597667
SmallVector<Decl *, 16> topLevelDecls;
598668
M->getTopLevelDecls(topLevelDecls);
599669
for (const Decl *D : topLevelDecls) {
600-
if (!D->shouldPrintInContext(printOptions))
670+
if (!D->shouldPrintInContext(printOptions) ||
671+
!printOptions.CurrentPrintabilityChecker->shouldPrint(D, printOptions)){
672+
InheritedProtocolCollector::collectSkippedConditionalConformances(
673+
inheritedProtocolMap, D);
601674
continue;
675+
}
602676

603677
D->print(out, printOptions);
604678
out << "\n";
@@ -607,11 +681,18 @@ bool swift::emitParseableInterface(raw_ostream &out,
607681
}
608682

609683
// Print dummy extensions for any protocols that were indirectly conformed to.
684+
bool needDummyProtocolDeclaration = false;
610685
for (const auto &nominalAndCollector : inheritedProtocolMap) {
686+
const NominalTypeDecl *nominal = nominalAndCollector.first;
611687
const InheritedProtocolCollector &collector = nominalAndCollector.second;
612-
collector.printSynthesizedExtensionIfNeeded(out, printOptions,
613-
nominalAndCollector.first);
688+
collector.printSynthesizedExtensionIfNeeded(out, printOptions, nominal);
689+
needDummyProtocolDeclaration |=
690+
collector.printInaccessibleConformanceExtensionIfNeeded(out,
691+
printOptions,
692+
nominal);
614693
}
694+
if (needDummyProtocolDeclaration)
695+
InheritedProtocolCollector::printDummyProtocolDeclaration(out);
615696

616697
return false;
617698
}

test/ParseableInterface/conformances.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,18 @@ public struct OuterGeneric<T> {
8181
// CHECK-NEXT: {{^ }$}}
8282
}
8383
// CHECK-NEXT: {{^}$}}
84+
85+
public protocol ConditionallyConformed {}
86+
public protocol ConditionallyConformedAgain {}
87+
88+
// CHECK-END: extension conformances.OuterGeneric : ConditionallyConformed, ConditionallyConformedAgain where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
89+
extension OuterGeneric: ConditionallyConformed where T: PrivateProto {}
90+
extension OuterGeneric: ConditionallyConformedAgain where T == PrivateProto {}
91+
8492
// CHECK-END: extension conformances.OuterGeneric.Inner : PublicBaseProto {}
93+
// CHECK-END: extension conformances.OuterGeneric.Inner : ConditionallyConformed, ConditionallyConformedAgain where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
94+
extension OuterGeneric.Inner: ConditionallyConformed where T: PrivateProto {}
95+
extension OuterGeneric.Inner: ConditionallyConformedAgain where T == PrivateProto {}
8596

8697
private protocol AnotherPrivateSubProto: PublicBaseProto {}
8798

@@ -147,3 +158,13 @@ private protocol BaseConstrainedProto: Base, PublicProto {}
147158
public class H1: Base, ClassConstrainedProto {}
148159
// CHECK: public class H1 : Base {
149160
// CHECK-END: extension conformances.H1 : PublicProto {}
161+
162+
public struct MultiGeneric<T, U, V> {}
163+
extension MultiGeneric: PublicProto where U: PrivateProto {}
164+
165+
// CHECK: public struct MultiGeneric<T, U, V> {
166+
// CHECK-END: extension conformances.MultiGeneric : PublicProto where T : _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}
167+
168+
169+
// CHECK-END: @usableFromInline
170+
// CHECK-END-NEXT: internal protocol _ConstraintThatIsNotPartOfTheAPIOfThisLibrary {}

0 commit comments

Comments
 (0)