Skip to content

Add casting methods to protocols #169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ For increased performance, the modelling of the syntax node hierarchy has been s
exprSyntax.is(IdentifierExprSyntax.self)
```

- To retrieve the non-type erased version of a type, use the `as(_: SyntaxProtocol.self)` method

```swift
let identifierExprSyntax: IdentifierExprSyntax = /* ... */
let node = Syntax(identifierExprSyntax)
node.as(SyntaxProtocol.self) // returns a IdentifierExprSyntax with static type SyntaxProtocol
node.as(ExprSyntaxProtocol.self) // returns a IdentifierExprSyntax with static type ExprSyntaxProtocol?
```


- 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))

Expand Down
14 changes: 10 additions & 4 deletions Sources/SwiftSyntax/Misc.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,16 @@ extension SyntaxNode {
% end
}

public extension Syntax {
/// Retrieve the concretely typed node that this Syntax node wraps.
/// This property is exposed for testing purposes only.
var _asConcreteType: Any {
extension Syntax {
/// Syntax nodes always conform to SyntaxProtocol. This API is just added
/// for consistency.
@available(*, deprecated, message: "Expression always evaluates to true")
public func `is`(_: SyntaxProtocol.Protocol) -> Bool {
return true
}

/// Return the non-type erased version of this syntax node.
public func `as`(_: SyntaxProtocol.Protocol) -> SyntaxProtocol {
switch self.as(SyntaxEnum.self) {
case .token(let node):
return node
Expand Down
31 changes: 19 additions & 12 deletions Sources/SwiftSyntax/Syntax.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//===-------------------- Syntax.swift - Syntax Protocol ------------------===//
//===--------------- Syntax.swift - Base Syntax Type eraser --------------===//
//
// This source file is part of the Swift.org open source project
//
Expand All @@ -13,7 +13,7 @@
/// A Syntax node represents a tree of nodes with tokens at the leaves.
/// Each node has accessors for its known children, and allows efficient
/// iteration over the children through its `children` property.
public struct Syntax: SyntaxProtocol {
public struct Syntax: SyntaxProtocol, SyntaxHashable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me, why is SyntaxHashable a separate protocol than SyntaxProtocol? Is there something that needs to conform to SyntaxProtocol but not SyntaxHashable or vice-versa?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hashable has a Self requirement and if we make SyntaxProtocol conform to Hashable, we cannot use it as a return type anymore.

let data: SyntaxData

public var _syntaxNode: Syntax {
Expand Down Expand Up @@ -53,15 +53,30 @@ extension Syntax: CustomReflectable {
/// Reconstructs the real syntax type for this type from the node's kind and
/// provides a mirror that reflects this type.
public var customMirror: Mirror {
return Mirror(reflecting: self._asConcreteType)
return Mirror(reflecting: self.as(SyntaxProtocol.self))
}
}

/// Protocol that provides a common Hashable implementation for all syntax nodes
public protocol SyntaxHashable: Hashable {
var _syntaxNode: Syntax { get }
}

public extension SyntaxHashable {
func hash(into hasher: inout Hasher) {
return _syntaxNode.data.nodeId.hash(into: &hasher)
}

static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs._syntaxNode.data.nodeId == rhs._syntaxNode.data.nodeId
}
}

/// Provide common functionality for specialized syntax nodes. Extend this
/// protocol to provide common functionality for all syntax nodes.
/// DO NOT CONFORM TO THIS PROTOCOL YOURSELF!
public protocol SyntaxProtocol: CustomStringConvertible,
CustomDebugStringConvertible, TextOutputStreamable, Hashable {
CustomDebugStringConvertible, TextOutputStreamable {

/// Retrieve the generic syntax node that is represented by this node.
/// Do not retrieve this property directly. Use `Syntax(self)` instead.
Expand Down Expand Up @@ -415,14 +430,6 @@ public extension SyntaxProtocol {
where Target: TextOutputStream {
data.raw.write(to: &target)
}

func hash(into hasher: inout Hasher) {
return data.nodeId.hash(into: &hasher)
}

static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.data.nodeId == rhs.data.nodeId
}
}

/// Sequence of tokens that are part of the provided Syntax node.
Expand Down
30 changes: 28 additions & 2 deletions Sources/SwiftSyntax/SyntaxBaseNodes.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,24 @@
/// DO NOT CONFORM TO THIS PROTOCOL YOURSELF!
public protocol ${node.name}Protocol: ${base_type}Protocol {}

public extension Syntax {
/// Check whether the non-type erased version of this syntax node conforms to
/// ${node.name}Protocol.
func `is`(_: ${node.name}Protocol.Protocol) -> Bool {
return self.as(${node.name}Protocol.self) != nil
}

/// Return the non-type erased version of this syntax node if it conforms to
/// ${node.name}Protocol. Otherwise return nil.
func `as`(_: ${node.name}Protocol.Protocol) -> ${node.name}Protocol? {
return self.as(SyntaxProtocol.self) as? ${node.name}Protocol
}
}

% for line in dedented_lines(node.description):
/// ${line}
% end
public struct ${node.name}: ${node.name}Protocol {
public struct ${node.name}: ${node.name}Protocol, SyntaxHashable {
public let _syntaxNode: Syntax

public init<S: ${node.name}Protocol>(_ syntax: S) {
Expand Down Expand Up @@ -83,13 +97,25 @@ public struct ${node.name}: ${node.name}Protocol {
public func `as`<S: ${node.name}Protocol>(_ syntaxType: S.Type) -> S? {
return S.init(_syntaxNode)
}

/// Syntax nodes always conform to `${node.name}Protocol`. This API is just
/// added for consistency.
@available(*, deprecated, message: "Expression always evaluates to true")
public func `is`(_: ${node.name}Protocol.Protocol) -> Bool {
return true
}

/// Return the non-type erased version of this syntax node.
public func `as`(_: ${node.name}Protocol.Protocol) -> ${node.name}Protocol {
return Syntax(self).as(${node.name}Protocol.self)!
}
}

extension ${node.name}: CustomReflectable {
/// Reconstructs the real syntax type for this type from the node's kind and
/// provides a mirror that reflects this type.
public var customMirror: Mirror {
return Mirror(reflecting: Syntax(self)._asConcreteType)
return Mirror(reflecting: Syntax(self).as(SyntaxProtocol.self))
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftSyntax/SyntaxCollections.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public protocol SyntaxCollection: SyntaxProtocol, Sequence {
/// `${node.collection_element_type}` nodes. ${node.name} behaves
/// as a regular Swift collection, and has accessors that return new
/// versions of the collection with different children.
public struct ${node.name}: SyntaxCollection {
public struct ${node.name}: SyntaxCollection, SyntaxHashable {
public let _syntaxNode: Syntax

/// Converts the given `Syntax` node to a `${node.name}` if possible. Returns
Expand Down
6 changes: 3 additions & 3 deletions Sources/SwiftSyntax/SyntaxNodes.swift.gyb.template
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ nodes whose base kind are that specified kind.
% for line in dedented_lines(node.description):
/// ${line}
% end
public struct ${node.name}: ${base_type}Protocol {
public struct ${node.name}: ${base_type}Protocol, SyntaxHashable {
% # ======
% # Cursor
% # ======
Expand Down Expand Up @@ -162,9 +162,9 @@ extension ${node.name}: CustomReflectable {
return Mirror(self, children: [
% for child in node.children:
% if child.is_optional:
"${child.swift_name}": ${child.swift_name}.map(Syntax.init)?._asConcreteType as Any,
"${child.swift_name}": ${child.swift_name}.map(Syntax.init)?.as(SyntaxProtocol.self) as Any,
% else:
"${child.swift_name}": Syntax(${child.swift_name})._asConcreteType,
"${child.swift_name}": Syntax(${child.swift_name}).as(SyntaxProtocol.self),
% end
% end
])
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftSyntax/SyntaxOtherNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// MARK: UnknownSyntax

/// A wrapper around a raw Syntax layout.
public struct UnknownSyntax: SyntaxProtocol {
public struct UnknownSyntax: SyntaxProtocol, SyntaxHashable {
public let _syntaxNode: Syntax

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

/// A Syntax node representing a single token.
public struct TokenSyntax: SyntaxProtocol {
public struct TokenSyntax: SyntaxProtocol, SyntaxHashable {
public let _syntaxNode: Syntax

/// Converts the given `Syntax` node to a `TokenSyntax` if possible. Returns
Expand Down
17 changes: 17 additions & 0 deletions Sources/SwiftSyntax/SyntaxTraits.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
//===----------------------------------------------------------------------===//

% for trait in TRAITS:
// MARK: - ${trait.trait_name}Syntax

public protocol ${trait.trait_name}Syntax: SyntaxProtocol {
% for child in trait.children:
% ret_type = child.type_name
Expand All @@ -30,6 +32,21 @@ public protocol ${trait.trait_name}Syntax: SyntaxProtocol {
func with${child.name}(_ newChild: ${child.type_name}?) -> Self
% end
}

public extension SyntaxProtocol {
/// Check whether the non-type erased version of this syntax node conforms to
/// `${trait.trait_name}Syntax`.
func `is`(_: ${trait.trait_name}Syntax.Protocol) -> Bool {
return self.as(${trait.trait_name}Syntax.self) != nil
}

/// Return the non-type erased version of this syntax node if it conforms to
/// `${trait.trait_name}Syntax`. Otherwise return `nil`.
func `as`(_: ${trait.trait_name}Syntax.Protocol) -> ${trait.trait_name}Syntax? {
return Syntax(self).as(SyntaxProtocol.self) as? ${trait.trait_name}Syntax
}
}

% end

% for node in SYNTAX_NODES:
Expand Down
14 changes: 10 additions & 4 deletions Sources/SwiftSyntax/gyb_generated/Misc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1348,10 +1348,16 @@ extension SyntaxNode {
}
}

public extension Syntax {
/// Retrieve the concretely typed node that this Syntax node wraps.
/// This property is exposed for testing purposes only.
var _asConcreteType: Any {
extension Syntax {
/// Syntax nodes always conform to SyntaxProtocol. This API is just added
/// for consistency.
@available(*, deprecated, message: "Expression always evaluates to true")
public func `is`(_: SyntaxProtocol.Protocol) -> Bool {
return true
}

/// Return the non-type erased version of this syntax node.
public func `as`(_: SyntaxProtocol.Protocol) -> SyntaxProtocol {
switch self.as(SyntaxEnum.self) {
case .token(let node):
return node
Expand Down
Loading