Skip to content

Commit aa97fbd

Browse files
committed
Reshuffle and rename some source files in SwiftIfConfig
Rename source files in SwiftIfConfig to better reflect what they do, move the public APIs up to the tops of files, and split the massive IfConfigEvaluation.swift into several files. The file itself defines the core logic for doing the evaluation (which is internal to the library), and other source files provide public APIs on top of it.
1 parent bccddb4 commit aa97fbd

8 files changed

+223
-194
lines changed

Sources/SwiftIfConfig/IfConfigRewriter.swift renamed to Sources/SwiftIfConfig/ActiveSyntaxRewriter.swift

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,40 @@
2222
import SwiftDiagnostics
2323
import SwiftSyntax
2424

25+
extension SyntaxProtocol {
26+
/// Produce a copy of this syntax node that removes all syntax regions that
27+
/// are inactive according to the given build configuration, leaving only
28+
/// the code that is active within that build configuration.
29+
///
30+
/// Returns the syntax node with all inactive regions removed, along with an
31+
/// array containing any diagnostics produced along the way.
32+
///
33+
/// If there are errors in the conditions of any configuration
34+
/// clauses, e.g., `#if FOO > 10`, then the condition will be
35+
/// considered to have failed and the clauses's elements will be
36+
/// removed.
37+
public func removingInactive(in configuration: some BuildConfiguration) -> (Syntax, [Diagnostic]) {
38+
// First pass: Find all of the active clauses for the #ifs we need to
39+
// visit, along with any diagnostics produced along the way. This process
40+
// does not change the tree in any way.
41+
let visitor = ActiveSyntaxVisitor(viewMode: .sourceAccurate, configuration: configuration)
42+
visitor.walk(self)
43+
44+
// If there were no active clauses to visit, we're done!
45+
if visitor.numIfClausesVisited == 0 {
46+
return (Syntax(self), visitor.diagnostics)
47+
}
48+
49+
// Second pass: Rewrite the syntax tree by removing the inactive clauses
50+
// from each #if (along with the #ifs themselves).
51+
let rewriter = ActiveSyntaxRewriter(configuration: configuration)
52+
return (
53+
rewriter.rewrite(Syntax(self)),
54+
visitor.diagnostics
55+
)
56+
}
57+
}
58+
2559
/// Syntax rewriter that only visits syntax nodes that are active according
2660
/// to a particular build configuration.
2761
///
@@ -272,37 +306,3 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
272306
return visit(rewrittenNode)
273307
}
274308
}
275-
276-
extension SyntaxProtocol {
277-
/// Produce a copy of this syntax node that removes all syntax regions that
278-
/// are inactive according to the given build configuration, leaving only
279-
/// the code that is active within that build configuration.
280-
///
281-
/// Returns the syntax node with all inactive regions removed, along with an
282-
/// array containing any diagnostics produced along the way.
283-
///
284-
/// If there are errors in the conditions of any configuration
285-
/// clauses, e.g., `#if FOO > 10`, then the condition will be
286-
/// considered to have failed and the clauses's elements will be
287-
/// removed.
288-
public func removingInactive(in configuration: some BuildConfiguration) -> (Syntax, [Diagnostic]) {
289-
// First pass: Find all of the active clauses for the #ifs we need to
290-
// visit, along with any diagnostics produced along the way. This process
291-
// does not change the tree in any way.
292-
let visitor = ActiveSyntaxVisitor(viewMode: .sourceAccurate, configuration: configuration)
293-
visitor.walk(self)
294-
295-
// If there were no active clauses to visit, we're done!
296-
if visitor.numIfClausesVisited == 0 {
297-
return (Syntax(self), visitor.diagnostics)
298-
}
299-
300-
// Second pass: Rewrite the syntax tree by removing the inactive clauses
301-
// from each #if (along with the #ifs themselves).
302-
let rewriter = ActiveSyntaxRewriter(configuration: configuration)
303-
return (
304-
rewriter.rewrite(Syntax(self)),
305-
visitor.diagnostics
306-
)
307-
}
308-
}

Sources/SwiftIfConfig/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_swift_syntax_library(SwiftIfConfig
10+
ActiveSyntaxVisitor.swift
11+
ActiveSyntaxRewriter.swift
1012
BuildConfiguration.swift
1113
ConfiguredRegions.swift
1214
ConfiguredRegionState.swift
15+
IfConfigDecl+IfConfig.swift
1316
IfConfigError.swift
1417
IfConfigEvaluation.swift
1518
IfConfigFunctions.swift
16-
IfConfigRewriter.swift
17-
IfConfigVisitor.swift
1819
SyntaxLiteralUtils.swift
20+
SyntaxProtocol+IfConfig.swift
1921
VersionTuple+Parsing.swift
2022
VersionTuple.swift
2123
)

Sources/SwiftIfConfig/ConfiguredRegionState.swift

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

1316
/// Describes the state of a particular region guarded by `#if` or similar.
1417
public enum ConfiguredRegionState {
@@ -19,4 +22,30 @@ public enum ConfiguredRegionState {
1922
case inactive
2023
/// The region is active and is part of the compiled program.
2124
case active
25+
26+
/// Evaluate the given `#if` condition using the given build configuration, throwing an error if there is
27+
/// insufficient information to make a determination.
28+
public init(
29+
condition: some ExprSyntaxProtocol,
30+
configuration: some BuildConfiguration,
31+
diagnosticHandler: ((Diagnostic) -> Void)? = nil
32+
) throws {
33+
// Apply operator folding for !/&&/||.
34+
let foldedCondition = try OperatorTable.logicalOperators.foldAll(condition) { error in
35+
diagnosticHandler?(error.asDiagnostic)
36+
throw error
37+
}.cast(ExprSyntax.self)
38+
39+
let (active, versioned) = try evaluateIfConfig(
40+
condition: foldedCondition,
41+
configuration: configuration,
42+
diagnosticHandler: diagnosticHandler
43+
)
44+
45+
switch (active, versioned) {
46+
case (true, _): self = .active
47+
case (false, false): self = .inactive
48+
case (false, true): self = .unparsed
49+
}
50+
}
2251
}

Sources/SwiftIfConfig/ConfiguredRegions.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ extension SyntaxProtocol {
4444
}
4545
}
4646

47+
/// Helper class that walks a syntax tree looking for configured regions.
4748
fileprivate class ConfiguredRegionVisitor<Configuration: BuildConfiguration>: SyntaxVisitor {
4849
let configuration: Configuration
4950

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 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+
import SwiftDiagnostics
13+
import SwiftSyntax
14+
15+
extension IfConfigDeclSyntax {
16+
/// Given a particular build configuration, determine which clause (if any) is the "active" clause.
17+
///
18+
/// For example, for code like the following:
19+
/// ```
20+
/// #if A
21+
/// func f()
22+
/// #elseif B
23+
/// func g()
24+
/// #endif
25+
/// ```
26+
///
27+
/// 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+
/// command line, the second clause (containing `func g()`) would be returned. If neither was
30+
/// passed, this function will return `nil` to indicate that none of the regions are active.
31+
///
32+
/// If an error occurrs while processing any of the `#if` clauses,
33+
/// that clause will be considered inactive and this operation will
34+
/// continue to evaluate later clauses.
35+
public func activeClause(
36+
in configuration: some BuildConfiguration,
37+
diagnosticHandler: ((Diagnostic) -> Void)? = nil
38+
) -> IfConfigClauseSyntax? {
39+
for clause in clauses {
40+
// If there is no condition, we have reached an unconditional clause. Return it.
41+
guard let condition = clause.condition else {
42+
return clause
43+
}
44+
45+
// 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
52+
if isActive {
53+
return clause
54+
}
55+
}
56+
57+
return nil
58+
}
59+
}

Sources/SwiftIfConfig/IfConfigEvaluation.swift

Lines changed: 1 addition & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212
import SwiftDiagnostics
13-
import SwiftOperators
1413
import SwiftSyntax
1514

1615
/// Evaluate the condition of an `#if`.
@@ -29,7 +28,7 @@ import SwiftSyntax
2928
/// condition holds with the given build configuration. The second whether
3029
/// the build condition is a "versioned" check that implies that we shouldn't
3130
/// diagnose syntax errors in blocks where the check fails.
32-
private func evaluateIfConfig(
31+
func evaluateIfConfig(
3332
condition: ExprSyntax,
3433
configuration: some BuildConfiguration,
3534
diagnosticHandler: ((Diagnostic) -> Void)?
@@ -406,162 +405,6 @@ private func evaluateIfConfig(
406405
throw recordedError(.unknownExpression(condition))
407406
}
408407

409-
extension ConfiguredRegionState {
410-
/// Evaluate the given `#if` condition using the given build configuration, throwing an error if there is
411-
/// insufficient information to make a determination.
412-
public init(
413-
condition: some ExprSyntaxProtocol,
414-
configuration: some BuildConfiguration,
415-
diagnosticHandler: ((Diagnostic) -> Void)? = nil
416-
) throws {
417-
// Apply operator folding for !/&&/||.
418-
let foldedCondition = try OperatorTable.logicalOperators.foldAll(condition) { error in
419-
diagnosticHandler?(error.asDiagnostic)
420-
throw error
421-
}.cast(ExprSyntax.self)
422-
423-
let (active, versioned) = try evaluateIfConfig(
424-
condition: foldedCondition,
425-
configuration: configuration,
426-
diagnosticHandler: diagnosticHandler
427-
)
428-
429-
switch (active, versioned) {
430-
case (true, _): self = .active
431-
case (false, false): self = .inactive
432-
case (false, true): self = .unparsed
433-
}
434-
}
435-
}
436-
437-
extension IfConfigDeclSyntax {
438-
/// Given a particular build configuration, determine which clause (if any) is the "active" clause.
439-
///
440-
/// For example, for code like the following:
441-
/// ```
442-
/// #if A
443-
/// func f()
444-
/// #elseif B
445-
/// func g()
446-
/// #endif
447-
/// ```
448-
///
449-
/// If the `A` configuration option was passed on the command line (e.g. via `-DA`), the first clause
450-
/// (containing `func f()`) would be returned. If not, and if the `B`configuration was passed on the
451-
/// command line, the second clause (containing `func g()`) would be returned. If neither was
452-
/// passed, this function will return `nil` to indicate that none of the regions are active.
453-
///
454-
/// If an error occurrs while processing any of the `#if` clauses,
455-
/// that clause will be considered inactive and this operation will
456-
/// continue to evaluate later clauses.
457-
public func activeClause(
458-
in configuration: some BuildConfiguration,
459-
diagnosticHandler: ((Diagnostic) -> Void)? = nil
460-
) -> IfConfigClauseSyntax? {
461-
for clause in clauses {
462-
// If there is no condition, we have reached an unconditional clause. Return it.
463-
guard let condition = clause.condition else {
464-
return clause
465-
}
466-
467-
// If this condition evaluates true, return this clause.
468-
let isActive =
469-
(try? evaluateIfConfig(
470-
condition: condition,
471-
configuration: configuration,
472-
diagnosticHandler: diagnosticHandler
473-
))?.active ?? false
474-
if isActive {
475-
return clause
476-
}
477-
}
478-
479-
return nil
480-
}
481-
}
482-
483-
extension SyntaxProtocol {
484-
/// Determine whether the given syntax node is active within the given build configuration.
485-
///
486-
/// This function evaluates the enclosing stack of `#if` conditions to determine whether the
487-
/// given node is active in the program when it is compiled with the given build configuration.
488-
///
489-
/// For example, given code like the following:
490-
/// #if DEBUG
491-
/// #if A
492-
/// func f()
493-
/// #elseif B
494-
/// func g()
495-
/// #endif
496-
/// #endif
497-
///
498-
/// a call to `isActive` on the syntax node for the function `g` would return `active` when the
499-
/// configuration options `DEBUG` and `B` are provided, but `A` is not.
500-
public func isActive(
501-
in configuration: some BuildConfiguration,
502-
diagnosticHandler: ((Diagnostic) -> Void)? = nil
503-
) throws -> ConfiguredRegionState {
504-
var currentNode: Syntax = Syntax(self)
505-
var currentState: ConfiguredRegionState = .active
506-
507-
while let parent = currentNode.parent {
508-
// If the parent is an `#if` configuration, check whether our current
509-
// clause is active. If not, we're in an inactive region. We also
510-
// need to determine whether
511-
if let ifConfigClause = currentNode.as(IfConfigClauseSyntax.self),
512-
let ifConfigDecl = ifConfigClause.parent?.parent?.as(IfConfigDeclSyntax.self)
513-
{
514-
let activeClause = ifConfigDecl.activeClause(
515-
in: configuration,
516-
diagnosticHandler: diagnosticHandler
517-
)
518-
519-
if activeClause != ifConfigClause {
520-
// This was not the active clause, so we know that we're in an
521-
// inactive block. However, if the condition is versioned, this is an
522-
// unparsed region.
523-
let isVersioned =
524-
(try? ifConfigClause.isVersioned(
525-
configuration: configuration,
526-
diagnosticHandler: diagnosticHandler
527-
)) ?? true
528-
if isVersioned {
529-
return .unparsed
530-
}
531-
532-
currentState = .inactive
533-
}
534-
}
535-
536-
currentNode = parent
537-
}
538-
539-
return currentState
540-
}
541-
542-
/// Determine whether the given syntax node is active given a set of
543-
/// configured regions as produced by `configuredRegions(in:)`.
544-
///
545-
/// This is
546-
/// an approximation
547-
public func isActive(
548-
inConfiguredRegions regions: [(IfConfigClauseSyntax, ConfiguredRegionState)]
549-
) -> ConfiguredRegionState {
550-
var currentState: ConfiguredRegionState = .active
551-
for (ifClause, state) in regions {
552-
if self.position < ifClause.position {
553-
return currentState
554-
}
555-
556-
if self.position <= ifClause.endPosition {
557-
currentState = state
558-
}
559-
}
560-
561-
return currentState
562-
}
563-
}
564-
565408
extension IfConfigClauseSyntax {
566409
/// Determine whether this condition is "versioned".
567410
func isVersioned(

0 commit comments

Comments
 (0)