Skip to content

Commit 92ebf8e

Browse files
authored
Merge pull request #2759 from DougGregor/swift-if-config-cleanup
Address most of the code review comments on the SwiftIfConfig library
2 parents 9869b70 + 4158c46 commit 92ebf8e

21 files changed

+380
-362
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ let package = Package(
143143

144144
.target(
145145
name: "SwiftIfConfig",
146-
dependencies: ["SwiftSyntax", "SwiftOperators"],
146+
dependencies: ["SwiftSyntax", "SwiftDiagnostics", "SwiftOperators"],
147147
exclude: ["CMakeLists.txt"]
148148
),
149149

Release Notes/601.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
- Description: This method translates an error into one or more diagnostics, recognizing `DiagnosticsError` and `DiagnosticMessage` instances or providing its own `Diagnostic` as needed.
1616
- Pull Request: https://github.com/swiftlang/swift-syntax/pull/1816
1717

18+
- Added a new library `SwiftIfConfig`.
19+
- Description: This new library provides facilities for evaluating `#if` conditions and determining which regions of a syntax tree are active according to a given build configuration.
20+
- Pull Request: https://github.com/swiftlang/swift-syntax/pull/1816
21+
1822
## API Behavior Changes
1923

2024
- `SyntaxProtocol.trimmed` detaches the node

Sources/SwiftIfConfig/ActiveSyntaxRewriter.swift

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,6 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
//
14-
// This file defines the SyntaxRewriter, a class that performs a standard walk
15-
// and tree-rebuilding pattern.
16-
//
17-
// Subclassers of this class can override the walking behavior for any syntax
18-
// node and transform nodes however they like.
19-
//
20-
//===----------------------------------------------------------------------===//
21-
2213
import SwiftDiagnostics
2314
import SwiftSyntax
2415

@@ -34,15 +25,17 @@ extension SyntaxProtocol {
3425
/// clauses, e.g., `#if FOO > 10`, then the condition will be
3526
/// considered to have failed and the clauses's elements will be
3627
/// removed.
37-
public func removingInactive(in configuration: some BuildConfiguration) -> (Syntax, [Diagnostic]) {
28+
public func removingInactive(
29+
in configuration: some BuildConfiguration
30+
) -> (result: Syntax, diagnostics: [Diagnostic]) {
3831
// First pass: Find all of the active clauses for the #ifs we need to
3932
// visit, along with any diagnostics produced along the way. This process
4033
// does not change the tree in any way.
4134
let visitor = ActiveSyntaxVisitor(viewMode: .sourceAccurate, configuration: configuration)
4235
visitor.walk(self)
4336

4437
// If there were no active clauses to visit, we're done!
45-
if visitor.numIfClausesVisited == 0 {
38+
if !visitor.visitedAnyIfClauses {
4639
return (Syntax(self), visitor.diagnostics)
4740
}
4841

@@ -88,12 +81,13 @@ extension SyntaxProtocol {
8881
/// than trivia).
8982
class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
9083
let configuration: Configuration
84+
var diagnostics: [Diagnostic] = []
9185

9286
init(configuration: Configuration) {
9387
self.configuration = configuration
9488
}
9589

96-
private func dropInactive<List: Collection & SyntaxCollection>(
90+
private func dropInactive<List: SyntaxCollection>(
9791
_ node: List,
9892
elementAsIfConfig: (List.Element) -> IfConfigDeclSyntax?
9993
) -> List {
@@ -105,7 +99,10 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
10599
// Find #ifs within the list.
106100
if let ifConfigDecl = elementAsIfConfig(element) {
107101
// Retrieve the active `#if` clause
108-
let activeClause = ifConfigDecl.activeClause(in: configuration)
102+
let (activeClause, localDiagnostics) = ifConfigDecl.activeClause(in: configuration)
103+
104+
// Add these diagnostics.
105+
diagnostics.append(contentsOf: localDiagnostics)
109106

110107
// If this is the first element that changed, note that we have
111108
// changes and add all prior elements to the list of new elements.
@@ -255,7 +252,8 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
255252
return dropInactive(outerBase: base, postfixIfConfig: postfixIfConfig)
256253
}
257254

258-
preconditionFailure("Unhandled postfix expression in #if elimination")
255+
assertionFailure("Unhandled postfix expression in #if elimination")
256+
return postfix
259257
}
260258

261259
/// Drop inactive regions from a postfix `#if` configuration, applying the
@@ -265,7 +263,10 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
265263
postfixIfConfig: PostfixIfConfigExprSyntax
266264
) -> ExprSyntax {
267265
// Retrieve the active `if` clause.
268-
let activeClause = postfixIfConfig.config.activeClause(in: configuration)
266+
let (activeClause, localDiagnostics) = postfixIfConfig.config.activeClause(in: configuration)
267+
268+
// Record these diagnostics.
269+
diagnostics.append(contentsOf: localDiagnostics)
269270

270271
guard case .postfixExpression(let postfixExpr) = activeClause?.elements
271272
else {

Sources/SwiftIfConfig/ActiveSyntaxVisitor.swift

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12+
1213
import SwiftDiagnostics
1314
import SwiftSyntax
1415

@@ -36,34 +37,35 @@ import SwiftSyntax
3637
/// it would not visit either `f` or `g`.
3738
///
3839
/// All notes visited by this visitor will have the "active" state, i.e.,
39-
/// `node.isActive(in: configuration)` will evaluate to `.active` or will
40-
/// throw. When errors occur, they will be recorded in the set of
40+
/// `node.isActive(in: configuration)` will have evaluated to `.active`
41+
/// When errors occur, they will be recorded in the set of
4142
/// diagnostics.
4243
open class ActiveSyntaxVisitor<Configuration: BuildConfiguration>: SyntaxVisitor {
4344
/// The build configuration, which will be queried for each relevant `#if`.
4445
public let configuration: Configuration
4546

46-
/// The set of diagnostics accumulated during this walk of active syntax.
47-
public var diagnostics: [Diagnostic] = []
47+
/// The diagnostics accumulated during this walk of active syntax.
48+
public private(set) var diagnostics: [Diagnostic] = []
4849

49-
/// The number of "#if" clauses that were visited.
50-
var numIfClausesVisited: Int = 0
50+
/// Whether we visited any "#if" clauses.
51+
var visitedAnyIfClauses: Bool = false
5152

5253
public init(viewMode: SyntaxTreeViewMode, configuration: Configuration) {
5354
self.configuration = configuration
5455
super.init(viewMode: viewMode)
5556
}
5657

5758
open override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind {
58-
let activeClause = node.activeClause(in: configuration) { diag in
59-
self.diagnostics.append(diag)
60-
}
59+
// Note: there is a clone of this code in ActiveSyntaxAnyVisitor. If you
60+
// change one, please also change the other.
61+
let (activeClause, localDiagnostics) = node.activeClause(in: configuration)
62+
diagnostics.append(contentsOf: localDiagnostics)
6163

62-
numIfClausesVisited += 1
64+
visitedAnyIfClauses = true
6365

6466
// If there is an active clause, visit it's children.
6567
if let activeClause, let elements = activeClause.elements {
66-
walk(Syntax(elements))
68+
walk(elements)
6769
}
6870

6971
// Skip everything else in the #if.
@@ -95,32 +97,30 @@ open class ActiveSyntaxVisitor<Configuration: BuildConfiguration>: SyntaxVisitor
9597
/// it would not visit either `f` or `g`.
9698
///
9799
/// All notes visited by this visitor will have the "active" state, i.e.,
98-
/// `node.isActive(in: configuration)` will evaluate to `.active` or will
99-
/// throw.
100-
///
101-
/// All notes visited by this visitor will have the "active" state, i.e.,
102-
/// `node.isActive(in: configuration)` will evaluate to `.active` or will
103-
/// throw. When errors occur, they will be recorded in the set of
104-
/// diagnostivs.
100+
/// `node.isActive(in: configuration)` will have evaluated to `.active`.
101+
/// When errors occur, they will be recorded in the array of diagnostics.
105102
open class ActiveSyntaxAnyVisitor<Configuration: BuildConfiguration>: SyntaxAnyVisitor {
106103
/// The build configuration, which will be queried for each relevant `#if`.
107104
public let configuration: Configuration
108105

109-
/// The set of diagnostics accumulated during this walk of active syntax.
110-
public var diagnostics: [Diagnostic] = []
106+
/// The diagnostics accumulated during this walk of active syntax.
107+
public private(set) var diagnostics: [Diagnostic] = []
111108

112109
public init(viewMode: SyntaxTreeViewMode, configuration: Configuration) {
113110
self.configuration = configuration
114111
super.init(viewMode: viewMode)
115112
}
116113

117114
open override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind {
115+
// Note: there is a clone of this code in ActiveSyntaxVisitor. If you
116+
// change one, please also change the other.
117+
118118
// If there is an active clause, visit it's children.
119-
let activeClause = node.activeClause(in: configuration) { diag in
120-
self.diagnostics.append(diag)
121-
}
119+
let (activeClause, localDiagnostics) = node.activeClause(in: configuration)
120+
diagnostics.append(contentsOf: localDiagnostics)
121+
122122
if let activeClause, let elements = activeClause.elements {
123-
walk(Syntax(elements))
123+
walk(elements)
124124
}
125125

126126
// Skip everything else in the #if.

Sources/SwiftIfConfig/BuildConfiguration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ public protocol BuildConfiguration {
253253
///
254254
/// The language version can be queried with the `swift` directive that checks
255255
/// how the supported language version compares, as described by
256-
/// [SE-0212](https://github.com/apple/swift-evolution/blob/main/proposals/0212-compiler-version-directive.md). For example:
256+
/// [SE-0212](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0212-compiler-version-directive.md). For example:
257257
///
258258
/// ```swift
259259
/// #if swift(>=5.5)

Sources/SwiftIfConfig/CMakeLists.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ add_swift_syntax_library(SwiftIfConfig
1111
ActiveSyntaxRewriter.swift
1212
BuildConfiguration.swift
1313
ConfiguredRegions.swift
14-
ConfiguredRegionState.swift
14+
IfConfigRegionState.swift
1515
IfConfigDecl+IfConfig.swift
1616
IfConfigError.swift
1717
IfConfigEvaluation.swift
@@ -25,5 +25,4 @@ add_swift_syntax_library(SwiftIfConfig
2525
target_link_swift_syntax_libraries(SwiftIfConfig PUBLIC
2626
SwiftSyntax
2727
SwiftDiagnostics
28-
SwiftOperators
29-
SwiftParser)
28+
SwiftOperators)

Sources/SwiftIfConfig/ConfiguredRegions.swift

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12+
1213
import SwiftDiagnostics
1314
import SwiftSyntax
1415

1516
extension SyntaxProtocol {
1617
/// Find all of the #if/#elseif/#else clauses within the given syntax node,
17-
/// indicating their active state. This operation will recurse into active
18-
/// clauses to represent the flattened nested structure, while nonactive
19-
/// clauses need no recursion (because there is no relevant structure in
20-
/// them).
18+
/// indicating their active state. This operation will recurse into all
19+
/// clauses to indicate regions of active / inactive / unparsed code.
2120
///
2221
/// For example, given code like the following:
2322
/// #if DEBUG
@@ -37,7 +36,7 @@ extension SyntaxProtocol {
3736
/// - Inactive region for the final `#else`.
3837
public func configuredRegions(
3938
in configuration: some BuildConfiguration
40-
) -> [(IfConfigClauseSyntax, ConfiguredRegionState)] {
39+
) -> [(IfConfigClauseSyntax, IfConfigRegionState)] {
4140
let visitor = ConfiguredRegionVisitor(configuration: configuration)
4241
visitor.walk(self)
4342
return visitor.regions
@@ -49,7 +48,7 @@ fileprivate class ConfiguredRegionVisitor<Configuration: BuildConfiguration>: Sy
4948
let configuration: Configuration
5049

5150
/// The regions we've found so far.
52-
var regions: [(IfConfigClauseSyntax, ConfiguredRegionState)] = []
51+
var regions: [(IfConfigClauseSyntax, IfConfigRegionState)] = []
5352

5453
/// Whether we are currently within an active region.
5554
var inActiveRegion = true
@@ -62,7 +61,7 @@ fileprivate class ConfiguredRegionVisitor<Configuration: BuildConfiguration>: Sy
6261
override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind {
6362
// If we're in an active region, find the active clause. Otherwise,
6463
// there isn't one.
65-
let activeClause = inActiveRegion ? node.activeClause(in: configuration) : nil
64+
let activeClause = inActiveRegion ? node.activeClause(in: configuration).clause : nil
6665
for clause in node.clauses {
6766
// If this is the active clause, record it and then recurse into the
6867
// elements.
@@ -79,11 +78,9 @@ fileprivate class ConfiguredRegionVisitor<Configuration: BuildConfiguration>: Sy
7978
}
8079

8180
// For inactive clauses, distinguish between inactive and unparsed.
82-
let isVersioned =
83-
(try? clause.isVersioned(
84-
configuration: configuration,
85-
diagnosticHandler: nil
86-
)) ?? true
81+
let isVersioned = clause.isVersioned(
82+
configuration: configuration
83+
).versioned
8784

8885
// If this is within an active region, or this is an unparsed region,
8986
// record it.

Sources/SwiftIfConfig/IfConfigDecl+IfConfig.swift

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12+
1213
import SwiftDiagnostics
1314
import SwiftSyntax
1415

@@ -25,35 +26,35 @@ extension IfConfigDeclSyntax {
2526
/// ```
2627
///
2728
/// If the `A` configuration option was passed on the command line (e.g. via `-DA`), the first clause
28-
/// (containing `func f()`) would be returned. If not, and if the `B`configuration was passed on the
29+
/// (containing `func f()`) would be returned. If not, and if the `B` configuration was passed on the
2930
/// command line, the second clause (containing `func g()`) would be returned. If neither was
3031
/// passed, this function will return `nil` to indicate that none of the regions are active.
3132
///
32-
/// If an error occurrs while processing any of the `#if` clauses,
33+
/// If an error occurs while processing any of the `#if` clauses,
3334
/// that clause will be considered inactive and this operation will
3435
/// continue to evaluate later clauses.
3536
public func activeClause(
36-
in configuration: some BuildConfiguration,
37-
diagnosticHandler: ((Diagnostic) -> Void)? = nil
38-
) -> IfConfigClauseSyntax? {
37+
in configuration: some BuildConfiguration
38+
) -> (clause: IfConfigClauseSyntax?, diagnostics: [Diagnostic]) {
39+
var diagnostics: [Diagnostic] = []
3940
for clause in clauses {
4041
// If there is no condition, we have reached an unconditional clause. Return it.
4142
guard let condition = clause.condition else {
42-
return clause
43+
return (clause, diagnostics: diagnostics)
4344
}
4445

4546
// If this condition evaluates true, return this clause.
46-
let isActive =
47-
(try? evaluateIfConfig(
48-
condition: condition,
49-
configuration: configuration,
50-
diagnosticHandler: diagnosticHandler
51-
))?.active ?? false
47+
let (isActive, _, localDiagnostics) = evaluateIfConfig(
48+
condition: condition,
49+
configuration: configuration
50+
)
51+
diagnostics.append(contentsOf: localDiagnostics)
52+
5253
if isActive {
53-
return clause
54+
return (clause, diagnostics: diagnostics)
5455
}
5556
}
5657

57-
return nil
58+
return (nil, diagnostics: diagnostics)
5859
}
5960
}

Sources/SwiftIfConfig/IfConfigError.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12+
1213
import SwiftDiagnostics
1314
import SwiftSyntax
1415

0 commit comments

Comments
 (0)