Using leading dot syntax in generic functions

Leading dot syntax in Swift

The “leading dot syntax” is an important feature in Swift to provide clean and concise APIs for callers. It supports enums and static members.

Let’s say we have an ItemStorage class that can store Item. We can define Item as enum:

class ItemStorage {
func store(_ item: Item) {
// store this item ...
}
}

enum Item {
case one
case two
// ...
}

At the call site, we can just use .one in the store(_:) function:

let storage = ItemStorage()
storage.store(.one)

Alternatively, we can change Item to struct and declare commonly used values as the static variables to make them available in leading dot syntax:

struct Item {
var identifier: String
}

extension Item {
static var someItem: Item { .init(identifier: "some-item") }
}

let storage = ItemStorage()
storage.store(.someItem) // dot syntax works for static members too

The problem: Generic functions ft. static members

Let’s say we want to expand ItemStorage to support any types that conform to a protocol StorableItem:

protocol StorableItem {
var identifier: String { get }
}

class ItemStorage {
func store<I: StorableItem>(_ item: I) {
// ...
}
}

We can make the existing Item struct conform to the new protocol because it already has the identifier property:

struct Item: StorableItem {
var identifier: String
// ...
}

extension Item {
static var someItem: Item { .init(identifier: "some-item") }
}

But .someItem no longer works:

let storage = ItemStorage()
storage.store(.someItem) // ❌ type 'StorableItem' has no member 'someItem'

The solution

In Swift 5.5, leading dot syntax is extended to support static member lookup declared in the extension of protocols if the declaring extension or member itself constrains Self to be a concrete type[1].

Here’s how to fix our example:

extension StorableItem where Self == Item {
static var someItem: Self { .init(identifier: "some-item") }
}

let storage = ItemStorage()
storage.store(.someItem) // ✅ this works now!

ℹ️ Note

where Self == Item in the extension is the key part, which makes sure Self is bound to a concrete type Item.

References


  1. SE-0299: Extending Static Member Lookup in Generic Contexts ↩︎