Swift equality operator takes long time to type check

I wanted to optimize compile time, so I had -Xfrontend -warn-long-expression-type-checking=200 flag on to figure out what causes long time to type check.

Turns out == equality operators were causing long time to type check. So my question is..

  1. Why does equality operator causes long time to type check?
  2. Is there an alternative way to compare equality? rather than explicity using == operator? And why would this way cause less compile time?
1 Like

Could you give the expression that causes the problem (including the type of each variable therein)? It could be other parts of the expression, in tandem with the ==, that complexify the possible search space.

It can usually be alleviated with one (or two) strategically placed as. Breaking the expression into multiple subexpressions may also helps.

I don't think, not in a way that'd improve the type checking performance (aside from the methods mentioned above).

2 Likes

With that said,

I wouldn't recommend relying on this. Many type checking work can be, and are cached. So the first expression of its kind can take a particularly long time without really impacting the type checking performance as a whole.

2 Likes

Occasionally when equality operator was used inside a closure that is passed as an argument
distinctUntilChanged { $0 == $1 } // Long time to type check
it warned me of type checking, but equality operator used alone warned me too
let a = SomeClass2() == SomeClass2() // Long time to type check

I think this maybe not really about the == operator itself, although it adds to the compile time the number of overloads of this operator defined in the standard library and probably in modules that people write and import on their apps. But this can also goes down to the operands and maybe sub-expressions, specially if they involve literals or types that have to be inferred where solver couldn't place constraints to reduce the possible overloads to this operator the solver will attempt in order to find a solution, so solver has to attempt to bind all possible overload to apply to the expression which makes the type checker time goes up a bit.
It can happen with any defined operator in fact, but is more likely to see this slow down on type checking with the == operator because there are many overloads defined for it either in stdlib or in the user defined module, so if the solver can place specific type constraints to filter possible operator overloads on a expression for any reason, the solver might end-up with a long list of overloads to attempt which causes type checking to take more time.

1 Like

ps. I made up function inside Equatable extension which uses pattern matching to compare equality, and this does not warn me about long time to type check.
What could this differ from == operator used explicitly?

func equals(_ object: Self?) -> Bool {
    guard case object = self else { return false }
    return true
}

In this case, it may depend on the previous code in the chain and whether the solver could have enough information to be able to place constraints that will filter out the possible overloads for this operator. If you explicitly specify the types e.g. distinctUntilChanged { (a: Int, b: Int) in a == b } helps the compiler to type-check faster because it can add constraints on the solver that reduce the list of possible overloads applicable for this on overload resolution. @xedin @hborla may be able to better explain this :)

1 Like

or even

distinctUntilChanged { $0 == $1 as Int }
3 Likes

I made up function inside Equatable extension which uses pattern matching to compare equality, and this does not warn me about long time to type check.
What could this differ from == operator used explicitly?

There are a few factors here:

  1. There are a lot more overloads for the == operator than for the equals method that you wrote. Looking at the debug output of the type checker tells me there are 73 overloads of == in the standard library alone. If you wrote 70 overloads of equals, it'd probably exhibit the same performance issues as ==
  2. The equals function you wrote is a method, which means you call it directly on an instance that conforms to Equatable, e.g. object.equals(otherObject). Methods are a lot easier for the compiler to figure out, because the compiler only needs to consider overloads on the base type that you called the method on. With operators, the compiler has to consider every overload of == regardless of what type or protocol it's declared on, because operators are always called like global functions.

EDIT:
To answer your question about why pattern matching type checks faster, the answer is the same as 1. above: there are a lot more overloads of == (~70 overloads) than there are of the pattern matching operator ~= (5 overloads)

9 Likes

Wow. It was super helpful :smile:
Thank you very much @hborla

1 Like