forked from swiftlang/swift-testing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathExpression.swift
618 lines (562 loc) · 23.6 KB
/
Expression.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//
/// A type representing a Swift expression captured at compile-time from source
/// code.
///
/// Instances of this type are generally opaque to callers. They can be
/// converted to strings representing their source code (captured at compile
/// time) using `String.init(describing:)`.
///
/// If parsing is needed, use the swift-syntax package to convert an instance of
/// this type to an instance of `ExprSyntax` using a Swift expression such as:
///
/// ```swift
/// let swiftSyntaxExpr: ExprSyntax = "\(testExpr)"
/// ```
///
/// - Warning: This type is used to implement the `#expect(exitsWith:)`
/// macro. Do not use it directly. Tools can use the SPI ``Expression``
/// typealias if needed.
public struct __Expression: Sendable {
/// An enumeration describing the various kinds of expression that can be
/// captured.
///
/// This type is not part of the public interface of the testing library.
enum Kind: Sendable {
/// The expression represents a single, complete syntax node.
///
/// - Parameters:
/// - sourceCode: The source code of the represented expression.
case generic(_ sourceCode: String)
/// The expression represents a string literal expression.
///
/// - Parameters:
/// - sourceCode: The source code of the represented expression. Note that
/// this string is not the _value_ of the string literal, but the string
/// literal itself (including leading and trailing quote marks and
/// extended punctuation.)
/// - stringValue: The value of the string literal.
case stringLiteral(sourceCode: String, stringValue: String)
/// The expression represents a binary operation.
///
/// - Parameters:
/// - lhs: The left-hand operand.
/// - operator: The operator.
/// - rhs: The right-hand operand.
indirect case binaryOperation(lhs: __Expression, `operator`: String, rhs: __Expression)
/// A type representing an argument to a function call, used by the
/// `__Expression.Kind.functionCall` case.
///
/// This type is not part of the public interface of the testing library.
struct FunctionCallArgument: Sendable {
/// The label, if present, of the argument.
var label: String?
/// The value, as an expression, of the argument.
var value: __Expression
}
/// The expression represents a function call.
///
/// - Parameters:
/// - value: The value on which the function was called, if any.
/// - functionName: The name of the function that was called.
/// - arguments: The arguments passed to the function.
indirect case functionCall(value: __Expression?, functionName: String, arguments: [FunctionCallArgument])
/// The expression represents a property access.
///
/// - Parameters:
/// - value: The value whose property was accessed.
/// - keyPath: The key path, relative to `value`, that was accessed, not
/// including a leading backslash or period.
indirect case propertyAccess(value: __Expression, keyPath: __Expression)
/// The expression negates another expression.
///
/// - Parameters:
/// - expression: The expression that was negated.
/// - isParenthetical: Whether or not `expression` was enclosed in
/// parentheses (and the `!` operator was outside it.) This argument
/// affects how this expression is represented as a string.
///
/// Unlike other cases in this enumeration, this case affects the runtime
/// behavior of the `__check()` family of functions.
indirect case negation(_ expression: __Expression, isParenthetical: Bool)
}
/// The kind of syntax node represented by this instance.
///
/// This property is not part of the public interface of the testing library.
/// Use `String(describing:)` to access the source code represented by an
/// instance of this type.
var kind: Kind
/// The source code of the original captured expression.
@_spi(ForToolsIntegrationOnly)
public var sourceCode: String {
switch kind {
case let .generic(sourceCode), let .stringLiteral(sourceCode, _):
return sourceCode
case let .binaryOperation(lhs, op, rhs):
return "\(lhs) \(op) \(rhs)"
case let .functionCall(value, functionName, arguments):
let argumentList = arguments.lazy
.map { argument in
if let argumentLabel = argument.label {
return "\(argumentLabel): \(argument.value.sourceCode)"
}
return argument.value.sourceCode
}.joined(separator: ", ")
if let value {
return "\(value.sourceCode).\(functionName)(\(argumentList))"
}
return "\(functionName)(\(argumentList))"
case let .propertyAccess(value, keyPath):
return "\(value.sourceCode).\(keyPath.sourceCode)"
case let .negation(expression, isParenthetical):
var sourceCode = expression.sourceCode
if isParenthetical {
sourceCode = "(\(sourceCode))"
}
return "!\(sourceCode)"
}
}
/// A type which represents an evaluated value, which may include textual
/// descriptions, type information, substructure, and other information.
@_spi(ForToolsIntegrationOnly)
public struct Value: Sendable {
/// A description of this value, formatted using
/// ``Swift/String/init(describingForTest:)``.
public var description: String
/// A debug description of this value, formatted using
/// `String(reflecting:)`.
public var debugDescription: String
/// Information about the type of this value.
public var typeInfo: TypeInfo
/// The label associated with this value, if any.
///
/// For non-child instances, or for child instances of members who do not
/// have a label (such as elements of a collection), the value of this
/// property is `nil`.
public var label: String?
/// Whether or not the values of certain properties of this instance have
/// been truncated for brevity.
///
/// If the value of this property is `true`, this instance does not
/// represent its original value completely because doing so would exceed
/// the maximum allowed data collection settings of the ``Configuration`` in
/// effect. When this occurs, the value ``children`` is not guaranteed to be
/// accurate or complete.
public var isTruncated: Bool = false
/// Whether or not this value represents a collection of values.
public var isCollection: Bool
/// The children of this value, representing its substructure, if any.
///
/// If the value this instance represents does not contain any substructural
/// values but ``isCollection`` is `true`, the value of this property is an
/// empty array. Otherwise, the value of this property is non-`nil` only if
/// the value it represents contains substructural values.
public var children: [Self]?
/// Initialize an instance of this type describing the specified subject.
///
/// - Parameters:
/// - subject: The subject this instance should describe.
init(describing subject: Any) {
description = String(describingForTest: subject)
debugDescription = String(reflecting: subject)
typeInfo = TypeInfo(describingTypeOf: subject)
let mirror = Mirror(reflecting: subject)
isCollection = mirror.displayStyle?.isCollection ?? false
}
/// Initialize an instance of this type with the specified description.
///
/// - Parameters:
/// - description: The value to use for this instance's `description`
/// property.
///
/// Unlike ``init(describing:)``, this initializer does not use
/// ``String/init(describingForTest:)`` to form a description.
private init(_description description: String) {
self.description = description
self.debugDescription = description
typeInfo = TypeInfo(describing: String.self)
isCollection = false
}
/// Initialize an instance of this type describing the specified subject and
/// its children (if any).
///
/// - Parameters:
/// - subject: The subject this instance should reflect.
init?(reflecting subject: Any) {
let configuration = Configuration.current ?? .init()
guard let options = configuration.valueReflectionOptions else {
return nil
}
var seenObjects: [ObjectIdentifier: AnyObject] = [:]
self.init(_reflecting: subject, label: nil, seenObjects: &seenObjects, depth: 0, options: options)
}
/// Initialize an instance of this type describing the specified subject and
/// its children (if any), recursively.
///
/// - Parameters:
/// - subject: The subject this instance should describe.
/// - label: An optional label for this value. This should be a non-`nil`
/// value when creating instances of this type which describe
/// substructural values.
/// - seenObjects: The objects which have been seen so far while calling
/// this initializer recursively, keyed by their object identifiers.
/// This is used to halt further recursion if a previously-seen object
/// is encountered again.
/// - depth: The depth of this recursive call.
/// - options: The configuration options to use when deciding how to
/// reflect `subject`.
private init(
_reflecting subject: Any,
label: String?,
seenObjects: inout [ObjectIdentifier: AnyObject],
depth: Int,
options: Configuration.ValueReflectionOptions
) {
// Stop recursing if we've reached the maximum allowed depth for
// reflection. Instead, return a node describing this value instead and
// set `isTruncated` to `true`.
if depth >= options.maximumChildDepth {
self = Self(describing: subject)
isTruncated = true
return
}
self.init(describing: subject)
self.label = label
let mirror = Mirror(reflecting: subject)
// If the subject being reflected is an instance of a reference type (e.g.
// a class), keep track of whether it has been seen previously. Later
// logic uses this to avoid infinite recursion for values which have
// cyclic object references.
//
// This behavior is gated on the display style of the subject's mirror
// being `.class`. That could be incorrect if a subject implements a
// custom mirror, but in that situation, the subject type is responsible
// for avoiding data references.
//
// For efficiency, this logic matches previously-seen objects based on
// their pointer using `ObjectIdentifier`. This requires conditionally
// down-casting the subject to `AnyObject`, but Swift can downcast any
// value to `AnyObject`, even value types. To ensure only true reference
// types are tracked, this checks the metatype of the subject using
// `type(of:)`, which is inexpensive. The object itself is stored as the
// value in the dictionary to ensure it is retained for the duration of
// the recursion.
var objectIdentifierToRemove: ObjectIdentifier?
var shouldIncludeChildren = true
if mirror.displayStyle == .class, type(of: subject) is AnyObject.Type {
let object = subject as AnyObject
let objectIdentifier = ObjectIdentifier(object)
let oldValue = seenObjects.updateValue(object, forKey: objectIdentifier)
if oldValue != nil {
shouldIncludeChildren = false
} else {
objectIdentifierToRemove = objectIdentifier
}
}
defer {
if let objectIdentifierToRemove {
// Remove the object from the set of previously-seen objects after
// (potentially) recursing to reflect children. This is so that
// repeated references to the same object are still included multiple
// times; only _cyclic_ object references should be avoided.
seenObjects[objectIdentifierToRemove] = nil
}
}
if shouldIncludeChildren && (!mirror.children.isEmpty || isCollection) {
var children: [Self] = []
for (index, child) in mirror.children.enumerated() {
if isCollection && index >= options.maximumCollectionCount {
isTruncated = true
let message = "(\(mirror.children.count - index) out of \(mirror.children.count) elements omitted for brevity)"
children.append(Self(_description: message))
break
}
children.append(Self(_reflecting: child.value, label: child.label, seenObjects: &seenObjects, depth: depth + 1, options: options))
}
self.children = children
}
}
}
/// A representation of the runtime value of this expression.
///
/// If the runtime value of this expression has not been evaluated, the value
/// of this property is `nil`.
@_spi(ForToolsIntegrationOnly)
public var runtimeValue: Value?
/// Copy this instance and capture the runtime value corresponding to it.
///
/// - Parameters:
/// - value: The captured runtime value.
///
/// - Returns: A copy of `self` with information about the specified runtime
/// value captured for future use.
func capturingRuntimeValue(_ value: (some Any)?) -> Self {
var result = self
result.runtimeValue = value.flatMap(Value.init(reflecting:))
if case let .negation(subexpression, isParenthetical) = kind, let value = value as? Bool {
result.kind = .negation(subexpression.capturingRuntimeValue(!value), isParenthetical: isParenthetical)
}
return result
}
/// Copy this instance and capture the runtime values corresponding to its
/// subexpressions.
///
/// - Parameters:
/// - firstValue: The first captured runtime value.
/// - additionalValues: Any additional captured runtime values after the
/// first.
///
/// - Returns: A copy of `self` with information about the specified runtime
/// values captured for future use.
///
/// If the ``kind`` of `self` is ``Kind/generic`` or ``Kind/stringLiteral``,
/// this function is equivalent to ``capturingRuntimeValue(_:)``.
func capturingRuntimeValues<each T>(_ firstValue: (some Any)?, _ additionalValues: repeat (each T)?) -> Self {
var result = self
// Convert the variadic generic argument list to an array.
var additionalValuesArray = [Any?]()
repeat additionalValuesArray.append(each additionalValues)
switch kind {
case .generic, .stringLiteral:
result = capturingRuntimeValue(firstValue)
case let .binaryOperation(lhsExpr, op, rhsExpr):
result.kind = .binaryOperation(
lhs: lhsExpr.capturingRuntimeValues(firstValue),
operator: op,
rhs: rhsExpr.capturingRuntimeValues(additionalValuesArray.first ?? nil)
)
case let .functionCall(value, functionName, arguments):
result.kind = .functionCall(
value: value?.capturingRuntimeValues(firstValue),
functionName: functionName,
arguments: zip(arguments, additionalValuesArray).map { argument, value in
.init(label: argument.label, value: argument.value.capturingRuntimeValues(value))
}
)
case let .propertyAccess(value, keyPath):
result.kind = .propertyAccess(
value: value.capturingRuntimeValues(firstValue),
keyPath: keyPath.capturingRuntimeValues(additionalValuesArray.first ?? nil)
)
case let .negation(expression, isParenthetical):
result.kind = .negation(
expression.capturingRuntimeValues(firstValue, repeat each additionalValues),
isParenthetical: isParenthetical
)
}
return result
}
/// Get an expanded description of this instance that contains the source
/// code and runtime value (or values) it represents.
///
/// - Returns: A string describing this instance.
@_spi(ForToolsIntegrationOnly)
public func expandedDescription() -> String {
_expandedDescription(in: _ExpandedDescriptionContext())
}
/// Get an expanded description of this instance that contains the source
/// code and runtime value (or values) it represents.
///
/// - Returns: A string describing this instance.
///
/// This function produces a more detailed description than
/// ``expandedDescription()``, similar to how `String(reflecting:)` produces
/// a more detailed description than `String(describing:)`.
func expandedDebugDescription() -> String {
var context = _ExpandedDescriptionContext()
context.includeTypeNames = true
context.includeParenthesesIfNeeded = false
return _expandedDescription(in: context)
}
/// A structure describing the state tracked while calling
/// `_expandedDescription(in:)`.
private struct _ExpandedDescriptionContext {
/// The depth of recursion at which the function is being called.
var depth = 0
/// Whether or not to include type names in output.
var includeTypeNames = false
/// Whether or not to enclose the resulting string in parentheses (as needed
/// depending on what information the resulting string contains.)
var includeParenthesesIfNeeded = true
}
/// Get an expanded description of this instance that contains the source
/// code and runtime value (or values) it represents.
///
/// - Parameters:
/// - context: The context for this call.
///
/// - Returns: A string describing this instance.
///
/// This function provides the implementation of ``expandedDescription()`` and
/// ``expandedDebugDescription()``.
private func _expandedDescription(in context: _ExpandedDescriptionContext) -> String {
// Create a (default) context value to pass to recursive calls for
// subexpressions.
var childContext = context
do {
// Bump the depth so that recursive calls track the next depth level.
childContext.depth += 1
// Subexpressions do not automatically disable parentheses if the parent
// does; they must opt in.
childContext.includeParenthesesIfNeeded = true
}
var result = ""
switch kind {
case let .generic(sourceCode), let .stringLiteral(sourceCode, _):
result = if context.includeTypeNames, let qualifiedName = runtimeValue?.typeInfo.fullyQualifiedName {
"\(sourceCode): \(qualifiedName)"
} else {
sourceCode
}
case let .binaryOperation(lhsExpr, op, rhsExpr):
result = "\(lhsExpr._expandedDescription(in: childContext)) \(op) \(rhsExpr._expandedDescription(in: childContext))"
case let .functionCall(value, functionName, arguments):
var argumentContext = childContext
argumentContext.includeParenthesesIfNeeded = (arguments.count > 1)
let argumentList = arguments.lazy
.map { argument in
(argument.label, argument.value._expandedDescription(in: argumentContext))
}.map { label, value in
if let label {
return "\(label): \(value)"
}
return value
}.joined(separator: ", ")
result = if let value {
"\(value._expandedDescription(in: childContext)).\(functionName)(\(argumentList))"
} else {
"\(functionName)(\(argumentList))"
}
case let .propertyAccess(value, keyPath):
var keyPathContext = childContext
keyPathContext.includeParenthesesIfNeeded = false
result = "\(value._expandedDescription(in: childContext)).\(keyPath._expandedDescription(in: keyPathContext))"
case let .negation(expression, isParenthetical):
childContext.includeParenthesesIfNeeded = !isParenthetical
var expandedDescription = expression._expandedDescription(in: childContext)
if isParenthetical {
expandedDescription = "(\(expandedDescription))"
}
result = "!\(expandedDescription)"
}
// If this expression is at the root of the expression graph...
if context.depth == 0 {
if runtimeValue == nil {
// ... and has no value, don't bother reporting the placeholder string
// for it...
return result
} else if let runtimeValue, runtimeValue.typeInfo.describes(Bool.self) {
// ... or if it is a boolean value, also don't bother (because it can be
// inferred from context.)
return result
}
}
let runtimeValueDescription = runtimeValue.map(String.init(describing:)) ?? "<not evaluated>"
result = if runtimeValueDescription == "(Function)" {
// Hack: don't print string representations of function calls.
result
} else if runtimeValueDescription == result {
result
} else if context.includeParenthesesIfNeeded && context.depth > 0 {
"(\(result) → \(runtimeValueDescription))"
} else {
"\(result) → \(runtimeValueDescription)"
}
return result
}
/// The set of parsed and captured subexpressions contained in this instance.
@_spi(ForToolsIntegrationOnly)
public var subexpressions: [Self] {
switch kind {
case .generic, .stringLiteral:
[]
case let .binaryOperation(lhs, _, rhs):
[lhs, rhs]
case let .functionCall(value, _, arguments):
if let value {
CollectionOfOne(value) + arguments.lazy.map(\.value)
} else {
arguments.lazy.map(\.value)
}
case let .propertyAccess(value: value, keyPath: keyPath):
[value, keyPath]
case let .negation(expression, _):
[expression]
}
}
/// The string value associated with this instance if it represents a string
/// literal.
///
/// If this instance represents an expression other than a string literal, the
/// value of this property is `nil`.
@_spi(ForToolsIntegrationOnly)
public var stringLiteralValue: String? {
if case let .stringLiteral(_, stringValue) = kind {
return stringValue
}
return nil
}
}
// MARK: - Codable
extension __Expression: Codable {}
extension __Expression.Kind: Codable {}
extension __Expression.Kind.FunctionCallArgument: Codable {}
extension __Expression.Value: Codable {}
// MARK: - CustomStringConvertible, CustomDebugStringConvertible
extension __Expression: CustomStringConvertible, CustomDebugStringConvertible {
/// Initialize an instance of this type containing the specified source code.
///
/// - Parameters:
/// - sourceCode: The source code of the expression being described.
///
/// To get the string value of an expression, pass it to
/// `String.init(describing:)`.
///
/// This initializer does not attempt to parse `sourceCode`.
@_spi(ForToolsIntegrationOnly)
public init(_ sourceCode: String) {
self.init(kind: .generic(sourceCode))
}
public var description: String {
sourceCode
}
public var debugDescription: String {
String(reflecting: kind)
}
}
extension __Expression.Value: CustomStringConvertible, CustomDebugStringConvertible {}
/// A type representing a Swift expression captured at compile-time from source
/// code.
///
/// Instances of this type are generally opaque to callers. They can be
/// converted to strings representing their source code (captured at compile
/// time) using `String.init(describing:)`.
///
/// If parsing is needed, use the swift-syntax package to convert an instance of
/// this type to an instance of `ExprSyntax` using a Swift expression such as:
///
/// ```swift
/// let swiftSyntaxExpr: ExprSyntax = "\(testExpr)"
/// ```
@_spi(ForToolsIntegrationOnly)
public typealias Expression = __Expression
extension Mirror.DisplayStyle {
/// Whether or not this display style represents a collection of values.
fileprivate var isCollection: Bool {
switch self {
case .collection,
.dictionary,
.set:
true
default:
false
}
}
}