@@ -249,12 +249,15 @@ extension VersionTuple {
249249/// build configuration itself.
250250/// - Throws: Throws if an error occurs occur during evaluation. The error will
251251/// also be provided to the diagnostic handler before doing so.
252- /// - Returns: Whether the condition holds with the given build configuration.
252+ /// - Returns: A pair of Boolean values. The first describes whether the
253+ /// condition holds with the given build configuration. The second whether
254+ /// the build condition is a "versioned" check that implies that we shouldn't
255+ /// diagnose syntax errors in blocks where the check fails.
253256private func evaluateIfConfig(
254257 condition: ExprSyntax ,
255258 configuration: some BuildConfiguration ,
256259 diagnosticHandler: ( ( Diagnostic ) -> Void ) ?
257- ) throws -> Bool {
260+ ) throws -> ( active : Bool , versioned : Bool ) {
258261 /// Record the error before returning it. Use this for every 'throw' site
259262 /// in this evaluation.
260263 func recordedError( _ error: any Error , at node: some SyntaxProtocol ) -> any Error {
@@ -275,8 +278,8 @@ private func evaluateIfConfig(
275278 /// appropriate diagnostic for the handler before rethrowing it.
276279 func checkConfiguration(
277280 at node: some SyntaxProtocol ,
278- body: ( ) throws -> Bool
279- ) throws -> Bool {
281+ body: ( ) throws -> ( Bool , Bool )
282+ ) throws -> ( active : Bool , versioned : Bool ) {
280283 do {
281284 return try body ( )
282285 } catch let error {
@@ -286,7 +289,7 @@ private func evaluateIfConfig(
286289
287290 // Boolean literals evaluate as-is
288291 if let boolLiteral = condition. as ( BooleanLiteralExprSyntax . self) {
289- return boolLiteral. literalValue
292+ return ( active : boolLiteral. literalValue, versioned : false )
290293 }
291294
292295 // Integer literals aren't allowed, but we recognize them.
@@ -302,7 +305,7 @@ private func evaluateIfConfig(
302305 ) . asDiagnostic
303306 )
304307
305- return result
308+ return ( active : result, versioned : false )
306309 }
307310
308311 // Declaration references are for custom compilation flags.
@@ -312,19 +315,21 @@ private func evaluateIfConfig(
312315
313316 // Evaluate the custom condition. If the build configuration cannot answer this query, fail.
314317 return try checkConfiguration ( at: identExpr) {
315- try configuration. isCustomConditionSet ( name: ident)
318+ ( active : try configuration. isCustomConditionSet ( name: ident) , versioned : false )
316319 }
317320 }
318321
319322 // Logical '!'.
320323 if let prefixOp = condition. as ( PrefixOperatorExprSyntax . self) ,
321324 prefixOp. operator. text == " ! "
322325 {
323- return try ! evaluateIfConfig(
326+ let ( innerActive , innerVersioned ) = try evaluateIfConfig (
324327 condition: prefixOp. expression,
325328 configuration: configuration,
326329 diagnosticHandler: diagnosticHandler
327330 )
331+
332+ return ( active: !innerActive, versioned: innerVersioned)
328333 }
329334
330335 // Logical '&&' and '||'.
@@ -333,25 +338,45 @@ private func evaluateIfConfig(
333338 ( op. operator. text == " && " || op. operator. text == " || " )
334339 {
335340 // Evaluate the left-hand side.
336- let lhsResult = try evaluateIfConfig (
341+ let ( lhsActive , lhsVersioned ) = try evaluateIfConfig (
337342 condition: binOp. leftOperand,
338343 configuration: configuration,
339344 diagnosticHandler: diagnosticHandler
340345 )
341346
342- // Short-circuit evaluation if we know the answer.
343- switch ( lhsResult, op. operator. text) {
344- case ( true , " || " ) : return true
345- case ( false , " && " ) : return false
346- default : break
347+ // Short-circuit evaluation if we know the answer and the left-hand side
348+ // was versioned.
349+ if lhsVersioned {
350+ switch ( lhsActive, op. operator. text) {
351+ case ( true , " || " ) : return ( active: true , versioned: lhsVersioned)
352+ case ( false , " && " ) : return ( active: false , versioned: lhsVersioned)
353+ default : break
354+ }
347355 }
348356
349- // Evaluate the right-hand side and use that result .
350- return try evaluateIfConfig (
357+ // Evaluate the right-hand side.
358+ let ( rhsActive , rhsVersioned ) = try evaluateIfConfig (
351359 condition: binOp. rightOperand,
352360 configuration: configuration,
353361 diagnosticHandler: diagnosticHandler
354362 )
363+
364+ switch op. operator. text {
365+ case " || " :
366+ return (
367+ active: lhsActive || rhsActive,
368+ versioned: lhsVersioned && rhsVersioned
369+ )
370+
371+ case " && " :
372+ return (
373+ active: lhsActive && rhsActive,
374+ versioned: lhsVersioned || rhsVersioned
375+ )
376+
377+ default :
378+ fatalError ( " prevented by condition for getting here " )
379+ }
355380 }
356381
357382 // Look through parentheses.
@@ -371,7 +396,10 @@ private func evaluateIfConfig(
371396 let fn = IfConfigFunctions ( rawValue: fnName)
372397 {
373398 /// Perform a check for an operation that takes a single identifier argument.
374- func doSingleIdentifierArgumentCheck( _ body: ( String ) throws -> Bool , role: String ) throws -> Bool {
399+ func doSingleIdentifierArgumentCheck(
400+ _ body: ( String ) throws -> Bool ,
401+ role: String
402+ ) throws -> ( active: Bool , versioned: Bool ) {
375403 // Ensure that we have a single argument that is a simple identifier.
376404 guard let argExpr = call. arguments. singleUnlabeledExpression,
377405 let arg = argExpr. simpleIdentifierExpr
@@ -382,12 +410,14 @@ private func evaluateIfConfig(
382410 }
383411
384412 return try checkConfiguration ( at: argExpr) {
385- try body ( arg)
413+ ( active : try body ( arg) , versioned : fn . isVersioned )
386414 }
387415 }
388416
389417 /// Perform a check for a version constraint as used in the "swift" or "compiler" version checks.
390- func doVersionComparisonCheck( _ actualVersion: VersionTuple ) throws -> Bool {
418+ func doVersionComparisonCheck(
419+ _ actualVersion: VersionTuple
420+ ) throws -> ( active: Bool , versioned: Bool ) {
391421 // Ensure that we have a single unlabeled argument that is either >= or < as a prefix
392422 // operator applied to a version.
393423 guard let argExpr = call. arguments. singleUnlabeledExpression,
@@ -410,9 +440,9 @@ private func evaluateIfConfig(
410440
411441 switch opToken. text {
412442 case " >= " :
413- return actualVersion >= version
443+ return ( active : actualVersion >= version, versioned : fn . isVersioned )
414444 case " < " :
415- return actualVersion < version
445+ return ( active : actualVersion < version, versioned : fn . isVersioned )
416446 default :
417447 throw recordedError ( . unsupportedVersionOperator( name: fnName, operator: opToken) )
418448 }
@@ -459,7 +489,10 @@ private func evaluateIfConfig(
459489 )
460490 }
461491
462- return configuration. endianness == expectedEndianness
492+ return (
493+ active: configuration. endianness == expectedEndianness,
494+ versioned: fn. isVersioned
495+ )
463496
464497 case . _pointerBitWidth:
465498 // Ensure that we have a single argument that is a simple identifier, which
@@ -479,7 +512,10 @@ private func evaluateIfConfig(
479512 )
480513 }
481514
482- return configuration. targetPointerBitWidth == expectedPointerBitWidth
515+ return (
516+ active: configuration. targetPointerBitWidth == expectedPointerBitWidth,
517+ versioned: fn. isVersioned
518+ )
483519
484520 case . swift:
485521 return try doVersionComparisonCheck ( configuration. languageVersion)
@@ -508,7 +544,10 @@ private func evaluateIfConfig(
508544 let versionString = stringSegment. content. text
509545 let expectedVersion = try VersionTuple ( parsingCompilerBuildVersion: versionString, argExpr)
510546
511- return configuration. compilerVersion >= expectedVersion
547+ return (
548+ active: configuration. compilerVersion >= expectedVersion,
549+ versioned: fn. isVersioned
550+ )
512551
513552 case . canImport:
514553 // Retrieve the first argument, which must not have a label. This is
@@ -577,9 +616,12 @@ private func evaluateIfConfig(
577616 }
578617
579618 return try checkConfiguration ( at: call) {
580- try configuration. canImport (
581- importPath: importPath. map { String ( $0) } ,
582- version: version
619+ (
620+ active: try configuration. canImport (
621+ importPath: importPath. map { String ( $0) } ,
622+ version: version
623+ ) ,
624+ versioned: fn. isVersioned
583625 )
584626 }
585627 }
@@ -602,12 +644,17 @@ extension IfConfigState {
602644 throw error
603645 } . cast ( ExprSyntax . self)
604646
605- let result = try evaluateIfConfig (
647+ let ( active , versioned ) = try evaluateIfConfig (
606648 condition: foldedCondition,
607649 configuration: configuration,
608650 diagnosticHandler: diagnosticHandler
609651 )
610- self = result ? . active : . inactive
652+
653+ switch ( active, versioned) {
654+ case ( true , _) : self = . active
655+ case ( false , false ) : self = . inactive
656+ case ( false , true ) : self = . unparsed
657+ }
611658 }
612659}
613660
@@ -644,7 +691,7 @@ extension IfConfigDeclSyntax {
644691 condition: condition,
645692 configuration: configuration,
646693 diagnosticHandler: diagnosticHandler
647- ) {
694+ ) . active {
648695 return clause
649696 }
650697 }
0 commit comments