8000 Merge `SourceEdit` and `IncrementalEdit` · swiftlang/swift-syntax@32fdeb0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 32fdeb0

Browse files
committed
Merge SourceEdit and IncrementalEdit
1 parent 728e2f6 commit 32fdeb0

File tree

8 files changed

+190
-163
lines changed

8 files changed

+190
-163
lines changed

Sources/SwiftDiagnostics/FixIt.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ extension FixIt {
5757
var existingEdits = [SourceEdit]()
5858
for change in changes {
5959
let edit = change.edit
60-
let isOverlapping = existingEdits.contains { edit.range.overlaps($0.range) }
60+
let isOverlapping = existingEdits.contains { edit.range.intersects($0.range) }
6161
if !isOverlapping {
6262
// The edit overlaps with the previous edit. We can't apply both
6363
// without conflicts. Apply the one that's listed first and drop the

Sources/SwiftIDEUtils/FixItApplier.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,6 @@ private extension SourceEdit {
103103
return range.upperBound.utf8Offset
104104
}
105105

106-
var replacementLength: Int {
107-
return replacement.utf8.count
108-
}
109-
110106
var replacementRange: Range<Int> {
111107
return startUtf8Offset..<endUtf8Offset
112108
}

Sources/SwiftParser/IncrementalParseTransition.swift

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ struct IncrementalParseLookup {
162162

163163
// Fast path check: if parser is past all the edits then any matching node
164164
// can be re-used.
165-
if !edits.edits.isEmpty && edits.edits.last!.range.endOffset < node.position.utf8Offset {
165+
if !edits.edits.isEmpty && edits.edits.last!.range.upperBound.utf8Offset < node.position.utf8Offset {
166166
return true
167167
}
168168

@@ -172,15 +172,15 @@ struct IncrementalParseLookup {
172172
return false
173173
}
174174

175-
let nodeAffectRange = ByteSourceRange(
176-
offset: node.position.utf8Offset,
177-
length: nodeAffectRangeLength
178-
)
175+
let nodeAffectRange =
176+
AbsolutePosition(
177+
utf8Offset: node.position.utf8Offset
178+
)..<AbsolutePosition(utf8Offset: node.position.utf8Offset + nodeAffectRangeLength)
179179

180180
for edit in edits.edits {
181181
// Check if this node or the trivia of the next node has been edited. If
182182
// it has, we cannot reuse it.
183-
if edit.range.offset > nodeAffectRange.endOffset {
183+
if edit.range.lowerBound.utf8Offset > nodeAffectRange.upperBound.utf8Offset {
184184
// Remaining edits don't affect the node. (Edits are sorted)
185185
break
186186
}
@@ -195,11 +195,11 @@ struct IncrementalParseLookup {
195195
fileprivate func translateToPreEditOffset(_ postEditOffset: Int) -> Int? {
196196
var offset = postEditOffset
197197
for edit in edits.edits {
198-
if edit.range.offset > offset {
198+
if edit.range.lowerBound.utf8Offset > offset {
199199
// Remaining edits doesn't affect the position. (Edits are sorted)
200200
break
201201
}
202-
if edit.range.offset + edit.replacementLength > offset {
202+
if edit.range.lowerBound.utf8Offset + edit.replacementLength > offset {
203203
// This is a position inserted by the edit, and thus doesn't exist in
204204
// the pre-edit version of the file.
205205
return nil
@@ -303,11 +303,11 @@ public struct ConcurrentEdits: Sendable {
303303

304304
/// The raw concurrent edits. Are guaranteed to satisfy the requirements
305305
/// stated above.
306-
public let edits: [IncrementalEdit]
306+
public let edits: [SourceEdit]
307307

308308
/// Initialize this struct from edits that are already in a concurrent form
309309
/// and are guaranteed to satisfy the requirements posed above.
310-
public init(concurrent: [IncrementalEdit]) throws {
310+
public init(concurrent: [SourceEdit]) throws {
311311
if !Self.isValidConcurrentEditArray(concurrent) {
312312
throw ConcurrentEditsError.editsNotConcurrent
313313
}
@@ -323,7 +323,7 @@ public struct ConcurrentEdits: Sendable {
323323
/// - insert 'z' at offset 2
324324
/// to '012345' results in 'xyz012345'.
325325

326-
public init(fromSequential sequentialEdits: [IncrementalEdit]) {
326+
public init(fromSequential sequentialEdits: [SourceEdit]) {
327327
do {
328328
try self.init(concurrent: Self.translateSequentialEditsToConcurrentEdits(sequentialEdits))
329329
} catch {
@@ -336,7 +336,7 @@ public struct ConcurrentEdits: Sendable {
336336
/// Construct a concurrent edits struct from a single edit. For a single edit,
337337
/// there is no differentiation between being it being applied concurrently
338338
/// or sequentially.
339-
public init(_ single: IncrementalEdit) {
339+
public init(_ single: SourceEdit) {
340340
do {
341341
try self.init(concurrent: [single])
342342
} catch {
@@ -345,9 +345,9 @@ public struct ConcurrentEdits: Sendable {
345345
}
346346

347347
private static func translateSequentialEditsToConcurrentEdits(
348-
_ edits: [IncrementalEdit]
349-
) -> [IncrementalEdit] {
350-
var concurrentEdits: [IncrementalEdit] = []
348+
_ edits: [SourceEdit]
349+
) -> [SourceEdit] {
350+
var concurrentEdits: [SourceEdit] = []
351351
for editToAdd in edits {
352352
var editToAdd = editToAdd
353353
var editIndicesMergedWithNewEdit: [Int] = []
@@ -360,14 +360,14 @@ public struct ConcurrentEdits: Sendable {
360360
existingEdit.replacement.prefix(max(0, editToAdd.offset - existingEdit.replacementRange.offset))
361361
+ editToAdd.replacement
362362
+ existingEdit.replacement.suffix(max(0, existingEdit.replacementRange.endOffset - editToAdd.endOffset))
363-
editToAdd = IncrementalEdit(
363+
editToAdd = SourceEdit(
364364
offset: Swift.min(existingEdit.offset, editToAdd.offset),
365365
length: existingEdit.length + editToAdd.length - intersectionLength,
366366
replacement: replacement
367367
)
368368
editIndicesMergedWithNewEdit.append(index)
369-
} else if existingEdit.offset < editToAdd.endOffset {
370-
editToAdd = IncrementalEdit(
369+
} else if existingEdit.range.lowerBound.utf8Offset < editToAdd.range.upperBound.utf8Offset {
370+
editToAdd = SourceEdit(
371371
offset: editToAdd.offset - existingEdit.replacementLength + existingEdit.length,
372372
length: editToAdd.length,
373373
replacement: editToAdd.replacement
@@ -380,15 +380,15 @@ public struct ConcurrentEdits: Sendable {
380380
}
381381
let insertPos =
382382
concurrentEdits.firstIndex(where: { edit in
383-
editToAdd.endOffset <= edit.offset
383+
editToAdd.range.upperBound.utf8Offset <= edit.range.lowerBound.utf8Offset
384384
}) ?? concurrentEdits.count
385385
concurrentEdits.insert(editToAdd, at: insertPos)
386386
precondition(ConcurrentEdits.isValidConcurrentEditArray(concurrentEdits))
387387
}
388388
return concurrentEdits
389389
}
390390

391-
private static func isValidConcurrentEditArray(_ edits: [IncrementalEdit]) -> Bool {
391+
private static func isValidConcurrentEditArray(_ edits: [SourceEdit]) -> Bool {
392392
// Not quite sure if we should disallow creating an `IncrementalParseTransition`
393393
// object without edits but there doesn't seem to be much benefit if we do,
394394
// and there are 'lit' tests that want to test incremental re-parsing without edits.
@@ -397,7 +397,7 @@ public struct ConcurrentEdits: Sendable {
397397
for i in 1..<edits.count {
398398
let prevEdit = edits[i - 1]
399399
let curEdit = edits[i]
400-
if curEdit.range.offset < prevEdit.range.endOffset {
400+
if curEdit.range.lowerBound.utf8Offset < prevEdit.range.upperBound.utf8Offset {
401401
return false
402402
}
403403
if curEdit.intersectsRange(prevEdit.range) {
@@ -408,7 +408,7 @@ public struct ConcurrentEdits: Sendable {
408408
}
409409

410410
/// **Public for testing purposes only**
411-
public static func _isValidConcurrentEditArray(_ edits: [IncrementalEdit]) -> Bool {
411+
public static func _isValidConcurrentEditArray(_ edits: [SourceEdit]) -> Bool {
412412
return isValidConcurrentEditArray(edits)
413413
}
414414
}

Sources/SwiftSyntax/SourceEdit.swift

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -13,22 +13,74 @@
1313
/// A textual edit to the original source represented by a range and a
1414
/// replacement.
1515
public struct SourceEdit: Equatable, Sendable {
16-
/// The half-open range that this edit applies to.
16+
/// The byte range of the original source buffer that the edit applies to.
1717
public let range: Range<AbsolutePosition>
18-
/// The text to replace the original range with. Empty for a deletion.
19-
public let replacement: String
2018

21-
/// Length of the original source range that this edit applies to. Zero if
22-
/// this is an addition.
23-
public var length: SourceLength {
24-
return SourceLength(utf8Length: range.upperBound.utf8Offset - range.lowerBound.utf8Offset)
19+
/// The UTF-8 bytes that should be inserted as part of the edit
20+
public let replacementBytes: [UInt8]
21+
22+
/// A string representation of the replacement
23+
public var replacement: String { return String(decoding: replacementBytes, as: UTF8.self) }
24+
25+
/// The length of the edit replacement in UTF8 bytes.
26+
public var replacementLength: Int { replacement.utf8.count }
27+
28+
@available(*, deprecated, message: "Use range instead")
29+
public var offset: Int { return range.lowerBound.utf8Offset }
30+
31+
@available(*, deprecated, message: "Use replacementLength instead")
32+
public var length: Int { return range.upperBound.utf8Offset - range.lowerBound.utf8Offset }
33+
34+
@available(*, deprecated, message: "Use range instead")
35+
public var endOffset: Int { return range.upperBound.utf8Offset }
36+
37+
/// After the edit has been applied the range of the replacement text.
38+
@available(*, deprecated, message: "Use range instead")
39+
public var replacementRange: ByteSourceRange {
40+
return ByteSourceRange(
41+
offset: range.lowerBound.utf8Offset,
42+
length: range.upperBound.utf8Offset - range.lowerBound.utf8Offset
43+
)
44+
}
45+
46+
@available(*, deprecated, message: "Use SourceEdit(range:replacement:) instead")
47+
public init(range: ByteSourceRange, replacementLength: Int) {
48+
self.range = AbsolutePosition(utf8Offset: range.offset)..<AbsolutePosition(utf8Offset: range.endOffset)
49+
self.replacementBytes = Array(repeating: UInt8(ascii: " "), count: replacementLength)
50+
}
51+
52+
@available(*, deprecated, message: "Use SourceEdit(range:replacement:) instead")
53+
public init(offset: Int, length: Int, replacementLength: Int) {
54+
self.range = AbsolutePosition(utf8Offset: offset)..<AbsolutePosition(utf8Offset: offset + length)
55+
self.replacementBytes = Array(repeating: UInt8(ascii: " "), count: replacementLength)
2556
}
2657

2758
/// Create an edit to replace `range` in the original source with
2859
/// `replacement`.
2960
public init(range: Range<AbsolutePosition>, replacement: String) {
3061
self.range = range
31-
self.replacement = replacement
62+
self.replacementBytes = Array(replacement.utf8)
63+
}
64+
65+
@available(*, deprecated, message: "Use SourceEdit(range:replacement:) instead")
66+
public init(offset: Int, length: Int, replacement: [UInt8]) {
67+
self.range = AbsolutePosition(utf8Offset: offset)..<AbsolutePosition(utf8Offset: offset + length)
68+
self.replacementBytes = replacement
69+
}
70+
71+
@available(*, deprecated, message: "Use SourceEdit(range:replacement:) instead")
72+
public init(offset: Int, length: Int, replacement: String) {
73+
self.init(offset: offset, length: length, replacement: Array(replacement.utf8))
74+
}
75+
76+
public func intersectsOrTouchesRange(_ other: Range<AbsolutePosition>) -> Bool {
77+
return self.range.upperBound.utf8Offset >= other.lowerBound.utf8Offset
78+
&& self.range.lowerBound.utf8Offset <= other.upperBound.utf8Offset
79+
}
80+
81+
public func intersectsRange(_ other: Range<AbsolutePosition>) -> Bool {
82+
return self.range.upperBound.utf8Offset > other.lowerBound.utf8Offset
83+
&& self.range.lowerBound.utf8Offset < other.upperBound.utf8Offset
3284
}
3385

3486
/// Convenience function to create a textual addition after the given node

Sources/SwiftSyntax/Utils.swift

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -47,57 +47,42 @@ public struct ByteSourceRange: Equatable, Sendable {
4747
}
4848
}
4949

50-
public struct IncrementalEdit: Equatable, Sendable {
51-
/// The byte range of the original source buffer that the edit applies to.
52-
public let range: ByteSourceRange
50+
extension Range<AbsolutePosition> {
51+
/// The number of bytes between the range's lower bound and its upper bound
52+
public var length: SourceLength { return SourceLength(utf8Length: upperBound.utf8Offset - lowerBound.utf8Offset) }
5353

54-
/// The UTF-8 bytes that should be inserted as part of the edit
55-
public let replacement: [UInt8]
56-
57-
/// The length of the edit replacement in UTF8 bytes.
58-
public var replacementLength: Int { replacement.count }
59-
60-
public var offset: Int { return range.offset }
61-
62-
public var length: Int { return range.length }
63-
64-
public var endOffset: Int { return range.endOffset }
65-
66-
/// After the edit has been applied the range of the replacement text.
67-
public var replacementRange: ByteSourceRange {
68-
return ByteSourceRange(offset: offset, length: replacementLength)
69-
}
70-
71-
@available(*, deprecated, message: "Use IncrementalEdit(range:replacement:) instead")
72-
public init(range: ByteSourceRange, replacementLength: Int) {
73-
self.range = range
74-
self.replacement = Array(repeating: UInt8(ascii: " "), count: replacementLength)
54+
public init(position: AbsolutePosition, length: SourceLength) {
55+
self = position..<(position + length)
7556
}
7657

77-
@available(*, deprecated, message: "Use IncrementalEdit(offset:length:replacement:) instead")
78-
public init(offset: Int, length: Int, replacementLength: Int) {
79-
self.range = ByteSourceRange(offset: offset, length: length)
80-
self.replacement = Array(repeating: UInt8(ascii: " "), count: replacementLength)
58+
/// Returns `true` if the intersection between this range and `other` is non-empty or if the two ranges are directly
59+
/// adjacent to each other.
60+
public func intersectsOrTouches(_ other: Range<AbsolutePosition>) -> Bool {
61+
return self.upperBound >= other.lowerBound && self.lowerBound <= other.upperBound
8162
}
8263

83-
public init(offset: Int, length: Int, replacement: [UInt8]) {
84-
self.range = ByteSourceRange(offset: offset, length: length)
85-
self.replacement = replacement
64+
/// Returns `true` if the intersection between this range and `other` is non-empty.
65+
public func intersects(_ other: Range<AbsolutePosition>) -> Bool {
66+
return self.upperBound > other.lowerBound && self.lowerBound < other.upperBound
8667
}
8768

88-
public init(offset: Int, length: Int, replacement: String) {
89-
self.init(offset: offset, length: length, replacement: Array(replacement.utf8))
90-
}
91-
92-
public func intersectsOrTouchesRange(_ other: ByteSourceRange) -> Bool {
93-
return self.range.intersectsOrTouches(other)
94-
}
95-
96-
public func intersectsRange(_ other: ByteSourceRange) -> Bool {
97-
return self.range.intersects(other)
69+
/// Returns the range for the overlapping region between two ranges.
70+
///
71+
/// If the intersection is empty, this returns `nil`.
72+
public func intersecting(_ other: Range<AbsolutePosition>) -> Range<AbsolutePosition>? {
73+
let lowerBound = Swift.max(self.lowerBound, other.lowerBound)
74+
let upperBound = Swift.min(self.upperBound, other.upperBound)
75+
if lowerBound > upperBound {
76+
return nil
77+
} else {
78+
return lowerBound..<upperBound
79+
}
9880
}
9981
}
10082

83+
@available(*, deprecated, renamed: "SourceEdit")
84+
public typealias IncrementalEdit = SourceEdit
85+
10186
extension RawUnexpectedNodesSyntax {
10287
/// Construct a ``RawUnexpectedNodesSyntax``with the given `elements`.
10388
///

Sources/_SwiftSyntaxTestSupport/IncrementalParseTestUtils.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public func extractEditsAndSources(
176176
) -> (edits: ConcurrentEdits, originalSource: Substring, editedSource: Substring) {
177177
var editedSource = Substring()
178178
var originalSource = Substring()
179-
var concurrentEdits: [IncrementalEdit] = []
179+
var concurrentEdits: [SourceEdit] = []
180180

181181
var lastStartIndex = source.startIndex
182182
while let startIndex = source[lastStartIndex...].firstIndex(where: { $0 == "⏩️" }),
@@ -185,7 +185,7 @@ public func extractEditsAndSources(
185185
{
186186

187187
originalSource += source[lastStartIndex..<startIndex]
188-
let edit = IncrementalEdit(
188+
let edit = SourceEdit(
189189
offset: originalSource.utf8.count,
190190
length: source.utf8.distance(
191191
from: source.index(after: startIndex),
@@ -219,7 +219,7 @@ public func extractEditsAndSources(
219219
/// `concurrent` specifies whether the edits should be interpreted as being
220220
/// applied sequentially or concurrently.
221221
public func applyEdits(
222-
_ edits: [IncrementalEdit],
222+
_ edits: [SourceEdit],
223223
concurrent: Bool,
224224
to testString: String
225225
) -> String {

0 commit comments

Comments
 (0)
0