@@ -33,9 +33,20 @@ public struct __ExpectationContext: ~Copyable {
33
33
/// will not be assigned a runtime value.
34
34
var runtimeValues : [ __ExpressionID : ( ) -> Expression . Value ? ]
35
35
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
+ ) {
37
47
self . sourceCode = sourceCode
38
48
self . runtimeValues = runtimeValues
49
+ self . differences = differences
39
50
}
40
51
41
52
/// Collapse the given expression graph into one or more expressions with
@@ -81,8 +92,8 @@ public struct __ExpectationContext: ~Copyable {
81
92
/// - Returns: An expression value representing the condition expression that
82
93
/// was evaluated.
83
94
///
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.
86
97
borrowing func finalize( successfully: Bool ) -> __Expression {
87
98
// Construct a graph containing the source code for all the subexpressions
88
99
// we've captured during evaluation.
@@ -102,6 +113,15 @@ public struct __ExpectationContext: ~Copyable {
102
113
expressionGraph [ keyPath] = expression
103
114
}
104
115
}
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
+ }
105
125
}
106
126
107
127
// Flatten the expression graph.
@@ -154,11 +174,12 @@ extension __ExpectationContext {
154
174
///
155
175
/// - Warning: This function is used to implement the `#expect()` and
156
176
/// `#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 {
158
178
runtimeValues [ id] = { Expression . Value ( reflecting: value) }
159
179
return value
160
180
}
161
181
182
+ #if SWT_SUPPORTS_MOVE_ONLY_EXPRESSION_EXPANSION
162
183
/// Capture information about a value for use if the expectation currently
163
184
/// being evaluated fails.
164
185
///
@@ -176,7 +197,181 @@ extension __ExpectationContext {
176
197
// TODO: add support for borrowing non-copyable expressions (need @lifetime)
177
198
return value
178
199
}
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
+ }
179
307
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 {
180
375
/// Perform a conditional cast (`as?`) on a value.
181
376
///
182
377
/// - Parameters:
@@ -258,15 +453,15 @@ extension __ExpectationContext {
258
453
///
259
454
/// - Warning: This function is used to implement the `#expect()` and
260
455
/// `#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 {
262
457
// Perform the normal value capture.
263
458
let result = self ( value, id)
264
459
265
460
// Create a C string copy of `value`.
266
461
#if os(Windows)
267
- let resultCString = _strdup ( String ( result) ) !
462
+ let resultCString = _strdup ( result) !
268
463
#else
269
- let resultCString = strdup ( String ( result) ) !
464
+ let resultCString = strdup ( result) !
270
465
#endif
271
466
272
467
// Store the C string pointer so we can free it later when this context is
@@ -277,7 +472,7 @@ extension __ExpectationContext {
277
472
_transformedCStrings. append ( resultCString)
278
473
279
474
// 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
281
476
}
282
477
}
283
478
#endif
0 commit comments