Skip to content

Commit a733687

Browse files
committed
Improve diagnostics for effect specifiers
Instead of modelling the effect specifiers as keywords in the declaration/signature, create dedicated nodes for them. This greatly simplifies how we can recover from incorrect effect specifiers.
1 parent a9eab70 commit a733687

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2201
-1251
lines changed

CodeGeneration/Sources/SyntaxSupport/gyb_generated/CommonNodes.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,62 @@ public let COMMON_NODES: [Node] = [
132132
requiresLeadingNewline: true)
133133
]),
134134

135+
Node(name: "DeclEffectSpecifiers",
136+
nameForDiagnostics: "effect specifiers",
137+
kind: "Syntax",
138+
traits: [
139+
"EffectSpecifiers"
140+
],
141+
children: [
142+
Child(name: "AsyncSpecifier",
143+
kind: "KeywordToken",
144+
isOptional: true,
145+
tokenChoices: [
146+
"Keyword"
147+
],
148+
textChoices: [
149+
"async",
150+
"reasync"
151+
]),
152+
Child(name: "ThrowsSpecifier",
153+
kind: "KeywordToken",
154+
isOptional: true,
155+
tokenChoices: [
156+
"Keyword"
157+
],
158+
textChoices: [
159+
"throws",
160+
"rethrows"
161+
])
162+
]),
163+
164+
Node(name: "TypeEffectSpecifiers",
165+
nameForDiagnostics: "effect specifiers",
166+
kind: "Syntax",
167+
traits: [
168+
"EffectSpecifiers"
169+
],
170+
children: [
171+
Child(name: "AsyncSpecifier",
172+
kind: "KeywordToken",
173+
isOptional: true,
174+
tokenChoices: [
175+
"Keyword"
176+
],
177+
textChoices: [
178+
"async"
179+
]),
180+
Child(name: "ThrowsSpecifier",
181+
kind: "KeywordToken",
182+
isOptional: true,
183+
tokenChoices: [
184+
"Keyword"
185+
],
186+
textChoices: [
187+
"throws"
188+
])
189+
]),
190+
135191
Node(name: "UnexpectedNodes",
136192
nameForDiagnostics: nil,
137193
description: "A collection of syntax nodes that occurred in the source code butcould not be used to form a valid syntax tree.",

CodeGeneration/Sources/SyntaxSupport/gyb_generated/DeclNodes.swift

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -152,26 +152,9 @@ public let DECL_NODES: [Node] = [
152152
children: [
153153
Child(name: "Input",
154154
kind: "ParameterClause"),
155-
Child(name: "AsyncOrReasyncKeyword",
156-
kind: "KeywordToken",
157-
isOptional: true,
158-
tokenChoices: [
159-
"Keyword"
160-
],
161-
textChoices: [
162-
"async",
163-
"reasync"
164-
]),
165-
Child(name: "ThrowsOrRethrowsKeyword",
166-
kind: "KeywordToken",
167-
isOptional: true,
168-
tokenChoices: [
169-
"Keyword"
170-
],
171-
textChoices: [
172-
"throws",
173-
"rethrows"
174-
]),
155+
Child(name: "EffectSpecifiers",
156+
kind: "DeclEffectSpecifiers",
157+
isOptional: true),
175158
Child(name: "Output",
176159
kind: "ReturnClause",
177160
isOptional: true)
@@ -1090,25 +1073,9 @@ public let DECL_NODES: [Node] = [
10901073
Child(name: "Parameter",
10911074
kind: "AccessorParameter",
10921075
isOptional: true),
1093-
Child(name: "AsyncKeyword",
1094-
kind: "KeywordToken",
1095-
isOptional: true,
1096-
tokenChoices: [
1097-
"Keyword"
1098-
],
1099-
textChoices: [
1100-
"async"
1101-
]),
1102-
Child(name: "ThrowsKeyword",
1103-
kind: "KeywordToken",
1104-
isOptional: true,
1105-
tokenChoices: [
1106-
"Keyword"
1107-
],
1108-
textChoices: [
1109-
"throws",
1110-
"rethrows"
1111-
]),
1076+
Child(name: "EffectSpecifiers",
1077+
kind: "DeclEffectSpecifiers",
1078+
isOptional: true),
11121079
Child(name: "Body",
11131080
kind: "CodeBlock",
11141081
isOptional: true)

CodeGeneration/Sources/SyntaxSupport/gyb_generated/ExprNodes.swift

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -303,17 +303,8 @@ public let EXPR_NODES: [Node] = [
303303
nameForDiagnostics: nil,
304304
kind: "Expr",
305305
children: [
306-
Child(name: "AsyncKeyword",
307-
kind: "KeywordToken",
308-
isOptional: true,
309-
tokenChoices: [
310-
"Keyword"
311-
],
312-
textChoices: [
313-
"async"
314-
]),
315-
Child(name: "ThrowsToken",
316-
kind: "ThrowsToken",
306+
Child(name: "EffectSpecifiers",
307+
kind: "TypeEffectSpecifiers",
317308
isOptional: true),
318309
Child(name: "ArrowToken",
319310
kind: "ArrowToken",
@@ -796,24 +787,9 @@ public let EXPR_NODES: [Node] = [
796787
Child(name: "Input",
797788
kind: "ParameterClause")
798789
]),
799-
Child(name: "AsyncKeyword",
800-
kind: "KeywordToken",
801-
isOptional: true,
802-
tokenChoices: [
803-
"Keyword"
804-
],
805-
textChoices: [
806-
"async"
807-
]),
808-
Child(name: "ThrowsTok",
809-
kind: "KeywordToken",
810-
isOptional: true,
811-
tokenChoices: [
812-
"Keyword"
813-
],
814-
textChoices: [
815-
"throws"
816-
]),
790+
Child(name: "EffectSpecifiers",
791+
kind: "TypeEffectSpecifiers",
792+
isOptional: true),
817793
Child(name: "Output",
818794
kind: "ReturnClause",
819795
isOptional: true),

CodeGeneration/Sources/SyntaxSupport/gyb_generated/Traits.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,13 @@ public let TRAITS: [Trait] = [
6969
Child(name: "Statements", kind: "CodeBlockItemList"),
7070
]
7171
),
72+
Trait(traitName: "EffectSpecifiers",
73+
children: [
74+
Child(name: "UnexpectedBeforeAsyncSpecifier", kind: "UnexpectedNodes", isOptional: true),
75+
Child(name: "AsyncSpecifier", kind: "KeywordToken", isOptional: true),
76+
Child(name: "UnexpectedBetweenAsyncSpecifierAndThrowsSpecifier", kind: "UnexpectedNodes", isOptional: true),
77+
Child(name: "ThrowsSpecifier", kind: "KeywordToken", isOptional: true),
78+
Child(name: "UnexpectedAfterThrowsSpecifier", kind: "UnexpectedNodes", isOptional: true),
79+
]
80+
),
7281
]

CodeGeneration/Sources/SyntaxSupport/gyb_generated/TypeNodes.swift

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -330,30 +330,11 @@ public let TYPE_NODES: [Node] = [
330330
tokenChoices: [
331331
"RightParen"
332332
]),
333-
Child(name: "AsyncKeyword",
334-
kind: "KeyworkToken",
335-
isOptional: true,
336-
textChoices: [
337-
"async"
338-
]),
339-
Child(name: "ThrowsOrRethrowsKeyword",
340-
kind: "KeywordToken",
341-
isOptional: true,
342-
tokenChoices: [
343-
"Keyword"
344-
],
345-
textChoices: [
346-
"throws",
347-
"rethrows",
348-
"throw"
349-
]),
350-
Child(name: "Arrow",
351-
kind: "ArrowToken",
352-
tokenChoices: [
353-
"Arrow"
354-
]),
355-
Child(name: "ReturnType",
356-
kind: "Type")
333+
Child(name: "EffectSpecifiers",
334+
kind: "TypeEffectSpecifiers",
335+
isOptional: true),
336+
Child(name: "Output",
337+
kind: "ReturnClause")
357338
]),
358339

359340
Node(name: "AttributedType",

Sources/SwiftParser/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ add_swift_host_library(SwiftParser
2323
RawTokenKindMatch.swift
2424
RawTokenKindSubset.swift
2525
Recovery.swift
26+
Specifiers.swift
2627
Statements.swift
2728
StringLiterals.swift
2829
SyntaxUtils.swift

Sources/SwiftParser/Declarations.swift

Lines changed: 16 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,26 +1217,25 @@ extension Parser {
12171217
}
12181218

12191219
/// If a `throws` keyword appears right in front of the `arrow`, it is returned as `misplacedThrowsKeyword` so it can be synthesized in front of the arrow.
1220-
@_spi(RawSyntax)
1221-
public mutating func parseFunctionReturnClause() -> (returnClause: RawReturnClauseSyntax, misplacedThrowsKeyword: RawTokenSyntax?) {
1220+
mutating func parseFunctionReturnClause<S: RawEffectSpecifiersTrait>(effectSpecifiers: inout S?, allowNamedOpaqueResultType: Bool) -> RawReturnClauseSyntax {
12221221
let (unexpectedBeforeArrow, arrow) = self.expect(.arrow)
1223-
var misplacedThrowsKeyword: RawTokenSyntax? = nil
1224-
let unexpectedBeforeReturnType: RawUnexpectedNodesSyntax?
1225-
if let throwsKeyword = self.consume(ifAny: [.keyword(.rethrows), .keyword(.throws)]) {
1226-
misplacedThrowsKeyword = throwsKeyword
1227-
unexpectedBeforeReturnType = RawUnexpectedNodesSyntax(elements: [RawSyntax(throwsKeyword)], arena: self.arena)
1222+
let unexpectedBeforeReturnType = self.parseMisplacedEffectSpecifiers(&effectSpecifiers)
1223+
let result: RawTypeSyntax
1224+
if allowNamedOpaqueResultType {
1225+
result = self.parseResultType()
12281226
} else {
1229-
unexpectedBeforeReturnType = nil
1227+
result = self.parseType()
12301228
}
1231-
let result = self.parseResultType()
1229+
let unexpectedAfterReturnType = self.parseMisplacedEffectSpecifiers(&effectSpecifiers)
12321230
let returnClause = RawReturnClauseSyntax(
12331231
unexpectedBeforeArrow,
12341232
arrow: arrow,
12351233
unexpectedBeforeReturnType,
12361234
returnType: result,
1235+
unexpectedAfterReturnType,
12371236
arena: self.arena
12381237
)
1239-
return (returnClause, misplacedThrowsKeyword)
1238+
return returnClause
12401239
}
12411240
}
12421241

@@ -1318,32 +1317,18 @@ extension Parser {
13181317
public mutating func parseFunctionSignature() -> RawFunctionSignatureSyntax {
13191318
let input = self.parseParameterClause(for: .functionParameters)
13201319

1321-
let async: RawTokenSyntax?
1322-
if let asyncTok = self.consume(if: .keyword(.async)) {
1323-
async = asyncTok
1324-
} else if let reasync = self.consume(if: .keyword(.reasync)) {
1325-
async = reasync
1326-
} else {
1327-
async = nil
1328-
}
1329-
1330-
var throwsKeyword = self.consume(ifAny: [.keyword(.throws), .keyword(.rethrows)])
1320+
var effectSpecifiers = self.parseDeclEffectSpecifiers()
13311321

13321322
let output: RawReturnClauseSyntax?
13331323
if self.at(.arrow) {
1334-
let result = self.parseFunctionReturnClause()
1335-
output = result.returnClause
1336-
if let misplacedThrowsKeyword = result.misplacedThrowsKeyword, throwsKeyword == nil {
1337-
throwsKeyword = RawTokenSyntax(missing: misplacedThrowsKeyword.tokenKind, arena: self.arena)
1338-
}
1324+
output = self.parseFunctionReturnClause(effectSpecifiers: &effectSpecifiers, allowNamedOpaqueResultType: true)
13391325
} else {
13401326
output = nil
13411327
}
13421328

13431329
return RawFunctionSignatureSyntax(
13441330
input: input,
1345-
asyncOrReasyncKeyword: async,
1346-
throwsOrRethrowsKeyword: throwsKeyword,
1331+
effectSpecifiers: effectSpecifiers,
13471332
output: output,
13481333
arena: self.arena
13491334
)
@@ -1384,7 +1369,8 @@ extension Parser {
13841369

13851370
let indices = self.parseParameterClause(for: .indices)
13861371

1387-
let result = self.parseFunctionReturnClause().returnClause
1372+
var misplacedEffectSpecifiers: RawDeclEffectSpecifiersSyntax?
1373+
let result = self.parseFunctionReturnClause(effectSpecifiers: &misplacedEffectSpecifiers, allowNamedOpaqueResultType: true)
13881374

13891375
// Parse a 'where' clause if present.
13901376
let genericWhereClause: RawGenericWhereClauseSyntax?
@@ -1588,41 +1574,6 @@ extension Parser {
15881574
)
15891575
}
15901576

1591-
@_spi(RawSyntax)
1592-
public mutating func parseEffectsSpecifier() -> RawTokenSyntax? {
1593-
// 'async'
1594-
if let async = self.consume(if: .keyword(.async)) {
1595-
return async
1596-
}
1597-
1598-
// 'reasync'
1599-
if let reasync = self.consume(if: .keyword(.reasync)) {
1600-
return reasync
1601-
}
1602-
1603-
// 'throws'/'rethrows'
1604-
if let throwsRethrows = self.consume(ifAny: [.keyword(.throws), .keyword(.rethrows)]) {
1605-
return throwsRethrows
1606-
}
1607-
1608-
// diagnose 'throw'/'try'.
1609-
if let throwTry = self.consume(ifAny: [.keyword(.throw), .keyword(.try)], allowTokenAtStartOfLine: false) {
1610-
return throwTry
1611-
}
1612-
1613-
return nil
1614-
}
1615-
1616-
@_spi(RawSyntax)
1617-
public mutating func parseEffectsSpecifiers() -> [RawTokenSyntax] {
1618-
var specifiers = [RawTokenSyntax]()
1619-
var loopProgress = LoopProgressCondition()
1620-
while let specifier = self.parseEffectsSpecifier(), loopProgress.evaluate(currentToken) {
1621-
specifiers.append(specifier)
1622-
}
1623-
return specifiers
1624-
}
1625-
16261577
/// Parse an accessor.
16271578
mutating func parseAccessorDecl() -> RawAccessorDeclSyntax {
16281579
let forcedHandle = TokenConsumptionHandle(tokenKind: .keyword(.get), missing: true)
@@ -1669,26 +1620,15 @@ extension Parser {
16691620
parameter = nil
16701621
}
16711622

1672-
// Next, parse effects specifiers. While it's only valid to have them
1673-
// on 'get' accessors, we also emit diagnostics if they show up on others.
1674-
let asyncKeyword: RawTokenSyntax?
1675-
let throwsKeyword: RawTokenSyntax?
1676-
if self.at(anyIn: EffectsSpecifier.self) != nil {
1677-
asyncKeyword = self.parseEffectsSpecifier()
1678-
throwsKeyword = self.parseEffectsSpecifier()
1679-
} else {
1680-
asyncKeyword = nil
1681-
throwsKeyword = nil
1682-
}
1623+
let effectSpecifiers = self.parseDeclEffectSpecifiers()
16831624

16841625
let body = self.parseOptionalCodeBlock()
16851626
return RawAccessorDeclSyntax(
16861627
attributes: introducer.attributes,
16871628
modifier: introducer.modifier,
16881629
accessorKind: introducer.token,
16891630
parameter: parameter,
1690-
asyncKeyword: asyncKeyword,
1691-
throwsKeyword: throwsKeyword,
1631+
effectSpecifiers: effectSpecifiers,
16921632
body: body,
16931633
arena: self.arena
16941634
)

0 commit comments

Comments
 (0)