Pitch: Import Objective-C Constants as Enums


(Jeff Kelley) #1

A lot of Cocoa APIs have long lists of constant values, typically NSStrings. I’d like to pitch a way to import them as enums with associated types. I can write up a full proposal if people think this is a good idea, but here’s my thinking:

Let’s take the error domains in NSError.h for a quick example. These entries in the header:

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

turn into this in the Swift interface:

public let NSCocoaErrorDomain: String
public let NSPOSIXErrorDomain: String
public let NSOSStatusErrorDomain: String
public let NSMachErrorDomain: String

What I’m proposing is a way to import those as an enum instead. Similar to how we mark sections of Objective-C code with NS_ASSUME_NONNULL_BEGIN, we could mark it with something like NS_CASE_LIST_BEGIN. Then, this code:

NS_CASE_LIST_BEGIN;

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

NS_CASE_LIST_END;

would be imported as follows:

enum ErrorDomain : String {
    case Cocoa
    case POSIX
    case OSStatus
    case Mach
}

I can think of a lot of areas in Cocoa where these APIs could make things much more type-safe in Swift. Is this a good idea? Would people use this?

Jeff Kelley

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


(Douglas Gregor) #2

FWIW, this has come up a number of times in discussions among Swift developers (although not, IIRC, on swift-evolution). Our current favored way to write this in (Objective-)C would be with a new typedef of NSString * that has some special attribute on it, e.g.,

  typedef NSString * NSErrorDomain __attribute__((enum(string)));

  FOUNDATION_EXPORT NSErrorDomain const NSCocoaErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSPOSIXErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSOSStatusErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSMachErrorDomain;

The typedef would import as a String-backed enum and all of the string constants declared with that typedef within the same module as the typedef would become cases of that enum. String constants declared with that typedef in a *different* module would become “static lets” within extensions of the String-backed enum.

Call that a +1 from me on your idea :slight_smile:

  - Doug

···

On Jan 17, 2016, at 7:13 PM, Jeff Kelley via swift-evolution <swift-evolution@swift.org> wrote:

A lot of Cocoa APIs have long lists of constant values, typically NSStrings. I’d like to pitch a way to import them as enums with associated types. I can write up a full proposal if people think this is a good idea, but here’s my thinking:

Let’s take the error domains in NSError.h for a quick example. These entries in the header:

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

turn into this in the Swift interface:

public let NSCocoaErrorDomain: String
public let NSPOSIXErrorDomain: String
public let NSOSStatusErrorDomain: String
public let NSMachErrorDomain: String

What I’m proposing is a way to import those as an enum instead. Similar to how we mark sections of Objective-C code with NS_ASSUME_NONNULL_BEGIN, we could mark it with something like NS_CASE_LIST_BEGIN. Then, this code:

NS_CASE_LIST_BEGIN;

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

NS_CASE_LIST_END;

would be imported as follows:

enum ErrorDomain : String {
    case Cocoa
    case POSIX
    case OSStatus
    case Mach
}

I can think of a lot of areas in Cocoa where these APIs could make things much more type-safe in Swift. Is this a good idea? Would people use this?


(Jeff Kelley) #3

Oh, that’s nice! Using the typedef would also allow Objective-C methods to be annotated with the type they expect while still allowing arbitrary strings for APIs where that’s necessary. Not only does that help in Swift—you wouldn’t have to use rawValue to pull the string values out when vending to the platform API—but it would give new developers to Apple platforms a head start on finding where those constants are defined in the headers.

I’ll write this up as a formal proposal using the typedef method, though I’m still all ears for any other comments/suggestions.

Jeff Kelley

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

···

On Jan 18, 2016, at 1:10 AM, Douglas Gregor <dgregor@apple.com> wrote:

On Jan 17, 2016, at 7:13 PM, Jeff Kelley via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

A lot of Cocoa APIs have long lists of constant values, typically NSStrings. I’d like to pitch a way to import them as enums with associated types. I can write up a full proposal if people think this is a good idea, but here’s my thinking:

Let’s take the error domains in NSError.h for a quick example. These entries in the header:

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

turn into this in the Swift interface:

public let NSCocoaErrorDomain: String
public let NSPOSIXErrorDomain: String
public let NSOSStatusErrorDomain: String
public let NSMachErrorDomain: String

What I’m proposing is a way to import those as an enum instead. Similar to how we mark sections of Objective-C code with NS_ASSUME_NONNULL_BEGIN, we could mark it with something like NS_CASE_LIST_BEGIN. Then, this code:

NS_CASE_LIST_BEGIN;

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

NS_CASE_LIST_END;

would be imported as follows:

enum ErrorDomain : String {
    case Cocoa
    case POSIX
    case OSStatus
    case Mach
}

I can think of a lot of areas in Cocoa where these APIs could make things much more type-safe in Swift. Is this a good idea? Would people use this?

FWIW, this has come up a number of times in discussions among Swift developers (although not, IIRC, on swift-evolution). Our current favored way to write this in (Objective-)C would be with a new typedef of NSString * that has some special attribute on it, e.g.,

  typedef NSString * NSErrorDomain __attribute__((enum(string)));

  FOUNDATION_EXPORT NSErrorDomain const NSCocoaErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSPOSIXErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSOSStatusErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSMachErrorDomain;

The typedef would import as a String-backed enum and all of the string constants declared with that typedef within the same module as the typedef would become cases of that enum. String constants declared with that typedef in a *different* module would become “static lets” within extensions of the String-backed enum.

Call that a +1 from me on your idea :slight_smile:

  - Doug


(Jordan Rose) #4

I still have reservations about this:

- Most of these strings are not things you switch on, making the enum-ness not particularly interesting. I'd be happier with a RawRepresentable struct.
- Our current prefix-stripping logic relies on being able to see all the cases. You can do this with an enum because they're all declared in one block, but string constants they may cross multiple files. This isn't impossible to deal with, but I think it's counterintuitive. (An alternative would be to only use the type name for stripping each value individually.)
- I'm not sure where the line is between "an open set of choices represented as strings" and "strings with some defaults defined". (Very few of these are truly closed sets; if we add a new UIFont attribute next year, it should prefix-strip like everything else.)
- As a nitpick, I think a typedef for 'NSString' rather than 'NSString *' would be preferred, so that the '*' still shows up in the declaration in Objective-C. This is just a feeling though, and maybe it's just clinging to the way things are done now.

Jordan

···

On Jan 17, 2016, at 22:10 , Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 17, 2016, at 7:13 PM, Jeff Kelley via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

A lot of Cocoa APIs have long lists of constant values, typically NSStrings. I’d like to pitch a way to import them as enums with associated types. I can write up a full proposal if people think this is a good idea, but here’s my thinking:

Let’s take the error domains in NSError.h for a quick example. These entries in the header:

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

turn into this in the Swift interface:

public let NSCocoaErrorDomain: String
public let NSPOSIXErrorDomain: String
public let NSOSStatusErrorDomain: String
public let NSMachErrorDomain: String

What I’m proposing is a way to import those as an enum instead. Similar to how we mark sections of Objective-C code with NS_ASSUME_NONNULL_BEGIN, we could mark it with something like NS_CASE_LIST_BEGIN. Then, this code:

NS_CASE_LIST_BEGIN;

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

NS_CASE_LIST_END;

would be imported as follows:

enum ErrorDomain : String {
    case Cocoa
    case POSIX
    case OSStatus
    case Mach
}

I can think of a lot of areas in Cocoa where these APIs could make things much more type-safe in Swift. Is this a good idea? Would people use this?

FWIW, this has come up a number of times in discussions among Swift developers (although not, IIRC, on swift-evolution). Our current favored way to write this in (Objective-)C would be with a new typedef of NSString * that has some special attribute on it, e.g.,

  typedef NSString * NSErrorDomain __attribute__((enum(string)));

  FOUNDATION_EXPORT NSErrorDomain const NSCocoaErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSPOSIXErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSOSStatusErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSMachErrorDomain;

The typedef would import as a String-backed enum and all of the string constants declared with that typedef within the same module as the typedef would become cases of that enum. String constants declared with that typedef in a *different* module would become “static lets” within extensions of the String-backed enum.

Call that a +1 from me on your idea :slight_smile:


(Jeff Kelley) #5

I’ve got an in-progress proposal written up here:

https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md

I’ll leave it there for any additional comments/feedback, then submit later today.

Jeff Kelley

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

···

On Jan 18, 2016, at 9:52 AM, Jeff Kelley <SlaunchaMan@gmail.com> wrote:

Oh, that’s nice! Using the typedef would also allow Objective-C methods to be annotated with the type they expect while still allowing arbitrary strings for APIs where that’s necessary. Not only does that help in Swift—you wouldn’t have to use rawValue to pull the string values out when vending to the platform API—but it would give new developers to Apple platforms a head start on finding where those constants are defined in the headers.

I’ll write this up as a formal proposal using the typedef method, though I’m still all ears for any other comments/suggestions.

Jeff Kelley

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

On Jan 18, 2016, at 1:10 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Jan 17, 2016, at 7:13 PM, Jeff Kelley via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

A lot of Cocoa APIs have long lists of constant values, typically NSStrings. I’d like to pitch a way to import them as enums with associated types. I can write up a full proposal if people think this is a good idea, but here’s my thinking:

Let’s take the error domains in NSError.h for a quick example. These entries in the header:

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

turn into this in the Swift interface:

public let NSCocoaErrorDomain: String
public let NSPOSIXErrorDomain: String
public let NSOSStatusErrorDomain: String
public let NSMachErrorDomain: String

What I’m proposing is a way to import those as an enum instead. Similar to how we mark sections of Objective-C code with NS_ASSUME_NONNULL_BEGIN, we could mark it with something like NS_CASE_LIST_BEGIN. Then, this code:

NS_CASE_LIST_BEGIN;

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

NS_CASE_LIST_END;

would be imported as follows:

enum ErrorDomain : String {
    case Cocoa
    case POSIX
    case OSStatus
    case Mach
}

I can think of a lot of areas in Cocoa where these APIs could make things much more type-safe in Swift. Is this a good idea? Would people use this?

FWIW, this has come up a number of times in discussions among Swift developers (although not, IIRC, on swift-evolution). Our current favored way to write this in (Objective-)C would be with a new typedef of NSString * that has some special attribute on it, e.g.,

  typedef NSString * NSErrorDomain __attribute__((enum(string)));

  FOUNDATION_EXPORT NSErrorDomain const NSCocoaErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSPOSIXErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSOSStatusErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSMachErrorDomain;

The typedef would import as a String-backed enum and all of the string constants declared with that typedef within the same module as the typedef would become cases of that enum. String constants declared with that typedef in a *different* module would become “static lets” within extensions of the String-backed enum.

Call that a +1 from me on your idea :slight_smile:

  - Doug


(Douglas Gregor) #6

A lot of Cocoa APIs have long lists of constant values, typically NSStrings. I’d like to pitch a way to import them as enums with associated types. I can write up a full proposal if people think this is a good idea, but here’s my thinking:

Let’s take the error domains in NSError.h for a quick example. These entries in the header:

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

turn into this in the Swift interface:

public let NSCocoaErrorDomain: String
public let NSPOSIXErrorDomain: String
public let NSOSStatusErrorDomain: String
public let NSMachErrorDomain: String

What I’m proposing is a way to import those as an enum instead. Similar to how we mark sections of Objective-C code with NS_ASSUME_NONNULL_BEGIN, we could mark it with something like NS_CASE_LIST_BEGIN. Then, this code:

NS_CASE_LIST_BEGIN;

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

NS_CASE_LIST_END;

would be imported as follows:

enum ErrorDomain : String {
    case Cocoa
    case POSIX
    case OSStatus
    case Mach
}

I can think of a lot of areas in Cocoa where these APIs could make things much more type-safe in Swift. Is this a good idea? Would people use this?

FWIW, this has come up a number of times in discussions among Swift developers (although not, IIRC, on swift-evolution). Our current favored way to write this in (Objective-)C would be with a new typedef of NSString * that has some special attribute on it, e.g.,

  typedef NSString * NSErrorDomain __attribute__((enum(string)));

  FOUNDATION_EXPORT NSErrorDomain const NSCocoaErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSPOSIXErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSOSStatusErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSMachErrorDomain;

The typedef would import as a String-backed enum and all of the string constants declared with that typedef within the same module as the typedef would become cases of that enum. String constants declared with that typedef in a *different* module would become “static lets” within extensions of the String-backed enum.

Call that a +1 from me on your idea :slight_smile:

I still have reservations about this:

- Most of these strings are not things you switch on, making the enum-ness not particularly interesting. I'd be happier with a RawRepresentable struct.

I think a RawRepresentable struct captures the intended semantics well. Jeff, what do you think?

- Our current prefix-stripping logic relies on being able to see all the cases. You can do this with an enum because they're all declared in one block, but string constants they may cross multiple files. This isn't impossible to deal with, but I think it's counterintuitive. (An alternative would be to only use the type name for stripping each value individually.)

Our two options are to compare against just the type name or to compare against just the set of cases that come from the module that defines the typedef… except the latter might not be unique because typedefs can be redeclared in Objective-C and C++. I think that says we should just compare against the type name.

- I'm not sure where the line is between "an open set of choices represented as strings" and "strings with some defaults defined". (Very few of these are truly closed sets; if we add a new UIFont attribute next year, it should prefix-strip like everything else.)

Perhaps the line is a somewhat squishy “it is rare or impossible for a client of the API to define new values”?

- As a nitpick, I think a typedef for 'NSString' rather than 'NSString *' would be preferred, so that the '*' still shows up in the declaration in Objective-C. This is just a feeling though, and maybe it's just clinging to the way things are done now.’

WFM.

  - Doug

···

On Jan 20, 2016, at 2:11 PM, Jordan Rose <jordan_rose@apple.com> wrote:

On Jan 17, 2016, at 22:10 , Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 17, 2016, at 7:13 PM, Jeff Kelley via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Philippe Hausler) #7

One note to those constants; there are a number of them (NSError domains in particular) that are intended to be a complement to the application space defines; such that your app could define a “MyGreatAppErrorDomain” and a “MyGreatFrameworkErrorDomain” in addition to the NSCocoaErrorDomain. So this isn’t exactly an enum but more so a non closed set of values. Enums are usually closed sets so that they can be switched upon exhaustively. Others would definitely improve the state of affairs for both objc and swift in that technically you could get a warning/error in objc if the type was not correct and there would be the swift ramifications you mentioned as well.

Examples:

@interface NSError : NSObject <NSCopying, NSSecureCoding>

- (instancetype)initWithDomain:(NSErrorDomain)domain code:(NSInteger)code userInfo:(nullable NSDictionary *)dict

@end

this would mean that the following code would not work (or should produce a warning)
[[NSError alloc] initWithDomain:@“MyAppDomain” code:42 userInfo:nil];

moreover in swift:
NSError(“MyAppDomain”, code:42, userInfo:nil) // would this even work?

On the other hand it might be useful for the (desperately in need of some updating) NSLocale method

@interface NSLocale : NSObject <NSCopying, NSSecureCoding>
...
- (nullable id)objectForKey:(NSLocaleKey)key; // we could limit the keys accepted here to just the appropriate values in the “enum"
...
@end

From a brief survey of the APIs this would be useful for they mostly seem to be “dictionary based programming” cases which expose poorly in swift because they don’t have type safe return values. Adding more swift-friendly interfaces to those APIs is not off the table; because it is not just the keys that are the issue here but the return values too (see NSLocale’s example). Not to say we couldn’t do both, but would it be the case that if the un-typed return values that access by keys are replaced with better, more safe interfaces, this still be needed?

···

On Jan 18, 2016, at 9:13 AM, Jeff Kelley via swift-evolution <swift-evolution@swift.org> wrote:

I’ve got an in-progress proposal written up here:

https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md

I’ll leave it there for any additional comments/feedback, then submit later today.

Jeff Kelley

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

On Jan 18, 2016, at 9:52 AM, Jeff Kelley <SlaunchaMan@gmail.com <mailto:SlaunchaMan@gmail.com>> wrote:

Oh, that’s nice! Using the typedef would also allow Objective-C methods to be annotated with the type they expect while still allowing arbitrary strings for APIs where that’s necessary. Not only does that help in Swift—you wouldn’t have to use rawValue to pull the string values out when vending to the platform API—but it would give new developers to Apple platforms a head start on finding where those constants are defined in the headers.

I’ll write this up as a formal proposal using the typedef method, though I’m still all ears for any other comments/suggestions.

Jeff Kelley

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

On Jan 18, 2016, at 1:10 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Jan 17, 2016, at 7:13 PM, Jeff Kelley via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

A lot of Cocoa APIs have long lists of constant values, typically NSStrings. I’d like to pitch a way to import them as enums with associated types. I can write up a full proposal if people think this is a good idea, but here’s my thinking:

Let’s take the error domains in NSError.h for a quick example. These entries in the header:

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

turn into this in the Swift interface:

public let NSCocoaErrorDomain: String
public let NSPOSIXErrorDomain: String
public let NSOSStatusErrorDomain: String
public let NSMachErrorDomain: String

What I’m proposing is a way to import those as an enum instead. Similar to how we mark sections of Objective-C code with NS_ASSUME_NONNULL_BEGIN, we could mark it with something like NS_CASE_LIST_BEGIN. Then, this code:

NS_CASE_LIST_BEGIN;

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

NS_CASE_LIST_END;

would be imported as follows:

enum ErrorDomain : String {
    case Cocoa
    case POSIX
    case OSStatus
    case Mach
}

I can think of a lot of areas in Cocoa where these APIs could make things much more type-safe in Swift. Is this a good idea? Would people use this?

FWIW, this has come up a number of times in discussions among Swift developers (although not, IIRC, on swift-evolution). Our current favored way to write this in (Objective-)C would be with a new typedef of NSString * that has some special attribute on it, e.g.,

  typedef NSString * NSErrorDomain __attribute__((enum(string)));

  FOUNDATION_EXPORT NSErrorDomain const NSCocoaErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSPOSIXErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSOSStatusErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSMachErrorDomain;

The typedef would import as a String-backed enum and all of the string constants declared with that typedef within the same module as the typedef would become cases of that enum. String constants declared with that typedef in a *different* module would become “static lets” within extensions of the String-backed enum.

Call that a +1 from me on your idea :slight_smile:

  - Doug

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


(Douglas Gregor) #8

One note to those constants; there are a number of them (NSError domains in particular) that are intended to be a complement to the application space defines; such that your app could define a “MyGreatAppErrorDomain” and a “MyGreatFrameworkErrorDomain” in addition to the NSCocoaErrorDomain. So this isn’t exactly an enum but more so a non closed set of values. Enums are usually closed sets so that they can be switched upon exhaustively. Others would definitely improve the state of affairs for both objc and swift in that technically you could get a warning/error in objc if the type was not correct and there would be the swift ramifications you mentioned as well.

Enums imported from C are effectively open, because C APIs frequently have both internal constants and add new constants with subsequent releases. So string-backed enum a aren't all that different in many cases.

I don't think error domains are a great example of this, though. For one, they're something that's tied in with the error handling model, and should be somewhat invisible. My favorite example is the set of text styles one can provide then getting a UIFont, such as UIFontTextStyleHeadline. It's more enum-like and benefits greatly from the leading dot syntax enum a provide.

···

Sent from my iPhone

On Jan 18, 2016, at 9:41 AM, Philippe Hausler via swift-evolution <swift-evolution@swift.org> wrote:

Examples:

@interface NSError : NSObject <NSCopying, NSSecureCoding>

- (instancetype)initWithDomain:(NSErrorDomain)domain code:(NSInteger)code userInfo:(nullable NSDictionary *)dict

@end

this would mean that the following code would not work (or should produce a warning)
[[NSError alloc] initWithDomain:@“MyAppDomain” code:42 userInfo:nil];

moreover in swift:
NSError(“MyAppDomain”, code:42, userInfo:nil) // would this even work?

On the other hand it might be useful for the (desperately in need of some updating) NSLocale method

@interface NSLocale : NSObject <NSCopying, NSSecureCoding>
...
- (nullable id)objectForKey:(NSLocaleKey)key; // we could limit the keys accepted here to just the appropriate values in the “enum"
...
@end

From a brief survey of the APIs this would be useful for they mostly seem to be “dictionary based programming” cases which expose poorly in swift because they don’t have type safe return values. Adding more swift-friendly interfaces to those APIs is not off the table; because it is not just the keys that are the issue here but the return values too (see NSLocale’s example). Not to say we couldn’t do both, but would it be the case that if the un-typed return values that access by keys are replaced with better, more safe interfaces, this still be needed?

On Jan 18, 2016, at 9:13 AM, Jeff Kelley via swift-evolution <swift-evolution@swift.org> wrote:

I’ve got an in-progress proposal written up here:

https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md

I’ll leave it there for any additional comments/feedback, then submit later today.

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan | jeffkelley.org

On Jan 18, 2016, at 9:52 AM, Jeff Kelley <SlaunchaMan@gmail.com> wrote:

Oh, that’s nice! Using the typedef would also allow Objective-C methods to be annotated with the type they expect while still allowing arbitrary strings for APIs where that’s necessary. Not only does that help in Swift—you wouldn’t have to use rawValue to pull the string values out when vending to the platform API—but it would give new developers to Apple platforms a head start on finding where those constants are defined in the headers.

I’ll write this up as a formal proposal using the typedef method, though I’m still all ears for any other comments/suggestions.

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan | jeffkelley.org

On Jan 18, 2016, at 1:10 AM, Douglas Gregor <dgregor@apple.com> wrote:

On Jan 17, 2016, at 7:13 PM, Jeff Kelley via swift-evolution <swift-evolution@swift.org> wrote:

A lot of Cocoa APIs have long lists of constant values, typically NSStrings. I’d like to pitch a way to import them as enums with associated types. I can write up a full proposal if people think this is a good idea, but here’s my thinking:

Let’s take the error domains in NSError.h for a quick example. These entries in the header:

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

turn into this in the Swift interface:

public let NSCocoaErrorDomain: String
public let NSPOSIXErrorDomain: String
public let NSOSStatusErrorDomain: String
public let NSMachErrorDomain: String

What I’m proposing is a way to import those as an enum instead. Similar to how we mark sections of Objective-C code with NS_ASSUME_NONNULL_BEGIN, we could mark it with something like NS_CASE_LIST_BEGIN. Then, this code:

NS_CASE_LIST_BEGIN;

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

NS_CASE_LIST_END;

would be imported as follows:

enum ErrorDomain : String {
    case Cocoa
    case POSIX
    case OSStatus
    case Mach
}

I can think of a lot of areas in Cocoa where these APIs could make things much more type-safe in Swift. Is this a good idea? Would people use this?

FWIW, this has come up a number of times in discussions among Swift developers (although not, IIRC, on swift-evolution). Our current favored way to write this in (Objective-)C would be with a new typedef of NSString * that has some special attribute on it, e.g.,

  typedef NSString * NSErrorDomain __attribute__((enum(string)));

  FOUNDATION_EXPORT NSErrorDomain const NSCocoaErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSPOSIXErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSOSStatusErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSMachErrorDomain;

The typedef would import as a String-backed enum and all of the string constants declared with that typedef within the same module as the typedef would become cases of that enum. String constants declared with that typedef in a *different* module would become “static lets” within extensions of the String-backed enum.

Call that a +1 from me on your idea :slight_smile:

  - Doug

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

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


(Jeff Kelley) #9

One note to those constants; there are a number of them (NSError domains in particular) that are intended to be a complement to the application space defines; such that your app could define a “MyGreatAppErrorDomain” and a “MyGreatFrameworkErrorDomain” in addition to the NSCocoaErrorDomain. So this isn’t exactly an enum but more so a non closed set of values. Enums are usually closed sets so that they can be switched upon exhaustively. Others would definitely improve the state of affairs for both objc and swift in that technically you could get a warning/error in objc if the type was not correct and there would be the swift ramifications you mentioned as well.

Great point, and for that reason I switched away from NSError domains in my proposal. It’s a much more clear win to use this for something like HealthKit constants where values outside of the constants may not be used. For NSError domains, leaving the type as NSString is probably best to indicate that custom strings are supported.

On the other hand it might be useful for the (desperately in need of some updating) NSLocale method

@interface NSLocale : NSObject <NSCopying, NSSecureCoding>
...
- (nullable id)objectForKey:(NSLocaleKey)key; // we could limit the keys accepted here to just the appropriate values in the “enum"
...
@end

From a brief survey of the APIs this would be useful for they mostly seem to be “dictionary based programming” cases which expose poorly in swift because they don’t have type safe return values. Adding more swift-friendly interfaces to those APIs is not off the table; because it is not just the keys that are the issue here but the return values too (see NSLocale’s example). Not to say we couldn’t do both, but would it be the case that if the un-typed return values that access by keys are replaced with better, more safe interfaces, this still be needed?

The “dictionary based programming” examples could definitely use a bit of an API refresh, as you’ve noticed with the return types. This proposal’s real aim is APIs that use strings to mean one of a finite set of values. Take HealthKit:

+ (nullable HKQuantityType *)quantityTypeForIdentifier:(NSString *)identifier;

The return value here is typesafe, but the identifier is a string. HKTypeIdentifiers.h defines these identifiers. Here’s one set:

HK_EXTERN NSString * const HKQuantityTypeIdentifierBodyMassIndex;
HK_EXTERN NSString * const HKQuantityTypeIdentifierBodyFatPercentage;
HK_EXTERN NSString * const HKQuantityTypeIdentifierHeight;
HK_EXTERN NSString * const HKQuantityTypeIdentifierBodyMass;
HK_EXTERN NSString * const HKQuantityTypeIdentifierLeanBodyMass;

Since the user isn’t permitted to use their own identifiers here, this is an ideal case for replacement. It also gives the user a better hint as to where to find these constants. Rewriting the Objective-C method along these lines:

+ (nullable HKQuantityType *)quantityTypeForIdentifier:(HKQuantityTypeIdentifier)identifier;

Would give the user a hint in the name of the type for where to look in the documentation for these identifiers.

I think frameworks like HealthKit with constant string identifiers would benefit greatly from this change, as would other frameworks like CoreText with opaque keys and values used to construct dictionaries.

Jeff Kelley

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

···

On Jan 18, 2016, at 12:41 PM, Philippe Hausler <phausler@apple.com> wrote:


(Jeff Kelley) #10

A lot of Cocoa APIs have long lists of constant values, typically NSStrings. I’d like to pitch a way to import them as enums with associated types. I can write up a full proposal if people think this is a good idea, but here’s my thinking:

Let’s take the error domains in NSError.h for a quick example. These entries in the header:

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

turn into this in the Swift interface:

public let NSCocoaErrorDomain: String
public let NSPOSIXErrorDomain: String
public let NSOSStatusErrorDomain: String
public let NSMachErrorDomain: String

What I’m proposing is a way to import those as an enum instead. Similar to how we mark sections of Objective-C code with NS_ASSUME_NONNULL_BEGIN, we could mark it with something like NS_CASE_LIST_BEGIN. Then, this code:

NS_CASE_LIST_BEGIN;

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

NS_CASE_LIST_END;

would be imported as follows:

enum ErrorDomain : String {
    case Cocoa
    case POSIX
    case OSStatus
    case Mach
}

I can think of a lot of areas in Cocoa where these APIs could make things much more type-safe in Swift. Is this a good idea? Would people use this?

FWIW, this has come up a number of times in discussions among Swift developers (although not, IIRC, on swift-evolution). Our current favored way to write this in (Objective-)C would be with a new typedef of NSString * that has some special attribute on it, e.g.,

  typedef NSString * NSErrorDomain __attribute__((enum(string)));

  FOUNDATION_EXPORT NSErrorDomain const NSCocoaErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSPOSIXErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSOSStatusErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSMachErrorDomain;

The typedef would import as a String-backed enum and all of the string constants declared with that typedef within the same module as the typedef would become cases of that enum. String constants declared with that typedef in a *different* module would become “static lets” within extensions of the String-backed enum.

Call that a +1 from me on your idea :slight_smile:

I still have reservations about this:

- Most of these strings are not things you switch on, making the enum-ness not particularly interesting. I'd be happier with a RawRepresentable struct.

I think a RawRepresentable struct captures the intended semantics well. Jeff, what do you think?

I haven’t used RawRepresentable by itself before. Is this the kind of result we’d get?

struct NSErrorDomain: RawRepresentable {
    
    static let Cocoa: MyRelatedValue
    static let POSIX: MyRelatedValue
    static let OSStatus: MyRelatedValue
    static let Mach: MyRelatedValue
    
    typealias RawValue = String
    
    init?(rawValue: RawValue)
    
    var rawValue: String { get }

}

If so, I think that has the same benefits of an enum without the added baggage. We get a type for the values, a list of the public instances of the value, and the typedef in Objective-C even makes that language more readable. As long as we can still get to an instance of the value using the short dot syntax:

  let foo: NSErrorDomain = .Cocoa

Then I see no downside to a struct vs. an enum. The only reason I’d thought of an enum in the first place was that I’ve seen existing Swift code use enums to group related strings—user defaults keys, etc.

- Our current prefix-stripping logic relies on being able to see all the cases. You can do this with an enum because they're all declared in one block, but string constants they may cross multiple files. This isn't impossible to deal with, but I think it's counterintuitive. (An alternative would be to only use the type name for stripping each value individually.)

Our two options are to compare against just the type name or to compare against just the set of cases that come from the module that defines the typedef… except the latter might not be unique because typedefs can be redeclared in Objective-C and C++. I think that says we should just compare against the type name.

Using the type name for stripping seems acceptable to me.

- I'm not sure where the line is between "an open set of choices represented as strings" and "strings with some defaults defined". (Very few of these are truly closed sets; if we add a new UIFont attribute next year, it should prefix-strip like everything else.)

Perhaps the line is a somewhat squishy “it is rare or impossible for a client of the API to define new values”?

I agree. NSErrorDomain wasn’t a great example because you can define your own. My pull request used HealthKit identifiers, which are a better example as the user of the API can’t define their own.

Adding a new value in the future should definitely be as straightforward as possible, but I’m not sure what about this approach makes that more difficult.

- As a nitpick, I think a typedef for 'NSString' rather than 'NSString *' would be preferred, so that the '*' still shows up in the declaration in Objective-C. This is just a feeling though, and maybe it's just clinging to the way things are done now.’

WFM.

Yep, that seems reasonable. Thanks for your feedback, Jordan and Doug!

···

On Jan 20, 2016, at 7:41 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Jan 20, 2016, at 2:11 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Jan 17, 2016, at 22:10 , Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 17, 2016, at 7:13 PM, Jeff Kelley via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Jacob Bandes-Storch) #11

If the method param is annotated, not only does the user get a hint for
where to look in the docs, but you can omit the type name qualification,
saying quantityForTypeIdentifier(.BodyMass).

Sounds great to me.

Jacob

···

On Mon, Jan 18, 2016 at 10:20 AM, Jeff Kelley via swift-evolution < swift-evolution@swift.org> wrote:

On Jan 18, 2016, at 12:41 PM, Philippe Hausler <phausler@apple.com> wrote:

One note to those constants; there are a number of them (NSError domains
in particular) that are intended to be a complement to the application
space defines; such that your app could define a “MyGreatAppErrorDomain”
and a “MyGreatFrameworkErrorDomain” in addition to the NSCocoaErrorDomain.
So this isn’t exactly an enum but more so a non closed set of values. Enums
are usually closed sets so that they can be switched upon exhaustively.
Others would definitely improve the state of affairs for both objc and
swift in that technically you could get a warning/error in objc if the type
was not correct and there would be the swift ramifications you mentioned as
well.

Great point, and for that reason I switched away from NSError domains in
my proposal. It’s a much more clear win to use this for something like
HealthKit constants where values outside of the constants may not be used.
For NSError domains, leaving the type as NSString is probably best to
indicate that custom strings are supported.

On the other hand it might be useful for the (desperately in need of some
updating) NSLocale method

@interface NSLocale : NSObject <NSCopying, NSSecureCoding>
...
- (nullable id)objectForKey:(NSLocaleKey)key; // we could limit the keys
accepted here to just the appropriate values in the “enum"
...
@end

From a brief survey of the APIs this would be useful for they mostly seem
to be “dictionary based programming” cases which expose poorly in swift
because they don’t have type safe return values. Adding more swift-friendly
interfaces to those APIs is not off the table; because it is not just the
keys that are the issue here but the return values too (see NSLocale’s
example). Not to say we couldn’t do both, but would it be the case that if
the un-typed return values that access by keys are replaced with better,
more safe interfaces, this still be needed?

The “dictionary based programming” examples could definitely use a bit of
an API refresh, as you’ve noticed with the return types. This proposal’s
real aim is APIs that use strings to mean one of a finite set of values.
Take HealthKit:

+ (nullable HKQuantityType *)quantityTypeForIdentifier:(NSString
*)identifier;

The return value here is typesafe, but the identifier is a string.
HKTypeIdentifiers.h defines these identifiers. Here’s one set:

HK_EXTERN NSString * const HKQuantityTypeIdentifierBodyMassIndex;
HK_EXTERN NSString * const HKQuantityTypeIdentifierBodyFatPercentage;
HK_EXTERN NSString * const HKQuantityTypeIdentifierHeight;
HK_EXTERN NSString * const HKQuantityTypeIdentifierBodyMass;
HK_EXTERN NSString * const HKQuantityTypeIdentifierLeanBodyMass;

Since the user isn’t permitted to use their own identifiers here, this is
an ideal case for replacement. It also gives the user a better hint as to
where to find these constants. Rewriting the Objective-C method along these
lines:

+ (nullable HKQuantityType
*)quantityTypeForIdentifier:(HKQuantityTypeIdentifier)identifier;

Would give the user a hint in the name of the type for where to look in
the documentation for these identifiers.

I think frameworks like HealthKit with constant string identifiers would
benefit greatly from this change, as would other frameworks like CoreText
with opaque keys and values used to construct dictionaries.

Jeff Kelley

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

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


(Douglas Gregor) #12

A lot of Cocoa APIs have long lists of constant values, typically NSStrings. I’d like to pitch a way to import them as enums with associated types. I can write up a full proposal if people think this is a good idea, but here’s my thinking:

Let’s take the error domains in NSError.h for a quick example. These entries in the header:

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

turn into this in the Swift interface:

public let NSCocoaErrorDomain: String
public let NSPOSIXErrorDomain: String
public let NSOSStatusErrorDomain: String
public let NSMachErrorDomain: String

What I’m proposing is a way to import those as an enum instead. Similar to how we mark sections of Objective-C code with NS_ASSUME_NONNULL_BEGIN, we could mark it with something like NS_CASE_LIST_BEGIN. Then, this code:

NS_CASE_LIST_BEGIN;

FOUNDATION_EXPORT NSString *const NSCocoaErrorDomain;
FOUNDATION_EXPORT NSString *const NSPOSIXErrorDomain;
FOUNDATION_EXPORT NSString *const NSOSStatusErrorDomain;
FOUNDATION_EXPORT NSString *const NSMachErrorDomain;

NS_CASE_LIST_END;

would be imported as follows:

enum ErrorDomain : String {
    case Cocoa
    case POSIX
    case OSStatus
    case Mach
}

I can think of a lot of areas in Cocoa where these APIs could make things much more type-safe in Swift. Is this a good idea? Would people use this?

FWIW, this has come up a number of times in discussions among Swift developers (although not, IIRC, on swift-evolution). Our current favored way to write this in (Objective-)C would be with a new typedef of NSString * that has some special attribute on it, e.g.,

  typedef NSString * NSErrorDomain __attribute__((enum(string)));

  FOUNDATION_EXPORT NSErrorDomain const NSCocoaErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSPOSIXErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSOSStatusErrorDomain;
  FOUNDATION_EXPORT NSErrorDomain const NSMachErrorDomain;

The typedef would import as a String-backed enum and all of the string constants declared with that typedef within the same module as the typedef would become cases of that enum. String constants declared with that typedef in a *different* module would become “static lets” within extensions of the String-backed enum.

Call that a +1 from me on your idea :slight_smile:

I still have reservations about this:

- Most of these strings are not things you switch on, making the enum-ness not particularly interesting. I'd be happier with a RawRepresentable struct.

I think a RawRepresentable struct captures the intended semantics well. Jeff, what do you think?

I haven’t used RawRepresentable by itself before. Is this the kind of result we’d get?

struct NSErrorDomain: RawRepresentable {
    
    static let Cocoa: MyRelatedValue
    static let POSIX: MyRelatedValue
    static let OSStatus: MyRelatedValue
    static let Mach: MyRelatedValue
    
    typealias RawValue = String
    
    init?(rawValue: RawValue)

This initializer should be non-failable; we’ll accept any raw value.

    var rawValue: String { get }

Otherwise, yes, this is it.

If so, I think that has the same benefits of an enum without the added baggage. We get a type for the values, a list of the public instances of the value, and the typedef in Objective-C even makes that language more readable. As long as we can still get to an instance of the value using the short dot syntax:

  let foo: NSErrorDomain = .Cocoa

Yes, that works with static lets.

Then I see no downside to a struct vs. an enum. The only reason I’d thought of an enum in the first place was that I’ve seen existing Swift code use enums to group related strings—user defaults keys, etc.

Right. struct seems to model the uses of these string constants betters. FWIW, I suggest that you put “String” somewhere in the title of your proposal when you revise it.

Adding a new value in the future should definitely be as straightforward as possible, but I’m not sure what about this approach makes that more difficult.

I think it’s just a matter of declaring a new constant on the Objective-C side, and it’ll get pulled into the struct definition or (if the new constant is in a different module) an extension thereof.

  - Doug

···

On Jan 20, 2016, at 6:39 PM, Jeff Kelley <slaunchaman@gmail.com> wrote:

On Jan 20, 2016, at 7:41 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Jan 20, 2016, at 2:11 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Jan 17, 2016, at 22:10 , Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 17, 2016, at 7:13 PM, Jeff Kelley via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Jeff Kelley) #13

I have updated the proposal <https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md> to use struct over enum (https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md).

···

On Jan 20, 2016, at 11:01 PM, Douglas Gregor <dgregor@apple.com> wrote:

FWIW, I suggest that you put “String” somewhere in the title of your proposal when you revise it.

Would this need to be limited to strings? There are other “magic constants” that could conceivably be imported as structs—I’m thinking, for instance, error codes.

Jeff Kelley

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


(Douglas Gregor) #14

Error codes are *usually* enums, but that’s a good point.

  - Doug

···

On Jan 22, 2016, at 1:14 PM, Jeff Kelley via swift-evolution <swift-evolution@swift.org> wrote:

I have updated the proposal <https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md> to use struct over enum (https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md).

On Jan 20, 2016, at 11:01 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

FWIW, I suggest that you put “String” somewhere in the title of your proposal when you revise it.

Would this need to be limited to strings? There are other “magic constants” that could conceivably be imported as structs—I’m thinking, for instance, error codes.


(Jeff Kelley) #15

Hi everyone!

  I know there has been a lot of activity on other, larger proposals, but does anyone have any more feedback here? Proposal link: https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md

Jeff Kelley

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

···

On Jan 22, 2016, at 4:14 PM, Jeff Kelley <SlaunchaMan@gmail.com> wrote:

I have updated the proposal <https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md> to use struct over enum (https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md).

On Jan 20, 2016, at 11:01 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

FWIW, I suggest that you put “String” somewhere in the title of your proposal when you revise it.

Would this need to be limited to strings? There are other “magic constants” that could conceivably be imported as structs—I’m thinking, for instance, error codes.

Jeff Kelley

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


(Zachary Waldowski) #16

+1 one to the updated proposal. I like the change a lot, even if it's
even more pain for the poor framework teams. :wink:

Apropos of nothing, a coworker reminded me today of something that would
fit well into this proposal, even if only as a clarifying example: UTIs
are a great instance of "strongly-typedef'ed strings", with the
possibility of user-defined cases, and whose use in Cocoa APIs is often
ambiguous. There are several hundred constants, too, so it'd be a good
stress-test for everything in the proposal.

Cheers! Zachary Waldowski zach@waldowski.me

···

On Fri, Jan 22, 2016, at 04:14 PM, Jeff Kelley via swift-evolution wrote:

I have updated the proposal[1] to use struct over enum
(https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md)
.

On Jan 20, 2016, at 11:01 PM, Douglas Gregor >> <dgregor@apple.com> wrote:

FWIW, I suggest that you put “String” somewhere in the title of your
proposal when you revise it.

Would this need to be limited to strings? There are other “magic
constants” that could conceivably be imported as structs—I’m thinking,
for instance, error codes.

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan[2] | jeffkelley.org

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

Links:

  1. https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md
  2. https://twitter.com/SlaunchaMan


(Douglas Gregor) #17

Hi everyone!

  I know there has been a lot of activity on other, larger proposals, but does anyone have any more feedback here? Proposal link: https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md

I have a few more comments.

typedef NSString * HKQuantityTypeIdentifier __attribute__((struct(string)));

I don’t think “struct(string)” is the right (Objective-)C attribute here. For one, we don’t need to restate the type “string” at all: we know what the type is because it’s the type behind the typedef. Additionally, this is a Swift-specific mapping. How about __attribute__((swift_struct_wrapper)), to indicate that it’s Swift-specific and that the annotated type is a wrapper struct?

I think you need to clarify a few things:

(1) How are the names of the constants transformed? It looks like you’re chopping off the common prefix between the type name and the enum constant name. Should the result have initial lowercase letters?
(2) What happens if additional constants are defined in a different module? (I assume that the new “static let”s go into an extension)

Otherwise, LGTM!

  - Doug

···

On Jan 29, 2016, at 9:19 AM, Jeff Kelley via swift-evolution <swift-evolution@swift.org> wrote:

Jeff Kelley

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

On Jan 22, 2016, at 4:14 PM, Jeff Kelley <SlaunchaMan@gmail.com <mailto:SlaunchaMan@gmail.com>> wrote:

I have updated the proposal <https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md> to use struct over enum (https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md).

On Jan 20, 2016, at 11:01 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

FWIW, I suggest that you put “String” somewhere in the title of your proposal when you revise it.

Would this need to be limited to strings? There are other “magic constants” that could conceivably be imported as structs—I’m thinking, for instance, error codes.

Jeff Kelley

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

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


(Douglas Gregor) #18

Hi everyone!

  I know there has been a lot of activity on other, larger proposals, but does anyone have any more feedback here? Proposal link: https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md

I have a few more comments.

typedef NSString * HKQuantityTypeIdentifier __attribute__((struct(string)));

I don’t think “struct(string)” is the right (Objective-)C attribute here. For one, we don’t need to restate the type “string” at all: we know what the type is because it’s the type behind the typedef. Additionally, this is a Swift-specific mapping. How about __attribute__((swift_struct_wrapper)), to indicate that it’s Swift-specific and that the annotated type is a wrapper struct?

Hrm, maybe

   __attribute__((swift_wrapper(struct)))

to leave space for

  __attribute__((swift_wrapper(enum))

which I’m sure we will want as well. I’m somewhat inclined to want both in the same proposal so we don’t have to revisit this later, because there are use cases for both.

···

On Jan 29, 2016, at 10:48 AM, Douglas Gregor <dgregor@apple.com> wrote:

On Jan 29, 2016, at 9:19 AM, Jeff Kelley via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I think you need to clarify a few things:

(1) How are the names of the constants transformed? It looks like you’re chopping off the common prefix between the type name and the enum constant name. Should the result have initial lowercase letters?
(2) What happens if additional constants are defined in a different module? (I assume that the new “static let”s go into an extension)

Otherwise, LGTM!

  - Doug

Jeff Kelley

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

On Jan 22, 2016, at 4:14 PM, Jeff Kelley <SlaunchaMan@gmail.com <mailto:SlaunchaMan@gmail.com>> wrote:

I have updated the proposal <https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md> to use struct over enum (https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md).

On Jan 20, 2016, at 11:01 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

FWIW, I suggest that you put “String” somewhere in the title of your proposal when you revise it.

Would this need to be limited to strings? There are other “magic constants” that could conceivably be imported as structs—I’m thinking, for instance, error codes.

Jeff Kelley

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

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


(Jeff Kelley) #19

Thanks for the feedback, everyone! I’ve updated the proposal <https://github.com/SlaunchaMan/swift-evolution/blob/import-objc-constants/proposals/0000-import-objc-constants.md> again to use both _attribute__((swift_wrapper(struct))) and __attribute__((swift_wrapper(struct))). Like Doug, I think having both fits the largest number of use cases.

I also changed the accessors on struct items to use var instead of let, which matches how option set types are imported.

I think you need to clarify a few things:

(1) How are the names of the constants transformed? It looks like you’re chopping off the common prefix between the type name and the enum constant name. Should the result have initial lowercase letters?

I’m removing both common prefixes and suffixes, if any. That way, both the “NS” and “ErrorDomain” of “NSErrorDomain” get chopped off. The initial result is uppercase to match existing imports (e.g. OptionSetType):

struct NSEnumerationOptions : OptionSetType <file:///Users/jeff/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Swift/Reference/Swift_OptionSetType_Protocol/index.html#//apple_ref/swift/intf/s:PSs13OptionSetType> {
    init(rawValue rawValue: UInt <file:///Users/jeff/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Swift/Reference/Swift_UInt_Structure/index.html#//apple_ref/swift/struct/s:Su>)
    static var Concurrent: NSEnumerationOptions <file:///Users/jeff/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/index.html#//apple_ref/swift/struct/c:@E@NSEnumerationOptions> { get }
    static var Reverse: NSEnumerationOptions <file:///Users/jeff/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset/Contents/Resources/Documents/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/index.html#//apple_ref/swift/struct/c:@E@NSEnumerationOptions> { get }
}

(2) What happens if additional constants are defined in a different module? (I assume that the new “static let”s go into an extension)

I don’t think it’s possible for another module to add cases to an enum, so for those, we’d have to make them structs instead and use an extension. There’s an example of this for structs in the new proposal.

Thanks for the feedback! This proposal is now better than ever.

Jeff Kelley

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

···

On Jan 29, 2016, at 1:48 PM, Douglas Gregor <dgregor@apple.com> wrote: