Skip to content

Commit 745f6a1

Browse files
authored
Retain Result Builder static methods. Closes #420 (#432)
1 parent 6ce971c commit 745f6a1

File tree

9 files changed

+108
-6
lines changed

9 files changed

+108
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- Retain parameters on protocol function members implemented by an external type.
1515
- Unused function parameters on unimplemented protocol function members are now retained, as the function may still be referenced from an existential type.
1616
- Fix incorrect redundant public accessibility on a public superclass when a subclass is used in another module.
17+
- Result Builder static methods are now retained.
1718

1819
## 2.8.2 (2021-11-06)
1920

Sources/PeripheryKit/Analyzer/Analyzer.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public final class Analyzer {
4646
StringInterpolationAppendInterpolationRetainer.self,
4747
PropertyWrapperRetainer.self,
4848
OptionalProtocolMemberRetainer.self,
49+
ResultBuilderRetainer.self,
4950

5051
PlainExtensionEliminator.self,
5152
AncestralReferenceEliminator.self,

Sources/PeripheryKit/Analyzer/Visitors/PropertyWrapperRetainer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class PropertyWrapperRetainer: SourceGraphVisitor {
1414
}
1515

1616
func visit() {
17-
for decl in graph.declarations(ofKinds: [.struct, .class]) {
17+
for decl in graph.declarations(ofKinds: Declaration.Kind.toplevelAttributableKind) {
1818
if decl.attributes.contains("propertyWrapper") {
1919
decl.declarations
2020
.filter { $0.kind == .varInstance && specialProperties.contains($0.name ?? "") }
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Foundation
2+
3+
/// Retains static methods used by the Result Builder language feature.
4+
final class ResultBuilderRetainer: SourceGraphVisitor {
5+
static func make(graph: SourceGraph) -> Self {
6+
return self.init(graph: graph)
7+
}
8+
9+
private let graph: SourceGraph
10+
private let resultBuilderMethods = Set<String>([
11+
"buildExpression(_:)",
12+
"buildOptional(_:)",
13+
"buildEither(first:)",
14+
"buildEither(second:)",
15+
"buildArray(_:)",
16+
"buildBlock(_:)"
17+
])
18+
19+
required init(graph: SourceGraph) {
20+
self.graph = graph
21+
}
22+
23+
func visit() {
24+
for decl in graph.declarations(ofKinds: Declaration.Kind.toplevelAttributableKind) {
25+
if decl.attributes.contains("resultBuilder") {
26+
for childDecl in decl.declarations {
27+
if let name = childDecl.name, resultBuilderMethods.contains(name) {
28+
graph.markRetained(childDecl)
29+
}
30+
}
31+
}
32+
}
33+
}
34+
}

Sources/PeripheryKit/Indexer/Declaration.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ public final class Declaration {
117117
rawValue.hasPrefix("function.accessor")
118118
}
119119

120+
static var toplevelAttributableKind: Set<Kind> {
121+
[.class, .struct, .enum]
122+
}
123+
120124
public var displayName: String? {
121125
switch self {
122126
case .class:

Sources/PeripheryKit/Syntax/DeclarationVisitor.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,15 +240,18 @@ final class DeclarationVisitor: PeripherySyntaxVisitor {
240240
genericWhereClause: GenericWhereClauseSyntax? = nil,
241241
at position: AbsolutePosition
242242
) {
243-
let modifierNames = modifiers?.map { $0.name.text } ?? []
243+
let modifierNames = modifiers?.withoutTrivia().map { $0.name.text } ?? []
244244
let accessibility = modifierNames.mapFirst { Accessibility(rawValue: $0) }
245-
245+
let attributeNames = attributes?.withoutTrivia().compactMap {
246+
AttributeSyntax($0)?.attributeName.text ?? CustomAttributeSyntax($0)?.attributeName.firstToken?.text
247+
} ?? []
246248
let location = sourceLocationBuilder.location(at: position)
249+
247250
results.append((
248251
location,
249252
accessibility,
250253
modifierNames,
251-
attributes?.compactMap { AttributeSyntax($0)?.attributeName.text } ?? [],
254+
attributeNames,
252255
CommentCommand.parseCommands(in: trivia),
253256
type(for: variableType),
254257
typeLocations(for: variableType),

Tests/Fixtures/RetentionFixtures/testRetainsExternalAssociatedTypeTypeAlias.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Foundation
2-
import RetentionFixturesCrossModule
2+
import ExternalModuleFixtures
33

44
struct Fixture110: ExternalAssociatedType {
55
typealias Value = Void
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#if swift(>=5.4)
2+
import Foundation
3+
4+
@resultBuilder
5+
class FixtureClass130 {
6+
typealias Component = [Int]
7+
typealias Expression = Int
8+
9+
static func buildExpression(_ element: Expression) -> Component {
10+
[element]
11+
}
12+
13+
static func buildOptional(_ component: Component?) -> Component {
14+
guard let component = component else { return [] }
15+
return component
16+
}
17+
18+
static func buildEither(first component: Component) -> Component {
19+
component
20+
}
21+
22+
static func buildEither(second component: Component) -> Component {
23+
component
24+
}
25+
26+
static func buildArray(_ components: [Component]) -> Component {
27+
Array(components.joined())
28+
}
29+
30+
static func buildBlock(_ components: Component...) -> Component {
31+
Array(components.joined())
32+
}
33+
}
34+
35+
public class FixtureClass130Retainer {
36+
public func build() {
37+
_ = buildNonPublic {}
38+
}
39+
40+
func buildNonPublic(@FixtureClass130 _ content: () -> [Int]) -> [Int] {
41+
content()
42+
}
43+
}
44+
#endif

Tests/PeripheryTests/RetentionTest.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -998,12 +998,27 @@ final class RetentionTest: SourceGraphTestCase {
998998
}
999999
}
10001000

1001+
#if swift(>=5.4)
1002+
func testRetainsResultBuilderMethods() {
1003+
analyze(retainPublic: true) {
1004+
assertReferenced(.class("FixtureClass130")) {
1005+
self.assertReferenced(.functionMethodStatic("buildExpression(_:)"))
1006+
self.assertReferenced(.functionMethodStatic("buildOptional(_:)"))
1007+
self.assertReferenced(.functionMethodStatic("buildEither(first:)"))
1008+
self.assertReferenced(.functionMethodStatic("buildEither(second:)"))
1009+
self.assertReferenced(.functionMethodStatic("buildArray(_:)"))
1010+
self.assertReferenced(.functionMethodStatic("buildBlock(_:)"))
1011+
}
1012+
}
1013+
}
1014+
#endif
1015+
10011016
// MARK: - Unused Parameters
10021017

10031018
func testRetainsParamUsedInOverriddenMethod() {
10041019
analyze(retainPublic: true) {
10051020
assertReferenced(.class("FixtureClass101Base")) {
1006-
// Not used and not overriden.
1021+
// Not used and not overridden.
10071022
self.assertReferenced(.functionMethodInstance("func1(param:)")) {
10081023
self.assertNotReferenced(.varParameter("param"))
10091024
}

0 commit comments

Comments
 (0)