@@ -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)
0 commit comments