@@ -18,15 +18,20 @@ extension SyntaxProtocol {
1818 /// are inactive according to the given build configuration, leaving only
1919 /// the code that is active within that build configuration.
2020 ///
21- /// Returns the syntax node with all inactive regions removed, along with an
22- /// array containing any diagnostics produced along the way.
23- ///
2421 /// If there are errors in the conditions of any configuration
2522 /// clauses, e.g., `#if FOO > 10`, then the condition will be
2623 /// considered to have failed and the clauses's elements will be
2724 /// removed.
25+ /// - Parameters:
26+ /// - configuration: the configuration to apply.
27+ /// - retainFeatureCheckIfConfigs: whether to retain `#if` blocks involving
28+ /// compiler version checks (e.g., `compiler(>=6.0)`) and `$`-based
29+ /// feature checks.
30+ /// - Returns: the syntax node with all inactive regions removed, along with
31+ /// an array containing any diagnostics produced along the way.
2832 public func removingInactive(
29- in configuration: some BuildConfiguration
33+ in configuration: some BuildConfiguration ,
34+ retainFeatureCheckIfConfigs: Bool = false
3035 ) -> ( result: Syntax , diagnostics: [ Diagnostic ] ) {
3136 // First pass: Find all of the active clauses for the #ifs we need to
3237 // visit, along with any diagnostics produced along the way. This process
@@ -41,7 +46,10 @@ extension SyntaxProtocol {
4146
4247 // Second pass: Rewrite the syntax tree by removing the inactive clauses
4348 // from each #if (along with the #ifs themselves).
44- let rewriter = ActiveSyntaxRewriter ( configuration: configuration)
49+ let rewriter = ActiveSyntaxRewriter (
50+ configuration: configuration,
51+ retainFeatureCheckIfConfigs: retainFeatureCheckIfConfigs
52+ )
4553 return (
4654 rewriter. rewrite ( Syntax ( self ) ) ,
4755 visitor. diagnostics
@@ -83,8 +91,12 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
8391 let configuration : Configuration
8492 var diagnostics : [ Diagnostic ] = [ ]
8593
86- init ( configuration: Configuration ) {
94+ /// Whether to retain `#if` blocks containing compiler and feature checks.
95+ var retainFeatureCheckIfConfigs : Bool
96+
97+ init ( configuration: Configuration , retainFeatureCheckIfConfigs: Bool ) {
8798 self . configuration = configuration
99+ self . retainFeatureCheckIfConfigs = retainFeatureCheckIfConfigs
88100 }
89101
90102 private func dropInactive< List: SyntaxCollection > (
@@ -97,7 +109,9 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
97109 let element = node [ elementIndex]
98110
99111 // Find #ifs within the list.
100- if let ifConfigDecl = elementAsIfConfig ( element) {
112+ if let ifConfigDecl = elementAsIfConfig ( element) ,
113+ ( !retainFeatureCheckIfConfigs || !ifConfigDecl. containsFeatureCheck)
114+ {
101115 // Retrieve the active `#if` clause
102116 let ( activeClause, localDiagnostics) = ifConfigDecl. activeClause ( in: configuration)
103117
@@ -262,6 +276,12 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
262276 outerBase: ExprSyntax ? ,
263277 postfixIfConfig: PostfixIfConfigExprSyntax
264278 ) -> ExprSyntax {
279+ // If we're supposed to retain #if configs that are feature checks, and
280+ // this configuration has one, do so.
281+ if retainFeatureCheckIfConfigs && postfixIfConfig. config. containsFeatureCheck {
282+ return ExprSyntax ( postfixIfConfig)
283+ }
284+
265285 // Retrieve the active `if` clause.
266286 let ( activeClause, localDiagnostics) = postfixIfConfig. config. activeClause ( in: configuration)
267287
@@ -307,3 +327,55 @@ class ActiveSyntaxRewriter<Configuration: BuildConfiguration>: SyntaxRewriter {
307327 return visit ( rewrittenNode)
308328 }
309329}
330+
331+ /// Helper class to find a feature or compiler check.
332+ fileprivate class FindFeatureCheckVisitor : SyntaxVisitor {
333+ var foundFeatureCheck = false
334+
335+ override func visit( _ node: DeclReferenceExprSyntax ) -> SyntaxVisitorContinueKind {
336+ // Checks that start with $ are feature checks that should be retained.
337+ if let identifier = node. simpleIdentifier,
338+ let initialChar = identifier. name. first,
339+ initialChar == " $ "
340+ {
341+ foundFeatureCheck = true
342+ return . skipChildren
343+ }
344+
345+ return . visitChildren
346+ }
347+
348+ override func visit( _ node: FunctionCallExprSyntax ) -> SyntaxVisitorContinueKind {
349+ if let calleeDeclRef = node. calledExpression. as ( DeclReferenceExprSyntax . self) ,
350+ let calleeName = calleeDeclRef. simpleIdentifier? . name,
351+ ( calleeName == " compiler " || calleeName == " _compiler_version " )
352+ {
353+ foundFeatureCheck = true
354+ }
355+
356+ return . skipChildren
357+ }
358+ }
359+
360+ extension ExprSyntaxProtocol {
361+ /// Whether any of the nodes in this expression involve compiler or feature
362+ /// checks.
363+ fileprivate var containsFeatureCheck : Bool {
364+ let visitor = FindFeatureCheckVisitor ( viewMode: . fixedUp)
365+ visitor. walk ( self )
366+ return visitor. foundFeatureCheck
367+ }
368+ }
369+
370+ extension IfConfigDeclSyntax {
371+ /// Whether any of the clauses in this #if contain a feature check.
372+ var containsFeatureCheck : Bool {
373+ return clauses. contains { clause in
374+ if let condition = clause. condition {
375+ return condition. containsFeatureCheck
376+ } else {
377+ return false
378+ }
379+ }
380+ }
381+ }
0 commit comments