Skip to content

Commit 39b4c41

Browse files
authored
Merge pull request #2588 from ahoppen/ahoppen/deprecate-bytesourcerange
Deprecate `ByteSourceRange` in favor of `Range<AbsolutePosition>`
2 parents 728e2f6 + b66915f commit 39b4c41

File tree

11 files changed

+248
-141
lines changed

11 files changed

+248
-141
lines changed

Release Notes/600.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,22 +75,26 @@
7575
- Description: With the change to parse `#if canImport(MyModule, _version: 1.2.3)` as a function call instead of a dedicated syntax node, `1.2.3` natively gets parsed as a member access `3` to the `1.2` float literal. This property allows the reinterpretation of such an expression as a version tuple.
7676
- Pull request: https://github.com/apple/swift-syntax/pull/2025
7777

78-
- `SyntaxProtocol.node(at:)`
78+
- `SyntaxProtocol.node(at:)`
7979
- Description: Given a `SyntaxIdentifier`, returns the `Syntax` node with that identifier
8080
- Pull request: https://github.com/apple/swift-syntax/pull/2594
8181

8282
- `SyntaxIdentifier.IndexInTree`
8383
- Description: Uniquely identifies a syntax node within a tree. This is similar to ``SyntaxIdentifier`` but does not store the root ID of the tree. It can thus be transferred across trees that are structurally equivalent, for example two copies of the same tree that live in different processes. The only public functions on this type are `toOpaque` and `init(fromOpaque:)`, which allow serialization of the `IndexInTree`.
8484
- Pull request: https://github.com/apple/swift-syntax/pull/2594
85-
85+
8686
- `SyntaxIdentifier` conformance to `Comparable`:
8787
- Description: A `SyntaxIdentifier` compares less than another `SyntaxIdentifier` if the node at that identifier occurs first during a depth-first traversal of the tree.
8888
- Pull request: https://github.com/apple/swift-syntax/pull/2594
8989

90-
- `SyntaxIdentifier.indexInTree` and `SyntaxIdentifier.fromIndexInTree`
90+
- `SyntaxIdentifier.indexInTree` and `SyntaxIdentifier.fromIndexInTree`
9191
- Description: `SyntaxIdentifier.indexInTree` allows the retrieval of a `SyntaxIdentifier` that identifies the syntax node independent of the syntax tree. `SyntaxIdentifier.fromIndexInTree` allows the creation for a `SyntaxIdentifier` from a tree-agnostic `SyntaxIdentifier.IndexInTree` and the tree's root node.
9292
- Pull request: https://github.com/apple/swift-syntax/pull/2594
9393

94+
- `Range<AbsolutePosition>`
95+
- Description: `Range<AbsolutePosition>` gained a few convenience functions inspired from `ByteSourceRange`: `init(position:length:)`, `length`, `overlapsOrTouches`
96+
- Pull request: https://github.com/apple/swift-syntax/pull/2587
97+
9498
## API Behavior Changes
9599

96100
## Deprecations
@@ -124,6 +128,18 @@
124128
- Description: Instead of parsing `canImport` inside `#if` directives as a special expression node, parse it as a functionc call expression. This is in-line with how the `swift(>=6.0)` and `compiler(>=6.0)` directives are parsed.
125129
- Pull request: https://github.com/apple/swift-syntax/pull/2025
126130

131+
- `SyntaxClassifiedRange.offset`, `length` and `endOffset`
132+
- Description: Deprecated these properties in favor of `range`.
133+
- Pull request: https://github.com/apple/swift-syntax/pull/2587
134+
135+
- `SyntaxProtocol.totalByteRange` and `trimmedByteRange`
136+
- Description: Renamed to `range` and `trimmedRange`, both now returning a `Range<AbsolutePosition>`.
137+
- Pull request: https://github.com/apple/swift-syntax/pull/2587
138+
139+
- `ByteSourceRange` deprecated in favor of `Range<AbsolutePosition>`
140+
- Description: `ByteSourceRange` is being dropped for `Range<AbsolutePosition>`, where the latter clearly signifies that it uses UTF-8 byte positions. `Range<AbsolutePosition>` has deprecated compatibility layers to make it API-compatible with `ByteSourceRange`
141+
- Pull request: https://github.com/apple/swift-syntax/pull/2587
142+
127143
## API-Incompatible Changes
128144

129145
- `MacroDefinition` used for expanding macros:
@@ -177,6 +193,21 @@
177193
- Pull request: https://github.com/apple/swift-syntax/pull/2489
178194
- Migration steps: Stop using this module.
179195

196+
- `ByteSourceRange.length` changed from `Int` to `SourceLength`
197+
- Description: `ByteSourceRange` has been deprecated and declared as a typealias for `Range<AbsolutePosition>`. At the same time, `Range<AbsolutePosition>` gained `length: SourceLength` that provides type-system information about the kind of length (UTF-8 byte length).
198+
- Pull request: https://github.com/apple/swift-syntax/pull/2587
199+
200+
- `IncrementalEdit.replacementLength` changed from `Int` to `SourceLength`
201+
- Description: The type of `IncrementalEdit.replacementLength` has been changed from `Int` to `SourceLength` which provides type-system information about the kind of length (UTF-8 byte length).
202+
- Pull request: https://github.com/apple/swift-syntax/pull/2587
203+
204+
## API-Behavior Changes
205+
206+
- `SyntaxProtocol.classifications(in:)` and `SyntaxProtocol.classification(at:)` take positions relative to the root of the syntax tree instead of relative to the start of the node
207+
- Description: With the deprecation of `ByteSourceRange` in favor of `Range<AbsolutePosition>`, the `AbsolutePosition`s passed as the range are measured from the start of the syntax tree instead of the start of the current node.
208+
- Pull request: https://github.com/apple/swift-syntax/pull/2587
209+
- Migration steps: Pass absolute positions measured from the root of the syntax tree instead of positions relative to the current node.
210+
180211
## Template
181212

182213
- *Affected API or two word description*

Sources/SwiftIDEUtils/Syntax+Classifications.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@ public extension SyntaxProtocol {
2525
/// consecutive tokens would have the same classification then a single classified
2626
/// range is provided for all of them.
2727
var classifications: SyntaxClassifications {
28-
let fullRange = ByteSourceRange(offset: 0, length: totalLength.utf8Length)
29-
return SyntaxClassifications(_syntaxNode, in: fullRange)
28+
return SyntaxClassifications(_syntaxNode, in: self.range)
3029
}
3130

3231
/// Sequence of ``SyntaxClassifiedRange``s contained in this syntax node within
33-
/// a relative range.
32+
/// a source range.
3433
///
3534
/// The provided classified ranges may extend beyond the provided `range`.
3635
/// Active classifications (non-`none`) will extend the range to include the
@@ -41,9 +40,9 @@ public extension SyntaxProtocol {
4140
/// intersect the provided `range`.
4241
///
4342
/// - Parameters:
44-
/// - in: The relative byte range to pull ``SyntaxClassifiedRange``s from.
43+
/// - in: The range to pull ``SyntaxClassifiedRange``s from.
4544
/// - Returns: Sequence of ``SyntaxClassifiedRange``s.
46-
func classifications(in range: ByteSourceRange) -> SyntaxClassifications {
45+
func classifications(in range: Range<AbsolutePosition>) -> SyntaxClassifications {
4746
return SyntaxClassifications(_syntaxNode, in: range)
4847
}
4948

@@ -52,19 +51,20 @@ public extension SyntaxProtocol {
5251
/// - at: The relative to the node byte offset.
5352
/// - Returns: The ``SyntaxClassifiedRange`` for the offset or nil if the source text
5453
/// at the given offset is unclassified.
54+
@available(*, deprecated, message: "Use classification(at: AbsolutePosition) instead.")
5555
func classification(at offset: Int) -> SyntaxClassifiedRange? {
56-
let classifications = SyntaxClassifications(_syntaxNode, in: ByteSourceRange(offset: offset, length: 1))
57-
var iterator = classifications.makeIterator()
58-
return iterator.next()
56+
return classification(at: AbsolutePosition(utf8Offset: offset + self.position.utf8Offset))
5957
}
6058

6159
/// The ``SyntaxClassifiedRange`` for an absolute position.
6260
/// - Parameters:
6361
/// - at: The absolute position.
64-
/// - Returns: The ``SyntaxClassifiedRange`` for the position or nil if the source text
62+
/// - Returns: The ``SyntaxClassifiedRange`` for the position or `nil`` if the source text
6563
/// at the given position is unclassified.
6664
func classification(at position: AbsolutePosition) -> SyntaxClassifiedRange? {
67-
let relativeOffset = position.utf8Offset - self.position.utf8Offset
68-
return self.classification(at: relativeOffset)
65+
let range = Range(position: position, length: SourceLength(utf8Length: 1))
66+
let classifications = SyntaxClassifications(_syntaxNode, in: range)
67+
var iterator = classifications.makeIterator()
68+
return iterator.next()
6969
}
7070
}

Sources/SwiftIDEUtils/SyntaxClassifier.swift

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ extension TokenSyntax {
4444

4545
extension RawTriviaPiece {
4646
func classify(offset: Int) -> SyntaxClassifiedRange {
47-
let range = ByteSourceRange(offset: offset, length: byteLength)
47+
let range = AbsolutePosition(utf8Offset: offset)..<AbsolutePosition(utf8Offset: offset + byteLength)
4848
switch self {
4949
case .lineComment: return .init(kind: .lineComment, range: range)
5050
case .blockComment: return .init(kind: .blockComment, range: range)
@@ -63,7 +63,7 @@ fileprivate struct TokenKindAndText {
6363
offset: Int,
6464
contextualClassification: (SyntaxClassification, Bool)?
6565
) -> SyntaxClassifiedRange {
66-
let range = ByteSourceRange(offset: offset, length: text.count)
66+
let range = AbsolutePosition(utf8Offset: offset)..<AbsolutePosition(utf8Offset: offset + text.count)
6767

6868
if let contextualClassify = contextualClassification {
6969
let (classify, force) = contextualClassify
@@ -91,10 +91,15 @@ fileprivate struct TokenKindAndText {
9191
/// Represents a source range that is associated with a syntax classification.
9292
public struct SyntaxClassifiedRange: Equatable, Sendable {
9393
public var kind: SyntaxClassification
94-
public var range: ByteSourceRange
94+
public var range: Range<AbsolutePosition>
9595

96+
@available(*, deprecated, message: "Use range.lowerBound.utf8Offset instead")
9697
public var offset: Int { return range.offset }
97-
public var length: Int { return range.length }
98+
99+
@available(*, deprecated, message: "Use range.utf8Length instead")
100+
public var length: Int { return range.length.utf8Length }
101+
102+
@available(*, deprecated, message: "Use range.upperBound.utf8Offset instead")
98103
public var endOffset: Int { return range.endOffset }
99104
}
100105

@@ -110,19 +115,14 @@ private struct ClassificationVisitor {
110115
var contextualClassification: (SyntaxClassification, Bool)?
111116
}
112117

113-
/// Only tokens within this absolute range will be classified. No
114-
/// classifications will be reported for tokens out of this range.
115-
private var targetRange: ByteSourceRange
118+
/// Only tokens within this range will be classified.
119+
/// No classifications will be reported for tokens out of this range.
120+
private var targetRange: Range<AbsolutePosition>
116121

117122
var classifications: [SyntaxClassifiedRange]
118123

119-
/// Only classify tokens in `relativeClassificationRange`, where the start
120-
/// offset is relative to `node`.
121-
init(node: Syntax, relativeClassificationRange: ByteSourceRange) {
122-
let range = ByteSourceRange(
123-
offset: node.position.utf8Offset + relativeClassificationRange.offset,
124-
length: relativeClassificationRange.length
125-
)
124+
/// Only classify tokens in `range`.
125+
init(node: Syntax, range: Range<AbsolutePosition>) {
126126
self.targetRange = range
127127
self.classifications = []
128128

@@ -140,24 +140,22 @@ private struct ClassificationVisitor {
140140
}
141141

142142
private mutating func report(range: SyntaxClassifiedRange) {
143-
if range.kind == .none && range.length == 0 {
143+
if range.kind == .none && range.range.isEmpty {
144144
return
145145
}
146146

147147
// Merge consecutive classified ranges of the same kind.
148148
if let last = classifications.last,
149149
last.kind == range.kind,
150-
last.endOffset == range.offset
150+
last.range.upperBound == range.range.lowerBound
151151
{
152-
classifications[classifications.count - 1].range = ByteSourceRange(
153-
offset: last.offset,
154-
length: last.length + range.length
155-
)
152+
classifications[classifications.count - 1].range =
153+
last.range.lowerBound..<(last.range.upperBound + range.range.length)
156154
return
157155
}
158156

159-
guard range.offset <= targetRange.endOffset,
160-
range.endOffset >= targetRange.offset
157+
guard range.range.lowerBound <= targetRange.upperBound,
158+
range.range.upperBound >= targetRange.lowerBound
161159
else {
162160
return
163161
}
@@ -219,10 +217,9 @@ private struct ClassificationVisitor {
219217
let layoutNodeTextLength = child.byteLength - child.leadingTriviaByteLength - child.trailingTriviaByteLength
220218
let range = SyntaxClassifiedRange(
221219
kind: classification.classification,
222-
range: ByteSourceRange(
223-
offset: byteOffset,
224-
length: layoutNodeTextLength
225-
)
220+
range: AbsolutePosition(
221+
utf8Offset: byteOffset
222+
)..<AbsolutePosition(utf8Offset: byteOffset + layoutNodeTextLength)
226223
)
227224
report(range: range)
228225
byteOffset += layoutNodeTextLength
@@ -250,10 +247,10 @@ private struct ClassificationVisitor {
250247
}
251248

252249
private mutating func visit(_ descriptor: ClassificationVisitor.Descriptor) -> VisitResult {
253-
guard descriptor.byteOffset < targetRange.endOffset else {
250+
guard descriptor.byteOffset < targetRange.upperBound.utf8Offset else {
254251
return .break
255252
}
256-
guard descriptor.byteOffset + descriptor.node.byteLength > targetRange.offset else {
253+
guard descriptor.byteOffset + descriptor.node.byteLength > targetRange.lowerBound.utf8Offset else {
257254
return .continue
258255
}
259256
guard SyntaxTreeViewMode.sourceAccurate.shouldTraverse(node: descriptor.node) else {
@@ -273,8 +270,8 @@ public struct SyntaxClassifications: Sequence, Sendable {
273270

274271
var classifications: [SyntaxClassifiedRange]
275272

276-
public init(_ node: Syntax, in relRange: ByteSourceRange) {
277-
let visitor = ClassificationVisitor(node: node, relativeClassificationRange: relRange)
273+
public init(_ node: Syntax, in range: Range<AbsolutePosition>) {
274+
let visitor = ClassificationVisitor(node: node, range: range)
278275
self.classifications = visitor.classifications
279276
}
280277

Sources/SwiftParser/IncrementalParseTransition.swift

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extension Parser {
2323
}
2424

2525
let currentOffset = self.lexemes.offsetToStart(self.currentToken)
26-
if let node = parseLookup!.lookUp(currentOffset, kind: kind) {
26+
if let node = parseLookup!.lookUp(AbsolutePosition(utf8Offset: currentOffset), kind: kind) {
2727
self.lexemes.advance(by: node.totalLength.utf8Length, currentToken: &self.currentToken)
2828
return node
2929
}
@@ -117,16 +117,15 @@ struct IncrementalParseLookup {
117117
/// has invalidated the previous ``Syntax`` node.
118118
///
119119
/// - Parameters:
120-
/// - offset: The byte offset of the source string that is currently parsed.
120+
/// - position: The position in the source string that is currently parsed.
121121
/// - kind: The `CSyntaxKind` that the parser expects at this position.
122122
/// - Returns: A ``Syntax`` node from the previous parse invocation,
123123
/// representing the contents of this region, if it is still valid
124124
/// to re-use. `nil` otherwise.
125-
fileprivate mutating func lookUp(_ newOffset: Int, kind: SyntaxKind) -> Syntax? {
126-
guard let prevOffset = translateToPreEditOffset(newOffset) else {
125+
fileprivate mutating func lookUp(_ newPosition: AbsolutePosition, kind: SyntaxKind) -> Syntax? {
126+
guard let prevPosition = translateToPreEditPosition(newPosition) else {
127127
return nil
128128
}
129-
let prevPosition = AbsolutePosition(utf8Offset: prevOffset)
130129
let node = cursorLookup(prevPosition: prevPosition, kind: kind)
131130
if let node {
132131
reusedCallback?(node)
@@ -162,7 +161,7 @@ struct IncrementalParseLookup {
162161

163162
// Fast path check: if parser is past all the edits then any matching node
164163
// can be re-used.
165-
if !edits.edits.isEmpty && edits.edits.last!.range.endOffset < node.position.utf8Offset {
164+
if !edits.edits.isEmpty && edits.edits.last!.range.upperBound < node.position {
166165
return true
167166
}
168167

@@ -172,15 +171,12 @@ struct IncrementalParseLookup {
172171
return false
173172
}
174173

175-
let nodeAffectRange = ByteSourceRange(
176-
offset: node.position.utf8Offset,
177-
length: nodeAffectRangeLength
178-
)
174+
let nodeAffectRange = node.position..<node.position.advanced(by: nodeAffectRangeLength)
179175

180176
for edit in edits.edits {
181177
// Check if this node or the trivia of the next node has been edited. If
182178
// it has, we cannot reuse it.
183-
if edit.range.offset > nodeAffectRange.endOffset {
179+
if edit.range.lowerBound > nodeAffectRange.upperBound {
184180
// Remaining edits don't affect the node. (Edits are sorted)
185181
break
186182
}
@@ -192,19 +188,19 @@ struct IncrementalParseLookup {
192188
return true
193189
}
194190

195-
fileprivate func translateToPreEditOffset(_ postEditOffset: Int) -> Int? {
191+
fileprivate func translateToPreEditPosition(_ postEditOffset: AbsolutePosition) -> AbsolutePosition? {
196192
var offset = postEditOffset
197193
for edit in edits.edits {
198-
if edit.range.offset > offset {
194+
if edit.range.lowerBound > offset {
199195
// Remaining edits doesn't affect the position. (Edits are sorted)
200196
break
201197
}
202-
if edit.range.offset + edit.replacementLength > offset {
198+
if edit.range.lowerBound + edit.replacementLength > offset {
203199
// This is a position inserted by the edit, and thus doesn't exist in
204200
// the pre-edit version of the file.
205201
return nil
206202
}
207-
offset = offset - edit.replacementLength + edit.range.length
203+
offset = offset + edit.range.length - edit.replacementLength
208204
}
209205
return offset
210206
}
@@ -352,24 +348,32 @@ public struct ConcurrentEdits: Sendable {
352348
var editToAdd = editToAdd
353349
var editIndicesMergedWithNewEdit: [Int] = []
354350
for (index, existingEdit) in concurrentEdits.enumerated() {
355-
if existingEdit.replacementRange.intersectsOrTouches(editToAdd.range) {
351+
if existingEdit.replacementRange.overlapsOrTouches(editToAdd.range) {
356352
let intersectionLength =
357-
existingEdit.replacementRange.intersected(editToAdd.range).length
353+
existingEdit.replacementRange.clamped(to: editToAdd.range).length
358354
let replacement: [UInt8]
359355
replacement =
360-
existingEdit.replacement.prefix(max(0, editToAdd.offset - existingEdit.replacementRange.offset))
356+
existingEdit.replacement.prefix(
357+
max(0, editToAdd.range.lowerBound.utf8Offset - existingEdit.replacementRange.lowerBound.utf8Offset)
358+
)
361359
+ editToAdd.replacement
362-
+ existingEdit.replacement.suffix(max(0, existingEdit.replacementRange.endOffset - editToAdd.endOffset))
360+
+ existingEdit.replacement.suffix(
361+
max(0, existingEdit.replacementRange.upperBound.utf8Offset - editToAdd.range.upperBound.utf8Offset)
362+
)
363363
editToAdd = IncrementalEdit(
364-
offset: Swift.min(existingEdit.offset, editToAdd.offset),
365-
length: existingEdit.length + editToAdd.length - intersectionLength,
364+
range: Range(
365+
position: Swift.min(existingEdit.range.lowerBound, editToAdd.range.lowerBound),
366+
length: existingEdit.range.length + editToAdd.range.length - intersectionLength
367+
),
366368
replacement: replacement
367369
)
368370
editIndicesMergedWithNewEdit.append(index)
369-
} else if existingEdit.offset < editToAdd.endOffset {
371+
} else if existingEdit.range.lowerBound < editToAdd.range.upperBound {
370372
editToAdd = IncrementalEdit(
371-
offset: editToAdd.offset - existingEdit.replacementLength + existingEdit.length,
372-
length: editToAdd.length,
373+
range: Range(
374+
position: editToAdd.range.lowerBound + existingEdit.range.length - existingEdit.replacementLength,
375+
length: editToAdd.range.length
376+
),
373377
replacement: editToAdd.replacement
374378
)
375379
}
@@ -380,7 +384,7 @@ public struct ConcurrentEdits: Sendable {
380384
}
381385
let insertPos =
382386
concurrentEdits.firstIndex(where: { edit in
383-
editToAdd.endOffset <= edit.offset
387+
editToAdd.range.upperBound <= edit.range.lowerBound
384388
}) ?? concurrentEdits.count
385389
concurrentEdits.insert(editToAdd, at: insertPos)
386390
precondition(ConcurrentEdits.isValidConcurrentEditArray(concurrentEdits))
@@ -397,7 +401,7 @@ public struct ConcurrentEdits: Sendable {
397401
for i in 1..<edits.count {
398402
let prevEdit = edits[i - 1]
399403
let curEdit = edits[i]
400-
if curEdit.range.offset < prevEdit.range.endOffset {
404+
if curEdit.range.lowerBound < prevEdit.range.upperBound {
401405
return false
402406
}
403407
if curEdit.intersectsRange(prevEdit.range) {

0 commit comments

Comments
 (0)