Skip to content

Commit d46ebe5

Browse files
authored
Merge pull request #16131 from harlanhaskins/absolute-position-positions-absolutely
[SwiftSyntax] Simplify AbsolutePosition offset calculation and support columns
2 parents 13a94d7 + 0f5ac8d commit d46ebe5

File tree

6 files changed

+95
-176
lines changed

6 files changed

+95
-176
lines changed

test/SwiftSyntax/AbsolutePosition.swift

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ PositionTests.test("Visitor") {
3131
let content = try SwiftLang.parse(getInput("visitor.swift"))
3232
let source = try String(contentsOf: getInput("visitor.swift"))
3333
let parsed = try SourceFileSyntax.decodeSourceFileSyntax(content)
34-
expectEqual(parsed.position.byteOffset, 0)
35-
expectEqual(parsed.eofToken.positionAfterSkippingLeadingTrivia.byteOffset,
36-
source.count)
37-
expectEqual(parsed.position.byteOffset, 0)
38-
expectEqual(parsed.byteSize, source.count)
34+
expectEqual(0, parsed.position.utf8Offset)
35+
expectEqual(source.count,
36+
parsed.eofToken.positionAfterSkippingLeadingTrivia.utf8Offset)
37+
expectEqual(0, parsed.position.utf8Offset)
38+
expectEqual(source.count, parsed.byteSize)
3939
})
4040
}
4141

@@ -44,10 +44,10 @@ PositionTests.test("Closure") {
4444
let content = try SwiftLang.parse(getInput("closure.swift"))
4545
let source = try String(contentsOf: getInput("closure.swift"))
4646
let parsed = try SourceFileSyntax.decodeSourceFileSyntax(content)
47-
expectEqual(parsed.eofToken.positionAfterSkippingLeadingTrivia.byteOffset,
48-
source.count)
49-
expectEqual(parsed.position.byteOffset, 0)
50-
expectEqual(parsed.byteSize, source.count)
47+
expectEqual(source.count,
48+
parsed.eofToken.positionAfterSkippingLeadingTrivia.utf8Offset)
49+
expectEqual(0, parsed.position.utf8Offset)
50+
expectEqual(source.count, parsed.byteSize)
5151
})
5252
}
5353

@@ -57,9 +57,9 @@ PositionTests.test("Rename") {
5757
let parsed = try SourceFileSyntax.decodeSourceFileSyntax(content)
5858
let renamed = FuncRenamer().visit(parsed) as! SourceFileSyntax
5959
let renamedSource = renamed.description
60-
expectEqual(renamed.eofToken.positionAfterSkippingLeadingTrivia.byteOffset,
61-
renamedSource.count)
62-
expectEqual(renamed.byteSize, renamedSource.count)
60+
expectEqual(renamedSource.count,
61+
renamed.eofToken.positionAfterSkippingLeadingTrivia.utf8Offset)
62+
expectEqual(renamedSource.count, renamed.byteSize)
6363
})
6464
}
6565

@@ -74,8 +74,8 @@ PositionTests.test("CurrentFile") {
7474
_ = node.positionAfterSkippingLeadingTrivia
7575
}
7676
override func visit(_ node: TokenSyntax) {
77-
expectEqual(node.position.byteOffset + node.leadingTrivia.byteSize,
78-
node.positionAfterSkippingLeadingTrivia.byteOffset)
77+
expectEqual(node.positionAfterSkippingLeadingTrivia.utf8Offset,
78+
node.position.utf8Offset + node.leadingTrivia.byteSize)
7979
}
8080
}
8181
Visitor().visit(parsed)
@@ -125,13 +125,14 @@ PositionTests.test("Trivias") {
125125
expectDoesNotThrow({
126126
let idx = 5
127127
let root = createSourceFile(idx + 1)
128-
expectEqual(root.leadingTrivia!.count, 3)
129-
expectEqual(root.trailingTrivia!.count, 0)
128+
expectEqual(3, root.leadingTrivia!.count)
129+
expectEqual(0, root.trailingTrivia!.count)
130130
let state = root.statements[idx]
131-
expectEqual(state.leadingTrivia!.count, 3)
132-
expectEqual(state.trailingTrivia!.count, 1)
133-
expectEqual(state.leadingTrivia!.byteSize + state.trailingTrivia!.byteSize
134-
+ state.byteSizeAfterTrimmingTrivia, state.byteSize)
131+
expectEqual(3, state.leadingTrivia!.count)
132+
expectEqual(1, state.trailingTrivia!.count)
133+
expectEqual(state.byteSize,
134+
state.leadingTrivia!.byteSize + state.trailingTrivia!.byteSize
135+
+ state.byteSizeAfterTrimmingTrivia)
135136
expectFalse(root.statements.isImplicit)
136137
})
137138
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//===--------------- AbsolutePosition.swift - Source Positions ------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// An absolute position in a source file as text - the absolute utf8Offset from
14+
/// the start, line, and column.
15+
public final class AbsolutePosition {
16+
public fileprivate(set) var utf8Offset: Int
17+
public fileprivate(set) var line: Int
18+
public fileprivate(set) var column: Int
19+
20+
public init(line: Int = 1, column: Int = 1, utf8Offset: Int = 0) {
21+
self.line = line
22+
self.column = column
23+
self.utf8Offset = utf8Offset
24+
}
25+
26+
internal func add(columns: Int) {
27+
self.column += columns
28+
self.utf8Offset += columns
29+
}
30+
31+
internal func add(lines: Int, size: Int) {
32+
self.line += lines * size
33+
self.column = 1
34+
self.utf8Offset += lines * size
35+
}
36+
37+
/// Use some text as a reference for adding to the absolute position,
38+
/// taking note of newlines, etc.
39+
internal func add(text: String) {
40+
for char in text {
41+
switch char {
42+
case "\n", "\r\n":
43+
line += 1
44+
column = 1
45+
default:
46+
column += 1
47+
}
48+
49+
// FIXME: This is currently very wasteful, but should be fast once the
50+
// small-string optimization lands.
51+
utf8Offset += String(char).utf8.count
52+
}
53+
}
54+
55+
internal func copy() -> AbsolutePosition {
56+
return AbsolutePosition(line: line, column: column,
57+
utf8Offset: utf8Offset)
58+
}
59+
}

tools/SwiftSyntax/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_swift_library(swiftSwiftSyntax SHARED
22
# This file should be listed the first. Module name is inferred from the
33
# filename.
44
SwiftSyntax.swift
5+
AbsolutePosition.swift
56
AtomicCache.swift
67
Diagnostic.swift
78
DiagnosticConsumer.swift

tools/SwiftSyntax/Diagnostic.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ public struct SourceLocation: Codable {
3030
public let file: String
3131

3232
public init(file: String, position: AbsolutePosition) {
33-
assert(position is UTF8Position, "must be utf8 position")
3433
self.init(line: position.line, column: position.column,
35-
offset: position.byteOffset, file: file)
34+
offset: position.utf8Offset, file: file)
3635
}
3736

3837
public init(line: Int, column: Int, offset: Int, file: String) {
@@ -88,7 +87,7 @@ public enum FixIt: Codable {
8887
let string = try container.decode(String.self, forKey: .string)
8988
let loc = try container.decode(SourceLocation.self, forKey: .location)
9089
self = .insert(loc, string)
91-
case "replace":
90+
case "replace":
9291
let string = try container.decode(String.self, forKey: .string)
9392
let range = try container.decode(SourceRange.self, forKey: .range)
9493
self = .replace(range, string)
@@ -202,7 +201,11 @@ public struct Diagnostic: Codable {
202201
/// An array of possible FixIts to apply to this diagnostic.
203202
public let fixIts: [FixIt]
204203

205-
/// A diagnostic builder that
204+
/// A diagnostic builder that exposes mutating operations for notes,
205+
/// highlights, and FixIts. When a Diagnostic is created, a builder
206+
/// will be provided in a closure where the user can conditionally
207+
/// add notes, highlights, and FixIts, that will then be wrapped
208+
/// into the immutable Diagnostic object.
206209
public struct Builder {
207210
/// An in-flight array of notes.
208211
internal var notes = [Note]()
@@ -225,7 +228,7 @@ public struct Diagnostic: Codable {
225228
/// - fixIts: Any FixIts that should be attached to this note.
226229
public mutating func note(_ message: Message,
227230
location: SourceLocation? = nil,
228-
highlights: [SourceRange] = [],
231+
highlights: [SourceRange] = [],
229232
fixIts: [FixIt] = []) {
230233
self.notes.append(Note(message: message, location: location,
231234
highlights: highlights, fixIts: fixIts))
@@ -252,7 +255,7 @@ public struct Diagnostic: Codable {
252255

253256
/// Adds a FixIt to replace the contents of the source file corresponding
254257
/// to the provided SourceRange with the provided text.
255-
public mutating
258+
public mutating
256259
func fixItReplace(_ sourceRange: SourceRange, with text: String) {
257260
fixIts.append(.replace(sourceRange, text))
258261
}

tools/SwiftSyntax/SyntaxData.swift

Lines changed: 2 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ final class SyntaxData: Equatable {
6565
}
6666

6767
var position: AbsolutePosition {
68-
return positionCache.value { return calculatePosition(UTF8Position()) }
68+
return positionCache.value { return calculatePosition(AbsolutePosition()) }
6969
}
7070

7171
var positionAfterSkippingLeadingTrivia: AbsolutePosition {
@@ -94,7 +94,7 @@ final class SyntaxData: Equatable {
9494
}
9595

9696
var byteSize: Int {
97-
return getNextSiblingPos().byteOffset - self.position.byteOffset
97+
return getNextSiblingPos().utf8Offset - self.position.utf8Offset
9898
}
9999

100100
/// Creates a SyntaxData with the provided raw syntax, pointing to the
@@ -258,81 +258,3 @@ final class SyntaxData: Equatable {
258258
return lhs === rhs
259259
}
260260
}
261-
262-
/// An absolute position in a source file as text - the absolute byteOffset from
263-
/// the start, line, and column.
264-
public class AbsolutePosition {
265-
public fileprivate(set) var byteOffset: Int
266-
public fileprivate(set) var line: Int
267-
public fileprivate(set) var column: Int
268-
269-
required public init(line: Int = 1, column: Int = 1, byteOffset: Int = 0) {
270-
self.line = line
271-
self.column = column
272-
self.byteOffset = byteOffset
273-
}
274-
275-
internal func add(text: String) {
276-
preconditionFailure("this function must be overridden")
277-
}
278-
279-
internal func copy() -> Self {
280-
return type(of: self).init(line: line, column: column, byteOffset: byteOffset)
281-
}
282-
}
283-
284-
extension AbsolutePosition {
285-
286-
/// Add some number of columns to the position.
287-
internal func add(columns: Int) {
288-
column += columns
289-
byteOffset += columns
290-
}
291-
292-
/// Add some number of newlines to the position, resetting the column.
293-
/// Size is byte size of newline char.
294-
/// '\n' and '\r' are 1, '\r\n' is 2.
295-
internal func add(lines: Int, size: Int) {
296-
line += lines
297-
column = 1
298-
byteOffset += lines * size
299-
}
300-
301-
/// Use some text as a reference for adding to the absolute position,
302-
/// taking note of newlines, etc.
303-
fileprivate func add<C: BidirectionalCollection>(text chars: C)
304-
where C.Element: UnsignedInteger {
305-
let cr: C.Element = 13
306-
let nl: C.Element = 10
307-
var idx = chars.startIndex
308-
while idx != chars.endIndex {
309-
let c = chars[idx]
310-
idx = chars.index(after: idx)
311-
switch c {
312-
case cr:
313-
if chars[idx] == nl {
314-
add(lines: 1, size: 2)
315-
idx = chars.index(after: idx)
316-
} else {
317-
add(lines: 1, size: 1)
318-
}
319-
case nl:
320-
add(lines: 1, size: 1)
321-
default:
322-
add(columns: 1)
323-
}
324-
}
325-
}
326-
}
327-
328-
class UTF8Position: AbsolutePosition {
329-
internal override func add(text: String) {
330-
add(text: text.utf8)
331-
}
332-
}
333-
334-
class UTF16Position: AbsolutePosition {
335-
internal override func add(text: String) {
336-
add(text: text.utf16)
337-
}
338-
}

tools/SwiftSyntax/Trivia.swift.gyb

Lines changed: 3 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -99,59 +99,6 @@ extension TriviaPiece: TextOutputStreamable {
9999
case let .${trivia.lower_name}(text):
100100
target.write(text)
101101
% end
102-
% end
103-
}
104-
}
105-
106-
/// Computes the information from this trivia to inform the source locations
107-
/// of the associated tokens.
108-
/// Specifically, walks through the trivia and keeps track of every newline
109-
/// to give a number of how many newlines and UTF8 characters appear in the
110-
/// trivia, along with the UTF8 offset of the last column.
111-
func characterSizes() -> (lines: Int, lastColumn: Int, utf8Length: Int) {
112-
func calculateTextSizes(_ text: String) ->
113-
(lines: Int, lastColumn: Int, utf8Length: Int) {
114-
var lines = 0
115-
var col = 0
116-
var total = 0
117-
var prevChar: UInt8? = nil
118-
// TODO: CR + LF should be regarded as one newline
119-
for char in text.utf8 {
120-
total += 1
121-
switch char {
122-
case 0x0a:
123-
if prevChar == 0x0d {
124-
/* ASCII CR LF */
125-
assert(col == 0)
126-
} else {
127-
/* ASCII newline */
128-
col = 0
129-
lines += 1
130-
}
131-
/* ASCII carriage-return */
132-
case 0x0d:
133-
col = 0
134-
lines += 1
135-
136-
default:
137-
col += 1
138-
}
139-
prevChar = char
140-
}
141-
return (lines: lines, lastColumn: col, utf8Length: total)
142-
}
143-
switch self {
144-
% for trivia in TRIVIAS:
145-
% if trivia.is_new_line:
146-
case let .${trivia.lower_name}s(n):
147-
return (lines: n, lastColumn: 0, utf8Length: n * ${trivia.characters_len()})
148-
% elif trivia.is_collection():
149-
case let .${trivia.lower_name}s(n):
150-
return (lines: 0, lastColumn: n, utf8Length: n * ${trivia.characters_len()})
151-
% else:
152-
case let .${trivia.lower_name}(text):
153-
return calculateTextSizes(text)
154-
% end
155102
% end
156103
}
157104
}
@@ -209,20 +156,6 @@ public struct Trivia: Codable {
209156
}
210157
% end
211158
% end
212-
213-
/// Computes the total sizes and offsets of all pieces in this Trivia.
214-
func characterSizes() -> (lines: Int, lastColumn: Int, utf8Length: Int) {
215-
var lines = 0
216-
var lastColumn = 0
217-
var length = 0
218-
for piece in pieces {
219-
let (ln, col, len) = piece.characterSizes()
220-
lines += ln
221-
lastColumn = col
222-
length += len
223-
}
224-
return (lines: lines, lastColumn: lastColumn, utf8Length: length)
225-
}
226159
}
227160

228161
/// Conformance for Trivia to the Collection protocol.
@@ -245,11 +178,11 @@ extension Trivia: Collection {
245178

246179
/// Get the byteSize of this trivia
247180
public var byteSize: Int {
248-
let pos = UTF8Position()
181+
let pos = AbsolutePosition()
249182
for piece in pieces {
250183
piece.accumulateAbsolutePosition(pos)
251184
}
252-
return pos.byteOffset
185+
return pos.utf8Offset
253186
}
254187
}
255188

@@ -281,6 +214,6 @@ extension TriviaPiece {
281214
pos.add(text: text)
282215
% end
283216
% end
284-
}
217+
}
285218
}
286219
}

0 commit comments

Comments
 (0)