Skip to content

Commit c40c836

Browse files
committed
Make presence a token-only property
It doesn’t really make sense to talk about presence for layout nodes. Are they missing if all of their tokens are missing? What if all tokens are missing but one has trivia attached to it? To simplify the semantics, define `presence` for tokens only. This slightly changes the semantics because layout nodes without any present children are now walked in the `sourceAccurate` view, but I think that’s a good change. rdar://98818987
1 parent 1b53f6a commit c40c836

File tree

12 files changed

+73
-55
lines changed

12 files changed

+73
-55
lines changed

Sources/SwiftSyntax/Raw/RawSyntax.swift

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -158,15 +158,15 @@ extension RawSyntax {
158158
}
159159

160160
var recursiveFlags: RecursiveRawSyntaxFlags {
161-
switch rawData.payload {
162-
case .materializedToken, .parsedToken:
161+
switch view {
162+
case .token(let tokenView):
163163
var recursiveFlags: RecursiveRawSyntaxFlags = []
164-
if presence == .missing {
164+
if tokenView.presence == .missing {
165165
recursiveFlags.insert(.hasError)
166166
}
167167
return recursiveFlags
168-
case .layout(let dat):
169-
return dat.recursiveFlags
168+
case .layout(let layoutView):
169+
return layoutView.layoutData.recursiveFlags
170170
}
171171
}
172172

@@ -181,26 +181,6 @@ extension RawSyntax {
181181
}
182182
}
183183

184-
var presence: SourcePresence {
185-
if self.byteLength != 0 {
186-
// The node has source text associated with it. It's present.
187-
return .present
188-
}
189-
if self.isCollection || self.isUnknown {
190-
// We always consider collections 'present' because they can just be empty.
191-
return .present
192-
}
193-
if isToken && (self.tokenView!.rawKind == .eof || self.tokenView!.rawKind == .stringSegment) {
194-
// The end of file token never has source code associated with it but we
195-
// still consider it valid.
196-
// String segments can be empty if they occur in an empty string literal or in between two interpolation segments.
197-
return .present
198-
}
199-
200-
// If none of the above apply, the node is missing.
201-
return .missing
202-
}
203-
204184
/// The "width" of the node.
205185
///
206186
/// Sum of text byte lengths of all descendant token nodes.

Sources/SwiftSyntax/Raw/RawSyntaxLayoutView.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ struct RawSyntaxLayoutView {
3737
}
3838
}
3939

40+
var layoutData: RawSyntaxData.Layout {
41+
switch raw.rawData.payload {
42+
case .parsedToken(_),
43+
.materializedToken(_):
44+
preconditionFailure("RawSyntax must be a layout")
45+
case .layout(let dat):
46+
return dat
47+
}
48+
}
49+
4050
/// Creates a new node of the same kind but with children replaced by `elements`.
4151
func replacingLayout<C: Collection>(
4252
with elements: C,
@@ -140,13 +150,7 @@ struct RawSyntaxLayoutView {
140150

141151
/// Child nodes.
142152
var children: RawSyntaxBuffer {
143-
switch raw.rawData.payload {
144-
case .parsedToken(_),
145-
.materializedToken(_):
146-
preconditionFailure("RawSyntax must be a layout")
147-
case .layout(let dat):
148-
return dat.layout
149-
}
153+
layoutData.layout
150154
}
151155

152156
/// Returns the child at the provided cursor in the layout.

Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public struct RawTokenSyntax: RawSyntaxNodeProtocol {
9595
}
9696

9797
public var presence: SourcePresence {
98-
raw.presence
98+
tokenView.presence
9999
}
100100

101101
public var isMissing: Bool {

Sources/SwiftSyntax/Raw/RawSyntaxTokenView.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,21 @@ struct RawSyntaxTokenView {
187187
preconditionFailure("Must be invoked on a token")
188188
}
189189
}
190+
191+
var presence: SourcePresence {
192+
if self.raw.byteLength != 0 {
193+
// The node has source text associated with it. It's present.
194+
return .present
195+
}
196+
if rawKind == .eof || rawKind == .stringSegment {
197+
// The end of file token never has source code associated with it but we
198+
// still consider it valid.
199+
// String segments can be empty if they occur in an empty string literal or in between two interpolation segments.
200+
return .present
201+
}
202+
203+
// If none of the above apply, the node is missing.
204+
return .missing
205+
}
206+
190207
}

Sources/SwiftSyntax/Syntax.swift

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -146,16 +146,6 @@ public extension SyntaxProtocol {
146146
return SyntaxChildrenIndex(self.data.absoluteRaw.info)
147147
}
148148

149-
/// Whether or not this node is marked as `present`.
150-
var isPresent: Bool {
151-
return raw.presence == .present
152-
}
153-
154-
/// Whether or not this node is marked as `missing`.
155-
var isMissing: Bool {
156-
return raw.presence == .missing
157-
}
158-
159149
/// Whether or not this node is a token one.
160150
var isToken: Bool {
161151
return raw.isToken
@@ -553,7 +543,7 @@ public extension SyntaxProtocol {
553543
target.write(" [\(range.start)...\(range.end)]")
554544
}
555545

556-
if isMissing {
546+
if let tokenView = raw.tokenView, tokenView.presence == .missing {
557547
target.write(" MISSING")
558548
}
559549

Sources/SwiftSyntax/SyntaxOtherNodes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable {
9898
}
9999

100100
public var presence: SourcePresence {
101-
return raw.presence
101+
return tokenView.presence
102102
}
103103

104104
/// The text of the token as written in the source code.

Sources/SwiftSyntax/SyntaxTreeViewMode.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ public enum SyntaxTreeViewMode {
3131
func shouldTraverse(node: RawSyntax) -> Bool {
3232
switch self {
3333
case .sourceAccurate:
34-
return node.presence == .present
34+
if let tokenView = node.tokenView {
35+
return tokenView.presence == .present
36+
}
37+
return true
3538
case .fixedUp:
3639
return node.kind != .unexpectedNodes
3740
case .all:

Sources/_SwiftSyntaxTestSupport/SyntaxComparison.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,11 @@ public extension SyntaxProtocol {
119119
return .nodeType
120120
}
121121

122-
if isPresent != baseline.isPresent {
123-
return .presence
124-
}
125-
126122
if isToken {
127123
if let token = Syntax(self).as(TokenSyntax.self), let baselineToken = Syntax(baseline).as(TokenSyntax.self) {
124+
if token.presence != baselineToken.presence {
125+
return .presence
126+
}
128127
if token.tokenKind != baselineToken.tokenKind {
129128
return .token
130129
}

Tests/SwiftSyntaxParserTest/SyntaxComparisonTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ public class SyntaxComparisonTests: XCTestCase {
8888
func expectations(_ diff: TreeDifference?, file: StaticString = #filePath, line: UInt = #line) throws {
8989
let diff = try XCTUnwrap(diff, file: file, line: line)
9090
XCTAssertEqual(diff.reason, .presence)
91-
XCTAssertTrue(diff.baseline.isMissing)
92-
XCTAssertFalse(diff.node.isMissing)
91+
XCTAssertEqual(diff.baseline.as(TokenSyntax.self)?.presence, .missing)
92+
XCTAssertEqual(diff.node.as(TokenSyntax.self)?.presence, .present)
9393
}
9494

9595
let actual = Syntax(makeFunc(identifier: .identifier("f")))

Tests/SwiftSyntaxTest/SyntaxChildrenTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public class SyntaxChildrenTests: XCTestCase {
5656
)))
5757

5858
var sourceAccurateIt = node.children(viewMode: .sourceAccurate).makeIterator()
59-
XCTAssertNextIsNil(&sourceAccurateIt)
59+
try XCTAssertNext(&sourceAccurateIt) { $0.is(MissingDeclSyntax.self) }
6060

6161
var fixedUpIt = node.children(viewMode: .fixedUp).makeIterator()
6262
try XCTAssertNext(&fixedUpIt) { $0.is(MissingDeclSyntax.self) }

Tests/SwiftSyntaxTest/VisitorTests.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,35 @@ public class VisitorTests: XCTestCase {
2323
}
2424
}
2525

26-
XCTAssertFalse(MissingDeclChecker.check(node, viewMode: .sourceAccurate))
26+
XCTAssertTrue(MissingDeclChecker.check(node, viewMode: .sourceAccurate))
2727
XCTAssertTrue(MissingDeclChecker.check(node, viewMode: .fixedUp))
2828
}
2929

30+
public func testVisitMissingToken() throws {
31+
let node = ReturnStmtSyntax(
32+
returnKeyword: .returnKeyword(presence: .missing),
33+
expression: nil
34+
)
35+
36+
class MissingTokenChecker: SyntaxVisitor {
37+
var didSeeToken = false
38+
39+
override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
40+
didSeeToken = true
41+
return .visitChildren
42+
}
43+
44+
static func check<Tree: SyntaxProtocol>(_ tree: Tree, viewMode: SyntaxTreeViewMode) -> Bool {
45+
let visitor = MissingTokenChecker(viewMode: viewMode)
46+
visitor.walk(tree)
47+
return visitor.didSeeToken
48+
}
49+
}
50+
51+
XCTAssertFalse(MissingTokenChecker.check(node, viewMode: .sourceAccurate))
52+
XCTAssertTrue(MissingTokenChecker.check(node, viewMode: .fixedUp))
53+
}
54+
3055
public func testVisitUnexpected() throws {
3156
// This is just bunch of unexpected
3257
let unexpectedReturnStmt = ReturnStmtSyntax(

lit_tests/coloring.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func f(x: Int) -> Int {
232232
)
233233
"""
234234

235-
// CHECK: <str>"</str>\<anchor>(</anchor><int>1</int><anchor>)</anchor>\<anchor>(</anchor><int>1</int><anchor>)</anchor><str>"</str>
235+
// CHECK: <str>"</str>\<anchor>(</anchor><int>1</int><anchor>)</anchor><str></str>\<anchor>(</anchor><int>1</int><anchor>)</anchor><str>"</str>
236236
"\(1)\(1)"
237237
}
238238

0 commit comments

Comments
 (0)