From bc8433f1861ba0d66f2d80df0750e551e0e866c7 Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Fri, 15 Jul 2016 15:55:44 -0700 Subject: [PATCH] Runtime: Implement an opaque 'SwiftValue' ObjC class to hold bridged values. If there's no better mapping for a Swift value into an Objective-C object for bridging purposes, we can fall back to boxing the value in a class. This class doesn't have any public interface beyond being `NSObject`-conforming in Objective-C, but is recognized by the Swift runtime so that it can be dynamically cast back to the boxed type. --- stdlib/public/runtime/CMakeLists.txt | 1 + stdlib/public/runtime/Casting.cpp | 83 ++++++++++- stdlib/public/runtime/ErrorObject.mm | 3 +- stdlib/public/runtime/MetadataImpl.h | 10 +- stdlib/public/runtime/SwiftObject.h | 90 ++++++++++++ stdlib/public/runtime/SwiftObject.mm | 55 +------ stdlib/public/runtime/SwiftValue.h | 59 ++++++++ stdlib/public/runtime/SwiftValue.mm | 199 ++++++++++++++++++++++++++ test/1_stdlib/BridgeIdAsAny.swift.gyb | 91 ++++++++++-- 9 files changed, 516 insertions(+), 75 deletions(-) create mode 100644 stdlib/public/runtime/SwiftObject.h create mode 100644 stdlib/public/runtime/SwiftValue.h create mode 100644 stdlib/public/runtime/SwiftValue.mm diff --git a/stdlib/public/runtime/CMakeLists.txt b/stdlib/public/runtime/CMakeLists.txt index cd5ff8068e9..4a60d2aeaae 100644 --- a/stdlib/public/runtime/CMakeLists.txt +++ b/stdlib/public/runtime/CMakeLists.txt @@ -26,6 +26,7 @@ if(SWIFT_HOST_VARIANT MATCHES "${SWIFT_DARWIN_VARIANTS}") set(swift_runtime_objc_sources ErrorObject.mm SwiftObject.mm + SwiftValue.mm Remangle.cpp Reflection.mm) else() diff --git a/stdlib/public/runtime/Casting.cpp b/stdlib/public/runtime/Casting.cpp index c8bf0ea54ba..3899553df95 100644 --- a/stdlib/public/runtime/Casting.cpp +++ b/stdlib/public/runtime/Casting.cpp @@ -31,6 +31,10 @@ #include "Private.h" #include "../SwiftShims/RuntimeShims.h" #include "stddef.h" +#if SWIFT_OBJC_INTEROP +#include "swift/Runtime/ObjCBridge.h" +#include "SwiftValue.h" +#endif #include #include @@ -2094,6 +2098,65 @@ checkDynamicCastFromOptional(OpaqueValue *dest, return {false, payloadType}; } +#if SWIFT_OBJC_INTEROP +/// Try to unbox a SwiftValue box to perform a dynamic cast. +static bool tryDynamicCastBoxedSwiftValue(OpaqueValue *dest, + OpaqueValue *src, + const Metadata *srcType, + const Metadata *targetType, + DynamicCastFlags flags) { + // Swift type should be AnyObject or a class type. + if (!srcType->isAnyClass()) { + auto existential = dyn_cast(srcType); + if (!existential || + existential->Flags.getSpecialProtocol() + != SpecialProtocol::AnyObject) + return false; + } + + id srcObject; + memcpy(&srcObject, src, sizeof(id)); + + // Do we have a SwiftValue? + SwiftValue *srcSwiftValue = getAsSwiftValue(srcObject); + if (!srcSwiftValue) + return false; + + // If so, extract the boxed value and try to cast it. + const Metadata *boxedType; + const OpaqueValue *boxedValue; + std::tie(boxedType, boxedValue) + = getValueFromSwiftValue(srcSwiftValue); + + // We can't touch the value from the box because it may be + // multiply-referenced. + // TODO: Check for uniqueness and consume if box is unique? + + // Does the boxed type exactly match the target type we're looking for? + if (boxedType == targetType) { + targetType->vw_initializeWithCopy(dest, + const_cast(boxedValue)); + // Release the box if we need to. + if (flags & DynamicCastFlags::TakeOnSuccess) + objc_release((id)srcSwiftValue); + return true; + } + + // Maybe we can cast the boxed value to our destination type somehow. + auto innerFlags = flags - DynamicCastFlags::TakeOnSuccess + - DynamicCastFlags::DestroyOnFailure; + if (swift_dynamicCast(dest, const_cast(boxedValue), + boxedType, targetType, innerFlags)) { + // Release the box if we need to. + if (flags & DynamicCastFlags::TakeOnSuccess) + objc_release((id)srcSwiftValue); + return true; + } + + return false; +} +#endif + /// Perform a dynamic cast to an arbitrary type. SWIFT_RT_ENTRY_VISIBILITY bool swift::swift_dynamicCast(OpaqueValue *dest, @@ -2238,14 +2301,24 @@ bool swift::swift_dynamicCast(OpaqueValue *dest, if (tryDynamicCastNSErrorToValue(dest, src, srcType, targetType, flags)) { return true; } +#endif + SWIFT_FALLTHROUGH; + } + + case MetadataKind::Existential: { +#if SWIFT_OBJC_INTEROP + // A class or AnyObject reference may point at a boxed SwiftValue. + if (tryDynamicCastBoxedSwiftValue(dest, src, srcType, + targetType, flags)) { + return true; + } #endif break; } + case MetadataKind::ExistentialMetatype: case MetadataKind::Enum: case MetadataKind::Optional: - case MetadataKind::Existential: - case MetadataKind::ExistentialMetatype: case MetadataKind::Function: case MetadataKind::HeapLocalVariable: case MetadataKind::HeapGenericLocalVariable: @@ -2557,7 +2630,7 @@ static id bridgeAnythingNonVerbatimToObjectiveC(OpaqueValue *src, if (consume) { if (canTake) { if (isOutOfLine) { - // Should only be true of opaque existentials. + // Should only be true of opaque existentials right now. assert(srcExistentialTy->getRepresentation() == ExistentialTypeRepresentation::Opaque); auto container = reinterpret_cast(src); @@ -2589,8 +2662,8 @@ static id bridgeAnythingNonVerbatimToObjectiveC(OpaqueValue *src, return (id)srcBridgedObject; } - // TODO: Fall back to boxing here. - crash("unimplemented boxing bridge"); + // Fall back to boxing. + return (id)bridgeAnythingToSwiftValueObject(src, srcType, consume); } SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERFACE diff --git a/stdlib/public/runtime/ErrorObject.mm b/stdlib/public/runtime/ErrorObject.mm index e322ad7b154..89c2dbb8d53 100644 --- a/stdlib/public/runtime/ErrorObject.mm +++ b/stdlib/public/runtime/ErrorObject.mm @@ -43,7 +43,8 @@ using namespace swift; @implementation _SwiftNativeNSError -+ (instancetype)alloc { ++ (instancetype)allocWithZone:(NSZone *)zone { + (void)zone; swift::crash("_SwiftNativeNSError cannot be instantiated"); } diff --git a/stdlib/public/runtime/MetadataImpl.h b/stdlib/public/runtime/MetadataImpl.h index e518247bb3c..c3f15b2deb4 100644 --- a/stdlib/public/runtime/MetadataImpl.h +++ b/stdlib/public/runtime/MetadataImpl.h @@ -44,6 +44,9 @@ #include "swift/Runtime/Config.h" #include "swift/Runtime/Metadata.h" #include "swift/Runtime/HeapObject.h" +#if SWIFT_OBJC_INTEROP +#include "swift/Runtime/ObjCBridge.h" +#endif #include #include @@ -373,20 +376,17 @@ struct SwiftWeakRetainableBox : }; #if SWIFT_OBJC_INTEROP -extern "C" void *objc_retain(void *obj); -extern "C" void objc_release(void *obj); - /// A box implementation class for Objective-C object pointers. struct ObjCRetainableBox : RetainableBoxBase { static constexpr unsigned numExtraInhabitants = swift_getHeapObjectExtraInhabitantCount(); static void *retain(void *obj) { - return objc_retain(obj); + return objc_retain((id)obj); } static void release(void *obj) { - objc_release(obj); + objc_release((id)obj); } }; diff --git a/stdlib/public/runtime/SwiftObject.h b/stdlib/public/runtime/SwiftObject.h new file mode 100644 index 00000000000..c9a5217fe84 --- /dev/null +++ b/stdlib/public/runtime/SwiftObject.h @@ -0,0 +1,90 @@ +//===--- SwiftObject.h - Native Swift Object root class -------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This implements the Objective-C root class that provides basic `id`- +// compatibility and `NSObject` protocol conformance for pure Swift classes. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_RUNTIME_SWIFTOBJECT_H +#define SWIFT_RUNTIME_SWIFTOBJECT_H + +#include "swift/Runtime/Config.h" +#include +#include +#include "swift/Runtime/HeapObject.h" +#if SWIFT_OBJC_INTEROP +#include +#endif + +namespace swift { + +#if SWIFT_OBJC_INTEROP +struct SwiftObject_s { + void *isa __attribute__((__unavailable__)); + uint32_t strongRefCount __attribute__((__unavailable__)); + uint32_t weakRefCount __attribute__((__unavailable__)); +}; + +static_assert(sizeof(SwiftObject_s) == sizeof(HeapObject), + "SwiftObject and HeapObject must have the same header"); +static_assert(std::is_trivially_constructible::value, + "SwiftObject must be trivially constructible"); +static_assert(std::is_trivially_destructible::value, + "SwiftObject must be trivially destructible"); + +} // namespace swift + +#if __has_attribute(objc_root_class) +__attribute__((__objc_root_class__)) +#endif +SWIFT_RUNTIME_EXPORT @interface SwiftObject { + swift::SwiftObject_s header; +} + +- (BOOL)isEqual:(id)object; +- (NSUInteger)hash; + +- (Class)superclass; +- (Class)class; +- (instancetype)self; +- (struct _NSZone *)zone; + +- (id)performSelector:(SEL)aSelector; +- (id)performSelector:(SEL)aSelector withObject:(id)object; +- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2; + +- (BOOL)isProxy; + ++ (BOOL)isSubclassOfClass:(Class)aClass; +- (BOOL)isKindOfClass:(Class)aClass; +- (BOOL)isMemberOfClass:(Class)aClass; +- (BOOL)conformsToProtocol:(Protocol *)aProtocol; + +- (BOOL)respondsToSelector:(SEL)aSelector; + +- (instancetype)retain; +- (oneway void)release; +- (instancetype)autorelease; +- (NSUInteger)retainCount; + +- (NSString *)description; +- (NSString *)debugDescription; +@end + +namespace swift { + +#endif + +} + +#endif diff --git a/stdlib/public/runtime/SwiftObject.mm b/stdlib/public/runtime/SwiftObject.mm index 61200513ebe..fab401a844c 100644 --- a/stdlib/public/runtime/SwiftObject.mm +++ b/stdlib/public/runtime/SwiftObject.mm @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// // -// This implements runtime support for bridging between Swift and Objective-C -// types in cases where they aren't trivial. +// This implements the Objective-C root class that provides basic `id`- +// compatibility and `NSObject` protocol conformance for pure Swift classes. // //===----------------------------------------------------------------------===// @@ -33,6 +33,7 @@ #include "swift/Strings.h" #include "../SwiftShims/RuntimeShims.h" #include "Private.h" +#include "SwiftObject.h" #include "swift/Runtime/Debug.h" #if SWIFT_OBJC_INTEROP #include @@ -76,56 +77,6 @@ const ClassMetadata *swift::_swift_getClass(const void *object) { } #if SWIFT_OBJC_INTEROP -struct SwiftObject_s { - void *isa __attribute__((__unavailable__)); - uint32_t strongRefCount __attribute__((__unavailable__)); - uint32_t weakRefCount __attribute__((__unavailable__)); -}; - -static_assert(sizeof(SwiftObject_s) == sizeof(HeapObject), - "SwiftObject and HeapObject must have the same header"); -static_assert(std::is_trivially_constructible::value, - "SwiftObject must be trivially constructible"); -static_assert(std::is_trivially_destructible::value, - "SwiftObject must be trivially destructible"); - -#if __has_attribute(objc_root_class) -__attribute__((__objc_root_class__)) -#endif -SWIFT_RUNTIME_EXPORT @interface SwiftObject { - SwiftObject_s header; -} - -- (BOOL)isEqual:(id)object; -- (NSUInteger)hash; - -- (Class)superclass; -- (Class)class; -- (instancetype)self; -- (struct _NSZone *)zone; - -- (id)performSelector:(SEL)aSelector; -- (id)performSelector:(SEL)aSelector withObject:(id)object; -- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2; - -- (BOOL)isProxy; - -+ (BOOL)isSubclassOfClass:(Class)aClass; -- (BOOL)isKindOfClass:(Class)aClass; -- (BOOL)isMemberOfClass:(Class)aClass; -- (BOOL)conformsToProtocol:(Protocol *)aProtocol; - -- (BOOL)respondsToSelector:(SEL)aSelector; - -- (instancetype)retain; -- (oneway void)release; -- (instancetype)autorelease; -- (NSUInteger)retainCount; - -- (NSString *)description; -- (NSString *)debugDescription; -@end - static SwiftObject *_allocHelper(Class cls) { // XXX FIXME // When we have layout information, do precise alignment rounding diff --git a/stdlib/public/runtime/SwiftValue.h b/stdlib/public/runtime/SwiftValue.h new file mode 100644 index 00000000000..cce3b3efb98 --- /dev/null +++ b/stdlib/public/runtime/SwiftValue.h @@ -0,0 +1,59 @@ +//===--- SwiftValue.h - Boxed Swift value class ---------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This implements the Objective-C class that is used to carry Swift values +// that have been bridged to Objective-C objects without special handling. +// The class is opaque to user code, but is `NSObject`- and `NSCopying`- +// conforming and is understood by the Swift runtime for dynamic casting +// back to the contained type. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_RUNTIME_SWIFTVALUE_H +#define SWIFT_RUNTIME_SWIFTVALUE_H + +#if SWIFT_OBJC_INTEROP +#include +#endif + +// SwiftValue is an Objective-C class, but we shouldn't interface with it +// directly as such. Keep the type opaque. +#if __OBJC__ +@class SwiftValue; +#else +typedef struct SwiftValue SwiftValue; +#endif + +namespace swift { + +/// Bridge a Swift value to an Objective-C object by boxing it as a SwiftValue. +SwiftValue *bridgeAnythingToSwiftValueObject(OpaqueValue *src, + const Metadata *srcType, + bool consume); + +/// Get the type metadata for a value in a Swift box. +const Metadata *getSwiftValueTypeMetadata(SwiftValue *v); + +/// Get the value out of a Swift box along with its type metadata. The value +/// inside the box is immutable and must not be modified or taken from the box. +std::pair +getValueFromSwiftValue(SwiftValue *v); + +#if SWIFT_OBJC_INTEROP +/// Return the object reference as a SwiftValue* if it is a SwiftValue instance, +/// or nil if it is not. +SwiftValue *getAsSwiftValue(id object); +#endif + +} + +#endif diff --git a/stdlib/public/runtime/SwiftValue.mm b/stdlib/public/runtime/SwiftValue.mm new file mode 100644 index 00000000000..061785d7415 --- /dev/null +++ b/stdlib/public/runtime/SwiftValue.mm @@ -0,0 +1,199 @@ +//===--- SwiftValue.mm - Boxed Swift value class --------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This implements the Objective-C class that is used to carry Swift values +// that have been bridged to Objective-C objects without special handling. +// The class is opaque to user code, but is `NSObject`- and `NSCopying`- +// conforming and is understood by the Swift runtime for dynamic casting +// back to the contained type. +// +//===----------------------------------------------------------------------===// + +#include "SwiftObject.h" +#include "SwiftValue.h" +#include "swift/Basic/Lazy.h" +#include "swift/Runtime/HeapObject.h" +#include "swift/Runtime/Metadata.h" +#include "swift/Runtime/ObjCBridge.h" +#include "swift/Runtime/Debug.h" +#include "Private.h" +#include +#include + +#if !SWIFT_OBJC_INTEROP +#error "This file should only be compiled when ObjC interop is enabled." +#endif + +using namespace swift; + +// TODO: Making this a SwiftObject subclass would let us use Swift refcounting, +// but we would need to be able to emit SwiftValue's Objective-C class object +// with the Swift destructor pointer prefixed before it. +@interface SwiftValue : NSObject + +- (id)copyWithZone:(NSZone *)zone; + +@end + +static constexpr const size_t SwiftValueMetadataOffset + = sizeof(Class); // isa pointer +static constexpr const size_t SwiftValueMinAlignMask + = alignof(Class) - 1; +/* TODO: If we're able to become a SwiftObject subclass in the future, + * change to this: +static constexpr const size_t SwiftValueMetadataOffset + = sizeof(SwiftObject_s); +static constexpr const size_t SwiftValueMinAlignMask + = alignof(SwiftObject_s) - 1; + */ + +static Class _getSwiftValueClass() { + auto theClass = [SwiftValue class]; + // Fixed instance size of SwiftValue should be same as object header. + assert(class_getInstanceSize(theClass) == SwiftValueMetadataOffset + && "unexpected size of SwiftValue?!"); + return theClass; +} + +static Class getSwiftValueClass() { + return SWIFT_LAZY_CONSTANT(_getSwiftValueClass()); +} + +static constexpr size_t getSwiftValueOffset(size_t alignMask) { + return SwiftValueMetadataOffset + sizeof(const Metadata *) + + alignMask & ~alignMask; +} + +const Metadata *swift::getSwiftValueTypeMetadata(SwiftValue *v) { + auto instanceBytes = reinterpret_cast(v); + const Metadata *result; + memcpy(&result, instanceBytes + SwiftValueMetadataOffset, + sizeof(result)); + return result; +} + +std::pair +swift::getValueFromSwiftValue(SwiftValue *v) { + auto instanceBytes = reinterpret_cast(v); + auto instanceType = getSwiftValueTypeMetadata(v); + size_t alignMask = instanceType->getValueWitnesses()->getAlignmentMask() + | SwiftValueMinAlignMask; + auto instanceOffset = getSwiftValueOffset(alignMask); + auto value = reinterpret_cast( + instanceBytes + instanceOffset); + return {instanceType, value}; +} + +SwiftValue *swift::bridgeAnythingToSwiftValueObject(OpaqueValue *src, + const Metadata *srcType, + bool consume) { + Class SwiftValueClass = getSwiftValueClass(); + + // We lay out the metadata after the object header, and the value after + // the metadata (rounded up to alignment). + size_t alignMask = srcType->getValueWitnesses()->getAlignmentMask() + | SwiftValueMinAlignMask; + size_t valueOffset = SwiftValueMetadataOffset + sizeof(const Metadata *) + + alignMask & ~alignMask; + + size_t totalSize = valueOffset + srcType->getValueWitnesses()->size; + + void *instanceMemory = swift_slowAlloc(totalSize, alignMask); + SwiftValue *instance + = objc_constructInstance(SwiftValueClass, instanceMemory); + /* TODO: If we're able to become a SwiftObject subclass in the future, + * change to this: + auto instance = swift_allocObject(SwiftValueClass, totalSize, alignMask); + */ + + auto instanceBytes = reinterpret_cast(instance); + memcpy(instanceBytes + SwiftValueMetadataOffset, &srcType, + sizeof(const Metadata*)); + auto instanceValue = reinterpret_cast( + instanceBytes + valueOffset); + + if (consume) + srcType->vw_initializeWithTake(instanceValue, src); + else + srcType->vw_initializeWithCopy(instanceValue, src); + + return instance; +} + +SwiftValue *swift::getAsSwiftValue(id object) { + // SwiftValue should have no subclasses or proxies. We can do an exact + // class check. + if (object_getClass(object) == getSwiftValueClass()) + return object; + return nil; +} + +@implementation SwiftValue + ++ (instancetype)allocWithZone:(NSZone *)zone { + swift::crash("SwiftValue cannot be instantiated"); +} + +- (id)copyWithZone:(NSZone *)zone { + // Instances are immutable, so we can just retain. + return objc_retain(self); + + /* TODO: If we're able to become a SwiftObject subclass in the future, + * change to this: + swift_retain((HeapObject*)self); + return self; + */ +} + +// Since we allocate using Swift's allocator to properly handle alignment, +// we need to deallocate ourselves instead of delegating to +// -[NSObject dealloc]. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" +- (void)dealloc { + // TODO: If we're able to become a SwiftObject subclass in the future, + // this should move to the heap metadata destructor function. + + // Destroy the contained value. + auto instanceBytes = reinterpret_cast(self); + auto instanceType = getSwiftValueTypeMetadata(self); + size_t alignMask = instanceType->getValueWitnesses()->getAlignmentMask() + | SwiftValueMinAlignMask; + auto instanceOffset = getSwiftValueOffset(alignMask); + auto value = reinterpret_cast( + instanceBytes + instanceOffset); + instanceType->vw_destroy(value); + + // Deallocate ourselves. + objc_destructInstance(self); + auto totalSize = instanceOffset = instanceType->getValueWitnesses()->size; + swift_slowDealloc(self, totalSize, alignMask); +} +#pragma clang diagnostic pop + +// Private methods for debugging purposes. + +- (const Metadata *)_swiftTypeMetadata { + return getSwiftValueTypeMetadata(self); +} +- (const OpaqueValue *)_swiftValue { + return getValueFromSwiftValue(self).second; +} + +// TODO: Forward description, debugDescription, isEqual:, hash, etc. to +// corresponding operations on the boxed Swift type. + +@end + +// TODO: We could pick specialized SwiftValue subclasses for trivial types +// or for types with known size and alignment characteristics. Probably +// not enough of a real perf bottleneck to be worth it... diff --git a/test/1_stdlib/BridgeIdAsAny.swift.gyb b/test/1_stdlib/BridgeIdAsAny.swift.gyb index 052d271399c..ddb942ed770 100644 --- a/test/1_stdlib/BridgeIdAsAny.swift.gyb +++ b/test/1_stdlib/BridgeIdAsAny.swift.gyb @@ -1,7 +1,7 @@ // RUN: rm -rf %t && mkdir %t // // RUN: %gyb %s -o %t/BridgeIdAsAny.swift -// RUN: %target-build-swift -module-name a %t/BridgeIdAsAny.swift -o %t.out +// RUN: %target-build-swift -g -module-name a %t/BridgeIdAsAny.swift -o %t.out // RUN: %target-run %t.out // REQUIRES: executable_test // @@ -20,28 +20,95 @@ func wantonlyWrapInAny(_ x: T) -> Any { extension LifetimeTracked: Error {} extension String: Error {} -% for testName, valueExpr, testExpr in [("classes", "LifetimeTracked(0)", " === x"), ("strings", '"vitameatavegamin"', '.isEqual(to: "vitameatavegamin")')]: +struct KnownUnbridged: Equatable, Error { + var x, y: LifetimeTracked + + init() { + x = LifetimeTracked(17) + y = LifetimeTracked(38) + } +} +func ==(a: KnownUnbridged, b: KnownUnbridged) -> Bool { + return a.x === b.x && a.y === b.y +} + +func bridgedObjectPreservesIdentity(original: LifetimeTracked, + bridged: AnyObject) { + expectTrue(original === bridged) +} + +func stringBridgesToEqualNSString(original: String, + bridged: AnyObject) { + expectTrue(bridged.isEqual(to: original)) +} + +func boxedTypeRoundTripsThroughDynamicCasting(original: KnownUnbridged, + bridged: AnyObject) { + direct: do { + guard let bridgedAndCast = bridged as? KnownUnbridged else { + expectUnreachable() + break direct + } + expectEqual(original, bridgedAndCast) + } + + let bridgedAny: Any = bridged + any: do { + guard let bridgedAndCastAny = bridgedAny as? KnownUnbridged else { + expectUnreachable() + break any + } + expectEqual(original, bridgedAndCastAny) + } + + anyInAny: do { + let bridgedAnyInAny = wantonlyWrapInAny(bridgedAny) + guard let bridgedAndCastAnyInAny = bridgedAnyInAny as? KnownUnbridged else { + expectUnreachable() + break anyInAny + } + expectEqual(original, bridgedAndCastAnyInAny) + } + + // Failed casts should fail, and hopefully shouldn't leak or corrupt memory + // either. + expectEqual(bridged as? Int, nil) + expectEqual(bridged as? String, nil) +} + +// We want to exhaustively check all paths through the bridging and dynamic +// casting infrastructure, so expand out test cases that wrap the different +// interesting bridging cases in different kinds of existential container. +%{ +testCases = [ + ("classes", "LifetimeTracked(0)", "bridgedObjectPreservesIdentity"), + ("strings", '"vitameatavegamin"', "stringBridgesToEqualNSString"), + ("unbridged type", "KnownUnbridged()", "boxedTypeRoundTripsThroughDynamicCasting"), +] +}% + +% for testName, valueExpr, testFunc in testCases: BridgeAnything.test("${testName}") { do { let x = ${valueExpr} - expectTrue(_bridgeAnythingToObjectiveC(x)${testExpr}) - expectTrue(_bridgeAnythingNonVerbatimToObjectiveC(x)${testExpr}) + ${testFunc}(original: x, bridged: _bridgeAnythingToObjectiveC(x)) + ${testFunc}(original: x, bridged: _bridgeAnythingNonVerbatimToObjectiveC(x)) let xInAny: Any = x - expectTrue(_bridgeAnythingToObjectiveC(xInAny)${testExpr}) - expectTrue(_bridgeAnythingNonVerbatimToObjectiveC(xInAny)${testExpr}) + ${testFunc}(original: x, bridged: _bridgeAnythingToObjectiveC(xInAny)) + ${testFunc}(original: x, bridged: _bridgeAnythingNonVerbatimToObjectiveC(xInAny)) let xInAnyInAny = wantonlyWrapInAny(xInAny) - expectTrue(_bridgeAnythingToObjectiveC(xInAnyInAny)${testExpr}) - expectTrue(_bridgeAnythingNonVerbatimToObjectiveC(xInAnyInAny)${testExpr}) + ${testFunc}(original: x, bridged: _bridgeAnythingToObjectiveC(xInAnyInAny)) + ${testFunc}(original: x, bridged: _bridgeAnythingNonVerbatimToObjectiveC(xInAnyInAny)) let xInError: Error = x - expectTrue(_bridgeAnythingToObjectiveC(xInError)${testExpr}) - expectTrue(_bridgeAnythingNonVerbatimToObjectiveC(xInError)${testExpr}) + ${testFunc}(original: x, bridged: _bridgeAnythingToObjectiveC(xInError)) + ${testFunc}(original: x, bridged: _bridgeAnythingNonVerbatimToObjectiveC(xInError)) let xInErrorInAny = wantonlyWrapInAny(xInError) - expectTrue(_bridgeAnythingToObjectiveC(xInErrorInAny)${testExpr}) - expectTrue(_bridgeAnythingNonVerbatimToObjectiveC(xInErrorInAny)${testExpr}) + ${testFunc}(original: x, bridged: _bridgeAnythingToObjectiveC(xInErrorInAny)) + ${testFunc}(original: x, bridged: _bridgeAnythingNonVerbatimToObjectiveC(xInErrorInAny)) } expectEqual(0, LifetimeTracked.instances)