Skip to content

Commit 59c2431

Browse files
committed
[SwiftWarningControl] Add ability to query warning group behavior including client-specified enclosing control rules
Adds an optional parameter `with enclosingControls: [DiagnosticGroupIdentifier: WarningGroupBehavior] = [:]` to both: - `SyntaxProtocol.warningGroupControlRegionTree` - `SyntaxProtocol.warningGroupBehavior` to specify enclosing global warning group behavior controls which span the entire syntax node. This mechanism will be used by the compiler to specify global controls over certain diagnostic groups from the compilation configuration. For example, a command-line-specified `-Werror Group` will become an input to this mechanism to be used as *the* behavior control at source positions where no syntactic control (`@warn`) is specified. With this in place, the `SwiftWarningControl` API can be used as a single query which captures both syntactic and command-line configuration of warning diagnostic group behavior.
1 parent bd53a85 commit 59c2431

File tree

4 files changed

+78
-12
lines changed

4 files changed

+78
-12
lines changed

Sources/SwiftWarningControl/SyntaxProtocol+WarningControl.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ extension SyntaxProtocol {
1818
/// by determining its containing `WarningControlRegion`, if one is present.
1919
@_spi(ExperimentalLanguageFeatures)
2020
public func warningGroupBehavior(
21-
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier
21+
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier,
22+
enclosingControls: [DiagnosticGroupIdentifier: WarningGroupBehavior] = [:]
2223
) -> WarningGroupBehavior? {
23-
let warningControlRegions = root.warningGroupControlRegionTreeImpl(containing: self.position)
24+
let warningControlRegions = root.warningGroupControlRegionTreeImpl(
25+
enclosingControls: enclosingControls,
26+
containing: self.position
27+
)
2428
return warningControlRegions.warningGroupBehavior(at: self.position, for: diagnosticGroupIdentifier)
2529
}
2630
}

Sources/SwiftWarningControl/WarningControlRegionBuilder.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,22 @@ import SwiftSyntax
1515
/// Compute the full set of warning control regions in this syntax node
1616
extension SyntaxProtocol {
1717
@_spi(ExperimentalLanguageFeatures)
18-
public func warningGroupControlRegionTree() -> WarningControlRegionTree {
19-
return warningGroupControlRegionTreeImpl()
18+
public func warningGroupControlRegionTree(
19+
enclosingControls: [DiagnosticGroupIdentifier: WarningGroupBehavior] = [:]
20+
) -> WarningControlRegionTree {
21+
return warningGroupControlRegionTreeImpl(enclosingControls: enclosingControls)
2022
}
2123

2224
/// Implementation of constructing a region tree with an optional parameter
2325
/// to specify that the constructed tree must only contain nodes which contain
2426
/// a specific absolute position - meant to speed up tree generation for individual
2527
/// queries.
26-
func warningGroupControlRegionTreeImpl(containing position: AbsolutePosition? = nil) -> WarningControlRegionTree {
28+
func warningGroupControlRegionTreeImpl(
29+
enclosingControls: [DiagnosticGroupIdentifier: WarningGroupBehavior],
30+
containing position: AbsolutePosition? = nil
31+
) -> WarningControlRegionTree {
2732
let visitor = WarningControlRegionVisitor(self.range, containing: position)
33+
visitor.tree.addWarningGroupControls(range: self.range, controls: enclosingControls)
2834
visitor.walk(self)
2935
return visitor.tree
3036
}

Sources/SwiftWarningControl/WarningControlRegions.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public struct DiagnosticGroupIdentifier: Hashable, Sendable, ExpressibleByString
104104
@_spi(ExperimentalLanguageFeatures)
105105
public struct WarningControlRegionTree {
106106
/// Root region representing top-level (file) scope
107-
private var rootRegionNode: WarningControlRegionNode
107+
var rootRegionNode: WarningControlRegionNode
108108

109109
init(range: Range<AbsolutePosition>) {
110110
rootRegionNode = WarningControlRegionNode(range: range)
@@ -115,6 +115,7 @@ public struct WarningControlRegionTree {
115115
range: Range<AbsolutePosition>,
116116
controls: [DiagnosticGroupIdentifier: WarningGroupBehavior]
117117
) {
118+
guard !controls.isEmpty else { return }
118119
let newNode = WarningControlRegionNode(range: range)
119120
for (diagnosticGroupIdentifier, behavior) in controls {
120121
newNode.addWarningGroupControl(for: diagnosticGroupIdentifier, behavior: behavior)
@@ -157,7 +158,8 @@ extension WarningControlRegionTree: CustomDebugStringConvertible {
157158
let spacing = String(repeating: " ", count: indent)
158159
result += "\(spacing)[\(node.range.lowerBound), \(node.range.upperBound)]"
159160
if !node.warningGroupControls.isEmpty {
160-
result += " id(s): \(node.warningGroupControls.keys.map { $0.identifier }.joined(separator: ", "))\n"
161+
result +=
162+
" control(s): \(node.warningGroupControls.map { $0.key.identifier + ":" + $0.value.rawValue }.joined(separator: ", "))\n"
161163
} else {
162164
result += "\n"
163165
}
@@ -185,7 +187,7 @@ extension WarningControlRegionTree {
185187

186188
/// A node in the warning control region tree, representing a collection of warning
187189
/// group controls and references to its nested child regions.
188-
private class WarningControlRegionNode {
190+
class WarningControlRegionNode {
189191
let range: Range<AbsolutePosition>
190192
var warningGroupControls: [DiagnosticGroupIdentifier: WarningGroupBehavior] = [:]
191193
var children: [WarningControlRegionNode] = []

Tests/SwiftWarningControlTest/WarningControlTests.swift

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,57 @@ public class WarningGroupControlTests: XCTestCase {
228228
]
229229
)
230230
}
231+
232+
func testEnclosingGlobalControlOverride() throws {
233+
// Global control does not override syntactic control
234+
try assertWarningGroupControl(
235+
"""
236+
@warn(GroupID, as: error)
237+
func foo() {
238+
1️⃣let x = 1
239+
}
240+
""",
241+
diagnosticGroupID: "GroupID",
242+
states: [
243+
"1️⃣": .error
244+
],
245+
enclosingControls: ["GroupID": .warning]
246+
)
247+
248+
try assertWarningGroupControl(
249+
"""
250+
func foo() {
251+
1️⃣let x = 1
252+
@warn(GroupID, as: ignored)
253+
func bar() {
254+
2️⃣let x = 1
255+
}
256+
}
257+
""",
258+
diagnosticGroupID: "GroupID",
259+
states: [
260+
"1️⃣": .error,
261+
"2️⃣": .ignored,
262+
],
263+
enclosingControls: ["GroupID": .error]
264+
)
265+
}
266+
267+
func testEnclosingGlobalControlOnly() throws {
268+
// Global control used in absense of a syntactic control
269+
try assertWarningGroupControl(
270+
"""
271+
func foo() {
272+
1️⃣let x = 1
273+
}
274+
""",
275+
diagnosticGroupID: "GroupID",
276+
states: [
277+
"1️⃣": .warning
278+
],
279+
enclosingControls: ["GroupID": .warning]
280+
)
281+
}
231282
}
232283

233284
/// Assert that the various marked positions in the source code have the
@@ -236,6 +287,7 @@ private func assertWarningGroupControl(
236287
_ markedSource: String,
237288
diagnosticGroupID: DiagnosticGroupIdentifier,
238289
states: [String: WarningGroupBehavior?],
290+
enclosingControls: [DiagnosticGroupIdentifier: WarningGroupBehavior] = [:],
239291
file: StaticString = #filePath,
240292
line: UInt = #line
241293
) throws {
@@ -244,9 +296,6 @@ private func assertWarningGroupControl(
244296

245297
var parser = Parser(source)
246298
let tree = SourceFileSyntax.parse(from: &parser)
247-
248-
let warningControlRegions = tree.warningGroupControlRegionTree()
249-
250299
for (marker, location) in markerLocations {
251300
guard let expectedState = states[marker] else {
252301
XCTFail("Missing marker \(marker) in expected states", file: file, line: line)
@@ -259,7 +308,12 @@ private func assertWarningGroupControl(
259308
continue
260309
}
261310

262-
let groupBehavior = token.warningGroupBehavior(for: diagnosticGroupID)
311+
let warningControlRegions = tree.warningGroupControlRegionTree(enclosingControls: enclosingControls)
312+
print(warningControlRegions)
313+
let groupBehavior = token.warningGroupBehavior(
314+
for: diagnosticGroupID,
315+
enclosingControls: enclosingControls
316+
)
263317
XCTAssertEqual(groupBehavior, expectedState)
264318

265319
let groupBehaviorViaRegions = warningControlRegions.warningGroupBehavior(

0 commit comments

Comments
 (0)