-
Notifications
You must be signed in to change notification settings - Fork 501
Expand file tree
/
Copy pathWarningControlRegions.swift
More file actions
332 lines (303 loc) · 12.5 KB
/
WarningControlRegions.swift
File metadata and controls
332 lines (303 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SwiftSyntax
/// A single warning control region, consisting of a start and end positions,
/// a diagnostic group identifier, and an emission behavior control specifier.
@_spi(ExperimentalLanguageFeatures)
public struct WarningControlRegion {
public let range: Range<AbsolutePosition>
public let diagnosticGroupIdentifier: DiagnosticGroupIdentifier
public let control: WarningGroupControl
init(
range: Range<AbsolutePosition>,
diagnosticGroupIdentifier: DiagnosticGroupIdentifier,
control: WarningGroupControl
) {
self.range = range
self.diagnosticGroupIdentifier = diagnosticGroupIdentifier
self.control = control
}
}
/// A struct representing a diagnostic group identifier string
@_spi(ExperimentalLanguageFeatures)
public struct DiagnosticGroupIdentifier: Hashable, Sendable, ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self.identifier = value
}
public init(_ value: String) {
self.identifier = value
}
public let identifier: String
}
/// Describes all of the `@diagnose` diagnostic group behavior controls within the
/// given syntax node, indicating each group's active behavior at a given position.
///
/// For example, given code like the following:
///
/// ```
/// 1 | @diagnose(Deprecate, as: error)
/// 2 | func foo() {
/// 3 | let a = dep
/// 4 | @diagnose(Deprecate, as: warning)
/// 5 | func bar() {
/// 6 | let b = dep
/// 7 | @diagnose(Deprecate, as: ignored)
/// 8 | func baz() {
/// 9 | let c = dep
/// 10 | }
/// 11 | @diagnose(Deprecate, as: error)
/// 12 | @diagnose(OtherGroup, as: error)
/// 13 | func qux() {
/// 14 | let d = dep
/// 15 | @diagnose(SomeOtherGroup, as: warning)
/// 16 | func corge() {
/// 17 | let e = dep
/// 18 | }
/// 19 | }
/// 20 | }
/// 21 | }
/// 22 | func grault() {
/// 23 | let f = dep
/// 24 | }
/// ```
///
/// the result will be:
/// - [`Deprecate`:`error`] region within `foo` lexical scope (lines 1-22)
/// - [`Deprecate`:`warning`] region within `bar` lexical scope (lines 4-20)
/// - [`Deprecate`:`ignored`] region within `baz` lexical scope (lines 7-10)
/// - [`Deprecate`:`error`, `OtherGroup`:`error`] region within `qux` lexical scope (lines 12-19)
/// - [`SomeOtherGroup`:`warning`] region within `corge` lexical scope (lines 15-18)
///
/// The data structure represents these regions and their nesting relationships
/// as an interval tree where interval nodes can only be nested or disjoint,
/// and where a given interval corresponds to a diagnostic control region for one
/// or more diagnostic group, with a behavior specifier for each.
///
/// Intervals cannot partially overlap, and each node's child intervals are always kept
/// sorted. The tree has multiple top-level nodes (roots) representing file-level warning
/// control regions.
///
/// Once the tree is computed, lookup of a diagnostic group behavior at a given position
/// is performed by recursively descending into the child node containing
/// the given position (located with a binary search of child nodes of a given parent node).
/// Once the position's depth in the interval tree is reached, we walk back the
/// traversal until we find the first containing region which specifies warning
/// behavior control for the given diagnostic group id.
///
@_spi(ExperimentalLanguageFeatures)
public struct WarningControlRegionTree {
/// Root region representing top-level (file) scope
private var rootRegionNode: WarningControlRegionNode
/// All of the diagnostic group identifiers contained in this tree
/// which have at least one occurence with a non-`ignored` behavior
/// specifier
@_spi(ExperimentalLanguageFeatures)
public private(set) var enabledGroups: Set<DiagnosticGroupIdentifier> = []
/// Inheritance tree among diagnostic group identifiers
private let groupInheritanceTree: DiagnosticGroupInheritanceTree
init(
range: Range<AbsolutePosition>,
groupInheritanceTree: DiagnosticGroupInheritanceTree?
) {
self.rootRegionNode = WarningControlRegionNode(range: range)
self.groupInheritanceTree = groupInheritanceTree ?? DiagnosticGroupInheritanceTree()
}
/// Add a warning control region to the tree
mutating func addWarningGroupControls(
range: Range<AbsolutePosition>,
controls: [(DiagnosticGroupIdentifier, WarningGroupControl)]
) {
guard !controls.isEmpty else { return }
let newNode = WarningControlRegionNode(range: range)
for (diagnosticGroupIdentifier, control) in controls {
// Handle the control for the added diagnostic group
// and propagate it to all of its subgroups.
var groups: [DiagnosticGroupIdentifier] = [diagnosticGroupIdentifier]
var processedGroups: Set<DiagnosticGroupIdentifier> = []
while !groups.isEmpty {
let groupIdentifier = groups.removeFirst()
processedGroups.insert(groupIdentifier)
newNode.addWarningGroupControl(for: groupIdentifier, control: control)
if control != .ignored {
enabledGroups.insert(diagnosticGroupIdentifier)
}
let newSubGroups = groupInheritanceTree.subgroups(of: groupIdentifier).filter { !processedGroups.contains($0) }
// Ensure we add a corresponding control to each direct and
// transitive sub-group of the one specified on this control.
groups.append(contentsOf: newSubGroups)
}
}
insertIntoSubtree(newNode, parent: rootRegionNode)
}
/// Add warning control regions to the tree root node.
/// For example, controls corresponding to a top-level `using @diagnose()` statement.
mutating func addRootWarningGroupControls(controls: [(DiagnosticGroupIdentifier, WarningGroupControl)]) {
addWarningGroupControls(range: rootRegionNode.range, controls: controls)
}
/// Insert a region node into the appropriate position in a subtree.
/// During top-down traversal of the syntax tree, nodes that are visited
/// later should never contain any of the previously visited nodes,
/// so it is sufficient to either find an existing child to insert this node
/// into, or add this node as a new child itself.
private func insertIntoSubtree(
_ node: WarningControlRegionNode,
parent: WarningControlRegionNode
) {
// Check if the new region has the same boundaries as the parent
if parent.range == node.range {
for (diagnosticGroupIdentifier, control) in node.warningGroupControls {
parent.addWarningGroupControl(for: diagnosticGroupIdentifier, control: control)
}
return
}
// Check if this should be a child of one of parent's children
if let containingChild = parent.findChildContaining(node.range.lowerBound) {
insertIntoSubtree(node, parent: containingChild)
return
}
// Add as direct child of parent
parent.addChild(node)
}
}
extension WarningControlRegionTree: CustomDebugStringConvertible {
public var debugDescription: String {
var result = "Warning Group Control Region Tree:\n"
func printNode(_ node: WarningControlRegionNode, indent: Int) {
let spacing = String(repeating: " ", count: indent)
result += "\(spacing)[\(node.range.lowerBound), \(node.range.upperBound)]"
if !node.warningGroupControls.isEmpty {
result +=
" control(s): \(node.warningGroupControls.map { $0.key.identifier + ": " + $0.value.rawValue }.joined(separator: ", "))\n"
} else {
result += "\n"
}
for child in node.children {
printNode(child, indent: indent + 1)
}
}
printNode(rootRegionNode, indent: 0)
return result
}
}
extension WarningControlRegionTree {
/// Determine the warning group behavior control at a specified position
/// for a given diagnostic group.
@_spi(ExperimentalLanguageFeatures)
public func warningGroupControl(
at position: AbsolutePosition,
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier
) -> WarningGroupControl? {
return rootRegionNode.innermostContainingRegion(at: position, for: diagnosticGroupIdentifier)?.control
}
}
/// A node in the warning control region tree, representing a collection of warning
/// group controls and references to its nested child regions.
private class WarningControlRegionNode {
let range: Range<AbsolutePosition>
var warningGroupControls: [DiagnosticGroupIdentifier: WarningGroupControl] = [:]
var children: [WarningControlRegionNode] = []
init(
range: Range<AbsolutePosition>,
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier,
control: WarningGroupControl
) {
self.range = range
self.warningGroupControls = [diagnosticGroupIdentifier: control]
}
init(range: Range<AbsolutePosition>) {
self.range = range
self.warningGroupControls = [:]
}
/// Add a region with the same bounds as this node
func addWarningGroupControl(
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier,
control: WarningGroupControl
) {
warningGroupControls[diagnosticGroupIdentifier] = control
}
/// Get region with specific identifier if it exists
func getWarningGroupControl(for diagnosticGroupIdentifier: DiagnosticGroupIdentifier) -> WarningControlRegion? {
guard let groupControl = warningGroupControls[diagnosticGroupIdentifier] else {
return nil
}
return WarningControlRegion(
range: range,
diagnosticGroupIdentifier: diagnosticGroupIdentifier,
control: groupControl
)
}
/// Add a child region that is directly nested within this region
func addChild(_ node: WarningControlRegionNode) {
precondition(range.lowerBound <= node.range.lowerBound && node.range.upperBound <= range.upperBound)
children.append(node)
children.sort()
}
}
extension WarningControlRegionNode {
/// Find the most deeply-nested warning control region with a given diagnostic group identifier
/// containing the specified position.
///
/// Lookup complexity is `O(log n + d)` where `n` is the branching factor and `d` is the warning
/// control node depth of the queried position.
func innermostContainingRegion(
at position: AbsolutePosition,
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier
) -> WarningControlRegion? {
guard range.contains(position) else {
return nil
}
if let childRegionContainingPosition = findChildContaining(position),
let nestedResult = childRegionContainingPosition.innermostContainingRegion(
at: position,
for: diagnosticGroupIdentifier
)
{
return nestedResult
}
// If no child region has this warning group, check self
return getWarningGroupControl(for: diagnosticGroupIdentifier)
}
/// Binary search to find the exact child containing the specified position,
/// or `nil` if one is not found, meaning it is only the `self` node itself that contains the position.
func findChildContaining(_ position: AbsolutePosition) -> WarningControlRegionNode? {
guard range.contains(position) && !children.isEmpty else { return nil }
var left = 0
var right = children.count - 1
while left <= right {
let mid = (left + right) / 2
let region = children[mid]
if region.range.lowerBound > position {
right = mid - 1
} else if region.range.upperBound <= position {
left = mid + 1
} else {
return region
}
}
return nil
}
}
extension WarningControlRegionNode: Comparable {
static func == (lhs: WarningControlRegionNode, rhs: WarningControlRegionNode) -> Bool {
return lhs.range == rhs.range
}
static func < (lhs: WarningControlRegionNode, rhs: WarningControlRegionNode) -> Bool {
if lhs.range.lowerBound != rhs.range.lowerBound {
return lhs.range.lowerBound < rhs.range.lowerBound
}
// For same start, larger interval comes first (reverse order by end)
// This can happen, for example, when the top-level region (source-file scope)
// also contains a warning control scope at the very beginning which
// also begins at position 0.
return lhs.range.upperBound > rhs.range.upperBound
}
}