[Proposal draft] Import as member


(Michael Ilseman) #1

You can find the living document at: https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md

For your convenience, here is the current state:

Import as member

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
Author(s): Michael Ilseman <https://github.com/milseman>
Status: Awaiting review
Review manager: TBD
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#introduction>Introduction

Swift imports C declarations, allowing Swift code to natively interact with C libraries and frameworks. But, such imported APIs do not feel natural to interact with in Swift. This proposal seeks to provide a mechanism for C API authors to specify the capability of importing functions and variables as members on imported Swift types. It also seeks to provide an automatic inference option for APIs that follow a consistent, disciplined naming convention.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#motivation>Motivation

C APIs and frameworks currently import into Swift as global functions and global variables. Interacting with these can feel awkward in Swift.

Here's an example of programmatic drawing using the Core Graphics C API:

override func drawRect(rect: CGRect) {
    let context: CGContext = UIGraphicsGetCurrentContext()!
    let toCenter = CGPoint(x: bounds.width/2.0, y: bounds.height/2.0)
    let angle = CGFloat(M_PI / 16)

    var transform = CGAffineTransformIdentity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = CGAffineTransformTranslate(transform, toCenter.x, toCenter.y)
        transform = CGAffineTransformRotate(transform, angle)
        transform = CGAffineTransformTranslate(transform, -toCenter.x, -toCenter.y)
    }
    CGContextSetLineWidth(context, bounds.size.width / 100)
    CGContextSetGrayStrokeColor(context, 0.5, 1.0)
    CGContextDrawPath(context, .Stroke)
}

func triangulateRect(bounds: CGRect, inputTransform: CGAffineTransform,
        context: CGContext) {
    var transform = inputTransform

    // Triangle from top left corner, to bottom middle, to top right, and then
    // draw the boundary
    let topLeft = bounds.origin
    let bottomRight = CGPoint(x: bounds.size.width, y: bounds.size.height)
    let path = CGPathCreateMutable()
    CGPathMoveToPoint(path, &transform, topLeft.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, CGRectGetMidX(bounds), bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, topLeft.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, topLeft.x, bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, topLeft.y)
    CGContextAddPath(context, path)
}
A much more natural expression of this in Swift, would be something more like:

override func drawRect(rect: CGRect) {
    let context: CGContext = UIGraphicsGetCurrentContext()!
    let toCenter = CGPoint(x: bounds.width/2.0, y: bounds.height/2.0)
    let angle = CGFloat(M_PI / 16)

    var transform = CGAffineTransform.identity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = transform.translate(toX: toCenter.x, toY: toCenter.y)
                             .rotate(angle: angle)
                             .translate(toX: -toCenter.x, toY: -toCenter.y)
    }

    context.lineWidth = bounds.size.width / 100
    context.strokeColor = CGColor(gray: 0.5, alpha: 1.0)
    context.drawPath(mode: .Stroke)
}

func triangulateRect(bounds: CGRect, inputTransform: CGAffineTransform,
        context: CGContext) {
    var transform = inputTransform

    // Triangle from top left corner, to bottom middle, to top right, and then
    // draw the boundary
    let topLeft = bounds.origin
    let bottomRight = CGPoint(x: bounds.size.width, y: bounds.size.height)
    let path = CGMutablePath()
    path.move(transform: &transform, x: topLeft.x, y: topLeft.y)
    path.addLine(transform: &transform, x: bounds.midX, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: topLeft.y)
    path.addLine(transform: &transform, x: topLeft.x, y: topLeft.y)
    path.addLine(transform: &transform, x: topLeft.x, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: topLeft.y)
    context.addPath(path)
}
Currently, the only way for a C framework to provide a natural Swift experience is to author large overlays or Swift wrappers.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#proposed-solution>Proposed solution

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#manual-specification>Manual specification

C framework authors should have a way to manually specify how their APIs appear in Swift beyond the limited functionality currently provided with NS_SWIFT_NAME. This includes the ability to specify a type on which a given variable or function should be imported. This also includes the ability to specify when a function should be imported as a computed getter or setter of a property on that type.

The goal is for developers using a C framework, which has these manual annotations applied, to develop in Swift as naturally as if they were working with a native object-oriented interface.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#automatic-inference>Automatic inference

Coupled with this manual specification ability is an automatic inference system. The inference system analyzes C global names and types, attempting to find an imported Swift type to extend with a method, initializer, or property from this global.

This inference system's goal for the is to be able to automatically handle the majority of global variables and functions in CF-style frameworks, and in the future be extensible to benefit other well structured, disciplined APIs.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#maps-directly-onto-c-calling-convention>Maps directly onto C calling convention

Wrappers and overlays have the downside that they result in an extra function call hop in order to reach the underlying C API (though fragility controls may somewhat alleviate this in the future).

This proposal calls for imported APIs to map directly onto the original C APIs, without calling through intermediary wrappers or overlaid definitions. For instance members, this means supplying a reference to self in the appropriate parameter slot.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#detailed-design>Detailed design

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#swift_name-attribute>swift_name attribute

The primary mechanism of manually communicating to the Swift compiler how an API should be imported is the swift_name attribute (e.g. through the CF_SWIFT_NAME macro). swift_name will be expanded to allow the user to provide a type on which the imported function will be a member of, and allow for specifying a function as a computed getter or setter for a type.

Examples:

struct Point3D createPoint3D(float x, float y, float z)
__attribute__((swift_name("Point3D.init(x:y:z:)")));

struct Point3D rotatePoint3D(Point3D point, float radians)
__attribute__((swift_name("Point3D.rotate(self:radius:)")));

float Point3DGetMagnitude(Point3D point)
__attribute__((swift_name("getter:Point3D.magnitude(self:)")));
The string present in swift_name will additionally support the following:

A type name proceeded by '.' to denote the context to import onto
'self' to denote which parameter to treat as self for an instance 'method/property, otherwise this will be a static method/property
'getter:' and 'setter:' to denote the function as a property getter/setter
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#automatic-inference-heuristics>Automatic inference heuristics

The following are some techniques and heuristics that can be useful for consistently named C APIs, e.g. CF-style frameworks. These heuristics are based off of the variable/function's name and type.

Identify init by return type
- func CGColorCreate(space: CGColorSpace?, _ components: UnsafePointer<CGFloat>)
- -> CGColor?

// extension CGColor { ...
+ init?(space: CGColorSpace?, components: UnsafePointer<CGFloat>)
Identify computed properties by finding "get" / "set" pairs
- func CGContextGetInterpolationQuality(c: CGContext?) -> CGInterpolationQuality
- func CGContextSetInterpolationQuality(c: CGContext?,
- _ quality: CGInterpolationQuality)

// extension CGContext { ...
+ final var interpolationQuality: CGInterpolationQuality
Identify boolean predicates and other computed property patterns
- func CGDisplayModeIsUsableForDesktopGUI(mode: CGDisplayMode?) -> Bool

// extension CGDisplayMode {
+ final var isUsableForDesktopGUI: Bool { get }
Identify methods by finding a self parameter
- func CGAffineTransformInvert(t: CGAffineTransform) -> CGAffineTransform

// extension CGAffineTransformation { ...
+ func invert() -> CGAffineTransform
Various special cases, fuzzy name matching, etc.
- func CGDisplayStreamUpdateGetTypeID() -> CFTypeID

// extension CGDisplayStreamUpdate { ...
+ final class var typeID: CFTypeID { get }

...

- func CGBitmapContextGetData(context: CGContext?) -> UnsafeMutablePointer<Void>

// extension CGContext { ...
+ final var bitmapData: UnsafeMutablePointer<Void> { get }
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#underlying-infrastructure>Underlying infrastructure

The Clang Importer will be extended to support importing function and variable declarations onto different effective contexts than they appear in Clang. Additionally, the Clang Importer will want to create a single extension point per submodule/type pair on which to add these members.

SILGen will need to be extended to map calls to these members directly to the original C API calls, passing self in the appropriate parameter location for instance members.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#migration>Migration

Projects using old style APIs will need to migrate to any new API. Since the proposed imports are done programmatically in the importer, migration attributes can be attached to the new decls, allowing the Swift migrator to automatically migrate user code.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#impact-on-existing-code>Impact on existing code

Any Swift code using a C framework that uses this functionality will be massively affected, though in ways that the Swift migrator can alleviate.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#alternatives-considered>Alternatives considered

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#wrap-everything>Wrap everything

One alternative, which is the only option available currently to framework authors, is to require C APIs to provide Swift wrapper APIs or overlays in order to call into the underlying C functionality.

This has the disadvantage of having to maintain separate APIs in addition to the C headers themselves. This proposal allows for the C header to specify how the name should appear when imported into Swift. Additionally, if a C API follows consistent, CF-like naming, most of it can be imported automatically


(Jeff Kelley) #2

This is one of those proposals that I immediately agree with and want to start using! Definite +1.

It might make sense in another proposal, but one thing that jumps out to me is seeing &context still used to pass a reference to the context around. I wonder if there’d be a good way to import these opaque reference types like CGContextRef and dispense with the & altogether?

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

···

On Mar 2, 2016, at 2:43 PM, Michael Ilseman via swift-evolution <swift-evolution@swift.org> wrote:

You can find the living document at: https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md

For your convenience, here is the current state:

Import as member

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
Author(s): Michael Ilseman <https://github.com/milseman>
Status: Awaiting review
Review manager: TBD
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#introduction>Introduction

Swift imports C declarations, allowing Swift code to natively interact with C libraries and frameworks. But, such imported APIs do not feel natural to interact with in Swift. This proposal seeks to provide a mechanism for C API authors to specify the capability of importing functions and variables as members on imported Swift types. It also seeks to provide an automatic inference option for APIs that follow a consistent, disciplined naming convention.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#motivation>Motivation

C APIs and frameworks currently import into Swift as global functions and global variables. Interacting with these can feel awkward in Swift.

Here's an example of programmatic drawing using the Core Graphics C API:

override func drawRect(rect: CGRect) {
    let context: CGContext = UIGraphicsGetCurrentContext()!
    let toCenter = CGPoint(x: bounds.width/2.0, y: bounds.height/2.0)
    let angle = CGFloat(M_PI / 16)

    var transform = CGAffineTransformIdentity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = CGAffineTransformTranslate(transform, toCenter.x, toCenter.y)
        transform = CGAffineTransformRotate(transform, angle)
        transform = CGAffineTransformTranslate(transform, -toCenter.x, -toCenter.y)
    }
    CGContextSetLineWidth(context, bounds.size.width / 100)
    CGContextSetGrayStrokeColor(context, 0.5, 1.0)
    CGContextDrawPath(context, .Stroke)
}

func triangulateRect(bounds: CGRect, inputTransform: CGAffineTransform,
        context: CGContext) {
    var transform = inputTransform

    // Triangle from top left corner, to bottom middle, to top right, and then
    // draw the boundary
    let topLeft = bounds.origin
    let bottomRight = CGPoint(x: bounds.size.width, y: bounds.size.height)
    let path = CGPathCreateMutable()
    CGPathMoveToPoint(path, &transform, topLeft.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, CGRectGetMidX(bounds), bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, topLeft.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, topLeft.x, bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, topLeft.y)
    CGContextAddPath(context, path)
}
A much more natural expression of this in Swift, would be something more like:

override func drawRect(rect: CGRect) {
    let context: CGContext = UIGraphicsGetCurrentContext()!
    let toCenter = CGPoint(x: bounds.width/2.0, y: bounds.height/2.0)
    let angle = CGFloat(M_PI / 16)

    var transform = CGAffineTransform.identity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = transform.translate(toX: toCenter.x, toY: toCenter.y)
                             .rotate(angle: angle)
                             .translate(toX: -toCenter.x, toY: -toCenter.y)
    }

    context.lineWidth = bounds.size.width / 100
    context.strokeColor = CGColor(gray: 0.5, alpha: 1.0)
    context.drawPath(mode: .Stroke)
}

func triangulateRect(bounds: CGRect, inputTransform: CGAffineTransform,
        context: CGContext) {
    var transform = inputTransform

    // Triangle from top left corner, to bottom middle, to top right, and then
    // draw the boundary
    let topLeft = bounds.origin
    let bottomRight = CGPoint(x: bounds.size.width, y: bounds.size.height)
    let path = CGMutablePath()
    path.move(transform: &transform, x: topLeft.x, y: topLeft.y)
    path.addLine(transform: &transform, x: bounds.midX, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: topLeft.y)
    path.addLine(transform: &transform, x: topLeft.x, y: topLeft.y)
    path.addLine(transform: &transform, x: topLeft.x, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: topLeft.y)
    context.addPath(path)
}
Currently, the only way for a C framework to provide a natural Swift experience is to author large overlays or Swift wrappers.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#proposed-solution>Proposed solution

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#manual-specification>Manual specification

C framework authors should have a way to manually specify how their APIs appear in Swift beyond the limited functionality currently provided with NS_SWIFT_NAME. This includes the ability to specify a type on which a given variable or function should be imported. This also includes the ability to specify when a function should be imported as a computed getter or setter of a property on that type.

The goal is for developers using a C framework, which has these manual annotations applied, to develop in Swift as naturally as if they were working with a native object-oriented interface.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#automatic-inference>Automatic inference

Coupled with this manual specification ability is an automatic inference system. The inference system analyzes C global names and types, attempting to find an imported Swift type to extend with a method, initializer, or property from this global.

This inference system's goal for the is to be able to automatically handle the majority of global variables and functions in CF-style frameworks, and in the future be extensible to benefit other well structured, disciplined APIs.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#maps-directly-onto-c-calling-convention>Maps directly onto C calling convention

Wrappers and overlays have the downside that they result in an extra function call hop in order to reach the underlying C API (though fragility controls may somewhat alleviate this in the future).

This proposal calls for imported APIs to map directly onto the original C APIs, without calling through intermediary wrappers or overlaid definitions. For instance members, this means supplying a reference to self in the appropriate parameter slot.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#detailed-design>Detailed design

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#swift_name-attribute>swift_name attribute

The primary mechanism of manually communicating to the Swift compiler how an API should be imported is the swift_name attribute (e.g. through the CF_SWIFT_NAME macro). swift_name will be expanded to allow the user to provide a type on which the imported function will be a member of, and allow for specifying a function as a computed getter or setter for a type.

Examples:

struct Point3D createPoint3D(float x, float y, float z)
__attribute__((swift_name("Point3D.init(x:y:z:)")));

struct Point3D rotatePoint3D(Point3D point, float radians)
__attribute__((swift_name("Point3D.rotate(self:radius:)")));

float Point3DGetMagnitude(Point3D point)
__attribute__((swift_name("getter:Point3D.magnitude(self:)")));
The string present in swift_name will additionally support the following:

A type name proceeded by '.' to denote the context to import onto
'self' to denote which parameter to treat as self for an instance 'method/property, otherwise this will be a static method/property
'getter:' and 'setter:' to denote the function as a property getter/setter
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#automatic-inference-heuristics>Automatic inference heuristics

The following are some techniques and heuristics that can be useful for consistently named C APIs, e.g. CF-style frameworks. These heuristics are based off of the variable/function's name and type.

Identify init by return type
- func CGColorCreate(space: CGColorSpace?, _ components: UnsafePointer<CGFloat>)
- -> CGColor?

// extension CGColor { ...
+ init?(space: CGColorSpace?, components: UnsafePointer<CGFloat>)
Identify computed properties by finding "get" / "set" pairs
- func CGContextGetInterpolationQuality(c: CGContext?) -> CGInterpolationQuality
- func CGContextSetInterpolationQuality(c: CGContext?,
- _ quality: CGInterpolationQuality)

// extension CGContext { ...
+ final var interpolationQuality: CGInterpolationQuality
Identify boolean predicates and other computed property patterns
- func CGDisplayModeIsUsableForDesktopGUI(mode: CGDisplayMode?) -> Bool

// extension CGDisplayMode {
+ final var isUsableForDesktopGUI: Bool { get }
Identify methods by finding a self parameter
- func CGAffineTransformInvert(t: CGAffineTransform) -> CGAffineTransform

// extension CGAffineTransformation { ...
+ func invert() -> CGAffineTransform
Various special cases, fuzzy name matching, etc.
- func CGDisplayStreamUpdateGetTypeID() -> CFTypeID

// extension CGDisplayStreamUpdate { ...
+ final class var typeID: CFTypeID { get }

...

- func CGBitmapContextGetData(context: CGContext?) -> UnsafeMutablePointer<Void>

// extension CGContext { ...
+ final var bitmapData: UnsafeMutablePointer<Void> { get }
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#underlying-infrastructure>Underlying infrastructure

The Clang Importer will be extended to support importing function and variable declarations onto different effective contexts than they appear in Clang. Additionally, the Clang Importer will want to create a single extension point per submodule/type pair on which to add these members.

SILGen will need to be extended to map calls to these members directly to the original C API calls, passing self in the appropriate parameter location for instance members.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#migration>Migration

Projects using old style APIs will need to migrate to any new API. Since the proposed imports are done programmatically in the importer, migration attributes can be attached to the new decls, allowing the Swift migrator to automatically migrate user code.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#impact-on-existing-code>Impact on existing code

Any Swift code using a C framework that uses this functionality will be massively affected, though in ways that the Swift migrator can alleviate.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#alternatives-considered>Alternatives considered

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#wrap-everything>Wrap everything

One alternative, which is the only option available currently to framework authors, is to require C APIs to provide Swift wrapper APIs or overlays in order to call into the underlying C functionality.

This has the disadvantage of having to maintain separate APIs in addition to the C headers themselves. This proposal allows for the C header to specify how the name should appear when imported into Swift. Additionally, if a C API follows consistent, CF-like naming, most of it can be imported automatically
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Michael Ilseman) #3

This is one of those proposals that I immediately agree with and want to start using! Definite +1.

It might make sense in another proposal, but one thing that jumps out to me is seeing &context still used to pass a reference to the context around. I wonder if there’d be a good way to import these opaque reference types like CGContextRef and dispense with the & altogether?

I assume you mean the &transform? Yes, that was particularly painful to write, as it seems like the API intention is more closely aligned with having an Optional<CGAffineTransform> here. Fixing that is probably outside of the scope of this proposal, or it might be possible to do orthogonally.

···

On Mar 2, 2016, at 11:59 AM, Jeff Kelley <slaunchaman@gmail.com> wrote:

Jeff Kelley

SlaunchaMan@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

On Mar 2, 2016, at 2:43 PM, Michael Ilseman via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

You can find the living document at: https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md

For your convenience, here is the current state:

Import as member

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
Author(s): Michael Ilseman <https://github.com/milseman>
Status: Awaiting review
Review manager: TBD
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#introduction>Introduction

Swift imports C declarations, allowing Swift code to natively interact with C libraries and frameworks. But, such imported APIs do not feel natural to interact with in Swift. This proposal seeks to provide a mechanism for C API authors to specify the capability of importing functions and variables as members on imported Swift types. It also seeks to provide an automatic inference option for APIs that follow a consistent, disciplined naming convention.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#motivation>Motivation

C APIs and frameworks currently import into Swift as global functions and global variables. Interacting with these can feel awkward in Swift.

Here's an example of programmatic drawing using the Core Graphics C API:

override func drawRect(rect: CGRect) {
    let context: CGContext = UIGraphicsGetCurrentContext()!
    let toCenter = CGPoint(x: bounds.width/2.0, y: bounds.height/2.0)
    let angle = CGFloat(M_PI / 16)

    var transform = CGAffineTransformIdentity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = CGAffineTransformTranslate(transform, toCenter.x, toCenter.y)
        transform = CGAffineTransformRotate(transform, angle)
        transform = CGAffineTransformTranslate(transform, -toCenter.x, -toCenter.y)
    }
    CGContextSetLineWidth(context, bounds.size.width / 100)
    CGContextSetGrayStrokeColor(context, 0.5, 1.0)
    CGContextDrawPath(context, .Stroke)
}

func triangulateRect(bounds: CGRect, inputTransform: CGAffineTransform,
        context: CGContext) {
    var transform = inputTransform

    // Triangle from top left corner, to bottom middle, to top right, and then
    // draw the boundary
    let topLeft = bounds.origin
    let bottomRight = CGPoint(x: bounds.size.width, y: bounds.size.height)
    let path = CGPathCreateMutable()
    CGPathMoveToPoint(path, &transform, topLeft.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, CGRectGetMidX(bounds), bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, topLeft.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, topLeft.x, bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, topLeft.y)
    CGContextAddPath(context, path)
}
A much more natural expression of this in Swift, would be something more like:

override func drawRect(rect: CGRect) {
    let context: CGContext = UIGraphicsGetCurrentContext()!
    let toCenter = CGPoint(x: bounds.width/2.0, y: bounds.height/2.0)
    let angle = CGFloat(M_PI / 16)

    var transform = CGAffineTransform.identity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = transform.translate(toX: toCenter.x, toY: toCenter.y)
                             .rotate(angle: angle)
                             .translate(toX: -toCenter.x, toY: -toCenter.y)
    }

    context.lineWidth = bounds.size.width / 100
    context.strokeColor = CGColor(gray: 0.5, alpha: 1.0)
    context.drawPath(mode: .Stroke)
}

func triangulateRect(bounds: CGRect, inputTransform: CGAffineTransform,
        context: CGContext) {
    var transform = inputTransform

    // Triangle from top left corner, to bottom middle, to top right, and then
    // draw the boundary
    let topLeft = bounds.origin
    let bottomRight = CGPoint(x: bounds.size.width, y: bounds.size.height)
    let path = CGMutablePath()
    path.move(transform: &transform, x: topLeft.x, y: topLeft.y)
    path.addLine(transform: &transform, x: bounds.midX, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: topLeft.y)
    path.addLine(transform: &transform, x: topLeft.x, y: topLeft.y)
    path.addLine(transform: &transform, x: topLeft.x, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: topLeft.y)
    context.addPath(path)
}
Currently, the only way for a C framework to provide a natural Swift experience is to author large overlays or Swift wrappers.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#proposed-solution>Proposed solution

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#manual-specification>Manual specification

C framework authors should have a way to manually specify how their APIs appear in Swift beyond the limited functionality currently provided with NS_SWIFT_NAME. This includes the ability to specify a type on which a given variable or function should be imported. This also includes the ability to specify when a function should be imported as a computed getter or setter of a property on that type.

The goal is for developers using a C framework, which has these manual annotations applied, to develop in Swift as naturally as if they were working with a native object-oriented interface.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#automatic-inference>Automatic inference

Coupled with this manual specification ability is an automatic inference system. The inference system analyzes C global names and types, attempting to find an imported Swift type to extend with a method, initializer, or property from this global.

This inference system's goal for the is to be able to automatically handle the majority of global variables and functions in CF-style frameworks, and in the future be extensible to benefit other well structured, disciplined APIs.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#maps-directly-onto-c-calling-convention>Maps directly onto C calling convention

Wrappers and overlays have the downside that they result in an extra function call hop in order to reach the underlying C API (though fragility controls may somewhat alleviate this in the future).

This proposal calls for imported APIs to map directly onto the original C APIs, without calling through intermediary wrappers or overlaid definitions. For instance members, this means supplying a reference to self in the appropriate parameter slot.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#detailed-design>Detailed design

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#swift_name-attribute>swift_name attribute

The primary mechanism of manually communicating to the Swift compiler how an API should be imported is the swift_name attribute (e.g. through the CF_SWIFT_NAME macro). swift_name will be expanded to allow the user to provide a type on which the imported function will be a member of, and allow for specifying a function as a computed getter or setter for a type.

Examples:

struct Point3D createPoint3D(float x, float y, float z)
__attribute__((swift_name("Point3D.init(x:y:z:)")));

struct Point3D rotatePoint3D(Point3D point, float radians)
__attribute__((swift_name("Point3D.rotate(self:radius:)")));

float Point3DGetMagnitude(Point3D point)
__attribute__((swift_name("getter:Point3D.magnitude(self:)")));
The string present in swift_name will additionally support the following:

A type name proceeded by '.' to denote the context to import onto
'self' to denote which parameter to treat as self for an instance 'method/property, otherwise this will be a static method/property
'getter:' and 'setter:' to denote the function as a property getter/setter
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#automatic-inference-heuristics>Automatic inference heuristics

The following are some techniques and heuristics that can be useful for consistently named C APIs, e.g. CF-style frameworks. These heuristics are based off of the variable/function's name and type.

Identify init by return type
- func CGColorCreate(space: CGColorSpace?, _ components: UnsafePointer<CGFloat>)
- -> CGColor?

// extension CGColor { ...
+ init?(space: CGColorSpace?, components: UnsafePointer<CGFloat>)
Identify computed properties by finding "get" / "set" pairs
- func CGContextGetInterpolationQuality(c: CGContext?) -> CGInterpolationQuality
- func CGContextSetInterpolationQuality(c: CGContext?,
- _ quality: CGInterpolationQuality)

// extension CGContext { ...
+ final var interpolationQuality: CGInterpolationQuality
Identify boolean predicates and other computed property patterns
- func CGDisplayModeIsUsableForDesktopGUI(mode: CGDisplayMode?) -> Bool

// extension CGDisplayMode {
+ final var isUsableForDesktopGUI: Bool { get }
Identify methods by finding a self parameter
- func CGAffineTransformInvert(t: CGAffineTransform) -> CGAffineTransform

// extension CGAffineTransformation { ...
+ func invert() -> CGAffineTransform
Various special cases, fuzzy name matching, etc.
- func CGDisplayStreamUpdateGetTypeID() -> CFTypeID

// extension CGDisplayStreamUpdate { ...
+ final class var typeID: CFTypeID { get }

...

- func CGBitmapContextGetData(context: CGContext?) -> UnsafeMutablePointer<Void>

// extension CGContext { ...
+ final var bitmapData: UnsafeMutablePointer<Void> { get }
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#underlying-infrastructure>Underlying infrastructure

The Clang Importer will be extended to support importing function and variable declarations onto different effective contexts than they appear in Clang. Additionally, the Clang Importer will want to create a single extension point per submodule/type pair on which to add these members.

SILGen will need to be extended to map calls to these members directly to the original C API calls, passing self in the appropriate parameter location for instance members.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#migration>Migration

Projects using old style APIs will need to migrate to any new API. Since the proposed imports are done programmatically in the importer, migration attributes can be attached to the new decls, allowing the Swift migrator to automatically migrate user code.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#impact-on-existing-code>Impact on existing code

Any Swift code using a C framework that uses this functionality will be massively affected, though in ways that the Swift migrator can alleviate.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#alternatives-considered>Alternatives considered

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#wrap-everything>Wrap everything

One alternative, which is the only option available currently to framework authors, is to require C APIs to provide Swift wrapper APIs or overlays in order to call into the underlying C functionality.

This has the disadvantage of having to maintain separate APIs in addition to the C headers themselves. This proposal allows for the C header to specify how the name should appear when imported into Swift. Additionally, if a C API follows consistent, CF-like naming, most of it can be imported automatically
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jeff Kelley) #4

Yes, I meant &transform. I think this proposal is more immediately helpful. If I have time I’ll draft an additional proposal for your Optional suggestion. :+1:

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

···

On Mar 2, 2016, at 3:33 PM, Michael Ilseman <milseman@apple.com> wrote:

On Mar 2, 2016, at 11:59 AM, Jeff Kelley <slaunchaman@gmail.com <mailto:slaunchaman@gmail.com>> wrote:

This is one of those proposals that I immediately agree with and want to start using! Definite +1.

It might make sense in another proposal, but one thing that jumps out to me is seeing &context still used to pass a reference to the context around. I wonder if there’d be a good way to import these opaque reference types like CGContextRef and dispense with the & altogether?

I assume you mean the &transform? Yes, that was particularly painful to write, as it seems like the API intention is more closely aligned with having an Optional<CGAffineTransform> here. Fixing that is probably outside of the scope of this proposal, or it might be possible to do orthogonally.

Jeff Kelley

SlaunchaMan@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

On Mar 2, 2016, at 2:43 PM, Michael Ilseman via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

You can find the living document at: https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md

For your convenience, here is the current state:

Import as member

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
Author(s): Michael Ilseman <https://github.com/milseman>
Status: Awaiting review
Review manager: TBD
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#introduction>Introduction

Swift imports C declarations, allowing Swift code to natively interact with C libraries and frameworks. But, such imported APIs do not feel natural to interact with in Swift. This proposal seeks to provide a mechanism for C API authors to specify the capability of importing functions and variables as members on imported Swift types. It also seeks to provide an automatic inference option for APIs that follow a consistent, disciplined naming convention.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#motivation>Motivation

C APIs and frameworks currently import into Swift as global functions and global variables. Interacting with these can feel awkward in Swift.

Here's an example of programmatic drawing using the Core Graphics C API:

override func drawRect(rect: CGRect) {
    let context: CGContext = UIGraphicsGetCurrentContext()!
    let toCenter = CGPoint(x: bounds.width/2.0, y: bounds.height/2.0)
    let angle = CGFloat(M_PI / 16)

    var transform = CGAffineTransformIdentity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = CGAffineTransformTranslate(transform, toCenter.x, toCenter.y)
        transform = CGAffineTransformRotate(transform, angle)
        transform = CGAffineTransformTranslate(transform, -toCenter.x, -toCenter.y)
    }
    CGContextSetLineWidth(context, bounds.size.width / 100)
    CGContextSetGrayStrokeColor(context, 0.5, 1.0)
    CGContextDrawPath(context, .Stroke)
}

func triangulateRect(bounds: CGRect, inputTransform: CGAffineTransform,
        context: CGContext) {
    var transform = inputTransform

    // Triangle from top left corner, to bottom middle, to top right, and then
    // draw the boundary
    let topLeft = bounds.origin
    let bottomRight = CGPoint(x: bounds.size.width, y: bounds.size.height)
    let path = CGPathCreateMutable()
    CGPathMoveToPoint(path, &transform, topLeft.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, CGRectGetMidX(bounds), bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, topLeft.x, topLeft.y)
    CGPathAddLineToPoint(path, &transform, topLeft.x, bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, bottomRight.y)
    CGPathAddLineToPoint(path, &transform, bottomRight.x, topLeft.y)
    CGContextAddPath(context, path)
}
A much more natural expression of this in Swift, would be something more like:

override func drawRect(rect: CGRect) {
    let context: CGContext = UIGraphicsGetCurrentContext()!
    let toCenter = CGPoint(x: bounds.width/2.0, y: bounds.height/2.0)
    let angle = CGFloat(M_PI / 16)

    var transform = CGAffineTransform.identity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = transform.translate(toX: toCenter.x, toY: toCenter.y)
                             .rotate(angle: angle)
                             .translate(toX: -toCenter.x, toY: -toCenter.y)
    }

    context.lineWidth = bounds.size.width / 100
    context.strokeColor = CGColor(gray: 0.5, alpha: 1.0)
    context.drawPath(mode: .Stroke)
}

func triangulateRect(bounds: CGRect, inputTransform: CGAffineTransform,
        context: CGContext) {
    var transform = inputTransform

    // Triangle from top left corner, to bottom middle, to top right, and then
    // draw the boundary
    let topLeft = bounds.origin
    let bottomRight = CGPoint(x: bounds.size.width, y: bounds.size.height)
    let path = CGMutablePath()
    path.move(transform: &transform, x: topLeft.x, y: topLeft.y)
    path.addLine(transform: &transform, x: bounds.midX, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: topLeft.y)
    path.addLine(transform: &transform, x: topLeft.x, y: topLeft.y)
    path.addLine(transform: &transform, x: topLeft.x, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: bottomRight.y)
    path.addLine(transform: &transform, x: bottomRight.x, y: topLeft.y)
    context.addPath(path)
}
Currently, the only way for a C framework to provide a natural Swift experience is to author large overlays or Swift wrappers.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#proposed-solution>Proposed solution

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#manual-specification>Manual specification

C framework authors should have a way to manually specify how their APIs appear in Swift beyond the limited functionality currently provided with NS_SWIFT_NAME. This includes the ability to specify a type on which a given variable or function should be imported. This also includes the ability to specify when a function should be imported as a computed getter or setter of a property on that type.

The goal is for developers using a C framework, which has these manual annotations applied, to develop in Swift as naturally as if they were working with a native object-oriented interface.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#automatic-inference>Automatic inference

Coupled with this manual specification ability is an automatic inference system. The inference system analyzes C global names and types, attempting to find an imported Swift type to extend with a method, initializer, or property from this global.

This inference system's goal for the is to be able to automatically handle the majority of global variables and functions in CF-style frameworks, and in the future be extensible to benefit other well structured, disciplined APIs.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#maps-directly-onto-c-calling-convention>Maps directly onto C calling convention

Wrappers and overlays have the downside that they result in an extra function call hop in order to reach the underlying C API (though fragility controls may somewhat alleviate this in the future).

This proposal calls for imported APIs to map directly onto the original C APIs, without calling through intermediary wrappers or overlaid definitions. For instance members, this means supplying a reference to self in the appropriate parameter slot.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#detailed-design>Detailed design

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#swift_name-attribute>swift_name attribute

The primary mechanism of manually communicating to the Swift compiler how an API should be imported is the swift_name attribute (e.g. through the CF_SWIFT_NAME macro). swift_name will be expanded to allow the user to provide a type on which the imported function will be a member of, and allow for specifying a function as a computed getter or setter for a type.

Examples:

struct Point3D createPoint3D(float x, float y, float z)
__attribute__((swift_name("Point3D.init(x:y:z:)")));

struct Point3D rotatePoint3D(Point3D point, float radians)
__attribute__((swift_name("Point3D.rotate(self:radius:)")));

float Point3DGetMagnitude(Point3D point)
__attribute__((swift_name("getter:Point3D.magnitude(self:)")));
The string present in swift_name will additionally support the following:

A type name proceeded by '.' to denote the context to import onto
'self' to denote which parameter to treat as self for an instance 'method/property, otherwise this will be a static method/property
'getter:' and 'setter:' to denote the function as a property getter/setter
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#automatic-inference-heuristics>Automatic inference heuristics

The following are some techniques and heuristics that can be useful for consistently named C APIs, e.g. CF-style frameworks. These heuristics are based off of the variable/function's name and type.

Identify init by return type
- func CGColorCreate(space: CGColorSpace?, _ components: UnsafePointer<CGFloat>)
- -> CGColor?

// extension CGColor { ...
+ init?(space: CGColorSpace?, components: UnsafePointer<CGFloat>)
Identify computed properties by finding "get" / "set" pairs
- func CGContextGetInterpolationQuality(c: CGContext?) -> CGInterpolationQuality
- func CGContextSetInterpolationQuality(c: CGContext?,
- _ quality: CGInterpolationQuality)

// extension CGContext { ...
+ final var interpolationQuality: CGInterpolationQuality
Identify boolean predicates and other computed property patterns
- func CGDisplayModeIsUsableForDesktopGUI(mode: CGDisplayMode?) -> Bool

// extension CGDisplayMode {
+ final var isUsableForDesktopGUI: Bool { get }
Identify methods by finding a self parameter
- func CGAffineTransformInvert(t: CGAffineTransform) -> CGAffineTransform

// extension CGAffineTransformation { ...
+ func invert() -> CGAffineTransform
Various special cases, fuzzy name matching, etc.
- func CGDisplayStreamUpdateGetTypeID() -> CFTypeID

// extension CGDisplayStreamUpdate { ...
+ final class var typeID: CFTypeID { get }

...

- func CGBitmapContextGetData(context: CGContext?) -> UnsafeMutablePointer<Void>

// extension CGContext { ...
+ final var bitmapData: UnsafeMutablePointer<Void> { get }
<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#underlying-infrastructure>Underlying infrastructure

The Clang Importer will be extended to support importing function and variable declarations onto different effective contexts than they appear in Clang. Additionally, the Clang Importer will want to create a single extension point per submodule/type pair on which to add these members.

SILGen will need to be extended to map calls to these members directly to the original C API calls, passing self in the appropriate parameter location for instance members.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#migration>Migration

Projects using old style APIs will need to migrate to any new API. Since the proposed imports are done programmatically in the importer, migration attributes can be attached to the new decls, allowing the Swift migrator to automatically migrate user code.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#impact-on-existing-code>Impact on existing code

Any Swift code using a C framework that uses this functionality will be massively affected, though in ways that the Swift migrator can alleviate.

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#alternatives-considered>Alternatives considered

<https://github.com/milseman/swift-evolution/blob/master/0000-import-as-member.md#wrap-everything>Wrap everything

One alternative, which is the only option available currently to framework authors, is to require C APIs to provide Swift wrapper APIs or overlays in order to call into the underlying C functionality.

This has the disadvantage of having to maintain separate APIs in addition to the C headers themselves. This proposal allows for the C header to specify how the name should appear when imported into Swift. Additionally, if a C API follows consistent, CF-like naming, most of it can be imported automatically
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution