Skip to content

Commit 6e3139b

Browse files
committed
Incorporate feedback from @lorentey and @moiseev
1 parent ed7cabb commit 6e3139b

File tree

3 files changed

+88
-74
lines changed

3 files changed

+88
-74
lines changed

stdlib/public/core/CollectionDifference.swift

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public struct CollectionDifference<ChangeElement> {
2020
/// each `remove` refers to the offset of its `element` in the original
2121
/// state. Non-`nil` values of `associatedWith` refer to the offset of the
2222
/// complementary change.
23+
@_frozen
2324
public enum Change {
2425
case insert(offset: Int, element: ChangeElement, associatedWith: Int?)
2526
case remove(offset: Int, element: ChangeElement, associatedWith: Int?)
@@ -87,22 +88,22 @@ public struct CollectionDifference<ChangeElement> {
8788
var insertOffset = Set<Int>()
8889
var removeOffset = Set<Int>()
8990

90-
for c in changes {
91-
let offset = c._offset
91+
for change in changes {
92+
let offset = change._offset
9293
if offset < 0 { return false }
9394

94-
switch c {
95+
switch change {
9596
case .remove(_, _, _):
9697
if removeOffset.contains(offset) { return false }
9798
removeOffset.insert(offset)
9899
case .insert(_, _, _):
99100
if insertOffset.contains(offset) { return false }
100101
insertOffset.insert(offset)
101-
}
102+
}
102103

103-
if let assoc = c._associatedOffset {
104+
if let assoc = change._associatedOffset {
104105
if assoc < 0 { return false }
105-
switch c {
106+
switch change {
106107
case .remove(_, _, _):
107108
if removeOffsetToAssoc[offset] != nil { return false }
108109
removeOffsetToAssoc[offset] = assoc
@@ -137,7 +138,7 @@ public struct CollectionDifference<ChangeElement> {
137138
public init?<Changes: Collection>(
138139
_ changes: Changes
139140
) where Changes.Element == Change {
140-
if !CollectionDifference<ChangeElement>._validateChanges(changes) {
141+
guard CollectionDifference<ChangeElement>._validateChanges(changes) else {
141142
return nil
142143
}
143144

@@ -217,8 +218,10 @@ public struct CollectionDifference<ChangeElement> {
217218
extension CollectionDifference: Collection {
218219
public typealias Element = Change
219220

220-
public struct Index: Equatable, Hashable, Comparable {
221+
@_fixed_layout
222+
public struct Index {
221223
// Opaque index type is isomorphic to Int
224+
@usableFromInline
222225
internal let _offset: Int
223226

224227
internal init(_offset offset: Int) {
@@ -259,7 +262,19 @@ extension CollectionDifference: Collection {
259262
}
260263

261264
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) // FIXME(availability-5.1)
262-
extension CollectionDifference.Index { // Comparable
265+
extension CollectionDifference.Index: Equatable {
266+
@inlinable
267+
public static func == (
268+
lhs: CollectionDifference.Index,
269+
rhs: CollectionDifference.Index
270+
) -> Bool {
271+
return lhs._offset == rhs._offset
272+
}
273+
}
274+
275+
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) // FIXME(availability-5.1)
276+
extension CollectionDifference.Index: Comparable {
277+
@inlinable
263278
public static func < (
264279
lhs: CollectionDifference.Index,
265280
rhs: CollectionDifference.Index
@@ -268,6 +283,14 @@ extension CollectionDifference.Index { // Comparable
268283
}
269284
}
270285

286+
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) // FIXME(availability-5.1)
287+
extension CollectionDifference.Index: Hashable {
288+
@inlinable
289+
public func hash(into hasher: inout Hasher) {
290+
hasher.combine(_offset)
291+
}
292+
}
293+
271294
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) // FIXME(availability-5.1)
272295
extension CollectionDifference.Change: Equatable where ChangeElement: Equatable {}
273296

@@ -289,50 +312,50 @@ extension CollectionDifference where ChangeElement: Hashable {
289312
///
290313
/// - Complexity: O(*n*) where *n* is `self.count`
291314
public func inferringMoves() -> CollectionDifference<ChangeElement> {
292-
let removeDict: [ChangeElement:Int?] = {
293-
var res = [ChangeElement:Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
294-
for r in removals {
295-
let element = r._element
296-
if res[element] != .none {
297-
res[element] = .some(.none)
315+
let uniqueRemovals: [ChangeElement:Int?] = {
316+
var result = [ChangeElement:Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
317+
for removal in removals {
318+
let element = removal._element
319+
if result[element] != .none {
320+
result[element] = .some(.none)
298321
} else {
299-
res[element] = .some(r._offset)
322+
result[element] = .some(removal._offset)
300323
}
301324
}
302-
return res.filter { (_, v) -> Bool in v != .none }
325+
return result.filter { (_, v) -> Bool in v != .none }
303326
}()
304327

305-
let insertDict: [ChangeElement:Int?] = {
306-
var res = [ChangeElement:Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
307-
for i in insertions {
308-
let element = i._element
309-
if res[element] != .none {
310-
res[element] = .some(.none)
328+
let uniqueInsertions: [ChangeElement:Int?] = {
329+
var result = [ChangeElement:Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
330+
for insertion in insertions {
331+
let element = insertion._element
332+
if result[element] != .none {
333+
result[element] = .some(.none)
311334
} else {
312-
res[element] = .some(i._offset)
335+
result[element] = .some(insertion._offset)
313336
}
314337
}
315-
return res.filter { (_, v) -> Bool in v != .none }
338+
return result.filter { (_, v) -> Bool in v != .none }
316339
}()
317340

318-
return CollectionDifference(_validatedChanges: map({ (c: Change) -> Change in
319-
switch c {
320-
case .remove(offset: let o, element: let e, associatedWith: _):
321-
if removeDict[e] == nil {
322-
return c
341+
return CollectionDifference(_validatedChanges: map({ (change: Change) -> Change in
342+
switch change {
343+
case .remove(offset: let offset, element: let element, associatedWith: _):
344+
if uniqueRemovals[element] == nil {
345+
return change
323346
}
324-
if let assoc = insertDict[e] {
325-
return .remove(offset: o, element: e, associatedWith: assoc)
347+
if let assoc = uniqueInsertions[element] {
348+
return .remove(offset: offset, element: element, associatedWith: assoc)
326349
}
327-
case .insert(offset: let o, element: let e, associatedWith: _):
328-
if insertDict[e] == nil {
329-
return c
350+
case .insert(offset: let offset, element: let element, associatedWith: _):
351+
if uniqueInsertions[element] == nil {
352+
return change
330353
}
331-
if let assoc = removeDict[e] {
332-
return .insert(offset: o, element: e, associatedWith: assoc)
354+
if let assoc = uniqueRemovals[element] {
355+
return .insert(offset: offset, element: element, associatedWith: assoc)
333356
}
334357
}
335-
return c
358+
return change
336359
}))
337360
}
338361
}

stdlib/public/core/Diffing.swift

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ extension RangeReplaceableCollection {
7575
func append(
7676
into target: inout Self,
7777
contentsOf source: Self,
78-
from index: inout Self.Index, count: Int) {
78+
from index: inout Self.Index, count: Int
79+
) {
7980
let start = index
8081
source.formIndex(&index, offsetBy: count)
8182
target.append(contentsOf: source[start..<index])
@@ -96,14 +97,14 @@ extension RangeReplaceableCollection {
9697
enumeratedOriginals += origCount
9798
enumeratedInserts += 1
9899
}
99-
assert(enumeratedOriginals <= self.count)
100+
_internalInvariant(enumeratedOriginals <= self.count)
100101
}
101102
let origCount = self.count - enumeratedOriginals
102103
append(into: &result, contentsOf: self, from: &currentIndex, count: origCount)
103104

104-
assert(currentIndex == self.endIndex)
105-
assert(enumeratedOriginals + origCount == self.count)
106-
assert(result.count == self.count + enumeratedInserts - enumeratedRemoves)
105+
_internalInvariant(currentIndex == self.endIndex)
106+
_internalInvariant(enumeratedOriginals + origCount == self.count)
107+
_internalInvariant(result.count == self.count + enumeratedInserts - enumeratedRemoves)
107108
return result
108109
}
109110
}
@@ -133,8 +134,7 @@ extension BidirectionalCollection {
133134
from other: C,
134135
by areEquivalent: (Element, C.Element) -> Bool
135136
) -> CollectionDifference<Element>
136-
where C.Element == Self.Element
137-
{
137+
where C.Element == Self.Element {
138138
var rawChanges: [CollectionDifference<Element>.Change] = []
139139

140140
let source = _CountingIndexCollection(other)
@@ -198,7 +198,8 @@ extension BidirectionalCollection {
198198
fileprivate func _commonPrefix<Other: BidirectionalCollection>(
199199
with other: Other,
200200
by areEquivalent: (Element, Other.Element) -> Bool
201-
) -> (SubSequence, Other.SubSequence) where Element == Other.Element {
201+
) -> (SubSequence, Other.SubSequence)
202+
where Element == Other.Element {
202203
let (s1, s2) = (startIndex, other.startIndex)
203204
let (e1, e2) = (endIndex, other.endIndex)
204205
var (i1, i2) = (s1, s2)
@@ -319,7 +320,7 @@ extension _CollectionChanges: RandomAccessCollection {
319320
}
320321

321322
fileprivate subscript(position: Index) -> Element {
322-
precondition((startIndex..<endIndex).contains(position))
323+
_internalInvariant((startIndex..<endIndex).contains(position))
323324

324325
let current = pathStorage[position + pathStartIndex]
325326
let next = pathStorage[position + pathStartIndex + 1]
@@ -500,13 +501,13 @@ fileprivate struct _SearchState<
500501
/// diagonal `k`.
501502
fileprivate subscript(d: Int, k: Int) -> Endpoint {
502503
get {
503-
assert((-d...d).contains(k))
504-
assert((d + k) % 2 == 0)
504+
_internalInvariant((-d...d).contains(k))
505+
_internalInvariant((d + k) % 2 == 0)
505506
return endpoints[d, (d + k) / 2]
506507
}
507508
set {
508-
assert((-d...d).contains(k))
509-
assert((d + k) % 2 == 0)
509+
_internalInvariant((-d...d).contains(k))
510+
_internalInvariant((d + k) % 2 == 0)
510511
endpoints[d, (d + k) / 2] = newValue
511512
}
512513
}
@@ -582,7 +583,7 @@ extension _SearchState {
582583
// *d* is the minimal number of inserted and removed elements). If there
583584
// are no consecutive removes or inserts and every remove or insert is
584585
// sandwiched between matches, the path will need `2 + d * 2` elements.
585-
assert(d > 0, "Must be at least one difference between `a` and `b`")
586+
_internalInvariant(d > 0, "Must be at least one difference between `a` and `b`")
586587
if d == 1 {
587588
endpoints.storage.append(pathEnd)
588589
}
@@ -737,11 +738,11 @@ fileprivate struct _LowerTriangularMatrix<Element> {
737738

738739
fileprivate subscript(row: Int, column: Int) -> Element {
739740
get {
740-
assert((0...row).contains(column))
741+
_internalInvariant((0...row).contains(column))
741742
return storage[_triangularNumber(row) + column]
742743
}
743744
set {
744-
assert((0...row).contains(column))
745+
_internalInvariant((0...row).contains(column))
745746
storage[_triangularNumber(row) + column] = newValue
746747
}
747748
}

test/stdlib/Diffing.swift

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ suite.test("Basic diffing algorithm validators") {
336336

337337
for (source, target, expected, line) in expectedChanges {
338338
let actual = target.difference(from: source).inferringMoves()
339-
expectEqual(actual, CollectionDifference(expected), "failed test at line \(line)")
339+
expectEqual(CollectionDifference(expected), actual, "failed test at line \(line)")
340340
}
341341
}
342342

@@ -444,7 +444,7 @@ suite.test("Enumeration order is safe") {
444444
diff.forEach { c in
445445
enumerationOrderedChanges.append(c)
446446
}
447-
expectEqual(safelyOrderedChanges, enumerationOrderedChanges)
447+
expectEqual(enumerationOrderedChanges, safelyOrderedChanges)
448448
}
449449

450450
suite.test("Change validator rejects bad associations") {
@@ -546,15 +546,10 @@ suite.test("Move inference") {
546546
expectEqual(w, n?.inferringMoves())
547547
}
548548

549-
suite.test("Three way diff demo code") {
550-
let base = "Is\nit\ntime\nalready?"
551-
let theirs = "Hi\nthere\nis\nit\ntime\nalready?"
552-
let mine = "Is\nit\nreview\ntime\nalready?"
553-
554-
// Split the contents of the sources into lines
555-
let baseLines = base.components(separatedBy: "\n")
556-
let theirLines = theirs.components(separatedBy: "\n")
557-
let myLines = mine.components(separatedBy: "\n")
549+
suite.test("Three way merge") {
550+
let baseLines = ["Is", "it", "time", "already?"]
551+
let theirLines = ["Hi", "there", "is", "it", "time", "already?"]
552+
let myLines = ["Is", "it", "review", "time", "already?"]
558553

559554
// Create a difference from base to theirs
560555
let diff = theirLines.difference(from: baseLines)
@@ -566,8 +561,7 @@ suite.test("Three way diff demo code") {
566561
}
567562

568563
// Reassemble the result
569-
let patched = patchedLines.joined(separator: "\n")
570-
expectEqual(patched, "Hi\nthere\nis\nit\nreview\ntime\nalready?")
564+
expectEqual(patchedLines, ["Hi", "there", "is", "it", "review", "time", "already?"])
571565
// print(patched)
572566
}
573567

@@ -586,12 +580,8 @@ suite.test("Diff reversal demo code") {
586580
}
587581

588582
suite.test("Naive application by enumeration") {
589-
let base = "Is\nit\ntime\nalready?"
590-
let theirs = "Hi\nthere\nis\nit\ntime\nalready?"
591-
592-
// Split the contents of the sources into lines
593-
var arr = base.components(separatedBy: "\n")
594-
let theirLines = theirs.components(separatedBy: "\n")
583+
var arr = ["Is", "it", "time", "already?"]
584+
let theirLines = ["Hi", "there", "is", "it", "time", "already?"]
595585

596586
// Create a difference from base to theirs
597587
let diff = theirLines.difference(from: arr)
@@ -605,7 +595,7 @@ suite.test("Naive application by enumeration") {
605595
}
606596
}
607597

608-
expectEqual(arr, theirLines)
598+
expectEqual(theirLines, arr)
609599
}
610600

611601
suite.test("Fast applicator boundary conditions") {

0 commit comments

Comments
 (0)