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:
-
Improved compiler diagnostics (errors and warnings) and code completion
-
Increased reliability in debugging
-
Improved handling of dependencies in the Swift Package Manager
-
Tooling improvements with LSP and SwiftSyntax
… 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.
Table of Contents
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
-
Faster completion by eliminating unnecessary type checking. For large files it can speed-up code completion by 1.2x to 1.6x, compared to Xcode 11.3.1, depending on the completion position.
-
Now can supply names of implicit members for incomplete dictionary literals and incomplete ternary expressions:
-
Easier to read types when they appear in the results. Using opaque result types (e.g.
some View
) when possible and preserving typealiases. Stopped printing parent types if not necessary. For example, in Swift 5.1.3 (Xcode 11.3.1):In Swift 5.2 (Xcode 11.4) this is now displayed as:
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:
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:
-
Remote Swift packages with tools version 5.2 and above no longer resolve package dependencies that are only used in their test targets, improving performance and reducing the chance of dependency version conflicts.
-
Swift Package Manager uses a new strategy to resolve package dependencies that significantly improves the quality of error messages and performance in complex package graphs.
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:
-
FixIts: Swift errors, warnings and notes that include FixIts are now supported using Code Actions from the Language Server Protocol.
-
Local Refactoring: Swift local refactorings such as extract-to-method are now supported using the “Refactoring” Code Action kind.
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):
-
Loading time of the compilation database was sped up by up to 10x
-
Indexing while building data is now supported by scraping the compilation database for the index store path arguments
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.
Leave a Reply