Importing NSUInteger in my own Objective-C Code as Int

Swift imports NSUInteger in Objective-C APIs as Int, but only for Frameworks like Foundation and AppKit. For other code, NSUInteger is imported simply as UInt. Is there some way to get those as Int in Swift for my Objective-C code, too? Maybe some macro in the spirit of NS_REFINED_FOR_SWIFT, NS_SWIFT_NAME, and NS_SWIFT_NOTHROW? Or probably rather something that gets applied to each parameter or return type? I didn’t find anything like that.

For example, I have an NSSegmentedCell subclass that has the following methods:

- (void)setRepresentedObject:(nullable id)anObject forSegment:(NSUInteger)segment;
- (nullable id)representedObjectForSegment:(NSUInteger)segment;

… which come through to Swift as:

func setRepresentedObject(anObject: AnyObject?, forSegment segment: UInt)
func representedObjectForSegment(segment: UInt) -> AnyObject?

Note the UInt instead of Int. This isn’t very nice because all of the NSSegmentedCell methods use Int. Also, writing code involving these methods and indices extracted from an Array isn’t nice because it requires constant conversions between UInt and Int.

A nice solution would be to have something that allows selectively annotating these parameter and return types to tell the Clang Importer how to import types like NSUInteger to Swift. I guess Frameworks like Foundation and AppKit use something like that internally to opt-in or opt-out of this conversion.
Is there something like that I can use? Am I missing something?

Cheers,

Marco

Really? I’d never noticed that. It seems wrong to me, since NSUInteger is clearly an unsigned type, so it should map to Uint.

—Jens

···

On Jan 15, 2016, at 5:19 AM, Marco Masser via swift-users <swift-users@swift.org> wrote:

Swift imports NSUInteger in Objective-C APIs as Int, but only for Frameworks like Foundation and AppKit. For other code, NSUInteger is imported simply as UInt.

Swift imports NSUInteger in Objective-C APIs as Int, but only for Frameworks like Foundation and AppKit. For other code, NSUInteger is imported simply as UInt.

Really? I’d never noticed that. It seems wrong to me, since NSUInteger is clearly an unsigned type, so it should map to Uint.

That is actually intentional on Apple’s part. I think it was mentioned in the WWDC sessions when Swift was introduced (don’t remember which one) and the “Using Swift with Cocoa and Objective-C” book mentions it since the very first release, if I’m not mistaken. To quite the section Foundation Data Types <Swift | Apple Developer Documentation

Swift bridges NSUInteger and NSInteger to Int. Both of these types come over as Int in Foundation APIs. Int is used for consistency whenever possible in Swift, but the UInt type is available if you require an unsigned integer type.

Clearly, Foundation APIs (and other Apple frameworks) get a special treatment here. What I’m looking for is a way to get that for my code, too.

···

On 2016-01-15, at 19:19, Jens Alfke <jens@mooseyard.com> wrote:

On Jan 15, 2016, at 5:19 AM, Marco Masser via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Yes, it is intentional, we do the same for size_t.

As far as I know, there is no way to get the same behavior for your own code. It would be reasonable to introduce a clang attribute for that though.

-Chris

···

On Jan 15, 2016, at 10:19 AM, Jens Alfke via swift-users <swift-users@swift.org> wrote:

On Jan 15, 2016, at 5:19 AM, Marco Masser via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Swift imports NSUInteger in Objective-C APIs as Int, but only for Frameworks like Foundation and AppKit. For other code, NSUInteger is imported simply as UInt.

Really? I’d never noticed that. It seems wrong to me, since NSUInteger is clearly an unsigned type, so it should map to Uint.

Should I file a request for that somewhere?

···

On 2016-01-17, at 22:01, Chris Lattner <clattner@apple.com> wrote:

On Jan 15, 2016, at 10:19 AM, Jens Alfke via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

On Jan 15, 2016, at 5:19 AM, Marco Masser via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Swift imports NSUInteger in Objective-C APIs as Int, but only for Frameworks like Foundation and AppKit. For other code, NSUInteger is imported simply as UInt.

Really? I’d never noticed that. It seems wrong to me, since NSUInteger is clearly an unsigned type, so it should map to Uint.

Yes, it is intentional, we do the same for size_t.

As far as I know, there is no way to get the same behavior for your own code. It would be reasonable to introduce a clang attribute for that though.

I've been meaning to do an investigation to see about just doing this automatically for user code—i.e. how many of the top frameworks in CocoaPods are using NSUInteger to really mean "word-sized unsigned integer", as opposed to "the Apple framework uses NSUInteger here so I will too".

By the way, the original rational for the NSUInteger -> Int mapping is mostly that our frameworks weren't consistent about the use of NSInteger/CFIndex vs. NSUInteger. Since Swift makes signedness conversions explicit, this would become an unnecessary burden. (For example, -[UITableViewDataSource numberOfSectionsInTableView:] <UITableViewDataSource | Apple Developer Documentation; uses NSInteger, but NSArray.count <NSArray | Apple Developer Documentation; uses NSUInteger.) We found that there would almost never be an NSUInteger value that actually went beyond NSIntegerMax, at least partly because this is the representation of NSNotFound <https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/#//apple_ref/doc/constant_group/NSNotFound&gt;\. In cases where there was a value that went past NSIntegerMax, it was usually a unique identifier or a bitfield, i.e. not something that needed arithmetic operations. Since Swift doesn't have strange undefined behavior around its bitwise operations for signed types, there's no problem bringing these sorts of values in as Int as well. Similar arguments apply to size_t and rsize_t, which never actually have to represent an integer spanning half of the address space. (If you actually need such a thing for non-size-related reasons, the right type would be uintptr_t, which is imported as UInt.)

One exception is that we do keep the base of NS_OPTIONS enums as NSUInteger if so specified. In that case, we want to keep the integer constant valid as is and not force a spurious minus sign on the 32nd option.

The main loss here is that NSUInteger always carries the precondition of the value being non-negative, but again, existing frameworks already weren't consistent about that, so we considered it an acceptable trade-off.

Jordan

···

On Jan 17, 2016, at 13:01, Chris Lattner via swift-users <swift-users@swift.org> wrote:

On Jan 15, 2016, at 10:19 AM, Jens Alfke via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

On Jan 15, 2016, at 5:19 AM, Marco Masser via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Swift imports NSUInteger in Objective-C APIs as Int, but only for Frameworks like Foundation and AppKit. For other code, NSUInteger is imported simply as UInt.

Really? I’d never noticed that. It seems wrong to me, since NSUInteger is clearly an unsigned type, so it should map to Uint.

Yes, it is intentional, we do the same for size_t.

As far as I know, there is no way to get the same behavior for your own code. It would be reasonable to introduce a clang attribute for that though.

Sure. bugs.swift.org <Issues · apple/swift · GitHub; is a good place for it.

-Chris

···

On Jan 18, 2016, at 1:15 AM, Marco Masser <lists@duckcode.com> wrote:

On 2016-01-17, at 22:01, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

On Jan 15, 2016, at 10:19 AM, Jens Alfke via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

On Jan 15, 2016, at 5:19 AM, Marco Masser via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Swift imports NSUInteger in Objective-C APIs as Int, but only for Frameworks like Foundation and AppKit. For other code, NSUInteger is imported simply as UInt.

Really? I’d never noticed that. It seems wrong to me, since NSUInteger is clearly an unsigned type, so it should map to Uint.

Yes, it is intentional, we do the same for size_t.

As far as I know, there is no way to get the same behavior for your own code. It would be reasonable to introduce a clang attribute for that though.

Should I file a request for that somewhere?

That is very interesting, thanks for sharing your thoughts and the implementers’ rationale on this!

···

On 2016-01-19, at 20:43, Jordan Rose via swift-users <swift-users@swift.org> wrote:

On Jan 17, 2016, at 13:01, Chris Lattner via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

On Jan 15, 2016, at 10:19 AM, Jens Alfke via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

On Jan 15, 2016, at 5:19 AM, Marco Masser via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Swift imports NSUInteger in Objective-C APIs as Int, but only for Frameworks like Foundation and AppKit. For other code, NSUInteger is imported simply as UInt.

Really? I’d never noticed that. It seems wrong to me, since NSUInteger is clearly an unsigned type, so it should map to Uint.

Yes, it is intentional, we do the same for size_t.

As far as I know, there is no way to get the same behavior for your own code. It would be reasonable to introduce a clang attribute for that though.

I've been meaning to do an investigation to see about just doing this automatically for user code—i.e. how many of the top frameworks in CocoaPods are using NSUInteger to really mean "word-sized unsigned integer", as opposed to "the Apple framework uses NSUInteger here so I will too".

By the way, the original rational for the NSUInteger -> Int mapping is mostly that our frameworks weren't consistent about the use of NSInteger/CFIndex vs. NSUInteger. Since Swift makes signedness conversions explicit, this would become an unnecessary burden. (For example, -[UITableViewDataSource numberOfSectionsInTableView:] <UITableViewDataSource | Apple Developer Documentation; uses NSInteger, but NSArray.count <NSArray | Apple Developer Documentation; uses NSUInteger.) We found that there would almost never be an NSUInteger value that actually went beyond NSIntegerMax, at least partly because this is the representation of NSNotFound <https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/#//apple_ref/doc/constant_group/NSNotFound&gt;\. In cases where there was a value that went past NSIntegerMax, it was usually a unique identifier or a bitfield, i.e. not something that needed arithmetic operations. Since Swift doesn't have strange undefined behavior around its bitwise operations for signed types, there's no problem bringing these sorts of values in as Int as well. Similar arguments apply to size_t and rsize_t, which never actually have to represent an integer spanning half of the address space. (If you actually need such a thing for non-size-related reasons, the right type would be uintptr_t, which is imported as UInt.)

One exception is that we do keep the base of NS_OPTIONS enums as NSUInteger if so specified. In that case, we want to keep the integer constant valid as is and not force a spurious minus sign on the 32nd option.

The main loss here is that NSUInteger always carries the precondition of the value being non-negative, but again, existing frameworks already weren't consistent about that, so we considered it an acceptable trade-off.

Jordan

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

Should I file a request for that somewhere?

Sure. bugs.swift.org <Issues · apple/swift · GitHub; is a good place for it.

I wasn’t sure if that bug belonged on bugs.swift.org or llvm.org/bugs/, since you wrote that it’s clang related.

Anyways: Done. [SR-583] Add clang attribute to import NSUInteger in C/Objective-C APIs as Int (like Foundation) · Issue #43200 · apple/swift · GitHub