A Swift file takes forever to compile. How do you find the cause and fix it?
Predict first
Which flag warns about individual expressions that take too long to type-check?
// A: -warn-long-expression-type-checking=200
// B: -emit-sil
// C: -enforce-exclusivity=unchecked
- A
- B
- C
Mental model. The type checker is a detective who must test every combination of overloads. Handing it a few facts (explicit types) saves it from trying thousands of possibilities.
First measure. The Xcode Build Timeline shows which files and phases dominate. Inside a file, ask the compiler to name the culprit: -Xfrontend -warn-long-expression-type-checking=200 and -Xfrontend -warn-long-function-bodies=200 warn for any expression or function that takes over 200 ms to type-check, and -Xfrontend -debug-time-function-bodies lists them all. xcodebuild -showBuildTimingSummary adds a per-target view.
The usual cause is the type checker on one complicated expression: many overloaded operators mixed with literals (+ over numbers and strings), long chains of optional and ternary operators, large array or dictionary literals whose element type must be inferred, and closures with no type annotations (common in result-builder-heavy SwiftUI bodies). The fix is to give it less to infer: add an explicit type to a literal or a closure, split one big expression into named steps, and avoid mixing literal types. Other causes are an oversized module under WMO (split it), heavy protocol and generic machinery, a large bridging header, and macros. Re-measure after each change.
It takes a threshold in milliseconds and reports each expression over it. Pass it through -Xfrontend to the compiler frontend.
| Symptom | Fix | |
|---|---|---|
| overloaded operators with mixed literals | one expression takes seconds | annotate a type or split it up |
| closures with inferred types | a view body is slow | annotate the closure or extract a function |
| big untyped collection literal | long type-check on one line | write the element type |
| one huge module under WMO | every build is slow | split the module |
| large bridging header | any change rebuilds everything | trim it, or import modules instead |
Common traps
- Blaming “too many lines”. Function bodies are checked separately; a short but ambiguous expression can cost more than a long simple file.
- Optimising by feel. Without the timing flags you will fix the wrong thing.
They might follow up with
- What does “expression was too complex to be solved in reasonable time” mean? The solver hit its budget. Break the expression up or add types.
- Do explicit types always help? They remove ambiguity, which is usually what makes the solver explore many cases.
Related questions
- 5.1.3 How do you look at what the compiler produced at each stage?
- 5.6.2 How does incremental compilation decide what to rebuild, and why does a small edit sometimes rebuild half the…
- 5.2.2 What does whole-module optimisation change, and what does it cost?
How does incremental compilation decide what to rebuild, and why does… →