Merge pull request #4828 from apple/stdlib-improve-range-checking-for-strideable-indices

stdlib: Improve range checking in default implementations for strideable indices
diff --git a/stdlib/public/core/Collection.swift b/stdlib/public/core/Collection.swift
index b758922..daeb5a0 100644
--- a/stdlib/public/core/Collection.swift
+++ b/stdlib/public/core/Collection.swift
@@ -117,6 +117,8 @@
   /// - Complexity: O(1).
   func _failEarlyRangeCheck(_ index: Index, bounds: Range<Index>)
 
+  func _failEarlyRangeCheck(_ index: Index, bounds: ClosedRange<Index>)
+
   /// Performs a range check in O(1), or a no-op when a range check is not
   /// implementable in O(1).
   ///
@@ -881,6 +883,16 @@
       "out of bounds: index >= endIndex")
   }
 
+  public func _failEarlyRangeCheck(_ index: Index, bounds: ClosedRange<Index>) {
+    // FIXME: swift-3-indexing-model: tests.
+    _precondition(
+      bounds.lowerBound <= index,
+      "out of bounds: index < startIndex")
+    _precondition(
+      index <= bounds.upperBound,
+      "out of bounds: index > endIndex")
+  }
+
   public func _failEarlyRangeCheck(_ range: Range<Index>, bounds: Range<Index>) {
     // FIXME: swift-3-indexing-model: tests.
     _precondition(
diff --git a/stdlib/public/core/RandomAccessCollection.swift b/stdlib/public/core/RandomAccessCollection.swift
index 7d613f6..fe21430 100644
--- a/stdlib/public/core/RandomAccessCollection.swift
+++ b/stdlib/public/core/RandomAccessCollection.swift
@@ -200,18 +200,16 @@
     return startIndex..<endIndex
   }
 
-  internal func _validityChecked(_ i: Index) -> Index {
-    _precondition(i >= startIndex && i <= endIndex, "index out of range")
-    return i
-  }
-  
   /// Returns the position immediately after the given index.
   ///
   /// - Parameter i: A valid index of the collection. `i` must be less than
   ///   `endIndex`.
   /// - Returns: The index value immediately after `i`.
   public func index(after i: Index) -> Index {
-    return _validityChecked(i.advanced(by: 1))
+    // FIXME: swift-3-indexing-model: tests for the trap.
+    _failEarlyRangeCheck(
+      i, bounds: Range(uncheckedBounds: (startIndex, endIndex)))
+    return i.advanced(by: 1)
   }
 
   /// Returns the position immediately after the given index.
@@ -220,7 +218,11 @@
   ///   `startIndex`.
   /// - Returns: The index value immediately before `i`.
   public func index(before i: Index) -> Index {
-    return _validityChecked(i.advanced(by: -1))
+    let result = i.advanced(by: -1)
+    // FIXME: swift-3-indexing-model: tests for the trap.
+    _failEarlyRangeCheck(
+      result, bounds: Range(uncheckedBounds: (startIndex, endIndex)))
+    return result
   }
 
   /// Returns an index that is the specified distance from the given index.
@@ -249,7 +251,15 @@
   ///   - If `n < 0`, `n >= self.distance(from: i, to: self.startIndex)`
   /// - Complexity: O(1)
   public func index(_ i: Index, offsetBy n: Index.Stride) -> Index {
-    return _validityChecked(i.advanced(by: n))
+    let result = i.advanced(by: n)
+    // This range check is not precise, tighter bounds exist based on `n`.
+    // Unfortunately, we would need to perform index manipulation to
+    // compute those bounds, which is probably too slow in the general
+    // case.
+    // FIXME: swift-3-indexing-model: tests for the trap.
+    _failEarlyRangeCheck(
+      result, bounds: ClosedRange(uncheckedBounds: (startIndex, endIndex)))
+    return result
   }
   
   /// Returns the distance between two indices.
@@ -262,6 +272,11 @@
   ///
   /// - Complexity: O(1)
   public func distance(from start: Index, to end: Index) -> Index.Stride {
+    // FIXME: swift-3-indexing-model: tests for traps.
+    _failEarlyRangeCheck(
+      start, bounds: ClosedRange(uncheckedBounds: (startIndex, endIndex)))
+    _failEarlyRangeCheck(
+      end, bounds: ClosedRange(uncheckedBounds: (startIndex, endIndex)))
     return start.distance(to: end)
   }
 }