@@ -33,9 +33,20 @@ 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
3950 }
4051
4152 /// Collapse the given expression graph into one or more expressions with
@@ -81,8 +92,8 @@ public struct __ExpectationContext: ~Copyable {
8192 /// - Returns: An expression value representing the condition expression that
8293 /// was evaluated.
8394 ///
84- /// This function should ideally be `consuming`, but because it is used in a
85- /// `lazy var` declaration, the compiler currently disallows it.
95+ /// - Bug: This function should ideally be `consuming`, but because it is used
96+ /// in a `lazy var` declaration, the compiler currently disallows it.
8697 borrowing func finalize( successfully: Bool ) -> __Expression {
8798 // Construct a graph containing the source code for all the subexpressions
8899 // we've captured during evaluation.
@@ -102,6 +113,15 @@ public struct __ExpectationContext: ~Copyable {
102113 expressionGraph [ keyPath] = expression
103114 }
104115 }
116+
117+ for (id, difference) in differences {
118+ let keyPath = id. keyPath
119+ if var expression = expressionGraph [ keyPath] , let difference = difference ( ) {
120+ let differenceDescription = Self . _description ( of: difference)
121+ expression. differenceDescription = differenceDescription
122+ expressionGraph [ keyPath] = expression
123+ }
124+ }
105125 }
106126
107127 // Flatten the expression graph.
@@ -154,11 +174,12 @@ extension __ExpectationContext {
154174 ///
155175 /// - Warning: This function is used to implement the `#expect()` and
156176 /// `#require()` macros. Do not call it directly.
157- public mutating func callAsFunction< T> ( _ value: T , _ id: __ExpressionID ) -> T where T : Copyable {
177+ public mutating func callAsFunction< T> ( _ value: T , _ id: __ExpressionID ) -> T {
158178 runtimeValues [ id] = { Expression . Value ( reflecting: value) }
159179 return value
160180 }
161181
182+ #if SWT_SUPPORTS_MOVE_ONLY_EXPRESSION_EXPANSION
162183 /// Capture information about a value for use if the expectation currently
163184 /// being evaluated fails.
164185 ///
@@ -176,7 +197,181 @@ extension __ExpectationContext {
176197 // TODO: add support for borrowing non-copyable expressions (need @lifetime)
177198 return value
178199 }
200+ #endif
201+ }
202+
203+ // MARK: - Collection comparison and diffing
204+
205+ extension __ExpectationContext {
206+ /// Convert an instance of `CollectionDifference` to one that is type-erased
207+ /// over elements of type `Any`.
208+ ///
209+ /// - Parameters:
210+ /// - difference: The difference to convert.
211+ ///
212+ /// - Returns: A type-erased copy of `difference`.
213+ private static func _typeEraseCollectionDifference( _ difference: CollectionDifference < some Any > ) -> CollectionDifference < Any > {
214+ CollectionDifference < Any > (
215+ difference. lazy. map { change in
216+ switch change {
217+ case let . insert( offset, element, associatedWith) :
218+ return . insert( offset: offset, element: element as Any , associatedWith: associatedWith)
219+ case let . remove( offset, element, associatedWith) :
220+ return . remove( offset: offset, element: element as Any , associatedWith: associatedWith)
221+ }
222+ }
223+ ) !
224+ }
225+
226+ /// Generate a description of a previously-computed collection difference.
227+ ///
228+ /// - Parameters:
229+ /// - difference: The difference to describe.
230+ ///
231+ /// - Returns: A human-readable string describing `difference`.
232+ private static func _description( of difference: CollectionDifference < some Any > ) -> String {
233+ let insertions : [ String ] = difference. insertions. lazy
234+ . map ( \. element)
235+ . map ( String . init ( describingForTest: ) )
236+ let removals : [ String ] = difference. removals. lazy
237+ . map ( \. element)
238+ . map ( String . init ( describingForTest: ) )
239+
240+ var resultComponents = [ String] ( )
241+ if !insertions. isEmpty {
242+ resultComponents. append ( " inserted [ \( insertions. joined ( separator: " , " ) ) ] " )
243+ }
244+ if !removals. isEmpty {
245+ resultComponents. append ( " removed [ \( removals. joined ( separator: " , " ) ) ] " )
246+ }
247+
248+ return resultComponents. joined ( separator: " , " )
249+ }
250+
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+ /// Compare two bidirectional collections using `==` or `!=`.
283+ ///
284+ /// This overload of `__cmp()` performs a diffing operation on `lhs` and `rhs`
285+ /// if the result of `op(lhs, rhs)` is `false`.
286+ ///
287+ /// - Warning: This function is used to implement the `#expect()` and
288+ /// `#require()` macros. Do not call it directly.
289+ public mutating func __cmp< C> (
290+ _ lhs: C ,
291+ _ lhsID: __ExpressionID ,
292+ _ rhs: C ,
293+ _ rhsID: __ExpressionID ,
294+ _ op: ( C , C ) -> Bool ,
295+ _ opID: __ExpressionID
296+ ) -> Bool where C: BidirectionalCollection , C. Element: Equatable {
297+ let result = self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
298+
299+ if !result {
300+ differences [ opID] = { [ lhs, rhs] in
301+ Self . _typeEraseCollectionDifference ( lhs. difference ( from: rhs) )
302+ }
303+ }
304+
305+ return result
306+ }
179307
308+ /// Compare two range expressions using `==` or `!=`.
309+ ///
310+ /// This overload of `__cmp()` does _not_ perform a diffing operation on `lhs`
311+ /// and `rhs`. Range expressions are not usefully diffable the way other kinds
312+ /// of collections are. ([139222774](rdar://139222774))
313+ ///
314+ /// - Warning: This function is used to implement the `#expect()` and
315+ /// `#require()` macros. Do not call it directly.
316+ public mutating func __cmp< R> (
317+ _ lhs: R ,
318+ _ lhsID: __ExpressionID ,
319+ _ rhs: R ,
320+ _ rhsID: __ExpressionID ,
321+ _ op: ( R , R ) -> Bool ,
322+ _ opID: __ExpressionID
323+ ) -> Bool where R: RangeExpression & BidirectionalCollection , R. Element: Equatable {
324+ self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
325+ }
326+
327+ /// Compare two strings using `==` or `!=`.
328+ ///
329+ /// This overload of `__cmp()` performs a diffing operation on `lhs` and `rhs`
330+ /// if the result of `op(lhs, rhs)` is `false`, but does so by _line_, not by
331+ /// _character_.
332+ ///
333+ /// - Warning: This function is used to implement the `#expect()` and
334+ /// `#require()` macros. Do not call it directly.
335+ public mutating func __cmp< S> (
336+ _ lhs: S ,
337+ _ lhsID: __ExpressionID ,
338+ _ rhs: S ,
339+ _ rhsID: __ExpressionID ,
340+ _ op: ( S , S ) -> Bool ,
341+ _ opID: __ExpressionID
342+ ) -> Bool where S: StringProtocol {
343+ let result = self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
344+
345+ if !result {
346+ differences [ opID] = { [ lhs, rhs] in
347+ // Compare strings by line, not by character.
348+ let lhsLines = String ( lhs) . split ( whereSeparator: \. isNewline)
349+ let rhsLines = String ( rhs) . split ( whereSeparator: \. isNewline)
350+
351+ if lhsLines. count == 1 && rhsLines. count == 1 {
352+ // There are no newlines in either string, so there's no meaningful
353+ // per-line difference. Bail.
354+ return nil
355+ }
356+
357+ let diff = lhsLines. difference ( from: rhsLines)
358+ if diff. isEmpty {
359+ // The strings must have compared on a per-character basis, or this
360+ // operator doesn't behave the way we expected. Bail.
361+ return nil
362+ }
363+
364+ return Self . _typeEraseCollectionDifference ( diff)
365+ }
366+ }
367+
368+ return result
369+ }
370+ }
371+
372+ // MARK: - Casting
373+
374+ extension __ExpectationContext {
180375 /// Perform a conditional cast (`as?`) on a value.
181376 ///
182377 /// - Parameters:
@@ -258,15 +453,15 @@ extension __ExpectationContext {
258453 ///
259454 /// - Warning: This function is used to implement the `#expect()` and
260455 /// `#require()` macros. Do not call it directly.
261- public mutating func callAsFunction< T , U > ( _ value: T , _ id: __ExpressionID ) -> U where T : StringProtocol , U : _Pointer {
456+ public mutating func callAsFunction< P > ( _ value: String , _ id: __ExpressionID ) -> P where P : _Pointer {
262457 // Perform the normal value capture.
263458 let result = self ( value, id)
264459
265460 // Create a C string copy of `value`.
266461#if os(Windows)
267- let resultCString = _strdup ( String ( result) ) !
462+ let resultCString = _strdup ( result) !
268463#else
269- let resultCString = strdup ( String ( result) ) !
464+ let resultCString = strdup ( result) !
270465#endif
271466
272467 // Store the C string pointer so we can free it later when this context is
@@ -277,7 +472,7 @@ extension __ExpectationContext {
277472 _transformedCStrings. append ( resultCString)
278473
279474 // Return the C string as whatever pointer type the caller wants.
280- return U ( bitPattern: Int ( bitPattern: resultCString) ) . unsafelyUnwrapped
475+ return P ( bitPattern: Int ( bitPattern: resultCString) ) . unsafelyUnwrapped
281476 }
282477}
283478#endif
0 commit comments