Skip to content

Commit e659d4f

Browse files
author
Eliza Block
committed
freestanding macros in lexical context
This makes it possible for a macro to know if it appears within a parameter to a freestanding macro. rdar://122330706
1 parent 25ce3a2 commit e659d4f

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

Sources/SwiftSyntaxMacros/Syntax+LexicalContext.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ extension SyntaxProtocol {
5757
patternBinding.initializer = nil
5858
return Syntax(patternBinding)
5959

60+
// Freestanding macros are fine as-is because if any arguments change
61+
// the whole macro would have to be re-evaluated.
62+
case let freestandingMacro as FreestandingMacroExpansionSyntax:
63+
return Syntax(freestandingMacro.detached) as Syntax
64+
6065
default:
6166
return nil
6267
}

Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,21 @@ public struct AllLexicalContextsMacro: DeclarationMacro {
200200
}
201201
}
202202

203+
public struct LexicalContextDescriptionMacro: ExpressionMacro {
204+
public static func expansion(
205+
of node: some FreestandingMacroExpansionSyntax,
206+
in context: some MacroExpansionContext
207+
) throws -> ExprSyntax {
208+
let descriptions = context.lexicalContext.reduce(into: "") { descriptions, syntax in
209+
descriptions += "\(syntax.trimmed)\n"
210+
}
211+
return """
212+
\"\"\"
213+
\(raw: descriptions)\"\"\"
214+
"""
215+
}
216+
}
217+
203218
final class LexicalContextTests: XCTestCase {
204219
private let indentationWidth: Trivia = .spaces(2)
205220

@@ -505,5 +520,49 @@ final class LexicalContextTests: XCTestCase {
505520
// Test closures separately, because they don't fit as declaration macros.
506521
let closure: ExprSyntax = "{ (a, b) in print(a + b) }"
507522
XCTAssertEqual(closure.asMacroLexicalContext()!.description, "{ (a, b) in }")
523+
524+
// Test freestanding macros separately, because since the context contains
525+
// the full body of the macro, including each decl from the context as a new
526+
// decl in the expansion results in a cycle. So we instead use a macro that
527+
// generates a description of the lexical context.
528+
assertMacroExpansion(
529+
"""
530+
#M(a: 1, b: 2) { c in
531+
struct S {
532+
let arg: C
533+
var contextDescription: String {
534+
#lexicalContextDescription
535+
}
536+
}
537+
return S(arg: c)
538+
}
539+
""",
540+
expandedSource: #"""
541+
#M(a: 1, b: 2) { c in
542+
struct S {
543+
let arg: C
544+
var contextDescription: String {
545+
"""
546+
contextDescription: String
547+
struct S {}
548+
{ c in
549+
}
550+
#M(a: 1, b: 2) { c in
551+
struct S {
552+
let arg: C
553+
var contextDescription: String {
554+
#lexicalContextDescription
555+
}
556+
}
557+
return S(arg: c)
558+
}
559+
"""
560+
}
561+
}
562+
return S(arg: c)
563+
}
564+
"""#,
565+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
566+
)
508567
}
509568
}

0 commit comments

Comments
 (0)