Skip to content

[overlay] Fix Foundation extensions to Substring #12151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 35 additions & 24 deletions stdlib/public/SDK/Foundation/NSStringAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,6 @@ func _toNSArray<T, U : AnyObject>(_ a: [T], f: (T) -> U) -> NSArray {
return result
}

func _toNSRange(_ r: Range<String.Index>) -> NSRange {
return NSRange(
location: r.lowerBound.encodedOffset,
length: r.upperBound.encodedOffset - r.lowerBound.encodedOffset)
}

#if !DEPLOYMENT_RUNTIME_SWIFT
// We only need this for UnsafeMutablePointer, but there's not currently a way
// to write that constraint.
Expand Down Expand Up @@ -439,10 +433,26 @@ extension StringProtocol where Index == String.Index {
return self._ephemeralString._bridgeToObjectiveC()
}

// self can be a Substring so we need to subtract/add this offset when
// passing _ns to the Foundation APIs. Will be 0 if self is String.
@_inlineable
@_versioned
internal var _substringOffset: Int {
return self.startIndex.encodedOffset
}

/// Return an `Index` corresponding to the given offset in our UTF-16
/// representation.
func _index(_ utf16Index: Int) -> Index {
return Index(encodedOffset: utf16Index)
return Index(encodedOffset: utf16Index + _substringOffset)
}

@_inlineable
@_versioned
internal func _toRelativeNSRange(_ r: Range<String.Index>) -> NSRange {
return NSRange(
location: r.lowerBound.encodedOffset - _substringOffset,
length: r.upperBound.encodedOffset - r.lowerBound.encodedOffset)
}

/// Return a `Range<Index>` corresponding to the given `NSRange` of
Expand Down Expand Up @@ -603,7 +613,7 @@ extension StringProtocol where Index == String.Index {
return locale != nil ? _ns.compare(
aString,
options: mask,
range: _toNSRange(
range: _toRelativeNSRange(
range ?? startIndex..<endIndex
),
locale: locale?._bridgeToObjectiveC()
Expand All @@ -612,7 +622,7 @@ extension StringProtocol where Index == String.Index {
: range != nil ? _ns.compare(
aString,
options: mask,
range: _toNSRange(range!)
range: _toRelativeNSRange(range!)
)

: !mask.isEmpty ? _ns.compare(aString, options: mask)
Expand Down Expand Up @@ -1050,7 +1060,7 @@ extension StringProtocol where Index == String.Index {
T : StringProtocol, R : RangeExpression
>(in range: R, with replacement: T) -> String where R.Bound == Index {
return _ns.replacingCharacters(
in: _toNSRange(range.relative(to: self)),
in: _toRelativeNSRange(range.relative(to: self)),
with: replacement._ephemeralString)
}

Expand Down Expand Up @@ -1083,7 +1093,7 @@ extension StringProtocol where Index == String.Index {
of: target,
with: replacement,
options: options,
range: _toNSRange(
range: _toRelativeNSRange(
searchRange ?? startIndex..<endIndex
)
)
Expand Down Expand Up @@ -1208,7 +1218,7 @@ extension StringProtocol where Index == String.Index {
) where R.Bound == Index {
let range = range.relative(to: self)
_ns.enumerateLinguisticTags(
in: _toNSRange(range),
in: _toRelativeNSRange(range),
scheme: tagScheme._ephemeralString,
options: opts,
orthography: orthography != nil ? orthography! : nil
Expand Down Expand Up @@ -1273,7 +1283,7 @@ extension StringProtocol where Index == String.Index {
) -> Void
) where R.Bound == Index {
_ns.enumerateSubstrings(
in: _toNSRange(range.relative(to: self)), options: opts) {
in: _toRelativeNSRange(range.relative(to: self)), options: opts) {
var stop_ = false

body($0,
Expand Down Expand Up @@ -1346,7 +1356,7 @@ extension StringProtocol where Index == String.Index {
usedLength: usedBufferCount,
encoding: encoding.rawValue,
options: options,
range: _toNSRange(range.relative(to: self)),
range: _toRelativeNSRange(range.relative(to: self)),
remaining: $0)
}
}
Expand All @@ -1373,7 +1383,7 @@ extension StringProtocol where Index == String.Index {
contentsEnd in self._ns.getLineStart(
start, end: end,
contentsEnd: contentsEnd,
for: _toNSRange(range.relative(to: self)))
for: _toRelativeNSRange(range.relative(to: self)))
}
}
}
Expand Down Expand Up @@ -1401,7 +1411,7 @@ extension StringProtocol where Index == String.Index {
contentsEnd in self._ns.getParagraphStart(
start, end: end,
contentsEnd: contentsEnd,
for: _toNSRange(range.relative(to: self)))
for: _toRelativeNSRange(range.relative(to: self)))
}
}
}
Expand All @@ -1428,7 +1438,8 @@ extension StringProtocol where Index == String.Index {
public func lineRange<
R : RangeExpression
>(for aRange: R) -> Range<Index> where R.Bound == Index {
return _range(_ns.lineRange(for: _toNSRange(aRange.relative(to: self))))
return _range(_ns.lineRange(
for: _toRelativeNSRange(aRange.relative(to: self))))
}

#if !DEPLOYMENT_RUNTIME_SWIFT
Expand All @@ -1453,7 +1464,7 @@ extension StringProtocol where Index == String.Index {
var nsTokenRanges: NSArray?
let result = tokenRanges._withNilOrAddress(of: &nsTokenRanges) {
self._ns.linguisticTags(
in: _toNSRange(range.relative(to: self)),
in: _toRelativeNSRange(range.relative(to: self)),
scheme: tagScheme._ephemeralString,
options: opts,
orthography: orthography,
Expand All @@ -1477,7 +1488,7 @@ extension StringProtocol where Index == String.Index {
R : RangeExpression
>(for aRange: R) -> Range<Index> where R.Bound == Index {
return _range(
_ns.paragraphRange(for: _toNSRange(aRange.relative(to: self))))
_ns.paragraphRange(for: _toRelativeNSRange(aRange.relative(to: self))))
}
#endif

Expand All @@ -1504,7 +1515,7 @@ extension StringProtocol where Index == String.Index {
_ns.rangeOfCharacter(
from: aSet,
options: mask,
range: _toNSRange(
range: _toRelativeNSRange(
aRange ?? startIndex..<endIndex
)
)
Expand Down Expand Up @@ -1535,7 +1546,7 @@ extension StringProtocol where Index == String.Index {
// and output ranges due (if nothing else) to locale changes
return _range(
_ns.rangeOfComposedCharacterSequences(
for: _toNSRange(range.relative(to: self))))
for: _toRelativeNSRange(range.relative(to: self))))
}

// - (NSRange)rangeOfString:(NSString *)aString
Expand Down Expand Up @@ -1570,13 +1581,13 @@ extension StringProtocol where Index == String.Index {
locale != nil ? _ns.range(
of: aString,
options: mask,
range: _toNSRange(
range: _toRelativeNSRange(
searchRange ?? startIndex..<endIndex
),
locale: locale
)
: searchRange != nil ? _ns.range(
of: aString, options: mask, range: _toNSRange(searchRange!)
of: aString, options: mask, range: _toRelativeNSRange(searchRange!)
)
: !mask.isEmpty ? _ns.range(of: aString, options: mask)
: _ns.range(of: aString)
Expand Down Expand Up @@ -1687,7 +1698,7 @@ extension StringProtocol where Index == String.Index {
@available(swift, deprecated: 4.0,
message: "Please use String slicing subscript.")
public func substring(with aRange: Range<Index>) -> String {
return _ns.substring(with: _toNSRange(aRange))
return _ns.substring(with: _toRelativeNSRange(aRange))
}
}

Expand Down
146 changes: 146 additions & 0 deletions test/stdlib/NSStringAPI+Substring.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// RUN: rm -rf %t ; mkdir -p %t
// RUN: %target-build-swift %s -o %t/a.out4 -swift-version 4 && %target-run %t/a.out4
// REQUIRES: executable_test

// REQUIRES: objc_interop

//
// Tests for the NSString APIs on Substring
//

import StdlibUnittest

import Foundation


extension String {
func range(fromStart: Int, fromEnd: Int) -> Range<String.Index> {
return index(startIndex, offsetBy: fromStart) ..<
index(endIndex, offsetBy: fromEnd)
}
subscript(fromStart: Int, fromEnd: Int) -> SubSequence {
return self[range(fromStart: fromStart, fromEnd: fromEnd)]
}
}

var tests = TestSuite("NSStringAPIs/Substring")

tests.test("range(of:)/NilRange") {
let ss = "aabcdd"[1, -1]
let range = ss.range(of: "bc")
expectOptionalEqual("bc", range.map { ss[$0] })
}

tests.test("range(of:)/NonNilRange") {
let s = "aabcdd"
let ss = s[1, -1]
let searchRange = s.range(fromStart: 2, fromEnd: -2)
let range = ss.range(of: "bc", range: searchRange)
expectOptionalEqual("bc", range.map { ss[$0] })
}

tests.test("rangeOfCharacter") {
let ss = "__hello__"[2, -2]
let range = ss.rangeOfCharacter(from: CharacterSet.alphanumerics)
expectOptionalEqual("h", range.map { ss[$0] })
}

tests.test("compare(_:options:range:locale:)/NilRange") {
let needle = "hello"
let haystack = "__hello__"[2, -2]
expectEqual(.orderedSame, haystack.compare(needle))
}

tests.test("compare(_:options:range:locale:)/NonNilRange") {
let needle = "hello"
let haystack = "__hello__"
let range = haystack.range(fromStart: 2, fromEnd: -2)
expectEqual(.orderedSame, haystack[range].compare(needle, range: range))
}

tests.test("replacingCharacters(in:with:)") {
let s = "__hello, world"
let range = s.range(fromStart: 2, fromEnd: -7)
let expected = "__goodbye, world"
let replacement = "goodbye"
expectEqual(expected,
s.replacingCharacters(in: range, with: replacement))
expectEqual(expected[2, 0],
s[2, 0].replacingCharacters(in: range, with: replacement))

expectEqual(replacement,
s.replacingCharacters(in: s.startIndex..., with: replacement))
expectEqual(replacement,
s.replacingCharacters(in: ..<s.endIndex, with: replacement))
expectEqual(expected[2, 0],
s[2, 0].replacingCharacters(in: range, with: replacement[...]))
}

tests.test("replacingOccurrences(of:with:options:range:)/NilRange") {
let s = "hello"

expectEqual("he11o", s.replacingOccurrences(of: "l", with: "1"))
expectEqual("he11o", s.replacingOccurrences(of: "l"[...], with: "1"))
expectEqual("he11o", s.replacingOccurrences(of: "l", with: "1"[...]))
expectEqual("he11o", s.replacingOccurrences(of: "l"[...], with: "1"[...]))

expectEqual("he11o",
s[...].replacingOccurrences(of: "l", with: "1"))
expectEqual("he11o",
s[...].replacingOccurrences(of: "l"[...], with: "1"))
expectEqual("he11o",
s[...].replacingOccurrences(of: "l", with: "1"[...]))
expectEqual("he11o",
s[...].replacingOccurrences(of: "l"[...], with: "1"[...]))
}

tests.test("replacingOccurrences(of:with:options:range:)/NonNilRange") {
let s = "hello"
let r = s.range(fromStart: 1, fromEnd: -2)

expectEqual("he1lo",
s.replacingOccurrences(of: "l", with: "1", range: r))
expectEqual("he1lo",
s.replacingOccurrences(of: "l"[...], with: "1", range: r))
expectEqual("he1lo",
s.replacingOccurrences(of: "l", with: "1"[...], range: r))
expectEqual("he1lo",
s.replacingOccurrences(of: "l"[...], with: "1"[...], range: r))

expectEqual("he1lo",
s[...].replacingOccurrences(of: "l", with: "1", range: r))
expectEqual("he1lo",
s[...].replacingOccurrences(of: "l"[...], with: "1", range: r))
expectEqual("he1lo",
s[...].replacingOccurrences(of: "l", with: "1"[...], range: r))
expectEqual("he1lo",
s[...].replacingOccurrences(of: "l"[...], with: "1"[...], range: r))

let ss = s[1, -1]
expectEqual("e1l",
ss.replacingOccurrences(of: "l", with: "1", range: r))
expectEqual("e1l",
ss.replacingOccurrences(of: "l"[...], with: "1", range: r))
expectEqual("e1l",
ss.replacingOccurrences(of: "l", with: "1"[...], range: r))
expectEqual("e1l",
ss.replacingOccurrences(of: "l"[...], with: "1"[...], range: r))
}

tests.test("substring(with:)") {
let s = "hello, world"
let r = s.range(fromStart: 7, fromEnd: 0)
expectEqual("world", s.substring(with: r))
expectEqual("world", s[...].substring(with: r))
expectEqual("world", s[1, 0].substring(with: r))
}

tests.test("substring(with:)/SubscriptEquivalence") {
let s = "hello, world"
let r = s.range(fromStart: 7, fromEnd: 0)
expectEqual(s[r], s.substring(with: r))
expectEqual(s[...][r], s[...].substring(with: r))
expectEqual(s[1, 0][r], s[1, 0].substring(with: r))
}

runAllTests()
10 changes: 6 additions & 4 deletions test/stdlib/NSStringAPI.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-run-simple-swift -swift-version 3
// RUN: %target-run-simple-swift
// REQUIRES: executable_test

// REQUIRES: objc_interop
Expand Down Expand Up @@ -1144,9 +1144,11 @@ NSStringAPIs.test("rangeOfComposedCharacterSequences(for:)") {
for: s.index(s.startIndex, offsetBy: 8)..<s.index(s.startIndex, offsetBy: 10))])
}

func toIntRange(
_ string: String, _ maybeRange: Range<String.Index>?
) -> Range<Int>? {
func toIntRange<
S : StringProtocol
>(
_ string: S, _ maybeRange: Range<String.Index>?
) -> Range<Int>? where S.Index == String.Index, S.IndexDistance == Int {
guard let range = maybeRange else { return nil }

return
Expand Down