Skip to content

Commit 1ecfa30

Browse files
authored
Merge pull request #748 from ahoppen/ahoppen/70-pound-if-recovery
[Recovery 7/7] Improve recovery in `#if` blocks
2 parents b03345b + 198d526 commit 1ecfa30

23 files changed

+2720
-1598
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 116 additions & 87 deletions
Large diffs are not rendered by default.

Sources/SwiftParser/Availability.swift

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ extension Parser {
3535
repeat {
3636
let entry: RawSyntax
3737
switch source {
38-
case .available where self.currentToken.isIdentifier,
39-
.unavailable where self.currentToken.isIdentifier:
38+
case .available where self.at(.identifier),
39+
.unavailable where self.at(.identifier):
4040
entry = RawSyntax(self.parseAvailabilityMacro())
4141
default:
4242
entry = self.parseAvailabilitySpec()
@@ -48,15 +48,12 @@ extension Parser {
4848

4949
// Before continuing to parse the next specification, we check that it's
5050
// also in the shorthand syntax and recover from it.
51-
if
52-
keepGoing != nil,
53-
self.currentToken.isIdentifier,
54-
AvailabilityArgumentKind(rawValue: self.currentToken.tokenText) != nil
55-
{
51+
if keepGoing != nil,
52+
let (_, handle) = self.at(anyIn: AvailabilityArgumentKind.self) {
5653
var tokens = [RawTokenSyntax]()
57-
tokens.append(self.consumeAnyToken())
54+
tokens.append(self.eat(handle))
5855
var recoveryProgress = LoopProgressCondition()
59-
while !self.at(.eof) && !self.at(.comma) && !self.at(.rightParen) && recoveryProgress.evaluate(currentToken) {
56+
while !self.at(any: [.eof, .comma, .rightParen]) && recoveryProgress.evaluate(currentToken) {
6057
tokens.append(self.consumeAnyToken())
6158
}
6259
let syntax = RawTokenListSyntax(elements: tokens, arena: self.arena)
@@ -70,7 +67,7 @@ extension Parser {
7067
return RawAvailabilitySpecListSyntax(elements: elements, arena: self.arena)
7168
}
7269

73-
enum AvailabilityArgumentKind: SyntaxText {
70+
enum AvailabilityArgumentKind: SyntaxText, ContextualKeywords {
7471
case message
7572
case renamed
7673
case introduced
@@ -92,21 +89,11 @@ extension Parser {
9289
do {
9390
var loopProgressCondition = LoopProgressCondition()
9491
while keepGoing != nil && loopProgressCondition.evaluate(currentToken) {
95-
guard self.currentToken.tokenKind == .identifier,
96-
let argKind = AvailabilityArgumentKind(rawValue: self.currentToken.tokenText) else {
97-
// Not sure what this label is but, let's just eat it and
98-
// keep going.
99-
let arg = self.consumeAnyToken()
100-
keepGoing = self.consume(if: .comma)
101-
elements.append(RawAvailabilityArgumentSyntax(
102-
entry: RawSyntax(arg), trailingComma: keepGoing, arena: self.arena))
103-
continue
104-
}
105-
10692
let entry: RawSyntax
107-
switch argKind {
108-
case .message, .renamed:
109-
let argumentLabel = self.consumeAnyToken()
93+
switch self.at(anyIn: AvailabilityArgumentKind.self) {
94+
case (.message, let handle)?,
95+
(.renamed, let handle)?:
96+
let argumentLabel = self.eat(handle)
11097
let (unexpectedBeforeColon, colon) = self.expect(.colon)
11198
// FIXME: Make sure this is a string literal with no interpolation.
11299
let stringValue = self.consumeAnyToken()
@@ -118,8 +105,9 @@ extension Parser {
118105
value: RawSyntax(stringValue),
119106
arena: self.arena
120107
))
121-
case .introduced, .obsoleted:
122-
let argumentLabel = self.consumeAnyToken()
108+
case (.introduced, let handle)?,
109+
(.obsoleted, let handle)?:
110+
let argumentLabel = self.eat(handle)
123111
let (unexpectedBeforeColon, colon) = self.expect(.colon)
124112
let version = self.parseVersionTuple()
125113
entry = RawSyntax(RawAvailabilityLabeledArgumentSyntax(
@@ -129,21 +117,29 @@ extension Parser {
129117
value: RawSyntax(version),
130118
arena: self.arena
131119
))
132-
case .deprecated:
133-
let argumentLabel = self.consumeAnyToken()
134-
if self.at(.colon) {
135-
let colon = self.eat(.colon)
120+
case (.deprecated, let handle)?:
121+
let argumentLabel = self.eat(handle)
122+
if let colon = self.consume(if: .colon) {
136123
let version = self.parseVersionTuple()
137124
entry = RawSyntax(RawAvailabilityLabeledArgumentSyntax(
138-
label: argumentLabel, colon: colon, value: RawSyntax(version), arena: self.arena))
125+
label: argumentLabel,
126+
colon: colon,
127+
value: RawSyntax(version),
128+
arena: self.arena
129+
))
139130
} else {
140131
entry = RawSyntax(argumentLabel)
141132
}
142-
case .unavailable, .noasync:
143-
let argument = self.consumeAnyToken()
133+
case (.unavailable, let handle)?,
134+
(.noasync, let handle)?:
135+
let argument = self.eat(handle)
144136
// FIXME: Can we model this in SwiftSyntax by making the
145137
// 'labeled' argument part optional?
146138
entry = RawSyntax(argument)
139+
case nil:
140+
// Not sure what this label is but, let's just eat it and
141+
// keep going.
142+
entry = RawSyntax(self.consumeAnyToken())
147143
}
148144

149145
keepGoing = self.consume(if: .comma)
@@ -162,15 +158,14 @@ extension Parser {
162158
/// availability-argument → platform-name platform-version
163159
/// availability-argument → *
164160
mutating func parseAvailabilitySpec() -> RawSyntax {
165-
if self.currentToken.isBinaryOperator && self.currentToken.tokenText == "*" {
166-
let star = self.consumeAnyToken()
161+
if let star = self.consumeIfContextualPunctuator("*") {
167162
// FIXME: Use makeAvailabilityVersionRestriction here - but swift-format
168163
// doesn't expect it.
169164
return RawSyntax(star)
170165
}
171166

172-
if self.currentToken.isIdentifier || self.at(.wildcardKeyword) {
173-
if self.currentToken.tokenText == "swift" || self.currentToken.tokenText == "_PackageDescription" {
167+
if self.at(any: [.identifier, .wildcardKeyword]) {
168+
if self.atContextualKeyword("swift") || self.atContextualKeyword("_PackageDescription") {
174169
return RawSyntax(self.parsePlatformAgnosticVersionConstraintSpec())
175170
}
176171
}
@@ -179,11 +174,14 @@ extension Parser {
179174
}
180175

181176
mutating func parsePlatformAgnosticVersionConstraintSpec() -> RawAvailabilityVersionRestrictionSyntax {
182-
assert(self.currentToken.isIdentifier || self.at(.wildcardKeyword))
183-
let platform = self.consumeAnyToken()
177+
let (unexpectedBeforePlatform, platform) = self.expectAny([.identifier, .wildcardKeyword], default: .identifier)
184178
let version = self.parseVersionTuple()
185179
return RawAvailabilityVersionRestrictionSyntax(
186-
platform: platform, version: version, arena: self.arena)
180+
unexpectedBeforePlatform,
181+
platform: platform,
182+
version: version,
183+
arena: self.arena
184+
)
187185
}
188186

189187
/// Parse a platform-specific version constraint.
@@ -202,7 +200,7 @@ extension Parser {
202200
/// platform-name → tvOS
203201
mutating func parsePlatformVersionConstraintSpec() -> RawAvailabilityVersionRestrictionSyntax {
204202
// Register the platform name as a keyword token.
205-
let plaform = self.consume(remapping: .contextualKeyword)
203+
let plaform = self.consumeAnyToken(remapping: .contextualKeyword)
206204
let version = self.parseVersionTuple()
207205
return RawAvailabilityVersionRestrictionSyntax(
208206
platform: plaform, version: version, arena: self.arena)
@@ -220,9 +218,9 @@ extension Parser {
220218
let platform = self.consumeAnyToken()
221219

222220
let version: RawVersionTupleSyntax?
223-
if case .integerLiteral = self.currentToken.tokenKind {
221+
if self.at(.integerLiteral) {
224222
version = self.parseVersionTuple()
225-
} else if case .floatingLiteral = self.currentToken.tokenKind {
223+
} else if self.at(.floatingLiteral) {
226224
version = self.parseVersionTuple()
227225
} else {
228226
version = nil
@@ -241,7 +239,7 @@ extension Parser {
241239
/// platform-version → decimal-digits '.' decimal-digits
242240
/// platform-version → decimal-digits '.' decimal-digits '.' decimal-digits
243241
mutating func parseVersionTuple() -> RawVersionTupleSyntax {
244-
if self.currentToken.tokenKind == .integerLiteral {
242+
if self.at(.integerLiteral) {
245243
let majorMinor = self.consumeAnyToken()
246244
return RawVersionTupleSyntax(
247245
majorMinor: RawSyntax(majorMinor), patchPeriod: nil, patchVersion: nil,

0 commit comments

Comments
 (0)