Skip to content

Commit e120a82

Browse files
[SymbolGraphGen] refactor protocol conformance inheritance checking (swiftlang#66012)
rdar://109418762
1 parent 889f813 commit e120a82

File tree

2 files changed

+58
-61
lines changed

2 files changed

+58
-61
lines changed

lib/SymbolGraphGen/SymbolGraphASTWalker.cpp

Lines changed: 16 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "llvm/ADT/StringSwitch.h"
1414
#include "swift/AST/Decl.h"
1515
#include "swift/AST/Module.h"
16+
#include "swift/AST/ProtocolConformance.h"
1617
#include "swift/Serialization/SerializedModuleLoader.h"
1718
#include "swift/SymbolGraphGen/SymbolGraphGen.h"
1819

@@ -181,79 +182,33 @@ bool SymbolGraphASTWalker::walkToDeclPre(Decl *D, CharSourceRange Range) {
181182
// We want to add conformsTo relationships for all protocols implicitly
182183
// implied by those explicitly stated on the extension.
183184
//
184-
// Thus, we have to expand two syntactic constructs:
185-
// * `protocol A: B, C { ... }` declarations, where those that still have
186-
// to be expanded are stored in `UnexpandedProtocols`
187-
// that still have to be expanded
188-
// * `typealias A = B & C` declarations, which are directly expanded to
189-
// unexpanded protocols in `HandleProtocolOrComposition`
190-
//
191-
// The expansion adds the base protocol to `Protocols` and calls
192-
// `HandleProtocolOrComposition` for the implied protocols. This process
193-
// continues until there is nothing left to expand (`UnexpandedProtocols`
194-
// is empty), because `HandleProtocolOrComposition` didn't add any new
195-
// unexpanded protocols. At that point, all direct and indirect
196-
// conformances are stored in `Protocols`.
197-
198-
SmallVector<const ProtocolDecl *, 4> Protocols;
185+
// We start by collecting the conformances declared on the extension with
186+
// `getLocalConformances`. From there, we inspect each protocol for any
187+
// other protocols it inherits (whether stated explicitly or via a
188+
// composed protocol type alias) with `getInheritedProtocols`. Each new
189+
// protocol is added to `UnexpandedProtocols` until there are no new
190+
// protocols to add. At that point, all direct and indirect conformances
191+
// are stored in `Protocols`.
192+
193+
SmallPtrSet<const ProtocolDecl *, 4> Protocols;
199194
SmallVector<const ProtocolDecl *, 4> UnexpandedProtocols;
200195

201-
// Unwrap `UnexpandedCompositions` and add all unexpanded protocols to the
202-
// `UnexpandedProtocols` list for expansion.
203-
auto HandleProtocolOrComposition = [&](Type Ty) {
204-
if (const auto *Proto =
205-
dyn_cast_or_null<ProtocolDecl>(Ty->getAnyNominal())) {
206-
UnexpandedProtocols.push_back(Proto);
207-
return;
208-
}
209-
210-
SmallVector<const ProtocolCompositionType *, 4> UnexpandedCompositions;
211-
212-
if (const auto *Comp = Ty->getAs<ProtocolCompositionType>()) {
213-
UnexpandedCompositions.push_back(Comp);
214-
} else {
215-
llvm_unreachable("Expected ProtocolDecl or ProtocolCompositionType");
216-
}
217-
218-
while (!UnexpandedCompositions.empty()) {
219-
const auto *Comp = UnexpandedCompositions.pop_back_val();
220-
for (const auto &Member : Comp->getMembers()) {
221-
if (const auto *Proto =
222-
dyn_cast_or_null<ProtocolDecl>(Member->getAnyNominal())) {
223-
Protocols.push_back(Proto);
224-
UnexpandedProtocols.push_back(Proto);
225-
} else if (const auto *Comp =
226-
Member->getAs<ProtocolCompositionType>()) {
227-
UnexpandedCompositions.push_back(Comp);
228-
} else {
229-
abort();
230-
}
231-
}
232-
}
233-
};
234-
235196
// Start the process with the conformances stated
236197
// explicitly on the extension.
237-
for (const auto &InheritedLoc : Extension->getInherited()) {
238-
auto InheritedTy = InheritedLoc.getType();
239-
if (!InheritedTy) {
240-
continue;
241-
}
242-
HandleProtocolOrComposition(InheritedTy);
198+
for (const auto *Conformance : Extension->getLocalConformances()) {
199+
UnexpandedProtocols.push_back(Conformance->getProtocol());
243200
}
244201

245202
// "Recursively" expand the unexpanded list and populate
246203
// the expanded `Protocols` list (in an iterative manner).
247204
while (!UnexpandedProtocols.empty()) {
248205
const auto *Proto = UnexpandedProtocols.pop_back_val();
249-
for (const auto &InheritedEntry : Proto->getInherited()) {
250-
auto InheritedTy = InheritedEntry.getType();
251-
if (!InheritedTy) {
252-
continue;
206+
if (!Protocols.contains(Proto)) {
207+
for (const auto *InheritedProtocol : Proto->getInheritedProtocols()) {
208+
UnexpandedProtocols.push_back(InheritedProtocol);
253209
}
254-
HandleProtocolOrComposition(InheritedTy);
210+
Protocols.insert(Proto);
255211
}
256-
Protocols.push_back(Proto);
257212
}
258213

259214
// Record the expanded list of protocols.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift %s -module-name ProtocolClassInheritance -emit-module -emit-module-path %t/
3+
// RUN: %target-swift-symbolgraph-extract -module-name ProtocolClassInheritance -I %t -output-dir %t
4+
// RUN: %FileCheck %s --input-file %t/ProtocolClassInheritance.symbols.json
5+
6+
// When a protocol that declares a class inheritance requirement is added by an extension, make sure
7+
// that SymbolGraphGen does not crash (rdar://109418762)
8+
9+
public class ClassOne {}
10+
public protocol ProtoOne: ClassOne {}
11+
12+
public class ClassTwo: ClassOne {}
13+
extension ClassTwo: ProtoOne {}
14+
15+
// Same for a generic class inheritance requirement
16+
17+
public class ClassThree<T> {}
18+
public protocol ProtoTwo: ClassThree<Int> {}
19+
20+
public class ClassFour: ClassThree<Int> {}
21+
extension ClassFour: ProtoTwo {}
22+
23+
// Same for a protocol with a primary associated type
24+
25+
public protocol ProtoThree<T> {
26+
associatedtype T
27+
}
28+
public protocol ProtoFour: ProtoThree<Int> {}
29+
30+
public class ClassFive: ProtoThree {
31+
public typealias T = Int
32+
}
33+
extension ClassFive: ProtoFour {}
34+
35+
// ClassTwo conforms to ProtoOne
36+
// CHECK-DAG: {"kind":"conformsTo","source":"s:24ProtocolClassInheritance0B3TwoC","target":"s:24ProtocolClassInheritance8ProtoOneP"}
37+
38+
// ClassFour conforms to ProtoTwo
39+
// CHECK-DAG: {"kind":"conformsTo","source":"s:24ProtocolClassInheritance0B4FourC","target":"s:24ProtocolClassInheritance8ProtoTwoP"}
40+
41+
// ClassFive conforms to ProtoFour
42+
// CHECK-DAG: {"kind":"conformsTo","source":"s:24ProtocolClassInheritance0B4FiveC","target":"s:24ProtocolClassInheritance9ProtoFourP"}

0 commit comments

Comments
 (0)