Skip to content

Parse attributes in #if #1276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sources/SwiftParser/Declarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ extension Parser {
public mutating func parseDeclaration(inMemberDeclList: Bool = false) -> RawDeclSyntax {
switch self.at(anyIn: PoundDeclarationStart.self) {
case (.poundIfKeyword, _)?:
if self.withLookahead({ $0.consumeIfConfigOfAttributes() }) {
// If we are at a `#if` of attributes, the `#if` directive should be
// parsed when we're parsing the attributes.
break
}
let directive = self.parsePoundIfDirective { parser in
let parsedDecl = parser.parseDeclaration()
let semicolon = parser.consume(if: .semicolon)
Expand Down
47 changes: 21 additions & 26 deletions Sources/SwiftParser/Lookahead.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,42 +207,37 @@ extension Parser.Lookahead {
return true
}

/// Tries consuming a `#if` directive that only contains attributes.
/// Returns `true` if that was successful and `false` if
/// - we are not at a valid `#if` directive (e.g. `#endif` is missing)
/// - the directive contained non-attributes
/// - the directive did not contain any attributes
mutating func consumeIfConfigOfAttributes() -> Bool {
while true {
// #if / #else / #elseif
assert(self.at(.poundIfKeyword))
var didSeeAnyAttributes = false
var poundIfLoopProgress = LoopProgressCondition()
repeat {
assert(self.at(any: [.poundIfKeyword, .poundElseKeyword, .poundElseifKeyword]))
self.consumeAnyToken()

// <expression>
// <expression> after `#if` or `#elseif`
self.skipUntilEndOfLine()

while true {
if self.at(.atSign) {
var attributesLoopProgress = LoopProgressCondition()
ATTRIBUTE_LOOP: while attributesLoopProgress.evaluate(self.currentToken) {
switch self.currentToken.rawTokenKind {
case .atSign:
didSeeAnyAttributes = true
_ = self.consumeAttributeList()
continue
}

if self.at(.poundIfKeyword) {
case .poundIfKeyword:
_ = self.consumeIfConfigOfAttributes()
continue
default:
break ATTRIBUTE_LOOP
}

break
}
} while self.at(any: [.poundElseifKeyword, .poundElseKeyword]) && poundIfLoopProgress.evaluate(self.currentToken)

guard self.at(any: [.poundElseifKeyword, .poundElseKeyword]) else {
break
}
}

// If we ran out of tokens, say we consumed the rest.
if self.at(.eof) {
return true
}

guard self.currentToken.isAtStartOfLine else {
return false
}
return self.consume(if: .poundEndifKeyword) != nil
return didSeeAnyAttributes && self.currentToken.isAtStartOfLine && self.consume(if: .poundEndifKeyword) != nil
}
}

Expand Down
4 changes: 3 additions & 1 deletion Sources/SwiftParser/TopLevel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ extension Parser {
/// If we are not at the top level, such a closing brace should close the
/// wrapping declaration instead of being consumed by lookeahead.
private mutating func parseItem(isAtTopLevel: Bool = false, allowInitDecl: Bool = true) -> RawCodeBlockItemSyntax.Item {
if self.at(.poundIfKeyword) {
if self.at(.poundIfKeyword) && !self.withLookahead({ $0.consumeIfConfigOfAttributes() }) {
// If config of attributes is parsed as part of declaration parsing as it
// doesn't constitute its own code block item.
let directive = self.parsePoundIfDirective {
$0.parseCodeBlockItem()
} addSemicolonIfNeeded: { lastElement, newItemAtStartOfLine, parser in
Expand Down
11 changes: 11 additions & 0 deletions Tests/SwiftParserTest/DeclarationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1371,6 +1371,17 @@ final class DeclarationTests: XCTestCase {
)
)
}

func testAttributeInPoundIf() {
AssertParse(
"""
#if hasAttribute(foo)
@foo
#endif
struct MyStruct {}
"""
)
}
}

extension Parser.DeclAttributes {
Expand Down
13 changes: 4 additions & 9 deletions Tests/SwiftParserTest/DirectiveTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,25 +147,20 @@ final class DirectiveTests: XCTestCase {
public struct S2 { }
#if hasAttribute(foo)
@foo1️⃣
@foo
#endif
@inlinable
func f1() { }
#if hasAttribute(foo)
@foo2️⃣
@foo
#else
@available(*, deprecated, message: "nope")
@frozen3️⃣
@frozen
#endif
public struct S3 { }
}
""",
diagnostics: [
DiagnosticSpec(locationMarker: "1️⃣", message: "expected declaration after attribute in conditional compilation clause"),
DiagnosticSpec(locationMarker: "2️⃣", message: "expected declaration after attribute in conditional compilation clause"),
DiagnosticSpec(locationMarker: "3️⃣", message: "expected declaration after attribute in conditional compilation clause"),
]
"""
)
}

Expand Down