From 354e4fa08171fb753f3811e76e2f98ce38f943bd Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 23 Aug 2024 17:29:19 -0700 Subject: [PATCH 1/5] Rework the configured regions computation to better match the compiler The compiler's heuristics for inactive vs. unparsed regions differ from the implementation in SwiftIfConfig and have changed fairly recently. Rework the implementation here to match what the compiler does. --- Sources/SwiftIfConfig/ConfiguredRegions.swift | 96 +++++++++++++------ .../SwiftIfConfigTest/ActiveRegionTests.swift | 23 ++++- 2 files changed, 88 insertions(+), 31 deletions(-) diff --git a/Sources/SwiftIfConfig/ConfiguredRegions.swift b/Sources/SwiftIfConfig/ConfiguredRegions.swift index f4553e09a7b..aa036dd4dc0 100644 --- a/Sources/SwiftIfConfig/ConfiguredRegions.swift +++ b/Sources/SwiftIfConfig/ConfiguredRegions.swift @@ -56,58 +56,96 @@ fileprivate class ConfiguredRegionVisitor: Sy /// Whether we are currently within an active region. var inActiveRegion = true + // All diagnostics encountered along the way. + var diagnostics: [Diagnostic] = [] + init(configuration: Configuration) { self.configuration = configuration super.init(viewMode: .sourceAccurate) } override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind { - // 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 + // Walk through the clauses to find the active one. 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 - } + let isActive: Bool + if let condition = clause.condition { + if !foundActive { + // Fold operators so we can evaluate this #if condition. + let (foldedCondition, foldDiagnostics) = IfConfigClauseSyntax.foldOperators(condition) + diagnostics.append(contentsOf: foldDiagnostics) - // If this is the active clause, record it and then recurse into the - // elements. - if clause == activeClause { - assert(inActiveRegion) + // In an active region, evaluate the condition to determine whether + // this clause is active. Otherwise, this clause is inactive. + // inactive. + if inActiveRegion { + let (thisIsActive, _, evalDiagnostics) = evaluateIfConfig( + condition: foldedCondition, + configuration: configuration + ) + diagnostics.append(contentsOf: evalDiagnostics) - regions.append((clause, .active)) + // Determine if there was an error that prevented us from + // evaluating the condition. If so, we'll allow syntax errors + // from here on out. + let hadError = + foldDiagnostics.contains { diag in + diag.diagMessage.severity == .error + } + || evalDiagnostics.contains { diag in + diag.diagMessage.severity == .error + } - if let elements = clause.elements { - walk(elements) - } + if hadError { + isActive = false + syntaxErrorsAllowed = true + } else { + isActive = thisIsActive - foundActive = true - continue + // Determine whether syntax errors are allowed. + syntaxErrorsAllowed = foldedCondition.allowsSyntaxErrorsFolded + } + } else { + isActive = false + + // Determine whether syntax errors are allowed, even though we + // skipped evaluation of the actual condition. + syntaxErrorsAllowed = foldedCondition.allowsSyntaxErrorsFolded + } + } else { + // We already found an active condition, so this is inactive. + isActive = false + } + } else { + // This is an #else. It's active if we haven't found an active clause + // yet and are in an active region. + isActive = !foundActive && inActiveRegion } - // If this is within an active region, or this is an unparsed region, - // record it. - if inActiveRegion || syntaxErrorsAllowed { - regions.append((clause, syntaxErrorsAllowed ? .unparsed : .inactive)) + // Determine and record the current state. + let currentState: IfConfigRegionState + switch (isActive, syntaxErrorsAllowed) { + case (true, _): currentState = .active + case (false, false): currentState = .inactive + case (false, true): currentState = .unparsed } + regions.append((clause, currentState)) - // Recurse into inactive (but not unparsed) regions to find any - // unparsed regions below. - if !syntaxErrorsAllowed, let elements = clause.elements { + // If this is a parsed region, recurse into it. + if currentState != .unparsed, let elements = clause.elements { let priorInActiveRegion = inActiveRegion - inActiveRegion = false + inActiveRegion = isActive defer { inActiveRegion = priorInActiveRegion } walk(elements) } + + // Note when we found an active clause. + if isActive { + foundActive = true + } } return .skipChildren diff --git a/Tests/SwiftIfConfigTest/ActiveRegionTests.swift b/Tests/SwiftIfConfigTest/ActiveRegionTests.swift index 33fd7c1b7f8..f2e5b55ff12 100644 --- a/Tests/SwiftIfConfigTest/ActiveRegionTests.swift +++ b/Tests/SwiftIfConfigTest/ActiveRegionTests.swift @@ -49,7 +49,7 @@ public class ActiveRegionTests: XCTestCase { "2️⃣": .active, "3️⃣": .inactive, "4️⃣": .unparsed, - "5️⃣": .inactive, + "5️⃣": .unparsed, "6️⃣": .active, ] ) @@ -77,7 +77,7 @@ public class ActiveRegionTests: XCTestCase { "2️⃣": .active, "3️⃣": .inactive, "4️⃣": .unparsed, - "5️⃣": .inactive, + "5️⃣": .unparsed, "6️⃣": .active, ] ) @@ -100,6 +100,25 @@ public class ActiveRegionTests: XCTestCase { ] ) } + + func testActiveRegionUnparsed() throws { + try assertActiveCode( + """ + #if false + #if compiler(>=4.1) + 1️⃣let _: Int = 1 + #else + // There should be no error here. + 2️⃣foo bar + #endif + #endif + """, + states: [ + "1️⃣": .unparsed, + "2️⃣": .unparsed, + ] + ) + } } /// Assert that the various marked positions in the source code have the From 049ed841a10bfd58c6e4fd004b8244785cb71a95 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 23 Aug 2024 17:54:44 -0700 Subject: [PATCH 2/5] Turn the "configured regions" into a proper struct Move the "isActive" check for configured regions into this new struct, and also capture all of the diagnostics. --- Sources/SwiftIfConfig/ConfiguredRegions.swift | 64 ++++++++++++++++++- .../SyntaxProtocol+IfConfig.swift | 16 +---- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/Sources/SwiftIfConfig/ConfiguredRegions.swift b/Sources/SwiftIfConfig/ConfiguredRegions.swift index aa036dd4dc0..0b86366c511 100644 --- a/Sources/SwiftIfConfig/ConfiguredRegions.swift +++ b/Sources/SwiftIfConfig/ConfiguredRegions.swift @@ -13,6 +13,63 @@ import SwiftDiagnostics import SwiftSyntax +/// Describes all of the #if/#elseif/#else clauses within the given syntax node, +/// indicating their active state. This operation will recurse into all +/// clauses to indicate regions of active / inactive / unparsed code. +/// +/// For example, given code like the following: +/// #if DEBUG +/// #if A +/// func f() +/// #elseif B +/// func g() +/// #elseif compiler(>= 12.0) +/// please print the number after 41 +/// #endif +/// #else +/// #endif +/// +/// If the configuration options `DEBUG` and `B` are provided, but `A` is not, +/// and the compiler version is less than 12.0, the results will be contain: +/// - Active region for the `#if DEBUG`. +/// - Inactive region for the `#if A`. +/// - Active region for the `#elseif B`. +/// - Unparsed region for the `#elseif compiler(>= 12.0)`. +/// - Inactive region for the final `#else`. +public struct ConfiguredRegions { + let regions: [Element] + + /// The set of diagnostics produced when evaluating the configured regions. + public let diagnostics: [Diagnostic] + + /// Determine whether the given syntax node is active within the configured + /// regions. + public func isActive(_ node: some SyntaxProtocol) -> IfConfigRegionState { + var currentState: IfConfigRegionState = .active + for (ifClause, state) in regions { + if node.position < ifClause.position { + return currentState + } + + if node.position <= ifClause.endPosition { + currentState = state + } + } + + return currentState + } +} + +extension ConfiguredRegions: RandomAccessCollection { + public typealias Element = (IfConfigClauseSyntax, IfConfigRegionState) + public var startIndex: Int { regions.startIndex } + public var endIndex: Int { regions.endIndex } + + public subscript(index: Int) -> Element { + regions[index] + } +} + extension SyntaxProtocol { /// Find all of the #if/#elseif/#else clauses within the given syntax node, /// indicating their active state. This operation will recurse into all @@ -39,10 +96,13 @@ extension SyntaxProtocol { /// - Inactive region for the final `#else`. public func configuredRegions( in configuration: some BuildConfiguration - ) -> [(IfConfigClauseSyntax, IfConfigRegionState)] { + ) -> ConfiguredRegions { let visitor = ConfiguredRegionVisitor(configuration: configuration) visitor.walk(self) - return visitor.regions + return ConfiguredRegions( + regions: visitor.regions, + diagnostics: visitor.diagnostics + ) } } diff --git a/Sources/SwiftIfConfig/SyntaxProtocol+IfConfig.swift b/Sources/SwiftIfConfig/SyntaxProtocol+IfConfig.swift index 4f87ad40877..53d3ff8abd3 100644 --- a/Sources/SwiftIfConfig/SyntaxProtocol+IfConfig.swift +++ b/Sources/SwiftIfConfig/SyntaxProtocol+IfConfig.swift @@ -82,20 +82,10 @@ extension SyntaxProtocol { /// If you are querying whether many syntax nodes in a particular file are /// active, consider calling `configuredRegions(in:)` once and using /// this function. For occasional queries, use `isActive(in:)`. + @available(*, deprecated, message: "Please use ConfiguredRegions.isActive(_:)") public func isActive( - inConfiguredRegions regions: [(IfConfigClauseSyntax, IfConfigRegionState)] + inConfiguredRegions regions: ConfiguredRegions ) -> IfConfigRegionState { - var currentState: IfConfigRegionState = .active - for (ifClause, state) in regions { - if self.position < ifClause.position { - return currentState - } - - if self.position <= ifClause.endPosition { - currentState = state - } - } - - return currentState + regions.isActive(self) } } From 5a19857bc6c15d45a04347bd0c15de5779504e48 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 23 Aug 2024 22:10:07 -0700 Subject: [PATCH 3/5] Reimplement `SyntaxProtocol.isActive(in:)` in terms of ConfiguredRegions. The compiler's semantics for determining unparsed vs. inactive regions require looking all the way up the `#if` tree to the root, and the bottom-up implementation of `isActive(in:)` was failing to match the semantics of configured regions. Use `ConfiguredRegions` directly, with a note that the performance of `SyntaxProtocol.isActive(in:)` is not good. I'm still considering whether this API should go away entirely. --- Sources/SwiftIfConfig/ConfiguredRegions.swift | 4 +- .../SyntaxProtocol+IfConfig.swift | 42 ++++--------------- 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/Sources/SwiftIfConfig/ConfiguredRegions.swift b/Sources/SwiftIfConfig/ConfiguredRegions.swift index 0b86366c511..729167210a1 100644 --- a/Sources/SwiftIfConfig/ConfiguredRegions.swift +++ b/Sources/SwiftIfConfig/ConfiguredRegions.swift @@ -51,7 +51,9 @@ public struct ConfiguredRegions { return currentState } - if node.position <= ifClause.endPosition { + let ifRegionStart = + ifClause.condition?.endPosition ?? ifClause.elements?._syntaxNode.position ?? ifClause.poundKeyword.endPosition + if node.position >= ifRegionStart && node.position <= ifClause.endPosition { currentState = state } } diff --git a/Sources/SwiftIfConfig/SyntaxProtocol+IfConfig.swift b/Sources/SwiftIfConfig/SyntaxProtocol+IfConfig.swift index 53d3ff8abd3..801e626ac15 100644 --- a/Sources/SwiftIfConfig/SyntaxProtocol+IfConfig.swift +++ b/Sources/SwiftIfConfig/SyntaxProtocol+IfConfig.swift @@ -36,44 +36,16 @@ extension SyntaxProtocol { /// If the compiler version is smaller than 12.0, then `isActive` on any of the tokens within /// that `#elseif` block would return "unparsed", because that syntax should not (conceptually) /// be parsed. + /// + /// Note that this function requires processing all #ifs from the root node + /// of the syntax tree down to the current node. If performing more than a + /// small number of `isActive(_:)` queries, please form a `ConfiguredRegions` + /// instance and use `ConfiguredRegions.isActive(_:)` instead. public func isActive( in configuration: some BuildConfiguration ) -> (state: IfConfigRegionState, diagnostics: [Diagnostic]) { - var currentNode: Syntax = Syntax(self) - var currentState: IfConfigRegionState = .active - var diagnostics: [Diagnostic] = [] - - while let parent = currentNode.parent { - // If the parent is an `#if` configuration, check whether our current - // clause is active. If not, we're in an inactive region. We also - // need to determine whether an inactive region should be parsed or not. - if let ifConfigClause = currentNode.as(IfConfigClauseSyntax.self), - let ifConfigDecl = ifConfigClause.parent?.parent?.as(IfConfigDeclSyntax.self) - { - let (activeClause, localDiagnostics) = ifConfigDecl.activeClause(in: configuration) - diagnostics.append(contentsOf: localDiagnostics) - - if activeClause != ifConfigClause { - // 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 = - ifConfigClause.condition.map { - IfConfigClauseSyntax.syntaxErrorsAllowed($0).syntaxErrorsAllowed - } ?? false - - if syntaxErrorsAllowed { - return (.unparsed, diagnostics) - } - - currentState = .inactive - } - } - - currentNode = parent - } - - return (currentState, diagnostics) + let configuredRegions = root.configuredRegions(in: configuration) + return (configuredRegions.isActive(self), configuredRegions.diagnostics) } /// Determine whether the given syntax node is active given a set of From 43ce0d6f0273f135545528d38e4aedae4259a631 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 23 Aug 2024 22:43:51 -0700 Subject: [PATCH 4/5] Provide TokenSyntax nodes for the import path in BuildConfiguration.canImport() The compiler produces diagnostics for `canImport` checks, which requires more source location information than was previously provided via `BuildConfiguration.canImport`. Add in TokenSyntax nodes for each of the elements in the import path so that the compiler has the syntax node information it needs. --- Sources/SwiftIfConfig/BuildConfiguration.swift | 11 ++++++----- Sources/SwiftIfConfig/IfConfigEvaluation.swift | 16 ++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Sources/SwiftIfConfig/BuildConfiguration.swift b/Sources/SwiftIfConfig/BuildConfiguration.swift index d94158801c3..d15b35f4fca 100644 --- a/Sources/SwiftIfConfig/BuildConfiguration.swift +++ b/Sources/SwiftIfConfig/BuildConfiguration.swift @@ -9,6 +9,7 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import SwiftSyntax /// Describes the ordering of a sequence of bytes that make up a word of /// storage for a particular architecture. @@ -114,15 +115,15 @@ public protocol BuildConfiguration { /// information, which will translate into the `version` argument. /// /// - Parameters: - /// - importPath: A nonempty sequence of identifiers describing the - /// imported module, which was written in source as a dotted sequence, - /// e.g., `UIKit.UIViewController` will be passed in as the import path - /// array `["UIKit", "UIViewController"]`. + /// - importPath: A nonempty sequence of (token, identifier) pairs + /// describing the imported module, which was written in source as a + /// dotted sequence, e.g., `UIKit.UIViewController` will be passed in as + /// the import path array `[(token, "UIKit"), (token, "UIViewController")]`. /// - version: The version restriction on the imported module. For the /// normal `canImport()` syntax, this will always be /// `CanImportVersion.unversioned`. /// - Returns: Whether the module can be imported. - func canImport(importPath: [String], version: CanImportVersion) throws -> Bool + func canImport(importPath: [(TokenSyntax, String)], version: CanImportVersion) throws -> Bool /// Determine whether the given name is the active target OS (e.g., Linux, iOS). /// diff --git a/Sources/SwiftIfConfig/IfConfigEvaluation.swift b/Sources/SwiftIfConfig/IfConfigEvaluation.swift index 4102dfae922..fe60df79edc 100644 --- a/Sources/SwiftIfConfig/IfConfigEvaluation.swift +++ b/Sources/SwiftIfConfig/IfConfigEvaluation.swift @@ -443,7 +443,7 @@ func evaluateIfConfig( } // Extract the import path. - let importPath: [String] + let importPath: [(TokenSyntax, String)] do { importPath = try extractImportPath(firstArg.expression) } catch { @@ -502,7 +502,7 @@ func evaluateIfConfig( return checkConfiguration(at: call) { ( active: try configuration.canImport( - importPath: importPath.map { String($0) }, + importPath: importPath, version: version ), syntaxErrorsAllowed: fn.syntaxErrorsAllowed @@ -540,22 +540,22 @@ extension SyntaxProtocol { } /// Given an expression with the expected form A.B.C, extract the import path -/// ["A", "B", "C"] from it. Throws an error if the expression doesn't match -/// this form. -private func extractImportPath(_ expression: some ExprSyntaxProtocol) throws -> [String] { +/// ["A", "B", "C"] from it with the token syntax nodes for each name. +/// Throws an error if the expression doesn't match this form. +private func extractImportPath(_ expression: some ExprSyntaxProtocol) throws -> [(TokenSyntax, String)] { // Member access. if let memberAccess = expression.as(MemberAccessExprSyntax.self), let base = memberAccess.base, let memberName = memberAccess.declName.simpleIdentifier?.name { - return try extractImportPath(base) + [memberName] + return try extractImportPath(base) + [(memberAccess.declName.baseName, memberName)] } // Declaration reference. if let declRef = expression.as(DeclReferenceExprSyntax.self), let name = declRef.simpleIdentifier?.name { - return [name] + return [(declRef.baseName, name)] } throw IfConfigDiagnostic.expectedModuleName(syntax: ExprSyntax(expression)) @@ -794,7 +794,7 @@ private struct CanImportSuppressingBuildConfiguration return try other.hasAttribute(name: name) } - func canImport(importPath: [String], version: CanImportVersion) throws -> Bool { + func canImport(importPath: [(TokenSyntax, String)], version: CanImportVersion) throws -> Bool { return false } From 98aeb4486a0cb895351e8009f80cc3912df915d9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 24 Aug 2024 08:03:52 -0700 Subject: [PATCH 5/5] Small improvements for ConfiguredRegions Add a debugDescription so it's easier to see what the regions are, and allow us to test for exact results. Tweak the set of configured regions more precisely so they match what the compiler is expecting. The only difference here is that we create a separate region for an active block within another active block. --- Sources/SwiftIfConfig/ConfiguredRegions.swift | 48 +++++++++++++++++-- .../SwiftIfConfigTest/ActiveRegionTests.swift | 17 ++++++- .../TestingBuildConfiguration.swift | 4 +- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftIfConfig/ConfiguredRegions.swift b/Sources/SwiftIfConfig/ConfiguredRegions.swift index 729167210a1..393d0e25f6b 100644 --- a/Sources/SwiftIfConfig/ConfiguredRegions.swift +++ b/Sources/SwiftIfConfig/ConfiguredRegions.swift @@ -51,9 +51,7 @@ public struct ConfiguredRegions { return currentState } - let ifRegionStart = - ifClause.condition?.endPosition ?? ifClause.elements?._syntaxNode.position ?? ifClause.poundKeyword.endPosition - if node.position >= ifRegionStart && node.position <= ifClause.endPosition { + if node.position >= ifClause.regionStart && node.position <= ifClause.endPosition { currentState = state } } @@ -72,6 +70,33 @@ extension ConfiguredRegions: RandomAccessCollection { } } +extension ConfiguredRegions: CustomDebugStringConvertible { + /// Provides source ranges for each of the configured regions. + public var debugDescription: String { + guard let firstRegion = first else { + return "[]" + } + + let root = firstRegion.0.root + let converter = SourceLocationConverter(fileName: "", tree: root) + let regionDescriptions = regions.map { (ifClause, state) in + let startPosition = converter.location(for: ifClause.position) + let endPosition = converter.location(for: ifClause.endPosition) + return "[\(startPosition.line):\(startPosition.column) - \(endPosition.line):\(endPosition.column)] = \(state)" + } + + return "[\(regionDescriptions.joined(separator: ", ")))]" + } +} + +extension IfConfigClauseSyntax { + /// The effective start of the region after which code is subject to its + /// condition. + fileprivate var regionStart: AbsolutePosition { + condition?.endPosition ?? elements?._syntaxNode.position ?? poundKeyword.endPosition + } +} + extension SyntaxProtocol { /// Find all of the #if/#elseif/#else clauses within the given syntax node, /// indicating their active state. This operation will recurse into all @@ -118,6 +143,9 @@ fileprivate class ConfiguredRegionVisitor: Sy /// Whether we are currently within an active region. var inActiveRegion = true + /// Whether we are currently within an #if at all. + var inAnyIfConfig = false + // All diagnostics encountered along the way. var diagnostics: [Diagnostic] = [] @@ -127,9 +155,17 @@ fileprivate class ConfiguredRegionVisitor: Sy } override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind { + // We are in an #if. + let priorInAnyIfConfig = inAnyIfConfig + inAnyIfConfig = true + defer { + inAnyIfConfig = priorInAnyIfConfig + } + // Walk through the clauses to find the active one. var foundActive = false var syntaxErrorsAllowed = false + let outerState: IfConfigRegionState = inActiveRegion ? .active : .inactive for clause in node.clauses { let isActive: Bool if let condition = clause.condition { @@ -192,7 +228,11 @@ fileprivate class ConfiguredRegionVisitor: Sy case (false, false): currentState = .inactive case (false, true): currentState = .unparsed } - regions.append((clause, currentState)) + + // If there is a state change, record it. + if !priorInAnyIfConfig || currentState != .inactive || currentState != outerState { + regions.append((clause, currentState)) + } // If this is a parsed region, recurse into it. if currentState != .unparsed, let elements = clause.elements { diff --git a/Tests/SwiftIfConfigTest/ActiveRegionTests.swift b/Tests/SwiftIfConfigTest/ActiveRegionTests.swift index f2e5b55ff12..997b15b3735 100644 --- a/Tests/SwiftIfConfigTest/ActiveRegionTests.swift +++ b/Tests/SwiftIfConfigTest/ActiveRegionTests.swift @@ -79,7 +79,9 @@ public class ActiveRegionTests: XCTestCase { "4️⃣": .unparsed, "5️⃣": .unparsed, "6️⃣": .active, - ] + ], + configuredRegionDescription: + "[[1:6 - 3:5] = active, [3:5 - 10:7] = inactive, [5:5 - 7:5] = unparsed, [7:5 - 9:5] = unparsed)]" ) } @@ -127,6 +129,7 @@ fileprivate func assertActiveCode( _ markedSource: String, configuration: some BuildConfiguration = TestingBuildConfiguration(), states: [String: IfConfigRegionState], + configuredRegionDescription: String? = nil, file: StaticString = #filePath, line: UInt = #line ) throws { @@ -152,7 +155,7 @@ fileprivate func assertActiveCode( let (actualState, _) = token.isActive(in: configuration) XCTAssertEqual(actualState, expectedState, "isActive(in:) at marker \(marker)", file: file, line: line) - let actualViaRegions = token.isActive(inConfiguredRegions: configuredRegions) + let actualViaRegions = configuredRegions.isActive(token) XCTAssertEqual( actualViaRegions, expectedState, @@ -160,5 +163,15 @@ fileprivate func assertActiveCode( file: file, line: line ) + + if let configuredRegionDescription { + XCTAssertEqual( + configuredRegions.debugDescription, + configuredRegionDescription, + "configured region descsription", + file: file, + line: line + ) + } } } diff --git a/Tests/SwiftIfConfigTest/TestingBuildConfiguration.swift b/Tests/SwiftIfConfigTest/TestingBuildConfiguration.swift index 6d6beb7caee..a902f825fe0 100644 --- a/Tests/SwiftIfConfigTest/TestingBuildConfiguration.swift +++ b/Tests/SwiftIfConfigTest/TestingBuildConfiguration.swift @@ -54,10 +54,10 @@ struct TestingBuildConfiguration: BuildConfiguration { } func canImport( - importPath: [String], + importPath: [(TokenSyntax, String)], version: CanImportVersion ) throws -> Bool { - guard let moduleName = importPath.first else { + guard let moduleName = importPath.first?.1 else { return false }