A Swift file takes forever to compile. How do you find the cause and fix it?

5.6.1 · L3 · Interview

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.

SymptomFix
overloaded operators with mixed literalsone expression takes secondsannotate a type or split it up
closures with inferred typesa view body is slowannotate the closure or extract a function
big untyped collection literallong type-check on one linewrite the element type
one huge module under WMOevery build is slowsplit the module
large bridging headerany change rebuilds everythingtrim 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… →