Skip to content

Commit 4d041a3

Browse files
authored
[ParseableInterface] Print protocols inherited through private protos (#20169)
In this code: private protocol MyProto: Hashable {} public struct MyStruct: MyProto {} Being Hashable is part of MyStruct's public API, even though it's not written explicitly. If we're not going to require people to write it explicitly, we need to make sure it gets printed. rdar://problem/44662501
1 parent cec6d69 commit 4d041a3

File tree

3 files changed

+307
-7
lines changed

3 files changed

+307
-7
lines changed

lib/AST/ASTPrinter.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4445,12 +4445,18 @@ swift::getInheritedForPrinting(const Decl *decl,
44454445
inherited = ed->getInherited();
44464446
}
44474447

4448-
// Collect explicit inheritted types.
4448+
// Collect explicit inherited types.
44494449
for (auto TL: inherited) {
4450-
if (auto Ty = TL.getType()) {
4451-
if (auto NTD = Ty->getAnyNominal())
4452-
if (!shouldPrint(NTD))
4453-
continue;
4450+
if (auto ty = TL.getType()) {
4451+
bool foundUnprintable = ty.findIf([shouldPrint](Type subTy) {
4452+
if (auto aliasTy = dyn_cast<NameAliasType>(subTy.getPointer()))
4453+
return !shouldPrint(aliasTy->getDecl());
4454+
if (auto NTD = subTy->getAnyNominal())
4455+
return !shouldPrint(NTD);
4456+
return false;
4457+
});
4458+
if (foundUnprintable)
4459+
continue;
44544460
}
44554461
Results.push_back(TL);
44564462
}

lib/Frontend/ParseableInterfaceSupport.cpp

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "swift/AST/ASTContext.h"
1515
#include "swift/AST/Decl.h"
1616
#include "swift/AST/DiagnosticsFrontend.h"
17+
#include "swift/AST/ExistentialLayout.h"
1718
#include "swift/AST/FileSystem.h"
1819
#include "swift/AST/Module.h"
1920
#include "swift/Frontend/Frontend.h"
@@ -431,6 +432,157 @@ static void printImports(raw_ostream &out, ModuleDecl *M) {
431432
}
432433
}
433434

435+
// FIXME: Copied from ASTPrinter.cpp...
436+
static bool isPublicOrUsableFromInline(const ValueDecl *VD) {
437+
AccessScope scope =
438+
VD->getFormalAccessScope(/*useDC*/nullptr,
439+
/*treatUsableFromInlineAsPublic*/true);
440+
return scope.isPublic();
441+
}
442+
443+
static bool isPublicOrUsableFromInline(Type ty) {
444+
// Note the double negative here: we're looking for any referenced decls that
445+
// are *not* public-or-usableFromInline.
446+
return !ty.findIf([](Type typePart) -> bool {
447+
// FIXME: If we have an internal typealias for a non-internal type, we ought
448+
// to be able to print it by desugaring.
449+
if (auto *aliasTy = dyn_cast<NameAliasType>(typePart.getPointer()))
450+
return !isPublicOrUsableFromInline(aliasTy->getDecl());
451+
if (auto *nominal = typePart->getAnyNominal())
452+
return !isPublicOrUsableFromInline(nominal);
453+
return false;
454+
});
455+
}
456+
457+
namespace {
458+
/// Collects protocols that are conformed to by a particular nominal. Since
459+
/// ASTPrinter will only print the public ones, the non-public ones get left by
460+
/// the wayside. This is a problem when a non-public protocol inherits from a
461+
/// public protocol; the generated parseable interface still needs to make that
462+
/// dependency public.
463+
///
464+
/// The solution implemented here is to generate synthetic extensions that
465+
/// declare the extra conformances. This isn't perfect (it loses the sugared
466+
/// spelling of the protocol type, as well as the locality in the file), but it
467+
/// does work.
468+
class InheritedProtocolCollector {
469+
/// Protocols that will be included by the ASTPrinter without any extra work.
470+
SmallVector<ProtocolDecl *, 8> IncludedProtocols;
471+
/// Protocols that will not be printed by the ASTPrinter.
472+
SmallVector<ProtocolDecl *, 8> ExtraProtocols;
473+
474+
/// For each type in \p directlyInherited, classify the protocols it refers to
475+
/// as included for printing or not, and record them in the appropriate
476+
/// vectors.
477+
void recordProtocols(ArrayRef<TypeLoc> directlyInherited) {
478+
for (TypeLoc inherited : directlyInherited) {
479+
Type inheritedTy = inherited.getType();
480+
if (!inheritedTy || !inheritedTy->isExistentialType())
481+
continue;
482+
483+
bool canPrintNormally = isPublicOrUsableFromInline(inheritedTy);
484+
SmallVectorImpl<ProtocolDecl *> &whichProtocols =
485+
canPrintNormally ? IncludedProtocols : ExtraProtocols;
486+
487+
ExistentialLayout layout = inheritedTy->getExistentialLayout();
488+
for (ProtocolType *protoTy : layout.getProtocols())
489+
whichProtocols.push_back(protoTy->getDecl());
490+
// FIXME: This ignores layout constraints, but currently we don't support
491+
// any of those besides 'AnyObject'.
492+
}
493+
}
494+
495+
public:
496+
using PerTypeMap = llvm::MapVector<const NominalTypeDecl *,
497+
InheritedProtocolCollector>;
498+
499+
/// Given that we're about to print \p D, record its protocols in \p map.
500+
///
501+
/// \sa recordProtocols
502+
static void collectProtocols(PerTypeMap &map, const Decl *D) {
503+
ArrayRef<TypeLoc> directlyInherited;
504+
const NominalTypeDecl *nominal;
505+
const IterableDeclContext *memberContext;
506+
507+
if ((nominal = dyn_cast<NominalTypeDecl>(D))) {
508+
directlyInherited = nominal->getInherited();
509+
memberContext = nominal;
510+
511+
} else if (auto *extension = dyn_cast<ExtensionDecl>(D)) {
512+
if (extension->isConstrainedExtension()) {
513+
// Conditional conformances never apply to inherited protocols, nor
514+
// can they provide unconditional conformances that might be used in
515+
// other extensions.
516+
return;
517+
}
518+
nominal = extension->getExtendedNominal();
519+
directlyInherited = extension->getInherited();
520+
memberContext = extension;
521+
522+
} else {
523+
return;
524+
}
525+
526+
map[nominal].recordProtocols(directlyInherited);
527+
528+
// Recurse to find any nested types.
529+
for (const Decl *member : memberContext->getMembers())
530+
collectProtocols(map, member);
531+
}
532+
533+
/// If there were any public protocols that need to be printed (i.e. they
534+
/// weren't conformed to explicitly or inherited by another printed protocol),
535+
/// do so now by printing a dummy extension on \p nominal to \p out.
536+
void
537+
printSynthesizedExtensionIfNeeded(raw_ostream &out,
538+
const PrintOptions &printOptions,
539+
const NominalTypeDecl *nominal) const {
540+
if (ExtraProtocols.empty())
541+
return;
542+
543+
SmallPtrSet<ProtocolDecl *, 16> handledProtocols;
544+
545+
// First record all protocols that have already been handled.
546+
for (ProtocolDecl *proto : IncludedProtocols) {
547+
proto->walkInheritedProtocols(
548+
[&handledProtocols](ProtocolDecl *inherited) -> TypeWalker::Action {
549+
handledProtocols.insert(inherited);
550+
return TypeWalker::Action::Continue;
551+
});
552+
}
553+
554+
// Then walk the remaining ones, and see what we need to print.
555+
// Note: We could do this in one pass, but the logic is easier to
556+
// understand if we build up the list and then print it, even if it takes
557+
// a bit more memory.
558+
SmallVector<ProtocolDecl *, 16> protocolsToPrint;
559+
for (ProtocolDecl *proto : ExtraProtocols) {
560+
proto->walkInheritedProtocols(
561+
[&](ProtocolDecl *inherited) -> TypeWalker::Action {
562+
if (!handledProtocols.insert(inherited).second)
563+
return TypeWalker::Action::SkipChildren;
564+
if (isPublicOrUsableFromInline(inherited)) {
565+
protocolsToPrint.push_back(inherited);
566+
return TypeWalker::Action::SkipChildren;
567+
}
568+
return TypeWalker::Action::Continue;
569+
});
570+
}
571+
if (protocolsToPrint.empty())
572+
return;
573+
574+
out << "extension ";
575+
nominal->getDeclaredType().print(out, printOptions);
576+
out << " : ";
577+
swift::interleave(protocolsToPrint,
578+
[&out, &printOptions](ProtocolDecl *proto) {
579+
proto->getDeclaredType()->print(out, printOptions);
580+
}, [&out] { out << ", "; });
581+
out << " {}\n";
582+
}
583+
};
584+
} // end anonymous namespace
585+
434586
bool swift::emitParseableInterface(raw_ostream &out,
435587
ParseableInterfaceOptions const &Opts,
436588
ModuleDecl *M) {
@@ -440,13 +592,26 @@ bool swift::emitParseableInterface(raw_ostream &out,
440592
printImports(out, M);
441593

442594
const PrintOptions printOptions = PrintOptions::printParseableInterfaceFile();
595+
InheritedProtocolCollector::PerTypeMap inheritedProtocolMap;
596+
443597
SmallVector<Decl *, 16> topLevelDecls;
444598
M->getTopLevelDecls(topLevelDecls);
445599
for (const Decl *D : topLevelDecls) {
446600
if (!D->shouldPrintInContext(printOptions))
447601
continue;
602+
448603
D->print(out, printOptions);
449604
out << "\n";
605+
606+
InheritedProtocolCollector::collectProtocols(inheritedProtocolMap, D);
607+
}
608+
609+
// Print dummy extensions for any protocols that were indirectly conformed to.
610+
for (const auto &nominalAndCollector : inheritedProtocolMap) {
611+
const InheritedProtocolCollector &collector = nominalAndCollector.second;
612+
collector.printSynthesizedExtensionIfNeeded(out, printOptions,
613+
nominalAndCollector.first);
450614
}
615+
451616
return false;
452617
}

test/ParseableInterface/conformances.swift

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// RUN: %target-swift-frontend-typecheck -emit-parseable-module-interface-path %t.swiftinterface %s
22
// RUN: %FileCheck %s < %t.swiftinterface
3+
// RUN: %FileCheck -check-prefix CHECK-END %s < %t.swiftinterface
34
// RUN: %FileCheck -check-prefix NEGATIVE %s < %t.swiftinterface
45

56
// CHECK-LABEL: public protocol SimpleProto {
@@ -11,10 +12,138 @@ public protocol SimpleProto {
1112
func inference(_: Inferred)
1213
} // CHECK: {{^}$}}
1314

14-
// CHECK-LABEL: public struct SimplImpl<Element> : SimpleProto {
15-
public struct SimplImpl<Element>: SimpleProto {
15+
// CHECK-LABEL: public struct SimpleImpl<Element> : SimpleProto {
16+
public struct SimpleImpl<Element>: SimpleProto {
1617
// NEGATIVE-NOT: typealias Element =
1718
// CHECK: public func inference(_: Int){{$}}
1819
public func inference(_: Int) {}
1920
// CHECK: public typealias Inferred = Swift.Int
2021
} // CHECK: {{^}$}}
22+
23+
24+
public protocol PublicProto {}
25+
private protocol PrivateProto {}
26+
27+
// CHECK: public struct A1 : PublicProto {
28+
// NEGATIVE-NOT: extension conformances.A1
29+
public struct A1: PublicProto, PrivateProto {}
30+
// CHECK: public struct A2 : PublicProto {
31+
// NEGATIVE-NOT: extension conformances.A2
32+
public struct A2: PrivateProto, PublicProto {}
33+
// CHECK: public struct A3 {
34+
// CHECK-END: extension conformances.A3 : PublicProto {}
35+
public struct A3: PublicProto & PrivateProto {}
36+
// CHECK: public struct A4 {
37+
// CHECK-END: extension conformances.A4 : PublicProto {}
38+
public struct A4: PrivateProto & PublicProto {}
39+
40+
public protocol PublicBaseProto {}
41+
private protocol PrivateSubProto: PublicBaseProto {}
42+
43+
// CHECK: public struct B1 {
44+
// CHECK-END: extension conformances.B1 : PublicBaseProto {}
45+
public struct B1: PrivateSubProto {}
46+
// CHECK: public struct B2 : PublicBaseProto {
47+
// NEGATIVE-NOT: extension conformances.B2
48+
public struct B2: PublicBaseProto, PrivateSubProto {}
49+
// CHECK: public struct B3 {
50+
// CHECK-END: extension conformances.B3 : PublicBaseProto {}
51+
public struct B3: PublicBaseProto & PrivateSubProto {}
52+
// CHECK: public struct B4 : PublicBaseProto {
53+
// CHECK: extension B4 {
54+
// NEGATIVE-NOT: extension conformances.B4
55+
public struct B4: PublicBaseProto {}
56+
extension B4: PrivateSubProto {}
57+
// CHECK: public struct B5 {
58+
// CHECK: extension B5 : PublicBaseProto {
59+
// NEGATIVE-NOT: extension conformances.B5
60+
public struct B5: PrivateSubProto {}
61+
extension B5: PublicBaseProto {}
62+
// CHECK: public struct B6 {
63+
// CHECK: extension B6 {
64+
// CHECK: extension B6 : PublicBaseProto {
65+
// NEGATIVE-NOT: extension conformances.B6
66+
public struct B6 {}
67+
extension B6: PrivateSubProto {}
68+
extension B6: PublicBaseProto {}
69+
// CHECK: public struct B7 {
70+
// CHECK: extension B7 : PublicBaseProto {
71+
// CHECK: extension B7 {
72+
// NEGATIVE-NOT: extension conformances.B7
73+
public struct B7 {}
74+
extension B7: PublicBaseProto {}
75+
extension B7: PrivateSubProto {}
76+
77+
// CHECK-LABEL: public struct OuterGeneric<T> {
78+
public struct OuterGeneric<T> {
79+
// CHECK-NEXT: public struct Inner {
80+
public struct Inner: PrivateSubProto {}
81+
// CHECK-NEXT: {{^ }$}}
82+
}
83+
// CHECK-NEXT: {{^}$}}
84+
// CHECK-END: extension conformances.OuterGeneric.Inner : PublicBaseProto {}
85+
86+
private protocol AnotherPrivateSubProto: PublicBaseProto {}
87+
88+
// CHECK: public struct C1 {
89+
// CHECK-END: extension conformances.C1 : PublicBaseProto {}
90+
public struct C1: PrivateSubProto, AnotherPrivateSubProto {}
91+
// CHECK: public struct C2 {
92+
// CHECK-END: extension conformances.C2 : PublicBaseProto {}
93+
public struct C2: PrivateSubProto & AnotherPrivateSubProto {}
94+
// CHECK: public struct C3 {
95+
// CHECK: extension C3 {
96+
// CHECK-END: extension conformances.C3 : PublicBaseProto {}
97+
public struct C3: PrivateSubProto {}
98+
extension C3: AnotherPrivateSubProto {}
99+
100+
public protocol PublicSubProto: PublicBaseProto {}
101+
public protocol APublicSubProto: PublicBaseProto {}
102+
103+
// CHECK: public struct D1 : PublicSubProto {
104+
// NEGATIVE-NOT: extension conformances.D1
105+
public struct D1: PublicSubProto, PrivateSubProto {}
106+
// CHECK: public struct D2 : PublicSubProto {
107+
// NEGATIVE-NOT: extension conformances.D2
108+
public struct D2: PrivateSubProto, PublicSubProto {}
109+
// CHECK: public struct D3 {
110+
// CHECK-END: extension conformances.D3 : PublicBaseProto, PublicSubProto {}
111+
public struct D3: PrivateSubProto & PublicSubProto {}
112+
// CHECK: public struct D4 {
113+
// CHECK-END: extension conformances.D4 : APublicSubProto, PublicBaseProto {}
114+
public struct D4: APublicSubProto & PrivateSubProto {}
115+
// CHECK: public struct D5 {
116+
// CHECK: extension D5 : PublicSubProto {
117+
// NEGATIVE-NOT: extension conformances.D5
118+
public struct D5: PrivateSubProto {}
119+
extension D5: PublicSubProto {}
120+
// CHECK: public struct D6 : PublicSubProto {
121+
// CHECK: extension D6 {
122+
// NEGATIVE-NOT: extension conformances.D6
123+
public struct D6: PublicSubProto {}
124+
extension D6: PrivateSubProto {}
125+
126+
private typealias PrivateProtoAlias = PublicProto
127+
128+
// CHECK: public struct E1 {
129+
// CHECK-END: extension conformances.E1 : PublicProto {}
130+
public struct E1: PrivateProtoAlias {}
131+
132+
private typealias PrivateSubProtoAlias = PrivateSubProto
133+
134+
// CHECK: public struct F1 {
135+
// CHECK-END: extension conformances.F1 : PublicBaseProto {}
136+
public struct F1: PrivateSubProtoAlias {}
137+
138+
private protocol ClassConstrainedProto: PublicProto, AnyObject {}
139+
140+
public class G1: ClassConstrainedProto {}
141+
// CHECK: public class G1 {
142+
// CHECK-END: extension conformances.G1 : PublicProto {}
143+
144+
public class Base {}
145+
private protocol BaseConstrainedProto: Base, PublicProto {}
146+
147+
public class H1: Base, ClassConstrainedProto {}
148+
// CHECK: public class H1 : Base {
149+
// CHECK-END: extension conformances.H1 : PublicProto {}

0 commit comments

Comments
 (0)