Skip to content

Commit 03ee18b

Browse files
committed
Add support for evaluating swift and compiler conditionals
These were introduced by SE-0212.
1 parent fb23103 commit 03ee18b

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed

Sources/SwiftIfConfig/BuildConfiguration.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,10 @@ public protocol BuildConfiguration {
6666

6767
/// The endianness of the target architecture.
6868
var endianness: Endianness? { get }
69+
70+
/// The effective language version, which can be set by the user (e.g., 5.0).
71+
var languageVersion: VersionTuple? { get }
72+
73+
/// The version of the compiler (e.g., 5.9).
74+
var compilerVersion: VersionTuple? { get }
6975
}

Sources/SwiftIfConfig/IfConfigEvaluation.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ enum IfConfigError: Error, CustomStringConvertible {
1616
case unknownExpression(ExprSyntax)
1717
case unhandledCustomCondition(name: String, syntax: TokenSyntax)
1818
case unhandledFunction(name: String, syntax: ExprSyntax)
19+
case unsupportedVersionOperator(name: String, operator: TokenSyntax)
20+
case invalidVersionOperand(name: String, syntax: ExprSyntax)
1921

2022
var description: String {
2123
switch self {
@@ -27,6 +29,12 @@ enum IfConfigError: Error, CustomStringConvertible {
2729

2830
case .unhandledFunction(name: let name, syntax: _):
2931
return "build configuration cannot handle '\(name)'"
32+
33+
case .unsupportedVersionOperator(name: let name, operator: let op):
34+
return "'\(name)' version check does not support operator '\(op.trimmedDescription)'"
35+
36+
case .invalidVersionOperand(name: let name, syntax: let version):
37+
return "'\(name)' version check has invalid version '\(version.trimmedDescription)'"
3038
}
3139
}
3240
}
@@ -106,6 +114,34 @@ private func evaluateIfConfig(
106114
return result
107115
}
108116

117+
/// Perform a check for a version constraint as used in the "swift" or "compiler" version checks.
118+
func doVersionComparisonCheck(_ actualVersion: VersionTuple?) throws -> Bool? {
119+
// Ensure that we have a single unlabeled argument that is either >= or < as a prefix
120+
// operator applied to a version.
121+
guard let argExpr = call.argumentList.singleUnlabeledExpression,
122+
let unaryArg = argExpr.as(PrefixOperatorExprSyntax.self),
123+
let opToken = unaryArg.operatorToken else {
124+
return nil
125+
}
126+
127+
guard let version = VersionTuple(parsing: unaryArg.postfixExpression.trimmedDescription) else {
128+
throw IfConfigError.invalidVersionOperand(name: fnName, syntax: unaryArg.postfixExpression)
129+
}
130+
131+
guard let actualVersion else {
132+
throw IfConfigError.unhandledFunction(name: fnName, syntax: argExpr)
133+
}
134+
135+
switch opToken.text {
136+
case ">=":
137+
return actualVersion >= version
138+
case "<":
139+
return actualVersion < version
140+
default:
141+
throw IfConfigError.unsupportedVersionOperator(name: fnName, operator: opToken)
142+
}
143+
}
144+
109145
let result: Bool?
110146
switch fn {
111147
case .hasAttribute:
@@ -165,6 +201,12 @@ private func evaluateIfConfig(
165201

166202
result = targetPointerBitWidth == expectedPointerBitWidth
167203

204+
case .swift:
205+
result = try doVersionComparisonCheck(configuration.languageVersion)
206+
207+
case .compiler:
208+
result = try doVersionComparisonCheck(configuration.compilerVersion)
209+
168210
default:
169211
// FIXME: Deal with all of the other kinds of checks we can perform.
170212
result = nil
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
/// Describes a version such as `5.9`.
14+
public struct VersionTuple {
15+
/// The components of the version tuple, start with the major version.
16+
public var components: [Int]
17+
}
18+
19+
extension VersionTuple {
20+
/// Create a version tuple from its components.
21+
public init(_ components: Int...) {
22+
self.components = components
23+
}
24+
25+
/// Parse a string into a version tuple, returning `nil` if any errors were
26+
/// present.
27+
public init?(parsing string: String) {
28+
self.components = []
29+
30+
for componentText in string.split(separator: ".") {
31+
guard let component = Int(componentText) else {
32+
return nil
33+
}
34+
35+
components.append(component)
36+
}
37+
}
38+
}
39+
40+
extension VersionTuple {
41+
/// Normalize the version tuple by removing trailing zeroes.
42+
var normalized: VersionTuple {
43+
var newComponents = components
44+
while newComponents.count > 1 && newComponents.last == 0 {
45+
newComponents.removeLast()
46+
}
47+
48+
return VersionTuple(components: newComponents)
49+
}
50+
}
51+
52+
extension VersionTuple: Equatable, Hashable { }
53+
54+
extension VersionTuple: Comparable {
55+
public static func <(lhs: VersionTuple, rhs: VersionTuple) -> Bool {
56+
return lhs.normalized.components.lexicographicallyPrecedes(rhs.normalized.components)
57+
}
58+
}

Tests/SwiftIfConfigTest/EvaluateTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ struct TestingBuildConfiguration : BuildConfiguration {
5555
var targetPointerBitWidth: Int? { 64 }
5656

5757
var endianness: SwiftIfConfig.Endianness? { .little }
58+
59+
var languageVersion: VersionTuple? { VersionTuple(5, 5) }
60+
61+
var compilerVersion: VersionTuple? { VersionTuple(5, 9) }
5862
}
5963

6064
public class EvaluateTests: XCTestCase {
@@ -152,4 +156,19 @@ public class EvaluateTests: XCTestCase {
152156
XCTAssertEqual(try ifConfigState("_pointerBitWidth(_64)"), .active)
153157
XCTAssertEqual(try ifConfigState("_pointerBitWidth(_32)"), .inactive)
154158
}
159+
160+
func testVersions() throws {
161+
let buildConfig = TestingBuildConfiguration()
162+
163+
func ifConfigState(_ condition: ExprSyntax) throws -> IfConfigState {
164+
try IfConfigState(condition: condition, configuration: buildConfig)
165+
}
166+
167+
XCTAssertEqual(try ifConfigState("swift(>=5.5"), .active)
168+
XCTAssertEqual(try ifConfigState("swift(<6"), .active)
169+
XCTAssertEqual(try ifConfigState("swift(>=6"), .inactive)
170+
XCTAssertEqual(try ifConfigState("compiler(>=5.8"), .active)
171+
XCTAssertEqual(try ifConfigState("compiler(>=5.9"), .active)
172+
XCTAssertEqual(try ifConfigState("compiler(>=5.10"), .inactive)
173+
}
155174
}

0 commit comments

Comments
 (0)