Skip to content

Commit 77c469b

Browse files
committed
Simplify the logic for parsing and handling test arguments in the @test macro
1 parent 1df9545 commit 77c469b

File tree

3 files changed

+27
-34
lines changed

3 files changed

+27
-34
lines changed

Diff for: Sources/TestingMacros/Support/AttributeDiscovery.swift

+25-28
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,19 @@ struct AttributeInfo {
6666
/// The traits applied to the attribute, if any.
6767
var traits = [ExprSyntax]()
6868

69+
/// Test arguments passed to a parameterized test function, if any.
70+
///
71+
/// When non-`nil`, the value of this property is an array beginning with the
72+
/// argument passed to this attribute for the parameter labeled `arguments:`
73+
/// followed by all of the remaining, unlabeled arguments.
74+
var testFunctionArguments: [Argument]?
75+
6976
/// Whether or not this attribute specifies arguments to the associated test
7077
/// function.
7178
var hasFunctionArguments: Bool {
72-
otherArguments.lazy
73-
.compactMap(\.label?.tokenKind)
74-
.contains(.identifier("arguments"))
79+
testFunctionArguments != nil
7580
}
7681

77-
/// Additional arguments passed to the attribute, if any.
78-
var otherArguments = [Argument]()
79-
8082
/// The source location of the attribute.
8183
///
8284
/// When parsing, the testing library uses the start of the attribute's name
@@ -98,6 +100,7 @@ struct AttributeInfo {
98100
init(byParsing attribute: AttributeSyntax, on declaration: some SyntaxProtocol, in context: some MacroExpansionContext) {
99101
self.attribute = attribute
100102

103+
var nonDisplayNameArguments: [Argument] = []
101104
if let arguments = attribute.arguments, case let .argumentList(argumentList) = arguments {
102105
// If the first argument is an unlabelled string literal, it's the display
103106
// name of the test or suite. If it's anything else, including a nil
@@ -106,11 +109,11 @@ struct AttributeInfo {
106109
let firstArgumentHasLabel = (firstArgument.label != nil)
107110
if !firstArgumentHasLabel, let stringLiteral = firstArgument.expression.as(StringLiteralExprSyntax.self) {
108111
displayName = stringLiteral
109-
otherArguments = argumentList.dropFirst().map(Argument.init)
112+
nonDisplayNameArguments = argumentList.dropFirst().map(Argument.init)
110113
} else if !firstArgumentHasLabel, firstArgument.expression.is(NilLiteralExprSyntax.self) {
111-
otherArguments = argumentList.dropFirst().map(Argument.init)
114+
nonDisplayNameArguments = argumentList.dropFirst().map(Argument.init)
112115
} else {
113-
otherArguments = argumentList.map(Argument.init)
116+
nonDisplayNameArguments = argumentList.map(Argument.init)
114117
}
115118
}
116119
}
@@ -119,7 +122,7 @@ struct AttributeInfo {
119122
// See _SelfRemover for more information. Rewriting a syntax tree discards
120123
// location information from the copy, so only invoke the rewriter if the
121124
// `Self` keyword is present somewhere.
122-
otherArguments = otherArguments.map { argument in
125+
nonDisplayNameArguments = nonDisplayNameArguments.map { argument in
123126
var expr = argument.expression
124127
if argument.expression.tokens(viewMode: .sourceAccurate).map(\.tokenKind).contains(.keyword(.Self)) {
125128
let selfRemover = _SelfRemover(in: context)
@@ -131,15 +134,14 @@ struct AttributeInfo {
131134
// Look for any traits in the remaining arguments and slice them off. Traits
132135
// are the remaining unlabelled arguments. The first labelled argument (if
133136
// present) is the start of subsequent context-specific arguments.
134-
if !otherArguments.isEmpty {
135-
if let labelledArgumentIndex = otherArguments.firstIndex(where: { $0.label != nil }) {
137+
if !nonDisplayNameArguments.isEmpty {
138+
if let labelledArgumentIndex = nonDisplayNameArguments.firstIndex(where: { $0.label != nil }) {
136139
// There is an argument with a label, so splice there.
137-
traits = otherArguments[otherArguments.startIndex ..< labelledArgumentIndex].map(\.expression)
138-
otherArguments = Array(otherArguments[labelledArgumentIndex...])
140+
traits = nonDisplayNameArguments[nonDisplayNameArguments.startIndex ..< labelledArgumentIndex].map(\.expression)
141+
testFunctionArguments = Array(nonDisplayNameArguments[labelledArgumentIndex...])
139142
} else {
140143
// No argument has a label, so all the remaining arguments are traits.
141-
traits = otherArguments.map(\.expression)
142-
otherArguments.removeAll(keepingCapacity: false)
144+
traits = nonDisplayNameArguments.map(\.expression)
143145
}
144146
}
145147

@@ -178,21 +180,16 @@ struct AttributeInfo {
178180
}
179181
}))
180182

181-
// Any arguments of the test declaration macro which specify test arguments
182-
// need to be wrapped a closure so they may be evaluated lazily by the
183-
// testing library at runtime. If any such arguments are present, they will
184-
// begin with a labeled argument named `arguments:` and include all
185-
// subsequent unlabeled arguments.
186-
var otherArguments = self.otherArguments
187-
if let argumentsIndex = otherArguments.firstIndex(where: { $0.label?.tokenKind == .identifier("arguments") }) {
188-
for index in argumentsIndex ..< otherArguments.endIndex {
189-
var argument = otherArguments[index]
190-
argument.expression = .init(ClosureExprSyntax { argument.expression.trimmed })
191-
otherArguments[index] = argument
183+
// If there are any parameterized test function arguments, wrap each in a
184+
// closure so they may be evaluated lazily at runtime.
185+
if let testFunctionArguments {
186+
arguments += testFunctionArguments.map { argument in
187+
var copy = argument
188+
copy.expression = .init(ClosureExprSyntax { argument.expression.trimmed })
189+
return copy
192190
}
193191
}
194192

195-
arguments += otherArguments
196193
arguments.append(Argument(label: "sourceLocation", expression: sourceLocation))
197194

198195
return LabeledExprListSyntax(arguments)

Diff for: Sources/TestingMacros/Support/DiagnosticMessage+Diagnosing.swift

+1-4
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,7 @@ private func _diagnoseIssuesWithParallelizationTrait(_ traitExpr: MemberAccessEx
142142
return
143143
}
144144

145-
let hasArguments = attributeInfo.otherArguments.lazy
146-
.compactMap(\.label?.textWithoutBackticks)
147-
.contains("arguments")
148-
if !hasArguments {
145+
if !attributeInfo.hasFunctionArguments {
149146
// Serializing a non-parameterized test function has no effect.
150147
context.diagnose(.traitHasNoEffect(traitExpr, in: attributeInfo.attribute))
151148
}

Diff for: Sources/TestingMacros/TestDeclarationMacro.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -425,9 +425,8 @@ public struct TestDeclarationMacro: PeerMacro, Sendable {
425425
// case the availability checks fail below.
426426
let unavailableTestName = context.makeUniqueName(thunking: functionDecl)
427427

428-
// TODO: don't assume otherArguments is only parameterized function arguments
429428
var attributeInfo = attributeInfo
430-
attributeInfo.otherArguments = []
429+
attributeInfo.testFunctionArguments = nil
431430
result.append(
432431
"""
433432
@available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.")

0 commit comments

Comments
 (0)