Skip to content

Commit 2d6cb19

Browse files
authored
Merge pull request #439 from kkebo/main
2 parents 5b5d96f + 90e6d96 commit 2d6cb19

File tree

9 files changed

+125
-55
lines changed

9 files changed

+125
-55
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ let package = Package(
186186
.target(
187187
name: "SwiftWarningControl",
188188
dependencies: ["SwiftSyntax", "SwiftParser"],
189-
exclude: ["CMakeLists.txt"]
189+
exclude: ["CMakeLists.txt", "SwiftWarningControl.md"]
190190
),
191191

192192
.testTarget(

Sources/SwiftWarningControl/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_swift_syntax_library(SwiftWarningControl
10-
WarningGroupBehavior.swift
10+
WarningGroupControl.swift
1111
WarningControlDeclSyntax.swift
1212
WarningControlRegionBuilder.swift
1313
WarningControlRegions.swift

Sources/SwiftWarningControl/SwiftWarningControl.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A library to evaluate `@warn` diagnostic group controls within a Swift syntax tr
66

77
Swift provides a mechanism to control the behavior of specific diagnostic groups for a given declaration's lexical scope with the `@warn` attribute.
88

9-
The syntax tree and its parser do not reason about warning group controls. The syntax tree produced by the parser represents the `@warn` attribute in a generic fashion, as it would any other basic attribute on a declaration. The per-declaration nature of the attribute means that for any given lexical scope, the behavior of a given diagnostic group can be queried by checking for the presence of this attribute in its parent declaration scope.
9+
The syntax tree and its parser do not reason about warning group controls. The syntax tree produced by the parser represents the `@warn` attribute in a generic fashion, as it would any other basic attribute on a declaration. The per-declaration nature of the attribute means that for any given lexical scope, the behavior of a given diagnostic group can be queried by checking for the presence of this attribute in its parent declaration scope.
1010

1111
```swift
1212
@warn(Deprecate, as: error)
@@ -23,8 +23,8 @@ func foo() {
2323
}
2424
```
2525

26-
The `SwiftWarningControl` library provides a utility to determine, for a given source location and diagnostic group identifier, whether or not its behavior is affected by an `@warn` attribute of any of its parent declaration scope.
26+
The `SwiftWarningControl` library provides a utility to determine, for a given source location and diagnostic group identifier, whether or not its behavior is affected by an `@warn` attribute of any of its parent declaration scope.
2727

28-
* `SyntaxProtocol.getWarningGroupControl(for diagnosticGroupIdentifier:)` produces the behavior specifier (`WarningGroupBehavior`: `error`, `warning`, `ignored`) which applies at this node.
28+
* `SyntaxProtocol.getWarningGroupControl(for diagnosticGroupIdentifier:)` produces the behavior control specifier (`WarningGroupControl`: `error`, `warning`, `ignored`) which applies at this node.
2929

30-
* `SyntaxProtocol.warningGroupControlRegionTree` holds a computed `WarningControlRegionTree` data structure value that can be used to efficiently test for the specified `WarningGroupBehavior` at a given source location and a given diagnostic group.
30+
* `SyntaxProtocol.warningGroupControlRegionTree` holds a computed `WarningControlRegionTree` data structure value that can be used to efficiently test for the specified `WarningGroupControl` at a given source location and a given diagnostic group.

Sources/SwiftWarningControl/SyntaxProtocol+WarningControl.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,25 @@ import SwiftDiagnostics
1414
import SwiftSyntax
1515

1616
extension SyntaxProtocol {
17-
/// Get the warning emission behavior for the specified diagnostic group
17+
/// Get the warning emission behavior control for the specified diagnostic group
1818
/// by determining its containing `WarningControlRegion`, if one is present.
19+
/// Returns the syntactic control for the given diagnostic group, or `nil` if
20+
/// there is not one.
21+
/// - Parameters:
22+
/// - for diagnosticGroupIdentifier: The identifier of the diagnostic group.
23+
/// - globalControls: The global controls to consider, specified by the client (compiler)
24+
/// representing module-wide diagnostic group emission configuration, for example
25+
/// with `-Wwarning` and `-Werror` flags. These controls can be overriden at
26+
/// finer-grained scopes with the `@warn` attribute.
1927
@_spi(ExperimentalLanguageFeatures)
20-
public func warningGroupBehavior(
21-
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier
22-
) -> WarningGroupBehavior? {
23-
let warningControlRegions = root.warningGroupControlRegionTreeImpl(containing: self.position)
24-
return warningControlRegions.warningGroupBehavior(at: self.position, for: diagnosticGroupIdentifier)
28+
public func warningGroupControl(
29+
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier,
30+
globalControls: [DiagnosticGroupIdentifier: WarningGroupControl] = [:]
31+
) -> WarningGroupControl? {
32+
let warningControlRegions = root.warningGroupControlRegionTreeImpl(
33+
globalControls: globalControls,
34+
containing: self.position
35+
)
36+
return warningControlRegions.warningGroupControl(at: self.position, for: diagnosticGroupIdentifier)
2537
}
2638
}

Sources/SwiftWarningControl/WarningControlDeclSyntax.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
import SwiftSyntax
1414

1515
extension WithAttributesSyntax {
16-
/// Compute a dictionary of all `@warn` diagnostic group behaviors
16+
/// Compute a dictionary of all `@warn` diagnostic group behavior controls
1717
/// specified on this warning control declaration scope.
18-
var allWarningGroupControls: [DiagnosticGroupIdentifier: WarningGroupBehavior] {
19-
attributes.reduce(into: [DiagnosticGroupIdentifier: WarningGroupBehavior]()) { result, attr in
18+
var allWarningGroupControls: [DiagnosticGroupIdentifier: WarningGroupControl] {
19+
attributes.reduce(into: [DiagnosticGroupIdentifier: WarningGroupControl]()) { result, attr in
2020
// `@warn` attributes
2121
guard case .attribute(let attributeSyntax) = attr,
2222
attributeSyntax.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "warn"
@@ -33,7 +33,7 @@ extension WithAttributesSyntax {
3333
return
3434
}
3535

36-
// Second argument is the `as: ` behavior specifier
36+
// Second argument is the `as: ` behavior control specifier
3737
guard
3838
let asParamExprSyntax = attributeSyntax
3939
.arguments?.as(LabeledExprListSyntax.self)?
@@ -43,14 +43,14 @@ extension WithAttributesSyntax {
4343
}
4444
guard
4545
asParamExprSyntax.label?.text == "as",
46-
let behaviorText = asParamExprSyntax
46+
let controlText = asParamExprSyntax
4747
.expression.as(DeclReferenceExprSyntax.self)?
4848
.baseName.text,
49-
let behavior = WarningGroupBehavior(rawValue: behaviorText)
49+
let control = WarningGroupControl(rawValue: controlText)
5050
else {
5151
return
5252
}
53-
result[DiagnosticGroupIdentifier(diagnosticGroupID)] = behavior
53+
result[DiagnosticGroupIdentifier(diagnosticGroupID)] = control
5454
}
5555
}
5656
}

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+
globalControls: [DiagnosticGroupIdentifier: WarningGroupControl] = [:]
20+
) -> WarningControlRegionTree {
21+
return warningGroupControlRegionTreeImpl(globalControls: globalControls)
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+
globalControls: [DiagnosticGroupIdentifier: WarningGroupControl],
30+
containing position: AbsolutePosition? = nil
31+
) -> WarningControlRegionTree {
2732
let visitor = WarningControlRegionVisitor(self.range, containing: position)
33+
visitor.tree.addWarningGroupControls(range: self.range, controls: globalControls)
2834
visitor.walk(self)
2935
return visitor.tree
3036
}

Sources/SwiftWarningControl/WarningControlRegions.swift

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,21 @@
1313
import SwiftSyntax
1414

1515
/// A single warning control region, consisting of a start and end positions,
16-
/// a diagnostic group identifier, and an emission behavior specifier.
16+
/// a diagnostic group identifier, and an emission behavior control specifier.
1717
@_spi(ExperimentalLanguageFeatures)
1818
public struct WarningControlRegion {
1919
public let range: Range<AbsolutePosition>
2020
public let diagnosticGroupIdentifier: DiagnosticGroupIdentifier
21-
public let behavior: WarningGroupBehavior
21+
public let control: WarningGroupControl
2222

2323
init(
2424
range: Range<AbsolutePosition>,
2525
diagnosticGroupIdentifier: DiagnosticGroupIdentifier,
26-
behavior: WarningGroupBehavior
26+
control: WarningGroupControl
2727
) {
2828
self.range = range
2929
self.diagnosticGroupIdentifier = diagnosticGroupIdentifier
30-
self.behavior = behavior
30+
self.control = control
3131
}
3232
}
3333

@@ -98,9 +98,6 @@ public struct DiagnosticGroupIdentifier: Hashable, Sendable, ExpressibleByString
9898
/// traversal until we find the first containing region which specifies warning
9999
/// behavior control for the given diagnostic group id.
100100
///
101-
/// TODO: Capture global configuration from command-line arguments
102-
/// to represent global rules, such as `-Werror`, `-Wwarning`,
103-
/// and `-suppress-warnings` as *the* root region node.
104101
@_spi(ExperimentalLanguageFeatures)
105102
public struct WarningControlRegionTree {
106103
/// Root region representing top-level (file) scope
@@ -113,11 +110,12 @@ public struct WarningControlRegionTree {
113110
/// Add a warning control region to the tree
114111
mutating func addWarningGroupControls(
115112
range: Range<AbsolutePosition>,
116-
controls: [DiagnosticGroupIdentifier: WarningGroupBehavior]
113+
controls: [DiagnosticGroupIdentifier: WarningGroupControl]
117114
) {
115+
guard !controls.isEmpty else { return }
118116
let newNode = WarningControlRegionNode(range: range)
119-
for (diagnosticGroupIdentifier, behavior) in controls {
120-
newNode.addWarningGroupControl(for: diagnosticGroupIdentifier, behavior: behavior)
117+
for (diagnosticGroupIdentifier, control) in controls {
118+
newNode.addWarningGroupControl(for: diagnosticGroupIdentifier, control: control)
121119
}
122120
insertIntoSubtree(newNode, parent: rootRegionNode)
123121
}
@@ -134,7 +132,7 @@ public struct WarningControlRegionTree {
134132
// Check if the new region has the same boundaries as the parent
135133
if parent.range == node.range {
136134
for (diagnosticGroupIdentifier, control) in node.warningGroupControls {
137-
parent.addWarningGroupControl(for: diagnosticGroupIdentifier, behavior: control)
135+
parent.addWarningGroupControl(for: diagnosticGroupIdentifier, control: control)
138136
}
139137
return
140138
}
@@ -157,7 +155,8 @@ extension WarningControlRegionTree: CustomDebugStringConvertible {
157155
let spacing = String(repeating: " ", count: indent)
158156
result += "\(spacing)[\(node.range.lowerBound), \(node.range.upperBound)]"
159157
if !node.warningGroupControls.isEmpty {
160-
result += " id(s): \(node.warningGroupControls.keys.map { $0.identifier }.joined(separator: ", "))\n"
158+
result +=
159+
" control(s): \(node.warningGroupControls.map { $0.key.identifier + ": " + $0.value.rawValue }.joined(separator: ", "))\n"
161160
} else {
162161
result += "\n"
163162
}
@@ -172,31 +171,31 @@ extension WarningControlRegionTree: CustomDebugStringConvertible {
172171
}
173172

174173
extension WarningControlRegionTree {
175-
/// Determine the warning group behavior at a specified position
176-
/// for a given diagnostic group
174+
/// Determine the warning group behavior control at a specified position
175+
/// for a given diagnostic group.
177176
@_spi(ExperimentalLanguageFeatures)
178-
public func warningGroupBehavior(
177+
public func warningGroupControl(
179178
at position: AbsolutePosition,
180179
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier
181-
) -> WarningGroupBehavior? {
182-
return rootRegionNode.innermostContainingRegion(at: position, for: diagnosticGroupIdentifier)?.behavior
180+
) -> WarningGroupControl? {
181+
return rootRegionNode.innermostContainingRegion(at: position, for: diagnosticGroupIdentifier)?.control
183182
}
184183
}
185184

186185
/// A node in the warning control region tree, representing a collection of warning
187186
/// group controls and references to its nested child regions.
188187
private class WarningControlRegionNode {
189188
let range: Range<AbsolutePosition>
190-
var warningGroupControls: [DiagnosticGroupIdentifier: WarningGroupBehavior] = [:]
189+
var warningGroupControls: [DiagnosticGroupIdentifier: WarningGroupControl] = [:]
191190
var children: [WarningControlRegionNode] = []
192191

193192
init(
194193
range: Range<AbsolutePosition>,
195194
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier,
196-
behavior: WarningGroupBehavior
195+
control: WarningGroupControl
197196
) {
198197
self.range = range
199-
self.warningGroupControls = [diagnosticGroupIdentifier: behavior]
198+
self.warningGroupControls = [diagnosticGroupIdentifier: control]
200199
}
201200

202201
init(range: Range<AbsolutePosition>) {
@@ -207,20 +206,20 @@ private class WarningControlRegionNode {
207206
/// Add a region with the same bounds as this node
208207
func addWarningGroupControl(
209208
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier,
210-
behavior: WarningGroupBehavior
209+
control: WarningGroupControl
211210
) {
212-
warningGroupControls[diagnosticGroupIdentifier] = behavior
211+
warningGroupControls[diagnosticGroupIdentifier] = control
213212
}
214213

215214
/// Get region with specific identifier if it exists
216215
func getWarningGroupControl(for diagnosticGroupIdentifier: DiagnosticGroupIdentifier) -> WarningControlRegion? {
217-
guard let behaviorControl = warningGroupControls[diagnosticGroupIdentifier] else {
216+
guard let groupControl = warningGroupControls[diagnosticGroupIdentifier] else {
218217
return nil
219218
}
220219
return WarningControlRegion(
221220
range: range,
222221
diagnosticGroupIdentifier: diagnosticGroupIdentifier,
223-
behavior: behaviorControl
222+
control: groupControl
224223
)
225224
}
226225

Sources/SwiftWarningControl/WarningGroupBehavior.swift renamed to Sources/SwiftWarningControl/WarningGroupControl.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import SwiftSyntax
1414

1515
// Describes the emission behavior state of a particular warning diagnostic group.
1616
@_spi(ExperimentalLanguageFeatures)
17-
public enum WarningGroupBehavior: String {
17+
public enum WarningGroupControl: String {
1818
/// Emitted as a fatal error, halting compilation
1919
case error
2020
/// Emitted as a warning

Tests/SwiftWarningControlTest/WarningControlTests.swift

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -228,14 +228,66 @@ 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+
globalControls: ["GroupID": .warning],
242+
diagnosticGroupID: "GroupID",
243+
states: [
244+
"1️⃣": .error
245+
]
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+
globalControls: ["GroupID": .error],
259+
diagnosticGroupID: "GroupID",
260+
states: [
261+
"1️⃣": .error,
262+
"2️⃣": .ignored,
263+
]
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+
globalControls: ["GroupID": .warning],
276+
diagnosticGroupID: "GroupID",
277+
states: [
278+
"1️⃣": .warning
279+
]
280+
)
281+
}
231282
}
232283

233284
/// Assert that the various marked positions in the source code have the
234285
/// expected warning behavior controls.
235286
private func assertWarningGroupControl(
236287
_ markedSource: String,
288+
globalControls: [DiagnosticGroupIdentifier: WarningGroupControl] = [:],
237289
diagnosticGroupID: DiagnosticGroupIdentifier,
238-
states: [String: WarningGroupBehavior?],
290+
states: [String: WarningGroupControl?],
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,13 +308,17 @@ private func assertWarningGroupControl(
259308
continue
260309
}
261310

262-
let groupBehavior = token.warningGroupBehavior(for: diagnosticGroupID)
263-
XCTAssertEqual(groupBehavior, expectedState)
311+
let warningControlRegions = tree.warningGroupControlRegionTree(globalControls: globalControls)
312+
let groupControl = token.warningGroupControl(
313+
for: diagnosticGroupID,
314+
globalControls: globalControls
315+
)
316+
XCTAssertEqual(groupControl, expectedState)
264317

265-
let groupBehaviorViaRegions = warningControlRegions.warningGroupBehavior(
318+
let groupControlViaRegions = warningControlRegions.warningGroupControl(
266319
at: absolutePosition,
267320
for: diagnosticGroupID
268321
)
269-
XCTAssertEqual(groupBehaviorViaRegions, expectedState)
322+
XCTAssertEqual(groupControlViaRegions, expectedState)
270323
}
271324
}

0 commit comments

Comments
 (0)