Skip to content
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

Merge IncrementalEdit andSourceEdit #2604

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Release Notes/600.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
- Description: Types can have multiple specifiers now and the syntax tree has been modified to reflect that.
- Pull request: https://github.com/apple/swift-syntax/pull/2433

- ` CanImportExprSyntax` and `CanImportVersionInfoSyntax`
- `CanImportExprSyntax` and `CanImportVersionInfoSyntax`
- 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.
- Pull request: https://github.com/apple/swift-syntax/pull/2025

Expand All @@ -137,7 +137,7 @@
- Pull request: https://github.com/apple/swift-syntax/pull/2587

- `ByteSourceRange` deprecated in favor of `Range<AbsolutePosition>`
- 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`
- 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`
- Pull request: https://github.com/apple/swift-syntax/pull/2587

## API-Incompatible Changes
Expand Down
5 changes: 5 additions & 0 deletions Release Notes/601.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

## Deprecations

- `IncrementalEdit` deprecated in favor of `SourceEdit`
- Description: `IncrementalEdit` is being dropped for `SourceEdit`. `SourceEdit` has deprecated compatibility layers to make it API-compatible with `IncrementalEdit`
- Issue: https://github.com/apple/swift-syntax/issues/2532
- Pull request: https://github.com/apple/swift-syntax/pull/2604

## API-Incompatible Changes

- Moved `Radix` and `IntegerLiteralExprSyntax.radix` from `SwiftRefactor` to `SwiftSyntax`.
Expand Down
8 changes: 2 additions & 6 deletions Sources/SwiftIDEUtils/FixItApplier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ public enum FixItApplier {
// shift it by the current edit's difference in length.
if edit.endUtf8Offset <= remainingEdit.startUtf8Offset {
let startPosition = AbsolutePosition(
utf8Offset: remainingEdit.startUtf8Offset - edit.replacementRange.count + edit.replacementLength
utf8Offset: remainingEdit.startUtf8Offset - edit.replacementRange.count + edit.replacementLength.utf8Length
)
let endPosition = AbsolutePosition(
utf8Offset: remainingEdit.endUtf8Offset - edit.replacementRange.count + edit.replacementLength
utf8Offset: remainingEdit.endUtf8Offset - edit.replacementRange.count + edit.replacementLength.utf8Length
)
return SourceEdit(range: startPosition..<endPosition, replacement: remainingEdit.replacement)
}
Expand All @@ -103,10 +103,6 @@ private extension SourceEdit {
return range.upperBound.utf8Offset
}

var replacementLength: Int {
return replacement.utf8.count
}

var replacementRange: Range<Int> {
return startUtf8Offset..<endUtf8Offset
}
Expand Down
28 changes: 14 additions & 14 deletions Sources/SwiftParser/IncrementalParseTransition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,11 @@ public struct ConcurrentEdits: Sendable {

/// The raw concurrent edits. Are guaranteed to satisfy the requirements
/// stated above.
public let edits: [IncrementalEdit]
public let edits: [SourceEdit]

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

public init(fromSequential sequentialEdits: [IncrementalEdit]) {
public init(fromSequential sequentialEdits: [SourceEdit]) {
do {
try self.init(concurrent: Self.translateSequentialEditsToConcurrentEdits(sequentialEdits))
} catch {
Expand All @@ -332,7 +332,7 @@ public struct ConcurrentEdits: Sendable {
/// Construct a concurrent edits struct from a single edit. For a single edit,
/// there is no differentiation between being it being applied concurrently
/// or sequentially.
public init(_ single: IncrementalEdit) {
public init(_ single: SourceEdit) {
do {
try self.init(concurrent: [single])
} catch {
Expand All @@ -341,9 +341,9 @@ public struct ConcurrentEdits: Sendable {
}

private static func translateSequentialEditsToConcurrentEdits(
_ edits: [IncrementalEdit]
) -> [IncrementalEdit] {
var concurrentEdits: [IncrementalEdit] = []
_ edits: [SourceEdit]
) -> [SourceEdit] {
var concurrentEdits: [SourceEdit] = []
for editToAdd in edits {
var editToAdd = editToAdd
var editIndicesMergedWithNewEdit: [Int] = []
Expand All @@ -353,14 +353,14 @@ public struct ConcurrentEdits: Sendable {
existingEdit.replacementRange.clamped(to: editToAdd.range).length
let replacement: [UInt8]
replacement =
existingEdit.replacement.prefix(
existingEdit.replacementBytes.prefix(
max(0, editToAdd.range.lowerBound.utf8Offset - existingEdit.replacementRange.lowerBound.utf8Offset)
)
+ editToAdd.replacement
+ existingEdit.replacement.suffix(
+ editToAdd.replacementBytes
+ existingEdit.replacementBytes.suffix(
max(0, existingEdit.replacementRange.upperBound.utf8Offset - editToAdd.range.upperBound.utf8Offset)
)
editToAdd = IncrementalEdit(
editToAdd = SourceEdit(
range: Range(
position: Swift.min(existingEdit.range.lowerBound, editToAdd.range.lowerBound),
length: existingEdit.range.length + editToAdd.range.length - intersectionLength
Expand All @@ -369,7 +369,7 @@ public struct ConcurrentEdits: Sendable {
)
editIndicesMergedWithNewEdit.append(index)
} else if existingEdit.range.lowerBound < editToAdd.range.upperBound {
editToAdd = IncrementalEdit(
editToAdd = SourceEdit(
range: Range(
position: editToAdd.range.lowerBound + existingEdit.range.length - existingEdit.replacementLength,
length: editToAdd.range.length
Expand All @@ -392,7 +392,7 @@ public struct ConcurrentEdits: Sendable {
return concurrentEdits
}

private static func isValidConcurrentEditArray(_ edits: [IncrementalEdit]) -> Bool {
private static func isValidConcurrentEditArray(_ edits: [SourceEdit]) -> Bool {
// Not quite sure if we should disallow creating an `IncrementalParseTransition`
// object without edits but there doesn't seem to be much benefit if we do,
// and there are 'lit' tests that want to test incremental re-parsing without edits.
Expand All @@ -412,7 +412,7 @@ public struct ConcurrentEdits: Sendable {
}

/// **Public for testing purposes only**
public static func _isValidConcurrentEditArray(_ edits: [IncrementalEdit]) -> Bool {
public static func _isValidConcurrentEditArray(_ edits: [SourceEdit]) -> Bool {
return isValidConcurrentEditArray(edits)
}
}
Expand Down
71 changes: 62 additions & 9 deletions Sources/SwiftSyntax/SourceEdit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
Expand All @@ -13,22 +13,75 @@
/// A textual edit to the original source represented by a range and a
/// replacement.
public struct SourceEdit: Equatable, Sendable {
/// The half-open range that this edit applies to.
/// The byte range of the original source buffer that the edit applies to.
public let range: Range<AbsolutePosition>
/// The text to replace the original range with. Empty for a deletion.
public let replacement: String

/// Length of the original source range that this edit applies to. Zero if
/// this is an addition.
public var length: SourceLength {
return SourceLength(utf8Length: range.upperBound.utf8Offset - range.lowerBound.utf8Offset)
/// The UTF-8 bytes that should be inserted as part of the edit
public let replacementBytes: [UInt8]

/// A string representation of the replacement
public var replacement: String { return String(decoding: replacementBytes, as: UTF8.self) }

/// The length of the edit replacement in UTF8 bytes.
public var replacementLength: SourceLength { return SourceLength(utf8Length: replacementBytes.count) }

@available(*, deprecated, renamed: "range.lowerBound.utf8Offset")
public var offset: Int { return range.offset }

@available(*, deprecated, renamed: "replacementLength")
public var length: Int { return range.length.utf8Length }

@available(*, deprecated, renamed: "range.upperBound.utf8Offset")
public var endOffset: Int { return range.endOffset }

/// After the edit has been applied the range of the replacement text.
public var replacementRange: Range<AbsolutePosition> {
return Range(position: range.lowerBound, length: replacementLength)
}

/// Create an edit to replace `range` in the original source with
/// `replacement`.
public init(range: Range<AbsolutePosition>, replacement: String) {
self.range = range
self.replacement = replacement
self.replacementBytes = Array(replacement.utf8)
}

public init(range: Range<AbsolutePosition>, replacement: [UInt8]) {
self.range = range
self.replacementBytes = replacement
}

@available(*, deprecated, message: "Use SourceEdit(range:replacement:) instead")
public init(range: ByteSourceRange, replacementLength: Int) {
self.range = AbsolutePosition(utf8Offset: range.offset)..<AbsolutePosition(utf8Offset: range.endOffset)
self.replacementBytes = Array(repeating: UInt8(ascii: " "), count: replacementLength)
}

@available(*, deprecated, message: "Use SourceEdit(range:replacement:) instead")
public init(offset: Int, length: Int, replacementLength: Int) {
self.range = AbsolutePosition(utf8Offset: offset)..<AbsolutePosition(utf8Offset: offset + length)
self.replacementBytes = Array(repeating: UInt8(ascii: " "), count: replacementLength)
}

@available(*, deprecated, message: "Use SourceEdit(range:replacement:) instead")
public init(offset: Int, length: Int, replacement: [UInt8]) {
self.range = AbsolutePosition(utf8Offset: offset)..<AbsolutePosition(utf8Offset: offset + length)
self.replacementBytes = replacement
}

@available(*, deprecated, message: "Use SourceEdit(range:replacement:) instead")
public init(offset: Int, length: Int, replacement: String) {
self.init(offset: offset, length: length, replacement: Array(replacement.utf8))
}

public func intersectsOrTouchesRange(_ other: Range<AbsolutePosition>) -> Bool {
return self.range.upperBound.utf8Offset >= other.lowerBound.utf8Offset
&& self.range.lowerBound.utf8Offset <= other.upperBound.utf8Offset
}

public func intersectsRange(_ other: Range<AbsolutePosition>) -> Bool {
return self.range.upperBound.utf8Offset > other.lowerBound.utf8Offset
&& self.range.lowerBound.utf8Offset < other.upperBound.utf8Offset
}

/// Convenience function to create a textual addition after the given node
Expand Down
57 changes: 3 additions & 54 deletions Sources/SwiftSyntax/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
//
//===----------------------------------------------------------------------===//

@available(*, deprecated, renamed: "SourceEdit")
public typealias IncrementalEdit = SourceEdit

@available(*, deprecated, message: "Use Range<AbsolutePosition> instead")
public typealias ByteSourceRange = Range<AbsolutePosition>

Expand Down Expand Up @@ -77,60 +80,6 @@ extension Range<AbsolutePosition> {
}
}

public struct IncrementalEdit: Equatable, Sendable {
/// The byte range of the original source buffer that the edit applies to.
public let range: Range<AbsolutePosition>

/// The UTF-8 bytes that should be inserted as part of the edit
public let replacement: [UInt8]

/// The length of the edit replacement in UTF-8 bytes.
public var replacementLength: SourceLength { SourceLength(utf8Length: replacement.count) }

@available(*, deprecated, message: "Use range.lowerBound.utf8Offset instead")
public var offset: Int { return range.offset }

@available(*, deprecated, message: "Use range.utf8Length instead")
public var length: Int { return range.length.utf8Length }

@available(*, deprecated, message: "Use range.upperBound.utf8Offset instead")
public var endOffset: Int { return range.endOffset }

/// After the edit has been applied the range of the replacement text.
public var replacementRange: Range<AbsolutePosition> {
return Range(position: range.lowerBound, length: replacementLength)
}

@available(*, deprecated, message: "Use IncrementalEdit(range:replacement:) instead")
public init(range: ByteSourceRange, replacementLength: Int) {
self.range = range
self.replacement = Array(repeating: UInt8(ascii: " "), count: replacementLength)
}

@available(*, deprecated, message: "Use IncrementalEdit(range:replacement:) instead")
public init(offset: Int, length: Int, replacementLength: Int) {
self.range = ByteSourceRange(offset: offset, length: length)
self.replacement = Array(repeating: UInt8(ascii: " "), count: replacementLength)
}

public init(range: Range<AbsolutePosition>, replacement: [UInt8]) {
self.range = range
self.replacement = replacement
}

public init(range: Range<AbsolutePosition>, replacement: String) {
self.init(range: range, replacement: Array(replacement.utf8))
}

public func intersectsOrTouchesRange(_ other: Range<AbsolutePosition>) -> Bool {
return self.range.overlapsOrTouches(other)
}

public func intersectsRange(_ other: Range<AbsolutePosition>) -> Bool {
return self.range.overlaps(other)
}
}

extension RawUnexpectedNodesSyntax {
/// Construct a ``RawUnexpectedNodesSyntax``with the given `elements`.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public func extractEditsAndSources(
) -> (edits: ConcurrentEdits, originalSource: Substring, editedSource: Substring) {
var editedSource = Substring()
var originalSource = Substring()
var concurrentEdits: [IncrementalEdit] = []
var concurrentEdits: [SourceEdit] = []

var lastStartIndex = source.startIndex
while let startIndex = source[lastStartIndex...].firstIndex(where: { $0 == "⏩️" }),
Expand All @@ -188,7 +188,7 @@ public func extractEditsAndSources(
{

originalSource += source[lastStartIndex..<startIndex]
let edit = IncrementalEdit(
let edit = SourceEdit(
range: Range(
position: AbsolutePosition(utf8Offset: originalSource.utf8.count),
length: SourceLength(utf8Length: source.utf8.distance(from: source.index(after: startIndex), to: separateIndex))
Expand Down Expand Up @@ -221,7 +221,7 @@ public func extractEditsAndSources(
/// `concurrent` specifies whether the edits should be interpreted as being
/// applied sequentially or concurrently.
public func applyEdits(
_ edits: [IncrementalEdit],
_ edits: [SourceEdit],
concurrent: Bool,
to testString: String
) -> String {
Expand All @@ -238,7 +238,7 @@ public func applyEdits(
for edit in edits {
assert(edit.range.upperBound.utf8Offset <= bytes.count)
bytes.removeSubrange(edit.range.lowerBound.utf8Offset..<edit.range.upperBound.utf8Offset)
bytes.insert(contentsOf: edit.replacement, at: edit.range.lowerBound.utf8Offset)
bytes.insert(contentsOf: edit.replacementBytes, at: edit.range.lowerBound.utf8Offset)
}
return String(bytes: bytes, encoding: .utf8)!
}