Skip to content

Commit 8316157

Browse files
committed
[SymbolGraph] Don't emit memberOf for default implementations or requirements
To differentiate between freestanding extensions of protocols and matching default implementations with their requirements. Otherwise, it's difficult to filter out "duplicate" entries for protocols. rdar://61459287
1 parent 7dd5b47 commit 8316157

File tree

8 files changed

+108
-17
lines changed

8 files changed

+108
-17
lines changed

lib/SymbolGraphGen/SymbolGraph.cpp

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,50 @@ PrintOptions SymbolGraph::getDeclarationFragmentsPrintOptions() const {
6969
return Opts;
7070
}
7171

72+
bool
73+
SymbolGraph::isRequirementOrDefaultImplementation(const ValueDecl *VD) const {
74+
const auto *DC = VD->getDeclContext();
75+
76+
if (isa<ProtocolDecl>(DC) && VD->isProtocolRequirement()) {
77+
return true;
78+
}
79+
80+
// At this point, VD is either a default implementation of a requirement
81+
// or a freestanding implementation from a protocol extension without
82+
// a corresponding requirement.
83+
84+
auto *Proto = dyn_cast_or_null<ProtocolDecl>(DC->getSelfNominalTypeDecl());
85+
if (!Proto) {
86+
return false;
87+
}
88+
89+
/// Try to find a member of the owning protocol with the same name
90+
/// that is a requirement.
91+
auto FoundRequirementMemberNamed = [](DeclName Name,
92+
ProtocolDecl *Proto) -> bool {
93+
for (const auto *Member : Proto->lookupDirect(Name)) {
94+
if (isa<ProtocolDecl>(Member->getDeclContext()) &&
95+
Member->isProtocolRequirement()) {
96+
return true;
97+
}
98+
}
99+
return false;
100+
};
101+
102+
if (FoundRequirementMemberNamed(VD->getFullName(), Proto)) {
103+
return true;
104+
}
105+
for (auto *Inherited : Proto->getInheritedProtocols()) {
106+
if (FoundRequirementMemberNamed(VD->getFullName(), Inherited)) {
107+
return true;
108+
}
109+
}
110+
111+
// Couldn't find any requirement members of a protocol by this name.
112+
// It's not a requirement or default implementation of a requirement.
113+
return false;
114+
}
115+
72116
// MARK: - Symbols (Nodes)
73117

74118
void SymbolGraph::recordNode(Symbol S) {
@@ -102,15 +146,26 @@ void SymbolGraph::recordEdge(Symbol Source,
102146
}
103147

104148
void SymbolGraph::recordMemberRelationship(Symbol S) {
105-
auto *DC = S.getSymbolDecl()->getDeclContext();
149+
const auto *DC = S.getSymbolDecl()->getDeclContext();
106150
switch (DC->getContextKind()) {
107151
case DeclContextKind::GenericTypeDecl:
108152
case DeclContextKind::ExtensionDecl:
109153
case swift::DeclContextKind::EnumElementDecl:
154+
/*
155+
If this symbol is itself a protocol requirement, or
156+
is a default implementation of a protocol requirement,
157+
don't record a memberOf relationship.
158+
159+
This is to allow distinguishing between requirements,
160+
default implementations of requirements, and just other
161+
things added to protocols in extensions not related to their
162+
requirements.
163+
*/
164+
if (isRequirementOrDefaultImplementation(S.getSymbolDecl())) {
165+
return;
166+
}
110167
return recordEdge(S,
111-
Symbol(this,
112-
S.getSymbolDecl()->getDeclContext()->getSelfNominalTypeDecl(),
113-
nullptr),
168+
Symbol(this, DC->getSelfNominalTypeDecl(), nullptr),
114169
RelationshipKind::MemberOf());
115170
case swift::DeclContextKind::AbstractClosureExpr:
116171
case swift::DeclContextKind::Initializer:

lib/SymbolGraphGen/SymbolGraph.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ struct SymbolGraph {
202202
/// Returns `true` if the declaration should be included as a node
203203
/// in the graph.
204204
bool canIncludeDeclAsNode(const Decl *D) const;
205+
206+
/// Returns `true` if the declaration is a requirement of a protocol
207+
/// or is a default implementation of a protocol
208+
bool isRequirementOrDefaultImplementation(const ValueDecl *VD) const;
205209
};
206210

207211
} // end namespace symbolgraphgen

test/SymbolGraph/Relationships/DefaultImplementationOf/Basic.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ extension P {
1616
// CHECK: "kind": "defaultImplementationOf",
1717
// CHECK-NEXT: "source": "s:23DefaultImplementationOf1PPAAE1xSivp",
1818
// CHECK-NEXT: "target": "s:23DefaultImplementationOf1PP1xSivp"
19+
20+
// Since x is a default implementation of a requirement, we don't consider this to be a "member".
21+
// CHECK-NOT: memberOf

test/SymbolGraph/Relationships/DefaultImplementationOf/Indirect.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
// RUN: %FileCheck %s --input-file %t/Indirect.symbols.json
55

66
public protocol P {
7-
associatedtype Thing
87
func foo()
98
}
109

@@ -15,3 +14,6 @@ extension Q {
1514
}
1615

1716
// CHECK-DAG: "kind": "defaultImplementationOf",{{[[:space:]]*}}"source": "s:8Indirect1QPAAE3fooyyF",{{[[:space:]]*}}"target": "s:8Indirect1PP3fooyyF"
17+
18+
// Since foo is a default implementation of a requirment, we don't consider this to be a "member"
19+
// CHECK-NOT: memberOf

test/SymbolGraph/Relationships/MemberOf.swift

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift %s -module-name Basic -emit-module -emit-module-path %t/
3+
// RUN: %target-swift-symbolgraph-extract -module-name Basic -I %t -pretty-print -output-dir %t
4+
// RUN: %FileCheck %s --input-file %t/Basic.symbols.json
5+
public struct S {
6+
public var x: Int
7+
}
8+
9+
// CHECK: "kind": "memberOf"
10+
// CHECK-NEXT: "source": "s:5Basic1SV1xSivp"
11+
// CHECK-NEXT: "target": "s:5Basic1SV"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift %s -module-name ProtocolMemberWithoutRequirement -emit-module -emit-module-path %t/
3+
// RUN: %target-swift-symbolgraph-extract -module-name ProtocolMemberWithoutRequirement -I %t -pretty-print -output-dir %t
4+
// RUN: %FileCheck %s --input-file %t/ProtocolMemberWithoutRequirement.symbols.json
5+
6+
public protocol P {}
7+
8+
extension P {
9+
public func foo() {}
10+
}
11+
public protocol Q : P {}
12+
13+
extension Q {
14+
public func bar() {}
15+
}
16+
17+
// foo is a member of P.
18+
// CHECK-DAG: "kind": "memberOf",{{[[:space:]]*}}"source": "s:32ProtocolMemberWithoutRequirement1PPAAE3fooyyF",{{[[:space:]]*}}"target": "s:32ProtocolMemberWithoutRequirement1PP"
19+
20+
// bar is a member of Q.
21+
// CHECK-DAG: "kind": "memberOf",{{[[:space:]]*}}"source": "s:32ProtocolMemberWithoutRequirement1QPAAE3baryyF",{{[[:space:]]*}}"target": "s:32ProtocolMemberWithoutRequirement1QP"
22+
23+
// foo is not a requirement of P nor a default implementation for any requirement.
24+
// Neither is bar for Q.
25+
// CHECK-NOT: requirementOf
26+
// CHECK-NOT: defaultImplementationOf
27+

test/SymbolGraph/Relationships/RequirementOf.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ public protocol P {
1010
// CHECK: "kind": "requirementOf"
1111
// CHECK-NEXT: "source": "s:10ConformsTo1PP1xSivp"
1212
// CHECK-NEXT: "target": "s:10ConformsTo1PP"
13+
// CHECK-NOT: defaultImplementationOf

0 commit comments

Comments
 (0)