How to @available-gate a stored `Regex` property?

i’ve been trying to port this type to macOS:

public
enum TestFilter
{
    case regex([Regex<Substring>])
    case path([String])
}

Regex is only available on macOS 13. so i figured this would do the trick:

public
enum TestFilter
{
    @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
    case regex([Regex<Substring>])
    case path([String])
}

alas, it does not:

error: enum cases with associated values cannot be marked potentially unavailable with '@available'
    @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)

obviously, i do not want to give up regex on linux just to make the library compile on macOS. but i am pretty stumped as to how to conditionally provide Regex-based features based on OS and OS version.

the only workaround i could think of is falling back to Any, like:

public
enum TestFilter
{
    #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
    case regex(Any)
    #else
    case regex([Regex<Substring>])
    #endif

    case path([String])
}

and then force-casting everywhere:

        case .regex(let filter):
            if #available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
            {
                #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
                let filter:[Regex<Substring>] = filter as! [Regex<Substring>]
                #endif

is there really no better way to do this?

My understanding is that this can't be allowed, at least in the general case: In order to know what the memory layout of an enum looks like, the compiler needs to know the memory layout of all its associated values, which doesn't make sense on macOS 12. Previous versions of the Swift compiler accepted it, but then it would crash at runtime.

I don't know what your use case is, so I don't know for certain what the best design is. One option would be something like this:

public protocol MyRegexProtocol {
    // ... the Regex methods you need ...
}

@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
extension Regex: MyRegexProtocol
where Output == Substring {
}

public enum TestFilter {
    case regex([any MyRegexProtocol])
    case path([String])
}
2 Likes

If you don't need exhaustive switching, you may be able to get away with using a struct instead of enum, which would allow you to make a static func regex(_ regexes: [Regex<Substring>]) conditionally available, as long as your underlying representation is abstracted, like a closure.

2 Likes

That's probably it. But would be nice if this could be achieved with an indirection (that's more type-safe than Any), such as @available(...) indirect case regex(...).

Not really. The Any approach is the standard workaround. The GitHub issue tracking support for potentially unavailable stored properties and enum associated values is here.

1 Like