Skip to content

Commit 702df37

Browse files
committed
[ParseableInterface] Print protocols inherited through private protos
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 5c03a0a commit 702df37

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
@@ -4418,12 +4418,18 @@ swift::getInheritedForPrinting(const Decl *decl,
44184418
inherited = ed->getInherited();
44194419
}
44204420

4421-
// Collect explicit inheritted types.
4421+
// Collect explicit inherited types.
44224422
for (auto TL: inherited) {
4423-
if (auto Ty = TL.getType()) {
4424-
if (auto NTD = Ty->getAnyNominal())
4425-
if (!shouldPrint(NTD))
4426-
continue;
4423+
if (auto ty = TL.getType()) {
4424+
bool foundUnprintable = ty.findIf([shouldPrint](Type subTy) {
4425+
if (auto aliasTy = dyn_cast<NameAliasType>(subTy.getPointer()))
4426+
return !shouldPrint(aliasTy->getDecl());
4427+
if (auto NTD = subTy->getAnyNominal())
4428+
return !shouldPrint(NTD);
4429+
return false;
4430+
});
4431+
if (foundUnprintable)
4432+
continue;
44274433
}
44284434
Results.push_back(TL);
44294435
}

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"
@@ -400,6 +401,157 @@ static void printImports(raw_ostream &out, ModuleDecl *M) {
400401
}
401402
}
402403

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

411563
const PrintOptions printOptions = PrintOptions::printParseableInterfaceFile();
564+
InheritedProtocolCollector::PerTypeMap inheritedProtocolMap;
565+
412566
SmallVector<Decl *, 16> topLevelDecls;
413567
M->getTopLevelDecls(topLevelDecls);
414568
for (const Decl *D : topLevelDecls) {
415569
if (!D->shouldPrintInContext(printOptions))
416570
continue;
571+
417572
D->print(out, printOptions);
418573
out << "\n";
574+
575+
InheritedProtocolCollector::collectProtocols(inheritedProtocolMap, D);
576+
}
577+
578+
// Print dummy extensions for any protocols that were indirectly conformed to.
579+
for (const auto &nominalAndCollector : inheritedProtocolMap) {
580+
const InheritedProtocolCollector &collector = nominalAndCollector.second;
581+
collector.printSynthesizedExtensionIfNeeded(out, printOptions,
582+
nominalAndCollector.first);
419583
}
584+
420585
return false;
421586
}

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)