Skip to content

Commit add30e7

Browse files
authored
Merge pull request #979 from ahoppen/ahoppen/missing-brace-wrapping-getter
Improve recovery if variable or subscript is missing brace wrapping getter and setter
2 parents b0b249f + 0c63cc9 commit add30e7

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
@@ -211,7 +211,7 @@ extension Parser {
211211
case (.subscriptKeyword, let handle)?:
212212
return RawDeclSyntax(self.parseSubscriptDeclaration(attrs, handle))
213213
case (.letKeyword, let handle)?, (.varKeyword, let handle)?:
214-
return RawDeclSyntax(self.parseLetOrVarDeclaration(attrs, handle))
214+
return RawDeclSyntax(self.parseLetOrVarDeclaration(attrs, handle, inMemberDeclList: inMemberDeclList))
215215
case (.initKeyword, let handle)?:
216216
return RawDeclSyntax(self.parseInitializerDeclaration(attrs, handle))
217217
case (.deinitKeyword, let handle)?:
@@ -223,7 +223,7 @@ extension Parser {
223223
case (.actorContextualKeyword, let handle)?:
224224
return RawDeclSyntax(self.parseActorDeclaration(attrs, handle))
225225
case nil:
226-
if allowMissingFuncOrVarKeywordRecovery {
226+
if inMemberDeclList {
227227
let isProbablyVarDecl = self.at(any: [.identifier, .wildcardKeyword]) && self.peek().tokenKind.is(any: [.colon, .equal, .comma])
228228
let isProbablyTupleDecl = self.at(.leftParen) && self.peek().tokenKind.is(any: [.identifier, .wildcardKeyword])
229229

@@ -612,7 +612,7 @@ extension Parser {
612612
if self.at(.poundSourceLocationKeyword) {
613613
decl = RawDeclSyntax(self.parsePoundSourceLocationDirective())
614614
} else {
615-
decl = self.parseDeclaration(allowMissingFuncOrVarKeywordRecovery: true)
615+
decl = self.parseDeclaration(inMemberDeclList: true)
616616
}
617617

618618
let semi = self.consume(if: .semicolon)
@@ -1694,7 +1694,7 @@ extension Parser {
16941694

16951695
// Parse getter and setter.
16961696
let accessor: RawSyntax?
1697-
if self.at(.leftBrace) {
1697+
if self.at(.leftBrace) || self.at(anyIn: AccessorKind.self) != nil {
16981698
accessor = self.parseGetSet()
16991699
} else {
17001700
accessor = nil
@@ -1725,10 +1725,22 @@ extension Parser {
17251725
/// pattern-initializer-list → pattern-initializer | pattern-initializer ',' pattern-initializer-list
17261726
/// pattern-initializer → pattern initializer?
17271727
/// initializer → = expression
1728+
///
1729+
/// If `inMemberDeclList` is `true`, we know that the next item needs to be a
1730+
/// declaration that is started by a keyword. Thus, we in the following case
1731+
/// we know that `set` can't start a new declaration and we can thus recover
1732+
/// by synthesizing a missing `{` in front of `set`.
1733+
/// ```
1734+
/// var x: Int
1735+
/// set {
1736+
/// }
1737+
/// }
1738+
/// ```
17281739
@_spi(RawSyntax)
17291740
public mutating func parseLetOrVarDeclaration(
17301741
_ attrs: DeclAttributes,
1731-
_ handle: RecoveryConsumptionHandle
1742+
_ handle: RecoveryConsumptionHandle,
1743+
inMemberDeclList: Bool = false
17321744
) -> RawVariableDeclSyntax {
17331745
let (unexpectedBeforeIntroducer, introducer) = self.eat(handle)
17341746
let hasTryBeforeIntroducer = unexpectedBeforeIntroducer?.containsToken(where: { $0.tokenKind == .tryKeyword }) ?? false
@@ -1790,7 +1802,7 @@ extension Parser {
17901802
}
17911803

17921804
let accessor: RawSyntax?
1793-
if self.at(.leftBrace) {
1805+
if self.at(.leftBrace) || (inMemberDeclList && self.at(anyIn: AccessorKind.self) != nil) {
17941806
accessor = self.parseGetSet()
17951807
} else {
17961808
accessor = nil
@@ -1816,21 +1828,6 @@ extension Parser {
18161828
arena: self.arena)
18171829
}
18181830

1819-
enum AccessorKind: SyntaxText, ContextualKeywords, Equatable {
1820-
case `get` = "get"
1821-
case `set` = "set"
1822-
case `didSet` = "didSet"
1823-
case `willSet` = "willSet"
1824-
case unsafeAddress = "unsafeAddress"
1825-
case addressWithOwner = "addressWithOwner"
1826-
case addressWithNativeOwner = "addressWithNativeOwner"
1827-
case unsafeMutableAddress = "unsafeMutableAddress"
1828-
case mutableAddressWithOwner = "mutableAddressWithOwner"
1829-
case mutableAddressWithNativeOwner = "mutableAddressWithNativeOwner"
1830-
case _read = "_read"
1831-
case _modify = "_modify"
1832-
}
1833-
18341831
struct AccessorIntroducer {
18351832
var attributes: RawAttributeListSyntax?
18361833
var modifier: RawDeclModifierSyntax?
@@ -1925,7 +1922,14 @@ extension Parser {
19251922
@_spi(RawSyntax)
19261923
public mutating func parseGetSet() -> RawSyntax {
19271924
// Parse getter and setter.
1928-
let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
1925+
let unexpectedBeforeLBrace: RawUnexpectedNodesSyntax?
1926+
let lbrace: RawTokenSyntax
1927+
if self.at(anyIn: AccessorKind.self) != nil {
1928+
unexpectedBeforeLBrace = nil
1929+
lbrace = missingToken(.leftBrace, text: nil)
1930+
} else {
1931+
(unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
1932+
}
19291933
// Collect all explicit accessors to a list.
19301934
var elements = [RawAccessorDeclSyntax]()
19311935
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)