Skip to content

Commit f974452

Browse files
authored
Merge pull request #2219 from DougGregor/typed-throws
Typed throws
2 parents 8c1977d + b243260 commit f974452

35 files changed

+924
-56
lines changed

CodeGeneration/Sources/SyntaxSupport/Child.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ public class Child {
8282
/// Whether this child is optional and can be `nil`.
8383
public let isOptional: Bool
8484

85+
/// The experimental feature the child represents, or `nil` if this isn't
86+
/// for an experimental feature.
87+
public let experimentalFeature: ExperimentalFeature?
88+
8589
/// A name of this child that can be shown in diagnostics.
8690
///
8791
/// This is used to e.g. describe the child if all of its tokens are missing in the source file.
@@ -110,6 +114,10 @@ public class Child {
110114
/// The first line of the child's documentation
111115
public let documentationAbstract: String
112116

117+
/// If `true`, this is for an experimental language feature, and any public
118+
/// API generated should be SPI.
119+
public var isExperimental: Bool { experimentalFeature != nil }
120+
113121
public var syntaxNodeKind: SyntaxNodeKind {
114122
switch kind {
115123
case .node(kind: let kind):
@@ -218,6 +226,15 @@ public class Child {
218226
}
219227
}
220228

229+
/// The attributes that should be printed on any API for the this child.
230+
///
231+
/// This is typically used to mark APIs as SPI when the keyword is part of
232+
/// an experimental language feature.
233+
public var apiAttributes: AttributeListSyntax {
234+
guard isExperimental else { return "" }
235+
return AttributeListSyntax("@_spi(ExperimentalLanguageFeatures)").with(\.trailingTrivia, .newline)
236+
}
237+
221238
/// If a classification is passed, it specifies the color identifiers in
222239
/// that subtree should inherit for syntax coloring. Must be a member of
223240
/// ``SyntaxClassification``.
@@ -227,6 +244,7 @@ public class Child {
227244
name: String,
228245
deprecatedName: String? = nil,
229246
kind: ChildKind,
247+
experimentalFeature: ExperimentalFeature? = nil,
230248
nameForDiagnostics: String? = nil,
231249
documentation: String? = nil,
232250
isOptional: Bool = false
@@ -236,6 +254,7 @@ public class Child {
236254
self.name = name
237255
self.deprecatedName = deprecatedName
238256
self.kind = kind
257+
self.experimentalFeature = experimentalFeature
239258
self.nameForDiagnostics = nameForDiagnostics
240259
self.documentationSummary = SwiftSyntax.Trivia.docCommentTrivia(from: documentation)
241260
self.documentationAbstract = String(documentation?.split(whereSeparator: \.isNewline).first ?? "")

CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,34 @@ public let COMMON_NODES: [Node] = [
7979
]
8080
),
8181

82+
Node(
83+
kind: .thrownTypeClause,
84+
base: .syntax,
85+
nameForDiagnostics: "thrown type clause",
86+
documentation: "The specific error type that a function can throw.",
87+
traits: [
88+
"Parenthesized"
89+
],
90+
children: [
91+
Child(
92+
name: "leftParen",
93+
kind: .token(choices: [.token(.leftParen)]),
94+
documentation: "The '(' to open the thrown type clause."
95+
),
96+
Child(
97+
name: "type",
98+
kind: .node(kind: .type),
99+
nameForDiagnostics: "thrown type",
100+
documentation: "The thrown error type."
101+
),
102+
Child(
103+
name: "rightParen",
104+
kind: .token(choices: [.token(.rightParen)]),
105+
documentation: "The ')' to closure the thrown type clause."
106+
),
107+
]
108+
),
109+
82110
Node(
83111
kind: .accessorEffectSpecifiers,
84112
base: .syntax,
@@ -99,6 +127,13 @@ public let COMMON_NODES: [Node] = [
99127
documentation: "The `throws` keyword.",
100128
isOptional: true
101129
),
130+
Child(
131+
name: "thrownError",
132+
kind: .node(kind: .thrownTypeClause),
133+
experimentalFeature: .typedThrows,
134+
documentation: "The specific error type thrown by this accessor.",
135+
isOptional: true
136+
),
102137
]
103138
),
104139

@@ -122,6 +157,13 @@ public let COMMON_NODES: [Node] = [
122157
documentation: "The `throws` or `rethrows` keyword.",
123158
isOptional: true
124159
),
160+
Child(
161+
name: "thrownError",
162+
kind: .node(kind: .thrownTypeClause),
163+
experimentalFeature: .typedThrows,
164+
documentation: "The specific error type thrown by this function.",
165+
isOptional: true
166+
),
125167
]
126168
),
127169

@@ -324,6 +366,13 @@ public let COMMON_NODES: [Node] = [
324366
kind: .token(choices: [.keyword(.throws)]),
325367
isOptional: true
326368
),
369+
Child(
370+
name: "thrownError",
371+
kind: .node(kind: .thrownTypeClause),
372+
experimentalFeature: .typedThrows,
373+
documentation: "The specific error type thrown by this function type.",
374+
isOptional: true
375+
),
327376
]
328377
),
329378

CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import SwiftSyntax
1515
public enum ExperimentalFeature: String, CaseIterable {
1616
case referenceBindings
1717
case thenStatements
18+
case typedThrows
1819

1920
/// The name of the feature, which is used in the doc comment.
2021
public var featureName: String {
@@ -23,6 +24,8 @@ public enum ExperimentalFeature: String, CaseIterable {
2324
return "reference bindings"
2425
case .thenStatements:
2526
return "'then' statements"
27+
case .typedThrows:
28+
return "typed throws"
2629
}
2730
}
2831

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ public enum SyntaxNodeKind: String, CaseIterable {
268268
case switchExpr
269269
case ternaryExpr
270270
case thenStmt
271+
case thrownTypeClause
271272
case throwStmt
272273
case tryExpr
273274
case tupleExpr

CodeGeneration/Sources/SyntaxSupport/Traits.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,15 @@ public let TRAITS: [Trait] = [
5656
Child(name: "asyncSpecifier", kind: .token(choices: [.keyword(.async), .keyword(.reasync)]), isOptional: true),
5757
Child(name: "unexpectedBetweenAsyncSpecifierAndThrowsSpecifier", kind: .node(kind: .unexpectedNodes), isOptional: true),
5858
Child(name: "throwsSpecifier", kind: .token(choices: [.keyword(.throws), .keyword(.rethrows)]), isOptional: true),
59-
Child(name: "unexpectedAfterThrowsSpecifier", kind: .node(kind: .unexpectedNodes), isOptional: true),
59+
Child(name: "unexpectedBetweenThrowsSpecifierAndThrownError", kind: .node(kind: .unexpectedNodes), isOptional: true),
60+
Child(
61+
name: "thrownError",
62+
kind: .node(kind: .thrownTypeClause),
63+
experimentalFeature: .typedThrows,
64+
documentation: "The specific thrown error type.",
65+
isOptional: true
66+
),
67+
Child(name: "unexpectedAfterThrownError", kind: .node(kind: .unexpectedNodes), isOptional: true),
6068
]
6169
),
6270
Trait(

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
138138
try! VariableDeclSyntax(
139139
"""
140140
\(child.documentation)
141-
public var \(child.varOrCaseName.backtickedIfNeeded): \(type)
141+
\(child.apiAttributes)public var \(child.varOrCaseName.backtickedIfNeeded): \(type)
142142
"""
143143
) {
144144
AccessorDeclSyntax(accessorSpecifier: .keyword(.get)) {

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxTraitsFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ let syntaxTraitsFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
3131
DeclSyntax(
3232
"""
3333
\(child.documentation)
34-
var \(child.varOrCaseName): \(child.syntaxNodeKind.syntaxType)\(questionMark) { get set }
34+
\(child.apiAttributes)var \(child.varOrCaseName): \(child.syntaxNodeKind.syntaxType)\(questionMark) { get set }
3535
"""
3636
)
3737
}

Release Notes/510.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050

5151
## API-Incompatible Changes
5252

53+
- Effect specifiers:
54+
- Description: The `unexpectedAfterThrowsSpecifier` node of the various effect specifiers has been removed.
55+
- Pull request: https://github.com/apple/swift-syntax/pull/2219
5356

5457
## Template
5558

Sources/SwiftParser/Specifiers.swift

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ public enum EffectSpecifier: TokenSpecSet {
128128
case .throwsSpecifier(let underlyingKind): return underlyingKind.spec
129129
}
130130
}
131+
132+
var isThrowsSpecifier: Bool {
133+
switch self {
134+
case .asyncSpecifier: return false
135+
case .throwsSpecifier: return true
136+
}
137+
}
131138
}
132139

133140
// MARK: - EffectSpecifiersTrait
@@ -153,10 +160,12 @@ protocol RawMisplacedEffectSpecifiersTrait {
153160

154161
var asyncSpecifier: RawTokenSyntax? { get }
155162
var throwsSpecifier: RawTokenSyntax? { get }
163+
var thrownError: RawThrownTypeClauseSyntax? { get }
156164

157165
init(
158166
asyncSpecifier: RawTokenSyntax?,
159167
throwsSpecifier: RawTokenSyntax?,
168+
thrownError: RawThrownTypeClauseSyntax?,
160169
arena: __shared SyntaxArena
161170
)
162171

@@ -166,14 +175,17 @@ protocol RawMisplacedEffectSpecifiersTrait {
166175
protocol RawEffectSpecifiersTrait: RawMisplacedEffectSpecifiersTrait {
167176
var unexpectedBeforeAsyncSpecifier: RawUnexpectedNodesSyntax? { get }
168177
var unexpectedBetweenAsyncSpecifierAndThrowsSpecifier: RawUnexpectedNodesSyntax? { get }
169-
var unexpectedAfterThrowsSpecifier: RawUnexpectedNodesSyntax? { get }
170-
178+
var unexpectedBetweenThrowsSpecifierAndThrownError: RawUnexpectedNodesSyntax? { get }
179+
var thrownError: RawThrownTypeClauseSyntax? { get }
180+
var unexpectedAfterThrownError: RawUnexpectedNodesSyntax? { get }
171181
init(
172182
_ unexpectedBeforeAsyncSpecifier: RawUnexpectedNodesSyntax?,
173183
asyncSpecifier: RawTokenSyntax?,
174184
_ unexpectedBetweenAsyncSpecifierAndThrowsSpecifier: RawUnexpectedNodesSyntax?,
175185
throwsSpecifier: RawTokenSyntax?,
176-
_ unexpectedAfterThrowsSpecifier: RawUnexpectedNodesSyntax?,
186+
_ unexpectedBetweenThrowsSpecifierAndThrownError: RawUnexpectedNodesSyntax?,
187+
thrownError: RawThrownTypeClauseSyntax?,
188+
_ unexpectedAfterThrownError: RawUnexpectedNodesSyntax?,
177189
arena: __shared SyntaxArena
178190
)
179191
}
@@ -182,6 +194,7 @@ extension RawEffectSpecifiersTrait {
182194
init(
183195
asyncSpecifier: RawTokenSyntax?,
184196
throwsSpecifier: RawTokenSyntax?,
197+
thrownError: RawThrownTypeClauseSyntax?,
185198
arena: __shared SyntaxArena
186199
) {
187200
self.init(
@@ -190,6 +203,8 @@ extension RawEffectSpecifiersTrait {
190203
nil,
191204
throwsSpecifier: throwsSpecifier,
192205
nil,
206+
thrownError: thrownError,
207+
nil,
193208
arena: arena
194209
)
195210
}
@@ -200,7 +215,9 @@ extension RawEffectSpecifiersTrait {
200215
asyncSpecifier: self.asyncSpecifier ?? misplacedAsyncKeyword,
201216
self.unexpectedBetweenAsyncSpecifierAndThrowsSpecifier,
202217
throwsSpecifier: self.throwsSpecifier ?? misplacedThrowsKeyword,
203-
self.unexpectedAfterThrowsSpecifier,
218+
self.unexpectedBetweenThrowsSpecifierAndThrownError,
219+
thrownError: thrownError,
220+
self.unexpectedAfterThrownError,
204221
arena: arena
205222
)
206223
}
@@ -521,10 +538,12 @@ extension RawDeinitializerEffectSpecifiersSyntax: RawMisplacedEffectSpecifiersTr
521538
}
522539

523540
var throwsSpecifier: RawTokenSyntax? { nil }
541+
var thrownError: RawThrownTypeClauseSyntax? { nil }
524542

525543
init(
526544
asyncSpecifier: RawTokenSyntax?,
527545
throwsSpecifier: RawTokenSyntax?,
546+
thrownError: RawThrownTypeClauseSyntax?,
528547
arena: __shared SwiftSyntax.SyntaxArena
529548
) {
530549
// `throwsSpecifier` should never be present because `parseMisplacedEffectSpecifiers()` only creates missing tokens
@@ -577,12 +596,28 @@ extension TokenConsumer {
577596
// MARK: - Parsing effect specifiers
578597

579598
extension Parser {
599+
private mutating func parseThrownTypeClause() -> RawThrownTypeClauseSyntax {
600+
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
601+
let type = self.parseType()
602+
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
603+
return RawThrownTypeClauseSyntax(
604+
unexpectedBeforeLeftParen,
605+
leftParen: leftParen,
606+
type: type,
607+
unexpectedBeforeRightParen,
608+
rightParen: rightParen,
609+
arena: self.arena
610+
)
611+
}
612+
580613
private mutating func parseEffectSpecifiers<S: RawEffectSpecifiersTrait>(_: S.Type) -> S? {
581614
var unexpectedBeforeAsync: [RawSyntax] = []
582615
var asyncKeyword: RawTokenSyntax? = nil
583616
var unexpectedBeforeThrows: [RawSyntax] = []
584617
var throwsKeyword: RawTokenSyntax?
585-
var unexpectedAfterThrows: [RawSyntax] = []
618+
var thrownError: RawThrownTypeClauseSyntax?
619+
var unexpectedAfterThrownError: [RawSyntax] = []
620+
586621
while let misspelledAsync = self.consume(ifAnyIn: S.MisspelledAsyncTokenKinds.self) {
587622
unexpectedBeforeAsync.append(RawSyntax(misspelledAsync))
588623
if asyncKeyword == nil {
@@ -617,26 +652,32 @@ extension Parser {
617652
let (unexpected, throwsKw) = self.eat(handle)
618653
unexpectedBeforeThrows.append(contentsOf: unexpected?.elements ?? [])
619654
throwsKeyword = throwsKw
655+
656+
if self.at(.leftParen) && experimentalFeatures.contains(.typedThrows) {
657+
thrownError = parseThrownTypeClause()
658+
}
620659
}
621660

622-
var unexpectedAfterThrowsLoopProgress = LoopProgressCondition()
623-
while self.hasProgressed(&unexpectedAfterThrowsLoopProgress) {
661+
var unexpectedAfterThrownErrorLoopProgress = LoopProgressCondition()
662+
while self.hasProgressed(&unexpectedAfterThrownErrorLoopProgress) {
624663
if let (_, handle, _) = self.at(anyIn: S.MisspelledAsyncTokenKinds.self, or: S.CorrectAsyncTokenKinds.self) {
625664
let misspelledAsync = self.eat(handle)
626-
unexpectedAfterThrows.append(RawSyntax(misspelledAsync))
665+
unexpectedAfterThrownError.append(RawSyntax(misspelledAsync))
627666
if asyncKeyword == nil {
628667
// Handle `async` after `throws`
629668
asyncKeyword = missingToken(.keyword(.async))
630669
}
631670
} else if let (_, handle, _) = self.at(anyIn: S.MisspelledThrowsTokenKinds.self, or: S.CorrectThrowsTokenKinds.self) {
632671
let misspelledThrows = self.eat(handle)
633-
unexpectedAfterThrows.append(RawSyntax(misspelledThrows))
672+
unexpectedAfterThrownError.append(RawSyntax(misspelledThrows))
634673
} else {
635674
break
636675
}
637676
}
638677

639-
if unexpectedBeforeAsync.isEmpty && asyncKeyword == nil && unexpectedBeforeThrows.isEmpty && throwsKeyword == nil && unexpectedAfterThrows.isEmpty {
678+
if unexpectedBeforeAsync.isEmpty && asyncKeyword == nil && unexpectedBeforeThrows.isEmpty && throwsKeyword == nil && thrownError == nil
679+
&& unexpectedAfterThrownError.isEmpty
680+
{
640681
return nil
641682
}
642683

@@ -645,7 +686,9 @@ extension Parser {
645686
asyncSpecifier: asyncKeyword,
646687
RawUnexpectedNodesSyntax(unexpectedBeforeThrows, arena: self.arena),
647688
throwsSpecifier: throwsKeyword,
648-
RawUnexpectedNodesSyntax(unexpectedAfterThrows, arena: self.arena),
689+
nil,
690+
thrownError: thrownError,
691+
RawUnexpectedNodesSyntax(unexpectedAfterThrownError, arena: self.arena),
649692
arena: self.arena
650693
)
651694
}
@@ -749,6 +792,7 @@ extension Parser {
749792
effectSpecifiers = S(
750793
asyncSpecifier: synthesizedAsync,
751794
throwsSpecifier: synthesizedThrows,
795+
thrownError: nil,
752796
arena: self.arena
753797
)
754798
}

Sources/SwiftParser/Types.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,11 +800,18 @@ extension Parser.Lookahead {
800800
return true
801801
}
802802

803-
if self.at(anyIn: EffectSpecifier.self) != nil {
803+
if let effect = self.at(anyIn: EffectSpecifier.self) {
804804
if self.peek().rawTokenKind == .arrow {
805805
return true
806806
}
807807

808+
if effect.spec.isThrowsSpecifier && self.peek().rawTokenKind == .leftParen {
809+
var backtrack = self.lookahead()
810+
backtrack.consumeAnyToken()
811+
backtrack.skipSingle()
812+
return backtrack.atFunctionTypeArrow()
813+
}
814+
808815
if peek(isAtAnyIn: EffectSpecifier.self) != nil {
809816
var backtrack = self.lookahead()
810817
backtrack.consumeAnyToken()

0 commit comments

Comments
 (0)