Skip to content

Commit c20e190

Browse files
committed
Name the arguments to the closure to __cmp so we can pass a return type and help the type checker a little bit maybe
1 parent 24a2d00 commit c20e190

File tree

6 files changed

+73
-19
lines changed

6 files changed

+73
-19
lines changed

Sources/TestingMacros/ConditionMacro.swift

+1-9
Original file line numberDiff line numberDiff line change
@@ -190,15 +190,7 @@ extension ConditionMacro {
190190
expandedFunctionName = .identifier("__checkConditionAsync")
191191
}
192192

193-
var expressionContextName = TokenSyntax.identifier("__ec")
194-
let isNameUsed = originalArgumentExpr.tokens(viewMode: .sourceAccurate).lazy
195-
.map(\.tokenKind)
196-
.contains(expressionContextName.tokenKind)
197-
if isNameUsed {
198-
// BUG: We should use the unique name directly. SEE: swift-syntax-#2256
199-
let uniqueName = context.makeUniqueName("")
200-
expressionContextName = .identifier("\(expressionContextName)\(uniqueName)")
201-
}
193+
let expressionContextName = context.makeUniqueClosureParameterName("__ec", in: originalArgumentExpr)
202194
let (closureExpr, rewrittenNodes) = rewrite(
203195
originalArgumentExpr,
204196
usingExpressionContextNamed: expressionContextName,

Sources/TestingMacros/Support/Additions/MacroExpansionContextAdditions.swift

+27
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,33 @@ extension MacroExpansionContext {
7575

7676
return makeUniqueName("\(prefix)\(suffix)")
7777
}
78+
79+
/// Generate a unique name for use in the macro as a closure parameter.
80+
///
81+
/// - Parameters:
82+
/// - name: The name to use as a basis for the uniquely-generated name.
83+
/// - node: A syntax node within which `name` must be unique.
84+
///
85+
/// - Returns: an identifier token containing a unique name suitable for use
86+
/// as a closure parameter.
87+
func makeUniqueClosureParameterName(_ name: String, in node: some SyntaxProtocol) -> TokenSyntax {
88+
precondition(name.isValidSwiftIdentifier(for: .variableName))
89+
var result = TokenSyntax.identifier(name)
90+
91+
func isNameUsed(_ name: TokenSyntax) -> Bool {
92+
node.tokens(viewMode: .sourceAccurate).lazy
93+
.map(\.tokenKind)
94+
.contains(name.tokenKind)
95+
}
96+
97+
var suffix = 0
98+
while isNameUsed(result) {
99+
defer { suffix += 1 }
100+
result = .identifier("\(name)\(suffix)")
101+
}
102+
103+
return result
104+
}
78105
}
79106

80107
// MARK: -

Sources/TestingMacros/Support/ConditionArgumentParsing.swift

+26-5
Original file line numberDiff line numberDiff line change
@@ -444,14 +444,35 @@ private final class _ContextInserter<C, M>: SyntaxRewriter where C: MacroExpansi
444444
if let op = node.operator.as(BinaryOperatorExprSyntax.self)?.operator.textWithoutBackticks,
445445
op == "==" || op == "!=" || op == "===" || op == "!==" {
446446

447+
let lhsName = context.makeUniqueClosureParameterName("lhs", in: effectiveRootNode)
448+
let rhsName = context.makeUniqueClosureParameterName("rhs", in: effectiveRootNode)
447449
return _rewrite(
448-
ClosureExprSyntax {
450+
ClosureExprSyntax(
451+
signature: ClosureSignatureSyntax(
452+
leadingTrivia: .space,
453+
parameterClause: .simpleInput(
454+
ClosureShorthandParameterListSyntax {
455+
ClosureShorthandParameterSyntax(name: lhsName)
456+
ClosureShorthandParameterSyntax(name: rhsName)
457+
}
458+
),
459+
returnClause: ReturnClauseSyntax(
460+
leadingTrivia: .space,
461+
type: MemberTypeSyntax(
462+
leadingTrivia: .space,
463+
baseType: IdentifierTypeSyntax(name: .identifier("Swift")),
464+
name: .identifier("Bool")
465+
),
466+
trailingTrivia: .space
467+
),
468+
inKeyword: .keyword(.in),
469+
trailingTrivia: .space
470+
)
471+
) {
449472
InfixOperatorExprSyntax(
450-
leftOperand: DeclReferenceExprSyntax(baseName: .dollarIdentifier("$0"))
451-
.with(\.trailingTrivia, .space),
473+
leftOperand: DeclReferenceExprSyntax(baseName: lhsName, trailingTrivia: .space),
452474
operator: BinaryOperatorExprSyntax(text: op),
453-
rightOperand: DeclReferenceExprSyntax(baseName: .dollarIdentifier("$1"))
454-
.with(\.leadingTrivia, .space)
475+
rightOperand: DeclReferenceExprSyntax(leadingTrivia: .space, baseName: rhsName, trailingTrivia: .space)
455476
)
456477
},
457478
originalWas: node,

Tests/SubexpressionShowcase/SubexpressionShowcase.swift

-3
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ func subexpressionShowcase() async throws {
4242
#expect(false || true)
4343

4444
#expect((fff == ttt) == ttt)
45-
Testing.__checkCondition({(__ec: Testing.__ExpectationContext) -> Swift.Bool in
46-
__ec.__cmp(==,0x0,__ec((__ec.__cmp(==,0x3a,__ec(fff,0x7a),0x7a,__ec(ttt,0x43a),0x43a)),0x2),0x2,__ec(ttt,0x8000),0x8000)
47-
},sourceCode: [0x0:"(fff == ttt) == ttt",0x2:"(fff == ttt)",0x3a:"fff == ttt",0x7a:"fff",0x43a:"ttt",0x8000:"ttt"],comments: [],isRequired: false,sourceLocation: Testing.SourceLocation.__here()).__expected()
4845

4946
#expect((Int)(123) == 124)
5047
#expect((Int, Double)(123, 456.0) == (124, 457.0))

Tests/TestingMacrosTests/ConditionMacroTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct ConditionMacroTests {
3535
##"#expect(9 > 8 && 7 > 6, "Some comment")"##:
3636
##"Testing.__checkCondition({ (__ec: Testing.__ExpectationContext) -> Swift.Bool in __ec(__ec(9 > 8, 0x2) && __ec(7 > 6, 0x400), 0x0) }, sourceCode: [0x0: "9 > 8 && 7 > 6", 0x2: "9 > 8", 0x400: "7 > 6"], comments: ["Some comment"], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()"##,
3737
##"#expect("a" == "b")"##:
38-
##"Testing.__checkCondition({ (__ec: Testing.__ExpectationContext) -> Swift.Bool in __ec.__cmp({ $0 == $1 }, 0x0, "a", 0x2, "b", 0x200) }, sourceCode: [0x0: #""a" == "b""#], comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()"##,
38+
##"Testing.__checkCondition({ (__ec: Testing.__ExpectationContext) -> Swift.Bool in __ec.__cmp({ lhs, rhs -> Swift.Bool in lhs == rhs }, 0x0, "a", 0x2, "b", 0x200) }, sourceCode: [0x0: #""a" == "b""#], comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()"##,
3939
##"#expect(!Bool.random())"##:
4040
##"Testing.__checkCondition({ (__ec: Testing.__ExpectationContext) -> Swift.Bool in __ec(!Bool.random(), 0x0) }, sourceCode: [0x0: "!Bool.random()"], comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()"##,
4141
##"#expect((true && false))"##:
@@ -116,7 +116,7 @@ struct ConditionMacroTests {
116116
##"#require(9 > 8 && 7 > 6, "Some comment")"##:
117117
##"Testing.__checkCondition({ (__ec: Testing.__ExpectationContext) -> Swift.Bool in try Testing.__requiringTry(__ec(__ec(9 > 8, 0x2) && __ec(7 > 6, 0x400), 0x0)) }, sourceCode: [0x0: "9 > 8 && 7 > 6", 0x2: "9 > 8", 0x400: "7 > 6"], comments: ["Some comment"], isRequired: true, sourceLocation: Testing.SourceLocation.__here()).__required()"##,
118118
##"#require("a" == "b")"##:
119-
##"Testing.__checkCondition({ (__ec: Testing.__ExpectationContext) -> Swift.Bool in try Testing.__requiringTry(__ec.__cmp({ $0 == $1 }, 0x0, "a", 0x2, "b", 0x200)) }, sourceCode: [0x0: #""a" == "b""#], comments: [], isRequired: true, sourceLocation: Testing.SourceLocation.__here()).__required()"##,
119+
##"Testing.__checkCondition({ (__ec: Testing.__ExpectationContext) -> Swift.Bool in try Testing.__requiringTry(__ec.__cmp({ lhs, rhs -> Swift.Bool in lhs == rhs }, 0x0, "a", 0x2, "b", 0x200)) }, sourceCode: [0x0: #""a" == "b""#], comments: [], isRequired: true, sourceLocation: Testing.SourceLocation.__here()).__required()"##,
120120
##"#require(!Bool.random())"##:
121121
##"Testing.__checkCondition({ (__ec: Testing.__ExpectationContext) -> Swift.Bool in try Testing.__requiringTry(__ec(!Bool.random(), 0x0)) }, sourceCode: [0x0: "!Bool.random()"], comments: [], isRequired: true, sourceLocation: Testing.SourceLocation.__here()).__required()"##,
122122
##"#require((true && false))"##:

Tests/TestingMacrosTests/UniqueIdentifierTests.swift

+17
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,21 @@ struct UniqueIdentifierTests {
8181
let uniqueName2 = try makeUniqueName("func f() { def() }")
8282
#expect(uniqueName1 == uniqueName2)
8383
}
84+
85+
@Test("Synthesized closure arguments are uniqued",
86+
arguments: [
87+
##"#expect(abc)"##:
88+
##"Testing.__checkCondition({ (__ec: Testing.__ExpectationContext) -> Swift.Bool in __ec(abc, 0x0) }, sourceCode: [0x0: "abc"], comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()"##,
89+
##"#expect(__ec)"##:
90+
##"Testing.__checkCondition({ (__ec0: Testing.__ExpectationContext) -> Swift.Bool in __ec0(__ec, 0x0) }, sourceCode: [0x0: "__ec"], comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()"##,
91+
##"#expect(__ec + __ec0)"##:
92+
##"Testing.__checkCondition({ (__ec1: Testing.__ExpectationContext) -> Swift.Bool in __ec1(__ec1(__ec, 0x2) + __ec1(__ec0, 0x20), 0x0) }, sourceCode: [0x0: "__ec + __ec0", 0x2: "__ec", 0x20: "__ec0"], comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()"##,
93+
]
94+
)
95+
func synthesizedClosureArgumentsUniqued(input: String, expectedOutput: String) throws {
96+
let (expectedOutput, _) = try parse(expectedOutput, removeWhitespace: true)
97+
let (actualOutput, _) = try parse(input, removeWhitespace: true)
98+
let (actualActual, _) = try parse(input)
99+
#expect(expectedOutput == actualOutput, "\(actualActual)")
100+
}
84101
}

0 commit comments

Comments
 (0)