diff --git a/Sources/FoundationEssentials/Predicate/Archiving/PredicateCodableConfiguration.swift b/Sources/FoundationEssentials/Predicate/Archiving/PredicateCodableConfiguration.swift index f788cea8d..d4fab3740 100644 --- a/Sources/FoundationEssentials/Predicate/Archiving/PredicateCodableConfiguration.swift +++ b/Sources/FoundationEssentials/Predicate/Archiving/PredicateCodableConfiguration.swift @@ -310,7 +310,7 @@ extension PredicateCodableConfiguration { guard root == rootReflectionType.partial, let constructed = constructor(rootReflectionType.genericArguments) else { return nil } - constructed._validateForPredicateUsage(restrictArguments: false) + constructed._validateForPredicateUsage() return constructed } } diff --git a/Sources/FoundationEssentials/Predicate/KeyPath+Inspection.swift b/Sources/FoundationEssentials/Predicate/KeyPath+Inspection.swift index 3fa824e44..f0d204931 100644 --- a/Sources/FoundationEssentials/Predicate/KeyPath+Inspection.swift +++ b/Sources/FoundationEssentials/Predicate/KeyPath+Inspection.swift @@ -50,7 +50,7 @@ extension UInt32 { extension AnyKeyPath { private static var WORD_SIZE: Int { MemoryLayout.size } - func _validateForPredicateUsage(restrictArguments: Bool = true) { + func _validateForPredicateUsage() { var ptr = unsafeBitCast(self, to: UnsafeRawPointer.self) ptr = ptr.advanced(by: Self.WORD_SIZE * 3) // skip isa, type metadata, and KVC string pointers let header = ptr.load(as: UInt32.self) @@ -72,9 +72,7 @@ extension AnyKeyPath { componentWords += 1 } if firstComponentHeader._keyPathComponentHeader_computedHasArguments { - if restrictArguments { - fatalError("Predicate does not support keypaths with arguments") - } + // TODO: Ensure KeyPath only contains generic arguments and not subscript arguments (https://github.com/swiftlang/swift-foundation/issues/1482) let capturesSize = ptr.advanced(by: Self.WORD_SIZE * componentWords).load(as: UInt.self) componentWords += 2 + (Int(capturesSize) / Self.WORD_SIZE) } diff --git a/Tests/FoundationEssentialsTests/PredicateTests.swift b/Tests/FoundationEssentialsTests/PredicateTests.swift index 3eea7d077..b16223c3e 100644 --- a/Tests/FoundationEssentialsTests/PredicateTests.swift +++ b/Tests/FoundationEssentialsTests/PredicateTests.swift @@ -39,6 +39,22 @@ struct PredicateTestObject2 { var a: Bool } + +fileprivate protocol PredicateProducer { + var prop: Int { get } + func getPredicate() -> Predicate +} + +fileprivate struct PredicateProducerConformer : PredicateProducer { + let prop = 2 +} + +extension PredicateProducer { + func getPredicate() -> Predicate { + #Predicate { $0.prop == 2 } + } +} + @Suite("Predicate") private struct PredicateTests { typealias Object = PredicateTestObject @@ -318,6 +334,12 @@ private struct PredicateTests { _ = #Predicate { $0.id == 2 } } + @Test func genericKeyPaths() { + let obj = PredicateProducerConformer() + // Ensure forming a predicate to a generic type does not cause crashes when validating keypaths + _ = obj.getPredicate() + } + #if FOUNDATION_EXIT_TESTS @Test func unsupportedKeyPaths() async { struct Sample { @@ -370,12 +392,15 @@ private struct PredicateTests { } // subscripts with arguments + // This keypath is currently allow but should be considered invalid (https://github.com/swiftlang/swift-foundation/issues/1482) + #if false await #expect(processExitsWith: .failure) { _ = PredicateExpressions.KeyPath( root: PredicateExpressions.Variable(), keyPath: \Sample.[0] ) } + #endif // Optional chaining await #expect(processExitsWith: .failure) {