How to do type check only?

Is there something like cargo check for spm? Or swift build is fast enough that there is no need to perform a check-only process during development?

My (limited) understanding of the Swift build process is that compiler errors are produced at many levels of the compilation process, so there isn’t actually that much work left to do after all possible warnings/errors have been produced. Plus, IDEs are able to compile in the background and surface live issues in real time which replaces manually running a check separately from the build.

2 Likes

Diagnostics are emitted from parsing, type checking, SILGen and the mandatory SIL passes. A "check-only" build could run those passes, but skip performance optimization and code generation. If your project has multiple modules, you also need .swiftmodule files, but a good approximation would be to only emit .swiftmodule files and not executables or shared libraries. However, I don't know if swiftpm is set up to do that today.

5 Likes

this is a little bit of a tangent, but are there settings that can be used to reduce the amount of memory used by the compiler, possibly at the expense of compilation speed?

the use case i am imagining is similar to OP’s, in that i would only be using the compiler to generate documentation, and i would not actually be using the compiled binaries at all.

I would guess that a debug build with -disable-batch-mode will use less memory, but I’ll let you test this theory ;-)

I can confirm this. In my observations, emitting only the .swiftmodule files and skipping object files is significantly faster for certain modules. SwiftSyntax, for example, takes ~25 seconds to do a full compile on my M1 Max, but emitting just the module only takes 5 seconds, since SwiftPM is using -experimental-skip-non-inlinable-function-bodies for its emit-module job.

When generating docs with SwiftPM, is its build planning sophisticated enough that it can only ask for modules to be emitted and not object files? I haven't poked at it that deeply.

1 Like

OP was specifically interested in seeing all diagnostics, which precludes body skipping. However, emitting a module should still be faster even without body skipping, because you can skip LLVM code generation and optimization.

1 Like

One extreme idea would be to run two jobs per module—one with body skipping to unblock dependents faster and a second to do full type checking but not generate any outputs—but I imagine there's not a noticeable enough performance difference between those two modes for it to be worthwhile?

swiftc main.swift -typecheck

If your project imports other modules, you need to specify modules folder with -I and -L options. You may also want to specify -gnone -Onone . If your project is a library, add -parse-as-library .

-typecheck alone won’t emit all diagnostics though, for example the following code is well-typed, but it has some problems discovered later in SIL lowering:

func f() -> Int {
  var x: Int  // uninitialized
  if x == 0 {
    return 3
  }
  // missing return on false branch
}
1 Like

Out of curiosity of wanting to understand how Swift actually works what are the mandatory SIL passes?

1 Like

Mandatory passes diagnose problems that require dataflow analysis to detect — they would be awkward to implement on type checked AST directly. They also implement transformations that are needed for acceptable performance at -Onone.

They’re defined in swift/lib/SILOptimizer/Mandatory at main · swiftlang/swift · GitHub.

A few important ones:

  • Diagnosing unreachable code
  • Diagnosing accesses to uninitialized memory locations
  • Diagnosing invalid uses of move-only types
  • Promoting heap-allocated ‘var’ boxes to the stack, which should be most of them
  • Some forms of constant propagation and folding
  • Inlining @_transparent functions, like Int.+, that we want even at -Onone
5 Likes

As an alternative, are there some suggested flags to disable/reduce optimization level and skip linking phase in order to reduce build time?

Xcode is pretty good at give realtime-ish feedback but less so for VSCode. It kinda works but not really when working with a mono repo with multiple packages and making cross packages edits.

If the goal is to run the compiler to the point of producing all diagnostics, but no further, then -emit-sil should do the job. This will run typechecking, SIL generation, and the mandatory SIL passes, but no performance optimization or LLVM- or machine-level code generation.

2 Likes

tried with swift build -Xswiftc -emit-sil but seems like actually dumping all those output to stdout makes it slower than just swift build. > /dev/null doesn't help as well.