Skip to content

Commit 431fe8a

Browse files
committed
Address a number of review comments (thanks, Alex!)
1 parent 15073e8 commit 431fe8a

File tree

5 files changed

+42
-23
lines changed

5 files changed

+42
-23
lines changed

Sources/SwiftIfConfig/IfConfigEvaluation.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,14 @@ enum IfConfigError: Error, CustomStringConvertible {
7272
extension VersionTuple {
7373
/// Parse a compiler build version of the form "5007.*.1.2.3*", which is
7474
/// used by an older if configuration form `_compiler_version("...")`.
75+
/// - Parameters:
76+
/// - versionString: The version string for the compiler build version that
77+
/// we are parsing.
78+
/// - versionSyntax: The syntax node that contains the version string, used
79+
/// only for diagnostic purposes.
7580
fileprivate init(
7681
parsingCompilerBuildVersion versionString: String,
77-
_ syntax: ExprSyntax
82+
_ versionSyntax: ExprSyntax
7883
) throws {
7984
components = []
8085

@@ -86,7 +91,7 @@ extension VersionTuple {
8691
let limit = components.isEmpty ? 9223371 : 999
8792
if value < 0 || value > limit {
8893
// FIXME: Can we provide a more precise location here?
89-
throw IfConfigError.compilerVersionOutOfRange(value: value, upperLimit: limit, syntax: syntax)
94+
throw IfConfigError.compilerVersionOutOfRange(value: value, upperLimit: limit, syntax: versionSyntax)
9095
}
9196

9297
components.append(value)
@@ -96,29 +101,29 @@ extension VersionTuple {
96101
for (index, componentString) in componentStrings.enumerated() {
97102
// Check ahead of time for empty version components
98103
if componentString.isEmpty {
99-
throw IfConfigError.emptyVersionComponent(syntax: syntax)
104+
throw IfConfigError.emptyVersionComponent(syntax: versionSyntax)
100105
}
101106

102107
// The second component is always "*", and is never used for comparison.
103108
if index == 1 {
104109
if componentString != "*" {
105-
throw IfConfigError.compilerVersionSecondComponentNotWildcard(syntax: syntax)
110+
throw IfConfigError.compilerVersionSecondComponentNotWildcard(syntax: versionSyntax)
106111
}
107112
try recordComponent(0)
108113
continue
109114
}
110115

111116
// Every other component must be an integer value.
112117
guard let component = Int(componentString) else {
113-
throw IfConfigError.invalidVersionOperand(name: "_compiler_version", syntax: syntax)
118+
throw IfConfigError.invalidVersionOperand(name: "_compiler_version", syntax: versionSyntax)
114119
}
115120

116121
try recordComponent(component)
117122
}
118123

119124
// Only allowed to specify up to 5 version components.
120125
if components.count > 5 {
121-
throw IfConfigError.compilerVersionTooManyComponents(syntax: syntax)
126+
throw IfConfigError.compilerVersionTooManyComponents(syntax: versionSyntax)
122127
}
123128

124129
// In the beginning, '_compiler_version(string-literal)' was designed for a
@@ -159,6 +164,13 @@ extension VersionTuple {
159164
}
160165

161166
/// Evaluate the condition of an `#if`.
167+
/// - Parameters:
168+
/// - condition: The condition to evaluate, which we assume has already been
169+
/// folded according to the logical operators table.
170+
/// - configuration: The configuration against which the condition will be
171+
/// evaluated.
172+
/// - Throws: Throws if an errors occur during evaluation.
173+
/// - Returns: Whether the condition holds with the given build configuration.
162174
private func evaluateIfConfig(
163175
condition: ExprSyntax,
164176
configuration: some BuildConfiguration
@@ -223,7 +235,6 @@ private func evaluateIfConfig(
223235
let fnName = call.calledExpression.simpleIdentifierExpr,
224236
let fn = IfConfigFunctions(rawValue: fnName)
225237
{
226-
227238
/// Perform a check for an operation that takes a single identifier argument.
228239
func doSingleIdentifierArgumentCheck(_ body: (String, ExprSyntax) -> Bool?) throws -> Bool? {
229240
// Ensure that we have a single argument that is a simple identifier.
@@ -297,6 +308,8 @@ private func evaluateIfConfig(
297308
let arg = argExpr.simpleIdentifierExpr,
298309
let expectedEndianness = Endianness(rawValue: arg)
299310
else {
311+
// FIXME: Custom error message when the endianness doesn't match any
312+
// case.
300313
result = nil
301314
break
302315
}
@@ -365,8 +378,9 @@ private func evaluateIfConfig(
365378
throw IfConfigError.canImportMissingModule(syntax: ExprSyntax(call))
366379
}
367380

381+
// FIXME: This is a gross hack. Actually look at the sequence of
382+
// `MemberAccessExprSyntax` nodes and pull out the identifiers.
368383
let importPath = firstArg.expression.trimmedDescription.split(separator: ".")
369-
// FIXME: Check to make sure we have all identifiers here.
370384

371385
// If there is a second argument, it shall have the label _version or
372386
// _underlyingVersion.
@@ -428,7 +442,7 @@ extension IfConfigState {
428442
/// insufficient information to make a determination.
429443
public init(condition: some ExprSyntaxProtocol, configuration: some BuildConfiguration) throws {
430444
// Apply operator folding for !/&&/||.
431-
let foldedCondition = try OperatorTable.logicalOperators.foldAll(condition).as(ExprSyntax.self)!
445+
let foldedCondition = try OperatorTable.logicalOperators.foldAll(condition).cast(ExprSyntax.self)
432446

433447
let result = try evaluateIfConfig(condition: foldedCondition, configuration: configuration)
434448
self = result ? .active : .inactive

Sources/SwiftIfConfig/SwiftIfConfig.docc/SwiftIfConfig.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# `SwiftIfConfig`
22

3-
4-
53
A library to evaluate `#if` conditionals within a Swift syntax tree.
64

75
## Overview
@@ -29,6 +27,6 @@ The syntax tree and its parser do not reason about the build configuration. Rath
2927
The `SwiftIfConfig` library provides utilities to determine which syntax nodes are part of a particular build configuration. Each utility requires that one provide a specific build configuration (i.e., an instance of a type that conforms to the <doc:BuildConfiguration> protocol), and provides a different view on essentially the same information:
3028

3129
* <doc:ActiveSyntaxVisitor> and <doc:ActiveSyntaxAnyVisitor> are visitor types that only visit the syntax nodes that are included ("active") for a given build configuration, implicitly skipping any nodes within inactive `#if` clauses.
32-
* `SyntaxProtocol.removingInactive(in:)` produces a syntax node that removes all inactive regions (and their corresponding `IfConfigDeclSyntax` nodes) from the given syntax tree, returning a new tree that is free of `#if` conditions.
33-
* `IfConfigDeclSyntax.activeClause(in:)` determines which of the clauses of an `#if` is active for the given build configuration, returning the active clause.
34-
* `SyntaxProtocol.isActive(in:)` determines whether the given syntax node is active for the given build configuration.
30+
* ``SyntaxProtocol/removingInactive(in:)`` produces a syntax node that removes all inactive regions (and their corresponding `IfConfigDeclSyntax` nodes) from the given syntax tree, returning a new tree that is free of `#if` conditions.
31+
* ``IfConfigDeclSyntax.activeClause(in:)`` determines which of the clauses of an `#if` is active for the given build configuration, returning the active clause.
32+
* ``SyntaxProtocol.isActive(in:)`` determines whether the given syntax node is active for the given build configuration.

Sources/SwiftIfConfig/SyntaxLiteralUtils.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ extension BooleanLiteralExprSyntax {
2020
extension TupleExprSyntax {
2121
/// Whether this tuple is a parenthesized expression, e.g., (x).
2222
var isParentheses: Bool {
23-
guard elements.count == 1, let element = elements.first else { return false }
24-
return element.label == nil
23+
return elements.singleUnlabeledExpression != nil
2524
}
2625
}
2726

Sources/SwiftIfConfig/VersionTuple.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@
1414
public struct VersionTuple {
1515
/// The components of the version tuple, start with the major version.
1616
public var components: [Int]
17-
}
1817

19-
extension VersionTuple {
20-
/// Create a version tuple from its components.
21-
public init(_ components: Int...) {
18+
/// Create a version tuple from a non-empty array of components.
19+
public init(components: [Int]) {
20+
precondition(!components.isEmpty)
2221
self.components = components
2322
}
2423

24+
/// Create a version tuple from its components.
25+
public init(_ firstComponent: Int, _ remainingComponents: Int...) {
26+
self.components = []
27+
self.components.append(firstComponent)
28+
self.components.append(contentsOf: remainingComponents)
29+
}
30+
2531
/// Parse a string into a version tuple, returning `nil` if any errors were
2632
/// present.
2733
public init?(parsing string: String) {
@@ -34,10 +40,10 @@ extension VersionTuple {
3440

3541
components.append(component)
3642
}
43+
44+
if components.isEmpty { return nil }
3745
}
38-
}
3946

40-
extension VersionTuple {
4147
/// Normalize the version tuple by removing trailing zeroes.
4248
var normalized: VersionTuple {
4349
var newComponents = components

Tests/SwiftIfConfigTest/VisitorTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ class AllActiveVisitor: ActiveSyntaxAnyVisitor<TestingBuildConfiguration> {
2424
super.init(viewMode: .sourceAccurate, configuration: configuration)
2525
}
2626
open override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
27-
XCTAssertTrue(try! node.isActive(in: configuration))
27+
var active: Bool = false
28+
XCTAssertNoThrow(try active = node.isActive(in: configuration))
29+
XCTAssertTrue(active)
2830
return .visitChildren
2931
}
3032
}

0 commit comments

Comments
 (0)