Skip to content

Commit 8001bbd

Browse files
authored
Merge pull request #350 from ahoppen/pr/refactor-swiftsyntaxbuilder
Refactor generation of SwiftSyntaxBuilder
2 parents 6d5fb4e + bcb9919 commit 8001bbd

23 files changed

+18960
-15908
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
# dependency versions.
1010
Package.resolved
1111

12-
.DS_Store
12+
.DS_Store
13+
*.pyc
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
%{
2+
from gyb_syntax_support.kinds import SYNTAX_BASE_KINDS
3+
from gyb_helpers import SyntaxBuildableType, conformance_clause
4+
# -*- mode: Swift -*-
5+
# Ignore the following admonition it applies to the resulting .swift file only
6+
}%
7+
//// Automatically Generated From BuildableBaseProtocols.swift.gyb.
8+
//// Do Not Edit Directly!
9+
//===----------------------------------------------------------------------===//
10+
//
11+
// This source file is part of the Swift.org open source project
12+
//
13+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
14+
// Licensed under Apache License v2.0 with Runtime Library Exception
15+
//
16+
// See https://swift.org/LICENSE.txt for license information
17+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
18+
//
19+
//===----------------------------------------------------------------------===//
20+
21+
import SwiftSyntax
22+
23+
%{
24+
# `SyntaxCollectionBuildable` and `ExpressibleAsSyntaxCollectionBuildable` don't exist because of the following reason:
25+
# Through `ExpressibleAs*` conformances, a syntax kind might conform to `ExpressibleAsSyntaxCollectionBuildable` via different paths, thus the implementation of `createSyntaxCollectionBuildable` is ambiguous.
26+
# We have the same issue for `ExpressibleAsSyntaxBuildable`, but in that case the solution is simple: Create a custom implementaiton of `createSyntaxBuildable` that doesn't perform any of the conversions via the `ExpressibleAs` protocols.
27+
# For `SyntaxCollection` we would need to perform at least one conversion to end up with a type that we can put inside a `SyntaxCollection`, so there is no single canonical implementation.
28+
# Since the types don't provide any value, we don't generate them for now.
29+
}%
30+
% for kind in [syntax_base_kind for syntax_base_kind in SYNTAX_BASE_KINDS if syntax_base_kind != 'SyntaxCollection']:
31+
% type = SyntaxBuildableType(kind)
32+
% buildable_conformances = [type.expressible_as(), type.list_buildable()] # Types that the `*Buildable` conforms to
33+
% list_conformances = [] # Types that the `*ListBuildable` conforms to
34+
% if kind != 'Syntax':
35+
% syntax_type = SyntaxBuildableType('Syntax')
36+
% list_conformances.append(syntax_type.list_buildable())
37+
% buildable_conformances.append(syntax_type.buildable())
38+
% end
39+
40+
public protocol ${type.list_buildable()}${conformance_clause(list_conformances)} {
41+
/// Builds list of `${type.syntax()}`s.
42+
/// - Parameter format: The `Format` to use.
43+
/// - Parameter leadingTrivia: Replaces the the last leading trivia if not nil.
44+
/// - Returns: A list of `${type.syntax()}`.
45+
func build${type.base_name()}List(format: Format, leadingTrivia: Trivia?) -> [${type.syntax()}]
46+
}
47+
48+
public protocol ${type.buildable()}${conformance_clause(buildable_conformances)} {
49+
/// Builds a `${type.syntax()}`.
50+
/// - Parameter format: The `Format` to use.
51+
/// - Parameter leadingTrivia: Replaces the the last leading trivia if not nil.
52+
/// - Returns: A list of `${type.syntax()}`.
53+
func build${type.base_name()}(format: Format, leadingTrivia: Trivia?) -> ${type.syntax()}
54+
}
55+
56+
public extension ${type.buildable()} {
57+
/// Satisfies conformance to `${type.expressible_as()}`.
58+
func create${type.buildable_base_name()}() -> ${type.buildable()} {
59+
return self
60+
}
61+
62+
/// Builds list of `${type.syntax()}`s.
63+
/// - Parameter format: The `Format` to use.
64+
/// - Parameter leadingTrivia: Replaces the the last leading trivia if not nil.
65+
/// - Returns: A list of `${type.syntax()}`.
66+
///
67+
/// Satisfies conformance to `${type.list_buildable()}`.
68+
func build${type.base_name()}List(format: Format, leadingTrivia: Trivia? = nil) -> [${type.syntax()}] {
69+
return [build${type.base_name()}(format: format, leadingTrivia: leadingTrivia)]
70+
}
71+
72+
% if kind != 'Syntax':
73+
/// Builds a `${type.syntax()}`.
74+
/// - Parameter format: The `Format` to use.
75+
/// - Parameter leadingTrivia: Replaces the the last leading trivia if not nil.
76+
/// - Returns: A new `Syntax` with the builded `${type.syntax()}`.
77+
///
78+
/// Satisfies conformance to `SyntaxBuildable`.
79+
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
80+
return Syntax(build${type.base_name()}(format: format, leadingTrivia: leadingTrivia))
81+
}
82+
% end
83+
}
84+
85+
% end
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
%{
2+
from gyb_syntax_support import SYNTAX_NODES
3+
from gyb_helpers import flat_documentation, SyntaxBuildableChild, SyntaxBuildableNode
4+
# -*- mode: Swift -*-
5+
# Ignore the following admonition it applies to the resulting .swift file only
6+
}%
7+
//// Automatically Generated From BuildableCollectionNodes.swift.gyb.
8+
//// Do Not Edit Directly!
9+
//===----------------------------------------------------------------------===//
10+
//
11+
// This source file is part of the Swift.org open source project
12+
//
13+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
14+
// Licensed under Apache License v2.0 with Runtime Library Exception
15+
//
16+
// See https://swift.org/LICENSE.txt for license information
17+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
18+
//
19+
//===----------------------------------------------------------------------===//
20+
21+
import SwiftSyntax
22+
23+
% for node in [SyntaxBuildableNode(syntax_node) for syntax_node in SYNTAX_NODES if syntax_node.is_syntax_collection()]:
24+
% type = node.type()
25+
% element_type = node.collection_element_type()
26+
% if node.documentation():
27+
/// ${node.documentation()}
28+
% end
29+
public struct ${type.buildable()}: ExpressibleByArrayLiteral, SyntaxBuildable, ${type.expressible_as()} {
30+
let elements: [${element_type.buildable()}]
31+
32+
/// Creates a `${type.buildable()}` with the provided list of elements.
33+
/// - Parameters:
34+
/// - elements: A list of `${element_type.expressible_as()}`
35+
public init(_ elements: [${element_type.expressible_as()}]) {
36+
% if element_type.is_token():
37+
self.elements = elements
38+
% else:
39+
self.elements = elements.map { $0.create${element_type.buildable_base_name()}() }
40+
% end
41+
}
42+
43+
public init(arrayLiteral elements: ${element_type.expressible_as()}...) {
44+
self.init(elements)
45+
}
46+
47+
public func build${type.base_name()}(format: Format, leadingTrivia: Trivia? = nil) -> ${type.syntax()} {
48+
% if element_type.is_token():
49+
let result = SyntaxFactory.make${type.base_name()}(elements)
50+
% else:
51+
% if node.elements_separated_by_newline():
52+
% element_leading_trivia = 'Trivia.newlines(1) + format._makeIndent()'
53+
% else:
54+
% element_leading_trivia = 'nil'
55+
% end
56+
let result = SyntaxFactory.make${type.base_name()}(elements.map {
57+
$0.build${element_type.base_name()}(format: format, leadingTrivia: ${element_leading_trivia})
58+
})
59+
% end
60+
if let leadingTrivia = leadingTrivia {
61+
return result.withLeadingTrivia(leadingTrivia + (result.leadingTrivia ?? []))
62+
} else {
63+
return result
64+
}
65+
}
66+
67+
public func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
68+
return Syntax(build${type.base_name()}(format: format, leadingTrivia: leadingTrivia))
69+
}
70+
71+
/// Conformance to `${type.expressible_as()}`.
72+
public func create${type.buildable_base_name()}() -> ${type.buildable()} {
73+
return self
74+
}
75+
76+
/// `${type.buildable()}` might conform to `SyntaxBuildable` via different `ExpressibleAs*` paths.
77+
/// Thus, there are multiple default implementations for `createSyntaxBuildable`, some of which perform conversions through `ExpressibleAs*` protocols.
78+
/// To resolve the ambiguity, provide a fixed implementation that doesn't perform any conversions.
79+
public func createSyntaxBuildable() -> SyntaxBuildable {
80+
return self
81+
}
82+
}
83+
84+
% end
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
%{
2+
from gyb_syntax_support import SYNTAX_NODES
3+
from gyb_helpers import SyntaxBuildableNode
4+
# -*- mode: Swift -*-
5+
# Ignore the following admonition it applies to the resulting .swift file only
6+
}%
7+
//// Automatically Generated From BuildableNodes.swift.gyb.
8+
//// Do Not Edit Directly!
9+
//===----------------------------------------------------------------------===//
10+
//
11+
// This source file is part of the Swift.org open source project
12+
//
13+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
14+
// Licensed under Apache License v2.0 with Runtime Library Exception
15+
//
16+
// See https://swift.org/LICENSE.txt for license information
17+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
18+
//
19+
//===----------------------------------------------------------------------===//
20+
21+
import SwiftSyntax
22+
23+
% for node in [SyntaxBuildableNode(syntax_node) for syntax_node in SYNTAX_NODES if syntax_node.is_buildable()]:
24+
% type = node.type()
25+
% base_type = node.base_type()
26+
% if node.documentation():
27+
/// ${node.documentation()}
28+
% end
29+
public struct ${type.buildable()}: ${base_type.buildable()}, ${type.expressible_as()} {
30+
% children = node.children()
31+
% for child in children:
32+
let ${child.name()}: ${child.type().buildable()}
33+
% end
34+
35+
/// Creates a `${type.buildable()}` using the provided parameters.
36+
/// - Parameters:
37+
% for child in children:
38+
/// - ${child.name()}: ${child.documentation()}
39+
% end
40+
public init(
41+
${',\n '.join(['%s: %s%s' % (
42+
child.name(),
43+
child.type().expressible_as(),
44+
child.type().default_initialization()
45+
) for child in children])}
46+
) {
47+
% for child in children:
48+
% assert_stmt = child.generate_assert_stmt_text_choices(child.name())
49+
self.${child.name()} = ${child.type().generate_expr_convert_param_type_to_storage_type(child.name())}
50+
% if assert_stmt:
51+
${assert_stmt}
52+
% end
53+
% end
54+
}
55+
56+
%{
57+
create_convenience_initializer = False # Only create the convenience initializer if at least one parameter is different than in the initializer defined above
58+
# Keep track of init parameter and result builder parameters in different lists to make sure result builder params occur at the end, so they can use trailing closure syntax
59+
convenience_init_normal_parameters = []
60+
convenience_init_result_builder_parameters = []
61+
delegated_init_args = []
62+
for child in node.children():
63+
produce_expr = None # The expression that is used to call the default initializer defined above
64+
if child.type().is_syntax_collection(): # Allow initializing syntax collections with result builders
65+
create_convenience_initializer = True
66+
default_value = ' = { nil }' if child.type().is_optional else ' = { %s([]) }' % (child.type().buildable())
67+
convenience_init_result_builder_parameters.append('@%s %sBuilder: () -> %s%s' % (child.type().non_optional().result_builder(), child.name(), child.type().expressible_as(), default_value))
68+
produce_expr = '%sBuilder()' % child.name()
69+
elif child.type().token() and not child.type().token().text: # Allow initializing identifier or a token without default text with String value
70+
create_convenience_initializer = True
71+
if child.type().is_optional:
72+
param_type = 'String?'
73+
produce_expr = '%s.map(TokenSyntax.%s)' % (child.name(), child.type().token().swift_kind())
74+
else:
75+
param_type = 'String'
76+
produce_expr = 'TokenSyntax.%s(%s)' % (child.type().token().swift_kind(), child.name())
77+
convenience_init_normal_parameters.append('%s: %s' % (child.name(), param_type))
78+
else:
79+
convenience_init_normal_parameters.append('%s: %s%s' % (child.name(), child.type().expressible_as(), child.type().default_initialization()))
80+
produce_expr = child.name()
81+
delegated_init_args.append('%s: %s' % (child.name(), produce_expr))
82+
}%
83+
% if create_convenience_initializer:
84+
/// A convenience initializer that allows:
85+
/// - Initializing syntax collections using result builders
86+
/// - Initializing tokens without default text using strings
87+
public init(
88+
${',\n '.join(convenience_init_normal_parameters + convenience_init_result_builder_parameters)}
89+
) {
90+
self.init(
91+
${',\n '.join(delegated_init_args)}
92+
)
93+
}
94+
% end
95+
96+
func build${type.base_name()}(format: Format, leadingTrivia: Trivia? = nil) -> ${type.syntax()} {
97+
let result = SyntaxFactory.make${type.base_name()}(
98+
${',\n '.join(['%s: %s' % (child.name(), child.generate_expr_build_syntax_node(child.name(), 'format')) for child in children])}
99+
)
100+
if let leadingTrivia = leadingTrivia {
101+
return result.withLeadingTrivia(leadingTrivia + (result.leadingTrivia ?? []))
102+
} else {
103+
return result
104+
}
105+
}
106+
107+
/// Conformance to `${base_type.buildable()}`.
108+
public func build${base_type.base_name()}(format: Format, leadingTrivia: Trivia? = nil) -> ${base_type.syntax()} {
109+
let result = build${type.base_name()}(format: format, leadingTrivia: leadingTrivia)
110+
return ${base_type.syntax()}(result)
111+
}
112+
113+
/// Conformance to `${type.expressible_as()}`.
114+
public func create${type.buildable_base_name()}() -> ${type.buildable()} {
115+
return self
116+
}
117+
118+
/// `${type.buildable()}` might conform to `${base_type.expressible_as()}` via different `ExpressibleAs*` paths.
119+
/// Thus, there are multiple default implementations for `create${base_type.buildable_base_name()}`, some of which perform conversions through `ExpressibleAs*` protocols.
120+
/// To resolve the ambiguity, provide a fixed implementation that doesn't perform any conversions.
121+
public func create${base_type.buildable_base_name()}() -> ${base_type.buildable()} {
122+
return self
123+
}
124+
125+
% if base_type.base_name() != 'Syntax':
126+
/// `${type.buildable()}` might conform to `SyntaxBuildable` via different `ExpressibleAs*` paths.
127+
/// Thus, there are multiple default implementations for `createSyntaxBuildable`, some of which perform conversions through `ExpressibleAs*` protocols.
128+
/// To resolve the ambiguity, provide a fixed implementation that doesn't perform any conversions.
129+
public func createSyntaxBuildable() -> SyntaxBuildable {
130+
return self
131+
}
132+
% end
133+
}
134+
% end

0 commit comments

Comments
 (0)