Swift 5.2 Released!

Ted Kremenek is a member of the Swift Core Team and manages the Languages and Runtimes group at Apple.

Swift 5.2 is now officially released! 🎉

This release focuses on improving the developer experience:

… and much more. Further, a few additions to the language have been added that provide new capabilities for building expressive APIs. This blog post takes a quick tour of the main changes.

Language Updates

Swift 5.2 implements the following language proposals from the Swift Evolution process:

To experience these changes, explore a playground put together by Paul Hudson. John Sundell has also written an article, “Exploring Swift 5.2’s new functional features”, that illustrates the expressive capabilities of these new features.

Improved Compiler Diagnostics

We have drastically improved the quality and precision of error messages in the Swift compiler.

Previously, the compiler attempted to guess the exact location of an error by breaking up an expression to search for failures in each subexpression separately. This worked well in cases where it is possible to narrow down the location of an error to a single subexpression without using any information about its parent expression. However, there were numerous kinds of programming mistakes that this strategy could not accurately identify.

The compiler leaves “breadcrumbs” when it encounters failures while inferring types in an expression, recording every specific failure along the way. These breadcrumbs allow the compiler to produce precise diagnostics, often with actionable fixes, that lead the developer toward correct code. Below are a few examples of improved error messages.

The following code attempts to compare an enum value with a case that doesn’t exist:

enum E { case one, two }

func check(e: E) {
  if e != .three {
    print("okay")
  }
}

Using Swift 5.1, you might be perplexed by the error message:

error: binary operator '!=' cannot be applied to operands of type 'E' and '_'
  if e != .three {
     ~ ^  ~~~~~~

Using Swift 5.2, you’ll see the problem right away:

error: type 'E' has no member 'three'
  if e != .three {
          ~^~~~~

The next snippet of code incorrectly invokes the initializer for TextField in SwiftUI:

import SwiftUI

struct RoomDetails: View {
  @State var roomName: String
  @State var imageName: String

  var body: some View {
    VStack {
      TextField("Room Name")

      Image(imageName)
        .frame(maxWidth: 300)
    }
  }
}

In Swift 5.1, a misleading error message appeared on a completely different line:

error: 'Int' is not convertible to 'CGFloat?'
        .frame(maxWidth: 300)
                         ^~~

The Swift 5.2 compiler now correctly points out that there is a missing argument for the TextField initializer:

error: missing argument for parameter 'text' in call
      TextField("Room Name")
                           ^

This error also includes a Fix-It to insert the missing argument.

You can find out more about the new diagnostic architecture on a previously published blog post dedicated to that topic.

Code Completion Improvements

Improved Build Algorithms

The Swift compiler supports two modes of operation:

In Xcode these can be seen in the build settings for a Swift project:

Swift compilation modes

The two modes have tradeoffs in compilation speed and amount of code optimization performed. Incremental builds are great during development where not every file in the project needs to be recompiled, and maximum optimization is not critical. Whole Module Optimization gives the compiler a more complete view of the entire code base and therefore a greater ability to optimize.

In an Incremental mode build, the work of rebuilding a module is split among multiple compilation tasks that run in parallel. For every source file that is rebuilt, there is exactly one associated compilation task responsible for type checking and generating code for the declarations in that source file.

Since Swift declarations (such as functions, properties, types, etc.) can reference each other across source files, a compilation task will sometimes be required to type check declarations in other source files. This cross-file referencing of declarations can decrease efficiency of an Incremental mode build because it can duplicate type-checking work across compilation tasks.

In contrast, Whole Module compilation works by processing all the code in a module in one compilation task. While there is no duplication of type checking work across compilation tasks, there is no parallelism when compiling the code in a module. Whole Module compilation, however, gives the compiler more visibility in one go over the code in a module and thus enables more code optimizations.

The build time advantage of Incremental over Whole Module compilation diminishes with the amount of duplicated work each compilation task performs. If this duplicated work is too high, it can be the case that Incremental mode does more work than Whole Module compilation. As long as the overhead does not exceed the number of processor cores, the Incremental mode build will still be faster overall, but reducing this overhead is key to improving build times.

Making Incremental Builds more Efficient

In order to minimize the wasted work done by Incremental mode builds, the Swift 5.2 compiler — notably the type checker — leverages a new centralized logic for caching, lazy evaluation, and dependency tracking between requests, where a request is a self-contained unit of computation. This logic is now used by the compiler to more efficiently resolve declarations and their references to one another.

Prior to Swift 5.2, when a declaration was referenced in another source file, the type checker would explicitly perform an operation on this declaration, called validation. Validation made use of mutable state and was rather coarse-grained, attempting to pre-compute any number of properties of the declaration that might be needed later during code generation. This eager, pre-computation of information could often be unnecessary and wasteful.

In Swift 5.2, the internal representation of declarations in the compiler is immutable, and the code generation phase of the compiler is able to trigger lazy evaluation of requests, the result of which are cached. Since requests are more fine-grained than the old validation step, this improves performance by avoiding wasted work. It also improves correctness, fixing a significant number of correctness issues where the type checker did not anticipate needing to validate something that was later required for code generation.

Additional Improvements

In addition to improved Incremental mode builds, the Swift 5.2 compiler includes a number of performance optimizations to foundational components such as the work the compiler does to resolve a named symbol to its declaration (i.e., name lookup). We expect that these improvements will improve build times for both Whole Module and Incremental mode builds. Since these changes reduce the algorithmic (big-O) complexity of various algorithms inside name lookup, they should particularly help on larger projects with many source files.

Debugger Improvements

Across all platforms where Swift debugging is supported, LLDB is now more resilient in reconstructing type information for Swift programs from debug information. This resilience enables the debugger to use more information about Swift types.

In particular LLDB can now also import C and Objective-C types from DWARF debug information instead of compiling the Clang module from source code. This behavior can be controlled by the symbols.use-swift-dwarfimporter LLDB setting. By default this setting is enabled as a fallback path when the traditional Clang module import fails.

Example: Xcode variable view and expression evaluator

To see these improvements in action, one can look at the variable view in Xcode or the LLDB expression evaluator. To power these debugging workflows, LLDB needs to import all Swift modules that are visible in the current debugging context (e.g., file, function, etc.). While Swift modules have a wealth of information about types, Swift modules often cannot be used just on their own without depending on separate modules files produced by Clang (the C/C++/Objective-C compiler) that are used for Swift code interoperating with C and Objective-C. Since LLDB has a global view of the entire program and all of its dynamic libraries with all their dependencies, importing Clang modules can sometimes fail. One common failure scenario is when the search paths from different dynamic libraries are in conflict.

Swift Package Manager

Swift Package Manager in Swift 5.2 includes the following new enhancements:

These changes were a result of discussion and review as part of the Swift Evolution process:

SwiftSyntax Updates

The syntax node hierarchy in SwiftSyntax’s API has been optimized by replacing protocols with structs. Consequently, tree visitation, especially when rewriting with SyntaxRewriter, is now faster. This has resulted in improved performance during tree visitation and especially when rewriting the tree using SyntaxRewriter.

See the changelog for more details on what changed in the Swift 5.2 release.

Language Server Protocol Updates

Xcode 11.4 and the corresponding Command Line Tools package include the Swift 5.2 release of SourceKit-LSP language server for Swift and C-based languages.

To find the sourcekit-lsp server executable on macOS while Xcode 11.4 is the active Xcode:

# Run the server.
xcrun sourcekit-lsp
# Get the full path to the server.
xcrun --find sourcekit-lsp

SourceKit-LSP now includes support for the following LSP features:

SourceKit-LSP also has a number of improvements for supporting C/C++/Objective-C code. In particular, when determining compiler arguments for processing header files SourceKit-LSP now uses the index to lookup their main file for improved accuracy of results.

There are also some notable improvements for projects using JSON compilation databases (e.g. CMake projects):

Documentation

An updated version of The Swift Programming Language for Swift 5.2 is now available on Swift.org. It is also available for free on the Apple Books store.

Platforms

Linux

Official binaries for Ubuntu 18.04 and Ubuntu 16.04 are available for download.

Apple (Xcode)

For development on Apple’s platforms, Swift 5.2 ships as part of Xcode 11.4.

A toolchain is also available for download from Swift.org.

Sources

Development on Swift 5.2 was tracked in the swift-5.2-branch on the following repositories on GitHub:

The tag swift-5.2-RELEASE designates the specific revisions in those repositories that make up the final version of Swift 5.2.

The swift-5.2-branch will remain open, but under the same release management process, to accumulate changes for the next release.