Swift 5.2 keeps the context needed to explain type errors
Swift 5.2 improves error diagnosis inside the type checker and adds more direct ways to express property projections in APIs.
A type error can become harder to explain when the compiler discards the context that would identify it. An expression may be invalid only because its parent requires a particular type. Diagnosing the child on its own can therefore point at a symptom. Swift 5.2, released on March 24, improves this part of the development experience by retaining information about failures during type inference. The release also changes package dependency handling and adds language facilities for expressing APIs.
I find build times the least pleasant part of writing small Swift applications. Misleading diagnostics impose another kind of delay because a successful edit depends on understanding the actual failure. The compiler already has information about types and source locations. Making that information available in an error message is a substantial usability improvement even when the source language itself stays familiar.
Context flows in both directions
The type checker documentation in the Swift 5.2 source release explains why local error guessing is difficult. Swift inference carries information outward from subexpressions and inward from their surrounding context. A call can establish the type of a variable receiving its result. Conversely, an explicitly typed destination can constrain a generic call and the literal passed to it.
An expression therefore describes a set of related requirements. The checker assigns type variables where types are not yet known and creates constraints between them. A constraint might require equality, a conversion or conformance to a protocol. Member lookup and overloaded functions introduce further choices. Finding a valid program means finding assignments and choices that satisfy those relationships together.
For example, an integer literal is not initially a commitment to one concrete integer type in every context. The solver can use a literal protocol requirement and surrounding constraints to determine its type. An overloaded operator contributes possible function signatures. Choosing one signature adds requirements on its arguments and result, which may reveal that the choice cannot work.
This explains why breaking an expression apart can lose evidence. The child expression may have several possible interpretations until a surrounding call or assignment narrows them. A diagnostic process needs enough of that relationship to explain which interpretation the programmer attempted and why it failed. Reporting that inference failed is correct but often leaves the most useful information unstated.
Recording a possible repair preserves evidence
The diagnostic architecture overview published in October 2019 describes a strategy that records constraint fixes when the solver encounters incompatible requirements. A fix allows diagnostic exploration to continue while remembering the violated rule and its location. It does not make an invalid program acceptable for compilation.
Consider a missing & when an argument must be passed to an inout parameter. The checker can retain the argument's type and the parameter's requirement while recording what would have to change. The resulting diagnostic can explain that particular omission. It has more useful evidence than a report that the whole call could not be typed.
The release announcement illustrates the benefit with an enum comparison containing a nonexistent case. Swift 5.2 points at the absent member instead of blaming the comparison operator. In a SwiftUI example, the new diagnosis identifies a missing initializer argument where an earlier message blamed a later numeric conversion. These are changes to how the compiler explains failure, not new permissions in the type system.
A useful consequence for API authors follows from that mechanism. A heavily inferred call should still have a meaningful explanation when one of its arguments is wrong. The quality of the successful syntax and the quality of the failed syntax belong in the same assessment of an API. I value Swift's expressive type system more when the compiler can explain its constraints without making a programmer reconstruct the entire inference process.
The solver explores choices within an expression
The compiler documentation separates generating constraints from solving them. Generation records the relationships implied by syntax. Solving assigns concrete types and resolves alternatives. Applying a successful solution then makes implicit conversions explicit in the compiler's representation of the expression.
Overloading is a useful example of the separation. A name with several function declarations does not immediately have one selected function type. The checker can represent the alternatives as a disjunction. Trying an alternative imposes its argument and result types on the surrounding expression. If they cannot satisfy the remaining constraints, the solver can backtrack and try another alternative.
This design also gives a practical reason for limiting inference scope. The documentation describes inference within an expression or statement, rather than across an entire program. Keeping the problem bounded supports both performance and diagnostics. Local context can still contain a complex search, especially when overloaded operations interact with generic types, but the compiler has a defined unit to analyze.
For someone investigating an error, an explicit type annotation can expose an intended constraint to the reader as well as to the compiler. That observation follows from bidirectional inference. It does not mean every inferred value needs an annotation. The useful distinction is whether the program's context communicates the intended type clearly enough for its surrounding operations.
Property projections use existing function parameters
SE-0249 adds a compact expression for a common operation. A key path literal can appear where a function taking the root and returning the property value is expected. An ordinary map call can therefore project a property without a separately written closure.
The following example uses the new syntax and its explicit closure equivalent. The declarations are otherwise ordinary Swift:
struct User {
let email: String
let isAdmin: Bool
}
let users = [User(email: "admin@example.com", isAdmin: true)]
let emails = users.map(\.email)
let admins = users.filter(\.isAdmin)
let sameEmails = users.map { $0.email }
The proposal avoids requiring every library to add key-path-specific overloads. Its motivating examples include projection and filtering, but the conversion applies wherever the expected function type has the appropriate shape. A multisegment property path can work too. Library authors gain the syntax through their existing function-taking APIs.
The restriction to literal expressions is important. A variable whose type is KeyPath<User, String> does not automatically become a function just because it is passed to map. A literal can instead initialize a value explicitly declared as (User) -> String, and that function value can be passed later. These are different static types even when both describe access to the same property.
That restriction gives a precise limit to the feature. It is a contextual conversion of syntax, not a general declaration that all key path values behave as functions. Keeping the distinction visible prevents examples from promising a broader conversion than this release implements. It also leaves ordinary closures available for transformations that do more than read one property.
Dependency resolution uses target information
SE-0226 describes another context problem, this time in a package graph. A library may declare a dependency used only by tests or by an example executable. A consumer of the library does not necessarily need that dependency. Resolving everything declared can introduce version constraints unrelated to the product the consumer actually builds.
The proposal uses target and product relationships to determine which dependencies are relevant. It also makes package identity explicit in product declarations where needed, so the resolver can associate a product with its package. The Swift 5.2 announcement specifically identifies avoiding test-only dependencies of remote packages with tools version 5.2 or later. That qualification matters when interpreting the broader proposal as a description of shipped behavior.
For a reusable library, separating test targets from the published product consequently affects downstream resolution. This is a concrete benefit of describing the dependency graph accurately in the manifest. A package can keep its own test helpers without making every application resolve those helpers as part of using the library.
The proposal also permits a graph containing packages with different tools versions. Older manifests keep the older resolution behavior, while packages declaring the newer tools version can use the new behavior. Updating the compiler alone therefore does not describe every dependency decision in a mixed graph. The manifest remains part of the contract between a library and its consumers.
The design avoids adding a separate category of development-only package dependency. Target relationships already contain the information needed to distinguish a library requirement from a test helper. Reusing those relationships keeps the distinction attached to the code that actually needs the dependency. For a package maintainer, the relevant declaration is consequently the target dependency, with the tools version determining which resolution behavior applies.