Description
Summary
I've discovered that Foundation's #Predicate
fails when a class is marked as final
. Here's a very simple test case:
final class Master {
var id: UUID = UUID()
}
let u = UUID()
let p = #Predicate<Master>{ $0.id == u }
That expands to:
Foundation.Predicate<Master>({
PredicateExpressions.build_Equal(
lhs: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg($0),
keyPath: \.id
),
rhs: PredicateExpressions.build_Arg(u)
)
})
It compiles correctly, but at runtime crashes on the build_KeyPath
line: Thread 1: Fatal error: Predicate does not support keypaths with multiple components
This is obviously not correct; there is only one component in the KeyPath. If you remove final
from the class declaration, it works fine.
I see where this check exists in Foundation (
final
should affect whether a UUID
property has an offset.
Any Value
The behavior is not limited to UUID
. I can reproduce it with a String
, Int
, and other values.
Speculation
I can’t find any information on how the presence of final
affects the memory layout of a class such that offset(of:)
would fail. I thought maybe the issue affects only types that are capable of storing their value without allocation (i.e. tagged pointers), but again I can’t see how final
would affect that.
Intermittent
IF a class has a certain combination of properties, this issue does not always manifest. For example, Predicates ran just fine here:
final class Master
{
var title: String = “”
var numbers: [Int] = []
var isEnabled: Bool = false
var percentage: Double = 57.5
}
But add var id: UUID = UUID()
and that fails until final
is removed. Given the offset(of:)
check, I figured the issue might have something to do with the exact way properties are aligned and packed into the storage for the class. But that’s just a guess.
Environment
Xcode 16.2.
Since #Predicate
isn’t available in Playgrounds, I created a new project from the “command line application” template in Xcode. I changed no build settings.