Skip to content

Commit a56cde8

Browse files
authored
Merge pull request #2806 from DougGregor/swift-if-config-cleanups
2 parents f8b6353 + a5afdab commit a56cde8

File tree

6 files changed

+94
-58
lines changed

6 files changed

+94
-58
lines changed

Sources/SwiftIfConfig/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ add_swift_syntax_library(SwiftIfConfig
1313
ConfiguredRegions.swift
1414
IfConfigRegionState.swift
1515
IfConfigDecl+IfConfig.swift
16-
IfConfigError.swift
16+
IfConfigDiagnostic.swift
1717
IfConfigEvaluation.swift
1818
IfConfigFunctions.swift
1919
SyntaxLiteralUtils.swift

Sources/SwiftIfConfig/IfConfigError.swift renamed to Sources/SwiftIfConfig/IfConfigDiagnostic.swift

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ import SwiftDiagnostics
1414
import SwiftSyntax
1515
import SwiftSyntaxBuilder
1616

17-
/// Describes the kinds of errors that can occur when processing #if conditions.
18-
enum IfConfigError: Error, CustomStringConvertible {
17+
/// Describes the kinds of diagnostics that can occur when processing #if
18+
/// conditions. This is an Error-conforming type so we can throw errors when
19+
/// needed, but the cases themselves are a mix of warnings and errors when
20+
/// rendered as a diagnostic.
21+
enum IfConfigDiagnostic: Error, CustomStringConvertible {
1922
case unknownExpression(ExprSyntax)
2023
case unhandledFunction(name: String, syntax: ExprSyntax)
2124
case requiresUnlabeledArgument(name: String, role: String, syntax: ExprSyntax)
@@ -57,10 +60,8 @@ enum IfConfigError: Error, CustomStringConvertible {
5760
case .emptyVersionComponent(syntax: _):
5861
return "found empty version component"
5962

60-
case .compilerVersionOutOfRange(value: _, upperLimit: let upperLimit, syntax: _):
61-
// FIXME: This matches the C++ implementation, but it would be more useful to
62-
// provide the actual value as-written and avoid the mathy [0, N] syntax.
63-
return "compiler version component out of range: must be in [0, \(upperLimit)]"
63+
case .compilerVersionOutOfRange(value: let value, upperLimit: let upperLimit, syntax: _):
64+
return "compiler version component '\(value)' is not in the allowed range 0...\(upperLimit)"
6465

6566
case .compilerVersionSecondComponentNotWildcard(syntax: _):
6667
return "the second version component is not used for comparison in legacy compiler versions"
@@ -134,11 +135,11 @@ enum IfConfigError: Error, CustomStringConvertible {
134135
}
135136
}
136137

137-
extension IfConfigError: DiagnosticMessage {
138+
extension IfConfigDiagnostic: DiagnosticMessage {
138139
var message: String { description }
139140

140141
var diagnosticID: MessageID {
141-
.init(domain: "SwiftIfConfig", id: "IfConfigError")
142+
.init(domain: "SwiftIfConfig", id: "IfConfigDiagnostic")
142143
}
143144

144145
var severity: SwiftDiagnostics.DiagnosticSeverity {

Sources/SwiftIfConfig/IfConfigEvaluation.swift

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ import SwiftSyntax
3030
/// diagnose syntax errors in blocks where the check fails.
3131
func evaluateIfConfig(
3232
condition: ExprSyntax,
33-
configuration: some BuildConfiguration,
34-
outermostCondition: Bool = true
33+
configuration: some BuildConfiguration
3534
) -> (active: Bool, syntaxErrorsAllowed: Bool, diagnostics: [Diagnostic]) {
3635
var extraDiagnostics: [Diagnostic] = []
3736

@@ -50,7 +49,7 @@ func evaluateIfConfig(
5049
/// Record an if-config evaluation error before returning it. Use this for
5150
/// every 'throw' site in this evaluation.
5251
func recordError(
53-
_ error: IfConfigError
52+
_ error: IfConfigDiagnostic
5453
) -> (active: Bool, syntaxErrorsAllowed: Bool, diagnostics: [Diagnostic]) {
5554
return recordError(error, at: error.syntax)
5655
}
@@ -88,7 +87,7 @@ func evaluateIfConfig(
8887
active: result,
8988
syntaxErrorsAllowed: false,
9089
diagnostics: [
91-
IfConfigError.integerLiteralCondition(
90+
IfConfigDiagnostic.integerLiteralCondition(
9291
syntax: condition,
9392
replacement: result
9493
).asDiagnostic
@@ -98,7 +97,7 @@ func evaluateIfConfig(
9897

9998
// Declaration references are for custom compilation flags.
10099
if let identExpr = condition.as(DeclReferenceExprSyntax.self),
101-
let ident = identExpr.simpleIdentifier
100+
let ident = identExpr.simpleIdentifier?.name
102101
{
103102
// Evaluate the custom condition. If the build configuration cannot answer this query, fail.
104103
return checkConfiguration(at: identExpr) {
@@ -115,8 +114,7 @@ func evaluateIfConfig(
115114

116115
let (innerActive, innerSyntaxErrorsAllowed, innerDiagnostics) = evaluateIfConfig(
117116
condition: prefixOp.expression,
118-
configuration: configuration,
119-
outermostCondition: outermostCondition
117+
configuration: configuration
120118
)
121119

122120
return (active: !innerActive, syntaxErrorsAllowed: innerSyntaxErrorsAllowed, diagnostics: innerDiagnostics)
@@ -133,7 +131,7 @@ func evaluateIfConfig(
133131
}
134132

135133
// Check whether this was likely to be a check for targetEnvironment(simulator).
136-
if outermostCondition,
134+
if binOp.isOutermostIfCondition,
137135
let targetEnvironmentDiag = diagnoseLikelySimulatorEnvironmentTest(binOp)
138136
{
139137
extraDiagnostics.append(targetEnvironmentDiag)
@@ -142,13 +140,12 @@ func evaluateIfConfig(
142140
// Evaluate the left-hand side.
143141
let (lhsActive, lhsSyntaxErrorsAllowed, lhsDiagnostics) = evaluateIfConfig(
144142
condition: binOp.leftOperand,
145-
configuration: configuration,
146-
outermostCondition: false
143+
configuration: configuration
147144
)
148145

149-
// Short-circuit evaluation if we know the answer. We still recurse into
150-
// the right-hand side, but with a dummy configuration that won't have
151-
// side effects, so we only get validation-related errors.
146+
// Determine whether we already know the result. We might short-circuit the
147+
// evaluation, depending on whether we need to produce validation
148+
// diagnostics for the right-hand side.
152149
let shortCircuitResult: Bool?
153150
switch (lhsActive, op.operator.text) {
154151
case (true, "||"): shortCircuitResult = true
@@ -157,8 +154,8 @@ func evaluateIfConfig(
157154
}
158155

159156
// If we are supposed to short-circuit and the left-hand side of this
160-
// operator with inactive &&, stop now: we shouldn't evaluate the right-
161-
// hand side at all.
157+
// operator permits syntax errors when it fails, stop now: we shouldn't
158+
// process the right-hand side at all.
162159
if let isActive = shortCircuitResult, lhsSyntaxErrorsAllowed {
163160
return (
164161
active: isActive,
@@ -167,21 +164,21 @@ func evaluateIfConfig(
167164
)
168165
}
169166

170-
// Evaluate the right-hand side.
167+
// Process the right-hand side. If we already know the answer, then
168+
// avoid performing any build configuration queries that might cause
169+
// side effects.
171170
let rhsActive: Bool
172171
let rhsSyntaxErrorsAllowed: Bool
173172
let rhsDiagnostics: [Diagnostic]
174173
if shortCircuitResult != nil {
175174
(rhsActive, rhsSyntaxErrorsAllowed, rhsDiagnostics) = evaluateIfConfig(
176175
condition: binOp.rightOperand,
177-
configuration: CanImportSuppressingBuildConfiguration(other: configuration),
178-
outermostCondition: false
176+
configuration: CanImportSuppressingBuildConfiguration(other: configuration)
179177
)
180178
} else {
181179
(rhsActive, rhsSyntaxErrorsAllowed, rhsDiagnostics) = evaluateIfConfig(
182180
condition: binOp.rightOperand,
183-
configuration: configuration,
184-
outermostCondition: false
181+
configuration: configuration
185182
)
186183
}
187184

@@ -211,14 +208,13 @@ func evaluateIfConfig(
211208
{
212209
return evaluateIfConfig(
213210
condition: element.expression,
214-
configuration: configuration,
215-
outermostCondition: outermostCondition
211+
configuration: configuration
216212
)
217213
}
218214

219215
// Call syntax is for operations.
220216
if let call = condition.as(FunctionCallExprSyntax.self),
221-
let fnName = call.calledExpression.simpleIdentifierExpr,
217+
let fnName = call.calledExpression.simpleIdentifierExpr?.name,
222218
let fn = IfConfigFunctions(rawValue: fnName)
223219
{
224220
/// Perform a check for an operation that takes a single identifier argument.
@@ -228,7 +224,7 @@ func evaluateIfConfig(
228224
) -> (active: Bool, syntaxErrorsAllowed: Bool, diagnostics: [Diagnostic]) {
229225
// Ensure that we have a single argument that is a simple identifier.
230226
guard let argExpr = call.arguments.singleUnlabeledExpression,
231-
var arg = argExpr.simpleIdentifierExpr
227+
var arg = argExpr.simpleIdentifierExpr?.name
232228
else {
233229
return recordError(
234230
.requiresUnlabeledArgument(name: fnName, role: role, syntax: ExprSyntax(call))
@@ -238,7 +234,7 @@ func evaluateIfConfig(
238234
// The historical "macabi" environment has been renamed to "macCatalyst".
239235
if role == "environment" && arg == "macabi" {
240236
extraDiagnostics.append(
241-
IfConfigError.macabiIsMacCatalyst(syntax: argExpr)
237+
IfConfigDiagnostic.macabiIsMacCatalyst(syntax: argExpr)
242238
.asDiagnostic
243239
)
244240

@@ -320,7 +316,7 @@ func evaluateIfConfig(
320316
case ._endian:
321317
// Ensure that we have a single argument that is a simple identifier.
322318
guard let argExpr = call.arguments.singleUnlabeledExpression,
323-
let arg = argExpr.simpleIdentifierExpr
319+
let arg = argExpr.simpleIdentifierExpr?.name
324320
else {
325321
return recordError(
326322
.requiresUnlabeledArgument(
@@ -339,7 +335,7 @@ func evaluateIfConfig(
339335
} else {
340336
// Complain about unknown endianness
341337
extraDiagnostics.append(
342-
IfConfigError.endiannessDoesNotMatch(syntax: argExpr, argument: arg)
338+
IfConfigDiagnostic.endiannessDoesNotMatch(syntax: argExpr, argument: arg)
343339
.asDiagnostic
344340
)
345341

@@ -356,7 +352,7 @@ func evaluateIfConfig(
356352
// Ensure that we have a single argument that is a simple identifier, which
357353
// is an underscore followed by an integer.
358354
guard let argExpr = call.arguments.singleUnlabeledExpression,
359-
let arg = argExpr.simpleIdentifierExpr,
355+
let arg = argExpr.simpleIdentifierExpr?.name,
360356
let argFirst = arg.first,
361357
argFirst == "_",
362358
let expectedBitWidth = Int(arg.dropFirst())
@@ -470,7 +466,7 @@ func evaluateIfConfig(
470466

471467
// Warn that we did this.
472468
extraDiagnostics.append(
473-
IfConfigError.ignoredTrailingComponents(
469+
IfConfigDiagnostic.ignoredTrailingComponents(
474470
version: versionTuple,
475471
syntax: secondArg.expression
476472
).asDiagnostic
@@ -502,26 +498,51 @@ func evaluateIfConfig(
502498
return recordError(.unknownExpression(condition))
503499
}
504500

501+
extension SyntaxProtocol {
502+
/// Determine whether this expression node is an "outermost" #if condition,
503+
/// meaning that it is not nested within some kind of expression like && or
504+
/// ||.
505+
fileprivate var isOutermostIfCondition: Bool {
506+
// If there is no parent, it's the outermost condition.
507+
guard let parent = self.parent else {
508+
return true
509+
}
510+
511+
// If we hit the #if condition clause, it's the outermost condition.
512+
if parent.is(IfConfigClauseSyntax.self) {
513+
return true
514+
}
515+
516+
// We found an infix operator, so this is not an outermost #if condition.
517+
if parent.is(InfixOperatorExprSyntax.self) {
518+
return false
519+
}
520+
521+
// Keep looking up the syntax tree.
522+
return parent.isOutermostIfCondition
523+
}
524+
}
525+
505526
/// Given an expression with the expected form A.B.C, extract the import path
506527
/// ["A", "B", "C"] from it. Throws an error if the expression doesn't match
507528
/// this form.
508529
private func extractImportPath(_ expression: some ExprSyntaxProtocol) throws -> [String] {
509530
// Member access.
510531
if let memberAccess = expression.as(MemberAccessExprSyntax.self),
511532
let base = memberAccess.base,
512-
let memberName = memberAccess.declName.simpleIdentifier
533+
let memberName = memberAccess.declName.simpleIdentifier?.name
513534
{
514535
return try extractImportPath(base) + [memberName]
515536
}
516537

517538
// Declaration reference.
518539
if let declRef = expression.as(DeclReferenceExprSyntax.self),
519-
let name = declRef.simpleIdentifier
540+
let name = declRef.simpleIdentifier?.name
520541
{
521542
return [name]
522543
}
523544

524-
throw IfConfigError.expectedModuleName(syntax: ExprSyntax(expression))
545+
throw IfConfigDiagnostic.expectedModuleName(syntax: ExprSyntax(expression))
525546
}
526547

527548
/// Determine whether the given condition only involves disjunctions that
@@ -553,11 +574,11 @@ private func isConditionDisjunction(
553574
// If we have a call to this function, check whether the argument is one of
554575
// the acceptable values.
555576
if let call = condition.as(FunctionCallExprSyntax.self),
556-
let fnName = call.calledExpression.simpleIdentifierExpr,
577+
let fnName = call.calledExpression.simpleIdentifierExpr?.name,
557578
let callFn = IfConfigFunctions(rawValue: fnName),
558579
callFn == function,
559580
let argExpr = call.arguments.singleUnlabeledExpression,
560-
let arg = argExpr.simpleIdentifierExpr
581+
let arg = argExpr.simpleIdentifierExpr?.name
561582
{
562583
return values.contains(arg)
563584
}
@@ -603,14 +624,14 @@ private func diagnoseLikelySimulatorEnvironmentTest(
603624
return nil
604625
}
605626

606-
return IfConfigError.likelySimulatorPlatform(syntax: ExprSyntax(binOp)).asDiagnostic
627+
return IfConfigDiagnostic.likelySimulatorPlatform(syntax: ExprSyntax(binOp)).asDiagnostic
607628
}
608629

609630
extension IfConfigClauseSyntax {
610631
/// Fold the operators within an #if condition, turning sequence expressions
611632
/// involving the various allowed operators (&&, ||, !) into well-structured
612633
/// binary operators.
613-
public static func foldOperators(
634+
static func foldOperators(
614635
_ condition: some ExprSyntaxProtocol
615636
) -> (folded: ExprSyntax, diagnostics: [Diagnostic]) {
616637
var foldingDiagnostics: [Diagnostic] = []
@@ -622,7 +643,7 @@ extension IfConfigClauseSyntax {
622643
{
623644

624645
foldingDiagnostics.append(
625-
IfConfigError.badInfixOperator(syntax: ExprSyntax(binOp)).asDiagnostic
646+
IfConfigDiagnostic.badInfixOperator(syntax: ExprSyntax(binOp)).asDiagnostic
626647
)
627648
return
628649
}
@@ -635,7 +656,7 @@ extension IfConfigClauseSyntax {
635656
/// Determine whether the given expression, when used as the condition in
636657
/// an inactive `#if` clause, implies that syntax errors are permitted within
637658
/// that region.
638-
public static func syntaxErrorsAllowed(
659+
static func syntaxErrorsAllowed(
639660
_ condition: some ExprSyntaxProtocol
640661
) -> (syntaxErrorsAllowed: Bool, diagnostics: [Diagnostic]) {
641662
let (foldedCondition, foldingDiagnostics) = IfConfigClauseSyntax.foldOperators(condition)
@@ -684,7 +705,7 @@ extension ExprSyntaxProtocol {
684705

685706
// Call syntax is for operations.
686707
if let call = self.as(FunctionCallExprSyntax.self),
687-
let fnName = call.calledExpression.simpleIdentifierExpr,
708+
let fnName = call.calledExpression.simpleIdentifierExpr?.name,
688709
let fn = IfConfigFunctions(rawValue: fnName)
689710
{
690711
return fn.syntaxErrorsAllowed

Sources/SwiftIfConfig/SyntaxLiteralUtils.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ extension LabeledExprListSyntax {
3535

3636
extension ExprSyntax {
3737
/// Whether this is a simple identifier expression and, if so, what the identifier string is.
38-
var simpleIdentifierExpr: String? {
38+
var simpleIdentifierExpr: Identifier? {
3939
guard let identExpr = self.as(DeclReferenceExprSyntax.self) else {
4040
return nil
4141
}
@@ -47,12 +47,11 @@ extension ExprSyntax {
4747
extension DeclReferenceExprSyntax {
4848
/// If this declaration reference is a simple identifier, return that
4949
/// string.
50-
var simpleIdentifier: String? {
50+
var simpleIdentifier: Identifier? {
5151
guard argumentNames == nil else {
5252
return nil
5353
}
5454

55-
/// FIXME: Make this an Identifier so we handle escaping properly.
56-
return baseName.text
55+
return baseName.identifier
5756
}
5857
}

0 commit comments

Comments
 (0)