How should NSArray<void(^)(void)>* be imported?

I'm interested in fixing a pet peeve of mine:

The failing assertion is in Array._forceBridgeFromObjectiveC, namely
Swift._isBridgedToObjectiveC(Element.self). Element is plain ol "() -> ()",
but probably should be @convention(block) since it was imported from
void(^)(void).

I've started tracing through the importer, and I found that
`adjustTypeForConcreteImport` is enforcing
FunctionTypeRepresentation::Swift because the ImportKind is BridgedValue —
this is hardcoded in the call to importType for the type parameters to
NSArray (and NSDictionary and NSSet) in
SwiftTypeConverter::VisitObjCObjectPointerType.

Are the Foundation collection classes only temporarily special-cased here,
until Obj-C generics are generally supported? Is someone working on this in
the near future?

If this worked correctly, would we expect to see "var executionBlocks:
[@convention(block) () -> ()]" ? If so, would this be best achieved by
passing a different ImportKind, possibly introducing a new ImportKind, or
some other solution?

I'm guessing that it doesn't make sense for () -> () to be
_ObjectiveCBridgeable, but either way I'm not sure where the
_isBridgedToObjectiveC implementation for blocks would come from.

Bumblingly,
Jacob

There's a hack to handle NSArray<Class> * bridging to [AnyObject.Type], which has similar problems. Look around for _BridgeableMetatype.

-Joe

···

On Mar 3, 2016, at 2:23 AM, Jacob Bandes-Storch via swift-dev <swift-dev@swift.org> wrote:

I'm interested in fixing a pet peeve of mine: [SR-772] Accessing NSBlockOperation.executionBlocks crashes at run time · Issue #43384 · apple/swift · GitHub

The failing assertion is in Array._forceBridgeFromObjectiveC, namely Swift._isBridgedToObjectiveC(Element.self). Element is plain ol "() -> ()", but probably should be @convention(block) since it was imported from void(^)(void).

I've started tracing through the importer, and I found that `adjustTypeForConcreteImport` is enforcing FunctionTypeRepresentation::Swift because the ImportKind is BridgedValue — this is hardcoded in the call to importType for the type parameters to NSArray (and NSDictionary and NSSet) in SwiftTypeConverter::VisitObjCObjectPointerType.

Are the Foundation collection classes only temporarily special-cased here, until Obj-C generics are generally supported? Is someone working on this in the near future?

If this worked correctly, would we expect to see "var executionBlocks: [@convention(block) () -> ()]" ? If so, would this be best achieved by passing a different ImportKind, possibly introducing a new ImportKind, or some other solution?

I'm guessing that it doesn't make sense for () -> () to be _ObjectiveCBridgeable, but either way I'm not sure where the _isBridgedToObjectiveC implementation for blocks would come from.

Bumblingly,
Jacob

Is another hack the way to go, or should it be correctly imported as
[@convention(block) () -> ()] (which I think would be
_isBridgedToObjectiveC already)?

···

On Thu, Mar 3, 2016 at 9:08 AM, Joe Groff <jgroff@apple.com> wrote:

On Mar 3, 2016, at 2:23 AM, Jacob Bandes-Storch via swift-dev < > swift-dev@swift.org> wrote:

I'm interested in fixing a pet peeve of mine:
https://bugs.swift.org/browse/SR-772

The failing assertion is in Array._forceBridgeFromObjectiveC, namely
Swift._isBridgedToObjectiveC(Element.self). Element is plain ol "() ->
()", but probably should be @convention(block) since it was imported from
void(^)(void).

I've started tracing through the importer, and I found that
`adjustTypeForConcreteImport` is enforcing
FunctionTypeRepresentation::Swift because the ImportKind is BridgedValue —
this is hardcoded in the call to importType for the type parameters to
NSArray (and NSDictionary and NSSet) in
SwiftTypeConverter::VisitObjCObjectPointerType.

Are the Foundation collection classes only temporarily special-cased here,
until Obj-C generics are generally supported? Is someone working on this in
the near future?

If this worked correctly, would we expect to see "var executionBlocks:
[@convention(block) () -> ()]" ? If so, would this be best achieved by
passing a different ImportKind, possibly introducing a new ImportKind, or
some other solution?

I'm guessing that it doesn't make sense for () -> () to be
_ObjectiveCBridgeable, but either way I'm not sure where the
_isBridgedToObjectiveC implementation for blocks would come from.

Bumblingly,
Jacob

There's a hack to handle NSArray<Class> * bridging to [AnyObject.Type],
which has similar problems. Look around for _BridgeableMetatype.

-Joe

Is another hack the way to go, or should it be correctly imported as [@convention(block) () -> ()] (which I think would be _isBridgedToObjectiveC already)?

Bridging to @convention(block) is probably more practical, since we'd otherwise need to be able to thunk an arbitrary call signature at runtime. However, @convention(block) is still a structural type, so it would need special case handling similar to _BridgeableMetatype to be able to feed a _BridgedToObjectiveC conformance into the runtime.

-Joe

···

On Mar 3, 2016, at 10:26 AM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

On Thu, Mar 3, 2016 at 9:08 AM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Mar 3, 2016, at 2:23 AM, Jacob Bandes-Storch via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:

I'm interested in fixing a pet peeve of mine: [SR-772] Accessing NSBlockOperation.executionBlocks crashes at run time · Issue #43384 · apple/swift · GitHub

The failing assertion is in Array._forceBridgeFromObjectiveC, namely Swift._isBridgedToObjectiveC(Element.self). Element is plain ol "() -> ()", but probably should be @convention(block) since it was imported from void(^)(void).

I've started tracing through the importer, and I found that `adjustTypeForConcreteImport` is enforcing FunctionTypeRepresentation::Swift because the ImportKind is BridgedValue — this is hardcoded in the call to importType for the type parameters to NSArray (and NSDictionary and NSSet) in SwiftTypeConverter::VisitObjCObjectPointerType.

Are the Foundation collection classes only temporarily special-cased here, until Obj-C generics are generally supported? Is someone working on this in the near future?

If this worked correctly, would we expect to see "var executionBlocks: [@convention(block) () -> ()]" ? If so, would this be best achieved by passing a different ImportKind, possibly introducing a new ImportKind, or some other solution?

I'm guessing that it doesn't make sense for () -> () to be _ObjectiveCBridgeable, but either way I'm not sure where the _isBridgedToObjectiveC implementation for blocks would come from.

Bumblingly,
Jacob

There's a hack to handle NSArray<Class> * bridging to [AnyObject.Type], which has similar problems. Look around for _BridgeableMetatype.

-Joe

I see, thanks for the clarification. How is it, then, that properties whose
type is void(^)(void) already work fine?

···

On Thu, Mar 3, 2016 at 12:16 PM Joe Groff <jgroff@apple.com> wrote:

On Mar 3, 2016, at 10:26 AM, Jacob Bandes-Storch <jtbandes@gmail.com> > wrote:

Is another hack the way to go, or should it be correctly imported as
[@convention(block) () -> ()] (which I think would be
_isBridgedToObjectiveC already)?

Bridging to @convention(block) is probably more practical, since we'd
otherwise need to be able to thunk an arbitrary call signature at runtime.
However, @convention(block) is still a structural type, so it would need
special case handling similar to _BridgeableMetatype to be able to feed a
_BridgedToObjectiveC conformance into the runtime.

-Joe

On Thu, Mar 3, 2016 at 9:08 AM, Joe Groff <jgroff@apple.com> wrote:

On Mar 3, 2016, at 2:23 AM, Jacob Bandes-Storch via swift-dev < >> swift-dev@swift.org> wrote:

I'm interested in fixing a pet peeve of mine:
[SR-772] Accessing NSBlockOperation.executionBlocks crashes at run time · Issue #43384 · apple/swift · GitHub

The failing assertion is in Array._forceBridgeFromObjectiveC, namely
Swift._isBridgedToObjectiveC(Element.self). Element is plain ol "() ->
()", but probably should be @convention(block) since it was imported from
void(^)(void).

I've started tracing through the importer, and I found that
`adjustTypeForConcreteImport` is enforcing
FunctionTypeRepresentation::Swift because the ImportKind is BridgedValue —
this is hardcoded in the call to importType for the type parameters to
NSArray (and NSDictionary and NSSet) in
SwiftTypeConverter::VisitObjCObjectPointerType.

Are the Foundation collection classes only temporarily special-cased
here, until Obj-C generics are generally supported? Is someone working on
this in the near future?

If this worked correctly, would we expect to see "var executionBlocks:
[@convention(block) () -> ()]" ? If so, would this be best achieved by
passing a different ImportKind, possibly introducing a new ImportKind, or
some other solution?

I'm guessing that it doesn't make sense for () -> () to be
_ObjectiveCBridgeable, but either way I'm not sure where the
_isBridgedToObjectiveC implementation for blocks would come from.

Bumblingly,
Jacob

There's a hack to handle NSArray<Class> * bridging to [AnyObject.Type],
which has similar problems. Look around for _BridgeableMetatype.

-Joe

For properties, we can statically bridge them by emitting thunk getters and setters at compile time. We have to do this dynamically for bridged containers since they can freely be in ObjC or Swift representation at runtime.

-Joe

···

On Mar 3, 2016, at 10:12 PM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

I see, thanks for the clarification. How is it, then, that properties whose type is void(^)(void) already work fine?

I'm wading through little by little, but I clearly still have a lot to
learn... sorry if I'm bothering you with questions!

Comparing the SIL for the `*completionBlock*` getter, which is imported as
()->()...

     %15 = class_method [volatile] %14 : $NSOperation,
#NSOperation.completionBlock!getter.1.foreign : NSOperation -> () -> *(()
-> ())?* , $@convention(objc_method) (NSOperation) -> @autoreleased
*Optional<@convention(block)
() -> ()>*

    Here I see that "@convention(block)" is retained in the type
information, and appears in the expanded form Optional<@convention(block)
()->()>, even though the getter completionBlock getter is of type
"NSOperation -> () -> (() -> ())?", and ()->() is what we see in generated
interfaces.

...vs. the `*executionBlocks*` getter, which is imported as [()->()]...

     %11 = class_method [volatile] %10 : $NSBlockOperation,
#NSBlockOperation.executionBlocks!getter.1.foreign : NSBlockOperation -> ()
-> *[() -> ()]* , $@convention(objc_method) (NSBlockOperation) ->
@autoreleased Optional<NSArray>
     %12 = apply %11(%10) : $@convention(objc_method) (NSBlockOperation) ->
@autoreleased Optional<NSArray>
     %13 = function_ref
@_TF10Foundation22_convertNSArrayToArrayurFGSqCSo7NSArray_GSax_ :
$@convention(thin) <τ_0_0> (@owned Optional<NSArray>) -> @owned Array<τ_0_0>
     %14 = apply *%13<() -> ()>*(%12) : $@convention(thin) <τ_0_0> (@owned
Optional<NSArray>) -> @owned Array<τ_0_0>
     release_value %14 : *$Array<() -> ()>*

I don't see @convention(block) anywhere here. And when I check the type of
static_cast<FunctionTypeMetadata*>(T)->getConvention(), from inside
findBridgeWitness, the convention is Swift.

So not only is there no conformance for blocks (which seems like an easy
thing to add, like you said, similarly to _BridgeableMetatype), but I think
the @convention(block) is being *completely lost* by
adjustTypeForConcreteImport. Is this true or am I missing something?

How is @convention(block) retained after importing in order to produce the
right type from completionBlock.getter, namely Optional<@convention(block)
()->()>, but it appears only as Optional<()->()> in the generated
interface? Why does this mechanism not produce a call to
_convertNSArrayToArray<@convention(block) ()->()>, but only
_convertNSArrayToArray<()->()> ?

Jacob

···

On Fri, Mar 4, 2016 at 9:46 AM, Joe Groff <jgroff@apple.com> wrote:

> On Mar 3, 2016, at 10:12 PM, Jacob Bandes-Storch <jtbandes@gmail.com> > wrote:
>
> I see, thanks for the clarification. How is it, then, that properties
whose type is void(^)(void) already work fine?

For properties, we can statically bridge them by emitting thunk getters
and setters at compile time. We have to do this dynamically for bridged
containers since they can freely be in ObjC or Swift representation at
runtime.

-Joe

I think this is a problem at the Clang importer level. It's probably assuming that if you have a parameter of type NSArray<T> in Objective-C, and T is bridgeable to BT, then it should always be imported as Array<BT> (and SILGen would be able to insert the conversions during code generation). If you want to preserve the block-ness, it may need a special case at that level.

···

On Mar 5, 2016, at 1:54 AM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

I'm wading through little by little, but I clearly still have a lot to learn... sorry if I'm bothering you with questions!

Comparing the SIL for the `completionBlock` getter, which is imported as ()->()...

     %15 = class_method [volatile] %14 : $NSOperation, #NSOperation.completionBlock!getter.1.foreign : NSOperation -> () -> (() -> ())? , $@convention(objc_method) (NSOperation) -> @autoreleased Optional<@convention(block) () -> ()>

    Here I see that "@convention(block)" is retained in the type information, and appears in the expanded form Optional<@convention(block) ()->()>, even though the getter completionBlock getter is of type "NSOperation -> () -> (() -> ())?", and ()->() is what we see in generated interfaces.

...vs. the `executionBlocks` getter, which is imported as [()->()]...

     %11 = class_method [volatile] %10 : $NSBlockOperation, #NSBlockOperation.executionBlocks!getter.1.foreign : NSBlockOperation -> () -> [() -> ()] , $@convention(objc_method) (NSBlockOperation) -> @autoreleased Optional<NSArray>
     %12 = apply %11(%10) : $@convention(objc_method) (NSBlockOperation) -> @autoreleased Optional<NSArray>
     %13 = function_ref @_TF10Foundation22_convertNSArrayToArrayurFGSqCSo7NSArray_GSax_ : $@convention(thin) <τ_0_0> (@owned Optional<NSArray>) -> @owned Array<τ_0_0>
     %14 = apply %13<() -> ()>(%12) : $@convention(thin) <τ_0_0> (@owned Optional<NSArray>) -> @owned Array<τ_0_0>
     release_value %14 : $Array<() -> ()>

I don't see @convention(block) anywhere here. And when I check the type of static_cast<FunctionTypeMetadata*>(T)->getConvention(), from inside findBridgeWitness, the convention is Swift.

So not only is there no conformance for blocks (which seems like an easy thing to add, like you said, similarly to _BridgeableMetatype), but I think the @convention(block) is being completely lost by adjustTypeForConcreteImport. Is this true or am I missing something?

How is @convention(block) retained after importing in order to produce the right type from completionBlock.getter, namely Optional<@convention(block) ()->()>, but it appears only as Optional<()->()> in the generated interface? Why does this mechanism not produce a call to _convertNSArrayToArray<@convention(block) ()->()>, but only _convertNSArrayToArray<()->()> ?