Skip to content

Commit 9fdc7f4

Browse files
authored
Merge pull request #2773 from DougGregor/are-syntax-errors-allowed
Make the "are syntax errors allowed?" condition independent of evaluation
2 parents 8a2ffd5 + 63bb090 commit 9fdc7f4

5 files changed

+91
-25
lines changed

Sources/SwiftIfConfig/ConfiguredRegions.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,18 @@ fileprivate class ConfiguredRegionVisitor<Configuration: BuildConfiguration>: Sy
6565
// If we're in an active region, find the active clause. Otherwise,
6666
// there isn't one.
6767
let activeClause = inActiveRegion ? node.activeClause(in: configuration).clause : nil
68+
var foundActive = false
69+
var syntaxErrorsAllowed = false
6870
for clause in node.clauses {
71+
// If we haven't found the active clause yet, syntax errors are allowed
72+
// depending on this clause.
73+
if !foundActive {
74+
syntaxErrorsAllowed =
75+
clause.condition.map {
76+
IfConfigClauseSyntax.syntaxErrorsAllowed($0).syntaxErrorsAllowed
77+
} ?? false
78+
}
79+
6980
// If this is the active clause, record it and then recurse into the
7081
// elements.
7182
if clause == activeClause {
@@ -77,14 +88,10 @@ fileprivate class ConfiguredRegionVisitor<Configuration: BuildConfiguration>: Sy
7788
walk(elements)
7889
}
7990

91+
foundActive = true
8092
continue
8193
}
8294

83-
// For inactive clauses, distinguish between inactive and unparsed.
84-
let syntaxErrorsAllowed = clause.syntaxErrorsAllowed(
85-
configuration: configuration
86-
).syntaxErrorsAllowed
87-
8895
// If this is within an active region, or this is an unparsed region,
8996
// record it.
9097
if inActiveRegion || syntaxErrorsAllowed {

Sources/SwiftIfConfig/IfConfigDecl+IfConfig.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@ extension IfConfigDeclSyntax {
4343
return (clause, diagnostics: diagnostics)
4444
}
4545

46+
// Apply operator folding for !/&&/||.
47+
let (foldedCondition, foldingDiagnostics) = IfConfigClauseSyntax.foldOperators(condition)
48+
diagnostics.append(contentsOf: foldingDiagnostics)
49+
4650
// If this condition evaluates true, return this clause.
4751
let (isActive, _, localDiagnostics) = evaluateIfConfig(
48-
condition: condition,
52+
condition: foldedCondition,
4953
configuration: configuration
5054
)
5155
diagnostics.append(contentsOf: localDiagnostics)

Sources/SwiftIfConfig/IfConfigEvaluation.swift

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import SwiftDiagnostics
14+
import SwiftOperators
1415
import SwiftSyntax
1516

1617
/// Evaluate the condition of an `#if`.
@@ -433,20 +434,77 @@ func evaluateIfConfig(
433434
}
434435

435436
extension IfConfigClauseSyntax {
436-
/// Determine whether this condition is "syntaxErrorsAllowed".
437-
func syntaxErrorsAllowed(
438-
configuration: some BuildConfiguration
437+
/// Fold the operators within an #if condition, turning sequence expressions
438+
/// involving the various allowed operators (&&, ||, !) into well-structured
439+
/// binary operators.
440+
public static func foldOperators(
441+
_ condition: some ExprSyntaxProtocol
442+
) -> (folded: ExprSyntax, diagnostics: [Diagnostic]) {
443+
var foldingDiagnostics: [Diagnostic] = []
444+
let foldedCondition = OperatorTable.logicalOperators.foldAll(condition) { error in
445+
foldingDiagnostics.append(contentsOf: error.asDiagnostics(at: condition))
446+
}.cast(ExprSyntax.self)
447+
return (folded: foldedCondition, diagnostics: foldingDiagnostics)
448+
}
449+
450+
/// Determine whether the given expression, when used as the condition in
451+
/// an inactive `#if` clause, implies that syntax errors are permitted within
452+
/// that region.
453+
public static func syntaxErrorsAllowed(
454+
_ condition: some ExprSyntaxProtocol
439455
) -> (syntaxErrorsAllowed: Bool, diagnostics: [Diagnostic]) {
440-
guard let condition else {
441-
return (syntaxErrorsAllowed: false, diagnostics: [])
442-
}
456+
let (foldedCondition, foldingDiagnostics) = IfConfigClauseSyntax.foldOperators(condition)
443457

444-
// Evaluate this condition against the build configuration.
445-
let (_, syntaxErrorsAllowed, diagnostics) = evaluateIfConfig(
446-
condition: condition,
447-
configuration: configuration
458+
return (
459+
!foldingDiagnostics.isEmpty || foldedCondition.allowsSyntaxErrorsFolded,
460+
foldingDiagnostics
448461
)
462+
}
463+
}
464+
465+
extension ExprSyntaxProtocol {
466+
/// Determine whether this expression, when used as a condition within a #if
467+
/// that evaluates false, implies that the code contained in that `#if`
468+
///
469+
/// Check whether of allowsSyntaxErrors(_:) that assumes that inputs have
470+
/// already been operator-folded.
471+
var allowsSyntaxErrorsFolded: Bool {
472+
// Logical '!'.
473+
if let prefixOp = self.as(PrefixOperatorExprSyntax.self),
474+
prefixOp.operator.text == "!"
475+
{
476+
return prefixOp.expression.allowsSyntaxErrorsFolded
477+
}
478+
479+
// Logical '&&' and '||'.
480+
if let binOp = self.as(InfixOperatorExprSyntax.self),
481+
let op = binOp.operator.as(BinaryOperatorExprSyntax.self)
482+
{
483+
switch op.operator.text {
484+
case "&&":
485+
return binOp.leftOperand.allowsSyntaxErrorsFolded || binOp.rightOperand.allowsSyntaxErrorsFolded
486+
case "||":
487+
return binOp.leftOperand.allowsSyntaxErrorsFolded && binOp.rightOperand.allowsSyntaxErrorsFolded
488+
default:
489+
return false
490+
}
491+
}
492+
493+
// Look through parentheses.
494+
if let tuple = self.as(TupleExprSyntax.self), tuple.isParentheses,
495+
let element = tuple.elements.first
496+
{
497+
return element.expression.allowsSyntaxErrorsFolded
498+
}
499+
500+
// Call syntax is for operations.
501+
if let call = self.as(FunctionCallExprSyntax.self),
502+
let fnName = call.calledExpression.simpleIdentifierExpr,
503+
let fn = IfConfigFunctions(rawValue: fnName)
504+
{
505+
return fn.syntaxErrorsAllowed
506+
}
449507

450-
return (syntaxErrorsAllowed, diagnostics)
508+
return false
451509
}
452510
}

Sources/SwiftIfConfig/IfConfigRegionState.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@ public enum IfConfigRegionState {
3232
in configuration: some BuildConfiguration
3333
) -> (state: IfConfigRegionState, syntaxErrorsAllowed: Bool, diagnostics: [Diagnostic]) {
3434
// Apply operator folding for !/&&/||.
35-
var foldingDiagnostics: [Diagnostic] = []
36-
let foldedCondition = OperatorTable.logicalOperators.foldAll(condition) { error in
37-
foldingDiagnostics.append(contentsOf: error.asDiagnostics(at: condition))
38-
}.cast(ExprSyntax.self)
35+
let (foldedCondition, foldingDiagnostics) = IfConfigClauseSyntax.foldOperators(condition)
3936

4037
let (active, syntaxErrorsAllowed, evalDiagnostics) = evaluateIfConfig(
4138
condition: foldedCondition,

Sources/SwiftIfConfig/SyntaxProtocol+IfConfig.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ extension SyntaxProtocol {
5757
// This was not the active clause, so we know that we're in an
5858
// inactive block. If syntax errors aren't allowable, this is an
5959
// unparsed region.
60-
let (syntaxErrorsAllowed, localDiagnostics) = ifConfigClause.syntaxErrorsAllowed(
61-
configuration: configuration
62-
)
63-
diagnostics.append(contentsOf: localDiagnostics)
60+
let syntaxErrorsAllowed =
61+
ifConfigClause.condition.map {
62+
IfConfigClauseSyntax.syntaxErrorsAllowed($0).syntaxErrorsAllowed
63+
} ?? false
6464

6565
if syntaxErrorsAllowed {
6666
return (.unparsed, diagnostics)

0 commit comments

Comments
 (0)