Skip to content

Make the "are syntax errors allowed?" condition independent of evaluation #2773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions Sources/SwiftIfConfig/ConfiguredRegions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,18 @@ fileprivate class ConfiguredRegionVisitor<Configuration: BuildConfiguration>: Sy
// If we're in an active region, find the active clause. Otherwise,
// there isn't one.
let activeClause = inActiveRegion ? node.activeClause(in: configuration).clause : nil
var foundActive = false
var syntaxErrorsAllowed = false
for clause in node.clauses {
// If we haven't found the active clause yet, syntax errors are allowed
// depending on this clause.
if !foundActive {
syntaxErrorsAllowed =
clause.condition.map {
IfConfigClauseSyntax.syntaxErrorsAllowed($0).syntaxErrorsAllowed
} ?? false
}

// If this is the active clause, record it and then recurse into the
// elements.
if clause == activeClause {
Expand All @@ -77,14 +88,10 @@ fileprivate class ConfiguredRegionVisitor<Configuration: BuildConfiguration>: Sy
walk(elements)
}

foundActive = true
continue
}

// For inactive clauses, distinguish between inactive and unparsed.
let syntaxErrorsAllowed = clause.syntaxErrorsAllowed(
configuration: configuration
).syntaxErrorsAllowed

// If this is within an active region, or this is an unparsed region,
// record it.
if inActiveRegion || syntaxErrorsAllowed {
Expand Down
6 changes: 5 additions & 1 deletion Sources/SwiftIfConfig/IfConfigDecl+IfConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ extension IfConfigDeclSyntax {
return (clause, diagnostics: diagnostics)
}

// Apply operator folding for !/&&/||.
let (foldedCondition, foldingDiagnostics) = IfConfigClauseSyntax.foldOperators(condition)
diagnostics.append(contentsOf: foldingDiagnostics)

// If this condition evaluates true, return this clause.
let (isActive, _, localDiagnostics) = evaluateIfConfig(
condition: condition,
condition: foldedCondition,
configuration: configuration
)
diagnostics.append(contentsOf: localDiagnostics)
Expand Down
80 changes: 69 additions & 11 deletions Sources/SwiftIfConfig/IfConfigEvaluation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//

import SwiftDiagnostics
import SwiftOperators
import SwiftSyntax

/// Evaluate the condition of an `#if`.
Expand Down Expand Up @@ -433,20 +434,77 @@ func evaluateIfConfig(
}

extension IfConfigClauseSyntax {
/// Determine whether this condition is "syntaxErrorsAllowed".
func syntaxErrorsAllowed(
configuration: some BuildConfiguration
/// Fold the operators within an #if condition, turning sequence expressions
/// involving the various allowed operators (&&, ||, !) into well-structured
/// binary operators.
public static func foldOperators(
_ condition: some ExprSyntaxProtocol
) -> (folded: ExprSyntax, diagnostics: [Diagnostic]) {
var foldingDiagnostics: [Diagnostic] = []
let foldedCondition = OperatorTable.logicalOperators.foldAll(condition) { error in
foldingDiagnostics.append(contentsOf: error.asDiagnostics(at: condition))
}.cast(ExprSyntax.self)
return (folded: foldedCondition, diagnostics: foldingDiagnostics)
}

/// Determine whether the given expression, when used as the condition in
/// an inactive `#if` clause, implies that syntax errors are permitted within
/// that region.
public static func syntaxErrorsAllowed(
_ condition: some ExprSyntaxProtocol
) -> (syntaxErrorsAllowed: Bool, diagnostics: [Diagnostic]) {
guard let condition else {
return (syntaxErrorsAllowed: false, diagnostics: [])
}
let (foldedCondition, foldingDiagnostics) = IfConfigClauseSyntax.foldOperators(condition)

// Evaluate this condition against the build configuration.
let (_, syntaxErrorsAllowed, diagnostics) = evaluateIfConfig(
condition: condition,
configuration: configuration
return (
!foldingDiagnostics.isEmpty || foldedCondition.allowsSyntaxErrorsFolded,
foldingDiagnostics
)
}
}

extension ExprSyntaxProtocol {
/// Determine whether this expression, when used as a condition within a #if
/// that evaluates false, implies that the code contained in that `#if`
///
/// Check whether of allowsSyntaxErrors(_:) that assumes that inputs have
/// already been operator-folded.
var allowsSyntaxErrorsFolded: Bool {
// Logical '!'.
if let prefixOp = self.as(PrefixOperatorExprSyntax.self),
prefixOp.operator.text == "!"
{
return prefixOp.expression.allowsSyntaxErrorsFolded
}

// Logical '&&' and '||'.
if let binOp = self.as(InfixOperatorExprSyntax.self),
let op = binOp.operator.as(BinaryOperatorExprSyntax.self)
{
switch op.operator.text {
case "&&":
return binOp.leftOperand.allowsSyntaxErrorsFolded || binOp.rightOperand.allowsSyntaxErrorsFolded
case "||":
return binOp.leftOperand.allowsSyntaxErrorsFolded && binOp.rightOperand.allowsSyntaxErrorsFolded
default:
return false
}
}

// Look through parentheses.
if let tuple = self.as(TupleExprSyntax.self), tuple.isParentheses,
let element = tuple.elements.first
{
return element.expression.allowsSyntaxErrorsFolded
}

// Call syntax is for operations.
if let call = self.as(FunctionCallExprSyntax.self),
let fnName = call.calledExpression.simpleIdentifierExpr,
let fn = IfConfigFunctions(rawValue: fnName)
{
return fn.syntaxErrorsAllowed
}

return (syntaxErrorsAllowed, diagnostics)
return false
}
}
5 changes: 1 addition & 4 deletions Sources/SwiftIfConfig/IfConfigRegionState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ public enum IfConfigRegionState {
in configuration: some BuildConfiguration
) -> (state: IfConfigRegionState, syntaxErrorsAllowed: Bool, diagnostics: [Diagnostic]) {
// Apply operator folding for !/&&/||.
var foldingDiagnostics: [Diagnostic] = []
let foldedCondition = OperatorTable.logicalOperators.foldAll(condition) { error in
foldingDiagnostics.append(contentsOf: error.asDiagnostics(at: condition))
}.cast(ExprSyntax.self)
let (foldedCondition, foldingDiagnostics) = IfConfigClauseSyntax.foldOperators(condition)

let (active, syntaxErrorsAllowed, evalDiagnostics) = evaluateIfConfig(
condition: foldedCondition,
Expand Down
8 changes: 4 additions & 4 deletions Sources/SwiftIfConfig/SyntaxProtocol+IfConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ extension SyntaxProtocol {
// This was not the active clause, so we know that we're in an
// inactive block. If syntax errors aren't allowable, this is an
// unparsed region.
let (syntaxErrorsAllowed, localDiagnostics) = ifConfigClause.syntaxErrorsAllowed(
configuration: configuration
)
diagnostics.append(contentsOf: localDiagnostics)
let syntaxErrorsAllowed =
ifConfigClause.condition.map {
IfConfigClauseSyntax.syntaxErrorsAllowed($0).syntaxErrorsAllowed
} ?? false

if syntaxErrorsAllowed {
return (.unparsed, diagnostics)
Expand Down