Skip to content

Commit b2ed797

Browse files
authored
Merge pull request #479 from kimdv/kimdv/generate-result-builders
2 parents 05af025 + f3f331a commit b2ed797

File tree

5 files changed

+328
-796
lines changed

5 files changed

+328
-796
lines changed

CodeGeneration/Sources/generate-swiftsyntaxbuilder/GenerateSwiftSyntaxBuilder.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct GenerateSwiftSyntaxBuilder: ParsableCommand {
3030
(buildableCollectionNodesFile, "BuildableCollectionNodes.swift"),
3131
(buildableNodesFile, "BuildableNodes.swift"),
3232
(expressibleAsProtocolsFile, "ExpressibleAsProtocols.swift"),
33+
(resultBuildersFile, "ResultBuildersFile.swift"),
3334
(tokenFile, "Token.swift"),
3435
],
3536
destination: URL(fileURLWithPath: generatedPath),
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
import SwiftSyntax
15+
import SyntaxSupport
16+
import SwiftSyntaxBuilder
17+
import Utils
18+
19+
let resultBuildersFile = SourceFile {
20+
ImportDecl(
21+
leadingTrivia: .docLineComment(copyrightHeader),
22+
path: "SwiftSyntax"
23+
)
24+
for node in SYNTAX_NODES where node.isSyntaxCollection {
25+
let type = SyntaxBuildableType(syntaxKind: node.syntaxKind)
26+
let elementType = node.collectionElementType
27+
28+
StructDecl(
29+
attributes: Token.identifier("@resultBuilder").withLeadingTrivia(.newlines(1)).withTrailingTrivia(.newlines(1)),
30+
modifiers: Token.public,
31+
identifier: "\(type.syntaxKind)Builder") {
32+
33+
TypealiasDecl(
34+
"""
35+
/// The type of individual statement expressions in the transformed function,
36+
/// which defaults to Component if buildExpression() is not provided.
37+
public typealias Expression = \(elementType.expressibleAsBaseName)
38+
"""
39+
)
40+
41+
TypealiasDecl(
42+
"""
43+
/// The type of a partial result, which will be carried through all of the
44+
/// build methods.
45+
public typealias Component = [\(elementType.expressibleAs)]
46+
"""
47+
)
48+
49+
TypealiasDecl(
50+
"""
51+
/// The type of the final returned result, which defaults to Component if
52+
/// buildFinalResult() is not provided.
53+
public typealias FinalResult = \(type.buildable)
54+
"""
55+
)
56+
57+
FunctionDecl(
58+
"""
59+
/// Required by every result builder to build combined results from
60+
/// statement blocks.
61+
public static func buildBlock(_ components: Component...) -> Component {
62+
return components.flatMap { $0 }
63+
}
64+
"""
65+
)
66+
67+
FunctionDecl(
68+
"""
69+
/// If declared, provides contextual type information for statement
70+
/// expressions to translate them into partial results.
71+
public static func buildExpression(_ expression: Expression) -> Component {
72+
return [expression]
73+
}
74+
"""
75+
)
76+
77+
FunctionDecl(
78+
"""
79+
/// Add all the elements of `expression` to this result builder, effectively flattening them.
80+
public static func buildExpression(_ expression: \(type.expressibleAs)) -> Component {
81+
return expression.create\(type.buildable)().elements
82+
}
83+
"""
84+
)
85+
86+
FunctionDecl(
87+
"""
88+
/// Enables support for `if` statements that do not have an `else`.
89+
public static func buildOptional(_ component: Component?) -> Component {
90+
return component ?? []
91+
}
92+
"""
93+
)
94+
95+
FunctionDecl(
96+
"""
97+
/// With buildEither(second:), enables support for 'if-else' and 'switch'
98+
/// statements by folding conditional results into a single result.
99+
public static func buildEither(first component: Component) -> Component {
100+
return component
101+
}
102+
"""
103+
)
104+
105+
FunctionDecl(
106+
"""
107+
/// With buildEither(first:), enables support for 'if-else' and 'switch'
108+
/// statements by folding conditional results into a single result.
109+
public static func buildEither(second component: Component) -> Component {
110+
return component
111+
}
112+
"""
113+
)
114+
115+
FunctionDecl(
116+
"""
117+
/// Enables support for 'for..in' loops by combining the
118+
/// results of all iterations into a single result.
119+
public static func buildArray(_ components: [Component]) -> Component {
120+
return components.flatMap { $0 }
121+
}
122+
"""
123+
)
124+
125+
FunctionDecl(
126+
"""
127+
/// If declared, this will be called on the partial result of an 'if'
128+
/// #available' block to allow the result builder to erase type
129+
/// information.
130+
public static func buildLimitedAvailability(_ component: Component) -> Component {
131+
return component
132+
}
133+
"""
134+
)
135+
136+
FunctionDecl(
137+
leadingTrivia: [
138+
.newlines(1),
139+
.docLineComment("/// If declared, this will be called on the partial result from the outermost"),
140+
.newlines(1),
141+
.docLineComment("/// block statement to produce the final returned result."),
142+
.newlines(1)],
143+
modifiers: [Token.public, Token.static],
144+
identifier: .identifier("buildFinalResult"),
145+
signature: FunctionSignature(
146+
input: ParameterClause {
147+
FunctionParameter(
148+
firstName: .wildcard,
149+
secondName: .identifier("component"),
150+
colon: .colon,
151+
type: "Component")
152+
},
153+
output: "FinalResult")) {
154+
if elementType.isToken {
155+
ReturnStmt(
156+
"""
157+
return .init(component)
158+
"""
159+
)
160+
} else if elementType.hasWithTrailingCommaTrait {
161+
VariableDecl(
162+
"""
163+
let lastIndex = component.count - 1
164+
"""
165+
)
166+
167+
ReturnStmt(
168+
"""
169+
return .init(component.enumerated().map { index, source in
170+
let element = source.create\(elementType.buildableBaseName)()
171+
return index < lastIndex ? element.ensuringTrailingComma() : element
172+
})
173+
"""
174+
)
175+
} else {
176+
ReturnStmt(
177+
"""
178+
return .init(component.map { $0.create\(elementType.buildableBaseName)() })
179+
"""
180+
)
181+
}
182+
}
183+
}
184+
185+
ExtensionDecl(
186+
"""
187+
public extension \(type.buildableBaseName) {
188+
init(@\(type.resultBuilderBaseName) itemsBuilder: () -> \(type.buildableBaseName)) {
189+
self = itemsBuilder()
190+
}
191+
}
192+
"""
193+
)
194+
}
195+
}

Package.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ let package = Package(
9797
dependencies: ["SwiftBasicFormat", "SwiftSyntax", "SwiftParser"],
9898
exclude: [
9999
"gyb_helpers",
100-
"ResultBuilders.swift.gyb",
101100
]
102101
),
103102
.target(

Sources/SwiftSyntaxBuilder/ResultBuilders.swift.gyb

Lines changed: 0 additions & 111 deletions
This file was deleted.

0 commit comments

Comments
 (0)