Skip to content

Commit 0381f9c

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

File tree

5 files changed

+44
-36
lines changed

5 files changed

+44
-36
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ extension Parser {
165165
public mutating func parseDeclaration(inMemberDeclList: Bool = false) -> RawDeclSyntax {
166166
switch self.at(anyIn: PoundDeclarationStart.self) {
167167
case (.poundIfKeyword, _)?:
168+
if self.withLookahead({ $0.consumeIfConfigOfAttributes() }) {
169+
// If we are at a `#if` of attributes, the `#if` directive should be
170+
// parsed when we're parsing the attributes.
171+
break
172+
}
168173
let directive = self.parsePoundIfDirective { parser in
169174
let parsedDecl = parser.parseDeclaration()
170175
let semicolon = parser.consume(if: .semicolon)

Sources/SwiftParser/Lookahead.swift

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -207,42 +207,37 @@ 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. `#endif` 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
216+
assert(self.at(.poundIfKeyword))
217+
var didSeeAnyAttributes = false
218+
var poundIfLoopProgress = LoopProgressCondition()
219+
repeat {
220+
assert(self.at(any: [.poundIfKeyword, .poundElseKeyword, .poundElseifKeyword]))
213221
self.consumeAnyToken()
214222

215-
// <expression>
223+
// <expression> after `#if` or `#elseif`
216224
self.skipUntilEndOfLine()
217225

218-
while true {
219-
if self.at(.atSign) {
226+
var attributesLoopProgress = LoopProgressCondition()
227+
ATTRIBUTE_LOOP: while attributesLoopProgress.evaluate(self.currentToken) {
228+
switch self.currentToken.rawTokenKind {
229+
case .atSign:
230+
didSeeAnyAttributes = true
220231
_ = self.consumeAttributeList()
221-
continue
222-
}
223-
224-
if self.at(.poundIfKeyword) {
232+
case .poundIfKeyword:
225233
_ = self.consumeIfConfigOfAttributes()
226-
continue
234+
default:
235+
break ATTRIBUTE_LOOP
227236
}
228-
229-
break
230237
}
238+
} while self.at(any: [.poundElseifKeyword, .poundElseKeyword]) && poundIfLoopProgress.evaluate(self.currentToken)
231239

232-
guard self.at(any: [.poundElseifKeyword, .poundElseKeyword]) else {
233-
break
234-
}
235-
}
236-
237-
// If we ran out of tokens, say we consumed the rest.
238-
if self.at(.eof) {
239-
return true
240-
}
241-
242-
guard self.currentToken.isAtStartOfLine else {
243-
return false
244-
}
245-
return self.consume(if: .poundEndifKeyword) != nil
240+
return didSeeAnyAttributes && self.currentToken.isAtStartOfLine && self.consume(if: .poundEndifKeyword) != nil
246241
}
247242
}
248243

Sources/SwiftParser/TopLevel.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,9 @@ 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() }) {
199+
// If config of attributes is parsed as part of declaration parsing as it
200+
// doesn't constitute its own code block item.
199201
let directive = self.parsePoundIfDirective {
200202
$0.parseCodeBlockItem()
201203
} 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)