Skip to content

Commit ebb1a0c

Browse files
committed
[Syntax] Provide a generic casting function that can cast any protocol type
This is useful for clients to be able to cast to their own protocol types. This replaces the existing casting functions that were going through existentials.
1 parent 946caf4 commit ebb1a0c

File tree

6 files changed

+29
-210
lines changed

6 files changed

+29
-210
lines changed

Sources/SwiftSyntax/Syntax.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
3434
self = syntax._syntaxNode
3535
}
3636

37+
/// Create a `Syntax` node from a specialized optional syntax node.
38+
public init?<S: SyntaxProtocol>(_ syntax: S?) {
39+
guard let syntax = syntax else { return nil }
40+
self = syntax._syntaxNode
41+
}
42+
3743
public func hash(into hasher: inout Hasher) {
3844
return data.nodeId.hash(into: &hasher)
3945
}
@@ -428,6 +434,20 @@ public extension SyntaxProtocol {
428434
}
429435
}
430436

437+
public extension SyntaxProtocol {
438+
/// Check whether the non-type erased version of this syntax node conforms to
439+
/// provided type.
440+
func `is`<T>(_ type: T.Type) -> Bool {
441+
return self.as(type) != nil
442+
}
443+
444+
/// Return the non-type erased version of this syntax node if it conforms to
445+
/// provided type. Otherwise return `nil`.
446+
func `as`<T>(_ type: T.Type) -> T? {
447+
return Syntax(self).as(SyntaxProtocol.self) as? T
448+
}
449+
}
450+
431451
/// Sequence of tokens that are part of the provided Syntax node.
432452
public struct TokenSequence: Sequence {
433453
public struct Iterator: IteratorProtocol {

Sources/SwiftSyntax/SyntaxBaseNodes.swift.gyb

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,6 @@
2929
/// DO NOT CONFORM TO THIS PROTOCOL YOURSELF!
3030
public protocol ${node.name}Protocol: ${base_type}Protocol {}
3131

32-
public extension Syntax {
33-
/// Check whether the non-type erased version of this syntax node conforms to
34-
/// ${node.name}Protocol.
35-
func `is`(_: ${node.name}Protocol.Protocol) -> Bool {
36-
return self.as(${node.name}Protocol.self) != nil
37-
}
38-
39-
/// Return the non-type erased version of this syntax node if it conforms to
40-
/// ${node.name}Protocol. Otherwise return nil.
41-
func `as`(_: ${node.name}Protocol.Protocol) -> ${node.name}Protocol? {
42-
return self.as(SyntaxProtocol.self) as? ${node.name}Protocol
43-
}
44-
}
45-
4632
% for line in dedented_lines(node.description):
4733
/// ${line}
4834
% end

Sources/SwiftSyntax/SyntaxTraits.swift.gyb

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,6 @@ public protocol ${trait.trait_name}Syntax: SyntaxProtocol {
3333
% end
3434
}
3535

36-
public extension SyntaxProtocol {
37-
/// Check whether the non-type erased version of this syntax node conforms to
38-
/// `${trait.trait_name}Syntax`.
39-
func `is`(_: ${trait.trait_name}Syntax.Protocol) -> Bool {
40-
return self.as(${trait.trait_name}Syntax.self) != nil
41-
}
42-
43-
/// Return the non-type erased version of this syntax node if it conforms to
44-
/// `${trait.trait_name}Syntax`. Otherwise return `nil`.
45-
func `as`(_: ${trait.trait_name}Syntax.Protocol) -> ${trait.trait_name}Syntax? {
46-
return Syntax(self).as(SyntaxProtocol.self) as? ${trait.trait_name}Syntax
47-
}
48-
}
49-
5036
% end
5137

5238
% for node in SYNTAX_NODES:

Sources/SwiftSyntax/gyb_generated/SyntaxBaseNodes.swift

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,6 @@
1919
/// DO NOT CONFORM TO THIS PROTOCOL YOURSELF!
2020
public protocol DeclSyntaxProtocol: SyntaxProtocol {}
2121

22-
public extension Syntax {
23-
/// Check whether the non-type erased version of this syntax node conforms to
24-
/// DeclSyntaxProtocol.
25-
func `is`(_: DeclSyntaxProtocol.Protocol) -> Bool {
26-
return self.as(DeclSyntaxProtocol.self) != nil
27-
}
28-
29-
/// Return the non-type erased version of this syntax node if it conforms to
30-
/// DeclSyntaxProtocol. Otherwise return nil.
31-
func `as`(_: DeclSyntaxProtocol.Protocol) -> DeclSyntaxProtocol? {
32-
return self.as(SyntaxProtocol.self) as? DeclSyntaxProtocol
33-
}
34-
}
35-
3622
public struct DeclSyntax: DeclSyntaxProtocol, SyntaxHashable {
3723
public let _syntaxNode: Syntax
3824

@@ -112,20 +98,6 @@ extension DeclSyntax: CustomReflectable {
11298
/// DO NOT CONFORM TO THIS PROTOCOL YOURSELF!
11399
public protocol ExprSyntaxProtocol: SyntaxProtocol {}
114100

115-
public extension Syntax {
116-
/// Check whether the non-type erased version of this syntax node conforms to
117-
/// ExprSyntaxProtocol.
118-
func `is`(_: ExprSyntaxProtocol.Protocol) -> Bool {
119-
return self.as(ExprSyntaxProtocol.self) != nil
120-
}
121-
122-
/// Return the non-type erased version of this syntax node if it conforms to
123-
/// ExprSyntaxProtocol. Otherwise return nil.
124-
func `as`(_: ExprSyntaxProtocol.Protocol) -> ExprSyntaxProtocol? {
125-
return self.as(SyntaxProtocol.self) as? ExprSyntaxProtocol
126-
}
127-
}
128-
129101
public struct ExprSyntax: ExprSyntaxProtocol, SyntaxHashable {
130102
public let _syntaxNode: Syntax
131103

@@ -205,20 +177,6 @@ extension ExprSyntax: CustomReflectable {
205177
/// DO NOT CONFORM TO THIS PROTOCOL YOURSELF!
206178
public protocol StmtSyntaxProtocol: SyntaxProtocol {}
207179

208-
public extension Syntax {
209-
/// Check whether the non-type erased version of this syntax node conforms to
210-
/// StmtSyntaxProtocol.
211-
func `is`(_: StmtSyntaxProtocol.Protocol) -> Bool {
212-
return self.as(StmtSyntaxProtocol.self) != nil
213-
}
214-
215-
/// Return the non-type erased version of this syntax node if it conforms to
216-
/// StmtSyntaxProtocol. Otherwise return nil.
217-
func `as`(_: StmtSyntaxProtocol.Protocol) -> StmtSyntaxProtocol? {
218-
return self.as(SyntaxProtocol.self) as? StmtSyntaxProtocol
219-
}
220-
}
221-
222180
public struct StmtSyntax: StmtSyntaxProtocol, SyntaxHashable {
223181
public let _syntaxNode: Syntax
224182

@@ -298,20 +256,6 @@ extension StmtSyntax: CustomReflectable {
298256
/// DO NOT CONFORM TO THIS PROTOCOL YOURSELF!
299257
public protocol TypeSyntaxProtocol: SyntaxProtocol {}
300258

301-
public extension Syntax {
302-
/// Check whether the non-type erased version of this syntax node conforms to
303-
/// TypeSyntaxProtocol.
304-
func `is`(_: TypeSyntaxProtocol.Protocol) -> Bool {
305-
return self.as(TypeSyntaxProtocol.self) != nil
306-
}
307-
308-
/// Return the non-type erased version of this syntax node if it conforms to
309-
/// TypeSyntaxProtocol. Otherwise return nil.
310-
func `as`(_: TypeSyntaxProtocol.Protocol) -> TypeSyntaxProtocol? {
311-
return self.as(SyntaxProtocol.self) as? TypeSyntaxProtocol
312-
}
313-
}
314-
315259
public struct TypeSyntax: TypeSyntaxProtocol, SyntaxHashable {
316260
public let _syntaxNode: Syntax
317261

@@ -391,20 +335,6 @@ extension TypeSyntax: CustomReflectable {
391335
/// DO NOT CONFORM TO THIS PROTOCOL YOURSELF!
392336
public protocol PatternSyntaxProtocol: SyntaxProtocol {}
393337

394-
public extension Syntax {
395-
/// Check whether the non-type erased version of this syntax node conforms to
396-
/// PatternSyntaxProtocol.
397-
func `is`(_: PatternSyntaxProtocol.Protocol) -> Bool {
398-
return self.as(PatternSyntaxProtocol.self) != nil
399-
}
400-
401-
/// Return the non-type erased version of this syntax node if it conforms to
402-
/// PatternSyntaxProtocol. Otherwise return nil.
403-
func `as`(_: PatternSyntaxProtocol.Protocol) -> PatternSyntaxProtocol? {
404-
return self.as(SyntaxProtocol.self) as? PatternSyntaxProtocol
405-
}
406-
}
407-
408338
public struct PatternSyntax: PatternSyntaxProtocol, SyntaxHashable {
409339
public let _syntaxNode: Syntax
410340

Sources/SwiftSyntax/gyb_generated/SyntaxTraits.swift

Lines changed: 0 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,6 @@ public protocol DeclGroupSyntax: SyntaxProtocol {
2323
func withMembers(_ newChild: MemberDeclBlockSyntax?) -> Self
2424
}
2525

26-
public extension SyntaxProtocol {
27-
/// Check whether the non-type erased version of this syntax node conforms to
28-
/// `DeclGroupSyntax`.
29-
func `is`(_: DeclGroupSyntax.Protocol) -> Bool {
30-
return self.as(DeclGroupSyntax.self) != nil
31-
}
32-
33-
/// Return the non-type erased version of this syntax node if it conforms to
34-
/// `DeclGroupSyntax`. Otherwise return `nil`.
35-
func `as`(_: DeclGroupSyntax.Protocol) -> DeclGroupSyntax? {
36-
return Syntax(self).as(SyntaxProtocol.self) as? DeclGroupSyntax
37-
}
38-
}
39-
4026
// MARK: - BracedSyntax
4127

4228
public protocol BracedSyntax: SyntaxProtocol {
@@ -46,62 +32,20 @@ public protocol BracedSyntax: SyntaxProtocol {
4632
func withRightBrace(_ newChild: TokenSyntax?) -> Self
4733
}
4834

49-
public extension SyntaxProtocol {
50-
/// Check whether the non-type erased version of this syntax node conforms to
51-
/// `BracedSyntax`.
52-
func `is`(_: BracedSyntax.Protocol) -> Bool {
53-
return self.as(BracedSyntax.self) != nil
54-
}
55-
56-
/// Return the non-type erased version of this syntax node if it conforms to
57-
/// `BracedSyntax`. Otherwise return `nil`.
58-
func `as`(_: BracedSyntax.Protocol) -> BracedSyntax? {
59-
return Syntax(self).as(SyntaxProtocol.self) as? BracedSyntax
60-
}
61-
}
62-
6335
// MARK: - IdentifiedDeclSyntax
6436

6537
public protocol IdentifiedDeclSyntax: SyntaxProtocol {
6638
var identifier: TokenSyntax { get }
6739
func withIdentifier(_ newChild: TokenSyntax?) -> Self
6840
}
6941

70-
public extension SyntaxProtocol {
71-
/// Check whether the non-type erased version of this syntax node conforms to
72-
/// `IdentifiedDeclSyntax`.
73-
func `is`(_: IdentifiedDeclSyntax.Protocol) -> Bool {
74-
return self.as(IdentifiedDeclSyntax.self) != nil
75-
}
76-
77-
/// Return the non-type erased version of this syntax node if it conforms to
78-
/// `IdentifiedDeclSyntax`. Otherwise return `nil`.
79-
func `as`(_: IdentifiedDeclSyntax.Protocol) -> IdentifiedDeclSyntax? {
80-
return Syntax(self).as(SyntaxProtocol.self) as? IdentifiedDeclSyntax
81-
}
82-
}
83-
8442
// MARK: - WithCodeBlockSyntax
8543

8644
public protocol WithCodeBlockSyntax: SyntaxProtocol {
8745
var body: CodeBlockSyntax { get }
8846
func withBody(_ newChild: CodeBlockSyntax?) -> Self
8947
}
9048

91-
public extension SyntaxProtocol {
92-
/// Check whether the non-type erased version of this syntax node conforms to
93-
/// `WithCodeBlockSyntax`.
94-
func `is`(_: WithCodeBlockSyntax.Protocol) -> Bool {
95-
return self.as(WithCodeBlockSyntax.self) != nil
96-
}
97-
98-
/// Return the non-type erased version of this syntax node if it conforms to
99-
/// `WithCodeBlockSyntax`. Otherwise return `nil`.
100-
func `as`(_: WithCodeBlockSyntax.Protocol) -> WithCodeBlockSyntax? {
101-
return Syntax(self).as(SyntaxProtocol.self) as? WithCodeBlockSyntax
102-
}
103-
}
104-
10549
// MARK: - ParenthesizedSyntax
10650

10751
public protocol ParenthesizedSyntax: SyntaxProtocol {
@@ -111,41 +55,13 @@ public protocol ParenthesizedSyntax: SyntaxProtocol {
11155
func withRightParen(_ newChild: TokenSyntax?) -> Self
11256
}
11357

114-
public extension SyntaxProtocol {
115-
/// Check whether the non-type erased version of this syntax node conforms to
116-
/// `ParenthesizedSyntax`.
117-
func `is`(_: ParenthesizedSyntax.Protocol) -> Bool {
118-
return self.as(ParenthesizedSyntax.self) != nil
119-
}
120-
121-
/// Return the non-type erased version of this syntax node if it conforms to
122-
/// `ParenthesizedSyntax`. Otherwise return `nil`.
123-
func `as`(_: ParenthesizedSyntax.Protocol) -> ParenthesizedSyntax? {
124-
return Syntax(self).as(SyntaxProtocol.self) as? ParenthesizedSyntax
125-
}
126-
}
127-
12858
// MARK: - WithTrailingCommaSyntax
12959

13060
public protocol WithTrailingCommaSyntax: SyntaxProtocol {
13161
var trailingComma: TokenSyntax? { get }
13262
func withTrailingComma(_ newChild: TokenSyntax?) -> Self
13363
}
13464

135-
public extension SyntaxProtocol {
136-
/// Check whether the non-type erased version of this syntax node conforms to
137-
/// `WithTrailingCommaSyntax`.
138-
func `is`(_: WithTrailingCommaSyntax.Protocol) -> Bool {
139-
return self.as(WithTrailingCommaSyntax.self) != nil
140-
}
141-
142-
/// Return the non-type erased version of this syntax node if it conforms to
143-
/// `WithTrailingCommaSyntax`. Otherwise return `nil`.
144-
func `as`(_: WithTrailingCommaSyntax.Protocol) -> WithTrailingCommaSyntax? {
145-
return Syntax(self).as(SyntaxProtocol.self) as? WithTrailingCommaSyntax
146-
}
147-
}
148-
14965
// MARK: - LabeledSyntax
15066

15167
public protocol LabeledSyntax: SyntaxProtocol {
@@ -155,41 +71,13 @@ public protocol LabeledSyntax: SyntaxProtocol {
15571
func withLabelColon(_ newChild: TokenSyntax?) -> Self
15672
}
15773

158-
public extension SyntaxProtocol {
159-
/// Check whether the non-type erased version of this syntax node conforms to
160-
/// `LabeledSyntax`.
161-
func `is`(_: LabeledSyntax.Protocol) -> Bool {
162-
return self.as(LabeledSyntax.self) != nil
163-
}
164-
165-
/// Return the non-type erased version of this syntax node if it conforms to
166-
/// `LabeledSyntax`. Otherwise return `nil`.
167-
func `as`(_: LabeledSyntax.Protocol) -> LabeledSyntax? {
168-
return Syntax(self).as(SyntaxProtocol.self) as? LabeledSyntax
169-
}
170-
}
171-
17274
// MARK: - WithStatementsSyntax
17375

17476
public protocol WithStatementsSyntax: SyntaxProtocol {
17577
var statements: CodeBlockItemListSyntax { get }
17678
func withStatements(_ newChild: CodeBlockItemListSyntax?) -> Self
17779
}
17880

179-
public extension SyntaxProtocol {
180-
/// Check whether the non-type erased version of this syntax node conforms to
181-
/// `WithStatementsSyntax`.
182-
func `is`(_: WithStatementsSyntax.Protocol) -> Bool {
183-
return self.as(WithStatementsSyntax.self) != nil
184-
}
185-
186-
/// Return the non-type erased version of this syntax node if it conforms to
187-
/// `WithStatementsSyntax`. Otherwise return `nil`.
188-
func `as`(_: WithStatementsSyntax.Protocol) -> WithStatementsSyntax? {
189-
return Syntax(self).as(SyntaxProtocol.self) as? WithStatementsSyntax
190-
}
191-
}
192-
19381

19482
extension CodeBlockSyntax: BracedSyntax, WithStatementsSyntax {}
19583
extension DeclNameArgumentsSyntax: ParenthesizedSyntax {}

Tests/SwiftSyntaxTest/SyntaxTests.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import XCTest
22
import SwiftSyntax
33

4+
// Check that casting works with client protocols.
5+
protocol MyCustomProtocol {}
6+
extension IntegerLiteralExprSyntax: MyCustomProtocol {}
7+
48
public class SyntaxTests: XCTestCase {
59

610
public func testSyntaxAPI() {
@@ -113,5 +117,10 @@ public class SyntaxTests: XCTestCase {
113117

114118
XCTAssertTrue(classDecl.is(BracedSyntax.self))
115119
XCTAssertNotNil(classDecl.as(BracedSyntax.self))
120+
121+
XCTAssertTrue(expr.is(MyCustomProtocol.self))
122+
XCTAssertNotNil(expr.as(MyCustomProtocol.self))
123+
XCTAssertTrue(node.is(MyCustomProtocol.self))
124+
XCTAssertNotNil(node.as(MyCustomProtocol.self))
116125
}
117126
}

0 commit comments

Comments
 (0)