Search code examples
swiftpredicatekeypathsswift-keypathswift-macro

Swift #Predicate Macro Crashes for Multiple KeyPath Components


Context

I'm attempting to create a Swift Predicate using the #Predicate macro as shown here:

final class Foo 
{
    var title: String = ""
    var name: String = ""
}


let p = #Predicate<Foo> { $0.title == "test" }

This compiles fine, but at runtime crashes with this:

Foundation/KeyPath+Inspection.swift:65: Fatal error: Predicate does not support keypaths with multiple components

Here's what the macro expands to, according to Xcode:

Foundation.Predicate<Foo>({
    PredicateExpressions.build_Equal(
        lhs: PredicateExpressions.build_KeyPath(
            root: PredicateExpressions.build_Arg($0),
            keyPath: \.title
        ),
        rhs: PredicateExpressions.build_Arg("test")
    )
})

Why does this crash? It's not a KeyPath with multiple components.

Update

Here's the relevant code in Foundation. It uses offset(of:) to determine whether a KeyPath to a stored property has multiple components, which is not a reliable check.

Apple is working on a fix: https://github.com/swiftlang/swift-foundation/issues/1173

Details

  • Swift Language Version: 6
  • Xcode Version: 16.2
  • Deployment Target: macOS 15.2

Solution

  • The Fix:

    Removing final before the class declaration solves the issue.

    // Works:
    class Foo {
        var title: String = ""
    }
    
    // Crashes:
    final class Foo {
        var title: String = ""
    }
    

    Elaboration:

    I noticed that THIS worked just fine:

    @Model
    final class Foo {
        var title: String = ""
    }
    let p = #Predicate<Foo> { $0.title == "test" }  // No crash
    

    All the @Model macro really does is rewrite the class into fancy computed properties, so I gave this a shot and found that it also works fine:

    final class Foo {
        var title: String {
            return ""
        }
    }
    let p = #Predicate<Foo> { $0.title == "test" }  // No crash
    

    But if you use the final keyword and attempt to create a KeyPath Predicate to a stored property of that class, it crashes.

    Discussion:

    That has to be a bug. The docs for Predicate make no mention of limitations for static dispatch. If this is somehow expected behavior, the nonsensical "multiple components in keyPath" error message is entirely misleading. Hopefully this can save someone else the hours I've wasted.