Shorthand for Offsetting startIndex and endIndex

I'd like to propose going in a different direction than coming up with an operator that fakes a prefix -. This syntax fools the eye but doesn't really express the desired semantics.

In the context of offsetting indices, 2..<-1 doesn't actually mean a negative range, because in this context we actually expect endIndex - 1 to come after (i.e., to be greater than) startIndex + 2. I'd rather we made it possible to actually express the desired semantics:

@_fixed_layout
public enum IndexOffset : Equatable {
  case start(Int)
  case end(Int)
}

extension IndexOffset {
  public init(_ source: Int) {
    self = source < 0 ? .end(source) : .start(source)
  }
}

extension IndexOffset : ExpressibleByIntegerLiteral {
  public init(integerLiteral value: Int) {
    self.init(value)
  }
}

extension IndexOffset : Comparable {
  public static func < (lhs: IndexOffset, rhs: IndexOffset) -> Bool {
    // Note how this comparison reflects our intended semantics.
    switch (lhs, rhs) {
    case (.start, .end): return true
    case let (.start(a), .start(b)): return a < b
    case (.end, .start): return false
    case let (.end(a), .end(b)): return a < b
    }
  }
}

extension Collection {
  internal func _index(_ offset: IndexOffset) -> Index {
    switch offset {
    case let .start(distance):
      return index(startIndex, offsetBy: distance)
    case let .end(distance):
      return index(endIndex, offsetBy: distance)
    }
  }

  public subscript(offset range: Range<IndexOffset>) -> SubSequence {
    return self[_index(range.lowerBound)..<_index(range.upperBound)]
  }
}

let x = [1, 2, 3, 4, 5]
x[offset: 1 ..< -1] // [2, 3, 4]

[Edited per Nevin's suggestion.]

11 Likes