@@ -249,12 +249,15 @@ extension VersionTuple {
249
249
/// build configuration itself.
250
250
/// - Throws: Throws if an error occurs occur during evaluation. The error will
251
251
/// 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.
253
256
private func evaluateIfConfig(
254
257
condition: ExprSyntax ,
255
258
configuration: some BuildConfiguration ,
256
259
diagnosticHandler: ( ( Diagnostic ) -> Void ) ?
257
- ) throws -> Bool {
260
+ ) throws -> ( active : Bool , versioned : Bool ) {
258
261
/// Record the error before returning it. Use this for every 'throw' site
259
262
/// in this evaluation.
260
263
func recordedError( _ error: any Error , at node: some SyntaxProtocol ) -> any Error {
@@ -275,8 +278,8 @@ private func evaluateIfConfig(
275
278
/// appropriate diagnostic for the handler before rethrowing it.
276
279
func checkConfiguration(
277
280
at node: some SyntaxProtocol ,
278
- body: ( ) throws -> Bool
279
- ) throws -> Bool {
281
+ body: ( ) throws -> ( Bool , Bool )
282
+ ) throws -> ( active : Bool , versioned : Bool ) {
280
283
do {
281
284
return try body ( )
282
285
} catch let error {
@@ -286,7 +289,7 @@ private func evaluateIfConfig(
286
289
287
290
// Boolean literals evaluate as-is
288
291
if let boolLiteral = condition. as ( BooleanLiteralExprSyntax . self) {
289
- return boolLiteral. literalValue
292
+ return ( active : boolLiteral. literalValue, versioned : false )
290
293
}
291
294
292
295
// Integer literals aren't allowed, but we recognize them.
@@ -302,7 +305,7 @@ private func evaluateIfConfig(
302
305
) . asDiagnostic
303
306
)
304
307
305
- return result
308
+ return ( active : result, versioned : false )
306
309
}
307
310
308
311
// Declaration references are for custom compilation flags.
@@ -312,19 +315,21 @@ private func evaluateIfConfig(
312
315
313
316
// Evaluate the custom condition. If the build configuration cannot answer this query, fail.
314
317
return try checkConfiguration ( at: identExpr) {
315
- try configuration. isCustomConditionSet ( name: ident)
318
+ ( active : try configuration. isCustomConditionSet ( name: ident) , versioned : false )
316
319
}
317
320
}
318
321
319
322
// Logical '!'.
320
323
if let prefixOp = condition. as ( PrefixOperatorExprSyntax . self) ,
321
324
prefixOp. operator. text == " ! "
322
325
{
323
- return try ! evaluateIfConfig(
326
+ let ( innerActive , innerVersioned ) = try evaluateIfConfig (
324
327
condition: prefixOp. expression,
325
328
configuration: configuration,
326
329
diagnosticHandler: diagnosticHandler
327
330
)
331
+
332
+ return ( active: !innerActive, versioned: innerVersioned)
328
333
}
329
334
330
335
// Logical '&&' and '||'.
@@ -333,25 +338,45 @@ private func evaluateIfConfig(
333
338
( op. operator. text == " && " || op. operator. text == " || " )
334
339
{
335
340
// Evaluate the left-hand side.
336
- let lhsResult = try evaluateIfConfig (
341
+ let ( lhsActive , lhsVersioned ) = try evaluateIfConfig (
337
342
condition: binOp. leftOperand,
338
343
configuration: configuration,
339
344
diagnosticHandler: diagnosticHandler
340
345
)
341
346
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
+ }
347
355
}
348
356
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 (
351
359
condition: binOp. rightOperand,
352
360
configuration: configuration,
353
361
diagnosticHandler: diagnosticHandler
354
362
)
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
+ }
355
380
}
356
381
357
382
// Look through parentheses.
@@ -371,7 +396,10 @@ private func evaluateIfConfig(
371
396
let fn = IfConfigFunctions ( rawValue: fnName)
372
397
{
373
398
/// 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 ) {
375
403
// Ensure that we have a single argument that is a simple identifier.
376
404
guard let argExpr = call. arguments. singleUnlabeledExpression,
377
405
let arg = argExpr. simpleIdentifierExpr
@@ -382,12 +410,14 @@ private func evaluateIfConfig(
382
410
}
383
411
384
412
return try checkConfiguration ( at: argExpr) {
385
- try body ( arg)
413
+ ( active : try body ( arg) , versioned : fn . isVersioned )
386
414
}
387
415
}
388
416
389
417
/// 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 ) {
391
421
// Ensure that we have a single unlabeled argument that is either >= or < as a prefix
392
422
// operator applied to a version.
393
423
guard let argExpr = call. arguments. singleUnlabeledExpression,
@@ -410,9 +440,9 @@ private func evaluateIfConfig(
410
440
411
441
switch opToken. text {
412
442
case " >= " :
413
- return actualVersion >= version
443
+ return ( active : actualVersion >= version, versioned : fn . isVersioned )
414
444
case " < " :
415
- return actualVersion < version
445
+ return ( active : actualVersion < version, versioned : fn . isVersioned )
416
446
default :
417
447
throw recordedError ( . unsupportedVersionOperator( name: fnName, operator: opToken) )
418
448
}
@@ -459,7 +489,10 @@ private func evaluateIfConfig(
459
489
)
460
490
}
461
491
462
- return configuration. endianness == expectedEndianness
492
+ return (
493
+ active: configuration. endianness == expectedEndianness,
494
+ versioned: fn. isVersioned
495
+ )
463
496
464
497
case . _pointerBitWidth:
465
498
// Ensure that we have a single argument that is a simple identifier, which
@@ -479,7 +512,10 @@ private func evaluateIfConfig(
479
512
)
480
513
}
481
514
482
- return configuration. targetPointerBitWidth == expectedPointerBitWidth
515
+ return (
516
+ active: configuration. targetPointerBitWidth == expectedPointerBitWidth,
517
+ versioned: fn. isVersioned
518
+ )
483
519
484
520
case . swift:
485
521
return try doVersionComparisonCheck ( configuration. languageVersion)
@@ -508,7 +544,10 @@ private func evaluateIfConfig(
508
544
let versionString = stringSegment. content. text
509
545
let expectedVersion = try VersionTuple ( parsingCompilerBuildVersion: versionString, argExpr)
510
546
511
- return configuration. compilerVersion >= expectedVersion
547
+ return (
548
+ active: configuration. compilerVersion >= expectedVersion,
549
+ versioned: fn. isVersioned
550
+ )
512
551
513
552
case . canImport:
514
553
// Retrieve the first argument, which must not have a label. This is
@@ -577,9 +616,12 @@ private func evaluateIfConfig(
577
616
}
578
617
579
618
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
583
625
)
584
626
}
585
627
}
@@ -602,12 +644,17 @@ extension IfConfigState {
602
644
throw error
603
645
} . cast ( ExprSyntax . self)
604
646
605
- let result = try evaluateIfConfig (
647
+ let ( active , versioned ) = try evaluateIfConfig (
606
648
condition: foldedCondition,
607
649
configuration: configuration,
608
650
diagnosticHandler: diagnosticHandler
609
651
)
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
+ }
611
658
}
612
659
}
613
660
@@ -644,7 +691,7 @@ extension IfConfigDeclSyntax {
644
691
condition: condition,
645
692
configuration: configuration,
646
693
diagnosticHandler: diagnosticHandler
647
- ) {
694
+ ) . active {
648
695
return clause
649
696
}
650
697
}
0 commit comments