Skip to content

Commit 0c63cc9

Browse files
committed
Improve recovery if variable or subscript is missing brace wrapping getter and setter
1 parent c1ab7e6 commit 0c63cc9

File tree

4 files changed

+101
-33
lines changed

4 files changed

+101
-33
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ extension Parser {
149149
///
150150
/// declarations → declaration declarations?
151151
///
152-
/// If `allowMissingFuncOrVarKeywordRecovery` is `true`, this methods tries to
153-
/// synthesize `func` or `var` keywords where necessary.
152+
/// If `inMemberDeclList` is `true`, we know that the next item must be a
153+
/// declaration and thus start with a keyword. This allows futher recovery.
154154
@_spi(RawSyntax)
155-
public mutating func parseDeclaration(allowMissingFuncOrVarKeywordRecovery: Bool = false) -> RawDeclSyntax {
155+
public mutating func parseDeclaration(inMemberDeclList: Bool = false) -> RawDeclSyntax {
156156
switch self.at(anyIn: PoundDeclarationStart.self) {
157157
case (.poundIfKeyword, _)?:
158158
let directive = self.parsePoundIfDirective { parser in
@@ -207,7 +207,7 @@ extension Parser {
207207
case (.subscriptKeyword, let handle)?:
208208
return RawDeclSyntax(self.parseSubscriptDeclaration(attrs, handle))
209209
case (.letKeyword, let handle)?, (.varKeyword, let handle)?:
210-
return RawDeclSyntax(self.parseLetOrVarDeclaration(attrs, handle))
210+
return RawDeclSyntax(self.parseLetOrVarDeclaration(attrs, handle, inMemberDeclList: inMemberDeclList))
211211
case (.initKeyword, let handle)?:
212212
return RawDeclSyntax(self.parseInitializerDeclaration(attrs, handle))
213213
case (.deinitKeyword, let handle)?:
@@ -219,7 +219,7 @@ extension Parser {
219219
case (.actorContextualKeyword, let handle)?:
220220
return RawDeclSyntax(self.parseActorDeclaration(attrs, handle))
221221
case nil:
222-
if allowMissingFuncOrVarKeywordRecovery {
222+
if inMemberDeclList {
223223
let isProbablyVarDecl = self.at(any: [.identifier, .wildcardKeyword]) && self.peek().tokenKind.is(any: [.colon, .equal, .comma])
224224
let isProbablyTupleDecl = self.at(.leftParen) && self.peek().tokenKind.is(any: [.identifier, .wildcardKeyword])
225225

@@ -608,7 +608,7 @@ extension Parser {
608608
if self.at(.poundSourceLocationKeyword) {
609609
decl = RawDeclSyntax(self.parsePoundSourceLocationDirective())
610610
} else {
611-
decl = self.parseDeclaration(allowMissingFuncOrVarKeywordRecovery: true)
611+
decl = self.parseDeclaration(inMemberDeclList: true)
612612
}
613613

614614
let semi = self.consume(if: .semicolon)
@@ -1690,7 +1690,7 @@ extension Parser {
16901690

16911691
// Parse getter and setter.
16921692
let accessor: RawSyntax?
1693-
if self.at(.leftBrace) {
1693+
if self.at(.leftBrace) || self.at(anyIn: AccessorKind.self) != nil {
16941694
accessor = self.parseGetSet()
16951695
} else {
16961696
accessor = nil
@@ -1721,10 +1721,22 @@ extension Parser {
17211721
/// pattern-initializer-list → pattern-initializer | pattern-initializer ',' pattern-initializer-list
17221722
/// pattern-initializer → pattern initializer?
17231723
/// initializer → = expression
1724+
///
1725+
/// If `inMemberDeclList` is `true`, we know that the next item needs to be a
1726+
/// declaration that is started by a keyword. Thus, we in the following case
1727+
/// we know that `set` can't start a new declaration and we can thus recover
1728+
/// by synthesizing a missing `{` in front of `set`.
1729+
/// ```
1730+
/// var x: Int
1731+
/// set {
1732+
/// }
1733+
/// }
1734+
/// ```
17241735
@_spi(RawSyntax)
17251736
public mutating func parseLetOrVarDeclaration(
17261737
_ attrs: DeclAttributes,
1727-
_ handle: RecoveryConsumptionHandle
1738+
_ handle: RecoveryConsumptionHandle,
1739+
inMemberDeclList: Bool = false
17281740
) -> RawVariableDeclSyntax {
17291741
let (unexpectedBeforeIntroducer, introducer) = self.eat(handle)
17301742
let hasTryBeforeIntroducer = unexpectedBeforeIntroducer?.containsToken(where: { $0.tokenKind == .tryKeyword }) ?? false
@@ -1786,7 +1798,7 @@ extension Parser {
17861798
}
17871799

17881800
let accessor: RawSyntax?
1789-
if self.at(.leftBrace) {
1801+
if self.at(.leftBrace) || (inMemberDeclList && self.at(anyIn: AccessorKind.self) != nil) {
17901802
accessor = self.parseGetSet()
17911803
} else {
17921804
accessor = nil
@@ -1812,21 +1824,6 @@ extension Parser {
18121824
arena: self.arena)
18131825
}
18141826

1815-
enum AccessorKind: SyntaxText, ContextualKeywords, Equatable {
1816-
case `get` = "get"
1817-
case `set` = "set"
1818-
case `didSet` = "didSet"
1819-
case `willSet` = "willSet"
1820-
case unsafeAddress = "unsafeAddress"
1821-
case addressWithOwner = "addressWithOwner"
1822-
case addressWithNativeOwner = "addressWithNativeOwner"
1823-
case unsafeMutableAddress = "unsafeMutableAddress"
1824-
case mutableAddressWithOwner = "mutableAddressWithOwner"
1825-
case mutableAddressWithNativeOwner = "mutableAddressWithNativeOwner"
1826-
case _read = "_read"
1827-
case _modify = "_modify"
1828-
}
1829-
18301827
struct AccessorIntroducer {
18311828
var attributes: RawAttributeListSyntax?
18321829
var modifier: RawDeclModifierSyntax?
@@ -1921,7 +1918,14 @@ extension Parser {
19211918
@_spi(RawSyntax)
19221919
public mutating func parseGetSet() -> RawSyntax {
19231920
// Parse getter and setter.
1924-
let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
1921+
let unexpectedBeforeLBrace: RawUnexpectedNodesSyntax?
1922+
let lbrace: RawTokenSyntax
1923+
if self.at(anyIn: AccessorKind.self) != nil {
1924+
unexpectedBeforeLBrace = nil
1925+
lbrace = missingToken(.leftBrace, text: nil)
1926+
} else {
1927+
(unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
1928+
}
19251929
// Collect all explicit accessors to a list.
19261930
var elements = [RawAccessorDeclSyntax]()
19271931
do {

Sources/SwiftParser/RawTokenKindSubset.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,21 @@ extension ContextualKeywords where RawValue == SyntaxText {
7272

7373
// MARK: - Subsets
7474

75+
enum AccessorKind: SyntaxText, ContextualKeywords, Equatable {
76+
case `get` = "get"
77+
case `set` = "set"
78+
case `didSet` = "didSet"
79+
case `willSet` = "willSet"
80+
case unsafeAddress = "unsafeAddress"
81+
case addressWithOwner = "addressWithOwner"
82+
case addressWithNativeOwner = "addressWithNativeOwner"
83+
case unsafeMutableAddress = "unsafeMutableAddress"
84+
case mutableAddressWithOwner = "mutableAddressWithOwner"
85+
case mutableAddressWithNativeOwner = "mutableAddressWithNativeOwner"
86+
case _read = "_read"
87+
case _modify = "_modify"
88+
}
89+
7590
enum BinaryOperator: RawTokenKindSubset {
7691
case spacedBinaryOperator
7792
case unspacedBinaryOperator

Tests/SwiftParserTest/Declarations.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,57 @@ final class DeclarationTests: XCTestCase {
11431143
"""
11441144
)
11451145
}
1146+
1147+
func testVariableDeclWithGetSetButNoBrace() {
1148+
AssertParse(
1149+
"""
1150+
struct Foo {
1151+
var x: Int 1️⃣
1152+
get {
1153+
4
1154+
}
1155+
set {
1156+
x = newValue
1157+
}
1158+
}
1159+
}
1160+
""",
1161+
diagnostics: [
1162+
DiagnosticSpec(message: "expected '{' in variable")
1163+
]
1164+
)
1165+
}
1166+
1167+
func testVariableDeclWithSetGetButNoBrace() {
1168+
AssertParse(
1169+
"""
1170+
struct Foo {
1171+
var x: Int 1️⃣
1172+
set {
1173+
x = newValue
1174+
}
1175+
get {
1176+
4
1177+
}
1178+
}
1179+
}
1180+
""",
1181+
diagnostics: [
1182+
DiagnosticSpec(message: "expected '{' in variable")
1183+
]
1184+
)
1185+
}
1186+
1187+
func testVariableFollowedByReferenceToSet() {
1188+
AssertParse(
1189+
"""
1190+
func bar() {
1191+
let a = b
1192+
set.c
1193+
}
1194+
"""
1195+
)
1196+
}
11461197
}
11471198

11481199
extension Parser.DeclAttributes {

Tests/SwiftParserTest/translated/SubscriptingTests.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -362,20 +362,18 @@ final class SubscriptingTests: XCTestCase {
362362
AssertParse(
363363
"""
364364
struct A8 {
365-
subscript(i : Int) -> Int
366-
1️⃣get 2️⃣{
365+
subscript(i : Int) -> Int1️⃣
366+
get {
367367
return stored
368368
}
369-
3️⃣set 4️⃣{
369+
set {
370370
stored = value
371371
}
372-
}
372+
}2️⃣
373373
""",
374374
diagnostics: [
375-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'func' in function"),
376-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected parameter clause in function signature"),
377-
DiagnosticSpec(locationMarker: "3️⃣", message: "expected 'func' in function"),
378-
DiagnosticSpec(locationMarker: "4️⃣", message: "expected parameter clause in function signature"),
375+
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in subscript"),
376+
DiagnosticSpec(locationMarker: "2️⃣", message: "expected '}' to end struct"),
379377
]
380378
)
381379
}

0 commit comments

Comments
 (0)