Key path cannot refer to static member

Hello,

The Swift 5.1 compiler won't let me create key paths to a static member:

struct S {
    static let member = 12
}

// Compiler error: Key path cannot refer to static member
let kp = \S.Type.member

In case this limitation was waiting for a use case... I have one.

The problem that needs a solution

Users of GRDB, the SQLite toolkit, happen to define static members when they define relationships between record types, the types that map the database tables:

// (Required protocol conformance stripped for brevity)
struct Player { ... }

struct Team {
    static let players = hasMany(Player.self)
}

Those static members have many usages. One of them is to access the associated records of a given record, as below. However, this code is quite heavy:

let team: Team = ...
let players = try team
    .request(for: Team.players)
    .fetchAll(database) // [Player]

In order to improve clarity at call site, users are advised to declare an extra property, as below:

struct Team {
    static let players = hasMany(Player.self)
    // One extra property...
    var players: QueryInterfaceRequest<Player> {
        request(for: Team.players)
    }
}

// ... for improved clarity:
let team: Team = ...
let players = try team.players.fetchAll(database)

The problem is this extra property. It is verbose, adds no information, is highly redundant, and, if useful, one can easily doubt it is worth the effort.

A solution: SE-0252 and key paths to static members

If key paths to those static members were possible, it would be possible to leverage SE-0252 Key Path Member Lookup and streamline a few database accesses:

// In an ideal world...
struct Player { ... }

@dynamicMemberLookup
struct Team {
    static let players = hasMany(Player.self)
}

// ... fetching all players from a team does not require more code
let team: Team = ...
let players = try team.players.fetchAll(database)
//                     ^ SE-0252

The user would only need to add @dynamicMemberLookup. It is the library that provides the implementation for the SE-0252 subscript:

// A default subscript that waits for @dynamicMemberLookup record types
extension TableRecord where Self: EncodableRecord {
    public subscript<A>(dynamicMember keyPath: KeyPath<Self.Type, A>)
        -> QueryInterfaceRequest<A.RowDecoder>
        where A: Association, A.OriginRowDecoder == Self
    {
        request(for: Self.self[keyPath: keyPath])
    }
}

Does any forum user know if key paths to static members require a Swift Evolution process, or if a report on bugs.swift.org would be enough?

9 Likes

IIRC it was part of @brentdax's static subscript proposal, but it was then dropped because it was too much for that proposal and maybe also a little hard to get implemented.

2 Likes

@DevAndArtist has a good memory—I tried to include this in SE-0254, but it wasn't the primary thrust of the proposal and I didn't finish implementing it in the limited time I could devote to a stretch goal. It seems totally doable, and might even be easy for someone who knows IRGen better than me—it just requires a lot of little corrections throughout the compiler. (When I stopped, I was in IRGen and still needed to make resilience work.)

I'm not a core team member, but I suspect that if there was a proposal and working implementation, it would very likely be accepted. Once we had static key paths working, key path-based dynamic member lookup for static members would most likely fall out of the implementation naturally.

10 Likes

+1 to this idea, I don't see why they wouldn't be supported.

1 Like
Terms of Service

Privacy Policy

Cookie Policy