Skip to content

Commit 69f9b40

Browse files
committed
Parity: NSCoding: NSTextCheckingResult
- NSRegularExpression now supports NSSecureCoding, and serializes in a way that’s compatible with Darwin. - NSTextCheckingResult now matches the class structure it has on Darwin, and can encode and decode those classes. - Missing NSTextCheckingResult API are now present and marked with descriptive diagnostics.
1 parent 65a378d commit 69f9b40

7 files changed

+329
-40
lines changed

Foundation/NSRegularExpression.swift

+9-6
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ extension NSRegularExpression {
2727
}
2828
}
2929

30-
open class NSRegularExpression: NSObject, NSCopying, NSCoding {
30+
open class NSRegularExpression: NSObject, NSCopying, NSSecureCoding {
3131
internal var _internal: _CFRegularExpression
3232

3333
open override func copy() -> Any {
@@ -44,26 +44,29 @@ open class NSRegularExpression: NSObject, NSCopying, NSCoding {
4444
}
4545

4646
aCoder.encode(self.pattern._nsObject, forKey: "NSPattern")
47-
aCoder.encode(self.options.rawValue._bridgeToObjectiveC(), forKey: "NSOptions")
47+
aCoder.encode(Int64(self.options.rawValue), forKey: "NSOptions")
4848
}
4949

5050
public required convenience init?(coder aDecoder: NSCoder) {
5151
guard aDecoder.allowsKeyedCoding else {
5252
preconditionFailure("Unkeyed coding is unsupported.")
5353
}
5454

55-
guard let pattern = aDecoder.decodeObject(forKey: "NSPattern") as? NSString,
56-
let options = aDecoder.decodeObject(forKey: "NSOptions") as? NSNumber else {
55+
guard let pattern = aDecoder.decodeObject(of: NSString.self, forKey: "NSPattern") else {
5756
return nil
5857
}
5958

59+
let options = aDecoder.decodeInt64(forKey: "NSOptions")
60+
6061
do {
61-
try self.init(pattern: pattern._swiftObject, options: Options(rawValue: options.uintValue))
62+
try self.init(pattern: pattern._swiftObject, options: Options(rawValue: UInt(options)))
6263
} catch {
6364
return nil
6465
}
6566
}
6667

68+
open class var supportsSecureCoding: Bool { return true }
69+
6770
open override func isEqual(_ object: Any?) -> Bool {
6871
guard let other = object as? NSRegularExpression else { return false }
6972

@@ -161,7 +164,7 @@ internal func _NSRegularExpressionMatch(_ context: UnsafeMutableRawPointer?, ran
161164
let flags = NSRegularExpression.MatchingFlags(rawValue: flags)
162165
#endif
163166
let result = ranges?.withMemoryRebound(to: NSRange.self, capacity: count) { rangePtr in
164-
NSTextCheckingResult.regularExpressionCheckingResultWithRanges(rangePtr, count: count, regularExpression: matcher.regex)
167+
NSTextCheckingResult.regularExpressionCheckingResult(ranges: rangePtr, count: count, regularExpression: matcher.regex)
165168
}
166169
stop.withMemoryRebound(to: ObjCBool.self, capacity: 1, {
167170
matcher.block(result, flags, $0)

Foundation/NSTextCheckingResult.swift

+237-25
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,29 @@ extension NSTextCheckingResult {
1515
public let rawValue: UInt64
1616
public init(rawValue: UInt64) { self.rawValue = rawValue }
1717

18-
public static let RegularExpression = CheckingType(rawValue: 1 << 10) // regular expression matches
18+
public static let regularExpression = CheckingType(rawValue: 1 << 10)
1919
}
2020
}
2121

22-
open class NSTextCheckingResult: NSObject, NSCopying, NSCoding {
22+
open class NSTextCheckingResult: NSObject, NSCopying, NSSecureCoding {
2323

2424
public override init() {
2525
if type(of: self) == NSTextCheckingResult.self {
2626
NSRequiresConcreteImplementation()
2727
}
2828
}
2929

30-
open class func regularExpressionCheckingResultWithRanges(_ ranges: NSRangePointer, count: Int, regularExpression: NSRegularExpression) -> NSTextCheckingResult {
31-
return _NSRegularExpressionNSTextCheckingResultResult(ranges: ranges, count: count, regularExpression: regularExpression)
30+
open class func regularExpressionCheckingResult(ranges: NSRangePointer, count: Int, regularExpression: NSRegularExpression) -> NSTextCheckingResult {
31+
let buffer = UnsafeBufferPointer(start: ranges, count: count)
32+
let array = Array(buffer)
33+
34+
if count > 0 && count <= 3 {
35+
return NSSimpleRegularExpressionCheckingResult(rangeArray: array, regularExpression: regularExpression)
36+
} else if count > 3 && count <= 7 {
37+
return NSExtendedRegularExpressionCheckingResult(rangeArray: array, regularExpression: regularExpression)
38+
} else {
39+
return NSComplexRegularExpressionCheckingResult(rangeArray: array, regularExpression: regularExpression)
40+
}
3241
}
3342

3443
public required init?(coder aDecoder: NSCoder) {
@@ -41,6 +50,10 @@ open class NSTextCheckingResult: NSObject, NSCopying, NSCoding {
4150
NSRequiresConcreteImplementation()
4251
}
4352

53+
open class var supportsSecureCoding: Bool {
54+
NSRequiresConcreteImplementation()
55+
}
56+
4457
open override func copy() -> Any {
4558
return copy(with: nil)
4659
}
@@ -58,44 +71,102 @@ open class NSTextCheckingResult: NSObject, NSCopying, NSCoding {
5871
open func range(withName: String) -> NSRange { NSRequiresConcreteImplementation() }
5972
open var regularExpression: NSRegularExpression? { return nil }
6073
open var numberOfRanges: Int { return 1 }
74+
75+
internal func encodeRange(with coder: NSCoder) {
76+
guard coder.allowsKeyedCoding else {
77+
fatalError("Encoding this class requires keyed coding")
78+
}
79+
80+
coder.encode(range.location, forKey: "NSRangeLocation")
81+
coder.encode(range.length, forKey: "NSRangeLength")
82+
}
83+
84+
internal func decodeRange(from coder: NSCoder) -> NSRange {
85+
guard coder.allowsKeyedCoding else {
86+
fatalError("Decoding this class requires keyed coding")
87+
}
88+
89+
return NSMakeRange(coder.decodeInteger(forKey: "NSRangeLocation"), coder.decodeInteger(forKey: "NSRangeLength"))
90+
}
6191
}
6292

63-
internal class _NSRegularExpressionNSTextCheckingResultResult : NSTextCheckingResult {
64-
var _ranges = [NSRange]()
65-
let _regularExpression: NSRegularExpression
93+
// Darwin uses these private subclasses, each of which can be archived. We reimplement all three here so that NSKeyed{Una,A}rchiver can find them.
94+
// Since we do not have to box an array of NSRanges into a NSArray of NSValues, we do not implement the storage optimization these subclasses would have on Darwin. They exist purely to be encoded or decoded. When we produce instances, we will produce the correct subclass for the count Darwin expects so that _that_ implementation can likewise be efficient.
95+
96+
internal class NSRegularExpressionCheckingResult: NSTextCheckingResult {
97+
let _regularExpression: NSRegularExpression!
98+
override var regularExpression: NSRegularExpression? { return _regularExpression }
6699

67-
init(ranges: NSRangePointer, count: Int, regularExpression: NSRegularExpression) {
100+
let _rangeArray: [NSRange]!
101+
var rangeArray: [NSRange] { return _rangeArray }
102+
103+
init(rangeArray: [NSRange], regularExpression: NSRegularExpression) {
104+
_rangeArray = rangeArray.map { $0.location == kCFNotFound ? NSMakeRange(NSNotFound, 0) : $0 }
68105
_regularExpression = regularExpression
69106
super.init()
70-
let notFound = NSRange(location: NSNotFound,length: 0)
71-
for i in 0..<count {
72-
ranges[i].location == kCFNotFound ? _ranges.append(notFound) : _ranges.append(ranges[i])
73-
}
74107
}
75-
76-
internal required init?(coder aDecoder: NSCoder) {
77-
NSUnimplemented()
108+
109+
override init() {
110+
if type(of: self) == NSRegularExpressionCheckingResult.self {
111+
NSRequiresConcreteImplementation()
112+
}
113+
114+
_regularExpression = nil
115+
_rangeArray = nil
116+
super.init()
78117
}
79118

80-
internal override func encode(with aCoder: NSCoder) {
81-
NSUnimplemented()
119+
public convenience required init?(coder aDecoder: NSCoder) {
120+
guard aDecoder.allowsKeyedCoding else {
121+
fatalError("Decoding this class requires keyed coding")
122+
}
123+
124+
let regularExpression = aDecoder.decodeObject(of: NSRegularExpression.self, forKey: "NSRegularExpression")!
125+
let nsRanges = aDecoder.decodeObject(of: [ NSArray.self, NSValue.self ], forKey: "NSRangeArray") as! NSArray
126+
let rangeArray = nsRanges.compactMap { return ($0 as! NSValue).rangeValue }
127+
128+
self.init(rangeArray: rangeArray, regularExpression: regularExpression)
129+
}
130+
131+
override func encode(with aCoder: NSCoder) {
132+
guard aCoder.allowsKeyedCoding else {
133+
fatalError("Encoding this class requires keyed coding")
134+
}
135+
136+
let ranges = rangeArray.map { NSValue(range: $0) }._nsObject
137+
138+
encodeRange(with: aCoder)
139+
aCoder.encode(regularExpression, forKey: "NSRegularExpression")
140+
aCoder.encode(ranges, forKey: "NSRangeArray")
82141
}
83142

84-
override var resultType: CheckingType { return .RegularExpression }
85-
override func range(at idx: Int) -> NSRange { return _ranges[idx] }
143+
override class var supportsSecureCoding: Bool { return true }
144+
145+
override var resultType: NSTextCheckingResult.CheckingType { return .regularExpression }
146+
86147
override func range(withName name: String) -> NSRange {
87-
let idx = _regularExpression._captureGroupNumber(withName: name)
148+
let idx = regularExpression!._captureGroupNumber(withName: name)
88149
if idx != kCFNotFound, idx < numberOfRanges {
89150
return range(at: idx)
90151
}
91-
152+
92153
return NSRange(location: NSNotFound, length: 0)
93154
}
94-
95-
override var numberOfRanges: Int { return _ranges.count }
96-
override var regularExpression: NSRegularExpression? { return _regularExpression }
155+
156+
override func range(at idx: Int) -> NSRange {
157+
return rangeArray[idx]
158+
}
159+
160+
override var numberOfRanges: Int {
161+
return rangeArray.count
162+
}
97163
}
98164

165+
internal class NSSimpleRegularExpressionCheckingResult: NSRegularExpressionCheckingResult {}
166+
internal class NSExtendedRegularExpressionCheckingResult: NSRegularExpressionCheckingResult {}
167+
internal class NSComplexRegularExpressionCheckingResult: NSRegularExpressionCheckingResult {}
168+
169+
99170
extension NSTextCheckingResult {
100171

101172
public func adjustingRanges(offset: Int) -> NSTextCheckingResult {
@@ -111,8 +182,149 @@ extension NSTextCheckingResult {
111182
newRanges.append(NSRange(location: currentRange.location + offset,length: currentRange.length))
112183
}
113184
}
114-
let result = NSTextCheckingResult.regularExpressionCheckingResultWithRanges(&newRanges, count: count, regularExpression: self.regularExpression!)
185+
let result = NSTextCheckingResult.regularExpressionCheckingResult(ranges: &newRanges, count: count, regularExpression: self.regularExpression!)
115186
return result
116187
}
117188
}
118189

190+
// MARK: Availability diagnostics for unsupported features
191+
192+
@available(*, deprecated, message: "These types of result will not be returned by swift-corelibs-foundation API.")
193+
extension NSTextCheckingResult.CheckingType {
194+
public static let orthography = NSTextCheckingResult.CheckingType(rawValue: 1 << 1)
195+
public static let spelling = NSTextCheckingResult.CheckingType(rawValue: 1 << 2)
196+
public static let grammar = NSTextCheckingResult.CheckingType(rawValue: 1 << 3)
197+
public static let date = NSTextCheckingResult.CheckingType(rawValue: 1 << 4)
198+
public static let address = NSTextCheckingResult.CheckingType(rawValue: 1 << 5)
199+
public static let link = NSTextCheckingResult.CheckingType(rawValue: 1 << 6)
200+
public static let quote = NSTextCheckingResult.CheckingType(rawValue: 1 << 7)
201+
public static let dash = NSTextCheckingResult.CheckingType(rawValue: 1 << 8)
202+
public static let replacement = NSTextCheckingResult.CheckingType(rawValue: 1 << 9)
203+
public static let correction = NSTextCheckingResult.CheckingType(rawValue: 1 << 10)
204+
public static let phoneNumber = NSTextCheckingResult.CheckingType(rawValue: 1 << 11)
205+
public static let transitInformation = NSTextCheckingResult.CheckingType(rawValue: 1 << 12)
206+
}
207+
208+
public struct NSTextCheckingKey: RawRepresentable, Hashable {
209+
public var rawValue: String
210+
211+
init(_ string: String) {
212+
self.init(rawValue: string)
213+
}
214+
215+
public init(rawValue: Self.RawValue) {
216+
self.rawValue = rawValue
217+
}
218+
}
219+
220+
@available(*, deprecated, message: "Results associated with these keys are not available in swift-corelibs-foundation.")
221+
extension NSTextCheckingKey {
222+
static let airline = NSTextCheckingKey(rawValue: "Airline")
223+
static let city = NSTextCheckingKey(rawValue: "City")
224+
static let country = NSTextCheckingKey(rawValue: "Country")
225+
static let flight = NSTextCheckingKey(rawValue: "Flight")
226+
static let jobTitle = NSTextCheckingKey(rawValue: "JobTitle")
227+
static let name = NSTextCheckingKey(rawValue: "Name")
228+
static let organization = NSTextCheckingKey(rawValue: "Organization")
229+
static let phone = NSTextCheckingKey(rawValue: "Phone")
230+
static let state = NSTextCheckingKey(rawValue: "State")
231+
static let street = NSTextCheckingKey(rawValue: "Street")
232+
static let zip = NSTextCheckingKey(rawValue: "Zip")
233+
}
234+
235+
@available(*, unavailable, message: "These types of results cannot be constructed in swift-corelibs-foundation")
236+
extension NSTextCheckingResult {
237+
open class func orthographyCheckingResult(range: NSRange, orthography: NSOrthography) -> NSTextCheckingResult {
238+
NSUnsupported()
239+
}
240+
241+
open class func spellCheckingResult(range: NSRange) -> NSTextCheckingResult {
242+
NSUnsupported()
243+
}
244+
245+
open class func grammarCheckingResult(range: NSRange, details: [[String : Any]]) -> NSTextCheckingResult {
246+
NSUnsupported()
247+
}
248+
249+
open class func dateCheckingResult(range: NSRange, date: Date) -> NSTextCheckingResult {
250+
NSUnsupported()
251+
}
252+
253+
open class func dateCheckingResult(range: NSRange, date: Date, timeZone: TimeZone, duration: TimeInterval) -> NSTextCheckingResult {
254+
NSUnsupported()
255+
}
256+
257+
open class func addressCheckingResult(range: NSRange, components: [NSTextCheckingKey : String]) -> NSTextCheckingResult {
258+
NSUnsupported()
259+
}
260+
261+
open class func linkCheckingResult(range: NSRange, url: URL) -> NSTextCheckingResult {
262+
NSUnsupported()
263+
}
264+
265+
open class func quoteCheckingResult(range: NSRange, replacementString: String) -> NSTextCheckingResult {
266+
NSUnsupported()
267+
}
268+
269+
open class func dashCheckingResult(range: NSRange, replacementString: String) -> NSTextCheckingResult {
270+
NSUnsupported()
271+
}
272+
273+
open class func replacementCheckingResult(range: NSRange, replacementString: String) -> NSTextCheckingResult {
274+
NSUnsupported()
275+
}
276+
277+
open class func correctionCheckingResult(range: NSRange, replacementString: String, alternativeStrings: [String]) -> NSTextCheckingResult {
278+
NSUnsupported()
279+
}
280+
281+
open class func phoneNumberCheckingResult(range: NSRange, phoneNumber: String) -> NSTextCheckingResult {
282+
NSUnsupported()
283+
}
284+
285+
open class func transitInformationCheckingResult(range: NSRange, components: [NSTextCheckingKey : String]) -> NSTextCheckingResult {
286+
NSUnsupported()
287+
}
288+
}
289+
290+
@available(*, deprecated, message: "NSOrtography is not available in swift-corelibs-foundation")
291+
open class NSOrthography: NSObject, NSCopying, NSSecureCoding {
292+
@available(*, unavailable, message: "NSOrtography is not available in swift-corelibs-foundation")
293+
open class func defaultOrtography(forLanguage: String) -> Self {
294+
NSUnsupported()
295+
}
296+
297+
@available(*, unavailable, message: "NSOrtography is not available in swift-corelibs-foundation")
298+
public init(dominantScript: String, languageMap: [String: [String]]) {
299+
NSUnsupported()
300+
}
301+
302+
public func copy(with zone: NSZone?) -> Any {
303+
NSUnsupported()
304+
}
305+
306+
open class var supportsSecureCoding: Bool { NSUnsupported() }
307+
308+
public func encode(with aCoder: NSCoder) {
309+
NSUnsupported()
310+
}
311+
312+
public required init?(coder aDecoder: NSCoder) {
313+
NSUnsupported()
314+
}
315+
316+
@available(*, unavailable, message: "NSOrtography is not available in swift-corelibs-foundation")
317+
open var languageMap: [String: [String]] { NSUnsupported() }
318+
@available(*, unavailable, message: "NSOrtography is not available in swift-corelibs-foundation")
319+
open var dominantLanguage: String { NSUnsupported() }
320+
@available(*, unavailable, message: "NSOrtography is not available in swift-corelibs-foundation")
321+
open var dominantScript: String { NSUnsupported() }
322+
@available(*, unavailable, message: "NSOrtography is not available in swift-corelibs-foundation")
323+
open func dominantLanguage(forScript: String) -> String? { NSUnsupported() }
324+
@available(*, unavailable, message: "NSOrtography is not available in swift-corelibs-foundation")
325+
open func language(forScript: String) -> [String]? { NSUnsupported() }
326+
@available(*, unavailable, message: "NSOrtography is not available in swift-corelibs-foundation")
327+
open var alLScripts: [String] { NSUnsupported() }
328+
@available(*, unavailable, message: "NSOrtography is not available in swift-corelibs-foundation")
329+
open var allLanguages: [String] { NSUnsupported() }
330+
}

0 commit comments

Comments
 (0)