From 1912d841e8e57de9181780d50ca3b7d0e0fa8a82 Mon Sep 17 00:00:00 2001 From: ojun9 Date: Sun, 15 Mar 2026 17:26:25 +0900 Subject: [PATCH 01/14] Preserve contextual type for literal arguments wrapped by @Test macro --- .../Support/AttributeDiscovery.swift | 41 ++++++++++++++++++- .../TestDeclarationMacroTests.swift | 29 +++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/Sources/TestingMacros/Support/AttributeDiscovery.swift b/Sources/TestingMacros/Support/AttributeDiscovery.swift index c3d19543b..4842c9cb8 100644 --- a/Sources/TestingMacros/Support/AttributeDiscovery.swift +++ b/Sources/TestingMacros/Support/AttributeDiscovery.swift @@ -171,7 +171,12 @@ struct AttributeInfo { if let testFunctionArguments { arguments += testFunctionArguments.map { argument in var copy = argument - copy.expression = .init(ClosureExprSyntax { argument.expression.trimmed }) + let argumentExpr = argument.expression.trimmed + if let contextualType = _contextualTypeForLiteralArgument(for: argumentExpr, among: testFunctionArguments) { + copy.expression = .init(ClosureExprSyntax { "\(argumentExpr) as \(raw: contextualType)" as ExprSyntax }) + } else { + copy.expression = .init(ClosureExprSyntax { argumentExpr }) + } return copy } } @@ -180,4 +185,38 @@ struct AttributeInfo { return LabeledExprListSyntax(arguments) } + + /// The contextual type to explicitly apply to a literal `arguments:` + /// expression after it is wrapped in a closure for lazy evaluation. + /// + /// Parameterized `@Test` declarations are modeled in terms of the collection + /// type supplied to the macro, but macro expansion only sees source syntax. + /// When the `arguments:` parameter is supplied as a single array literal, + /// reconstruct the array type from the test function's parameters so the + /// literal retains enough contextual type information after lazy wrapping. + private func _contextualTypeForLiteralArgument( + for expression: ExprSyntax, + among testFunctionArguments: [Argument] + ) -> String? { + guard let functionDecl = declaration.as(FunctionDeclSyntax.self) else { + return nil + } + + let parameters = functionDecl.signature.parameterClause.parameters + guard !parameters.isEmpty else { + return nil + } + + if testFunctionArguments.count == 1, expression.is(ArrayExprSyntax.self) { + if parameters.count == 1, let parameter = parameters.first { + // A single-parameter test expects collection elements of the parameter + // type itself, not tuple-shaped elements. + return "[\(parameter.baseTypeName)]" + } + let elementType = parameters.map(\.baseTypeName).joined(separator: ", ") + return "[(\(elementType))]" + } + + return nil + } } diff --git a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift index 8131bbb67..b0fbd216e 100644 --- a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift +++ b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift @@ -512,6 +512,35 @@ struct TestDeclarationMacroTests { } } + static var parameterizedArgumentTypePreservationInputs: [(String, String)] { + [ + ( + """ + @Test(arguments: []) + func f(i: Int) {} + """, + #"arguments:{[]as[Int]}"# + ), + ( + """ + @Test(arguments: [ + (nil, 1), + ("a", nil), + ("b", nil) + ]) + func f(s: String?, i: Int?) {} + """, + #"arguments:{[(nil,1),("a",nil),("b",nil)]as[(String?,Int?)]}"# + ), + ] + } + + @Test("Literal arguments preserve contextual types after lazy wrapping", arguments: parameterizedArgumentTypePreservationInputs) + func preservesParameterizedArgumentTypes(input: String, expectedOutput: String) throws { + let (output, _) = try parse(input, removeWhitespace: true) + #expect(output.contains(expectedOutput)) + } + @Test("Display name is preserved", arguments: [ #"@Test("Display Name") func f() {}"#, From 91213c6e082d3b1513ac58052d911b643230272c Mon Sep 17 00:00:00 2001 From: ojun9 Date: Tue, 17 Mar 2026 14:05:17 +0900 Subject: [PATCH 02/14] review feedback --- Sources/TestingMacros/Support/AttributeDiscovery.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TestingMacros/Support/AttributeDiscovery.swift b/Sources/TestingMacros/Support/AttributeDiscovery.swift index 4842c9cb8..bf65676e1 100644 --- a/Sources/TestingMacros/Support/AttributeDiscovery.swift +++ b/Sources/TestingMacros/Support/AttributeDiscovery.swift @@ -203,7 +203,7 @@ struct AttributeInfo { } let parameters = functionDecl.signature.parameterClause.parameters - guard !parameters.isEmpty else { + if parameters.isEmpty { return nil } From c27dc5c98424854eb8afbaa1949c270becfc0017 Mon Sep 17 00:00:00 2001 From: ojun9 Date: Tue, 17 Mar 2026 14:06:32 +0900 Subject: [PATCH 03/14] review feedback --- Sources/TestingMacros/Support/AttributeDiscovery.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/TestingMacros/Support/AttributeDiscovery.swift b/Sources/TestingMacros/Support/AttributeDiscovery.swift index bf65676e1..1a01e08a5 100644 --- a/Sources/TestingMacros/Support/AttributeDiscovery.swift +++ b/Sources/TestingMacros/Support/AttributeDiscovery.swift @@ -197,7 +197,7 @@ struct AttributeInfo { private func _contextualTypeForLiteralArgument( for expression: ExprSyntax, among testFunctionArguments: [Argument] - ) -> String? { + ) -> TypeSyntax? { guard let functionDecl = declaration.as(FunctionDeclSyntax.self) else { return nil } @@ -211,10 +211,10 @@ struct AttributeInfo { if parameters.count == 1, let parameter = parameters.first { // A single-parameter test expects collection elements of the parameter // type itself, not tuple-shaped elements. - return "[\(parameter.baseTypeName)]" + return TypeSyntax(stringLiteral: "[\(parameter.baseTypeName)]") } let elementType = parameters.map(\.baseTypeName).joined(separator: ", ") - return "[(\(elementType))]" + return TypeSyntax(stringLiteral: "[(\(elementType))]") } return nil From 8d515620d40b11a73a3e0bf2004e8833c11e3acf Mon Sep 17 00:00:00 2001 From: ojun9 Date: Tue, 17 Mar 2026 14:40:19 +0900 Subject: [PATCH 04/14] review feedback --- .../FunctionDeclSyntaxAdditions.swift | 12 +++- .../Support/AttributeDiscovery.swift | 68 +++++++++++++++---- .../TestDeclarationMacroTests.swift | 27 ++++++++ 3 files changed, 91 insertions(+), 16 deletions(-) diff --git a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift index 8065d299e..fa3e776c3 100644 --- a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift +++ b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift @@ -174,12 +174,18 @@ extension FunctionParameterSyntax { } extension FunctionParameterSyntax { - /// The base type name of this parameter. - var baseTypeName: String { + /// The underlying type of this parameter with any attributed type wrappers + /// (such as `inout`) removed. + var baseType: TypeSyntax { // Discard any specifiers such as `inout` or `borrowing`, since we're only // trying to obtain the base type to reference it in an expression. let baseType = type.as(AttributedTypeSyntax.self)?.baseType ?? type - return baseType.trimmedDescription + return baseType.trimmed + } + + /// The base type name of this parameter. + var baseTypeName: String { + baseType.trimmedDescription } } diff --git a/Sources/TestingMacros/Support/AttributeDiscovery.swift b/Sources/TestingMacros/Support/AttributeDiscovery.swift index 1a01e08a5..92a3b0684 100644 --- a/Sources/TestingMacros/Support/AttributeDiscovery.swift +++ b/Sources/TestingMacros/Support/AttributeDiscovery.swift @@ -169,14 +169,24 @@ struct AttributeInfo { // If there are any parameterized test function arguments, wrap each in a // closure so they may be evaluated lazily at runtime. if let testFunctionArguments { - arguments += testFunctionArguments.map { argument in + arguments += testFunctionArguments.enumerated().map { index, argument in var copy = argument - let argumentExpr = argument.expression.trimmed - if let contextualType = _contextualTypeForLiteralArgument(for: argumentExpr, among: testFunctionArguments) { - copy.expression = .init(ClosureExprSyntax { "\(argumentExpr) as \(raw: contextualType)" as ExprSyntax }) - } else { - copy.expression = .init(ClosureExprSyntax { argumentExpr }) + var expr = copy.expression.trimmed + if let contextualType = _contextualTypeForLiteralArgument( + at: index, + for: expr, + among: testFunctionArguments + ) { + expr = ExprSyntax( + AsExprSyntax( + expression: expr, + asKeyword: .keyword(.as, leadingTrivia: .space, trailingTrivia: .space), + type: contextualType.trimmed + ) + ) } + let lazyExpression = expr.trimmed + copy.expression = .init(ClosureExprSyntax { lazyExpression }) return copy } } @@ -191,30 +201,62 @@ struct AttributeInfo { /// /// Parameterized `@Test` declarations are modeled in terms of the collection /// type supplied to the macro, but macro expansion only sees source syntax. - /// When the `arguments:` parameter is supplied as a single array literal, - /// reconstruct the array type from the test function's parameters so the + /// When the `arguments:` parameter is supplied as an array literal, derive + /// the corresponding array type from the test function's parameters so the /// literal retains enough contextual type information after lazy wrapping. + /// + /// This applies to both the single-collection form and the overloads where + /// each `arguments:` expression corresponds directly to one parameter. + /// + /// - Parameters: + /// - index: The position of `expression` within `testFunctionArguments`. + /// - expression: The argument expression being wrapped for lazy evaluation. + /// - testFunctionArguments: The full list of argument expressions supplied + /// to the parameterized `@Test`. + /// + /// - Returns: The array type to apply to `expression`, or `nil` if no + /// contextual type reconstruction is needed. private func _contextualTypeForLiteralArgument( + at index: Int, for expression: ExprSyntax, among testFunctionArguments: [Argument] ) -> TypeSyntax? { + guard expression.is(ArrayExprSyntax.self) else { + return nil + } + guard let functionDecl = declaration.as(FunctionDeclSyntax.self) else { return nil } - let parameters = functionDecl.signature.parameterClause.parameters + let parameters = Array(functionDecl.signature.parameterClause.parameters) if parameters.isEmpty { return nil } - if testFunctionArguments.count == 1, expression.is(ArrayExprSyntax.self) { + if testFunctionArguments.count == parameters.count { + let parameter = parameters[index] + return TypeSyntax( + ArrayTypeSyntax(element: parameter.baseType.trimmed) + ) + } + + if testFunctionArguments.count == 1 { if parameters.count == 1, let parameter = parameters.first { // A single-parameter test expects collection elements of the parameter // type itself, not tuple-shaped elements. - return TypeSyntax(stringLiteral: "[\(parameter.baseTypeName)]") + return TypeSyntax( + ArrayTypeSyntax(element: parameter.baseType.trimmed) + ) } - let elementType = parameters.map(\.baseTypeName).joined(separator: ", ") - return TypeSyntax(stringLiteral: "[(\(elementType))]") + let elementType = TypeSyntax( + TupleTypeSyntax(elements: TupleTypeElementListSyntax { + for parameter in parameters { + TupleTypeElementSyntax(type: parameter.baseType.trimmed) + } + }) + ) + return TypeSyntax(ArrayTypeSyntax(element: elementType)) } return nil diff --git a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift index b0fbd216e..359bc5dd1 100644 --- a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift +++ b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift @@ -521,6 +521,20 @@ struct TestDeclarationMacroTests { """, #"arguments:{[]as[Int]}"# ), + ( + """ + @Test(arguments: [], []) + func f(i: Int, s: String) {} + """, + #"arguments:{[]as[Int]},{[]as[String]}"# + ), + ( + """ + @Test(arguments: [nil], [nil]) + func f(i: Int?, s: String?) {} + """, + #"arguments:{[nil]as[Int?]},{[nil]as[String?]}"# + ), ( """ @Test(arguments: [ @@ -541,6 +555,19 @@ struct TestDeclarationMacroTests { #expect(output.contains(expectedOutput)) } + @Test("Non-literal parameterized arguments are left unchanged") + func nonLiteralParameterizedArgumentsRemainUncast() throws { + let input = """ + let ints = [1, 2] + let strings = ["a", "b"] + @Test(arguments: ints, strings) + func f(i: Int, s: String) {} + """ + let (output, _) = try parse(input, removeWhitespace: true) + #expect(output.contains(#"arguments:{ints},{strings}"#)) + #expect(!output.contains(#"arguments:{intsas[Int]},{stringsas[String]}"#)) + } + @Test("Display name is preserved", arguments: [ #"@Test("Display Name") func f() {}"#, From 5d3e5d0068f7f009ed9fcfff9bed634c8f12f614 Mon Sep 17 00:00:00 2001 From: ojun9 Date: Thu, 26 Mar 2026 22:19:19 +0900 Subject: [PATCH 05/14] Untrim baseType --- .../Support/Additions/FunctionDeclSyntaxAdditions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift index fa3e776c3..060738979 100644 --- a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift +++ b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift @@ -180,7 +180,7 @@ extension FunctionParameterSyntax { // Discard any specifiers such as `inout` or `borrowing`, since we're only // trying to obtain the base type to reference it in an expression. let baseType = type.as(AttributedTypeSyntax.self)?.baseType ?? type - return baseType.trimmed + return baseType } /// The base type name of this parameter. From a5b1498f00ef0614841dae20260ca6091422fc72 Mon Sep 17 00:00:00 2001 From: ojun9 Date: Thu, 26 Mar 2026 22:21:09 +0900 Subject: [PATCH 06/14] Remove redundant trimming --- Sources/TestingMacros/Support/AttributeDiscovery.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/TestingMacros/Support/AttributeDiscovery.swift b/Sources/TestingMacros/Support/AttributeDiscovery.swift index 92a3b0684..b1c46c1a7 100644 --- a/Sources/TestingMacros/Support/AttributeDiscovery.swift +++ b/Sources/TestingMacros/Support/AttributeDiscovery.swift @@ -185,8 +185,7 @@ struct AttributeInfo { ) ) } - let lazyExpression = expr.trimmed - copy.expression = .init(ClosureExprSyntax { lazyExpression }) + copy.expression = .init(ClosureExprSyntax { expr }) return copy } } From 9bdd79de4657e86179826a8a7e56c10a19c7505a Mon Sep 17 00:00:00 2001 From: ojun9 Date: Thu, 26 Mar 2026 22:54:20 +0900 Subject: [PATCH 07/14] Handle dictionary literal contextual types --- .../Support/AttributeDiscovery.swift | 58 ++++++++++++------- .../TestDeclarationMacroTests.swift | 7 +++ 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/Sources/TestingMacros/Support/AttributeDiscovery.swift b/Sources/TestingMacros/Support/AttributeDiscovery.swift index b1c46c1a7..0aec5325e 100644 --- a/Sources/TestingMacros/Support/AttributeDiscovery.swift +++ b/Sources/TestingMacros/Support/AttributeDiscovery.swift @@ -220,10 +220,6 @@ struct AttributeInfo { for expression: ExprSyntax, among testFunctionArguments: [Argument] ) -> TypeSyntax? { - guard expression.is(ArrayExprSyntax.self) else { - return nil - } - guard let functionDecl = declaration.as(FunctionDeclSyntax.self) else { return nil } @@ -233,29 +229,47 @@ struct AttributeInfo { return nil } - if testFunctionArguments.count == parameters.count { - let parameter = parameters[index] - return TypeSyntax( - ArrayTypeSyntax(element: parameter.baseType.trimmed) - ) + if expression.is(ArrayExprSyntax.self) { + if testFunctionArguments.count == parameters.count { + let parameter = parameters[index] + return TypeSyntax( + ArrayTypeSyntax(element: parameter.baseType.trimmed) + ) + } + + if testFunctionArguments.count == 1 { + if parameters.count == 1, let parameter = parameters.first { + // A single-parameter test expects collection elements of the parameter + // type itself, not tuple-shaped elements. + return TypeSyntax( + ArrayTypeSyntax(element: parameter.baseType.trimmed) + ) + } + let elementType = TypeSyntax( + TupleTypeSyntax(elements: TupleTypeElementListSyntax { + for parameter in parameters { + TupleTypeElementSyntax(type: parameter.baseType.trimmed) + } + }) + ) + return TypeSyntax(ArrayTypeSyntax(element: elementType)) + } } - if testFunctionArguments.count == 1 { - if parameters.count == 1, let parameter = parameters.first { - // A single-parameter test expects collection elements of the parameter - // type itself, not tuple-shaped elements. + if expression.is(DictionaryExprSyntax.self) { + if testFunctionArguments.count == 1, parameters.count == 2 { return TypeSyntax( - ArrayTypeSyntax(element: parameter.baseType.trimmed) + IdentifierTypeSyntax( + name: .identifier("KeyValuePairs"), + genericArgumentClause: GenericArgumentClauseSyntax( + arguments: GenericArgumentListSyntax { + GenericArgumentSyntax(argument: .type(parameters[0].baseType.trimmed)) + GenericArgumentSyntax(argument: .type(parameters[1].baseType.trimmed)) + } + ) + ) ) } - let elementType = TypeSyntax( - TupleTypeSyntax(elements: TupleTypeElementListSyntax { - for parameter in parameters { - TupleTypeElementSyntax(type: parameter.baseType.trimmed) - } - }) - ) - return TypeSyntax(ArrayTypeSyntax(element: elementType)) } return nil diff --git a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift index 359bc5dd1..093654a1d 100644 --- a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift +++ b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift @@ -546,6 +546,13 @@ struct TestDeclarationMacroTests { """, #"arguments:{[(nil,1),("a",nil),("b",nil)]as[(String?,Int?)]}"# ), + ( + """ + @Test(arguments: ["value": 123]) + func f(s: String, i: Int) {} + """, + #"arguments:{["value":123]asKeyValuePairs}"# + ), ] } From 3d6b0724c23f4122d1c82692473a035ce9b3ec70 Mon Sep 17 00:00:00 2001 From: ojun9 Date: Thu, 26 Mar 2026 23:42:26 +0900 Subject: [PATCH 08/14] Add literal contextual typing fixtures --- Tests/TestingTests/Test.Case.ArgumentTests.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/TestingTests/Test.Case.ArgumentTests.swift b/Tests/TestingTests/Test.Case.ArgumentTests.swift index 4ea9925d6..641328864 100644 --- a/Tests/TestingTests/Test.Case.ArgumentTests.swift +++ b/Tests/TestingTests/Test.Case.ArgumentTests.swift @@ -165,11 +165,24 @@ struct ParameterizedTests { @Test(.hidden, arguments: [("value", 123)]) func one2TupleParameter(x: (String, Int)) {} + @Test<[(String?, Int?)]>(.hidden, arguments: [ + (nil, 123), + ("value1", nil), + ("value2", nil), + ]) + func contextualArrayLiteral(x: String?, y: Int?) {} + @Test(.hidden, arguments: ["value": 123]) func twoDictionaryElementParameters(x: String, y: Int) {} @Test(.hidden, arguments: ["value": 123]) func oneDictionaryElementTupleParameter(x: (key: String, value: Int)) {} + @Test>(.hidden, arguments: [ + "value1": nil, + "value2": 123, + ]) + func contextualDictionaryLiteral(key: String, value: Int?) {} + @Test(.disabled(), arguments: [1, 2, 3]) func disabled(x: Int) {} } From 5316889ab64ed8ad91f0bf7d43202d262deafa2f Mon Sep 17 00:00:00 2001 From: ojun <42542860+ojun9@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:45:58 +0900 Subject: [PATCH 09/14] Update Sources/TestingMacros/Support/AttributeDiscovery.swift Co-authored-by: Jonathan Grynspan --- Sources/TestingMacros/Support/AttributeDiscovery.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/TestingMacros/Support/AttributeDiscovery.swift b/Sources/TestingMacros/Support/AttributeDiscovery.swift index 0aec5325e..92cef04b4 100644 --- a/Sources/TestingMacros/Support/AttributeDiscovery.swift +++ b/Sources/TestingMacros/Support/AttributeDiscovery.swift @@ -254,9 +254,7 @@ struct AttributeInfo { ) return TypeSyntax(ArrayTypeSyntax(element: elementType)) } - } - - if expression.is(DictionaryExprSyntax.self) { + } else if expression.is(DictionaryExprSyntax.self) { if testFunctionArguments.count == 1, parameters.count == 2 { return TypeSyntax( IdentifierTypeSyntax( From 542a027e64df2458b20c6a40488a60e3c9deaf79 Mon Sep 17 00:00:00 2001 From: ojun <42542860+ojun9@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:46:28 +0900 Subject: [PATCH 10/14] Update Tests/TestingTests/Test.Case.ArgumentTests.swift Co-authored-by: Jonathan Grynspan --- Tests/TestingTests/Test.Case.ArgumentTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/TestingTests/Test.Case.ArgumentTests.swift b/Tests/TestingTests/Test.Case.ArgumentTests.swift index 641328864..0f1e8ec43 100644 --- a/Tests/TestingTests/Test.Case.ArgumentTests.swift +++ b/Tests/TestingTests/Test.Case.ArgumentTests.swift @@ -165,7 +165,7 @@ struct ParameterizedTests { @Test(.hidden, arguments: [("value", 123)]) func one2TupleParameter(x: (String, Int)) {} - @Test<[(String?, Int?)]>(.hidden, arguments: [ + @Test(.hidden, arguments: [ (nil, 123), ("value1", nil), ("value2", nil), From ced014e0955dcdf094f76146659d4bbf30e1c62d Mon Sep 17 00:00:00 2001 From: ojun <42542860+ojun9@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:47:14 +0900 Subject: [PATCH 11/14] Update Tests/TestingTests/Test.Case.ArgumentTests.swift Co-authored-by: Jonathan Grynspan --- Tests/TestingTests/Test.Case.ArgumentTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/TestingTests/Test.Case.ArgumentTests.swift b/Tests/TestingTests/Test.Case.ArgumentTests.swift index 0f1e8ec43..42ddb79bc 100644 --- a/Tests/TestingTests/Test.Case.ArgumentTests.swift +++ b/Tests/TestingTests/Test.Case.ArgumentTests.swift @@ -178,7 +178,7 @@ struct ParameterizedTests { @Test(.hidden, arguments: ["value": 123]) func oneDictionaryElementTupleParameter(x: (key: String, value: Int)) {} - @Test>(.hidden, arguments: [ + @Test(.hidden, arguments: [ "value1": nil, "value2": 123, ]) From b64e134f677e4a75fb1d08d2caf477db6088ee1a Mon Sep 17 00:00:00 2001 From: ojun9 Date: Mon, 20 Apr 2026 23:16:21 +0900 Subject: [PATCH 12/14] cast contextual @Test arguments --- Tests/TestingTests/Test.Case.ArgumentTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/TestingTests/Test.Case.ArgumentTests.swift b/Tests/TestingTests/Test.Case.ArgumentTests.swift index 42ddb79bc..17b7d2225 100644 --- a/Tests/TestingTests/Test.Case.ArgumentTests.swift +++ b/Tests/TestingTests/Test.Case.ArgumentTests.swift @@ -169,7 +169,7 @@ struct ParameterizedTests { (nil, 123), ("value1", nil), ("value2", nil), - ]) + ] as [(String?, Int?)]) func contextualArrayLiteral(x: String?, y: Int?) {} @Test(.hidden, arguments: ["value": 123]) @@ -181,7 +181,7 @@ struct ParameterizedTests { @Test(.hidden, arguments: [ "value1": nil, "value2": 123, - ]) + ] as KeyValuePairs) func contextualDictionaryLiteral(key: String, value: Int?) {} @Test(.disabled(), arguments: [1, 2, 3]) func disabled(x: Int) {} From 04fa4875894beb6d32ef1e622451529a6b16c137 Mon Sep 17 00:00:00 2001 From: ojun9 Date: Mon, 20 Apr 2026 23:20:44 +0900 Subject: [PATCH 13/14] qualify KeyValuePairs with Swift --- Sources/TestingMacros/Support/AttributeDiscovery.swift | 3 ++- Tests/TestingMacrosTests/TestDeclarationMacroTests.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/TestingMacros/Support/AttributeDiscovery.swift b/Sources/TestingMacros/Support/AttributeDiscovery.swift index 92cef04b4..c2cf74141 100644 --- a/Sources/TestingMacros/Support/AttributeDiscovery.swift +++ b/Sources/TestingMacros/Support/AttributeDiscovery.swift @@ -257,7 +257,8 @@ struct AttributeInfo { } else if expression.is(DictionaryExprSyntax.self) { if testFunctionArguments.count == 1, parameters.count == 2 { return TypeSyntax( - IdentifierTypeSyntax( + MemberTypeSyntax( + baseType: IdentifierTypeSyntax(name: .identifier("Swift")), name: .identifier("KeyValuePairs"), genericArgumentClause: GenericArgumentClauseSyntax( arguments: GenericArgumentListSyntax { diff --git a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift index f50aef105..916c15548 100644 --- a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift +++ b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift @@ -572,7 +572,7 @@ struct TestDeclarationMacroTests { @Test(arguments: ["value": 123]) func f(s: String, i: Int) {} """, - #"arguments:{["value":123]asKeyValuePairs}"# + #"arguments:{["value":123]asSwift.KeyValuePairs}"# ), ] } From 988f08f8ab2573f346b7b3171c0aff73470adb79 Mon Sep 17 00:00:00 2001 From: ojun9 Date: Mon, 20 Apr 2026 23:28:18 +0900 Subject: [PATCH 14/14] inline parameter base type return --- .../Support/Additions/FunctionDeclSyntaxAdditions.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift index 060738979..1fe4577c2 100644 --- a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift +++ b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift @@ -179,8 +179,7 @@ extension FunctionParameterSyntax { var baseType: TypeSyntax { // Discard any specifiers such as `inout` or `borrowing`, since we're only // trying to obtain the base type to reference it in an expression. - let baseType = type.as(AttributedTypeSyntax.self)?.baseType ?? type - return baseType + type.as(AttributedTypeSyntax.self)?.baseType ?? type } /// The base type name of this parameter.