@@ -33,9 +33,65 @@ public struct __ExpectationContext: ~Copyable {
3333 /// will not be assigned a runtime value.
3434 var runtimeValues : [ __ExpressionID : ( ) -> Expression . Value ? ]
3535
36- init ( sourceCode: [ __ExpressionID : String ] = [ : ] , runtimeValues: [ __ExpressionID : ( ) -> Expression . Value ? ] = [ : ] ) {
36+ /// Computed differences between the operands or arguments of expressions.
37+ ///
38+ /// The values in this dictionary are gathered at runtime as subexpressions
39+ /// are evaluated, much like ``runtimeValues``.
40+ var differences : [ __ExpressionID : ( ) -> CollectionDifference < Any > ? ]
41+
42+ init (
43+ sourceCode: [ __ExpressionID : String ] = [ : ] ,
44+ runtimeValues: [ __ExpressionID : ( ) -> Expression . Value ? ] = [ : ] ,
45+ differences: [ __ExpressionID : ( ) -> CollectionDifference < Any > ? ] = [ : ]
46+ ) {
3747 self . sourceCode = sourceCode
3848 self . runtimeValues = runtimeValues
49+ self . differences = differences
50+ }
51+
52+ /// Convert an instance of `CollectionDifference` to one that is type-erased
53+ /// over elements of type `Any`.
54+ ///
55+ /// - Parameters:
56+ /// - difference: The difference to convert.
57+ ///
58+ /// - Returns: A type-erased copy of `difference`.
59+ private static func _typeEraseCollectionDifference( _ difference: CollectionDifference < some Any > ) -> CollectionDifference < Any > {
60+ CollectionDifference < Any > (
61+ difference. lazy. map { change in
62+ switch change {
63+ case let . insert( offset, element, associatedWith) :
64+ return . insert( offset: offset, element: element as Any , associatedWith: associatedWith)
65+ case let . remove( offset, element, associatedWith) :
66+ return . remove( offset: offset, element: element as Any , associatedWith: associatedWith)
67+ }
68+ }
69+ ) !
70+ }
71+
72+ /// Generate a description of a previously-computed collection difference.
73+ ///
74+ /// - Parameters:
75+ /// - difference: The difference to describe.
76+ ///
77+ /// - Returns: A human-readable string describing `difference`.
78+ private borrowing func _description( of difference: CollectionDifference < some Any > ) -> String {
79+ let insertions : [ String ] = difference. insertions. lazy
80+ . map ( \. element)
81+ . map ( String . init ( describingForTest: ) )
82+ let removals : [ String ] = difference. removals. lazy
83+ . map ( \. element)
84+ . map ( String . init ( describingForTest: ) )
85+
86+ var resultComponents = [ String] ( )
87+ if !insertions. isEmpty {
88+ resultComponents. append ( " inserted [ \( insertions. joined ( separator: " , " ) ) ] " )
89+ }
90+ if !removals. isEmpty {
91+ resultComponents. append ( " removed [ \( removals. joined ( separator: " , " ) ) ] " )
92+ }
93+
94+ return resultComponents. joined ( separator: " , " )
3995 }
4096
4197 /// Collapse the given expression graph into one or more expressions with
@@ -102,6 +158,15 @@ public struct __ExpectationContext: ~Copyable {
102158 expressionGraph [ keyPath] = expression
103159 }
104160 }
161+
162+ for (id, difference) in differences {
163+ let keyPath = id. keyPath
164+ if var expression = expressionGraph [ keyPath] , let difference = difference ( ) {
165+ let differenceDescription = _description ( of: difference)
166+ expression. differenceDescription = differenceDescription
167+ expressionGraph [ keyPath] = expression
168+ }
169+ }
105170 }
106171
107172 // Flatten the expression graph.
@@ -154,11 +219,12 @@ extension __ExpectationContext {
154219 ///
155220 /// - Warning: This function is used to implement the `#expect()` and
156221 /// `#require()` macros. Do not call it directly.
157- public mutating func callAsFunction< T> ( _ value: T , _ id: __ExpressionID ) -> T where T : Copyable {
222+ public mutating func callAsFunction< T> ( _ value: T , _ id: __ExpressionID ) -> T {
158223 runtimeValues [ id] = { Expression . Value ( reflecting: value) }
159224 return value
160225 }
161226
227+ #if SWT_SUPPORTS_MOVE_ONLY_EXPRESSION_EXPANSION
162228 /// Capture information about a value for use if the expectation currently
163229 /// being evaluated fails.
164230 ///
@@ -176,7 +242,113 @@ extension __ExpectationContext {
176242 // TODO: add support for borrowing non-copyable expressions (need @lifetime)
177243 return value
178244 }
245+ #endif
246+ }
247+
248+ // MARK: - Collection comparison
249+
250+ extension __ExpectationContext {
251+ /// Compare two values using `==` or `!=`.
252+ ///
253+ /// - Parameters:
254+ /// - lhs: The left-hand operand.
255+ /// - lhsID: A value that uniquely identifies the expression represented by
256+ /// `lhs` in the context of the expectation currently being evaluated.
257+ /// - rhs: The left-hand operand.
258+ /// - rhsID: A value that uniquely identifies the expression represented by
259+ /// `rhs` in the context of the expectation currently being evaluated.
260+ /// - op: A function that performs an operation on `lhs` and `rhs`.
261+ /// - opID: A value that uniquely identifies the expression represented by
262+ /// `op` in the context of the expectation currently being evaluated.
263+ ///
264+ /// - Returns: The result of calling `op(lhs, rhs)`.
265+ ///
266+ /// This overload of `__cmp()` serves as a catch-all for operands that are not
267+ /// collections or otherwise are not interesting to the testing library.
268+ ///
269+ /// - Warning: This function is used to implement the `#expect()` and
270+ /// `#require()` macros. Do not call it directly.
271+ public mutating func __cmp< T, U, R> (
272+ _ lhs: T ,
273+ _ lhsID: __ExpressionID ,
274+ _ rhs: U ,
275+ _ rhsID: __ExpressionID ,
276+ _ op: ( T , U ) throws -> R ,
277+ _ opID: __ExpressionID
278+ ) rethrows -> R {
279+ try self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
280+ }
281+
282+ public mutating func __cmp< C> (
283+ _ lhs: C ,
284+ _ lhsID: __ExpressionID ,
285+ _ rhs: C ,
286+ _ rhsID: __ExpressionID ,
287+ _ op: ( C , C ) -> Bool ,
288+ _ opID: __ExpressionID
289+ ) -> Bool where C: BidirectionalCollection , C. Element: Equatable {
290+ let result = self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
291+
292+ if !result {
293+ differences [ opID] = { [ lhs, rhs] in
294+ Self . _typeEraseCollectionDifference ( lhs. difference ( from: rhs) )
295+ }
296+ }
297+
298+ return result
299+ }
300+
301+ public mutating func __cmp< R> (
302+ _ lhs: R ,
303+ _ lhsID: __ExpressionID ,
304+ _ rhs: R ,
305+ _ rhsID: __ExpressionID ,
306+ _ op: ( R , R ) -> Bool ,
307+ _ opID: __ExpressionID
308+ ) -> Bool where R: RangeExpression & BidirectionalCollection , R. Element: Equatable {
309+ self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
310+ }
311+
312+ public mutating func __cmp< S> (
313+ _ lhs: S ,
314+ _ lhsID: __ExpressionID ,
315+ _ rhs: S ,
316+ _ rhsID: __ExpressionID ,
317+ _ op: ( S , S ) -> Bool ,
318+ _ opID: __ExpressionID
319+ ) -> Bool where S: StringProtocol {
320+ let result = self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
179321
322+ if !result {
323+ differences [ opID] = { [ lhs, rhs] in
324+ // Compare strings by line, not by character.
325+ let lhsLines = String ( lhs) . split ( whereSeparator: \. isNewline)
326+ let rhsLines = String ( rhs) . split ( whereSeparator: \. isNewline)
327+
328+ if lhsLines. count == 1 && rhsLines. count == 1 {
329+ // There are no newlines in either string, so there's no meaningful
330+ // per-line difference. Bail.
331+ return nil
332+ }
333+
334+ let diff = lhsLines. difference ( from: rhsLines)
335+ if diff. isEmpty {
336+ // The strings must have compared on a per-character basis, or this
337+ // operator doesn't behave the way we expected. Bail.
338+ return nil
339+ }
340+
341+ return Self . _typeEraseCollectionDifference ( diff)
342+ }
343+ }
344+
345+ return result
346+ }
347+ }
348+
349+ // MARK: - Casting
350+
351+ extension __ExpectationContext {
180352 /// Perform a conditional cast (`as?`) on a value.
181353 ///
182354 /// - Parameters:
@@ -258,15 +430,15 @@ extension __ExpectationContext {
258430 ///
259431 /// - Warning: This function is used to implement the `#expect()` and
260432 /// `#require()` macros. Do not call it directly.
261- public mutating func callAsFunction< T , U > ( _ value: T , _ id: __ExpressionID ) -> U where T : StringProtocol , U : _Pointer {
433+ public mutating func callAsFunction< P > ( _ value: String , _ id: __ExpressionID ) -> P where P : _Pointer {
262434 // Perform the normal value capture.
263435 let result = self ( value, id)
264436
265437 // Create a C string copy of `value`.
266438#if os(Windows)
267- let resultCString = _strdup ( String ( result) ) !
439+ let resultCString = _strdup ( result) !
268440#else
269- let resultCString = strdup ( String ( result) ) !
441+ let resultCString = strdup ( result) !
270442#endif
271443
272444 // Store the C string pointer so we can free it later when this context is
@@ -277,7 +449,7 @@ extension __ExpectationContext {
277449 _transformedCStrings. append ( resultCString)
278450
279451 // Return the C string as whatever pointer type the caller wants.
280- return U ( bitPattern: Int ( bitPattern: resultCString) ) . unsafelyUnwrapped
452+ return P ( bitPattern: Int ( bitPattern: resultCString) ) . unsafelyUnwrapped
281453 }
282454}
283455#endif
0 commit comments