Introduction
Following up on the discussion thread from the other day, I propose that we extend implicit member syntax (a.k.a. "leading dot syntax") to allow for chained member references rather than just a single level.
Motivation
When the type of an expression is implied by the context, Swift allows developers to use what is formally referred to as an "implicit member expression," sometimes referred to as "leading dot syntax":
class C {
static let zero = C(0)
var x: Int
init(_ x: Int) {
self.x = x
}
}
func f(_ c: C) {
print(c.x)
}
f(.zero) // prints '0'
However, attempting to use this with a chain of member references fails:
extension C {
var incremented: C {
return C(self.x + 1)
}
}
f(.zero.incremented) // Error: Type of expression is ambiguous without more context
On master
, the new diagnostic system has improved the produced error:
f(.zero.incremented) // Error: Cannot infer contextual base in reference to member 'zero'
but the same problem persists. This error breaks the mental model that many users likely have for implicit member syntax, which boils down to a simple lexical omission of the type name in contexts where the type is clear. I.e., users expect that writing:
let one: C = .zero.incremented
is just the same as writing
let one = C.zero.incremented
This issue arises in practice with any type that offers "modifier" methods that vend updated instances according to some rule. For example, UIColor
offers the withAlphaComponent(_:)
modifier for constructing new colors, which cannot be used with implicit member syntax:
let milky: UIColor = .white.withAlphaComponent(0.5) // error
Proposed solution
Type inference would be improved in order to be able to handle multiple chained member accesses. The type of the resulting expression would be constrained to match the contextual type.
Detailed design
This proposal would provide the model mentioned earlier for implicit member expressions: anywhere that a contextual type T
can be inferred, writing
.member1.member2.(...).memberN
Will behave exactly as if the user had written:
T.member1.member2.(...).memberN
Further, if T
is the contextually inferred type but memberN
has non-convertible type R
, a diagnostic of the form:
Error: Cannot convert value of type 'R' to expected type 'T'
will be produced. The exact form of the diagnostic will depend on how T
was contextually inferred (e.g. as an argument, as an explicit type annotation, etc.).
Source compatibility
This is a purely additive change and does not have any effect on source compatibility.
Effect on ABI stability
This change is frontend only and would not impact ABI.
Effect on API resilience
This is not an API-level change and would not impact resilience.