@@ -125,7 +125,7 @@ extension String {
125
125
///
126
126
/// This function handles loading a character from a string while respecting
127
127
/// an end boundary, even if that end boundary is sub-character or sub-scalar.
128
-
128
+ ///
129
129
/// - If `pos` is at or past `end`, this function returns `nil`.
130
130
/// - If `end` is between `pos` and the next grapheme cluster boundary (i.e.,
131
131
/// `end` is before `self.index(after: pos)`, then the returned character
@@ -145,10 +145,20 @@ extension String {
145
145
func characterAndEnd( at pos: String . Index , limitedBy end: String . Index ) -> ( Character , String . Index ) ? {
146
146
// FIXME: Sink into the stdlib to avoid multiple boundary calculations
147
147
guard pos < end else { return nil }
148
- let next = index ( pos, offsetBy: 1 , limitedBy: end) ?? end
149
- // Substring will round down non-scalar aligned indices
150
- let substr = self [ pos..< next]
151
- return substr. first. map { ( $0, substr. endIndex) }
148
+ let next = index ( after: pos)
149
+ if next <= end {
150
+ return ( self [ pos] , next)
151
+ }
152
+
153
+ // `end` must be a sub-character position that is between `pos` and the
154
+ // next grapheme boundary. This is okay if `end` is on a Unicode scalar
155
+ // boundary, but if it's in the middle of a scalar's code units, there
156
+ // may not be a character to return at all after rounding down. Use
157
+ // `Substring`'s rounding to determine what we can return.
158
+ let substr = self [ pos..< end]
159
+ return substr. isEmpty
160
+ ? nil
161
+ : ( substr. first!, substr. endIndex)
152
162
}
153
163
154
164
func matchAnyNonNewline(
0 commit comments