Skip to content

Commit b3a2458

Browse files
committed
Parse attributes in #if
Fixes #1119 rdar://103048943
1 parent 1159abd commit b3a2458

File tree

5 files changed

+48
-37
lines changed

5 files changed

+48
-37
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ extension TokenConsumer {
120120
return false
121121
}
122122
}
123+
124+
mutating func atStartOfIfConfigOfAttributes() -> Bool {
125+
guard self.at(.poundIfKeyword) else {
126+
return false
127+
}
128+
return self.withLookahead({ $0.consumeIfConfigOfAttributes() })
129+
}
123130
}
124131

125132
extension Parser {
@@ -165,6 +172,11 @@ extension Parser {
165172
public mutating func parseDeclaration(inMemberDeclList: Bool = false) -> RawDeclSyntax {
166173
switch self.at(anyIn: PoundDeclarationStart.self) {
167174
case (.poundIfKeyword, _)?:
175+
if self.withLookahead({ $0.consumeIfConfigOfAttributes() }) {
176+
// If we are at a `#if` of attributes, the `#if` directive should be
177+
// parsed when we're parsing the attributes.
178+
break
179+
}
168180
let directive = self.parsePoundIfDirective { parser in
169181
let parsedDecl = parser.parseDeclaration()
170182
let semicolon = parser.consume(if: .semicolon)

Sources/SwiftParser/Lookahead.swift

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -207,42 +207,35 @@ extension Parser.Lookahead {
207207
return true
208208
}
209209

210+
/// Tries consuming a `#if` directive that only contains attributes.
211+
/// Returns `true` if that was successful and `false` if
212+
/// - we are not at a valid `#if` directive (e.g. `#elseif` is missing)
213+
/// - the directive contained non-attributes
214+
/// - the directive did not contain any attributes
210215
mutating func consumeIfConfigOfAttributes() -> Bool {
211-
while true {
212-
// #if / #else / #elseif
213-
self.consumeAnyToken()
216+
var didSeeAnyAttributes = false
217+
var poundIfLoopProgress = LoopProgressCondition()
218+
repeat {
219+
self.consume(ifAny: [.poundIfKeyword, .poundElseKeyword, .poundEndifKeyword])
214220

215-
// <expression>
221+
// <expression> after `#if` or `#elseif`
216222
self.skipUntilEndOfLine()
217223

218-
while true {
219-
if self.at(.atSign) {
224+
var attributesLoopProgress = LoopProgressCondition()
225+
ATTRIBUTE_LOOP: while attributesLoopProgress.evaluate(self.currentToken) {
226+
switch self.currentToken.rawTokenKind {
227+
case .atSign:
228+
didSeeAnyAttributes = true
220229
_ = self.consumeAttributeList()
221-
continue
222-
}
223-
224-
if self.at(.poundIfKeyword) {
230+
case .poundIfKeyword:
225231
_ = self.consumeIfConfigOfAttributes()
226-
continue
232+
default:
233+
break ATTRIBUTE_LOOP
227234
}
228-
229-
break
230-
}
231-
232-
guard self.at(any: [.poundElseifKeyword, .poundElseKeyword]) else {
233-
break
234235
}
235-
}
236-
237-
// If we ran out of tokens, say we consumed the rest.
238-
if self.at(.eof) {
239-
return true
240-
}
236+
} while self.at(any: [.poundElseifKeyword, .poundElseKeyword]) && poundIfLoopProgress.evaluate(self.currentToken)
241237

242-
guard self.currentToken.isAtStartOfLine else {
243-
return false
244-
}
245-
return self.consume(if: .poundEndifKeyword) != nil
238+
return didSeeAnyAttributes && self.currentToken.isAtStartOfLine && self.consume(if: .poundEndifKeyword) != nil
246239
}
247240
}
248241

Sources/SwiftParser/TopLevel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ extension Parser {
195195
/// If we are not at the top level, such a closing brace should close the
196196
/// wrapping declaration instead of being consumed by lookeahead.
197197
private mutating func parseItem(isAtTopLevel: Bool = false, allowInitDecl: Bool = true) -> RawCodeBlockItemSyntax.Item {
198-
if self.at(.poundIfKeyword) {
198+
if self.at(.poundIfKeyword) && self.withLookahead({ $0.consumeIfConfigOfAttributes() }) {
199199
let directive = self.parsePoundIfDirective {
200200
$0.parseCodeBlockItem()
201201
} addSemicolonIfNeeded: { lastElement, newItemAtStartOfLine, parser in

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,6 +1371,17 @@ final class DeclarationTests: XCTestCase {
13711371
)
13721372
)
13731373
}
1374+
1375+
func testAttributeInPoundIf() {
1376+
AssertParse(
1377+
"""
1378+
#if hasAttribute(foo)
1379+
@foo
1380+
#endif
1381+
struct MyStruct {}
1382+
"""
1383+
)
1384+
}
13741385
}
13751386

13761387
extension Parser.DeclAttributes {

Tests/SwiftParserTest/DirectiveTests.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,25 +147,20 @@ final class DirectiveTests: XCTestCase {
147147
public struct S2 { }
148148
149149
#if hasAttribute(foo)
150-
@foo1️⃣
150+
@foo
151151
#endif
152152
@inlinable
153153
func f1() { }
154154
155155
#if hasAttribute(foo)
156-
@foo2️⃣
156+
@foo
157157
#else
158158
@available(*, deprecated, message: "nope")
159-
@frozen3️⃣
159+
@frozen
160160
#endif
161161
public struct S3 { }
162162
}
163-
""",
164-
diagnostics: [
165-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected declaration after attribute in conditional compilation clause"),
166-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected declaration after attribute in conditional compilation clause"),
167-
DiagnosticSpec(locationMarker: "3️⃣", message: "expected declaration after attribute in conditional compilation clause"),
168-
]
163+
"""
169164
)
170165
}
171166

0 commit comments

Comments
 (0)