Skip to content

Commit d50749f

Browse files
committed
Teach the ActiveSyntaxRewriter to use either ConfiguredRegions or BuildConfiguration
Instead of always forcing the construction of ConfiguredRegions ahead of time, introduce the notion of an ActiveClauseEvaluator that stores either configured regions or a build configuration, and can query for the active clause within a given #if declaration from either representation. Use this in ActiveSyntaxRewriter to make the operation itself single-pass, always. Always test both paths to make sure they stay in sync.
1 parent 5fba646 commit d50749f

File tree

4 files changed

+120
-41
lines changed

4 files changed

+120
-41
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 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 SwiftSyntax
15+
16+
/// Captures sufficient information to determine the active clause for an `#if`
17+
/// either by querying existing configured regions or by evaluating the
18+
/// clause's conditions against a build configuration.
19+
enum ActiveClauseEvaluator {
20+
case configuredRegions(ConfiguredRegions)
21+
case configuration(any BuildConfiguration)
22+
23+
/// Previously-known diagnostics.
24+
var priorDiagnostics: [Diagnostic] {
25+
switch self {
26+
case .configuredRegions(let configuredRegions):
27+
return configuredRegions.diagnostics
28+
case .configuration:
29+
return []
30+
}
31+
}
32+
33+
/// Determine which clause of an `#if` declaration is active, if any.
34+
///
35+
/// If this evaluation produced any diagnostics, they will be appended to
36+
/// the diagnostics parameter.
37+
func activeClause(
38+
for node: IfConfigDeclSyntax,
39+
diagnostics: inout [Diagnostic]
40+
) -> IfConfigClauseSyntax? {
41+
switch self {
42+
case .configuredRegions(let configuredRegions):
43+
return configuredRegions.activeClause(for: node)
44+
case .configuration(let configuration):
45+
let (activeClause, localDiagnostics) = node.activeClause(in: configuration)
46+
diagnostics.append(contentsOf: localDiagnostics)
47+
return activeClause
48+
}
49+
}
50+
}

Sources/SwiftIfConfig/ActiveSyntaxRewriter.swift

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@ extension SyntaxProtocol {
5252
in configuration: some BuildConfiguration,
5353
retainFeatureCheckIfConfigs: Bool
5454
) -> (result: Syntax, diagnostics: [Diagnostic]) {
55-
let configuredRegions = configuredRegions(in: configuration)
55+
// Rewrite the syntax tree by removing the inactive clauses
56+
// from each #if (along with the #ifs themselves).
57+
let rewriter = ActiveSyntaxRewriter(
58+
configuration: configuration,
59+
retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
60+
)
5661
return (
57-
result: configuredRegions.removingInactive(
58-
from: self,
59-
retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
60-
),
61-
diagnostics: configuredRegions.diagnostics
62+
rewriter.rewrite(Syntax(self)),
63+
rewriter.diagnostics
6264
)
6365
}
6466
}
@@ -144,15 +146,21 @@ extension ConfiguredRegions {
144146
/// For any other target platforms, the resulting tree will be empty (other
145147
/// than trivia).
146148
class ActiveSyntaxRewriter: SyntaxRewriter {
147-
let configuredRegions: ConfiguredRegions
149+
let activeClauses: ActiveClauseEvaluator
148150
var diagnostics: [Diagnostic]
149151

150152
/// Whether to retain `#if` blocks containing compiler and feature checks.
151153
var retainFeatureCheckIfConfigs: Bool
152154

153155
init(configuredRegions: ConfiguredRegions, retainFeatureCheckIfConfigs: Bool) {
154-
self.configuredRegions = configuredRegions
155-
self.diagnostics = configuredRegions.diagnostics
156+
self.activeClauses = .configuredRegions(configuredRegions)
157+
self.diagnostics = activeClauses.priorDiagnostics
158+
self.retainFeatureCheckIfConfigs = retainFeatureCheckIfConfigs
159+
}
160+
161+
init(configuration: some BuildConfiguration, retainFeatureCheckIfConfigs: Bool) {
162+
self.activeClauses = .configuration(configuration)
163+
self.diagnostics = activeClauses.priorDiagnostics
156164
self.retainFeatureCheckIfConfigs = retainFeatureCheckIfConfigs
157165
}
158166

@@ -183,7 +191,7 @@ class ActiveSyntaxRewriter: SyntaxRewriter {
183191
(!retainFeatureCheckIfConfigs || !ifConfigDecl.containsFeatureCheck)
184192
{
185193
// Retrieve the active `#if` clause
186-
let activeClause = configuredRegions.activeClause(for: ifConfigDecl)
194+
let activeClause = activeClauses.activeClause(for: ifConfigDecl, diagnostics: &diagnostics)
187195

188196
noteElementChanged(at: elementIndex)
189197

@@ -346,7 +354,7 @@ class ActiveSyntaxRewriter: SyntaxRewriter {
346354
}
347355

348356
// Retrieve the active `if` clause.
349-
let activeClause = configuredRegions.activeClause(for: postfixIfConfig.config)
357+
let activeClause = activeClauses.activeClause(for: postfixIfConfig.config, diagnostics: &diagnostics)
350358

351359
guard case .postfixExpression(let postfixExpr) = activeClause?.elements
352360
else {

Sources/SwiftIfConfig/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_swift_syntax_library(SwiftIfConfig
10+
ActiveClauseEvaluator.swift
1011
ActiveSyntaxVisitor.swift
1112
ActiveSyntaxRewriter.swift
1213
BuildConfiguration.swift

Tests/SwiftIfConfigTest/VisitorTests.swift

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,8 @@ public class VisitorTests: XCTestCase {
337337
}
338338
}
339339

340-
/// Assert that applying the given build configuration to the source code
341-
/// returns the expected source and diagnostics.
340+
/// Assert that removing any inactive code according to the given build
341+
/// configuration returns the expected source and diagnostics.
342342
fileprivate func assertRemoveInactive(
343343
_ source: String,
344344
configuration: some BuildConfiguration,
@@ -351,39 +351,59 @@ fileprivate func assertRemoveInactive(
351351
var parser = Parser(source)
352352
let tree = SourceFileSyntax.parse(from: &parser)
353353

354-
let (treeWithoutInactive, actualDiagnostics) = tree.removingInactive(
355-
in: configuration,
356-
retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
357-
)
358-
359-
// Check the resulting tree.
360-
assertStringsEqualWithDiff(
361-
treeWithoutInactive.description,
362-
expectedSource,
363-
file: file,
364-
line: line
365-
)
354+
for useConfiguredRegions in [false, true] {
355+
let fromDescription = useConfiguredRegions ? "configured regions" : "build configuration"
356+
let treeWithoutInactive: Syntax
357+
let actualDiagnostics: [Diagnostic]
358+
359+
if useConfiguredRegions {
360+
let configuredRegions = tree.configuredRegions(in: configuration)
361+
actualDiagnostics = configuredRegions.diagnostics
362+
treeWithoutInactive = configuredRegions.removingInactive(
363+
from: tree,
364+
retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
365+
)
366+
} else {
367+
(treeWithoutInactive, actualDiagnostics) = tree.removingInactive(
368+
in: configuration,
369+
retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
370+
)
371+
}
366372

367-
// Check the diagnostics.
368-
if actualDiagnostics.count != expectedDiagnostics.count {
369-
XCTFail(
370-
"""
371-
Expected \(expectedDiagnostics.count) diagnostics, but got \(actualDiagnostics.count):
372-
\(actualDiagnostics.map(\.debugDescription).joined(separator: "\n"))
373-
""",
373+
// Check the resulting tree.
374+
assertStringsEqualWithDiff(
375+
treeWithoutInactive.description,
376+
expectedSource,
377+
"Active code (\(fromDescription))",
374378
file: file,
375379
line: line
376380
)
377-
} else {
378-
for (actualDiag, expectedDiag) in zip(actualDiagnostics, expectedDiagnostics) {
379-
assertDiagnostic(
380-
actualDiag,
381-
in: .tree(tree),
382-
expected: expectedDiag,
383-
failureHandler: {
384-
XCTFail($0.message, file: $0.location.staticFilePath, line: $0.location.unsignedLine)
385-
}
381+
382+
// Check the diagnostics.
383+
if actualDiagnostics.count != expectedDiagnostics.count {
384+
XCTFail(
385+
"""
386+
Expected \(expectedDiagnostics.count) diagnostics, but got \(actualDiagnostics.count) via \(fromDescription):
387+
\(actualDiagnostics.map(\.debugDescription).joined(separator: "\n"))
388+
""",
389+
file: file,
390+
line: line
386391
)
392+
} else {
393+
for (actualDiag, expectedDiag) in zip(actualDiagnostics, expectedDiagnostics) {
394+
assertDiagnostic(
395+
actualDiag,
396+
in: .tree(tree),
397+
expected: expectedDiag,
398+
failureHandler: {
399+
XCTFail(
400+
$0.message + " via \(fromDescription)",
401+
file: $0.location.staticFilePath,
402+
line: $0.location.unsignedLine
403+
)
404+
}
405+
)
406+
}
387407
}
388408
}
389409
}

0 commit comments

Comments
 (0)