Pitch: Inline @convention(...) attributes for closures

Motivation:

Presently when working with Swift closures there's a shortfall when working with closures that need to be declared with the @convention(...) attribute. Consider the following situation when working with the JavaScriptCore framework:

func makeJavaScriptFunctionValue(with context: JSContext) -> JSValue {
    let block: @convention(block) (JSValue) -> JSValue = { value in 
        ...

        return value
    }

    return JSValue(object: block, in: context)
}

Or the scenario where we need to use objc_msgSend to send an Objective-C message:

func send(message: String, to object: AnyObject) {
    typealias Function = @convention(c) (AnyObject, Selector, String) -> Void
    let function = unsafeBitCast(objc_msgSend, Function.self)

    function(object, #selector(sendMessage(_:)), to: message)
}

In both of these cases we need are unable to inline the @convention(...) attribute and need to either create a local variable with explicit typing, or create a new typealias to accommodate the attribute.

Proposed Solution:

Extend the Swift complier to allow these attributes to be inlined with the declaration of the closure/type as we can currently do with closures that don't use these attributes.

With this solution, the above scenarios would look like this:

func makeJavaScriptFunctionValue(with context: JSContext) -> JSValue {
    return JSValue(object: { @convention(block) (value: JSValue) -> JSValue in
        ...

        return value
    }, in: context)
}

func send(message: String, to object: AnyObject) {
    let function = unsafeBitCast(objc_msgSend, to: (@convention(c) (AnyObject, Selector, String) -> Void).self)

    function(object, #selector(sendMessage(_:)), message)
}

This change would merely be a convenience or cosmetic change as there is currently a way to declare blocks with @convention(...) attributes, albeit a more cumbersome way. Adding this proposed change would allow for using closures in this way with other convenience features of the language, e.g. trailing closures, that are not currently possible when using these attributes.

As part of this change the parameters & return type of the closure could be omitted if the compiler is able to infer those from context:

// func sort<T>(_ array: [T], by handler: (T, T) -> Bool) where T: Comparable
func foobar(_ array: [Int]) {
    let sortedArray = sort(array) { @convention(block) in $0 < $1 }
    ...
}

And lastly, @convention(...) attributes should be able to used in as statements when declaring new closures:

let closure = { int1, int2 in 
    return int1 + int2
} as (@convention(c) (Int, Int) -> Int)

// `closure` is of type `@convention(c) (Int, Int) -> Int`

Impact on Existing Code:

None that I can think of; this change would simply provide an alternative way of declaring closures with @convention(...) attributes.

Alternatives Considered:

The only other alternative that was considered was to place the @convention(...) attribute outside of the closure. Upon consideration I believe that doing this would interrupt the flow of how the code reads as well as potentially add confusion as to where the attribute applies.

1 Like

I could see this being useful. Note, though, that with @Jumhyn's proposal for placeholder types, you could do this:

let closure: @convention(c) _ = { int1, int2 in 
    return int1 + int2
}

or

let sortedArray = sort(array, { $0 < $1 } as @convention(block) _)
5 Likes

If adopted using these placeholder types could make it a bit easier in terms of declaration. That being said, one of the main purposes of this proposal is the ability to add the attribute inline so as to not have to declare a local variable or type, which isn't currently possible nor does it seem it would be possible with the placeholder types proposal.

@Joe_Groff do you see any downside or pitfalls for this proposal as currently written?

IMO the part with the ability to explicitly spell out the convention like @convention(block) (value: JSValue) -> JSValue seems reasonable. The other example with @convention(block) in seems a bit awkward to read because one might expect a function type after the @convention but there is none...

@typesanitizer I can see what you mean in regards to the second example. My thought behind that is to make it similar to closure capture semantics where the type of the closure can also be inferred. For example:

DispatchQueue.main.async { [weak self] in ... }

For me, even I didn't necessarily know about the @convention attribute the fact that it's preceded with an @ tells me that there's some compiler significance to it that's separate from the arguments or type of the closure. Somewhat similar to the @escaping attribute. Thoughts?