diff --git a/Sources/FoundationEssentials/Predicate/NSPredicateConversion.swift b/Sources/FoundationEssentials/Predicate/NSPredicateConversion.swift index 9e06056f5..cf554e09b 100644 --- a/Sources/FoundationEssentials/Predicate/NSPredicateConversion.swift +++ b/Sources/FoundationEssentials/Predicate/NSPredicateConversion.swift @@ -374,11 +374,11 @@ extension PredicateExpressions.RangeExpressionContains : ConvertibleExpression { } else if let rangeValue = (range as? _RangeValue)?._anyRange { // Otherwise, if the range is a captured value then convert it to appropriate comparison expressions based on the range type switch rangeValue { - case let .range(upper, lower): + case let .range(lower, upper): let lowerBoundCondition = _comparison(elementExpr, try _expressionForBound(lower), type: .greaterThanOrEqualTo) let upperBoundCondition = _comparison(elementExpr, try _expressionForBound(upper), type: .lessThan) return .predicate(NSCompoundPredicate(andPredicateWithSubpredicates: [lowerBoundCondition, upperBoundCondition])) - case let .closed(upper, lower): + case let .closed(lower, upper): let lowerValue = try _expressionCompatibleValue(for: lower) let upperValue = try _expressionCompatibleValue(for: upper) return .predicate(NSComparisonPredicate( diff --git a/Tests/FoundationEssentialsTests/PredicateConversionTests.swift b/Tests/FoundationEssentialsTests/PredicateConversionTests.swift index 77fa06ac8..0f307d359 100644 --- a/Tests/FoundationEssentialsTests/PredicateConversionTests.swift +++ b/Tests/FoundationEssentialsTests/PredicateConversionTests.swift @@ -177,6 +177,32 @@ private struct NSPredicateConversionTests { #expect(!converted.evaluate(with: ObjCObject())) } + @Test func rangesDistinctBounds() throws { + // Test that captured ranges have correct bounds order in NSPredicate conversion + // This tests the _RangeValue branch in RangeExpressionContains.convert + let lowerDate = Date(timeIntervalSinceReferenceDate: 100) + let upperDate = Date(timeIntervalSinceReferenceDate: 200) + let range = lowerDate ..< upperDate + + let predicate = #Predicate { + range.contains($0.i) + } + let converted = try #require(convert(predicate)) + + // Verify the bounds are in the correct order (lower, upper) + #expect(converted == NSPredicate(format: "i >= %@ AND i < %@", lowerDate as NSDate, upperDate as NSDate)) + + // Create an object with a date in the range + let objInRange = ObjCObject() + objInRange.i = Date(timeIntervalSinceReferenceDate: 150) + #expect(converted.evaluate(with: objInRange), "Date in range should match") + + // Create an object with a date outside the range + let objOutOfRange = ObjCObject() + objOutOfRange.i = Date(timeIntervalSinceReferenceDate: 250) + #expect(!converted.evaluate(with: objOutOfRange), "Date outside range should not match") + } + @Test func nonObjC() throws { let predicate = #Predicate { $0.nonObjCKeypath == 2