Skip to content

[SwiftSyntax] Make Syntax nodes structs instead of classes #14122

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
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
2 changes: 1 addition & 1 deletion test/SwiftSyntax/SyntaxChildren.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ var SyntaxChildrenAPI = TestSuite("SyntaxChildrenAPI")
SyntaxChildrenAPI.test("IterateWithAllPresent") {
let returnStmt = SyntaxFactory.makeReturnStmt(
returnKeyword: SyntaxFactory.makeReturnKeyword(),
expression: SyntaxFactory.makeBlankExpr(),
expression: SyntaxFactory.makeBlankUnknownExpr(),
semicolon: SyntaxFactory.makeSemicolonToken())

var iterator = returnStmt.children.makeIterator()
Expand Down
11 changes: 6 additions & 5 deletions test/SwiftSyntax/SyntaxFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ SyntaxFactoryAPI.test("Generated") {
}
""")

let forType = SyntaxFactory.makeIdentifier("for",
let forType = SyntaxFactory.makeIdentifier("for",
leadingTrivia: .backticks(1),
trailingTrivia: [
.backticks(1), .spaces(1)
Expand All @@ -53,15 +53,16 @@ SyntaxFactoryAPI.test("Generated") {
""")

expectNotEqual(structDecl.members, renamed.members)
expectEqual(structDecl, structDecl.root)
expectEqual(structDecl, structDecl.root as? StructDeclSyntax)
expectNil(structDecl.parent)
expectNotNil(structDecl.members.parent)
expectEqual(structDecl.members.parent, structDecl)
expectEqual(structDecl.members.parent as? StructDeclSyntax, structDecl)

// Ensure that accessing children via named identifiers is exactly the
// same as accessing them as their underlying data.
expectEqual(structDecl.members, structDecl.child(at: 7))

expectEqual(structDecl.members,
structDecl.child(at: 7) as? MemberDeclBlockSyntax)

expectEqual("\(structDecl.members.rightBrace)",
"""

Expand Down
2 changes: 1 addition & 1 deletion test/SwiftSyntax/VisitorTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ VisitorTests.test("Basic") {
})
}

runAllTests()
runAllTests()
2 changes: 1 addition & 1 deletion tools/SwiftSyntax/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ add_swift_library(swiftSwiftSyntax SHARED
Syntax.swift
SyntaxData.swift
SyntaxChildren.swift
SyntaxCollection.swift
SyntaxCollections.swift.gyb
SyntaxBuilders.swift.gyb
SyntaxFactory.swift.gyb
SyntaxKind.swift.gyb
Expand Down
2 changes: 1 addition & 1 deletion tools/SwiftSyntax/SwiftSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ extension Syntax {
}
let decoder = JSONDecoder()
let raw = try decoder.decode(RawSyntax.self, from: result.stdoutData)
guard let file = Syntax.fromRaw(raw) as? SourceFileSyntax else {
guard let file = makeSyntax(raw) as? SourceFileSyntax else {
throw ParserError.invalidFile
}
return file
Expand Down
89 changes: 57 additions & 32 deletions tools/SwiftSyntax/Syntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,47 @@ import Foundation
/// 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 class Syntax: CustomStringConvertible {
public protocol Syntax:
CustomStringConvertible, TextOutputStreamable {}

internal protocol _SyntaxBase: Syntax {
/// The type of sequence containing the indices of present children.
internal typealias PresentChildIndicesSequence =
typealias PresentChildIndicesSequence =
LazyFilterSequence<CountableRange<Int>>

/// The root of the tree this node is currently in.
internal let _root: SyntaxData
var _root: SyntaxData { get } // Must be of type SyntaxData

/// The data backing this node.
/// - note: This is unowned, because the reference to the root data keeps it
/// alive. This means there is an implicit relationship -- the data
/// property must be a descendent of the root. This relationship must
/// be preserved in all circumstances where Syntax nodes are created.
internal unowned var data: SyntaxData
var _data: SyntaxData { get }

#if DEBUG
func validate() {
// This is for subclasses to override to perform structural validation.
}
func validate()
#endif
}
extension _SyntaxBase {
public func validate() {
// This is for implementers to override to perform structural validation.
}
}

/// Creates a Syntax node from the provided root and data.
internal init(root: SyntaxData, data: SyntaxData) {
self._root = root
self.data = data
#if DEBUG
validate()
#endif
extension Syntax {
var data: SyntaxData {
guard let base = self as? _SyntaxBase else {
fatalError("only first-class syntax nodes can conform to Syntax")
}
return base._data
}

var _root: SyntaxData {
guard let base = self as? _SyntaxBase else {
fatalError("only first-class syntax nodes can conform to Syntax")
}
return base._root
}

/// Access the raw syntax assuming the node is a Syntax.
Expand Down Expand Up @@ -93,7 +106,7 @@ public class Syntax: CustomStringConvertible {
/// The parent of this syntax node, or `nil` if this node is the root.
public var parent: Syntax? {
guard let parentData = data.parent else { return nil }
return Syntax.make(root: _root, data: parentData)
return makeSyntax(root: _root, data: parentData)
}

/// The index of this node in the parent's children.
Expand All @@ -103,14 +116,14 @@ public class Syntax: CustomStringConvertible {

/// The root of the tree in which this node resides.
public var root: Syntax {
return Syntax.make(root: _root, data: _root)
return makeSyntax(root: _root, data: _root)
}

/// The sequence of indices that correspond to child nodes that are not
/// missing.
///
/// This property is an implementation detail of `SyntaxChildren`.
internal var presentChildIndices: PresentChildIndicesSequence {
internal var presentChildIndices: _SyntaxBase.PresentChildIndicesSequence {
return raw.layout.indices.lazy.filter { self.raw.layout[$0].isPresent }
}

Expand All @@ -121,7 +134,7 @@ public class Syntax: CustomStringConvertible {
public func child(at index: Int) -> Syntax? {
guard raw.layout.indices.contains(index) else { return nil }
if raw.layout[index].isMissing { return nil }
return Syntax.make(root: _root, data: data.cachedChild(at: index))
return makeSyntax(root: _root, data: data.cachedChild(at: index))
}

/// A source-accurate description of this node.
Expand All @@ -130,9 +143,7 @@ public class Syntax: CustomStringConvertible {
self.write(to: &s)
return s
}
}

extension Syntax: TextOutputStreamable {

/// Prints the raw value of this node to the provided stream.
/// - Parameter stream: The stream to which to print the raw tree.
public func write<Target>(to target: inout Target)
Expand All @@ -141,21 +152,27 @@ extension Syntax: TextOutputStreamable {
}
}

extension Syntax: Hashable {
/// Determines if two nodes are equal to each other.
public static func ==(lhs: Syntax, rhs: Syntax) -> Bool {
return lhs.hashValue == rhs.hashValue
}
/// Use reference value as the hashValue for this Syntax
public var hashValue: Int {
return ObjectIdentifier(data).hashValue
}
/// Determines if two nodes are equal to each other.
public func ==(lhs: Syntax, rhs: Syntax) -> Bool {
return lhs.data === rhs.data
}

/// MARK: - Nodes

/// A Syntax node representing a single token.
public class TokenSyntax: Syntax {
public struct TokenSyntax: _SyntaxBase, Hashable {
var _root: SyntaxData
unowned var _data: SyntaxData

/// Creates a Syntax node from the provided root and data.
internal init(root: SyntaxData, data: SyntaxData) {
self._root = root
self._data = data
#if DEBUG
validate()
#endif
}

/// The text of the token as written in the source code.
public var text: String {
return tokenKind.text
Expand Down Expand Up @@ -230,4 +247,12 @@ public class TokenSyntax: Syntax {
}
return kind
}

public static func ==(lhs: TokenSyntax, rhs: TokenSyntax) -> Bool {
return lhs._data === rhs._data
}

public var hashValue: Int {
return ObjectIdentifier(_data).hashValue
}
}
2 changes: 1 addition & 1 deletion tools/SwiftSyntax/SyntaxBuilders.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ extension ${node.name} {
/// incrementally build the structure of the node.
/// - Returns: A `${node.name}` with all the fields populated in the builder
/// closure.
public convenience init(_ build: (inout ${Builder}) -> Void) {
public init(_ build: (inout ${Builder}) -> Void) {
var builder = ${Builder}()
build(&builder)
let data = builder.buildData()
Expand Down
2 changes: 1 addition & 1 deletion tools/SwiftSyntax/SyntaxChildren.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Foundation
public struct SyntaxChildren: Sequence {
public struct Iterator: IteratorProtocol {
let node: Syntax
var indexIterator: Syntax.PresentChildIndicesSequence.Iterator
var indexIterator: _SyntaxBase.PresentChildIndicesSequence.Iterator

init(node: Syntax) {
self.indexIterator = node.presentChildIndices.makeIterator()
Expand Down
150 changes: 0 additions & 150 deletions tools/SwiftSyntax/SyntaxCollection.swift

This file was deleted.

Loading