Skip to content

Commit 0517bfb

Browse files
committed
added typed throws
1 parent d462ce7 commit 0517bfb

11 files changed

Lines changed: 296 additions & 61 deletions

File tree

Examples/Remaining/errors_async/code copy.swift

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,8 @@ do {
1414
print("Unexpected error: \(error).")
1515
}
1616

17-
func nonThrowingFunction() throws(Never) {
18-
// ...
19-
}
20-
2117
func summarize(_ ratings: [Int]) throws(StatisticsError) {
2218
guard !ratings.isEmpty else { throw .noRatings }
23-
24-
25-
var counts = [1: 0, 2: 0, 3: 0]
26-
for rating in ratings {
27-
guard rating > 0 && rating <= 3 else { throw .invalidRating(rating) }
28-
counts[rating]! += 1
29-
}
30-
31-
32-
print("*", counts[1]!, "-- **", counts[2]!, "-- ***", counts[3]!)
33-
}
34-
35-
let ratings = []
36-
do throws(StatisticsError) {
37-
try summarize(ratings)
38-
} catch {
39-
switch error {
40-
case .noRatings:
41-
print("No ratings available")
42-
case .invalidRating(let rating):
43-
print("Invalid rating: \(rating)")
44-
}
4519
}
4620

4721
// MARK: - Task Groups
@@ -76,4 +50,9 @@ Task {
7650

7751
Task { @MainActor [unowned self] in
7852
_ = try await fetchUserData(id: 1)
53+
}
54+
55+
56+
func nonThrowingFunction() throws(Never) {
57+
7958
}

Examples/Remaining/errors_async/code.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,12 @@ do {
1212
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
1313
} catch {
1414
print("Unexpected error: \(error).")
15-
}
15+
}
16+
17+
func summarize(_ ratings: [Int]) throws(StatisticsError) {
18+
guard !ratings.isEmpty else { throw .noRatings }
19+
}
20+
21+
async let data = fetchUserData(id: 1)
22+
async let posts = fetchUserPosts(id: 1)
23+
let (fetchedData, fetchedPosts) = try await (data, posts)

Examples/Remaining/errors_async/dsl.swift

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,50 @@
1-
Variable(.var, "vendingMachine", equals: Init("VendingMachine"))
1+
Variable(.var, name: "vendingMachine", equals: Init("VendingMachine"))
22
Assignment("vendingMachine.coinsDeposited", Literal.integer(8))
3-
Do{
4-
Call("buyFavoriteSnack"){
5-
ParameterExp("person", Literal.string("Alice"))
6-
ParameterExp("vendingMachine", Literal.ref("vendingMachine"))
3+
Do {
4+
Call("buyFavoriteSnack") {
5+
ParameterExp(name: "person", value: Literal.string("Alice"))
6+
ParameterExp(name: "vendingMachine", value: Literal.ref("vendingMachine"))
77
}.throwing()
8-
Call("print", Literal.string("Success! Yum."))
8+
Call("print") {
9+
ParameterExp(unlabeled: Literal.string("Success! Yum."))
910
}
10-
} catch: {
11+
} catch: {
1112
Catch(EnumCase("VendingMachineError.invalidSelection")) {
12-
Call("print", Literal.string("Invalid Selection."))
13+
Call("print") {
14+
ParameterExp(unlabeled: Literal.string("Invalid Selection."))
15+
}
1316
}
1417
Catch(EnumCase("VendingMachineError.outOfStock")) {
15-
Call("print", Literal.string("Out of Stock."))
18+
Call("print") {
19+
ParameterExp(unlabeled: Literal.string("Out of Stock."))
20+
}
1621
}
17-
Catch(EnumCase("VendingMachineError.insufficientFunds").associatedValue("coinsNeeded", type: "Int")) {
18-
Call("print", Literal.string("Insufficient funds. Please insert an additional \\(coinsNeeded) coins."))
22+
Catch(
23+
EnumCase("VendingMachineError.insufficientFunds").associatedValue(
24+
"coinsNeeded", type: "Int")
25+
) {
26+
Call("print") {
27+
ParameterExp(
28+
unlabeled: Literal.string(
29+
"Insufficient funds. Please insert an additional \\(coinsNeeded) coins."))
30+
}
1931
}
2032
Catch {
21-
Call("print", Literal.string("Unexpected error: \\(error)."))
33+
Call("print") {
34+
ParameterExp(unlabeled: Literal.string("Unexpected error: \\(error)."))
35+
}
2236
}
2337
}
2438

25-
39+
Function("summarize") {
40+
Parameter(name: "ratings", type: "[Int]")
41+
} _: {
42+
Guard{
43+
(VariableExp("ratings").property("isEmpty") as! PropertyAccessExp).not()
44+
} else: {
45+
Throw(EnumCase("noRatings"))
46+
}
47+
}.throws("StatisticsError")
2648

2749

2850

Examples/Remaining/errors_async/syntax.json

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

Sources/SyntaxKit/EnumCase.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,47 @@ public struct EnumCase: CodeBlock {
9090
self.equals(.float(value))
9191
}
9292

93+
/// Returns a SwiftSyntax expression for this enum case (for use in throw/return/etc).
94+
public var asExpressionSyntax: ExprSyntax {
95+
// Support qualified (Type.case) and unqualified (.case) forms
96+
let parts = name.split(separator: ".", maxSplits: 1)
97+
let base: ExprSyntax? =
98+
parts.count == 2
99+
? ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(String(parts[0])))) : nil
100+
let caseName = parts.count == 2 ? String(parts[1]) : name
101+
102+
let memberAccess = MemberAccessExprSyntax(
103+
base: base,
104+
dot: .periodToken(),
105+
name: .identifier(caseName)
106+
)
107+
108+
if let associated = associatedValue {
109+
// .caseName(associated)
110+
let tuple = TupleExprSyntax(
111+
leftParen: .leftParenToken(),
112+
elements: TupleExprElementListSyntax([
113+
TupleExprElementSyntax(
114+
label: nil,
115+
colon: nil,
116+
expression: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(associated.name))),
117+
trailingComma: nil
118+
)
119+
]),
120+
rightParen: .rightParenToken()
121+
)
122+
return ExprSyntax(
123+
FunctionCallExprSyntax(
124+
calledExpression: ExprSyntax(memberAccess),
125+
leftParen: tuple.leftParen,
126+
arguments: tuple.elements,
127+
rightParen: tuple.rightParen
128+
))
129+
} else {
130+
return ExprSyntax(memberAccess)
131+
}
132+
}
133+
93134
public var syntax: SyntaxProtocol {
94135
let caseKeyword = TokenSyntax.keyword(.case, trailingTrivia: .space)
95136
let identifier = TokenSyntax.identifier(name, trailingTrivia: .space)

Sources/SyntaxKit/Function+Effects.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,25 @@ extension Function {
3434
internal enum Effect {
3535
case none
3636
/// synchronous effect specifier: throws or rethrows
37-
case `throws`(isRethrows: Bool)
37+
case `throws`(isRethrows: Bool, errorType: String?)
3838
case async
3939
/// combined async and throws/rethrows
40-
case asyncThrows(isRethrows: Bool)
40+
case asyncThrows(isRethrows: Bool, errorType: String?)
4141
}
4242

4343
/// Marks the function as `throws` or `rethrows`.
4444
/// - Parameter rethrows: Pass `true` to emit `rethrows` instead of `throws`.
4545
public func `throws`(isRethrows: Bool = false) -> Self {
4646
var copy = self
47-
copy.effect = .throws(isRethrows: isRethrows)
47+
copy.effect = .throws(isRethrows: isRethrows, errorType: nil)
48+
return copy
49+
}
50+
51+
/// Marks the function as `throws` with a specific error type.
52+
/// - Parameter errorType: The error type to specify in the throws clause.
53+
public func `throws`(_ errorType: String) -> Self {
54+
var copy = self
55+
copy.effect = .throws(isRethrows: false, errorType: errorType)
4856
return copy
4957
}
5058

@@ -59,7 +67,15 @@ extension Function {
5967
/// - Parameter rethrows: Pass `true` to emit `async rethrows`.
6068
public func asyncThrows(isRethrows: Bool = false) -> Self {
6169
var copy = self
62-
copy.effect = .asyncThrows(isRethrows: isRethrows)
70+
copy.effect = .asyncThrows(isRethrows: isRethrows, errorType: nil)
71+
return copy
72+
}
73+
74+
/// Marks the function as `async throws` with a specific error type.
75+
/// - Parameter errorType: The error type to specify in the throws clause.
76+
public func asyncThrows(_ errorType: String) -> Self {
77+
var copy = self
78+
copy.effect = .asyncThrows(isRethrows: false, errorType: errorType)
6379
return copy
6480
}
6581
}

Sources/SyntaxKit/Function+Syntax.swift

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,23 +113,54 @@ extension Function {
113113
switch effect {
114114
case .none:
115115
return nil
116-
case .throws(let isRethrows):
117-
return FunctionEffectSpecifiersSyntax(
118-
asyncSpecifier: nil,
119-
throwsSpecifier: .keyword(
120-
isRethrows ? .rethrows : .throws, leadingTrivia: .space, trailingTrivia: .space)
121-
)
116+
case .throws(let isRethrows, let errorType):
117+
let throwsSpecifier: TokenSyntax
118+
if let errorType = errorType {
119+
throwsSpecifier = .keyword(
120+
isRethrows ? .rethrows : .throws, leadingTrivia: .space)
121+
return FunctionEffectSpecifiersSyntax(
122+
asyncSpecifier: nil,
123+
throwsClause: ThrowsClauseSyntax(
124+
throwsSpecifier: throwsSpecifier,
125+
leftParen: .leftParenToken(),
126+
type: IdentifierTypeSyntax(name: .identifier(errorType)),
127+
rightParen: .rightParenToken()
128+
)
129+
)
130+
} else {
131+
throwsSpecifier = .keyword(
132+
isRethrows ? .rethrows : .throws, leadingTrivia: .space)
133+
return FunctionEffectSpecifiersSyntax(
134+
asyncSpecifier: nil,
135+
throwsSpecifier: throwsSpecifier
136+
)
137+
}
122138
case .async:
123139
return FunctionEffectSpecifiersSyntax(
124140
asyncSpecifier: .keyword(.async, leadingTrivia: .space, trailingTrivia: .space),
125141
throwsSpecifier: nil
126142
)
127-
case .asyncThrows(let isRethrows):
128-
return FunctionEffectSpecifiersSyntax(
129-
asyncSpecifier: .keyword(.async, leadingTrivia: .space, trailingTrivia: .space),
130-
throwsSpecifier: .keyword(
131-
isRethrows ? .rethrows : .throws, leadingTrivia: .space, trailingTrivia: .space)
132-
)
143+
case .asyncThrows(let isRethrows, let errorType):
144+
let throwsSpecifier: TokenSyntax
145+
if let errorType = errorType {
146+
throwsSpecifier = .keyword(.throws, leadingTrivia: .space)
147+
return FunctionEffectSpecifiersSyntax(
148+
asyncSpecifier: .keyword(.async, leadingTrivia: .space, trailingTrivia: .space),
149+
throwsClause: ThrowsClauseSyntax(
150+
throwsSpecifier: throwsSpecifier,
151+
leftParen: .leftParenToken(),
152+
type: IdentifierTypeSyntax(name: .identifier(errorType)),
153+
rightParen: .rightParenToken()
154+
)
155+
)
156+
} else {
157+
throwsSpecifier = .keyword(
158+
isRethrows ? .rethrows : .throws, leadingTrivia: .space)
159+
return FunctionEffectSpecifiersSyntax(
160+
asyncSpecifier: .keyword(.async, leadingTrivia: .space, trailingTrivia: .space),
161+
throwsSpecifier: throwsSpecifier
162+
)
163+
}
133164
}
134165
}()
135166

Sources/SyntaxKit/Function.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,20 @@ public struct Function: CodeBlock {
7171
self.returnType = returnType
7272
self.body = content()
7373
}
74+
75+
/// Creates a `func` declaration with parameters and body using the DSL syntax.
76+
/// - Parameters:
77+
/// - name: The name of the function.
78+
/// - params: A ``ParameterBuilder`` that provides the parameters of the function.
79+
/// - body: A ``CodeBlockBuilder`` that provides the body of the function.
80+
public init(
81+
_ name: String,
82+
@ParameterBuilderResult _ params: () -> [Parameter],
83+
@CodeBlockBuilderResult _ body: () -> [CodeBlock]
84+
) {
85+
self.name = name
86+
self.parameters = params()
87+
self.returnType = nil
88+
self.body = body()
89+
}
7490
}

Sources/SyntaxKit/Throw.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,14 @@ public struct Throw: CodeBlock {
3737
}
3838

3939
public var syntax: SyntaxProtocol {
40-
let expression =
41-
expr.syntax.as(ExprSyntax.self)
42-
?? ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("")))
40+
let expression: ExprSyntax
41+
if let enumCase = expr as? EnumCase {
42+
expression = enumCase.asExpressionSyntax
43+
} else {
44+
expression =
45+
expr.syntax.as(ExprSyntax.self)
46+
?? ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("")))
47+
}
4348
return StmtSyntax(
4449
ThrowStmtSyntax(
4550
throwKeyword: .keyword(.throw, trailingTrivia: .space),

Sources/SyntaxKit/VariableExp.swift

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public struct VariableExp: CodeBlock, PatternConvertible {
4242
/// Accesses a property on the variable.
4343
/// - Parameter propertyName: The name of the property to access.
4444
/// - Returns: A ``PropertyAccessExp`` that represents the property access.
45-
public func property(_ propertyName: String) -> CodeBlock {
45+
public func property(_ propertyName: String) -> PropertyAccessExp {
4646
PropertyAccessExp(baseName: name, propertyName: propertyName)
4747
}
4848

@@ -87,6 +87,12 @@ public struct PropertyAccessExp: CodeBlock {
8787
self.propertyName = propertyName
8888
}
8989

90+
/// Negates the property access expression.
91+
/// - Returns: A negated property access expression.
92+
public func not() -> CodeBlock {
93+
NegatedPropertyAccessExp(baseName: baseName, propertyName: propertyName)
94+
}
95+
9096
public var syntax: SyntaxProtocol {
9197
let base = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(baseName)))
9298
let property = TokenSyntax.identifier(propertyName)
@@ -99,6 +105,38 @@ public struct PropertyAccessExp: CodeBlock {
99105
}
100106
}
101107

108+
/// An expression that negates a property access.
109+
public struct NegatedPropertyAccessExp: CodeBlock {
110+
internal let baseName: String
111+
internal let propertyName: String
112+
113+
/// Creates a negated property access expression.
114+
/// - Parameters:
115+
/// - baseName: The name of the base variable.
116+
/// - propertyName: The name of the property to access.
117+
public init(baseName: String, propertyName: String) {
118+
self.baseName = baseName
119+
self.propertyName = propertyName
120+
}
121+
122+
public var syntax: SyntaxProtocol {
123+
let base = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(baseName)))
124+
let property = TokenSyntax.identifier(propertyName)
125+
let memberAccess = ExprSyntax(
126+
MemberAccessExprSyntax(
127+
base: base,
128+
dot: .periodToken(),
129+
name: property
130+
))
131+
return ExprSyntax(
132+
PrefixOperatorExprSyntax(
133+
operator: .prefixOperator("!", leadingTrivia: [], trailingTrivia: []),
134+
expression: memberAccess
135+
)
136+
)
137+
}
138+
}
139+
102140
/// An expression that calls a function.
103141
public struct FunctionCallExp: CodeBlock {
104142
internal let baseName: String

0 commit comments

Comments
 (0)