Skip to content

Commit cbb7a1a

Browse files
moiseevianpartridge
authored andcommitted
Fix Foundation extensions to Substring (#1244)
When a substring gets bridged to NSString, it loses the initial offset, therefore APIs that accept or return StringIndex ranges should handle this case explicitly by adding/subtracting the substring start offset.
1 parent d217afa commit cbb7a1a

File tree

1 file changed

+35
-24
lines changed

1 file changed

+35
-24
lines changed

Foundation/NSStringAPI.swift

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,6 @@ func _toNSArray<T, U : AnyObject>(_ a: [T], f: (T) -> U) -> NSArray {
4242
return result
4343
}
4444

45-
func _toNSRange(_ r: Range<String.Index>) -> NSRange {
46-
return NSRange(
47-
location: r.lowerBound.encodedOffset,
48-
length: r.upperBound.encodedOffset - r.lowerBound.encodedOffset)
49-
}
50-
5145
#if !DEPLOYMENT_RUNTIME_SWIFT
5246
// We only need this for UnsafeMutablePointer, but there's not currently a way
5347
// to write that constraint.
@@ -439,10 +433,26 @@ extension StringProtocol where Index == String.Index {
439433
return self._ephemeralString._bridgeToObjectiveC()
440434
}
441435

436+
// self can be a Substring so we need to subtract/add this offset when
437+
// passing _ns to the Foundation APIs. Will be 0 if self is String.
438+
@_inlineable
439+
@_versioned
440+
internal var _substringOffset: Int {
441+
return self.startIndex.encodedOffset
442+
}
443+
442444
/// Return an `Index` corresponding to the given offset in our UTF-16
443445
/// representation.
444446
func _index(_ utf16Index: Int) -> Index {
445-
return Index(encodedOffset: utf16Index)
447+
return Index(encodedOffset: utf16Index + _substringOffset)
448+
}
449+
450+
@_inlineable
451+
@_versioned
452+
internal func _toRelativeNSRange(_ r: Range<String.Index>) -> NSRange {
453+
return NSRange(
454+
location: r.lowerBound.encodedOffset - _substringOffset,
455+
length: r.upperBound.encodedOffset - r.lowerBound.encodedOffset)
446456
}
447457

448458
/// Return a `Range<Index>` corresponding to the given `NSRange` of
@@ -603,7 +613,7 @@ extension StringProtocol where Index == String.Index {
603613
return locale != nil ? _ns.compare(
604614
aString,
605615
options: mask,
606-
range: _toNSRange(
616+
range: _toRelativeNSRange(
607617
range ?? startIndex..<endIndex
608618
),
609619
locale: locale?._bridgeToObjectiveC()
@@ -612,7 +622,7 @@ extension StringProtocol where Index == String.Index {
612622
: range != nil ? _ns.compare(
613623
aString,
614624
options: mask,
615-
range: _toNSRange(range!)
625+
range: _toRelativeNSRange(range!)
616626
)
617627

618628
: !mask.isEmpty ? _ns.compare(aString, options: mask)
@@ -1050,7 +1060,7 @@ extension StringProtocol where Index == String.Index {
10501060
T : StringProtocol, R : RangeExpression
10511061
>(in range: R, with replacement: T) -> String where R.Bound == Index {
10521062
return _ns.replacingCharacters(
1053-
in: _toNSRange(range.relative(to: self)),
1063+
in: _toRelativeNSRange(range.relative(to: self)),
10541064
with: replacement._ephemeralString)
10551065
}
10561066

@@ -1083,7 +1093,7 @@ extension StringProtocol where Index == String.Index {
10831093
of: target,
10841094
with: replacement,
10851095
options: options,
1086-
range: _toNSRange(
1096+
range: _toRelativeNSRange(
10871097
searchRange ?? startIndex..<endIndex
10881098
)
10891099
)
@@ -1208,7 +1218,7 @@ extension StringProtocol where Index == String.Index {
12081218
) where R.Bound == Index {
12091219
let range = range.relative(to: self)
12101220
_ns.enumerateLinguisticTags(
1211-
in: _toNSRange(range),
1221+
in: _toRelativeNSRange(range),
12121222
scheme: tagScheme._ephemeralString,
12131223
options: opts,
12141224
orthography: orthography != nil ? orthography! : nil
@@ -1273,7 +1283,7 @@ extension StringProtocol where Index == String.Index {
12731283
) -> Void
12741284
) where R.Bound == Index {
12751285
_ns.enumerateSubstrings(
1276-
in: _toNSRange(range.relative(to: self)), options: opts) {
1286+
in: _toRelativeNSRange(range.relative(to: self)), options: opts) {
12771287
var stop_ = false
12781288

12791289
body($0,
@@ -1346,7 +1356,7 @@ extension StringProtocol where Index == String.Index {
13461356
usedLength: usedBufferCount,
13471357
encoding: encoding.rawValue,
13481358
options: options,
1349-
range: _toNSRange(range.relative(to: self)),
1359+
range: _toRelativeNSRange(range.relative(to: self)),
13501360
remaining: $0)
13511361
}
13521362
}
@@ -1373,7 +1383,7 @@ extension StringProtocol where Index == String.Index {
13731383
contentsEnd in self._ns.getLineStart(
13741384
start, end: end,
13751385
contentsEnd: contentsEnd,
1376-
for: _toNSRange(range.relative(to: self)))
1386+
for: _toRelativeNSRange(range.relative(to: self)))
13771387
}
13781388
}
13791389
}
@@ -1401,7 +1411,7 @@ extension StringProtocol where Index == String.Index {
14011411
contentsEnd in self._ns.getParagraphStart(
14021412
start, end: end,
14031413
contentsEnd: contentsEnd,
1404-
for: _toNSRange(range.relative(to: self)))
1414+
for: _toRelativeNSRange(range.relative(to: self)))
14051415
}
14061416
}
14071417
}
@@ -1428,7 +1438,8 @@ extension StringProtocol where Index == String.Index {
14281438
public func lineRange<
14291439
R : RangeExpression
14301440
>(for aRange: R) -> Range<Index> where R.Bound == Index {
1431-
return _range(_ns.lineRange(for: _toNSRange(aRange.relative(to: self))))
1441+
return _range(_ns.lineRange(
1442+
for: _toRelativeNSRange(aRange.relative(to: self))))
14321443
}
14331444

14341445
#if !DEPLOYMENT_RUNTIME_SWIFT
@@ -1453,7 +1464,7 @@ extension StringProtocol where Index == String.Index {
14531464
var nsTokenRanges: NSArray?
14541465
let result = tokenRanges._withNilOrAddress(of: &nsTokenRanges) {
14551466
self._ns.linguisticTags(
1456-
in: _toNSRange(range.relative(to: self)),
1467+
in: _toRelativeNSRange(range.relative(to: self)),
14571468
scheme: tagScheme._ephemeralString,
14581469
options: opts,
14591470
orthography: orthography,
@@ -1477,7 +1488,7 @@ extension StringProtocol where Index == String.Index {
14771488
R : RangeExpression
14781489
>(for aRange: R) -> Range<Index> where R.Bound == Index {
14791490
return _range(
1480-
_ns.paragraphRange(for: _toNSRange(aRange.relative(to: self))))
1491+
_ns.paragraphRange(for: _toRelativeNSRange(aRange.relative(to: self))))
14811492
}
14821493
#endif
14831494

@@ -1504,7 +1515,7 @@ extension StringProtocol where Index == String.Index {
15041515
_ns.rangeOfCharacter(
15051516
from: aSet,
15061517
options: mask,
1507-
range: _toNSRange(
1518+
range: _toRelativeNSRange(
15081519
aRange ?? startIndex..<endIndex
15091520
)
15101521
)
@@ -1535,7 +1546,7 @@ extension StringProtocol where Index == String.Index {
15351546
// and output ranges due (if nothing else) to locale changes
15361547
return _range(
15371548
_ns.rangeOfComposedCharacterSequences(
1538-
for: _toNSRange(range.relative(to: self))))
1549+
for: _toRelativeNSRange(range.relative(to: self))))
15391550
}
15401551

15411552
// - (NSRange)rangeOfString:(NSString *)aString
@@ -1570,13 +1581,13 @@ extension StringProtocol where Index == String.Index {
15701581
locale != nil ? _ns.range(
15711582
of: aString,
15721583
options: mask,
1573-
range: _toNSRange(
1584+
range: _toRelativeNSRange(
15741585
searchRange ?? startIndex..<endIndex
15751586
),
15761587
locale: locale
15771588
)
15781589
: searchRange != nil ? _ns.range(
1579-
of: aString, options: mask, range: _toNSRange(searchRange!)
1590+
of: aString, options: mask, range: _toRelativeNSRange(searchRange!)
15801591
)
15811592
: !mask.isEmpty ? _ns.range(of: aString, options: mask)
15821593
: _ns.range(of: aString)
@@ -1687,7 +1698,7 @@ extension StringProtocol where Index == String.Index {
16871698
@available(swift, deprecated: 4.0,
16881699
message: "Please use String slicing subscript.")
16891700
public func substring(with aRange: Range<Index>) -> String {
1690-
return _ns.substring(with: _toNSRange(aRange))
1701+
return _ns.substring(with: _toRelativeNSRange(aRange))
16911702
}
16921703
}
16931704

0 commit comments

Comments
 (0)