mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This undoes some of Joe's work in 8665342 to add a guarantee: if an
@objc convenience initializer only calls other @objc initializers that
eventually call a designated initializer, it won't result in an extra
allocation. While Objective-C /allows/ returning a different object
from an initializer than the allocation you were given, doing so
doesn't play well with some very hairy implementation details of
compiled nib files (or NSCoding archives with cyclic references in
general).
This guarantee only applies to
(1) calling `self.init`
(2) where the delegated-to initializer is @objc
because convenience initializers must do dynamic dispatch when they
delegate, and Swift only stores allocating entry points for
initializers in a class's vtable. To dynamically find an initializing
entry point, ObjC dispatch must be used instead.
(It's worth noting that this patch does NOT check that the calling
initializer is a convenience initializer when deciding whether to use
ObjC dispatch for `self.init`. If we ever add peer delegation to
designated initializers, which is totally a valid feature, that should
use static dispatch and therefore should not go through objc_msgSend.)
This change doesn't /always/ result in fewer allocations; if the
delegated-to initializer ends up returning a different object after
all, the original allocation was wasted. Objective-C has the same
problem (one of the reasons why factory methods exist for things like
NSNumber and NSArray).
We do still get most of the benefits of Joe's original change. In
particular, vtables only ever contain allocating initializer entry
points, never the initializing ones, and never /both/ (which was a
thing that could happen with 'required' before).
rdar://problem/46823518
480 lines
15 KiB
Swift
480 lines
15 KiB
Swift
// RUN: %target-run-simple-swift
|
|
// REQUIRES: executable_test
|
|
|
|
// REQUIRES: objc_interop
|
|
|
|
import Foundation
|
|
import StdlibUnittest
|
|
|
|
|
|
// rdar://problem/18884272
|
|
// Make sure that NSObject conforms to NSObjectProtocol. This
|
|
// particular bug is ridiculously hard to trigger without a complete
|
|
// SDK, so it sits here.
|
|
let objcProtocol: NSObjectProtocol = NSObject()
|
|
|
|
var FoundationTestSuite = TestSuite("Foundation")
|
|
|
|
func asNSString(_ s: String) -> NSString { return s as NSString }
|
|
func asString(_ ns: NSString) -> String { return ns as String }
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Strings
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
FoundationTestSuite.test("NSString") {
|
|
var str = "Hello"
|
|
var nsStr = str as NSString
|
|
assert(nsStr.compare(str).rawValue == ComparisonResult.orderedSame.rawValue)
|
|
assert(nsStr.compare(str) == ComparisonResult.orderedSame)
|
|
nsStr = "World"
|
|
str = nsStr as String
|
|
// FIXME: Shouldn't need coercion here to resolve ambiguity. <rdar://problem/14637688>
|
|
assert(str == asString(nsStr))
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Numbers
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
FoundationTestSuite.test("NSNumber") {
|
|
var i = 17
|
|
var d = 3.14159
|
|
var b = true
|
|
|
|
// Implicit boxing/explicit unboxing
|
|
var nsNum: NSNumber = i as NSNumber
|
|
expectEqual(i, Int(nsNum))
|
|
|
|
nsNum = d as NSNumber
|
|
expectEqual(d, Double(nsNum))
|
|
|
|
nsNum = b as NSNumber
|
|
expectEqual(b, Bool(nsNum))
|
|
|
|
// Literals
|
|
nsNum = 42
|
|
expectEqual(42, Int(nsNum))
|
|
|
|
nsNum = 3.14159
|
|
expectEqual(3.14159, Double(nsNum))
|
|
|
|
nsNum = false
|
|
expectEqual(false, Bool(nsNum))
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Arrays
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
FoundationTestSuite.test("NSArray") {
|
|
// Literals
|
|
var nsArr: NSArray = [1, 2.5, "Hello"]
|
|
assert(nsArr.count == 3)
|
|
|
|
// Subscripting
|
|
expectEqual(1, Int(nsArr[0] as! NSNumber))
|
|
expectEqual(2.5, Double(nsArr[1] as! NSNumber))
|
|
expectTrue((nsArr[2] as! NSString).isEqual("Hello"))
|
|
|
|
// Iteration
|
|
var result = [String]()
|
|
for x: Any in nsArr {
|
|
result.append((x as! NSObject).description)
|
|
}
|
|
expectEqualSequence(["1", "2.5", "Hello"], result)
|
|
}
|
|
|
|
FoundationTestSuite.test("NSMutableArray") {
|
|
let nsMutableArr: NSMutableArray = ["Constant", "Moon"]
|
|
nsMutableArr[0] = "Inconstant"
|
|
|
|
expectEqual(2, nsMutableArr.count)
|
|
expectEqual("Inconstant", nsMutableArr[0] as! NSString)
|
|
expectEqual("Moon", nsMutableArr[1] as! NSString)
|
|
}
|
|
|
|
FoundationTestSuite.test("NSArrayVariadicInit") {
|
|
let variadicArray = NSArray(objects: "A", "B", "C")
|
|
expectEqual(3, variadicArray.count)
|
|
}
|
|
|
|
FoundationTestSuite.test("arrayConversions") {
|
|
var nsa = NSArray()
|
|
var aoa: Array<AnyObject> = []
|
|
|
|
nsa as Array<AnyObject>
|
|
var nsa2 = NSArray()
|
|
var aoa2 = nsa2 as Array<AnyObject>
|
|
|
|
var nsaoa = aoa as NSArray
|
|
|
|
func nsArrayToAnyObjectArray(_ nsa: NSArray) -> [AnyObject] {
|
|
return nsa as [AnyObject]
|
|
}
|
|
|
|
nsArrayToAnyObjectArray(nsa)
|
|
nsArrayToAnyObjectArray(aoa as NSArray)
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Dictionaries
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
FoundationTestSuite.test("NSDictionary") {
|
|
var nsDict : NSDictionary = [1 : "Hello", 2 : "World"]
|
|
assert((nsDict[1]! as! NSString).isEqual("Hello"))
|
|
assert((nsDict[2]! as! NSString).isEqual("World"))
|
|
|
|
let nsMutableDict: NSMutableDictionary = ["Hello" : 1, "World" : 2 as NSNumber]
|
|
assert((nsMutableDict["Hello"]! as AnyObject).isEqual(1))
|
|
assert((nsMutableDict["World"]! as AnyObject).isEqual(2))
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Ranges
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
FoundationTestSuite.test("NSRange") {
|
|
let nsRange = NSRange(1..<5)
|
|
expectEqual("{1, 4}", String(NSStringFromRange(nsRange)))
|
|
}
|
|
|
|
FoundationTestSuite.test("RangeConversion") {
|
|
let i: Int8 = 10
|
|
let j: Int8 = 20
|
|
|
|
let nsrFromInt8 = NSRange(i..<j)
|
|
expectEqual(nsrFromInt8, NSRange(location: 10, length: 10))
|
|
|
|
var r = Range(nsrFromInt8)
|
|
expectNotNil(r)
|
|
expectEqual(r!.lowerBound, 10)
|
|
expectEqual(r!.upperBound, 20)
|
|
expectType(Optional<Range<Int>>.self, &r)
|
|
|
|
var r8 = Range<Int8>(nsrFromInt8)
|
|
expectNotNil(r8 != nil)
|
|
expectEqual(r8?.lowerBound, 10)
|
|
expectEqual(r8?.upperBound, 20)
|
|
expectType(Optional<Range<Int8>>.self, &r8)
|
|
|
|
var nsrFromPartial = NSRange(..<5)
|
|
expectEqual("{0, 5}", NSStringFromRange(nsrFromPartial))
|
|
|
|
let s = "Hello, 🌎!"
|
|
let b = s.firstIndex(of: ",")!
|
|
let e = s.firstIndex(of: "!")!
|
|
let nsr = NSRange(b..<e, in: s)
|
|
expectEqual(nsr.location, 5)
|
|
expectEqual(nsr.length, 4)
|
|
let rs = Range(nsr, in: s)!
|
|
expectEqual(s[rs], ", 🌎")
|
|
|
|
let nsrTo = NSRange(..<b, in: s)
|
|
expectEqual(nsrTo.location, 0)
|
|
expectEqual(nsrTo.length, 5)
|
|
let nsrFrom = NSRange(b..., in: s)
|
|
expectEqual(nsrFrom.location,5)
|
|
expectEqual(nsrFrom.length, 5)
|
|
|
|
expectNil(Range(NSRange(location: 100, length: 0), in: s))
|
|
expectNil(Range(NSRange(location: 0, length: 100), in: s))
|
|
|
|
let empty = ""
|
|
expectNil(Range(NSRange(location: 1, length: 0), in: empty))
|
|
expectNil(Range(NSRange(location: 0, length: 1), in: empty))
|
|
expectNotNil(Range(NSRange(location: 0, length: 0), in: empty))
|
|
|
|
// FIXME: enable once indices conform to RangeExpression
|
|
// let nsrFull = NSRange(s.indices, in: s)
|
|
// expectEqual(nsrFull.location, 0)
|
|
// expectEqual(nsrFull.length, 10)
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// URLs
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
FoundationTestSuite.test("NSURL") {
|
|
let nsURL = NSURL(string: "http://llvm.org")!
|
|
expectEqual("http://llvm.org", nsURL.description)
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Pattern-matching
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
func matchesEither(_ input: NSNumber, _ a: NSNumber, _ b: NSNumber) -> Bool {
|
|
switch input {
|
|
case a, b:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
FoundationTestSuite.test("patternMatching") {
|
|
var one, two, three, oneAgain : NSNumber
|
|
one = NSNumber(value: 1)
|
|
two = NSNumber(value: 2)
|
|
three = NSNumber(value: 3)
|
|
oneAgain = NSNumber(value: 1)
|
|
expectFalse(matchesEither(one, two, three))
|
|
expectTrue(matchesEither(one, oneAgain, three))
|
|
expectTrue(matchesEither(one, two, oneAgain))
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Miscellaneous
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// <rdar://problem/14474701>
|
|
// Type checker used to crash on this.
|
|
class ClassWithDtor : NSObject {
|
|
deinit {
|
|
let noteCenter = NotificationCenter.default
|
|
noteCenter.removeObserver(self, name: Notification.Name(rawValue: "ReceivedContentNotification"), object: nil)
|
|
}
|
|
}
|
|
|
|
FoundationTestSuite.test("rdar://17584531") {
|
|
// <rdar://problem/17584531>
|
|
// Type checker used to be confused by this.
|
|
var dict: NSDictionary = ["status": 200, "people": [["id": 255, "name": ["first": "John", "last": "Appleseed"] as NSDictionary] as NSDictionary] as NSArray]
|
|
var dict2 = dict["people"].flatMap { $0 as? NSArray }?[0] as! NSDictionary
|
|
expectEqual("Optional(255)", String(describing: dict2["id" as NSString]))
|
|
}
|
|
|
|
FoundationTestSuite.test("DarwinBoolean smoke test") {
|
|
expectFalse(CFEqual("abc" as NSString as CFString, "def" as NSString as CFString))
|
|
|
|
let _: CFArrayEqualCallBack = { DarwinBoolean($0 == $1) }
|
|
}
|
|
|
|
#if os(macOS)
|
|
FoundationTestSuite.test("NSRectEdge/constants") {
|
|
// Check that the following constants have the correct type and value.
|
|
//
|
|
// It is valid to hardcode the value in the test. The way they are currently
|
|
// defined in the SDK makes them ABI for Objective-C code.
|
|
expectEqual(0, CGRectEdge(rectEdge: NSRectEdge.minX).rawValue)
|
|
expectEqual(0, NSRectEdge(rectEdge: CGRectEdge.minXEdge).rawValue)
|
|
|
|
expectEqual(1, CGRectEdge(rectEdge: NSRectEdge.minY).rawValue)
|
|
expectEqual(1, NSRectEdge(rectEdge: CGRectEdge.minYEdge).rawValue)
|
|
|
|
expectEqual(2, CGRectEdge(rectEdge: NSRectEdge.maxX).rawValue)
|
|
expectEqual(2, NSRectEdge(rectEdge: CGRectEdge.maxXEdge).rawValue)
|
|
|
|
expectEqual(3, CGRectEdge(rectEdge: NSRectEdge.maxY).rawValue)
|
|
expectEqual(3, NSRectEdge(rectEdge: CGRectEdge.maxYEdge).rawValue)
|
|
}
|
|
#endif
|
|
|
|
|
|
if #available(OSX 10.11, iOS 9.0, *) {
|
|
FoundationTestSuite.test("UndoManager/ObjCClass") {
|
|
let UM = UndoManager()
|
|
|
|
// Confirm with an ObjC class.
|
|
class ObjCClass : NSObject {
|
|
var someProperty: String = ""
|
|
}
|
|
let f = ObjCClass()
|
|
UM.registerUndo(withTarget: f) { target in
|
|
target.someProperty = "expected"
|
|
}
|
|
UM.undo()
|
|
expectEqual(f.someProperty, "expected")
|
|
}
|
|
|
|
FoundationTestSuite.test("UndoManager/SwiftClass") {
|
|
let UM = UndoManager()
|
|
|
|
// Confirm with a Swift class.
|
|
class SwiftClass {
|
|
var someOtherProperty: String = ""
|
|
}
|
|
var b = SwiftClass()
|
|
UM.registerUndo(withTarget:b) { target in
|
|
target.someOtherProperty = "expected"
|
|
}
|
|
UM.undo()
|
|
expectEqual(b.someOtherProperty, "expected")
|
|
}
|
|
}
|
|
|
|
private let KEY = "some-key"
|
|
private func createTestArchive() -> NSData {
|
|
let mutableData = NSMutableData()
|
|
let KA = NSKeyedArchiver(forWritingWith: mutableData)
|
|
|
|
// Set up some fake data.
|
|
let obj = NSPredicate(value: true)
|
|
KA.encode(obj, forKey: KEY)
|
|
KA.encode(obj, forKey: NSKeyedArchiveRootObjectKey)
|
|
KA.finishEncoding()
|
|
|
|
return mutableData
|
|
}
|
|
|
|
FoundationTestSuite.test("NSKeyedUnarchiver/decodeObjectOfClass(_:forKey:)") {
|
|
let obj = NSPredicate(value: true)
|
|
let data = createTestArchive()
|
|
|
|
var KU = NSKeyedUnarchiver(forReadingWith: data as Data)
|
|
|
|
var missing = KU.decodeObject(of: NSPredicate.self, forKey: "Not there")
|
|
expectNil(missing)
|
|
expectType((NSPredicate?).self, &missing)
|
|
|
|
var decoded = KU.decodeObject(of: NSPredicate.self, forKey: KEY)
|
|
expectNotNil(decoded)
|
|
expectType((NSPredicate?).self, &decoded)
|
|
|
|
var wrongType = KU.decodeObject(of: DateFormatter.self, forKey: KEY)
|
|
expectNil(missing)
|
|
|
|
KU.finishDecoding()
|
|
}
|
|
|
|
if #available(OSX 10.11, iOS 9.0, *) {
|
|
FoundationTestSuite.test("NSKeyedUnarchiver/decodeTopLevel*") {
|
|
let obj = NSPredicate(value: true)
|
|
let data = createTestArchive()
|
|
|
|
// first confirm .decodeObjectWithClasses overlay requires an array of classes
|
|
var KU = NSKeyedUnarchiver(forReadingWith: data as Data)
|
|
var nonTopLevelResult = KU.decodeObject(of: [NSPredicate.self], forKey: KEY)
|
|
expectTrue(nonTopLevelResult != nil)
|
|
KU.finishDecoding()
|
|
|
|
KU = NSKeyedUnarchiver(forReadingWith: data as Data)
|
|
do {
|
|
// decodeObjectForKey(_:) throws
|
|
let decoded1 = try KU.decodeTopLevelObject(forKey: KEY) as? NSPredicate
|
|
let missing1 = try KU.decodeTopLevelObject(forKey:"Not there")
|
|
expectTrue(decoded1 != nil)
|
|
expectEqual(obj, decoded1)
|
|
expectTrue(missing1 == nil)
|
|
KU.finishDecoding()
|
|
|
|
// recreate so we can do the rest securely
|
|
KU = NSKeyedUnarchiver(forReadingWith: data as Data)
|
|
KU.requiresSecureCoding = true
|
|
|
|
// decodeObjectOfClass(_:,forKey:) throws
|
|
let decoded2 = try KU.decodeTopLevelObject(of: NSPredicate.self, forKey: KEY)
|
|
let missing2 = try KU.decodeTopLevelObject(of: NSPredicate.self, forKey: "Not there")
|
|
expectTrue(decoded2 != nil)
|
|
expectEqual(obj, decoded2)
|
|
expectTrue(missing2 == nil)
|
|
|
|
let classes: [AnyClass] = [NSPredicate.self]
|
|
let decoded3 = try KU.decodeTopLevelObject(of: classes, forKey: KEY) as? NSPredicate
|
|
let missing3 = try KU.decodeTopLevelObject(of: classes, forKey: "Not there")
|
|
expectTrue(decoded3 != nil)
|
|
expectEqual(obj, decoded3)
|
|
expectTrue(missing3 == nil)
|
|
}
|
|
catch {
|
|
expectTrue(false) // should have no errors
|
|
}
|
|
KU.finishDecoding()
|
|
|
|
KU = NSKeyedUnarchiver(forReadingWith: data as Data)
|
|
KU.requiresSecureCoding = true
|
|
do {
|
|
// recreate so we avoid caches from above
|
|
let wrong = try KU.decodeTopLevelObject(of: DateFormatter.self, forKey: KEY)
|
|
expectUnreachable() // should have error
|
|
}
|
|
catch {
|
|
// expected
|
|
}
|
|
KU.finishDecoding()
|
|
KU = NSKeyedUnarchiver(forReadingWith: data as Data)
|
|
KU.requiresSecureCoding = true
|
|
do {
|
|
let wrongClasses: [AnyClass] = [DateFormatter.self]
|
|
let wrong = try KU.decodeTopLevelObject(of: wrongClasses, forKey: KEY)
|
|
expectUnreachable() // should have error
|
|
}
|
|
catch {
|
|
// expected
|
|
}
|
|
KU.finishDecoding()
|
|
|
|
// confirm the that class function works
|
|
do {
|
|
let decoded = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data as Data) as? NSPredicate
|
|
expectEqual(obj, decoded)
|
|
}
|
|
catch {
|
|
expectUnreachableCatch(error)
|
|
}
|
|
}
|
|
|
|
FoundationTestSuite.test("NSKeyedUnarchiver/decodeTopLevelObjectOfClass(_:forKey:)/trap") {
|
|
let obj = NSPredicate(value: true)
|
|
let data = createTestArchive()
|
|
|
|
var KU = NSKeyedUnarchiver(forReadingWith: data as Data)
|
|
|
|
// Even though we're doing non-secure coding, this should still be checked
|
|
// in Swift.
|
|
do {
|
|
var wrongType = try KU.decodeTopLevelObject(of: DateFormatter.self, forKey: KEY)
|
|
expectType((DateFormatter?).self, &wrongType)
|
|
} catch {
|
|
expectUnreachableCatch(error)
|
|
}
|
|
|
|
KU.finishDecoding()
|
|
}
|
|
|
|
FoundationTestSuite.test("NSKeyedUnarchiver/CycleWithConvenienceInit") {
|
|
@objc(HasACyclicReference)
|
|
class HasACyclicReference: NSObject, NSCoding {
|
|
var ref: AnyObject?
|
|
override init() {
|
|
super.init()
|
|
ref = self
|
|
}
|
|
|
|
required convenience init?(coder: NSCoder) {
|
|
let decodedRef = coder.decodeObject(forKey: "ref") as AnyObject?
|
|
self.init(ref: decodedRef)
|
|
}
|
|
@objc init(ref: AnyObject?) {
|
|
self.ref = ref
|
|
super.init()
|
|
}
|
|
|
|
func encode(with coder: NSCoder) {
|
|
coder.encode(ref, forKey: "ref")
|
|
}
|
|
}
|
|
|
|
let data = NSKeyedArchiver.archivedData(withRootObject: HasACyclicReference())
|
|
let decodedObj = NSKeyedUnarchiver.unarchiveObject(with: data) as! HasACyclicReference
|
|
|
|
expectEqual(ObjectIdentifier(decodedObj), ObjectIdentifier(decodedObj.ref!),
|
|
"cycle was not preserved (due to object replacement?)")
|
|
}
|
|
}
|
|
|
|
FoundationTestSuite.test("NotificationCenter/addObserver(_:selector:name:object:)") {
|
|
let obj: AnyObject = "Hello" as NSString
|
|
NotificationCenter.default.addObserver(obj, selector: Selector("blah:"),
|
|
name: nil, object: nil)
|
|
let name = "hello"
|
|
NotificationCenter.default.addObserver(obj, selector: Selector("blarg:"),
|
|
name: Notification.Name(name),
|
|
object: nil)
|
|
}
|
|
|
|
runAllTests()
|
|
|