Skip to content

Commit 9aa0907

Browse files
authored
Merge pull request #3041 from hamishknight/in-sequence-6.2
[6.2] Introduce `try`/`await`/`unsafe` macro lexical contexts with unfolded sequence handling
2 parents c54a9d4 + 315d8fd commit 9aa0907

File tree

3 files changed

+241
-3
lines changed

3 files changed

+241
-3
lines changed

Sources/SwiftOperators/OperatorTable+Folding.swift

+2
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ extension OperatorTable {
155155
)
156156
)
157157
}
158+
// NOTE: If you add a new try/await/unsafe-like hoisting case here, make
159+
// sure to also update `allMacroLexicalContexts` to handle it.
158160

159161
// The form of the binary operation depends on the operator itself,
160162
// which will be one of the unresolved infix operators.

Sources/SwiftSyntaxMacros/Syntax+LexicalContext.swift

+55
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,24 @@ extension SyntaxProtocol {
6767
case let freestandingMacro as FreestandingMacroExpansionSyntax:
6868
return Syntax(freestandingMacro.detached) as Syntax
6969

70+
// `try`, `await`, and `unsafe` are preserved: A freestanding expression
71+
// macro may need to know whether those keywords are present so it can
72+
// propagate them to any expressions in its expansion which were passed as
73+
// arguments to the macro. The sub-expression is replaced with a trivial
74+
// placeholder, though.
75+
case var tryExpr as TryExprSyntax:
76+
tryExpr = tryExpr.detached
77+
tryExpr.expression = ExprSyntax(TypeExprSyntax(type: IdentifierTypeSyntax(name: .wildcardToken())))
78+
return Syntax(tryExpr)
79+
case var awaitExpr as AwaitExprSyntax:
80+
awaitExpr = awaitExpr.detached
81+
awaitExpr.expression = ExprSyntax(TypeExprSyntax(type: IdentifierTypeSyntax(name: .wildcardToken())))
82+
return Syntax(awaitExpr)
83+
case var unsafeExpr as UnsafeExprSyntax:
84+
unsafeExpr = unsafeExpr.detached
85+
unsafeExpr.expression = ExprSyntax(TypeExprSyntax(type: IdentifierTypeSyntax(name: .wildcardToken())))
86+
return Syntax(unsafeExpr)
87+
7088
default:
7189
return nil
7290
}
@@ -92,6 +110,43 @@ extension SyntaxProtocol {
92110
if let parentContext = parentNode.asMacroLexicalContext() {
93111
parentContexts.append(parentContext)
94112
}
113+
// Unfolded sequence expressions require special handling - effect marker
114+
// nodes like `try`, `await`, and `unsafe` are treated as lexical contexts
115+
// for all the nodes on their right. Cases where they don't end up
116+
// covering nodes to their right in the folded tree are invalid and will
117+
// be diagnosed by the compiler. This matches the compiler's ASTScope
118+
// handling logic.
119+
if let sequence = parentNode.as(SequenceExprSyntax.self) {
120+
var sequenceExprContexts: [Syntax] = []
121+
for elt in sequence.elements {
122+
if elt.range.contains(self.position) {
123+
// `sequenceExprContexts` is built from the top-down, but we
124+
// build the rest of the contexts bottom-up. Reverse for
125+
// consistency.
126+
parentContexts += sequenceExprContexts.reversed()
127+
break
128+
}
129+
var elt = elt
130+
while true {
131+
if let tryElt = elt.as(TryExprSyntax.self) {
132+
sequenceExprContexts.append(tryElt.asMacroLexicalContext()!)
133+
elt = tryElt.expression
134+
continue
135+
}
136+
if let awaitElt = elt.as(AwaitExprSyntax.self) {
137+
sequenceExprContexts.append(awaitElt.asMacroLexicalContext()!)
138+
elt = awaitElt.expression
139+
continue
140+
}
141+
if let unsafeElt = elt.as(UnsafeExprSyntax.self) {
142+
sequenceExprContexts.append(unsafeElt.asMacroLexicalContext()!)
143+
elt = unsafeElt.expression
144+
continue
145+
}
146+
break
147+
}
148+
}
149+
}
95150

96151
currentNode = parentNode
97152
}

Tests/SwiftSyntaxMacroExpansionTest/LexicalContextTests.swift

+184-3
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ final class LexicalContextTests: XCTestCase {
531531
struct S {
532532
let arg: C
533533
var contextDescription: String {
534-
#lexicalContextDescription
534+
unsafe try await #lexicalContextDescription
535535
}
536536
}
537537
return S(arg: c)
@@ -542,7 +542,10 @@ final class LexicalContextTests: XCTestCase {
542542
struct S {
543543
let arg: C
544544
var contextDescription: String {
545-
"""
545+
unsafe try await """
546+
await _
547+
try _
548+
unsafe _
546549
contextDescription: String
547550
struct S {}
548551
{ c in
@@ -551,7 +554,7 @@ final class LexicalContextTests: XCTestCase {
551554
struct S {
552555
let arg: C
553556
var contextDescription: String {
554-
#lexicalContextDescription
557+
unsafe try await #lexicalContextDescription
555558
}
556559
}
557560
return S(arg: c)
@@ -565,4 +568,182 @@ final class LexicalContextTests: XCTestCase {
565568
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
566569
)
567570
}
571+
572+
func testEffectMarkersInSequenceLexicalContext() {
573+
// Valid cases.
574+
assertMacroExpansion(
575+
"unsafe try await #lexicalContextDescription + #lexicalContextDescription",
576+
expandedSource: #"""
577+
unsafe try await """
578+
await _
579+
try _
580+
unsafe _
581+
""" + """
582+
await _
583+
try _
584+
unsafe _
585+
"""
586+
"""#,
587+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
588+
)
589+
assertMacroExpansion(
590+
"try unsafe await 0 + 1 + foo(#lexicalContextDescription) + 2",
591+
expandedSource: #"""
592+
try unsafe await 0 + 1 + foo("""
593+
await _
594+
unsafe _
595+
try _
596+
""") + 2
597+
"""#,
598+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
599+
)
600+
assertMacroExpansion(
601+
"x = try await unsafe 0 + 1 + foo(#lexicalContextDescription) + 2",
602+
expandedSource: #"""
603+
x = try await unsafe 0 + 1 + foo("""
604+
unsafe _
605+
await _
606+
try _
607+
""") + 2
608+
"""#,
609+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
610+
)
611+
// `unsafe try await` in the 'then' branch doesn't cover condition or else.
612+
assertMacroExpansion(
613+
"#lexicalContextDescription ? unsafe try await #lexicalContextDescription : #lexicalContextDescription",
614+
expandedSource: #"""
615+
"""
616+
""" ? unsafe try await """
617+
await _
618+
try _
619+
unsafe _
620+
""" : """
621+
"""
622+
"""#,
623+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
624+
)
625+
// Same for else.
626+
assertMacroExpansion(
627+
"#lexicalContextDescription ? #lexicalContextDescription : unsafe try await #lexicalContextDescription",
628+
expandedSource: #"""
629+
"""
630+
""" ? """
631+
""" : unsafe try await """
632+
await _
633+
try _
634+
unsafe _
635+
"""
636+
"""#,
637+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
638+
)
639+
// 'unsafe try await' in the condition here covers the entire expression
640+
assertMacroExpansion(
641+
"unsafe try await #lexicalContextDescription ? #lexicalContextDescription : #lexicalContextDescription ~~ #lexicalContextDescription",
642+
expandedSource: #"""
643+
unsafe try await """
644+
await _
645+
try _
646+
unsafe _
647+
""" ? """
648+
await _
649+
try _
650+
unsafe _
651+
""" : """
652+
await _
653+
try _
654+
unsafe _
655+
""" ~~ """
656+
await _
657+
try _
658+
unsafe _
659+
"""
660+
"""#,
661+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
662+
)
663+
assertMacroExpansion(
664+
"x = unsafe try try! await 0 + #lexicalContextDescription",
665+
expandedSource: #"""
666+
x = unsafe try try! await 0 + """
667+
await _
668+
try! _
669+
try _
670+
unsafe _
671+
"""
672+
"""#,
673+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
674+
)
675+
676+
// Invalid cases
677+
assertMacroExpansion(
678+
"0 + unsafe try await #lexicalContextDescription",
679+
expandedSource: #"""
680+
0 + unsafe try await """
681+
await _
682+
try _
683+
unsafe _
684+
"""
685+
"""#,
686+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
687+
)
688+
// The `unsafe try await` may not actually cover `lexicalContextDescription`
689+
// here, but this will be rejected by the compiler.
690+
assertMacroExpansion(
691+
"0 + unsafe try await 1 ^ #lexicalContextDescription",
692+
expandedSource: #"""
693+
0 + unsafe try await 1 ^ """
694+
await _
695+
try _
696+
unsafe _
697+
"""
698+
"""#,
699+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
700+
)
701+
// Invalid if '^' has a lower precedence than '='.
702+
assertMacroExpansion(
703+
"x = unsafe try await 0 ^ #lexicalContextDescription",
704+
expandedSource: #"""
705+
x = unsafe try await 0 ^ """
706+
await _
707+
try _
708+
unsafe _
709+
"""
710+
"""#,
711+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
712+
)
713+
// Unassignable
714+
assertMacroExpansion(
715+
"#lexicalContextDescription = unsafe try await 0 + 1",
716+
expandedSource: #"""
717+
"""
718+
""" = unsafe try await 0 + 1
719+
"""#,
720+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
721+
)
722+
assertMacroExpansion(
723+
"unsafe try await #lexicalContextDescription = 0 + #lexicalContextDescription",
724+
expandedSource: #"""
725+
unsafe try await """
726+
await _
727+
try _
728+
unsafe _
729+
""" = 0 + """
730+
await _
731+
try _
732+
unsafe _
733+
"""
734+
"""#,
735+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
736+
)
737+
assertMacroExpansion(
738+
"unsafe try await foo() ? 0 : 1 = #lexicalContextDescription",
739+
expandedSource: #"""
740+
unsafe try await foo() ? 0 : 1 = """
741+
await _
742+
try _
743+
unsafe _
744+
"""
745+
"""#,
746+
macros: ["lexicalContextDescription": LexicalContextDescriptionMacro.self]
747+
)
748+
}
568749
}

0 commit comments

Comments
 (0)