Beware operator ~= with OptionSet

I recently spent a couple of hours trying to figure out why code like this never did what I wanted it to do:

    var opts: NSString.CompareOptions = [.caseInsensitive, .diacriticInsensitive]
    ....
    if opts ~= .diacriticInsensitive {
        print("Using diacritics") <<<<< Never called
    }

It should have been:

    if opts.contains(.diacriticInsensitive) {
        print("Using diacritics")
    }

Having been doing a bunch of this kind of thing recently:

if 200...299 ~= statusCode {...}

It seemed only natural to use the ~= operator instead of using contains on the OptionSet.

Normal Set is prevented from compiling such a thing:

var opts = Set([1, 2])
if opts ~= 2 { // Fails to compile - Cannot convert value of type 'Int' to expected argument type 'Set<Int>'
}

Can anyone suggest a way to prevent this mistake again as its a bit of a gotcha?

It's calling the func ~= <T>(a: T, b: T) -> Bool where T : Equatable implementation that ships with the Standard Library, which is why it doesn't work. Perhaps what you can do is provide a custom implementation but with an @available annotation to discourage its use and promote the usage of .contains. For example:

@available(*, deprecated, message: "Use .contains instead!")
func ~=<T: OptionSet>(a: T, b: T) -> Bool { a.isSuperset(of: b) }

Then when you do use ~= you will get a warning:


You can get rid of the @available if you want the option to use both ~= and .contains.

1 Like

~= is also used during pattern matching of values (e.g. in a switch/case). I think it could make sense to define this for option set, I'd recommend bringing it up on the evolution forum

4 Likes

~= is the pattern-matching operator, not the contains operator.

This doesn't compile in Xcode Version 13.0 beta (13A5155e):

let statusCode = 150
if 200...299 ~= statusCode {        // Expected expression after unary operator
    print("statusCode is in range") // Expected expression
}

I can not confirm that. Both

if 200...299 ~= statusCode { ... }

and the equivalent

if case 200...299 = statusCode { ... }

compile and run without problems in my Xcode 13.0 beta (13A5155e).

I delete DerivedData and restart Xcode and now it compile. But it doesn't print, so what's is it suppose to do?

let statusCode = 150
if 200...299 ~= statusCode {
    print("statusCode is in range 111") // do not print
}

if case 200...299 = statusCode {
    print("statusCode is in range 222") // do not print
}

The code tests if the status code matches the given range, i.e. if statusCode is between 200 and 299.

My mistake :person_facepalming:

Done: Catching unwanted uses of ~= on OptionSet

1 Like