Skip to content

Commit b15030f

Browse files
authored
Merge pull request swiftlang#169 from ahoppen/as-protocol
Add casting methods to protocols
2 parents 897ac41 + 2861240 commit b15030f

20 files changed

+1210
-842
lines changed

Changelog.md

+9
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ For increased performance, the modelling of the syntax node hierarchy has been s
101101
exprSyntax.is(IdentifierExprSyntax.self)
102102
```
103103

104+
- To retrieve the non-type erased version of a type, use the `as(_: SyntaxProtocol.self)` method
105+
106+
```swift
107+
let identifierExprSyntax: IdentifierExprSyntax = /* ... */
108+
let node = Syntax(identifierExprSyntax)
109+
node.as(SyntaxProtocol.self) // returns a IdentifierExprSyntax with static type SyntaxProtocol
110+
node.as(ExprSyntaxProtocol.self) // returns a IdentifierExprSyntax with static type ExprSyntaxProtocol?
111+
```
112+
104113

105114
- Downcasting can no longer be performed using the `as` operator. For downcasting use the `as(_: SyntaxProtocol)` method on any type eraser. ([#155](https://github.com/apple/swift-syntax/pull/155))
106115

Sources/SwiftSyntax/Misc.swift.gyb

+10-4
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,16 @@ extension SyntaxNode {
3939
% end
4040
}
4141

42-
public extension Syntax {
43-
/// Retrieve the concretely typed node that this Syntax node wraps.
44-
/// This property is exposed for testing purposes only.
45-
var _asConcreteType: Any {
42+
extension Syntax {
43+
/// Syntax nodes always conform to SyntaxProtocol. This API is just added
44+
/// for consistency.
45+
@available(*, deprecated, message: "Expression always evaluates to true")
46+
public func `is`(_: SyntaxProtocol.Protocol) -> Bool {
47+
return true
48+
}
49+
50+
/// Return the non-type erased version of this syntax node.
51+
public func `as`(_: SyntaxProtocol.Protocol) -> SyntaxProtocol {
4652
switch self.as(SyntaxEnum.self) {
4753
case .token(let node):
4854
return node

Sources/SwiftSyntax/Syntax.swift

+19-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===-------------------- Syntax.swift - Syntax Protocol ------------------===//
1+
//===--------------- Syntax.swift - Base Syntax Type eraser --------------===//
22
//
33
// This source file is part of the Swift.org open source project
44
//
@@ -13,7 +13,7 @@
1313
/// A Syntax node represents a tree of nodes with tokens at the leaves.
1414
/// Each node has accessors for its known children, and allows efficient
1515
/// iteration over the children through its `children` property.
16-
public struct Syntax: SyntaxProtocol {
16+
public struct Syntax: SyntaxProtocol, SyntaxHashable {
1717
let data: SyntaxData
1818

1919
public var _syntaxNode: Syntax {
@@ -53,15 +53,30 @@ extension Syntax: CustomReflectable {
5353
/// Reconstructs the real syntax type for this type from the node's kind and
5454
/// provides a mirror that reflects this type.
5555
public var customMirror: Mirror {
56-
return Mirror(reflecting: self._asConcreteType)
56+
return Mirror(reflecting: self.as(SyntaxProtocol.self))
57+
}
58+
}
59+
60+
/// Protocol that provides a common Hashable implementation for all syntax nodes
61+
public protocol SyntaxHashable: Hashable {
62+
var _syntaxNode: Syntax { get }
63+
}
64+
65+
public extension SyntaxHashable {
66+
func hash(into hasher: inout Hasher) {
67+
return _syntaxNode.data.nodeId.hash(into: &hasher)
68+
}
69+
70+
static func ==(lhs: Self, rhs: Self) -> Bool {
71+
return lhs._syntaxNode.data.nodeId == rhs._syntaxNode.data.nodeId
5772
}
5873
}
5974

6075
/// Provide common functionality for specialized syntax nodes. Extend this
6176
/// protocol to provide common functionality for all syntax nodes.
6277
/// DO NOT CONFORM TO THIS PROTOCOL YOURSELF!
6378
public protocol SyntaxProtocol: CustomStringConvertible,
64-
CustomDebugStringConvertible, TextOutputStreamable, Hashable {
79+
CustomDebugStringConvertible, TextOutputStreamable {
6580

6681
/// Retrieve the generic syntax node that is represented by this node.
6782
/// Do not retrieve this property directly. Use `Syntax(self)` instead.
@@ -415,14 +430,6 @@ public extension SyntaxProtocol {
415430
where Target: TextOutputStream {
416431
data.raw.write(to: &target)
417432
}
418-
419-
func hash(into hasher: inout Hasher) {
420-
return data.nodeId.hash(into: &hasher)
421-
}
422-
423-
static func ==(lhs: Self, rhs: Self) -> Bool {
424-
return lhs.data.nodeId == rhs.data.nodeId
425-
}
426433
}
427434

428435
/// Sequence of tokens that are part of the provided Syntax node.

Sources/SwiftSyntax/SyntaxBaseNodes.swift.gyb

+28-2
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,24 @@
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+
3246
% for line in dedented_lines(node.description):
3347
/// ${line}
3448
% end
35-
public struct ${node.name}: ${node.name}Protocol {
49+
public struct ${node.name}: ${node.name}Protocol, SyntaxHashable {
3650
public let _syntaxNode: Syntax
3751

3852
public init<S: ${node.name}Protocol>(_ syntax: S) {
@@ -83,13 +97,25 @@ public struct ${node.name}: ${node.name}Protocol {
8397
public func `as`<S: ${node.name}Protocol>(_ syntaxType: S.Type) -> S? {
8498
return S.init(_syntaxNode)
8599
}
100+
101+
/// Syntax nodes always conform to `${node.name}Protocol`. This API is just
102+
/// added for consistency.
103+
@available(*, deprecated, message: "Expression always evaluates to true")
104+
public func `is`(_: ${node.name}Protocol.Protocol) -> Bool {
105+
return true
106+
}
107+
108+
/// Return the non-type erased version of this syntax node.
109+
public func `as`(_: ${node.name}Protocol.Protocol) -> ${node.name}Protocol {
110+
return Syntax(self).as(${node.name}Protocol.self)!
111+
}
86112
}
87113

88114
extension ${node.name}: CustomReflectable {
89115
/// Reconstructs the real syntax type for this type from the node's kind and
90116
/// provides a mirror that reflects this type.
91117
public var customMirror: Mirror {
92-
return Mirror(reflecting: Syntax(self)._asConcreteType)
118+
return Mirror(reflecting: Syntax(self).as(SyntaxProtocol.self))
93119
}
94120
}
95121

Sources/SwiftSyntax/SyntaxCollections.swift.gyb

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public protocol SyntaxCollection: SyntaxProtocol, Sequence {
3131
/// `${node.collection_element_type}` nodes. ${node.name} behaves
3232
/// as a regular Swift collection, and has accessors that return new
3333
/// versions of the collection with different children.
34-
public struct ${node.name}: SyntaxCollection {
34+
public struct ${node.name}: SyntaxCollection, SyntaxHashable {
3535
public let _syntaxNode: Syntax
3636

3737
/// Converts the given `Syntax` node to a `${node.name}` if possible. Returns

Sources/SwiftSyntax/SyntaxNodes.swift.gyb.template

+3-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ nodes whose base kind are that specified kind.
4646
% for line in dedented_lines(node.description):
4747
/// ${line}
4848
% end
49-
public struct ${node.name}: ${base_type}Protocol {
49+
public struct ${node.name}: ${base_type}Protocol, SyntaxHashable {
5050
% # ======
5151
% # Cursor
5252
% # ======
@@ -162,9 +162,9 @@ extension ${node.name}: CustomReflectable {
162162
return Mirror(self, children: [
163163
% for child in node.children:
164164
% if child.is_optional:
165-
"${child.swift_name}": ${child.swift_name}.map(Syntax.init)?._asConcreteType as Any,
165+
"${child.swift_name}": ${child.swift_name}.map(Syntax.init)?.as(SyntaxProtocol.self) as Any,
166166
% else:
167-
"${child.swift_name}": Syntax(${child.swift_name})._asConcreteType,
167+
"${child.swift_name}": Syntax(${child.swift_name}).as(SyntaxProtocol.self),
168168
% end
169169
% end
170170
])

Sources/SwiftSyntax/SyntaxOtherNodes.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// MARK: UnknownSyntax
1616

1717
/// A wrapper around a raw Syntax layout.
18-
public struct UnknownSyntax: SyntaxProtocol {
18+
public struct UnknownSyntax: SyntaxProtocol, SyntaxHashable {
1919
public let _syntaxNode: Syntax
2020

2121
/// Convert the given `Syntax` node to an `UnknownSyntax` if possible. Return
@@ -43,7 +43,7 @@ extension UnknownSyntax: CustomReflectable {
4343
// MARK: TokenSyntax
4444

4545
/// A Syntax node representing a single token.
46-
public struct TokenSyntax: SyntaxProtocol {
46+
public struct TokenSyntax: SyntaxProtocol, SyntaxHashable {
4747
public let _syntaxNode: Syntax
4848

4949
/// Converts the given `Syntax` node to a `TokenSyntax` if possible. Returns

Sources/SwiftSyntax/SyntaxTraits.swift.gyb

+17
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
//===----------------------------------------------------------------------===//
2121

2222
% for trait in TRAITS:
23+
// MARK: - ${trait.trait_name}Syntax
24+
2325
public protocol ${trait.trait_name}Syntax: SyntaxProtocol {
2426
% for child in trait.children:
2527
% ret_type = child.type_name
@@ -30,6 +32,21 @@ public protocol ${trait.trait_name}Syntax: SyntaxProtocol {
3032
func with${child.name}(_ newChild: ${child.type_name}?) -> Self
3133
% end
3234
}
35+
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+
3350
% end
3451

3552
% for node in SYNTAX_NODES:

Sources/SwiftSyntax/gyb_generated/Misc.swift

+10-4
Original file line numberDiff line numberDiff line change
@@ -1348,10 +1348,16 @@ extension SyntaxNode {
13481348
}
13491349
}
13501350

1351-
public extension Syntax {
1352-
/// Retrieve the concretely typed node that this Syntax node wraps.
1353-
/// This property is exposed for testing purposes only.
1354-
var _asConcreteType: Any {
1351+
extension Syntax {
1352+
/// Syntax nodes always conform to SyntaxProtocol. This API is just added
1353+
/// for consistency.
1354+
@available(*, deprecated, message: "Expression always evaluates to true")
1355+
public func `is`(_: SyntaxProtocol.Protocol) -> Bool {
1356+
return true
1357+
}
1358+
1359+
/// Return the non-type erased version of this syntax node.
1360+
public func `as`(_: SyntaxProtocol.Protocol) -> SyntaxProtocol {
13551361
switch self.as(SyntaxEnum.self) {
13561362
case .token(let node):
13571363
return node

0 commit comments

Comments
 (0)