[Idea] ObjectiveCBridgeable


(Russ Bishop) #1

I wanted to float a proposal and see what the community thinks. If there is interest I’m happy to submit an official proposal. This comes from real problems we’re encountering with a large mixed codebase. Every time our nice clean Swift design hits an Objective-C boundary we end up having to go back and dumb-down the Swift API significantly because manually converting at every touch point is just too painful.

# Introduction

Provide an ObjectiveCBridgeable protocol that allows a type to control how it is represented in Objective-C by converting into and back from an entirely separate type. This frees an API designer to create truly native Swift types like structs, generics, enums with associated values, protocols with associated types, etc without having to compromise to allow interop with Objective-C.

# Motivation

When working in a mixed codebase, we find ourselves unable to use generics, enums with associated values, structs, and various other Swift features because there are chunks of Objective-C code that we can’t stop and rewrite. This leads to a catch-22 situation where your new APIs (or refactored APIs) have a compromised design, which has a cascading effect on new Swift code that uses the compromised API. When the last vestiges of Objective-C have been swept away you’re left with a mountain of Swift code that essentially looks like a direct port of Objective-C code and doesn’t take advantage of any Swift-ish features.

# Proposed Solution

Today, you can adopt the private protocol _ObjectiveCBridgeable and if your parameters or return types are inside an Array, Swift will automatically call the appropriate functions to let you control the way the type bridges. This allows you to define a completely different Objective-C compatible API for the type and round-trip it appropriately.

My proposal is simple: expose the existing protocol as a public protocol ObjectiveCBridgeable: _ObjectiveCBridgeable, and have the compiler generate the appropriate Objective-C bridging outside of the existing Array support.

# Detailed Design

1. Expose the existing protocol as a public protocol
2. When generating an Objective-C interface for a type that is itself bridgeable to Objective-C:
  a. When a function contains parameters or return types that are @nonobjc but those types adopt ObjectiveCBridgeable:
    i) Create @objc overloads that call the Swift functions but substitute the corresponding ObjectiveCType.
    ii) The overloads will automatically call the appropriate protocol functions to perform the conversion.
  b. If any @nonobjc types do not adopt ObjectiveCBridgeable, the function itself is not exposed to Objective-C (current behavior).
3. It is up to the API designer to decide if they want to allow construction of wholly new values from Objective-C and how to handle convertibility back to the Swift type in that case

An example enum that adopts the protocol and bridges by converting itself into an object representation.
Note: The ways you can represent the type in Objective-C are endless; I’d prefer not to bikeshed that particular bit :slight_smile:

enum Fizzer: ObjectiveCBridgeable {
    case Case1(String)
    case Case2(Int, Int)

    static func getObjectiveCType() -> Any.Type {
        return ObjCFizzer.self
    }
    static func isBridgedToObjectiveC() -> Bool {
        return true
    }
    func bridgeToObjectiveC() -> ObjCFizzer {
        let bridge = ObjCFizzer()
        switch self {
        case let .Case1(x):
            bridge._case1 = x
        case let .Case2(x, y):
            bridge._case2 = (x, y)
        }
        return bridge
    }
    static func conditionallyBridgeFromObjectiveC(source: ObjCFizzer, inout result: Fizzer?) -> Bool {
        _forceBridgeFromObjectiveC(source, result: &result)
        return true
    }
    static func forceBridgeFromObjectiveC(source: ObjCFizzer, inout result: Fizzer?) {
        if let stringValue = source._case1 {
            result = Fizzer.Case1(stringValue)
        } else if let tupleValue = source._case2 {
            result = Fizzer.Case2(tupleValue)
        } else {
            fatalError("Unable to bridge")
        }
    }
}

class ObjCFizzer: NSObject {
    private var _case1: String?
    private var _case2: (Int, Int)?
}

# Impact on existing code

No breaking changes. Adoption would be opt-in.

# Alternatives considered

The only alternative, as stated above, is not to adopt Swift features that cannot be expressed in Objective-C.

# TBD / Questions:

Should the shape of the protocol change? It may not make sense for the public protocol to support both conditional and force bridging; maybe we only provide a throwing equivalent of force bridge and let the implementer throw if the conversion is invalid?

Russ


(Radek Pietruszewski) #2

Yes! I’ve had the privilege of being able to write Swift nearly ObjC-free, so I don’t feel the pain personally very often. But I’ve played with _ObjectiveCBridgeable in the past and thought it’s an awesome little hidden gem. Aside from other things, being able to use value types in Swift without compromising ObjC bridgeability is very useful, for library code in particular.

— Radek

···

On 25 Feb 2016, at 20:02, Russ Bishop via swift-evolution <swift-evolution@swift.org> wrote:

I wanted to float a proposal and see what the community thinks. If there is interest I’m happy to submit an official proposal. This comes from real problems we’re encountering with a large mixed codebase. Every time our nice clean Swift design hits an Objective-C boundary we end up having to go back and dumb-down the Swift API significantly because manually converting at every touch point is just too painful.

# Introduction

Provide an ObjectiveCBridgeable protocol that allows a type to control how it is represented in Objective-C by converting into and back from an entirely separate type. This frees an API designer to create truly native Swift types like structs, generics, enums with associated values, protocols with associated types, etc without having to compromise to allow interop with Objective-C.

# Motivation

When working in a mixed codebase, we find ourselves unable to use generics, enums with associated values, structs, and various other Swift features because there are chunks of Objective-C code that we can’t stop and rewrite. This leads to a catch-22 situation where your new APIs (or refactored APIs) have a compromised design, which has a cascading effect on new Swift code that uses the compromised API. When the last vestiges of Objective-C have been swept away you’re left with a mountain of Swift code that essentially looks like a direct port of Objective-C code and doesn’t take advantage of any Swift-ish features.

# Proposed Solution

Today, you can adopt the private protocol _ObjectiveCBridgeable and if your parameters or return types are inside an Array, Swift will automatically call the appropriate functions to let you control the way the type bridges. This allows you to define a completely different Objective-C compatible API for the type and round-trip it appropriately.

My proposal is simple: expose the existing protocol as a public protocol ObjectiveCBridgeable: _ObjectiveCBridgeable, and have the compiler generate the appropriate Objective-C bridging outside of the existing Array support.

# Detailed Design

1. Expose the existing protocol as a public protocol
2. When generating an Objective-C interface for a type that is itself bridgeable to Objective-C:
a. When a function contains parameters or return types that are @nonobjc but those types adopt ObjectiveCBridgeable:
   i) Create @objc overloads that call the Swift functions but substitute the corresponding ObjectiveCType.
   ii) The overloads will automatically call the appropriate protocol functions to perform the conversion.
b. If any @nonobjc types do not adopt ObjectiveCBridgeable, the function itself is not exposed to Objective-C (current behavior).
3. It is up to the API designer to decide if they want to allow construction of wholly new values from Objective-C and how to handle convertibility back to the Swift type in that case

An example enum that adopts the protocol and bridges by converting itself into an object representation.
Note: The ways you can represent the type in Objective-C are endless; I’d prefer not to bikeshed that particular bit :slight_smile:

enum Fizzer: ObjectiveCBridgeable {
   case Case1(String)
   case Case2(Int, Int)

   static func getObjectiveCType() -> Any.Type {
       return ObjCFizzer.self
   }
   static func isBridgedToObjectiveC() -> Bool {
       return true
   }
   func bridgeToObjectiveC() -> ObjCFizzer {
       let bridge = ObjCFizzer()
       switch self {
       case let .Case1(x):
           bridge._case1 = x
       case let .Case2(x, y):
           bridge._case2 = (x, y)
       }
       return bridge
   }
   static func conditionallyBridgeFromObjectiveC(source: ObjCFizzer, inout result: Fizzer?) -> Bool {
       _forceBridgeFromObjectiveC(source, result: &result)
       return true
   }
   static func forceBridgeFromObjectiveC(source: ObjCFizzer, inout result: Fizzer?) {
       if let stringValue = source._case1 {
           result = Fizzer.Case1(stringValue)
       } else if let tupleValue = source._case2 {
           result = Fizzer.Case2(tupleValue)
       } else {
           fatalError("Unable to bridge")
       }
   }
}

class ObjCFizzer: NSObject {
   private var _case1: String?
   private var _case2: (Int, Int)?
}

# Impact on existing code

No breaking changes. Adoption would be opt-in.

# Alternatives considered

The only alternative, as stated above, is not to adopt Swift features that cannot be expressed in Objective-C.

# TBD / Questions:

Should the shape of the protocol change? It may not make sense for the public protocol to support both conditional and force bridging; maybe we only provide a throwing equivalent of force bridge and let the implementer throw if the conversion is invalid?

Russ

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Douglas Gregor) #3

Hi Russ,

Sorry for the long delay in replying. This is a topic of interest to me.

I wanted to float a proposal and see what the community thinks. If there is interest I’m happy to submit an official proposal. This comes from real problems we’re encountering with a large mixed codebase. Every time our nice clean Swift design hits an Objective-C boundary we end up having to go back and dumb-down the Swift API significantly because manually converting at every touch point is just too painful.

# Introduction

Provide an ObjectiveCBridgeable protocol that allows a type to control how it is represented in Objective-C by converting into and back from an entirely separate type. This frees an API designer to create truly native Swift types like structs, generics, enums with associated values, protocols with associated types, etc without having to compromise to allow interop with Objective-C.

There may be some limitations that are inherent to the bridging process, so it’s not clear that we can achieve everything you mention above. As an example, check out my proposal to bridge Objective-C’s lightweight generics into Swift:

  http://thread.gmane.org/gmane.comp.lang.swift.evolution/2886

The content of that proposal isn’t as important as the scope of implementation effort to bridge between two similar-looking but very-differently-implemented features across the languages. It’s likely that other features you’ve mentioned above have similar complexities. That said, I do think it’s possible to extend the bridging mechanism to help alleviate some of the issues you’re describing.

# Motivation

When working in a mixed codebase, we find ourselves unable to use generics, enums with associated values, structs, and various other Swift features because there are chunks of Objective-C code that we can’t stop and rewrite. This leads to a catch-22 situation where your new APIs (or refactored APIs) have a compromised design, which has a cascading effect on new Swift code that uses the compromised API. When the last vestiges of Objective-C have been swept away you’re left with a mountain of Swift code that essentially looks like a direct port of Objective-C code and doesn’t take advantage of any Swift-ish features.

# Proposed Solution

Today, you can adopt the private protocol _ObjectiveCBridgeable and if your parameters or return types are inside an Array, Swift will automatically call the appropriate functions to let you control the way the type bridges. This allows you to define a completely different Objective-C compatible API for the type and round-trip it appropriately.

The other bridged collection types (Set/Dictionary) also work this way as well.

My proposal is simple: expose the existing protocol as a public protocol ObjectiveCBridgeable: _ObjectiveCBridgeable, and have the compiler generate the appropriate Objective-C bridging outside of the existing Array support.

# Detailed Design

1. Expose the existing protocol as a public protocol
2. When generating an Objective-C interface for a type that is itself bridgeable to Objective-C:
a. When a function contains parameters or return types that are @nonobjc but those types adopt ObjectiveCBridgeable:
   i) Create @objc overloads that call the Swift functions but substitute the corresponding ObjectiveCType.
   ii) The overloads will automatically call the appropriate protocol functions to perform the conversion.

This is *roughly* what the compiler does today for the various bridged types (Array/Dictionary/Set/String): the function with the Swift calling convention uses the Swift-native types, and the compiler emits an @objc thunk that translates the Objective-C-visible types into the Swift-native types and back.

b. If any @nonobjc types do not adopt ObjectiveCBridgeable, the function itself is not exposed to Objective-C (current behavior).

Right.

3. It is up to the API designer to decide if they want to allow construction of wholly new values from Objective-C and how to handle convertibility back to the Swift type in that case

I’m not sure what you mean by this?

An example enum that adopts the protocol and bridges by converting itself into an object representation.
Note: The ways you can represent the type in Objective-C are endless; I’d prefer not to bikeshed that particular bit :slight_smile:

enum Fizzer: ObjectiveCBridgeable {
   case Case1(String)
   case Case2(Int, Int)

   static func getObjectiveCType() -> Any.Type {
       return ObjCFizzer.self
   }

FWIW, this method is an anachronism. You can leave it out, because the runtime can now recover this information directly. However, I do suggest that you show

  typealias ObjectiveCType = ObjCFizzer

in your example, so its clear what the mapping is.

   static func isBridgedToObjectiveC() -> Bool {
       return true
   }

   func bridgeToObjectiveC() -> ObjCFizzer {
       let bridge = ObjCFizzer()
       switch self {
       case let .Case1(x):
           bridge._case1 = x
       case let .Case2(x, y):
           bridge._case2 = (x, y)
       }
       return bridge
   }

Okay.

   static func conditionallyBridgeFromObjectiveC(source: ObjCFizzer, inout result: Fizzer?) -> Bool {
       _forceBridgeFromObjectiveC(source, result: &result)
       return true
   }

The above isn’t a correct implementation, because it will fatalError return than returning “nil” if the source cannot be bridged.

   static func forceBridgeFromObjectiveC(source: ObjCFizzer, inout result: Fizzer?) {
       if let stringValue = source._case1 {
           result = Fizzer.Case1(stringValue)
       } else if let tupleValue = source._case2 {
           result = Fizzer.Case2(tupleValue)
       } else {
           fatalError("Unable to bridge")
       }
   }
}

class ObjCFizzer: NSObject {
   private var _case1: String?
   private var _case2: (Int, Int)?
}

I had expected ObjCFizzer to be defined in Objective-C. If it can also be defined in Swift, that’s a nontrivial expansion of what bridging currently does.

Along these lines, I think we’ll need a Clang-side attribute on the Objective-C type to specify what Swift type it bridges to. That’s important for Swift’s Clang importer, because when it sees the Clang type it needs to know whether it’s importing that type directly or bridging to a different type.

# TBD / Questions:

Should the shape of the protocol change?

I think the protocol is still basically the right interface, except that getObjectiveCType is no longer necessary. How Objective-C-centric should this protocol be? Should it be possible for (e.g.) the NSInteger-to-Int mapping to be expressible this way? Can a simple C struct be bridged?

It may not make sense for the public protocol to support both conditional and force bridging; maybe we only provide a throwing equivalent of force bridge and let the implementer throw if the conversion is invalid?

The distinction between forced and conditional bridging is fairly important for performance. Forced bridging is used for “as!” while conditional bridging is used for “as?”; when we know we’re in the former case, we can sometimes delay validation work.

From a proposal perspective, I think it’s important to show the ObjectiveCBridgeable protocol you’re proposing and say what each requirement does. You can grab them directly from the comments, of course, but I suspect that most people haven’t dug into bridging at all.

If it’s not obvious already, I think this is definitely worthy of a proposal.

  - Doug

···

On Feb 25, 2016, at 11:02 AM, Russ Bishop via swift-evolution <swift-evolution@swift.org> wrote:


(Russ Bishop) #4

An official proposal PR has been opened on this: https://github.com/apple/swift-evolution/pull/198

It includes clarifications, a few changes, and some better examples.

Russ


(Russ Bishop) #5

There may be some limitations that are inherent to the bridging process, so it’s not clear that we can achieve everything you mention above. As an example, check out my proposal to bridge Objective-C’s lightweight generics into Swift:

  http://thread.gmane.org/gmane.comp.lang.swift.evolution/2886

The content of that proposal isn’t as important as the scope of implementation effort to bridge between two similar-looking but very-differently-implemented features across the languages. It’s likely that other features you’ve mentioned above have similar complexities. That said, I do think it’s possible to extend the bridging mechanism to help alleviate some of the issues you’re describing.

Well the idea is that real bridging is impossible in many cases, so let the Swift type define an arbitrarily different type to represent itself in the world of Objective-C. In some cases this may behave like an opaque value that Objective-C code can’t touch, merely pass around. In others (eg structs/enums) a simpler less type-safe class interface can be provided. Or perhaps enum cases with associated values can map to a class cluster.

The important part is that the implementer is completely free to design a native Swift API for a feature without actually having to maintain a separate API and test suite.

3. It is up to the API designer to decide if they want to allow construction of wholly new values from Objective-C and how to handle convertibility back to the Swift type in that case

I’m not sure what you mean by this?

If I define a class (ObjCFizzer) I can choose to keep the initializer private meaning no one can create an instance from Objective-C. I only wanted to mention that explicitly because I can imagine a library author’s objections. In reality this rule just falls out naturally from private/internal visibility anyway.

FWIW, this method is an anachronism. You can leave it out, because the runtime can now recover this information directly. However, I do suggest that you show

  typealias ObjectiveCType = ObjCFizzer

in your example, so its clear what the mapping is.

Ah thanks, I’ll update it.

The above isn’t a correct implementation, because it will fatalError return than returning “nil” if the source cannot be bridged.

You’re correct, I meant to do it the other way around. I’ll fix it.

I had expected ObjCFizzer to be defined in Objective-C. If it can also be defined in Swift, that’s a nontrivial expansion of what bridging currently does.

Along these lines, I think we’ll need a Clang-side attribute on the Objective-C type to specify what Swift type it bridges to. That’s important for Swift’s Clang importer, because when it sees the Clang type it needs to know whether it’s importing that type directly or bridging to a different type.

First, my assumption is that a type can’t be recursively bridged (the ObjectiveCType cannot itself be bridgeable - the compiler could emit a diagnostic for that).

I guess my second assumption is that the thunk generation happens after the Swift compiler has discovered all the types in the module so whether the ObjectiveCType is defined in Swift or Objective-C doesn’t matter. This may not be true. To be honest I hadn’t considered defining the bridge types in the other direction (an Objective-C API that wants to present a nicer Swift API) but I can see how that would be very useful.

One of the benefits of being able to define the bridge type in Swift is that the bridge type can be a simple @objc type that contains stored properties any arbitrary Swift type. If defining the type in Swift has nontrivial implementation issues we can require the bridged type be defined in Objective-C but it would be nice if we didn’t have to do that.

Good point on the Clang attribute.

# TBD / Questions:

Should the shape of the protocol change?

I think the protocol is still basically the right interface, except that getObjectiveCType is no longer necessary. How Objective-C-centric should this protocol be? Should it be possible for (e.g.) the NSInteger-to-Int mapping to be expressible this way? Can a simple C struct be bridged?

In the case of C structs or NS_ENUM, Swift extensions already provide much of the value IMHO. I haven’t actually looked at how the NSInteger typedef gets mapped to Int; do you see value there?

For me the primary motivation is when I’m doing the actual implementation of a feature in Swift, then providing a less type-safe non-generics interface to Objective-C. I’m also looking to a future where we can export free functions to C. Bi-directional exchange with C++ (assuming the committee adopts something like “extern abi” someday) is an interesting case but far out of scope.

From a proposal perspective, I think it’s important to show the ObjectiveCBridgeable protocol you’re proposing and say what each requirement does. You can grab them directly from the comments, of course, but I suspect that most people haven’t dug into bridging at all.

If it’s not obvious already, I think this is definitely worthy of a proposal.

  - Doug

I’ll write up some of these changes and open a PR.

Russ

···

On Mar 7, 2016, at 10:36 AM, Douglas Gregor <dgregor@apple.com> wrote:


(Adrian Kashivskyy) #6

I have a couple of ideas regarding the API of ObjectiveCBridgeable. My general thought is to improve upon the existing _ObjectiveCBridgeable protocol, not just make it public. The main idea would be to make it more "Swifty" by:

1. Removing inout arguments which look like implementation artifacts and converting them into return values
2. Converting static functions to initializers
3. Removing isBridgedToObjectiveC() method since it doesn't introduce any useful information

Doug, I know that some API design decisions were made due to runtime performance convenience, therefore I'd like your opinion on that before I go on with detailed design.

Pozdrawiam – Regards,
Adrian Kashivskyy


(Douglas Gregor) #7

FWIW, I’ve implemented much of this proposal as a series of compiler cleanups. It introduces a few tweaks to the _ObjectiveCBridgeable protocol that were necessary to generalize (and de-special-case) the NSString/NSArray/NSDictionary/NSSet bridging, but it’s still considered an implementation detail. So, some comments on the proposal itself…

/// A type adopting `ObjectiveCBridgeable` will be exposed
/// to Objective-C as the type `ObjectiveCType`
public protocol ObjectiveCBridgeable: _ObjectiveCBridgeable {
    associatedtype ObjectiveCType : AnyObject
    associatedtype _ObjectiveCType = ObjectiveCType
I think we should just say that ObjectiveCBridgeable replaces _ObjectiveCBridgeable, and only have the first associated type. (You actually wanted a typealias anyway, I think).

    /// Returns `true` iff instances of `Self` can be converted to
    /// Objective-C. Even if this method returns `true`, a given
    /// instance of `Self._ObjectiveCType` may, or may not, convert
    /// successfully to `Self`.
    ///
    /// A default implementation returns `true`.
    @warn_unused_result
    static func isBridgedToObjectiveC() -> Bool
It’s probably worth saying why someone might override this method: usually, it’s because the Swift type is generic and it only makes sense to bridge for some type arguments. Granted, there is no way to communicate this information to the compiler, which is a bit of a hole in the design. For example, Array<T> only bridges to NSArray when T is itself representable in Objective-C. We really need conditional conformances to for this part of the feature to work properly.

    /// Try to construct a value of the Self type from
    /// an Objective-C object of the bridged class type.
    ///
    /// If the conversion fails this initializer returns `nil`.
    init?(bridgedFromObjectiveC: ObjectiveCType)
FWIW, implementing this required a new “unconditional” entry point used by the compiler:

  /// Bridge from an Objective-C object of the bridged class type to a
  /// value of the Self type.
  ///
  /// This bridging operation is used for unconditional bridging when
  /// interoperating with Objective-C code, either in the body of an
  /// Objective-C thunk or when calling Objective-C code, and may
  /// defer complete checking until later. For example, when bridging
  /// from `NSArray` to `Array<Element>`, we can defer the checking
  /// for the individual elements of the array.
  ///
  /// \param source The Objective-C object from which we are
  /// bridging. This optional value will only be `nil` in cases where
  /// an Objective-C method has returned a `nil` despite being marked
  /// as `_Nonnull`/`nonnull`. In most such cases, bridging will
  /// generally force the value immediately. However, this gives
  /// bridging the flexibility to substitute a default value to cope
  /// with historical decisions, e.g., an existing Objective-C method
  /// that returns `nil` to for "empty result" rather than (say) an
  /// empty array. In such cases, when `nil` does occur, the
  /// implementation of `Swift.Array`'s conformance to
  /// `_ObjectiveCBridgeable` will produce an empty array rather than
  /// dynamically failing.
  static func _unconditionallyBridgeFromObjectiveC(source: _ObjectiveCType?)
      -> Self

It can get a default implementation, and should be a non-failable initializer.

    static func _getObjectiveCType() -> Any.Type {
        return ObjectiveCType.self
    }
This was an implementation hack; it’s gone now.

Expose the protocol ObjectiveCBridgeable
Any type adopting ObjectiveCBridgeable will gain conformance to _ObjectiveCBridgeable
As noted earlier, I think there should just be one protocol here, ObjectiveCBridgeable.

  4. The ObjectiveCType must be defined in Swift. If a -swift.h header is generated, it will include a SWIFT_BRIDGED()macro where the parameter indicates the Swift type with which the ObjectiveCType bridges. The macro will be applied to the ObjectiveCType and any subclasses.

This is unnecessarily restrictive, and eliminates the “make my Objective-C class bridge into a Swift value type” case that (for example) the compiler already does for String/Array/Dictionary/Set. I think there are two cases:

  (a) The ObjectiveCType is defined in Objective-C. It must be an Objective-C class with the attribute swift_bridge(“Bar”), where “Bar” is the name of the bridged Swift type.
  (b) The ObjectiveCType is defined in Swift, in which case it must be an @objc class. When emitting the generated header, the appropriate swift_bridge attribute will be added to the @interface declaration.

(This was #5) It is an error for bridging to be ambiguous.
A Swift type may bridge to an Objective-C base class, then provide different subclass instances at runtime but no other Swift type may bridge to that base class or any of its subclasses.
The compiler must emit a diagnostic when it detects two Swift types attempting to bridge to the same ObjectiveCType.
This is a tricky area. Currently, Int/Float/Double/Bool/CGFloat/UInt all have _ObjectiveCBridgeable conformances, although those conformances only really kick in at runtime (e.g., when dynamically casting an [AnyObject] or [NSNumber] to [Int] or [Double] with as? or matching a switch case). They would run afoul of this rule. However, this rule does generally make sense: if two Swift types have the same ObjectiveCType, we won’t know how to map an Objective-C API back into Swift. Those numeric types only work because they are trivially mapped between Swift and (Objective-)C; they don’t need to go through the _ObjectiveCBridgeable conformance.

On the other hand, the greater ambiguity problem is if there are two conformances to ObjectiveCBridgeable on the same type; that’s already covered by Swift’s rules about multiple conformances.

(This was #6) The Swift type and ObjectiveCType must be defined in the same module
Yes, absolutely. If the ObjectiveCType comes from Objective-C, then it must come from the same-named Objective-C module.

Under “Alternatives considered”, there is a typo “fesible”.

I’ve been assuming we’re only allowed to bridge with Objective-C classes (whether they are defined in Swift or Objective-C), but Swift also has @objc enums now… I assume you don’t intend @objc enums to be part of this, so I think it makes sense to be explicit about the limitation to classes.

  - Doug

···

On Mar 9, 2016, at 12:26 PM, Russ Bishop via swift-evolution <swift-evolution@swift.org> wrote:

An official proposal PR has been opened on this: https://github.com/apple/swift-evolution/pull/198

It includes clarifications, a few changes, and some better examples.


(Adrian Kashivskyy) #8

I'm +1 on this. Like Radek, I have played with _ObjectiveCBridgeable in the past with my own types and I find it very useful, especially in library code.

Pozdrawiam – Regards,
Adrian Kashivskyy

···

Wiadomość napisana przez Russ Bishop via swift-evolution <swift-evolution@swift.org> w dniu 08.03.2016, o godz. 07:29:

On Mar 7, 2016, at 10:36 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

There may be some limitations that are inherent to the bridging process, so it’s not clear that we can achieve everything you mention above. As an example, check out my proposal to bridge Objective-C’s lightweight generics into Swift:

  http://thread.gmane.org/gmane.comp.lang.swift.evolution/2886

The content of that proposal isn’t as important as the scope of implementation effort to bridge between two similar-looking but very-differently-implemented features across the languages. It’s likely that other features you’ve mentioned above have similar complexities. That said, I do think it’s possible to extend the bridging mechanism to help alleviate some of the issues you’re describing.

Well the idea is that real bridging is impossible in many cases, so let the Swift type define an arbitrarily different type to represent itself in the world of Objective-C. In some cases this may behave like an opaque value that Objective-C code can’t touch, merely pass around. In others (eg structs/enums) a simpler less type-safe class interface can be provided. Or perhaps enum cases with associated values can map to a class cluster.

The important part is that the implementer is completely free to design a native Swift API for a feature without actually having to maintain a separate API and test suite.

3. It is up to the API designer to decide if they want to allow construction of wholly new values from Objective-C and how to handle convertibility back to the Swift type in that case

I’m not sure what you mean by this?

If I define a class (ObjCFizzer) I can choose to keep the initializer private meaning no one can create an instance from Objective-C. I only wanted to mention that explicitly because I can imagine a library author’s objections. In reality this rule just falls out naturally from private/internal visibility anyway.

FWIW, this method is an anachronism. You can leave it out, because the runtime can now recover this information directly. However, I do suggest that you show

  typealias ObjectiveCType = ObjCFizzer

in your example, so its clear what the mapping is.

Ah thanks, I’ll update it.

The above isn’t a correct implementation, because it will fatalError return than returning “nil” if the source cannot be bridged.

You’re correct, I meant to do it the other way around. I’ll fix it.

I had expected ObjCFizzer to be defined in Objective-C. If it can also be defined in Swift, that’s a nontrivial expansion of what bridging currently does.

Along these lines, I think we’ll need a Clang-side attribute on the Objective-C type to specify what Swift type it bridges to. That’s important for Swift’s Clang importer, because when it sees the Clang type it needs to know whether it’s importing that type directly or bridging to a different type.

First, my assumption is that a type can’t be recursively bridged (the ObjectiveCType cannot itself be bridgeable - the compiler could emit a diagnostic for that).

I guess my second assumption is that the thunk generation happens after the Swift compiler has discovered all the types in the module so whether the ObjectiveCType is defined in Swift or Objective-C doesn’t matter. This may not be true. To be honest I hadn’t considered defining the bridge types in the other direction (an Objective-C API that wants to present a nicer Swift API) but I can see how that would be very useful.

One of the benefits of being able to define the bridge type in Swift is that the bridge type can be a simple @objc type that contains stored properties any arbitrary Swift type. If defining the type in Swift has nontrivial implementation issues we can require the bridged type be defined in Objective-C but it would be nice if we didn’t have to do that.

Good point on the Clang attribute.

# TBD / Questions:

Should the shape of the protocol change?

I think the protocol is still basically the right interface, except that getObjectiveCType is no longer necessary. How Objective-C-centric should this protocol be? Should it be possible for (e.g.) the NSInteger-to-Int mapping to be expressible this way? Can a simple C struct be bridged?

In the case of C structs or NS_ENUM, Swift extensions already provide much of the value IMHO. I haven’t actually looked at how the NSInteger typedef gets mapped to Int; do you see value there?

For me the primary motivation is when I’m doing the actual implementation of a feature in Swift, then providing a less type-safe non-generics interface to Objective-C. I’m also looking to a future where we can export free functions to C. Bi-directional exchange with C++ (assuming the committee adopts something like “extern abi” someday) is an interesting case but far out of scope.

From a proposal perspective, I think it’s important to show the ObjectiveCBridgeable protocol you’re proposing and say what each requirement does. You can grab them directly from the comments, of course, but I suspect that most people haven’t dug into bridging at all.

If it’s not obvious already, I think this is definitely worthy of a proposal.

  - Doug

I’ll write up some of these changes and open a PR.

Russ

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Douglas Gregor) #9

I have a couple of ideas regarding the API of ObjectiveCBridgeable. My general thought is to improve upon the existing _ObjectiveCBridgeable protocol, not just make it public. The main idea would be to make it more "Swifty" by:

1. Removing inout arguments which look like implementation artifacts and converting them into return values

They are implementation artifacts, because these methods are called by C++ code in the runtime (for dynamic casting), and inout is *much* easier to work with because you know it’s always passed indirectly.

One could almost clearly clean up the primary interface to make it more natural in Swift, then use defaulted implementations for the more runtime-friendly entrypoints.

2. Converting static functions to initializers

Yes, that’s reasonable, but see the above.

3. Removing isBridgedToObjectiveC() method since it doesn't introduce any useful information

It tells us, dynamically, whether the bridging is actually possible, and is used by the runtime. For example:

func foo<T>(foo: Array<T>) {
  if let obj = foo as? NSArray {
    // whether we get here depends on T, dynamically
  }
}

struct SomeStruct { }

foo([SomeStruct]()) // “Not bridged"
foo([String]()) // “Bridged"

If you look at the implementation of Array’s conformance to _ObjectiveCBridgeable, for example, it dynamically checks whether its element type is _ObjectiveCBridgeable.

Note that isBridgedToObjectiveC is a hack because we don’t have conditional conformances, i.e., we can’t say that Array<T> conforms to _ObjectiveCBridgeable when T is representable in Objective-C. That’s obviously not something to be fixed by this proposal, but hopefully it helps illustrate why isBridgedToObjectiveC matters.

Perhaps you and Russ can work together to clean up _ObjectiveCBridgeable within the proposal? I’ll have more comments on the proposal soon-ish.

  - Doug

···

On Mar 17, 2016, at 12:04 AM, Adrian Kashivskyy via swift-evolution <swift-evolution@swift.org> wrote:


(Russ Bishop) #10

An official proposal PR has been opened on this: https://github.com/apple/swift-evolution/pull/198

It includes clarifications, a few changes, and some better examples.

FWIW, I’ve implemented much of this proposal as a series of compiler cleanups. It introduces a few tweaks to the _ObjectiveCBridgeable protocol that were necessary to generalize (and de-special-case) the NSString/NSArray/NSDictionary/NSSet bridging, but it’s still considered an implementation detail. So, some comments on the proposal itself…

/// A type adopting `ObjectiveCBridgeable` will be exposed
/// to Objective-C as the type `ObjectiveCType`
public protocol ObjectiveCBridgeable: _ObjectiveCBridgeable {
    associatedtype ObjectiveCType : AnyObject
    associatedtype _ObjectiveCType = ObjectiveCType
I think we should just say that ObjectiveCBridgeable replaces _ObjectiveCBridgeable, and only have the first associated type. (You actually wanted a typealias anyway, I think).

That sounds great to me.

    /// Returns `true` iff instances of `Self` can be converted to
    /// Objective-C. Even if this method returns `true`, a given
    /// instance of `Self._ObjectiveCType` may, or may not, convert
    /// successfully to `Self`.
    ///
    /// A default implementation returns `true`.
    @warn_unused_result
    static func isBridgedToObjectiveC() -> Bool
It’s probably worth saying why someone might override this method: usually, it’s because the Swift type is generic and it only makes sense to bridge for some type arguments. Granted, there is no way to communicate this information to the compiler, which is a bit of a hole in the design. For example, Array<T> only bridges to NSArray when T is itself representable in Objective-C. We really need conditional conformances to for this part of the feature to work properly.

I’ve added a section to the proposal to call out that it is intended for conformance to be conditional in some cases (eg Array), and a small explanation of what to do and a code example. It’s a bit unwieldy but workable until we have conditional conformance.

    /// Try to construct a value of the Self type from
    /// an Objective-C object of the bridged class type.
    ///
    /// If the conversion fails this initializer returns `nil`.
    init?(bridgedFromObjectiveC: ObjectiveCType)
FWIW, implementing this required a new “unconditional” entry point used by the compiler:

  static func _unconditionallyBridgeFromObjectiveC(source: _ObjectiveCType?)
      -> Self

It can get a default implementation, and should be a non-failable initializer.

I’ve updated the proposal to add this. The default implementation of "init(unconditionallyBridgedFromObjectiveC: ObjectiveCType?)" just calls the fallible initializer and aborts if anything goes wrong. Default implementation of “unconditionallyBridgeFromObjectiveC(source: ObjectiveCType?)” calls the initializer. My guess is most types will probably just accept the default behavior.

I assume this is a static function to avoid allocating memory by calling the initializer directly for each element, given the point is to defer the work? I wonder if we can skip the static though and just call the initializer directly? It would simplify the protocol a tiny bit.

  4. The ObjectiveCType must be defined in Swift. If a -swift.h header is generated, it will include a SWIFT_BRIDGED()macro where the parameter indicates the Swift type with which the ObjectiveCType bridges. The macro will be applied to the ObjectiveCType and any subclasses.

This is unnecessarily restrictive, and eliminates the “make my Objective-C class bridge into a Swift value type” case that (for example) the compiler already does for String/Array/Dictionary/Set. I think there are two cases:

  (a) The ObjectiveCType is defined in Objective-C. It must be an Objective-C class with the attribute swift_bridge(“Bar”), where “Bar” is the name of the bridged Swift type.
  (b) The ObjectiveCType is defined in Swift, in which case it must be an @objc class. When emitting the generated header, the appropriate swift_bridge attribute will be added to the @interface declaration.

I mostly hadn’t really considered the implications but I see the value and I trust your assessment; I removed the restriction and reworded the whole section.

(This was #5) It is an error for bridging to be ambiguous.
A Swift type may bridge to an Objective-C base class, then provide different subclass instances at runtime but no other Swift type may bridge to that base class or any of its subclasses.
The compiler must emit a diagnostic when it detects two Swift types attempting to bridge to the same ObjectiveCType.
This is a tricky area. Currently, Int/Float/Double/Bool/CGFloat/UInt all have _ObjectiveCBridgeable conformances, although those conformances only really kick in at runtime (e.g., when dynamically casting an [AnyObject] or [NSNumber] to [Int] or [Double] with as? or matching a switch case). They would run afoul of this rule. However, this rule does generally make sense: if two Swift types have the same ObjectiveCType, we won’t know how to map an Objective-C API back into Swift. Those numeric types only work because they are trivially mapped between Swift and (Objective-)C; they don’t need to go through the _ObjectiveCBridgeable conformance.

Perhaps the rule should simply be that any Objective-C API imported that has ambiguity is imported as the Objective-C type without automatic bridging support. The compiler can continue to import Int and friends with special magic. Creating an ambiguity just leaves you to resolve the problem manually? The rule would be something like "omitting the SWIFT_BRIDGED() attribute from ObjC //or// multiple Swift types bridging to the same ObjC type" turns off automatic thunk generation but bridged collections will still call the protocol where appropriate.

On the other hand, the greater ambiguity problem is if there are two conformances to ObjectiveCBridgeable on the same type; that’s already covered by Swift’s rules about multiple conformances.

(This was #6) The Swift type and ObjectiveCType must be defined in the same module
Yes, absolutely. If the ObjectiveCType comes from Objective-C, then it must come from the same-named Objective-C module.

Under “Alternatives considered”, there is a typo “fesible”.

I’ve been assuming we’re only allowed to bridge with Objective-C classes (whether they are defined in Swift or Objective-C), but Swift also has @objc enums now… I assume you don’t intend @objc enums to be part of this, so I think it makes sense to be explicit about the limitation to classes.

Yes that’s true. I added wording to that effect to the proposal.

An update has been posted to https://github.com/russbishop/swift-evolution/blob/master/proposals/0000-objectivecbridgeable.md

Would you prefer if I did / did not add your name to the proposal? I feel guilty taking all the credit.

Russ

···

On Mar 21, 2016, at 9:41 AM, Douglas Gregor <dgregor@apple.com> wrote:

On Mar 9, 2016, at 12:26 PM, Russ Bishop via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Russ Bishop) #11

We will. I will incorporate some of Adrian’s changes to the proposal shortly.

Russ

···

On Mar 17, 2016, at 11:29 AM, Douglas Gregor <dgregor@apple.com> wrote:

Perhaps you and Russ can work together to clean up _ObjectiveCBridgeable within the proposal? I’ll have more comments on the proposal soon-ish.

  - Doug


(Douglas Gregor) #12

An official proposal PR has been opened on this: https://github.com/apple/swift-evolution/pull/198

It includes clarifications, a few changes, and some better examples.

    /// Returns `true` iff instances of `Self` can be converted to
    /// Objective-C. Even if this method returns `true`, a given
    /// instance of `Self._ObjectiveCType` may, or may not, convert
    /// successfully to `Self`.
    ///
    /// A default implementation returns `true`.
    @warn_unused_result
    static func isBridgedToObjectiveC() -> Bool
It’s probably worth saying why someone might override this method: usually, it’s because the Swift type is generic and it only makes sense to bridge for some type arguments. Granted, there is no way to communicate this information to the compiler, which is a bit of a hole in the design. For example, Array<T> only bridges to NSArray when T is itself representable in Objective-C. We really need conditional conformances to for this part of the feature to work properly.

I’ve added a section to the proposal to call out that it is intended for conformance to be conditional in some cases (eg Array), and a small explanation of what to do and a code example. It’s a bit unwieldy but workable until we have conditional conformance.

Great. The suggestion to use an extension won’t actually work:

    /// A default implementation returns `true`. If a Swift type is
    /// generic and should only be bridged for some type arguments,
    /// provide alternate implementations in extensions
    /// and return `false` in those cases.
    ///
    /// struct Foo<T>: ObjectiveCBridgeable { ... }
    /// extension Foo where T: NonBridgedType {
    /// static func isBridgedToObjectiveC() -> Bool {
    /// return false
    /// }
    /// }
because you don’t get dynamic dispatching based on T. Instead, you want one implementation of isBridgedToObjectiveC that does a dynamic check, e.g.,

struct Foo<T>: ObjectiveCBridgeable {
  static func isBridgedToObjectiveC() -> Bool {
    return !(T is NonBridgedType)
  }
}
  

    /// Try to construct a value of the Self type from
    /// an Objective-C object of the bridged class type.
    ///
    /// If the conversion fails this initializer returns `nil`.
    init?(bridgedFromObjectiveC: ObjectiveCType)
FWIW, implementing this required a new “unconditional” entry point used by the compiler:

  static func _unconditionallyBridgeFromObjectiveC(source: _ObjectiveCType?)
      -> Self

It can get a default implementation, and should be a non-failable initializer.

I’ve updated the proposal to add this. The default implementation of "init(unconditionallyBridgedFromObjectiveC: ObjectiveCType?)" just calls the fallible initializer and aborts if anything goes wrong. Default implementation of “unconditionallyBridgeFromObjectiveC(source: ObjectiveCType?)” calls the initializer. My guess is most types will probably just accept the default behavior.

Yes, I suspect you’re right that most types will use the default.

I assume this is a static function to avoid allocating memory by calling the initializer directly for each element, given the point is to defer the work? I wonder if we can skip the static though and just call the initializer directly? It would simplify the protocol a tiny bit.

From an implementation perspective, the entry point for an initializer in a protocol handles the allocation itself. It’s a static function because it was easy to implement that way and the actual definitions get a bit more flexibility in how they can come up with the object (since we don’t have factory initializers).

  4. The ObjectiveCType must be defined in Swift. If a -swift.h header is generated, it will include a SWIFT_BRIDGED()macro where the parameter indicates the Swift type with which the ObjectiveCType bridges. The macro will be applied to the ObjectiveCType and any subclasses.

This is unnecessarily restrictive, and eliminates the “make my Objective-C class bridge into a Swift value type” case that (for example) the compiler already does for String/Array/Dictionary/Set. I think there are two cases:

  (a) The ObjectiveCType is defined in Objective-C. It must be an Objective-C class with the attribute swift_bridge(“Bar”), where “Bar” is the name of the bridged Swift type.
  (b) The ObjectiveCType is defined in Swift, in which case it must be an @objc class. When emitting the generated header, the appropriate swift_bridge attribute will be added to the @interface declaration.

I mostly hadn’t really considered the implications but I see the value and I trust your assessment; I removed the restriction and reworded the whole section.

Looks good, thanks!

(This was #5) It is an error for bridging to be ambiguous.
A Swift type may bridge to an Objective-C base class, then provide different subclass instances at runtime but no other Swift type may bridge to that base class or any of its subclasses.
The compiler must emit a diagnostic when it detects two Swift types attempting to bridge to the same ObjectiveCType.
This is a tricky area. Currently, Int/Float/Double/Bool/CGFloat/UInt all have _ObjectiveCBridgeable conformances, although those conformances only really kick in at runtime (e.g., when dynamically casting an [AnyObject] or [NSNumber] to [Int] or [Double] with as? or matching a switch case). They would run afoul of this rule. However, this rule does generally make sense: if two Swift types have the same ObjectiveCType, we won’t know how to map an Objective-C API back into Swift. Those numeric types only work because they are trivially mapped between Swift and (Objective-)C; they don’t need to go through the _ObjectiveCBridgeable conformance.

Perhaps the rule should simply be that any Objective-C API imported that has ambiguity is imported as the Objective-C type without automatic bridging support. The compiler can continue to import Int and friends with special magic. Creating an ambiguity just leaves you to resolve the problem manually? The rule would be something like "omitting the SWIFT_BRIDGED() attribute from ObjC //or// multiple Swift types bridging to the same ObjC type" turns off automatic thunk generation but bridged collections will still call the protocol where appropriate.

Yeah. Thinking about it a bit further, I like your rule basically as you’ve stated it, because having two _ObjectiveCBridgeable conformances mapping to the same type is a problem the user should have to resolve. I guess I’m just looking for something small to indicate that a direct mapping of the representation (e.g., NSInteger -> Int) supersedes the _ObjectiveCBridgeable conformance when mapping APIs between Objective-C and Swift.

An update has been posted to https://github.com/russbishop/swift-evolution/blob/master/proposals/0000-objectivecbridgeable.md

Thanks!

Would you prefer if I did / did not add your name to the proposal? I feel guilty taking all the credit.

I don’t have a strong preference. Feel free to add my name if you’d like. I really appreciate your work on driving this proposal forward!

  - Doug

···

On Mar 23, 2016, at 12:21 AM, Russ Bishop <xenadu@gmail.com> wrote:

On Mar 21, 2016, at 9:41 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Mar 9, 2016, at 12:26 PM, Russ Bishop via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Russ Bishop) #13

Great. The suggestion to use an extension won’t actually work:

Doh. Thanks for catching that, I pushed a fix.

I assume this is a static function to avoid allocating memory by calling the initializer directly for each element, given the point is to defer the work? I wonder if we can skip the static though and just call the initializer directly? It would simplify the protocol a tiny bit.

From an implementation perspective, the entry point for an initializer in a protocol handles the allocation itself. It’s a static function because it was easy to implement that way and the actual definitions get a bit more flexibility in how they can come up with the object (since we don’t have factory initializers).

Good point about the factory initializers. I’ve been trying to keep the whole “class cluster” situation in mind. I wonder if there should be an equivalent static function for the conditional bridging for similar reasons?

(This was #5) It is an error for bridging to be ambiguous.
A Swift type may bridge to an Objective-C base class, then provide different subclass instances at runtime but no other Swift type may bridge to that base class or any of its subclasses.
The compiler must emit a diagnostic when it detects two Swift types attempting to bridge to the same ObjectiveCType.
This is a tricky area. Currently, Int/Float/Double/Bool/CGFloat/UInt all have _ObjectiveCBridgeable conformances, although those conformances only really kick in at runtime (e.g., when dynamically casting an [AnyObject] or [NSNumber] to [Int] or [Double] with as? or matching a switch case). They would run afoul of this rule. However, this rule does generally make sense: if two Swift types have the same ObjectiveCType, we won’t know how to map an Objective-C API back into Swift. Those numeric types only work because they are trivially mapped between Swift and (Objective-)C; they don’t need to go through the _ObjectiveCBridgeable conformance.

Perhaps the rule should simply be that any Objective-C API imported that has ambiguity is imported as the Objective-C type without automatic bridging support. The compiler can continue to import Int and friends with special magic. Creating an ambiguity just leaves you to resolve the problem manually? The rule would be something like "omitting the SWIFT_BRIDGED() attribute from ObjC //or// multiple Swift types bridging to the same ObjC type" turns off automatic thunk generation but bridged collections will still call the protocol where appropriate.

Yeah. Thinking about it a bit further, I like your rule basically as you’ve stated it, because having two _ObjectiveCBridgeable conformances mapping to the same type is a problem the user should have to resolve. I guess I’m just looking for something small to indicate that a direct mapping of the representation (e.g., NSInteger -> Int) supersedes the _ObjectiveCBridgeable conformance when mapping APIs between Objective-C and Swift.

I added a separate section on Ambiguity and what the behavior is. I think you should be able to resolve ambiguity by casting so I went ahead and put that in. An example:

//Bar and Foo bridge to SomeObjectiveCType
struct Bar<T>: ObjectiveCBridgeable { }
struct Foo<T>: ObjectiveCBridgeable { }

class API {
    let foo: Foo<Int>
    func objCVersionOfAFunction(obj: SomeObjectiveCType) -> SomeObjectiveCType {
        let x = obj as! Bar<Int>
        // We've told the compiler which protocol impl to call
        return foo as! SomeObjectiveCType
    }
}

Any problems with this approach? It makes handling the ambiguous or manual bridging case relatively straightforward, though there may be objections to using casting this way. [Be careful, I still mourn the loss of @conversion so I’m biased :)]

`NSInteger` is already imported as `Int` from Objective-C right? I assume a Clang attribute is specifying that somewhere but there wouldn’t be any ambiguity on the Swift side during import. I could definitely see having an attribute to declare that this specific parameter or return value should bridge to a specific Swift type (this NSNumber should import as Int) but that’s a lot of work and may be worth a separate proposal.

It seems like the problem is going the other direction: you want to materialize this parameter or whatever as `NSInteger` instead of the default `NSNumber *` but only when directly bridged, not inside collections. There’s no existing Objective-C header to tell us what to do. I’m not sure how we can resolve this without a Swift attribute to tell the compiler because the handling of it would be specific to each declaration.

We could just say that we aren’t going to let people have that level of granularity. Then just introduce a BuiltInBridgeable protocol that supersedes ObjectiveCBridgeable. A type adopting both will cause the compiler to prefer the built-in protocol when generating a bridging header, but the collection types can ignore that and just use ObjectiveCBridgeable. Presumably the BuiltInBridgeable protocol would just have an associated type to indicate that the bits are directly mapped to BuiltIn.Word or whatever.

An update has been posted to https://github.com/russbishop/swift-evolution/blob/master/proposals/0000-objectivecbridgeable.md

Thanks!

Would you prefer if I did / did not add your name to the proposal? I feel guilty taking all the credit.

I don’t have a strong preference. Feel free to add my name if you’d like. I really appreciate your work on driving this proposal forward!

  - Doug

Happy to do so!

Russ

···

On Mar 23, 2016, at 11:49 AM, Douglas Gregor <dgregor@apple.com> wrote:


(Douglas Gregor) #14

I assume this is a static function to avoid allocating memory by calling the initializer directly for each element, given the point is to defer the work? I wonder if we can skip the static though and just call the initializer directly? It would simplify the protocol a tiny bit.

From an implementation perspective, the entry point for an initializer in a protocol handles the allocation itself. It’s a static function because it was easy to implement that way and the actual definitions get a bit more flexibility in how they can come up with the object (since we don’t have factory initializers).

Good point about the factory initializers. I’ve been trying to keep the whole “class cluster” situation in mind. I wonder if there should be an equivalent static function for the conditional bridging for similar reasons?

I realize that my comment about factory initializers was a bit off: these are initializers on the value type, so there are no “class clusters” to be had. Sorry for the noise!

I added a separate section on Ambiguity and what the behavior is. I think you should be able to resolve ambiguity by casting so I went ahead and put that in. An example:

//Bar and Foo bridge to SomeObjectiveCType
struct Bar<T>: ObjectiveCBridgeable { }
struct Foo<T>: ObjectiveCBridgeable { }

class API {
    let foo: Foo<Int>
    func objCVersionOfAFunction(obj: SomeObjectiveCType) -> SomeObjectiveCType {
        let x = obj as! Bar<Int>
        // We've told the compiler which protocol impl to call
        return foo as! SomeObjectiveCType
    }
}

Any problems with this approach? It makes handling the ambiguous or manual bridging case relatively straightforward, though there may be objections to using casting this way. [Be careful, I still mourn the loss of @conversion so I’m biased :)]

The problem I have with allowing the ambiguity is that you can get weird behavior if Bar and Foo are in different modules: import just Bar’s module, and an Objective-C API mentioning SomeObjectiveCType gets bridged as a Bar. Import just Foo’s module, and an Objective-C API mentioning SomeObjectiveCType gets bridged as a Foo. Import both, and SomeObjectiveCType doesn’t get bridged! Now start splitting class hierarchies among those modules and you get some very inconsistent imports… that’s why I think this needs to be an error.

`NSInteger` is already imported as `Int` from Objective-C right? I assume a Clang attribute is specifying that somewhere but there wouldn’t be any ambiguity on the Swift side during import.

It’s hardcoded, but it’s completely reasonable to imagine swift_bridge doing this some day.

I could definitely see having an attribute to declare that this specific parameter or return value should bridge to a specific Swift type (this NSNumber should import as Int) but that’s a lot of work and may be worth a separate proposal.

It would be hard for me to get motivated for such a proposal; at that point, just wrap up the API.

It seems like the problem is going the other direction: you want to materialize this parameter or whatever as `NSInteger` instead of the default `NSNumber *` but only when directly bridged, not inside collections. There’s no existing Objective-C header to tell us what to do. I’m not sure how we can resolve this without a Swift attribute to tell the compiler because the handling of it would be specific to each declaration.

I don’t have a good answer here; today, it’s hardcoded.

We could just say that we aren’t going to let people have that level of granularity. Then just introduce a BuiltInBridgeable protocol that supersedes ObjectiveCBridgeable. A type adopting both will cause the compiler to prefer the built-in protocol when generating a bridging header, but the collection types can ignore that and just use ObjectiveCBridgeable. Presumably the BuiltInBridgeable protocol would just have an associated type to indicate that the bits are directly mapped to BuiltIn.Word or whatever.

Yes, that’s a reasonable approach. I don’t think it’s important for this proposal.

Two last comments came up, then I’d like to merge and schedule this:

(1) isBridgedToObjectiveC should be a computed property, not a function

(2) Please add something indicating that, while one can “as” cast between a value type and its bridged Objective-C type, there are no implicit conversions. We currently have implicit conversions from String -> NSString, Array<T> -> NSArray, Dictionary<K, V> -> NSDictionary, and Set<T> -> NSSet, but we’re not happy about them and we don’t want to create more implicit conversions [*].

  - Doug

[*] The fact that you mourn the loss of @conversion did not go unnoticed as I was writing this :wink:

···

On Mar 23, 2016, at 1:25 PM, Russ Bishop <xenadu@gmail.com> wrote:

On Mar 23, 2016, at 11:49 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:


(Russ Bishop) #15

I added a separate section on Ambiguity and what the behavior is. I think you should be able to resolve ambiguity by casting so I went ahead and put that in. An example:

//Bar and Foo bridge to SomeObjectiveCType
struct Bar<T>: ObjectiveCBridgeable { }
struct Foo<T>: ObjectiveCBridgeable { }

class API {
    let foo: Foo<Int>
    func objCVersionOfAFunction(obj: SomeObjectiveCType) -> SomeObjectiveCType {
        let x = obj as! Bar<Int>
        // We've told the compiler which protocol impl to call
        return foo as! SomeObjectiveCType
    }
}

Any problems with this approach? It makes handling the ambiguous or manual bridging case relatively straightforward, though there may be objections to using casting this way. [Be careful, I still mourn the loss of @conversion so I’m biased :)]

The problem I have with allowing the ambiguity is that you can get weird behavior if Bar and Foo are in different modules: import just Bar’s module, and an Objective-C API mentioning SomeObjectiveCType gets bridged as a Bar. Import just Foo’s module, and an Objective-C API mentioning SomeObjectiveCType gets bridged as a Foo. Import both, and SomeObjectiveCType doesn’t get bridged! Now start splitting class hierarchies among those modules and you get some very inconsistent imports… that’s why I think this needs to be an error.

The rule requiring the Swift and @objc types to be in the same module wouldn’t allow the scenario you describe.

I’m fine to say it’s an error as this isn’t a capability I have any use for and it definitely could cause confusion. The rule could always be relaxed in the future if there’s a convincing case for it. I’ll update the proposal to make it an error again.

We could just say that we aren’t going to let people have that level of granularity. Then just introduce a BuiltInBridgeable protocol that supersedes ObjectiveCBridgeable. A type adopting both will cause the compiler to prefer the built-in protocol when generating a bridging header, but the collection types can ignore that and just use ObjectiveCBridgeable. Presumably the BuiltInBridgeable protocol would just have an associated type to indicate that the bits are directly mapped to BuiltIn.Word or whatever.

Yes, that’s a reasonable approach. I don’t think it’s important for this proposal.

I added some wording about magic built-in types like `Int` and a section at the bottom about the overall issue and an overriding protocol, but indicated we won’t pursue it at this time.

Two last comments came up, then I’d like to merge and schedule this:

(1) isBridgedToObjectiveC should be a computed property, not a function

(2) Please add something indicating that, while one can “as” cast between a value type and its bridged Objective-C type, there are no implicit conversions. We currently have implicit conversions from String -> NSString, Array<T> -> NSArray, Dictionary<K, V> -> NSDictionary, and Set<T> -> NSSet, but we’re not happy about them and we don’t want to create more implicit conversions [*].

Done!

Russ


(Douglas Gregor) #16

I added a separate section on Ambiguity and what the behavior is. I think you should be able to resolve ambiguity by casting so I went ahead and put that in. An example:

//Bar and Foo bridge to SomeObjectiveCType
struct Bar<T>: ObjectiveCBridgeable { }
struct Foo<T>: ObjectiveCBridgeable { }

class API {
    let foo: Foo<Int>
    func objCVersionOfAFunction(obj: SomeObjectiveCType) -> SomeObjectiveCType {
        let x = obj as! Bar<Int>
        // We've told the compiler which protocol impl to call
        return foo as! SomeObjectiveCType
    }
}

Any problems with this approach? It makes handling the ambiguous or manual bridging case relatively straightforward, though there may be objections to using casting this way. [Be careful, I still mourn the loss of @conversion so I’m biased :)]

The problem I have with allowing the ambiguity is that you can get weird behavior if Bar and Foo are in different modules: import just Bar’s module, and an Objective-C API mentioning SomeObjectiveCType gets bridged as a Bar. Import just Foo’s module, and an Objective-C API mentioning SomeObjectiveCType gets bridged as a Foo. Import both, and SomeObjectiveCType doesn’t get bridged! Now start splitting class hierarchies among those modules and you get some very inconsistent imports… that’s why I think this needs to be an error.

The rule requiring the Swift and @objc types to be in the same module wouldn’t allow the scenario you describe.

Ah, yes.

I’m fine to say it’s an error as this isn’t a capability I have any use for and it definitely could cause confusion. The rule could always be relaxed in the future if there’s a convincing case for it. I’ll update the proposal to make it an error again.

I’d rather call it an error and consider relaxing the rule if we find it’s very important later on.

  - Doug

···

On Mar 24, 2016, at 12:39 AM, Russ Bishop <xenadu@gmail.com> wrote: