@@ -123,6 +123,11 @@ func evaluateIfConfig(
123
123
let op = binOp. operator. as ( BinaryOperatorExprSyntax . self) ,
124
124
( op. operator. text == " && " || op. operator. text == " || " )
125
125
{
126
+ // Check whether this was likely to be a check for targetEnvironment(simulator).
127
+ if let targetEnvironmentDiag = diagnoseLikelySimulatorEnvironmentTest ( binOp) {
128
+ extraDiagnostics. append ( targetEnvironmentDiag)
129
+ }
130
+
126
131
// Evaluate the left-hand side.
127
132
let ( lhsActive, lhssyntaxErrorsAllowed, lhsDiagnostics) = evaluateIfConfig (
128
133
condition: binOp. leftOperand,
@@ -134,9 +139,17 @@ func evaluateIfConfig(
134
139
if lhssyntaxErrorsAllowed {
135
140
switch ( lhsActive, op. operator. text) {
136
141
case ( true , " || " ) :
137
- return ( active: true , syntaxErrorsAllowed: lhssyntaxErrorsAllowed, diagnostics: lhsDiagnostics)
142
+ return (
143
+ active: true ,
144
+ syntaxErrorsAllowed: lhssyntaxErrorsAllowed,
145
+ diagnostics: extraDiagnostics + lhsDiagnostics
146
+ )
138
147
case ( false , " && " ) :
139
- return ( active: false , syntaxErrorsAllowed: lhssyntaxErrorsAllowed, diagnostics: lhsDiagnostics)
148
+ return (
149
+ active: false ,
150
+ syntaxErrorsAllowed: lhssyntaxErrorsAllowed,
151
+ diagnostics: extraDiagnostics + lhsDiagnostics
152
+ )
140
153
default :
141
154
break
142
155
}
@@ -153,14 +166,14 @@ func evaluateIfConfig(
153
166
return (
154
167
active: lhsActive || rhsActive,
155
168
syntaxErrorsAllowed: lhssyntaxErrorsAllowed && rhssyntaxErrorsAllowed,
156
- diagnostics: lhsDiagnostics + rhsDiagnostics
169
+ diagnostics: extraDiagnostics + lhsDiagnostics + rhsDiagnostics
157
170
)
158
171
159
172
case " && " :
160
173
return (
161
174
active: lhsActive && rhsActive,
162
175
syntaxErrorsAllowed: lhssyntaxErrorsAllowed || rhssyntaxErrorsAllowed,
163
- diagnostics: lhsDiagnostics + rhsDiagnostics
176
+ diagnostics: extraDiagnostics + lhsDiagnostics + rhsDiagnostics
164
177
)
165
178
166
179
default :
@@ -433,6 +446,88 @@ func evaluateIfConfig(
433
446
return recordError ( . unknownExpression( condition) )
434
447
}
435
448
449
+ /// Determine whether the given condition only involves disjunctions that
450
+ /// check the given config function against one of the provided values.
451
+ ///
452
+ /// For example, this will match a condition like `os(iOS) || os(tvOS)`
453
+ /// when passed `IfConfigFunctions.os` and `["iOS", "tvOS"]`.
454
+ private func isConditionDisjunction(
455
+ _ condition: some ExprSyntaxProtocol ,
456
+ function: IfConfigFunctions ,
457
+ anyOf values: [ String ]
458
+ ) -> Bool {
459
+ // Recurse into disjunctions. Both sides need to match.
460
+ if let binOp = condition. as ( InfixOperatorExprSyntax . self) ,
461
+ let op = binOp. operator. as ( BinaryOperatorExprSyntax . self) ,
462
+ op. operator. text == " || "
463
+ {
464
+ return isConditionDisjunction ( binOp. leftOperand, function: function, anyOf: values)
465
+ && isConditionDisjunction ( binOp. rightOperand, function: function, anyOf: values)
466
+ }
467
+
468
+ // Look through parentheses.
469
+ if let tuple = condition. as ( TupleExprSyntax . self) , tuple. isParentheses,
470
+ let element = tuple. elements. first
471
+ {
472
+ return isConditionDisjunction ( element. expression, function: function, anyOf: values)
473
+ }
474
+
475
+ // If we have a call to this function, check whether the argument is one of
476
+ // the acceptable values.
477
+ if let call = condition. as ( FunctionCallExprSyntax . self) ,
478
+ let fnName = call. calledExpression. simpleIdentifierExpr,
479
+ let callFn = IfConfigFunctions ( rawValue: fnName) ,
480
+ callFn == function,
481
+ let argExpr = call. arguments. singleUnlabeledExpression,
482
+ let arg = argExpr. simpleIdentifierExpr
483
+ {
484
+ return values. contains ( arg)
485
+ }
486
+
487
+ return false
488
+ }
489
+
490
+ /// If this binary operator looks like it could be replaced by a
491
+ /// targetEnvironment(simulator) check, produce a diagnostic that does so.
492
+ ///
493
+ /// For example, this checks for conditions like:
494
+ ///
495
+ /// ```
496
+ /// #if (os(iOS) || os(tvOS)) && (arch(i386) || arch(x86_64))
497
+ /// ```
498
+ ///
499
+ /// which should be replaced with
500
+ ///
501
+ /// ```
502
+ /// #if targetEnvironment(simulator)
503
+ /// ```
504
+ private func diagnoseLikelySimulatorEnvironmentTest(
505
+ _ binOp: InfixOperatorExprSyntax
506
+ ) -> Diagnostic ? {
507
+ guard let op = binOp. operator. as ( BinaryOperatorExprSyntax . self) ,
508
+ op. operator. text == " && "
509
+ else {
510
+ return nil
511
+ }
512
+
513
+ func isSimulatorPlatformOSTest( _ condition: ExprSyntax ) -> Bool {
514
+ return isConditionDisjunction ( condition, function: . os, anyOf: [ " iOS " , " tvOS " , " watchOS " ] )
515
+ }
516
+
517
+ func isSimulatorPlatformArchTest( _ condition: ExprSyntax ) -> Bool {
518
+ return isConditionDisjunction ( condition, function: . arch, anyOf: [ " i386 " , " x86_64 " ] )
519
+ }
520
+
521
+ guard
522
+ ( isSimulatorPlatformOSTest ( binOp. leftOperand) && isSimulatorPlatformArchTest ( binOp. rightOperand) )
523
+ || ( isSimulatorPlatformOSTest ( binOp. rightOperand) && isSimulatorPlatformArchTest ( binOp. leftOperand) )
524
+ else {
525
+ return nil
526
+ }
527
+
528
+ return IfConfigError . likelySimulatorPlatform ( syntax: ExprSyntax ( binOp) ) . asDiagnostic
529
+ }
530
+
436
531
extension IfConfigClauseSyntax {
437
532
/// Fold the operators within an #if condition, turning sequence expressions
438
533
/// involving the various allowed operators (&&, ||, !) into well-structured
0 commit comments