Skip to content

Commit 918c260

Browse files
authored
Merge pull request #2827 from AppAppWorks/unify-code-generation-node-choices
[CodeGeneration] Unify handling for node choices
2 parents bef1ba1 + 4d6b90a commit 918c260

28 files changed

+832
-864
lines changed

CodeGeneration/Sources/SyntaxSupport/Child.swift

+1-7
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public enum ChildKind {
8080

8181
/// A child of a node, that may be declared optional or a token with a
8282
/// restricted subset of acceptable kinds or texts.
83-
public class Child: IdentifierConvertible {
83+
public class Child: NodeChoiceConvertible {
8484
/// The name of the child.
8585
///
8686
/// The first character of the name is always uppercase.
@@ -97,8 +97,6 @@ public class Child: IdentifierConvertible {
9797
/// Whether this child is optional and can be `nil`.
9898
public let isOptional: Bool
9999

100-
/// The experimental feature the child represents, or `nil` if this isn't
101-
/// for an experimental feature.
102100
public let experimentalFeature: ExperimentalFeature?
103101

104102
/// A name of this child that can be shown in diagnostics.
@@ -129,10 +127,6 @@ public class Child: IdentifierConvertible {
129127
/// The first line of the child's documentation
130128
public let documentationAbstract: String
131129

132-
/// If `true`, this is for an experimental language feature, and any public
133-
/// API generated should be SPI.
134-
public var isExperimental: Bool { experimentalFeature != nil }
135-
136130
public var syntaxNodeKind: SyntaxNodeKind {
137131
switch kind {
138132
case .node(kind: let kind):

CodeGeneration/Sources/SyntaxSupport/Node.swift

+6-14
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import SwiftSyntax
2121
/// but fixed types.
2222
/// - Collection nodes contains an arbitrary number of children but all those
2323
/// children are of the same type.
24-
public class Node: IdentifierConvertible {
24+
public class Node: NodeChoiceConvertible {
2525
fileprivate enum Data {
2626
case layout(children: [Child], traits: [String])
2727
case collection(choices: [SyntaxNodeKind])
@@ -40,8 +40,6 @@ public class Node: IdentifierConvertible {
4040
/// The kind of node’s supertype. This kind must have `isBase == true`
4141
public let base: SyntaxNodeKind
4242

43-
/// The experimental feature the node is part of, or `nil` if this isn't
44-
/// for an experimental feature.
4543
public let experimentalFeature: ExperimentalFeature?
4644

4745
/// When the node name is printed for diagnostics, this name is used.
@@ -57,9 +55,9 @@ public class Node: IdentifierConvertible {
5755
/// function that should be invoked to create this node.
5856
public let parserFunction: TokenSyntax?
5957

60-
/// If `true`, this is for an experimental language feature, and any public
61-
/// API generated should be SPI.
62-
public var isExperimental: Bool { experimentalFeature != nil }
58+
public var syntaxNodeKind: SyntaxNodeKind {
59+
self.kind
60+
}
6361

6462
/// A name for this node as an identifier.
6563
public var identifier: TokenSyntax {
@@ -112,14 +110,8 @@ public class Node: IdentifierConvertible {
112110
return attrList.with(\.trailingTrivia, attrList.isEmpty ? [] : .newline)
113111
}
114112

115-
/// The documentation note to print for an experimental feature.
116-
public var experimentalDocNote: SwiftSyntax.Trivia {
117-
let comment = experimentalFeature.map {
118-
"""
119-
- Experiment: Requires experimental feature `\($0.token)`.
120-
"""
121-
}
122-
return SwiftSyntax.Trivia.docCommentTrivia(from: comment)
113+
public var apiAttributes: AttributeListSyntax {
114+
self.apiAttributes()
123115
}
124116

125117
/// Construct the specification for a layout syntax node.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 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 SwiftSyntax
14+
15+
/// Instances of a conforming type should provide necessary information for generating code of a node choice.
16+
public protocol NodeChoiceConvertible: IdentifierConvertible {
17+
/// A docc comment describing the syntax node convertible, including the trivia provided when
18+
/// initializing the syntax node convertible, and the list of possible token choices inferred automatically.
19+
var documentation: SwiftSyntax.Trivia { get }
20+
21+
/// The experimental feature the syntax node convertible represents, or `nil` if this isn't
22+
/// for an experimental feature.
23+
var experimentalFeature: ExperimentalFeature? { get }
24+
25+
/// The attributes that should be printed on any API for the syntax node convertible.
26+
///
27+
/// This is typically used to mark APIs as SPI when the keyword is part of
28+
/// an experimental language feature.
29+
var apiAttributes: AttributeListSyntax { get }
30+
31+
/// The kind of the syntax node convertible.
32+
var syntaxNodeKind: SyntaxNodeKind { get }
33+
}
34+
35+
public extension NodeChoiceConvertible {
36+
/// The documentation note to print for an experimental feature.
37+
var experimentalDocNote: SwiftSyntax.Trivia {
38+
guard let experimentalFeature else {
39+
return []
40+
}
41+
return .docCommentTrivia(from: "- Note: Requires experimental feature `\(experimentalFeature.token)`.")
42+
}
43+
44+
/// If `true`, this is for an experimental language feature, and any public
45+
/// API generated should be SPI.
46+
var isExperimental: Bool {
47+
self.experimentalFeature != nil
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 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 SwiftSyntax
14+
15+
/// A wrapper of ``SyntaxNodeKind`` providing syntax type information for the raw side.
16+
public struct RawSyntaxNodeKind: TypeConvertible {
17+
public var syntaxNodeKind: SyntaxNodeKind
18+
19+
public var isBase: Bool {
20+
self.syntaxNodeKind.isBase
21+
}
22+
23+
public var syntaxType: TypeSyntax {
24+
"Raw\(self.syntaxNodeKind.syntaxType)"
25+
}
26+
27+
public var protocolType: TypeSyntax {
28+
switch self {
29+
case .syntax, .syntaxCollection:
30+
return "RawSyntaxNodeProtocol"
31+
default:
32+
return "\(self.syntaxType)NodeProtocol"
33+
}
34+
}
35+
36+
public var isAvailableInDocc: Bool {
37+
false
38+
}
39+
40+
public static func ~= (lhs: SyntaxNodeKind, rhs: Self) -> Bool {
41+
lhs == rhs.syntaxNodeKind
42+
}
43+
}

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

+5-34
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import SwiftSyntaxBuilder
1717
///
1818
/// Using the cases of this enum, children of syntax nodes can refer the syntax
1919
/// node that defines their layout.
20-
public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
20+
public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeConvertible {
2121
// Please keep this list sorted alphabetically
2222

2323
case _canImportExpr
@@ -327,7 +327,6 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
327327
}
328328
}
329329

330-
/// Whether this is one of the syntax base nodes.
331330
public var isBase: Bool {
332331
switch self {
333332
case .decl, .expr, .pattern, .stmt, .syntax, .syntaxCollection, .type:
@@ -337,12 +336,10 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
337336
}
338337
}
339338

340-
/// A name for this node as an identifier.
341339
public var identifier: TokenSyntax {
342340
return .identifier(rawValue)
343341
}
344342

345-
/// The type name of this node in the SwiftSyntax module.
346343
public var syntaxType: TypeSyntax {
347344
switch self {
348345
case .syntax:
@@ -354,7 +351,6 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
354351
}
355352
}
356353

357-
/// Whether the node is public API and not underscored/deprecated and can thus be referenced in docc links.
358354
public var isAvailableInDocc: Bool {
359355
if let node = SYNTAX_NODE_MAP[self], node.isExperimental {
360356
return false
@@ -365,39 +361,10 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
365361
}
366362
}
367363

368-
/// If this node is non-experimental a docc link wrapped in two backticks.
369-
///
370-
/// For experimental nodes, the node's type name in code font.
371-
public var doccLink: String {
372-
if isAvailableInDocc {
373-
return "``\(syntaxType)``"
374-
} else {
375-
return "`\(syntaxType)`"
376-
}
377-
}
378-
379-
/// For base nodes, the name of the corresponding protocol to which all the
380-
/// concrete nodes that have this base kind, conform.
381364
public var protocolType: TypeSyntax {
382365
return "\(syntaxType)Protocol"
383366
}
384367

385-
/// The name of this node at the `RawSyntax` level.
386-
public var rawType: TypeSyntax {
387-
return "Raw\(syntaxType)"
388-
}
389-
390-
/// For base nodes, the name of the corresponding raw protocol to which all the
391-
/// concrete raw nodes that have this base kind, conform.
392-
public var rawProtocolType: TypeSyntax {
393-
switch self {
394-
case .syntax, .syntaxCollection:
395-
return "RawSyntaxNodeProtocol"
396-
default:
397-
return "Raw\(raw: rawValue.withFirstCharacterUppercased)SyntaxNodeProtocol"
398-
}
399-
}
400-
401368
/// For base node types, generates the name of the protocol to which all
402369
/// concrete leaf nodes that derive from this base kind should conform.
403370
///
@@ -507,4 +474,8 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
507474
AttributeSyntax(#"@available(*, deprecated, renamed: "\#(syntaxType)")"#)
508475
}
509476
}
477+
478+
public var raw: RawSyntaxNodeKind {
479+
RawSyntaxNodeKind(syntaxNodeKind: self)
480+
}
510481
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 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 SwiftSyntax
14+
15+
/// Instances of a conforming type should provide syntax type information to be used in code generation.
16+
public protocol TypeConvertible {
17+
/// Whether this is one of the syntax base nodes.
18+
var isBase: Bool {
19+
get
20+
}
21+
22+
/// The type name of this node in the SwiftSyntax module.
23+
var syntaxType: TypeSyntax {
24+
get
25+
}
26+
27+
/// For base nodes, the name of the corresponding protocol to which all the
28+
/// concrete nodes that have this base kind, conform.
29+
var protocolType: TypeSyntax {
30+
get
31+
}
32+
33+
/// Whether the node is public API and not underscored/deprecated and can thus be referenced in docc links.
34+
var isAvailableInDocc: Bool {
35+
get
36+
}
37+
}
38+
39+
public extension TypeConvertible {
40+
/// If this node is non-experimental a docc link wrapped in two backticks.
41+
///
42+
/// For experimental nodes, the node's type name in code font.
43+
var doccLink: String {
44+
if self.isAvailableInDocc {
45+
return "``\(self.syntaxType)``"
46+
} else {
47+
return "`\(self.syntaxType)`"
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)