Skip to content

Commit 2d01e9c

Browse files
committed
WIP: Make convenience initializers with CodeGeneration
1 parent 29c1564 commit 2d01e9c

File tree

6 files changed

+150
-13
lines changed

6 files changed

+150
-13
lines changed

CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ public let ATTRIBUTE_NODES: [Node] = [
4949
nameForDiagnostics: "attribute",
5050
documentation: "An `@` attribute.",
5151
parserFunction: "parseAttribute",
52+
rules: [
53+
NodeInitRule(
54+
nonOptionalChildName: "arguments",
55+
childDefaultValues: [
56+
"leftParen": .leftParen,
57+
"rightParen": .rightParen
58+
])
59+
],
5260
children: [
5361
Child(
5462
name: "atSign",

CodeGeneration/Sources/SyntaxSupport/Node.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ public class Node {
6363
return kind.varOrCaseName
6464
}
6565

66+
/// List of convenience initializer rules for this node.
67+
public let rules: [NodeInitRule]
68+
6669
/// If this is a layout node, return a view of the node that provides access
6770
/// to the layout-node specific properties.
6871
public var layoutNode: LayoutNode? {
@@ -112,6 +115,7 @@ public class Node {
112115
documentation: String? = nil,
113116
parserFunction: TokenSyntax? = nil,
114117
traits: [String] = [],
118+
rules: [NodeInitRule] = [],
115119
children: [Child] = []
116120
) {
117121
precondition(base != .syntaxCollection)
@@ -124,6 +128,11 @@ public class Node {
124128
self.documentation = docCommentTrivia(from: documentation)
125129
self.parserFunction = parserFunction
126130

131+
132+
// FIXME: We should validate rules and check that all referenced children
133+
// elements in fact exist on that node.
134+
self.rules = rules
135+
127136
let childrenWithUnexpected: [Child]
128137
if children.isEmpty {
129138
childrenWithUnexpected = [
@@ -229,6 +238,7 @@ public class Node {
229238
isExperimental: Bool = false,
230239
nameForDiagnostics: String?,
231240
documentation: String? = nil,
241+
rules: [NodeInitRule] = [],
232242
parserFunction: TokenSyntax? = nil,
233243
elementChoices: [SyntaxNodeKind]
234244
) {
@@ -239,6 +249,7 @@ public class Node {
239249
self.nameForDiagnostics = nameForDiagnostics
240250
self.documentation = docCommentTrivia(from: documentation)
241251
self.parserFunction = parserFunction
252+
self.rules = rules
242253

243254
assert(!elementChoices.isEmpty)
244255
self.data = .collection(choices: elementChoices)
@@ -380,3 +391,8 @@ fileprivate extension Child {
380391
fileprivate extension Node {
381392

382393
}
394+
395+
public struct NodeInitRule {
396+
public let nonOptionalChildName: String
397+
public let childDefaultValues: [String: Token]
398+
}

CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public extension Child {
8484

8585
/// If the child node has a default value, return an expression of the form
8686
/// ` = default_value` that can be used as the default value to for a
87-
/// function parameter. Otherwise, return `nil`.
87+
/// function parameter. Otherwise, return `nil`.]
8888
var defaultInitialization: InitializerClauseSyntax? {
8989
if let defaultValue {
9090
return InitializerClauseSyntax(equal: .equalToken(leadingTrivia: .space, trailingTrivia: .space), value: defaultValue)

CodeGeneration/Sources/generate-swift-syntax/LayoutNode+Extensions.swift

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,65 @@ import SyntaxSupport
1616
import Utils
1717

1818
extension LayoutNode {
19-
func generateInitializerDeclHeader(useDeprecatedChildName: Bool = false) -> SyntaxNodeString {
19+
/// Generates a memberwise SyntaxNode initializer `SyntaxNodeString`.
20+
///
21+
/// - parameters:
22+
/// - rule: The ``NodeInitRule`` to use for generating the initializer. Applying a rule will make some children non-optional, and set default values for other children.
23+
/// - useDeprecatedChildName: Whether to use the deprecated child name for the initializer parameter.
24+
func generateInitializerDeclHeader(for rule: NodeInitRule? = nil, useDeprecatedChildName: Bool = false) -> SyntaxNodeString {
2025
if children.isEmpty {
2126
return "public init()"
2227
}
2328

24-
func createFunctionParameterSyntax(for child: Child) -> FunctionParameterSyntax {
29+
func childParameterName(for child: Child) -> TokenSyntax {
30+
let parameterName: TokenSyntax
31+
32+
if useDeprecatedChildName, let deprecatedVarName = child.deprecatedVarName {
33+
parameterName = deprecatedVarName
34+
} else {
35+
parameterName = child.varOrCaseName
36+
}
37+
return parameterName
38+
}
39+
40+
func ruleBasedChildIsOptional(for child: Child, with rule: NodeInitRule?) -> Bool? {
41+
if let rule = rule {
42+
if rule.nonOptionalChildName == child.name {
43+
return false
44+
} else {
45+
return child.isOptional
46+
}
47+
} else {
48+
return nil
49+
}
50+
}
51+
52+
func ruleBasedChildDefaultValue(for child: Child, with rule: NodeInitRule?) -> InitializerClauseSyntax? {
53+
if let rule, let defaultValue = rule.childDefaultValues[child.name] {
54+
return InitializerClauseSyntax(
55+
equal: .equalToken(leadingTrivia: .space, trailingTrivia: .space),
56+
value: ExprSyntax(".\(defaultValue.spec.varOrCaseName)Token()")
57+
)
58+
} else {
59+
return nil
60+
}
61+
}
62+
63+
func ruleBasedShouldOverrideDefault(for child: Child, with rule: NodeInitRule?) -> Bool {
64+
if let rule {
65+
// If the rule provides a default for this child, override it and set the rule-based default.
66+
if rule.childDefaultValues[child.name] != nil {
67+
return true
68+
}
69+
70+
// For the non-optional rule-based parameter, strip the default value (override, but there will be no default)
71+
return rule.nonOptionalChildName == child.name
72+
} else {
73+
return false
74+
}
75+
}
76+
77+
func createFunctionParameterSyntax(for child: Child, overrideOptional: Bool? = nil, shouldOverrideDefault: Bool = false, overrideDefaultValue: InitializerClauseSyntax? = nil) -> FunctionParameterSyntax {
2578
var paramType: TypeSyntax
2679
if !child.kind.isNodeChoicesEmpty {
2780
paramType = "\(child.syntaxChoicesType)"
@@ -31,37 +84,34 @@ extension LayoutNode {
3184
paramType = child.syntaxNodeKind.syntaxType
3285
}
3386

34-
if child.isOptional {
87+
if overrideOptional ?? child.isOptional {
3588
if paramType.is(SomeOrAnyTypeSyntax.self) {
3689
paramType = "(\(paramType))?"
3790
} else {
3891
paramType = "\(paramType)?"
3992
}
4093
}
4194

42-
let parameterName: TokenSyntax
43-
44-
if useDeprecatedChildName, let deprecatedVarName = child.deprecatedVarName {
45-
parameterName = deprecatedVarName
46-
} else {
47-
parameterName = child.varOrCaseName
48-
}
95+
let parameterName = childParameterName(for: child)
4996

5097
return FunctionParameterSyntax(
5198
leadingTrivia: .newline,
5299
firstName: child.isUnexpectedNodes ? .wildcardToken(trailingTrivia: .space) : parameterName,
53100
secondName: child.isUnexpectedNodes ? parameterName : nil,
54101
colon: .colonToken(),
55102
type: paramType,
56-
defaultValue: child.defaultInitialization
103+
defaultValue: shouldOverrideDefault ? overrideDefaultValue : child.defaultInitialization
57104
)
58105
}
59106

60107
let params = FunctionParameterListSyntax {
61108
FunctionParameterSyntax("leadingTrivia: Trivia? = nil")
62109

63110
for child in children {
64-
createFunctionParameterSyntax(for: child)
111+
createFunctionParameterSyntax(for: child,
112+
overrideOptional: ruleBasedChildIsOptional(for: child, with: rule),
113+
shouldOverrideDefault: ruleBasedShouldOverrideDefault(for: child, with: rule),
114+
overrideDefaultValue: ruleBasedChildDefaultValue(for: child, with: rule))
65115
}
66116

67117
FunctionParameterSyntax("trailingTrivia: Trivia? = nil")
@@ -75,6 +125,14 @@ extension LayoutNode {
75125
"""
76126
}
77127

128+
func generateRuleBasedDefaultValuesDocComment(for rule: NodeInitRule) -> SwiftSyntax.Trivia {
129+
var params = ""
130+
for (childName, defaultValue) in rule.childDefaultValues {
131+
params += " - `\(childName)`: `TokenSyntax.\(defaultValue.spec.varOrCaseName)Token()`\n"
132+
}
133+
return docCommentTrivia(from: params)
134+
}
135+
78136
func generateInitializerDocComment() -> SwiftSyntax.Trivia {
79137
func generateParamDocComment(for child: Child) -> String? {
80138
if child.documentationAbstract.isEmpty {

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,35 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
7878
"""
7979
)
8080

81+
for rule in node.rules {
82+
try! InitializerDeclSyntax(
83+
"""
84+
/// A convenience initializer for ``\(node.kind.syntaxType)``
85+
/// that takes a non-optional value for `\(raw: rule.nonOptionalChildName)` parameter,
86+
/// and adds the following default values:
87+
\(node.generateRuleBasedDefaultValuesDocComment(for: rule))
88+
\(node.generateInitializerDeclHeader(for: rule))
89+
"""
90+
) {
91+
// Convenience initializer just calls the full initializer
92+
// with certain child parameters specified as optional types
93+
// and providing the rule-based default value for the affected
94+
// parameters.
95+
FunctionCallExprSyntax(
96+
calledExpression: ExprSyntax("self.init"),
97+
leftParen: .leftParenToken(),
98+
arguments: LabeledExprListSyntax {
99+
// generate the list of children for the call site.
100+
},
101+
rightParen: .rightParenToken()
102+
)
103+
}
104+
}
105+
106+
// The main member-wise initializer
107+
// generateInitializerDocComment renders DocC comment
108+
// generateInitializerDeclHeader renders the actual init declaration
109+
// and lists out all it's parameters and their default values
81110
try! InitializerDeclSyntax(
82111
"""
83112
\(node.generateInitializerDocComment())
@@ -100,6 +129,7 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
100129
)
101130
)
102131
)
132+
103133
let layoutList = ArrayExprSyntax {
104134
for child in node.children {
105135
ArrayElementSyntax(

Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,6 +2575,31 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable {
25752575
self._syntaxNode = Syntax(data)
25762576
}
25772577

2578+
/// A convenience initializer for ``AttributeSyntax``
2579+
/// that takes a non-optional value for `arguments` parameter,
2580+
/// and adds the following default values:
2581+
/// - `rightParen`: `rightParen`
2582+
/// - `leftParen`: `leftParen`
2583+
///
2584+
public init(
2585+
leadingTrivia: Trivia? = nil,
2586+
_ unexpectedBeforeAtSign: UnexpectedNodesSyntax? = nil,
2587+
atSign: TokenSyntax = .atSignToken(),
2588+
_ unexpectedBetweenAtSignAndAttributeName: UnexpectedNodesSyntax? = nil,
2589+
attributeName: some TypeSyntaxProtocol,
2590+
_ unexpectedBetweenAttributeNameAndLeftParen: UnexpectedNodesSyntax? = nil,
2591+
leftParen: TokenSyntax? = .leftParenToken(),
2592+
_ unexpectedBetweenLeftParenAndArguments: UnexpectedNodesSyntax? = nil,
2593+
arguments: Arguments,
2594+
_ unexpectedBetweenArgumentsAndRightParen: UnexpectedNodesSyntax? = nil,
2595+
rightParen: TokenSyntax? = .rightParenToken(),
2596+
_ unexpectedAfterRightParen: UnexpectedNodesSyntax? = nil,
2597+
trailingTrivia: Trivia? = nil
2598+
2599+
) {
2600+
self.init()
2601+
}
2602+
25782603
/// - Parameters:
25792604
/// - leadingTrivia: Trivia to be prepended to the leading trivia of the node’s first token. If the node is empty, there is no token to attach the trivia to and the parameter is ignored.
25802605
/// - atSign: The `@` sign.

0 commit comments

Comments
 (0)