mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Update PR #68720 with lessons learned in reviewing #69464 Background: * SwiftValue can expose Swift value types (structs/enums) to ObjC by wrapping them in an Obj-C object on the heap * SwiftObject is the Obj-C type that Swift class objects all inherit from (when viewed from Obj-C). This allows arbitrary Swift class objects to be passed into Obj-C. History: * PR #4124 made Obj-C `-hash` and `-isEqual:` work for SwiftValue for Hashable Swift types * PR #68720 extended SwiftValue to also support Equatable Swift types * PR #69464 added similar support to SwiftObject In the process of working through #69464, we found a better way to handle an ObjC request for `-hash` for a type that is Swift Equatable but not Hashable. This PR updates SwiftValue to use the same approach. The approach considers three cases: 1. A Hashable type can forward both `-hash` and `-isEqual:` to the Swift object. 2. A type that is neither Equatable nor Hashable can implement `-isEqual:` as the identity check and `-hash` as returning the address of the object in memory. 3. A type is that Equatable but not Hashable is more complex. In this last case, we can easily forward `-isEqual:` to the Equatable conformance but ObjC also requires us to always provide a compatible `-hash` implementation. The only way to do this is to have `-hash` return a constant, but that is a very bad idea in general, so we're also including a log message whenever we see a request for `-hash` on a Swift value that is Equatable but not Hashable. To limit performance problems from the logging itself, we emit the log message only once for each type.
159 lines
5.5 KiB
Swift
159 lines
5.5 KiB
Swift
//===--- SwiftValueNSObject.swift - Test SwiftValue's NSObject interop --===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// RUN: %empty-directory(%t)
|
|
//
|
|
// RUN: %target-clang %S/Inputs/SwiftValueNSObject/SwiftValueNSObject.m -c -o %t/SwiftValueNSObject.o -g
|
|
// RUN: %target-build-swift %s -g -I %S/Inputs/SwiftValueNSObject/ -Xlinker %t/SwiftValueNSObject.o -o %t/SwiftValueNSObject
|
|
// RUN: %target-codesign %t/SwiftValueNSObject
|
|
// RUN: %target-run %t/SwiftValueNSObject 2> %t/log.txt
|
|
// RUN: cat %t/log.txt 1>&2
|
|
// RUN: %FileCheck %s < %t/log.txt
|
|
// REQUIRES: executable_test
|
|
|
|
// REQUIRES: objc_interop
|
|
|
|
import Foundation
|
|
|
|
struct C: CustomDebugStringConvertible {
|
|
var description: String { "This is not C's description" }
|
|
var debugDescription: String { "This is C's debug description" }
|
|
}
|
|
struct D: CustomStringConvertible {
|
|
var description: String { "This is D's description" }
|
|
var debugDescription: String { "This is not D's debug description" }
|
|
}
|
|
|
|
struct E : Equatable, CustomStringConvertible {
|
|
var i : Int
|
|
static func ==(lhs: E, rhs: E) -> Bool { lhs.i == rhs.i }
|
|
init(i: Int) { self.i = i }
|
|
var description: String { "\(type(of:self))(i:\(self.i))" }
|
|
}
|
|
|
|
struct E1 : Equatable {
|
|
var i : Int
|
|
static func ==(lhs: E1, rhs: E1) -> Bool { lhs.i == rhs.i }
|
|
init(i: Int) { self.i = i }
|
|
}
|
|
|
|
struct F : CustomStringConvertible {
|
|
var i : Int
|
|
init(i: Int) { self.i = i }
|
|
var description: String { "\(type(of:self))(i:\(self.i))" }
|
|
}
|
|
|
|
struct H : Hashable {
|
|
var i : Int
|
|
static func ==(lhs: H, rhs: H) -> Bool { lhs.i == rhs.i }
|
|
init(i: Int) { self.i = i }
|
|
var description: String { "\(type(of:self))(i:\(self.i))" }
|
|
func hash(into hasher: inout Hasher) { hasher.combine(i + 17) }
|
|
}
|
|
|
|
@_silgen_name("TestSwiftValueNSObject")
|
|
func TestSwiftValueNSObject(_ c: AnyObject, _ d: AnyObject)
|
|
@_silgen_name("TestSwiftValueNSObjectEquals")
|
|
func TestSwiftValueNSObjectEquals(_: AnyObject, _: AnyObject)
|
|
@_silgen_name("TestSwiftValueNSObjectNotEquals")
|
|
func TestSwiftValueNSObjectNotEquals(_: AnyObject, _: AnyObject)
|
|
@_silgen_name("TestSwiftValueNSObjectHashValue")
|
|
func TestSwiftValueNSObjectHashValue(_: AnyObject, _: Int)
|
|
@_silgen_name("TestSwiftValueNSObjectDefaultHashValue")
|
|
func TestSwiftValueNSObjectDefaultHashValue(_: AnyObject)
|
|
@_silgen_name("TestSwiftValueNSObjectAssertNoErrors")
|
|
func TestSwiftValueNSObjectAssertNoErrors()
|
|
|
|
// Verify that Obj-C isEqual: provides same answer as Swift ==
|
|
func TestEquatableEquals<T: Equatable>(_ e1: T, _ e2: T) {
|
|
if e1 == e2 {
|
|
TestSwiftValueNSObjectEquals(e1 as AnyObject, e2 as AnyObject)
|
|
} else {
|
|
TestSwiftValueNSObjectNotEquals(e1 as AnyObject, e2 as AnyObject)
|
|
}
|
|
}
|
|
|
|
func TestNonEquatableEquals<T>(_ e1: T, _ e2: T) {
|
|
TestSwiftValueNSObjectNotEquals(e1 as AnyObject, e2 as AnyObject)
|
|
}
|
|
|
|
// Verify that Obj-C hashValue matches Swift hashValue for Hashable types
|
|
func TestHashable<T: Hashable>(_ h: T)
|
|
{
|
|
TestSwiftValueNSObjectHashValue(h as AnyObject, h.hashValue)
|
|
}
|
|
|
|
// Test Obj-C hashValue for Swift types that are Equatable but not Hashable
|
|
func TestEquatableHash<T: Equatable>(_ e: T)
|
|
{
|
|
// These should have a constant hash value
|
|
TestSwiftValueNSObjectHashValue(e as AnyObject, 1)
|
|
}
|
|
|
|
func TestNonEquatableHash<T>(_ e: T)
|
|
{
|
|
TestSwiftValueNSObjectDefaultHashValue(e as AnyObject)
|
|
}
|
|
|
|
// Check NSLog() output from TestSwiftValueNSObject().
|
|
|
|
// CHECK: c ##This is C's debug description##
|
|
// CHECK-NEXT: d ##This is D's description##
|
|
// CHECK-NEXT: S ##{{.*}}__SwiftValue##
|
|
|
|
// Full message is longer, but this is the essential part...
|
|
// CHECK-NEXT: Obj-C `-hash` {{.*}} type `SwiftValueNSObject.E` {{.*}} Equatable but not Hashable
|
|
// CHECK-NEXT: Obj-C `-hash` {{.*}} type `SwiftValueNSObject.E1` {{.*}} Equatable but not Hashable
|
|
|
|
// Temporarily disable this test on older OSes until we have time to
|
|
// look into why it's failing there. rdar://problem/47870743
|
|
if #available(OSX 10.12, iOS 10.0, *) {
|
|
// Test a large number of Obj-C APIs
|
|
let c = C() as AnyObject
|
|
let d = D() as AnyObject
|
|
TestSwiftValueNSObject(c, d)
|
|
|
|
TestEquatableEquals(E(i: 1), E(i: 1))
|
|
TestEquatableEquals(E(i: 790), E(i: 790))
|
|
TestEquatableEquals(E(i: 1), E(i: 2))
|
|
TestNonEquatableEquals(F(i: 1), F(i: 2))
|
|
TestNonEquatableEquals(F(i: 1), F(i: 1))
|
|
TestSwiftValueNSObjectNotEquals(H(i:1) as AnyObject, E(i:1) as AnyObject)
|
|
|
|
// Equatable but not Hashable: alway have the same Obj-C hashValue
|
|
TestEquatableHash(E(i: 1))
|
|
TestEquatableHash(E1(i: 17))
|
|
|
|
// Neither Equatable nor Hashable
|
|
TestNonEquatableHash(C())
|
|
TestNonEquatableHash(D())
|
|
|
|
// Hashable types are also Equatable
|
|
TestEquatableEquals(H(i:1), H(i:1))
|
|
TestEquatableEquals(H(i:1), H(i:2))
|
|
TestEquatableEquals(H(i:2), H(i:1))
|
|
|
|
// Verify Obj-C hash value agrees with Swift
|
|
TestHashable(H(i:1))
|
|
TestHashable(H(i:2))
|
|
TestHashable(H(i:18))
|
|
|
|
TestSwiftValueNSObjectAssertNoErrors()
|
|
} else {
|
|
// Horrible hack to satisfy FileCheck
|
|
fputs("c ##This is C's debug description##\n", stderr)
|
|
fputs("d ##This is D's description##\n", stderr)
|
|
fputs("S ##__SwiftValue##\n", stderr)
|
|
fputs("Obj-C `-hash` ... type `SwiftValueNSObject.E` ... Equatable but not Hashable", stderr)
|
|
fputs("Obj-C `-hash` ... type `SwiftValueNSObject.E1` ... Equatable but not Hashable", stderr)
|
|
}
|