Skip to content

Commit e7d83be

Browse files
committed
Add 'SwiftWarningControl' library for source-level lexical warning group behavior settings
This library provides support for a mechanism to control the behavior of specific diagnostic groups for a given declaration's lexical scope with the `@warn` attribute. The syntax tree and its parser do not reason about warning group controls. The syntax tree produced by the parser reprsents 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 declaration's lexical scope, the behavior of a given diagnosic group can be queried by checking for the presence of this attribute in its parent declaration scope. 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. This utility relies on constructing a warning control tree data structure to represent all warning control lexical scopes in a given source file. This data structure all of the `@warn` 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: @warn(Deprecate, as: error) 2: func foo() { 3: let a = dep 4: @warn(Deprecate, as: warning) 5: func bar() { 6: let b = dep 7: @warn(Deprecate, as: ignored) 8: func baz() { 9: let c = dep 10: } 11: @warn(Deprecate, as: error) 12: @warn(OtherGroup, as: ignored) 13: func qux() { 14: let d = dep 15: @warn(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`:`ignored`] 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.
1 parent 06f123a commit e7d83be

File tree

11 files changed

+819
-0
lines changed

11 files changed

+819
-0
lines changed

.spi.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ builder:
1818
- SwiftParser
1919
- SwiftParserDiagnostics
2020
- SwiftRefactor
21+
- SwiftWarningControl
2122
- SwiftSyntaxBuilder
2223
- SwiftSyntaxMacros
2324
- SwiftSyntaxMacroExpansion

Package.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ if buildDynamicLibrary {
2929
.library(name: "SwiftDiagnostics", targets: ["SwiftDiagnostics"]),
3030
.library(name: "SwiftIDEUtils", targets: ["SwiftIDEUtils"]),
3131
.library(name: "SwiftIfConfig", targets: ["SwiftIfConfig"]),
32+
.library(name: "SwiftWarningControl", targets: ["SwiftWarningControl"]),
3233
.library(name: "SwiftLexicalLookup", targets: ["SwiftLexicalLookup"]),
3334
.library(name: "SwiftOperators", targets: ["SwiftOperators"]),
3435
.library(name: "SwiftParser", targets: ["SwiftParser"]),
@@ -180,6 +181,24 @@ let package = Package(
180181
]
181182
),
182183

184+
// MARK: SwiftWarningControl
185+
186+
.target(
187+
name: "SwiftWarningControl",
188+
dependencies: ["SwiftSyntax", "SwiftParser"],
189+
exclude: ["CMakeLists.txt"]
190+
),
191+
192+
.testTarget(
193+
name: "SwiftWarningControlTest",
194+
dependencies: [
195+
"_SwiftSyntaxTestSupport",
196+
"SwiftWarningControl",
197+
"SwiftParser",
198+
"SwiftSyntaxMacrosGenericTestSupport",
199+
]
200+
),
201+
183202
// MARK: SwiftLexicalLookup
184203

185204
.target(

Sources/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ add_subdirectory(SwiftSyntaxMacroExpansion)
2424
add_subdirectory(SwiftCompilerPluginMessageHandling)
2525
add_subdirectory(SwiftIDEUtils)
2626
add_subdirectory(SwiftCompilerPlugin)
27+
add_subdirectory(SwiftWarningControl)
2728
add_subdirectory(VersionMarkerModules)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# This source file is part of the Swift.org open source project
2+
#
3+
# Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
4+
# Licensed under Apache License v2.0 with Runtime Library Exception
5+
#
6+
# See http://swift.org/LICENSE.txt for license information
7+
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
8+
9+
add_swift_syntax_library(SwiftWarningControl
10+
WarningGroupBehavior.swift
11+
WarningControlDeclSyntax.swift
12+
WarningControlRegionBuilder.swift
13+
WarningControlRegions.swift
14+
SyntaxProtocol+WarningControl.swift
15+
)
16+
17+
target_link_swift_syntax_libraries(SwiftWarningControl PUBLIC
18+
SwiftSyntax
19+
SwiftParser)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# SwiftWarningControl
2+
3+
A library to evaluate `@warn` diagnostic group controls within a Swift syntax tree.
4+
5+
## Overview
6+
7+
Swift provides a mechanism to control the behavior of specific diagnostic groups for a given declaration's lexical scope with the `@warn` attribute.
8+
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.
10+
11+
```swift
12+
@warn(Deprecate, as: error)
13+
func foo() {
14+
...
15+
@warn(Deprecate, as: warning)
16+
func bar() {
17+
...
18+
@warn("Deprecate", as: ignored, reason: "Foo")
19+
func baz() {
20+
...
21+
}
22+
}
23+
}
24+
```
25+
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.
27+
28+
* `SyntaxProtocol.getWarningGroupControl(for diagnosticGroupIdentifier:)` produces the behavior specifier (`WarningGroupBehavior`: `error`, `warning`, `ignored`) which applies at this node.
29+
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.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftDiagnostics
14+
import SwiftSyntax
15+
16+
extension SyntaxProtocol {
17+
/// Get the warning emission behavior for the specified diagnostic group
18+
/// by determining its containing `WarningControlRegion`, if one is present.
19+
@_spi(ExperimentalLanguageFeatures)
20+
public func warningGroupBehavior(
21+
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier
22+
) -> WarningGroupBehavior? {
23+
let warningControlRegions = root.warningGroupControlRegionTree
24+
return warningControlRegions.warningGroupBehavior(at: self.position, for: diagnosticGroupIdentifier)
25+
}
26+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
15+
extension WithAttributesSyntax {
16+
/// Compute a dictionary of all `@warn` diagnostic group behaviors
17+
/// specified on this warning control declaration scope.
18+
var allWarningGroupControls: [DiagnosticGroupIdentifier: WarningGroupBehavior] {
19+
attributes.reduce(into: [DiagnosticGroupIdentifier: WarningGroupBehavior]()) { result, attr in
20+
// `@warn` attributes
21+
guard case .attribute(let attributeSyntax) = attr,
22+
attributeSyntax.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "warn"
23+
else {
24+
return
25+
}
26+
27+
// First argument is the unquoted diagnostic group identifier
28+
guard
29+
let diagnosticGroupID = attributeSyntax.arguments?
30+
.as(LabeledExprListSyntax.self)?.first?.expression
31+
.as(DeclReferenceExprSyntax.self)?.baseName.text
32+
else {
33+
return
34+
}
35+
36+
// Second argument is the `as: ` behavior specifier
37+
guard
38+
let asParamExprSyntax = attributeSyntax
39+
.arguments?.as(LabeledExprListSyntax.self)?
40+
.dropFirst().first
41+
else {
42+
return
43+
}
44+
guard
45+
asParamExprSyntax.label?.text == "as",
46+
let behaviorText = asParamExprSyntax
47+
.expression.as(DeclReferenceExprSyntax.self)?
48+
.baseName.text,
49+
let behavior = WarningGroupBehavior(rawValue: behaviorText)
50+
else {
51+
return
52+
}
53+
result[DiagnosticGroupIdentifier(diagnosticGroupID)] = behavior
54+
}
55+
}
56+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
15+
/// Compute the full set of warning control regions in this syntax node
16+
extension SyntaxProtocol {
17+
@_spi(ExperimentalLanguageFeatures)
18+
public var warningGroupControlRegionTree: WarningControlRegionTree {
19+
let visitor = WarningControlRegionVisitor(range: self.range)
20+
visitor.walk(self)
21+
return visitor.tree
22+
}
23+
}
24+
25+
/// Add this warning control decl syntax node warning group controls (as specified with `@warn`)
26+
/// to the tree.
27+
extension WarningControlRegionTree {
28+
mutating func addWarningControlRegions(for syntax: some WithAttributesSyntax) {
29+
addWarningGroupControls(
30+
range: syntax.range,
31+
controls: syntax.allWarningGroupControls
32+
)
33+
}
34+
}
35+
36+
/// Helper class that walks a syntax tree looking for warning behavior control regions.
37+
private class WarningControlRegionVisitor: SyntaxAnyVisitor {
38+
/// The tree of warning control regions we have found so far
39+
var tree: WarningControlRegionTree
40+
41+
init(range: Range<AbsolutePosition>) {
42+
self.tree = WarningControlRegionTree(range: range)
43+
super.init(viewMode: .fixedUp)
44+
}
45+
46+
override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
47+
if let withAttributesSyntax = node.asProtocol(WithAttributesSyntax.self) {
48+
tree.addWarningControlRegions(for: withAttributesSyntax)
49+
}
50+
return .visitChildren
51+
}
52+
}

0 commit comments

Comments
 (0)