Skip to content

Commit 43867bc

Browse files
committed
[Foundation] Add initializers for NSRange<-->Range (swiftlang#9709)
* Add initializers to NSRange/Range * Create Ranges unchecked
1 parent 2a975c9 commit 43867bc

File tree

2 files changed

+97
-11
lines changed

2 files changed

+97
-11
lines changed

stdlib/public/SDK/Foundation/NSRange.swift

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,59 @@ extension NSRange {
105105
//===----------------------------------------------------------------------===//
106106

107107
extension NSRange {
108-
public init(_ x: Range<Int>) {
109-
location = x.lowerBound
110-
length = x.count
111-
}
108+
public init<R: RangeExpression>(_ rangeExpression: R)
109+
where R.Bound: FixedWidthInteger, R.Bound.Stride : SignedInteger {
110+
let range = rangeExpression.relative(to: 0..<R.Bound.max)
111+
let start: Int = numericCast(range.lowerBound)
112+
let end: Int = numericCast(range.upperBound)
113+
self = NSRange(location: start, length: end - start)
114+
}
115+
116+
public init<R: RangeExpression, S: StringProtocol>(_ rangeExpression: R, in string: S)
117+
where R.Bound == String.Index, S.Index == String.Index {
118+
let range = rangeExpression.relative(to: string)
119+
let start = range.lowerBound.samePosition(in: string.utf16)
120+
let end = range.upperBound.samePosition(in: string.utf16)
121+
let location = string.utf16.distance(from: string.utf16.startIndex, to: start)
122+
let length = string.utf16.distance(from: start, to: end)
123+
self = NSRange(location: location, length: length)
124+
}
112125

113-
// FIXME(ABI)#75 (Conditional Conformance): this API should be an extension on Range.
114-
// Can't express it now because the compiler does not support conditional
115-
// extensions with type equality constraints.
116-
public func toRange() -> Range<Int>? {
117-
if location == NSNotFound { return nil }
118-
return location..<(location+length)
119-
}
126+
@available(swift, deprecated: 4, renamed: "Range.init(_:)")
127+
public func toRange() -> Range<Int>? {
128+
if location == NSNotFound { return nil }
129+
return location..<(location+length)
130+
}
131+
}
132+
133+
extension Range where Bound: BinaryInteger {
134+
public init?(_ range: NSRange) {
135+
guard range.location != NSNotFound else { return nil }
136+
self.init(uncheckedBounds: (numericCast(range.lowerBound), numericCast(range.upperBound)))
137+
}
138+
}
139+
140+
// This additional overload will mean Range.init(_:) defaults to Range<Int> when
141+
// no additional type context is provided:
142+
extension Range where Bound == Int {
143+
public init?(_ range: NSRange) {
144+
guard range.location != NSNotFound else { return nil }
145+
self.init(uncheckedBounds: (range.lowerBound, range.upperBound))
146+
}
147+
}
148+
149+
extension Range where Bound == String.Index {
150+
public init?(_ range: NSRange, in string: String) {
151+
let u = string.utf16
152+
guard range.location != NSNotFound,
153+
let start = u.index(u.startIndex, offsetBy: range.location, limitedBy: u.endIndex),
154+
let end = u.index(u.startIndex, offsetBy: range.location + range.length, limitedBy: u.endIndex),
155+
let lowerBound = String.Index(start, within: string),
156+
let upperBound = String.Index(end, within: string)
157+
else { return nil }
158+
159+
self = lowerBound..<upperBound
160+
}
120161
}
121162

122163
extension NSRange : CustomReflectable {
@@ -130,3 +171,4 @@ extension NSRange : CustomPlaygroundQuickLookable {
130171
return .range(Int64(location), Int64(length))
131172
}
132173
}
174+

test/Interpreter/SDK/Foundation_test.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,50 @@ FoundationTestSuite.test("NSRange") {
140140
expectEqual("{1, 4}", String(NSStringFromRange(nsRange)))
141141
}
142142

143+
FoundationTestSuite.test("RangeConversion") {
144+
let i: Int8 = 10
145+
let j: Int8 = 20
146+
147+
let nsrFromInt8 = NSRange(i..<j)
148+
expectEqual(nsrFromInt8, NSRange(location: 10, length: 10))
149+
150+
var r = Range(nsrFromInt8)
151+
expectNotNil(r)
152+
expectEqual(r!.lowerBound, 10)
153+
expectEqual(r!.upperBound, 20)
154+
expectType(Optional<Range<Int>>.self, &r)
155+
156+
var r8 = Range<Int8>(nsrFromInt8)
157+
expectNotNil(r8 != nil)
158+
expectEqual(r8?.lowerBound, 10)
159+
expectEqual(r8?.upperBound, 20)
160+
expectType(Optional<Range<Int8>>.self, &r8)
161+
162+
var nsrFromPartial = NSRange(..<5)
163+
expectEqual("{0, 5}", NSStringFromRange(nsrFromPartial))
164+
165+
let s = "Hello, 🌎!"
166+
let b = s.index(of: ",")!
167+
let e = s.index(of: "!")!
168+
let nsr = NSRange(b..<e, in: s)
169+
expectEqual(nsr.location, 5)
170+
expectEqual(nsr.length, 4)
171+
let rs = Range(nsr, in: s)!
172+
expectEqual(s[rs], ", 🌎")
173+
174+
let nsrTo = NSRange(..<b, in: s)
175+
expectEqual(nsrTo.location, 0)
176+
expectEqual(nsrTo.length, 5)
177+
let nsrFrom = NSRange(b..., in: s)
178+
expectEqual(nsrFrom.location,5)
179+
expectEqual(nsrFrom.length, 5)
180+
181+
// FIXME: enable once indices conform to RangeExpression
182+
// let nsrFull = NSRange(s.indices, in: s)
183+
// expectEqual(nsrFull.location, 0)
184+
// expectEqual(nsrFull.length, 10)
185+
}
186+
143187
//===----------------------------------------------------------------------===//
144188
// URLs
145189
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)