Skip to content

Commit 0f2cedb

Browse files
committed
[ASTGen] Implement #if config handling using the SwiftIfConfig library
Wherever there is a syntax collection in the syntax tree that can involve an `#if` clause, evaluate the `#if` conditions to find the active clause, then recurse into the active clause (if one exists) to "flatten" the translated collection to only contain active elements. Note that this does not yet handle #if for postfix expressions.
1 parent 03a0019 commit 0f2cedb

File tree

9 files changed

+240
-40
lines changed

9 files changed

+240
-40
lines changed

lib/ASTGen/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ endif()
2020

2121
add_pure_swift_host_library(swiftASTGen STATIC
2222
Sources/ASTGen/ASTGen.swift
23+
Sources/ASTGen/ASTGen+CompilerBuildConfiguration.swift
2324
Sources/ASTGen/Bridge.swift
2425
Sources/ASTGen/CompilerBuildConfiguration.swift
2526
Sources/ASTGen/DeclAttrs.swift
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//===--- ASTGen+CompilerBuildConfiguration.swift --------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024-2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftDiagnostics
14+
import SwiftIfConfig
15+
import SwiftSyntax
16+
17+
/// Enumeration that separates an #if config decl from the underlying element.
18+
enum IfConfigOrUnderlying<Element> {
19+
case ifConfigDecl(IfConfigDeclSyntax)
20+
case underlying(Element)
21+
}
22+
23+
extension ASTGenVisitor {
24+
/// Determine the active clause for the given #if, emitting any diagnostics
25+
/// produced due to the evaluation.
26+
func activeClause(in node: IfConfigDeclSyntax) -> IfConfigClauseSyntax? {
27+
// Determine the active clause.
28+
buildConfiguration.conditionLoc = generateSourceLoc(node)
29+
let (activeClause, diagnostics) = node.activeClause(in: buildConfiguration)
30+
diagnoseAll(diagnostics)
31+
32+
return activeClause
33+
}
34+
35+
/// Visit a collection of elements that may contain #if clauses within it
36+
/// within it, recursing into the active clauses and to ensure that every
37+
/// active element is visited.
38+
/// - Parameters:
39+
/// - elements: A syntax collection that can config `IfConfigDeclSyntax`
40+
/// nodes in it.
41+
/// - flatElementType: The element type within the syntax collection for the
42+
/// non-#if case.
43+
/// - split: Splits an element in elements into the two cases: #if config
44+
/// or a flat element.
45+
/// - body: The body that handles each syntax node of type FlatElement.
46+
func visitIfConfigElements<Elements, FlatElement>(
47+
_ elements: Elements,
48+
of flatElementType: FlatElement.Type,
49+
split: (Elements.Element) -> IfConfigOrUnderlying<FlatElement>,
50+
body: (FlatElement) -> Void
51+
) where Elements: SyntaxCollection, FlatElement: SyntaxProtocol {
52+
for element in elements {
53+
switch split(element) {
54+
case .ifConfigDecl(let ifConfigDecl):
55+
if let activeClause = self.activeClause(in: ifConfigDecl),
56+
let activeElements = activeClause.elements {
57+
guard let activeElements = activeElements._syntaxNode.as(Elements.self) else {
58+
fatalError("Parser produced invalid nesting of #if decls")
59+
}
60+
61+
visitIfConfigElements(
62+
activeElements,
63+
of: FlatElement.self,
64+
split: split,
65+
body: body
66+
)
67+
}
68+
69+
case .underlying(let element):
70+
body(element)
71+
}
72+
}
73+
}
74+
}
75+
76+

lib/ASTGen/Sources/ASTGen/ASTGen.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ struct ASTGenVisitor {
7676

7777
let ctx: BridgedASTContext
7878

79+
let buildConfiguration: CompilerBuildConfiguration
80+
7981
fileprivate let allocator: SwiftSyntax.BumpPtrAllocator = .init(initialSlabSize: 256)
8082

8183
/// Fallback legacy parser used when ASTGen doesn't have the generate(_:)
@@ -94,12 +96,20 @@ struct ASTGenVisitor {
9496
self.declContext = declContext
9597
self.ctx = astContext
9698
self.legacyParse = legacyParser
99+
self.buildConfiguration = CompilerBuildConfiguration(
100+
ctx: ctx,
101+
conditionLoc: BridgedSourceLoc(at: AbsolutePosition(utf8Offset: 0), in: sourceBuffer)
102+
)
97103
}
98104

99105
func generate(sourceFile node: SourceFileSyntax) -> [BridgedDecl] {
100106
var out = [BridgedDecl]()
101107

102-
for element in node.statements {
108+
visitIfConfigElements(
109+
node.statements,
110+
of: CodeBlockItemSyntax.self,
111+
split: Self.splitCodeBlockItemIfConfig
112+
) { element in
103113
let loc = self.generateSourceLoc(element)
104114
let swiftASTNodes = generate(codeBlockItem: element)
105115
switch swiftASTNodes {
@@ -260,6 +270,11 @@ extension ASTGenVisitor {
260270
diagnosticSeverity: diagnostic.diagMessage.severity
261271
)
262272
}
273+
274+
/// Emits the given diagnostics via the C++ diagnostic engine.
275+
func diagnoseAll(_ diagnostics: [Diagnostic]) {
276+
diagnostics.forEach(diagnose)
277+
}
263278
}
264279

265280
// Forwarding overloads that take optional syntax nodes. These are defined on demand to achieve a consistent

lib/ASTGen/Sources/ASTGen/CompilerBuildConfiguration.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ import SwiftSyntax
1818

1919
/// A build configuration that uses the compiler's ASTContext to answer
2020
/// queries.
21-
struct CompilerBuildConfiguration: BuildConfiguration {
21+
final class CompilerBuildConfiguration: BuildConfiguration {
2222
let ctx: BridgedASTContext
23-
let conditionLoc: BridgedSourceLoc
23+
var conditionLoc: BridgedSourceLoc
24+
25+
init(ctx: BridgedASTContext, conditionLoc: BridgedSourceLoc) {
26+
self.ctx = ctx
27+
self.conditionLoc = conditionLoc
28+
}
2429

2530
func isCustomConditionSet(name: String) throws -> Bool {
2631
var name = name

lib/ASTGen/Sources/ASTGen/DeclAttrs.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import ASTBridging
1414
import BasicBridging
1515
import SwiftDiagnostics
16+
import SwiftIfConfig
17+
1618
@_spi(ExperimentalLanguageFeatures) @_spi(RawSyntax) import SwiftSyntax
1719

1820
extension ASTGenVisitor {
@@ -52,17 +54,15 @@ extension ASTGenVisitor {
5254
}
5355

5456
// '@' attributes.
55-
for node in node.attributes {
56-
switch node {
57-
case .attribute(let node):
58-
addAttribute(self.generateDeclAttribute(attribute: node))
59-
case .ifConfigDecl:
60-
fatalError("unimplemented")
61-
#if RESILIENT_SWIFT_SYNTAX
62-
@unknown default:
63-
fatalError()
64-
#endif
57+
visitIfConfigElements(node.attributes, of: AttributeSyntax.self) { element in
58+
switch element {
59+
case .ifConfigDecl(let ifConfigDecl):
60+
return .ifConfigDecl(ifConfigDecl)
61+
case .attribute(let attribute):
62+
return .underlying(attribute)
6563
}
64+
} body: { attribute in
65+
addAttribute(self.generateDeclAttribute(attribute: attribute))
6666
}
6767

6868
func genStatic(node: DeclModifierSyntax, spelling: BridgedStaticSpelling) {

lib/ASTGen/Sources/ASTGen/Decls.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ extension ASTGenVisitor {
4141
case .functionDecl(let node):
4242
return self.generate(functionDecl: node).asDecl
4343
case .ifConfigDecl:
44-
break
44+
fatalError("Should have been handled by the caller")
4545
case .importDecl(let node):
4646
return self.generate(importDecl: node).asDecl
4747
case .initializerDecl(let node):
@@ -838,7 +838,19 @@ extension ASTGenVisitor {
838838
extension ASTGenVisitor {
839839
@inline(__always)
840840
func generate(memberBlockItemList node: MemberBlockItemListSyntax) -> BridgedArrayRef {
841-
node.lazy.map(self.generate).bridgedArray(in: self)
841+
var allBridged: [BridgedDecl] = []
842+
visitIfConfigElements(node, of: MemberBlockItemSyntax.self) { element in
843+
if let ifConfigDecl = element.decl.as(IfConfigDeclSyntax.self) {
844+
return .ifConfigDecl(ifConfigDecl)
845+
}
846+
847+
return .underlying(element)
848+
} body: { member in
849+
// TODO: Set semicolon loc.
850+
allBridged.append(self.generate(decl: member.decl))
851+
}
852+
853+
return allBridged.lazy.bridgedArray(in: self)
842854
}
843855

844856
@inline(__always)

lib/ASTGen/Sources/ASTGen/Stmts.swift

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,28 @@ extension ASTGenVisitor {
7777

7878
@inline(__always)
7979
func generate(codeBlockItemList node: CodeBlockItemListSyntax) -> BridgedArrayRef {
80-
node.lazy.map { self.generate(codeBlockItem: $0).bridged }.bridgedArray(in: self)
80+
var allItems: [BridgedASTNode] = []
81+
visitIfConfigElements(
82+
node,
83+
of: CodeBlockItemSyntax.self,
84+
split: Self.splitCodeBlockItemIfConfig
85+
) { codeBlockItem in
86+
allItems.append(self.generate(codeBlockItem: codeBlockItem).bridged)
87+
}
88+
89+
return allItems.lazy.bridgedArray(in: self)
90+
}
91+
92+
/// Function that splits a code block item into either an #if or the item.
93+
static func splitCodeBlockItemIfConfig(
94+
_ element: CodeBlockItemSyntax
95+
) -> IfConfigOrUnderlying<CodeBlockItemSyntax> {
96+
if case .decl(let decl) = element.item,
97+
let ifConfigDecl = decl.as(IfConfigDeclSyntax.self) {
98+
return .ifConfigDecl(ifConfigDecl)
99+
}
100+
101+
return .underlying(element)
81102
}
82103

83104
func generate(codeBlock node: CodeBlockSyntax) -> BridgedBraceStmt {
@@ -461,18 +482,21 @@ extension ASTGenVisitor {
461482
}
462483

463484
func generate(switchCaseList node: SwitchCaseListSyntax) -> BridgedArrayRef {
464-
return node.lazy.map({ node -> BridgedASTNode in
465-
switch node {
466-
case .switchCase(let node):
467-
return ASTNode.stmt(self.generate(switchCase: node).asStmt).bridged
468-
case .ifConfigDecl(_):
469-
fatalError("unimplemented")
470-
#if RESILIENT_SWIFT_SYNTAX
471-
@unknown default:
472-
fatalError()
473-
#endif
485+
var allBridgedCases: [BridgedASTNode] = []
486+
visitIfConfigElements(node, of: SwitchCaseSyntax.self) { element in
487+
switch element {
488+
case .ifConfigDecl(let ifConfigDecl):
489+
return .ifConfigDecl(ifConfigDecl)
490+
case .switchCase(let switchCase):
491+
return .underlying(switchCase)
474492
}
475-
}).bridgedArray(in: self)
493+
} body: { caseNode in
494+
allBridgedCases.append(
495+
ASTNode.stmt(self.generate(switchCase: caseNode).asStmt).bridged
496+
)
497+
}
498+
499+
return allBridgedCases.lazy.bridgedArray(in: self)
476500
}
477501

478502
func generateSwitchStmt(switchExpr node: SwitchExprSyntax, labelInfo: BridgedLabeledStmtInfo = nil)

lib/ASTGen/Sources/ASTGen/TypeAttrs.swift

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,16 @@ extension ASTGenVisitor {
2222
}
2323

2424
let attrs = BridgedTypeAttributes.new()
25-
for node in node.attributes {
26-
switch node {
27-
case .attribute(let node):
28-
guard let attr = self.generateTypeAttribute(attribute: node) else {
29-
continue
30-
}
31-
attrs.add(attr);
32-
case .ifConfigDecl:
33-
fatalError("unimplemented")
34-
#if RESILIENT_SWIFT_SYNTAX
35-
@unknown default:
36-
fatalError()
37-
#endif
25+
visitIfConfigElements(node.attributes, of: AttributeSyntax.self) { element in
26+
switch element {
27+
case .ifConfigDecl(let ifConfigDecl):
28+
return .ifConfigDecl(ifConfigDecl)
29+
case .attribute(let attribute):
30+
return .underlying(attribute)
31+
}
32+
} body: { attribute in
33+
if let attr = self.generateTypeAttribute(attribute: attribute) {
34+
attrs.add(attr)
3835
}
3936
}
4037

test/ASTGen/if_config.swift

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-feature ParserASTGen -DDISCARDABLE -DNONSENDABLE -swift-version 6
2+
/// -enable-experimental-feature requires an asserts build
3+
// REQUIRES: asserts
4+
5+
#if NOT_SET
6+
func f { } // FIXME: Error once the parser diagnostics generator knows to
7+
// evaluate the active clause.
8+
#endif
9+
10+
#if compiler(>=10.0)
11+
bad code is not diagnosed
12+
#endif
13+
14+
#if canImport(NoSuchModule)
15+
func g() { }
16+
#else
17+
func h() { }
18+
#endif
19+
20+
21+
func testH() {
22+
h()
23+
}
24+
25+
// Declaration attributes
26+
#if DISCARDABLE
27+
@discardableResult
28+
#endif
29+
func ignorable() -> Int { 5 }
30+
31+
32+
func testIgnorable() {
33+
ignorable() // no warning
34+
}
35+
36+
// Type attributes
37+
// FIXME: Something isn't getting converted properly when I try to put an #if
38+
// within the type attributes.
39+
typealias CallbackType = () -> Void
40+
41+
func acceptMaybeSendable(body: CallbackType) { }
42+
43+
func testMaybeSendable() {
44+
var x = 10
45+
acceptMaybeSendable {
46+
x += 1
47+
}
48+
print(x)
49+
}
50+
51+
// Switch cases
52+
enum SafetyError: Error {
53+
case unsafe
54+
55+
#if NONSENDABLE
56+
case nonSendable
57+
#endif
58+
}
59+
60+
extension SafetyError {
61+
var description: String {
62+
switch self {
63+
case .unsafe: "unsafe"
64+
65+
#if NONSENDABLE
66+
case .nonSendable: "non-sendable"
67+
#endif
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)