Skip to content

Commit 3f4a947

Browse files
committed
adding async let
1 parent 0517bfb commit 3f4a947

7 files changed

Lines changed: 258 additions & 3 deletions

File tree

Examples/Remaining/errors_async/dsl.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,27 @@ Function("summarize") {
4040
Parameter(name: "ratings", type: "[Int]")
4141
} _: {
4242
Guard{
43-
(VariableExp("ratings").property("isEmpty") as! PropertyAccessExp).not()
43+
VariableExp("ratings").property("isEmpty").not()
4444
} else: {
4545
Throw(EnumCase("noRatings"))
4646
}
4747
}.throws("StatisticsError")
4848

4949

50+
Variable(.let, name: "data") {
51+
Call("fetchUserData") {
52+
ParameterExp(name: "id", value: Literal.integer(1))
53+
}
54+
}.async()
55+
Variable(.let, name: "posts") {
56+
Call("fetchUserPosts") {
57+
ParameterExp(name: "id", value: Literal.integer(1))
58+
}
59+
}.async()
60+
TupleAssignment(["fetchedData", "fetchedPosts"], equals: Tuple {
61+
VariableExp("data")
62+
VariableExp("posts")
63+
}).async().throwing()
5064

5165

5266

Examples/Remaining/errors_async/syntax.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

Sources/SyntaxKit/Call.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public struct Call: CodeBlock {
3434
private let functionName: String
3535
private let parameters: [ParameterExp]
3636
private var isThrowing: Bool = false
37+
private var isAsync: Bool = false
3738

3839
/// Creates a global function call expression.
3940
/// - Parameter functionName: The name of the function to call.
@@ -59,6 +60,14 @@ public struct Call: CodeBlock {
5960
return copy
6061
}
6162

63+
/// Marks this function call as async.
64+
/// - Returns: A copy of the call marked as async.
65+
public func async() -> Self {
66+
var copy = self
67+
copy.isAsync = true
68+
return copy
69+
}
70+
6271
public var syntax: SyntaxProtocol {
6372
let function = TokenSyntax.identifier(functionName)
6473
let args = LabeledExprListSyntax(
@@ -96,6 +105,13 @@ public struct Call: CodeBlock {
96105
expression: functionCall
97106
)
98107
)
108+
} else if isAsync {
109+
return ExprSyntax(
110+
AwaitExprSyntax(
111+
awaitKeyword: .keyword(.await, trailingTrivia: .space),
112+
expression: functionCall
113+
)
114+
)
99115
} else {
100116
return ExprSyntax(functionCall)
101117
}

Sources/SyntaxKit/Tuple.swift

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import SwiftSyntax
3232
/// A tuple expression, e.g. `(a, b, c)`.
3333
public struct Tuple: CodeBlock {
3434
private let elements: [CodeBlock]
35+
private var isAsync: Bool = false
36+
private var isThrowing: Bool = false
3537

3638
/// Creates a tuple expression comprising the supplied elements.
3739
/// - Parameter content: A ``CodeBlockBuilder`` producing the tuple elements **in order**.
@@ -53,6 +55,30 @@ public struct Tuple: CodeBlock {
5355
TuplePatternCodeBlock(elements: elements)
5456
}
5557

58+
/// Marks this tuple as async.
59+
/// - Returns: A copy of the tuple marked as async.
60+
public func async() -> Self {
61+
var copy = self
62+
copy.isAsync = true
63+
return copy
64+
}
65+
66+
/// Marks this tuple as await.
67+
/// - Returns: A copy of the tuple marked as await.
68+
public func await() -> Self {
69+
var copy = self
70+
copy.isAsync = true
71+
return copy
72+
}
73+
74+
/// Marks this tuple as throwing.
75+
/// - Returns: A copy of the tuple marked as throwing.
76+
public func throwing() -> Self {
77+
var copy = self
78+
copy.isThrowing = true
79+
return copy
80+
}
81+
5682
public var syntax: SyntaxProtocol {
5783
guard !elements.isEmpty else {
5884
fatalError("Tuple must contain at least one element.")
@@ -78,6 +104,27 @@ public struct Tuple: CodeBlock {
78104
)
79105
)
80106

81-
return tupleExpr
107+
if isThrowing {
108+
return ExprSyntax(
109+
TryExprSyntax(
110+
tryKeyword: .keyword(.try, trailingTrivia: .space),
111+
expression: isAsync
112+
? ExprSyntax(
113+
AwaitExprSyntax(
114+
awaitKeyword: .keyword(.await, trailingTrivia: .space),
115+
expression: tupleExpr
116+
)) : tupleExpr
117+
)
118+
)
119+
} else if isAsync {
120+
return ExprSyntax(
121+
AwaitExprSyntax(
122+
awaitKeyword: .keyword(.await, trailingTrivia: .space),
123+
expression: tupleExpr
124+
)
125+
)
126+
} else {
127+
return tupleExpr
128+
}
82129
}
83130
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//
2+
// TupleAssignment.swift
3+
// SyntaxKit
4+
//
5+
// Created by Leo Dion.
6+
// Copyright © 2025 BrightDigit.
7+
//
8+
// Permission is hereby granted, free of charge, to any person
9+
// obtaining a copy of this software and associated documentation
10+
// files (the “Software”), to deal in the Software without
11+
// restriction, including without limitation the rights to use,
12+
// copy, modify, merge, publish, distribute, sublicense, and/or
13+
// sell copies of the Software, and to permit persons to whom the
14+
// Software is furnished to do so, subject to the following
15+
// conditions:
16+
//
17+
// The above copyright notice and this permission notice shall be
18+
// included in all copies or substantial portions of the Software.
19+
//
20+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
21+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27+
// OTHER DEALINGS IN THE SOFTWARE.
28+
//
29+
30+
import SwiftSyntax
31+
32+
/// A tuple destructuring pattern for variable declarations.
33+
public struct TupleAssignment: CodeBlock {
34+
private let elements: [String]
35+
private let value: CodeBlock
36+
private var isAsync: Bool = false
37+
private var isThrowing: Bool = false
38+
39+
/// Creates a tuple destructuring declaration.
40+
/// - Parameters:
41+
/// - elements: The names of the variables to destructure into.
42+
/// - value: The expression to destructure.
43+
public init(_ elements: [String], equals value: CodeBlock) {
44+
self.elements = elements
45+
self.value = value
46+
}
47+
48+
/// Marks this destructuring as async.
49+
/// - Returns: A copy of the destructuring marked as async.
50+
public func async() -> Self {
51+
var copy = self
52+
copy.isAsync = true
53+
return copy
54+
}
55+
56+
/// Marks this destructuring as throwing.
57+
/// - Returns: A copy of the destructuring marked as throwing.
58+
public func throwing() -> Self {
59+
var copy = self
60+
copy.isThrowing = true
61+
return copy
62+
}
63+
64+
public var syntax: SyntaxProtocol {
65+
// Build the tuple pattern
66+
let patternElements = TuplePatternElementListSyntax(
67+
elements.enumerated().map { index, element in
68+
TuplePatternElementSyntax(
69+
label: nil,
70+
colon: nil,
71+
pattern: PatternSyntax(IdentifierPatternSyntax(identifier: .identifier(element))),
72+
trailingComma: index < elements.count - 1 ? .commaToken(trailingTrivia: .space) : nil
73+
)
74+
}
75+
)
76+
77+
let tuplePattern = PatternSyntax(
78+
TuplePatternSyntax(
79+
leftParen: .leftParenToken(),
80+
elements: patternElements,
81+
rightParen: .rightParenToken()
82+
)
83+
)
84+
85+
// Build the value expression
86+
let valueExpr: ExprSyntax
87+
if isThrowing {
88+
valueExpr = ExprSyntax(
89+
TryExprSyntax(
90+
tryKeyword: .keyword(.try, trailingTrivia: .space),
91+
expression: isAsync
92+
? ExprSyntax(
93+
AwaitExprSyntax(
94+
awaitKeyword: .keyword(.await, trailingTrivia: .space),
95+
expression: value.syntax.as(ExprSyntax.self)
96+
?? ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("")))
97+
))
98+
: value.syntax.as(ExprSyntax.self)
99+
?? ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("")))
100+
)
101+
)
102+
} else if isAsync {
103+
valueExpr = ExprSyntax(
104+
AwaitExprSyntax(
105+
awaitKeyword: .keyword(.await, trailingTrivia: .space),
106+
expression: value.syntax.as(ExprSyntax.self)
107+
?? ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("")))
108+
)
109+
)
110+
} else {
111+
valueExpr =
112+
value.syntax.as(ExprSyntax.self)
113+
?? ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("")))
114+
}
115+
116+
// Build the variable declaration
117+
return VariableDeclSyntax(
118+
bindingSpecifier: .keyword(.let, trailingTrivia: .space),
119+
bindings: PatternBindingListSyntax([
120+
PatternBindingSyntax(
121+
pattern: tuplePattern,
122+
initializer: InitializerClauseSyntax(
123+
equal: .equalToken(leadingTrivia: .space, trailingTrivia: .space),
124+
value: valueExpr
125+
)
126+
)
127+
])
128+
)
129+
}
130+
}

Sources/SyntaxKit/Variable.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public struct Variable: CodeBlock {
3737
let type: String
3838
let defaultValue: CodeBlock?
3939
var isStatic: Bool = false
40+
var isAsync: Bool = false
4041
var attributes: [AttributeInfo] = []
4142
var explicitType: Bool = false
4243

@@ -78,6 +79,14 @@ public struct Variable: CodeBlock {
7879
return copy
7980
}
8081

82+
/// Marks the variable as `async`.
83+
/// - Returns: A copy of the variable marked as `async`.
84+
public func async() -> Self {
85+
var copy = self
86+
copy.isAsync = true
87+
return copy
88+
}
89+
8190
/// Adds an attribute to the variable declaration.
8291
/// - Parameters:
8392
/// - attribute: The attribute name (without the @ symbol).
@@ -124,6 +133,13 @@ public struct Variable: CodeBlock {
124133
DeclModifierSyntax(name: .keyword(.static, trailingTrivia: .space))
125134
])
126135
}
136+
if isAsync {
137+
modifiers = DeclModifierListSyntax(
138+
modifiers + [
139+
DeclModifierSyntax(name: .keyword(.async, trailingTrivia: .space))
140+
]
141+
)
142+
}
127143
return VariableDeclSyntax(
128144
attributes: buildAttributeList(from: attributes),
129145
modifiers: modifiers,

Tests/SyntaxKitTests/Unit/ErrorHandlingTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,36 @@ import Testing
147147

148148
#expect(generated.normalize() == expected.normalize())
149149
}
150+
151+
@Test("Async functionality generates correct syntax")
152+
internal func testAsyncFunctionality() throws {
153+
let asyncCode = Group {
154+
Variable(.let, name: "data") {
155+
Call("fetchUserData") {
156+
ParameterExp(name: "id", value: Literal.integer(1))
157+
}
158+
}.async()
159+
Variable(.let, name: "posts") {
160+
Call("fetchUserPosts") {
161+
ParameterExp(name: "id", value: Literal.integer(1))
162+
}
163+
}.async()
164+
TupleAssignment(
165+
["fetchedData", "fetchedPosts"],
166+
equals: Tuple {
167+
VariableExp("data")
168+
VariableExp("posts")
169+
}
170+
).async().throwing()
171+
}
172+
173+
let generated = asyncCode.generateCode()
174+
let expected = """
175+
async let data = fetchUserData(id: 1)
176+
async let posts = fetchUserPosts(id: 1)
177+
let (fetchedData, fetchedPosts) = try await (data, posts)
178+
"""
179+
180+
#expect(generated.normalize() == expected.normalize())
181+
}
150182
}

0 commit comments

Comments
 (0)