@jrose I heavily rewrote the proposal to address @tkremenek's remarks and to improve clarity. Could you give me some feedback on it? By the way, I don't understand why we need to stick with version 4.1.50
if this proposal is accepted. Can you give me an example that the proposal would break?
A bit late in responding, but we still need 4.1.50 so that 4.1 compilers can distinguish between Swift 4.1.x and Swift 4.2-in-4-mode! If you need to be compatible with a 4.0 or 4.1 compiler, you have to keep using #if swift
a little longer.
Hello everybody,
I'd like to contribute to this thread with a sample code. It not only exemplifies this thread, but this other one as well: C interoperability, combinations of library and OS versions thread.
It shows how many different techniques have to be used in order to crawl through compiler options:
#if CUSTOM_OPTION
-
#if swift(...) || ...
(the monster @hartbit wants to replace with#if compiler(...)
) if #available(...)
- the really nice @jrose's workaround against code duplication.
- very long comments because otherwise this piece of code is really really challenging.
static func api(_ db: Database) -> UnsafePointer<fts5_api> {
// Access to FTS5 is one of the rare SQLite api which was broken in
// SQLite 3.20.0+, for security reasons:
//
// Starting SQLite 3.20.0+, we need to use the new sqlite3_bind_pointer api.
// The previous way to access FTS5 does not work any longer.
//
// So let's see which SQLite version we are linked against:
#if GRDBCUSTOMSQLITE || GRDBCIPHER
// GRDB is linked against SQLCipher or a custom SQLite build: SQLite 3.20.0 or more.
return api_v2(db, sqlite3_prepare_v3, sqlite3_bind_pointer)
#else
// GRDB is linked against the system SQLite.
//
// Do we use SQLite 3.19.3 (iOS 11.4), or SQLite 3.24.0 (iOS 12.0)?
// We need to check for available(iOS 12.0, OSX 10.14, watchOS 5.0, *).
//
// This test requires the Swift 4.2 compiler.
//
// It does not need the Swift 4.2 language, though: the Swift 4.2
// compiler running in Swift 4.0 compatibility mode is OK.
//
// On top of that, we want to preserve compatibility with Xcode 9.3+.
//
// So let's check exactly which compiler version we are using.
//
// Fortunately, this horribly complex check has been solved
// by @hartbit: see https://forums.swift.org/t/compiler-version-directive/11952
// and https://github.com/hartbit/swift-evolution/blob/compiler-directive/proposals/XXXX-compiler-version-directive.md
#if swift(>=4.1.50) || (swift(>=3.4) && !swift(>=4.0))
if #available(iOS 12.0, OSX 10.14, watchOS 5.0, *) {
// SQLite 3.24.0 or more
// setup: Xcode 10.0, SWIFT_VERSION = 4.0, iOS 12
// setup: Xcode 10.0, SWIFT_VERSION = 4.2, iOS 12
return api_v2(db, sqlite3_prepare_v3, sqlite3_bind_pointer)
} else {
// SQLite 3.19.3 or less
// setup: Xcode 10.0, SWIFT_VERSION = 4.0, iOS 11
// setup: Xcode 10.0, SWIFT_VERSION = 4.2, iOS 11
return api_v1(db)
}
#else
// SQLite 3.19.3 or less
// setup: Xcode 9.4.1, iOS 11
return api_v1(db)
#endif
#endif
}
private static func api_v1(_ db: Database) -> UnsafePointer<fts5_api> {
// Code that targets SQLite before 3.20.0
...
}
private static func api_v2(
_ db: Database,
_ sqlite3_prepare_v3: @convention(c) (OpaquePointer?, UnsafePointer<Int8>?, Int32, UInt32, UnsafeMutablePointer<OpaquePointer?>?, UnsafeMutablePointer<UnsafePointer<Int8>?>?) -> Int32,
_ sqlite3_bind_pointer: @convention(c) (OpaquePointer?, Int32, UnsafeMutableRawPointer?, UnsafePointer<Int8>?, (@convention(c) (UnsafeMutableRawPointer?) -> Void)?) -> Int32)
-> UnsafePointer<fts5_api>
{
// Code that targets SQLite 3.20.0+
...
}
OMG I totally missed that SE-0212 was implemented .
I can't use it because I need to support Swift 4.1: this is no big deal, I'll wait.
But in an ideal world, I would be able to use &&
and ||
between the various checks:
static func api(_ db: Database) -> UnsafePointer<fts5_api> {
#if GRDBCUSTOMSQLITE || GRDBCIPHER || (compiler(>=4.2) && #available(iOS 12.0, OSX 10.14, watchOS 5.0, *))
return api_v2(db, sqlite3_prepare_v3, sqlite3_bind_pointer)
#else
return api_v1(db)
#endif
}
private static func api_v1(_ db: Database) -> UnsafePointer<fts5_api> {
// Code that targets SQLite before 3.20.0
...
}
private static func api_v2(
_ db: Database,
_ sqlite3_prepare_v3: @convention(c) (OpaquePointer?, UnsafePointer<Int8>?, Int32, UInt32, UnsafeMutablePointer<OpaquePointer?>?, UnsafeMutablePointer<UnsafePointer<Int8>?>?) -> Int32,
_ sqlite3_bind_pointer: @convention(c) (OpaquePointer?, Int32, UnsafeMutableRawPointer?, UnsafePointer<Int8>?, (@convention(c) (UnsafeMutableRawPointer?) -> Void)?) -> Int32)
-> UnsafePointer<fts5_api>
{
// Code that targets SQLite 3.20.0+
...
}
The reason that compiler(…)
and swift(…)
are special is because there's no guarantee that the code within the #if
even parses if the requirement isn't met, because we might have added some new syntax to the language. (Like, say, throwing subscripts?) The other conditions still require that the disabled code be syntactically valid, for syntax-highlighting and potential structural editing purposes.
It would probably be safe to add a rule that says "you can combine compiler(…)
and swift(…)
with other conditions, but then the other side is always parsed", but that makes the "special" behavior of those two a little more subtle.
Thanks Jordan, I (mostly) see why it is difficult. It looks like #if compiler
and #if swift
are not at all handled at the same step as #if FOO
and if #available
during the compilation process, and their reunification is a really hard and subtle work.
I also understand that parsability of code is a hard requirement (I think SourceKit, eventual future hygienic macros, and tools I'm not even aware of).
I wish that a solution would have been to process the AST beforehand so that...
#if FOO && swift(>=4.2)
...
#endif
...would be interpreted as:
#if FOO
#if swift(>=4.2)
...
#endif
#endif
But the AST can't be built without knowing the Swift compiler version . Finding the first
#endif
indeed depends on the Swift version (future raw strings are expected to change this game, for example).
I guess I've just rewritten your thoughts, with less precision. Anyway, thank you very much for your answer: it's good to have an ear in such a situation. I expect that as SPM strengthens, more library developers will come with similar needs.