@@ -123,6 +123,11 @@ func evaluateIfConfig(
123123 let op = binOp. operator. as ( BinaryOperatorExprSyntax . self) ,
124124 ( op. operator. text == " && " || op. operator. text == " || " )
125125 {
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+
126131 // Evaluate the left-hand side.
127132 let ( lhsActive, lhssyntaxErrorsAllowed, lhsDiagnostics) = evaluateIfConfig (
128133 condition: binOp. leftOperand,
@@ -134,9 +139,17 @@ func evaluateIfConfig(
134139 if lhssyntaxErrorsAllowed {
135140 switch ( lhsActive, op. operator. text) {
136141 case ( true , " || " ) :
137- return ( active: true , syntaxErrorsAllowed: lhssyntaxErrorsAllowed, diagnostics: lhsDiagnostics)
142+ return (
143+ active: true ,
144+ syntaxErrorsAllowed: lhssyntaxErrorsAllowed,
145+ diagnostics: extraDiagnostics + lhsDiagnostics
146+ )
138147 case ( false , " && " ) :
139- return ( active: false , syntaxErrorsAllowed: lhssyntaxErrorsAllowed, diagnostics: lhsDiagnostics)
148+ return (
149+ active: false ,
150+ syntaxErrorsAllowed: lhssyntaxErrorsAllowed,
151+ diagnostics: extraDiagnostics + lhsDiagnostics
152+ )
140153 default :
141154 break
142155 }
@@ -153,14 +166,14 @@ func evaluateIfConfig(
153166 return (
154167 active: lhsActive || rhsActive,
155168 syntaxErrorsAllowed: lhssyntaxErrorsAllowed && rhssyntaxErrorsAllowed,
156- diagnostics: lhsDiagnostics + rhsDiagnostics
169+ diagnostics: extraDiagnostics + lhsDiagnostics + rhsDiagnostics
157170 )
158171
159172 case " && " :
160173 return (
161174 active: lhsActive && rhsActive,
162175 syntaxErrorsAllowed: lhssyntaxErrorsAllowed || rhssyntaxErrorsAllowed,
163- diagnostics: lhsDiagnostics + rhsDiagnostics
176+ diagnostics: extraDiagnostics + lhsDiagnostics + rhsDiagnostics
164177 )
165178
166179 default :
@@ -433,6 +446,88 @@ func evaluateIfConfig(
433446 return recordError ( . unknownExpression( condition) )
434447}
435448
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+
436531extension IfConfigClauseSyntax {
437532 /// Fold the operators within an #if condition, turning sequence expressions
438533 /// involving the various allowed operators (&&, ||, !) into well-structured
0 commit comments