JetForMe
(Rick M)
1
I'm still struggling to use SDL from Swift. I'm using a Swift library I found, which was working for me a couple weeks ago, but now I'm trying to add text support to it and I can't build any of it.
SDL_PixelFormatEnum I believe is an imported-from-C enum; at least SDL_PIXELFORMAT_INDEX1LSB is imported from the C headers. I thought those always came in as Int, so I'm not sure what's going on here.
/Users/rmann/Projects/Personal/InfoDisplay/InfoDisplay/Packages/SDL/Sources/SDL/PixelFormat.swift:83:60: error: initializer 'init(_:)' requires that 'SDL_PixelFormatEnum' conform to 'BinaryInteger'
static let index1LSB = SDLPixelFormat.Format(rawValue: UInt32(SDL_PIXELFORMAT_INDEX1LSB))
^
Swift.UnsignedInteger:2:23: note: where 'T' = 'SDL_PixelFormatEnum'
@inlinable public init<T>(_ source: T) where T : BinaryInteger
^
SDLPixelFormat.Format looks like:
public extension SDLPixelFormat {
/// SDL Pixel Format Enum
struct Format: RawRepresentable, Equatable, Hashable {
public let rawValue: UInt32
public init(rawValue: UInt32) {
self.rawValue = rawValue
}
}
}
and in an extension:
public extension SDLPixelFormat.Format {
/// SDL_PIXELFORMAT_INDEX1LSB
static let index1LSB = SDLPixelFormat.Format(rawValue: UInt32(SDL_PIXELFORMAT_INDEX1LSB))
eskimo
(Quinn “The Eskimo!”)
2
What’s happened here is that the Swift importer has decided to import SDL_PixelFormatEnum as a struct. Thus, in Swift, SDL_PIXELFORMAT_INDEX1LSB is not an enum case but a struct constant. Thus, this line:
static let index1LSB = SDLPixelFormat.Format(rawValue: UInt32(SDL_PIXELFORMAT_INDEX1LSB))
is trying to initialise a UInt32 from an SDL_PixelFormatEnum. To make this work, do this:
static let index1LSB = SDLPixelFormat.Format(rawValue: UInt32(SDL_PIXELFORMAT_INDEX1LSB.rawValue))
which you can simplify to this:
static let index1LSB = SDLPixelFormat.Format(rawValue: SDL_PIXELFORMAT_INDEX1LSB.rawValue)
I’m not sure what the exact rules the importer uses to decide whether to create an enum or a struct, but I can see what it chose to do the latter in this case. The SDL_PixelFormatEnum is a complex beast, full of macros and potentially overlapping cases.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
2 Likes
JetForMe
(Rick M)
3
Thanks Quinn. I'm trying to understand why it worked a couple weeks ago. Is there a chance this import behavior changed from Swift 5.0 to 5.1?
I seem to have some trouble keeping Xcode and SPM in sync, in the sense that Xcode won't always rebuild all the dependencies, but if I rebuild them with SPM (or attempt to, because it used Swift 5.1 and would fail), now Xcode fails when it tries to build my app. It's very confusing.
eskimo
(Quinn “The Eskimo!”)
4
Is there a chance this import behavior changed from Swift 5.0 to 5.1?
I dug out my test project out of the trash and opened it in Xcode 11 (hence Swift 5). It behaves the same way, that is, SDL_PixelFormatEnum is imported as a struct, not an enum. *shrug*
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
JetForMe
(Rick M)
5
I discovered something tonight. The difference in import seems to be if the enum is declared as
enum {
...
};
or
typedef enum {
...
} EnumName;
The latter comes in as a struct.
eskimo
(Quinn “The Eskimo!”)
6
Hmmm, that’s not what I’m seeing. I created a new command-line tool test project, added an Objective-C class to give me a bridging header, and then added this to that bridging header:
enum Test1 {
Test1Case1,
Test1Case2
};
typedef enum {
Test2Case1,
Test2Case2
} Test2;
Both Test1 and Test2 comes through as structs.
This is with Xcode 11.3.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
JetForMe
(Rick M)
7
What I saw was that SDL 2.0.9 (installed by default on Raspbian Buster) has an anonymous enum. SDL 2.0.10 (what I had installed on macOS) has the typedefed and named enum.
eskimo
(Quinn “The Eskimo!”)
8
The one sure-fire way to get an enum to come through as an enum is to use NS_ENUM, so I dug into what that actually does. If you have a declaration like this:
typedef NS_ENUM(NSInteger, QQQTest) {
QQQTestCase1,
QQQTestCase2
};
it expands to this:
typedef enum __attribute__((enum_extensibility(open))) QQQTest : NSInteger QQQTest;
enum QQQTest : NSInteger {
QQQTestCase1,
QQQTestCase2
};
Note I’ve rewrapped this to make it easier to read.
I then started removing goo until it switched back to a struct. Eventually I got it down to this:
enum __attribute__((enum_extensibility(open))) QQQTest8 {
QQQTest8Case1,
QQQTest8Case2
};
It seems that the enum_extensibility attribute is key here. And based on that I took another look at SE-0192 Handling Future Enum Cases, and it turns out that this discusses this pretty extensively. Specifically, in the C enums section it says:
This section only applies to enums that Swift considers "true enums",
rather than option sets or funny integer values … the presence of
enum_extensibility(closed) or enum_extensibility(open) will
instruct Swift to treat the enum as a "true enum".
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
1 Like
JetForMe
(Rick M)
9
Thanks for the deep dive. I don't mind if it's a struct or an enum, I just want it to stay consistent. Unfortunately, I can't use NS_ENUM, it's a header provided by a third-party. I'd love to add apinotes, but that's hard enough to do, and I don't know if it provides a facility for specifying enum/struct. The SDL header doesn't have anything special in the way it declares its enum, just enum { … }; in 2.0.9, and typedef enum { … } Name; in 2.0.10.
In any case this will all be moot if I can't get SPM to issue the right linker flags.
Thanks again!