Skip to content

Commit 463a525

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 baef1ff commit 463a525

File tree

10 files changed

+927
-0
lines changed

10 files changed

+927
-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(
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()` produces a `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 WarningControlScope, if one is present.
19+
@_spi(ExperimentalLanguageFeatures)
20+
public func warningGroupBehavior(
21+
for diagnosticGroupIdentifier: String
22+
) -> WarningGroupBehavior? {
23+
let warningControlRegions = root.warningGroupControlRegionTree()
24+
return warningControlRegions.warningGroupBehavior(at: self.position, for: diagnosticGroupIdentifier)
25+
}
26+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
/// Protocol representing a declaration scope which can potentially carry warning
16+
/// diagnostic group behavior controls (`@warn` attributes).
17+
protocol WarningControlDeclSyntax {
18+
var attributes: AttributeListSyntax { get }
19+
var position: AbsolutePosition { get }
20+
var endPosition: AbsolutePosition { get }
21+
}
22+
23+
// Compute a dictionary of all `@warn` diagnostic group behaviors
24+
// specified on this warning control declaration scope.
25+
extension WarningControlDeclSyntax {
26+
func allWarningGroupControls() -> [String: WarningGroupBehavior] {
27+
return attributes.reduce(into: [String: WarningGroupBehavior]()) { result, attr in
28+
if case .attribute(let attributeSyntax) = attr,
29+
attributeSyntax.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "warn"
30+
{
31+
if let diagnosticGroupID = attributeSyntax.arguments?.as(LabeledExprListSyntax.self)?.first?.expression.as(
32+
DeclReferenceExprSyntax.self
33+
)?.baseName.text {
34+
if let behaviorText = attr.as(AttributeSyntax.self)?
35+
.arguments?.as(LabeledExprListSyntax.self)?
36+
.dropFirst().first?
37+
.expression.as(DeclReferenceExprSyntax.self)?
38+
.baseName.text
39+
{
40+
result[diagnosticGroupID] = WarningGroupBehavior(rawValue: behaviorText)
41+
}
42+
}
43+
}
44+
}
45+
}
46+
}
47+
48+
// All syntax nodes which carry attributes and may represent a warning control scope
49+
50+
// @warn(Deprecate, as: ignored)
51+
// func foo() {...}
52+
extension FunctionDeclSyntax: WarningControlDeclSyntax {}
53+
54+
// @warn(Deprecate, as: ignored)
55+
// class Foo {...}
56+
extension ClassDeclSyntax: WarningControlDeclSyntax {}
57+
58+
// @warn(Deprecate, as: ignored)
59+
// actor Foo {...}
60+
extension ActorDeclSyntax: WarningControlDeclSyntax {}
61+
62+
// struct Foo {
63+
// @warn(Deprecate, as: ignored)
64+
// deinit() {...}
65+
extension DeinitializerDeclSyntax: WarningControlDeclSyntax {}
66+
67+
// struct Foo {
68+
// @warn(Deprecate, as: ignored)
69+
// init() {...}
70+
extension InitializerDeclSyntax: WarningControlDeclSyntax {}
71+
72+
// @warn(Deprecate, as: ignored)
73+
// enum Foo {...}
74+
extension EnumDeclSyntax: WarningControlDeclSyntax {}
75+
76+
// @warn(Deprecate, as: ignored)
77+
// extension Foo {...}
78+
extension ExtensionDeclSyntax: WarningControlDeclSyntax {}
79+
80+
// @warn(Deprecate, as: ignored)
81+
// import bar
82+
extension ImportDeclSyntax: WarningControlDeclSyntax {}
83+
84+
// @warn(Deprecate, as: ignored)
85+
// protocol Foo {...}
86+
extension ProtocolDeclSyntax: WarningControlDeclSyntax {}
87+
88+
// @warn(Deprecate, as: ignored)
89+
// struct Foo {...}
90+
extension StructDeclSyntax: WarningControlDeclSyntax {}
91+
92+
// @warn(Deprecate, as: ignored)
93+
// subscript(index: Int) -> T {...}
94+
extension SubscriptDeclSyntax: WarningControlDeclSyntax {}
95+
96+
// @warn(Deprecate, as: ignored)
97+
// var x: {...}
98+
extension VariableDeclSyntax: WarningControlDeclSyntax {}
99+
100+
// struct Foo {
101+
// var property: Int {
102+
// @warn(Deprecate, as: ignored)
103+
// get {...}
104+
extension AccessorDeclSyntax: WarningControlDeclSyntax {}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 func warningGroupControlRegionTree() -> WarningControlRegionTree {
19+
let visitor = WarningControlRegionVisitor(
20+
start: self.position,
21+
end: self.endPosition
22+
)
23+
visitor.walk(self)
24+
return visitor.tree
25+
}
26+
}
27+
28+
/// Add this warning control decl syntax node warning group controls (as specified with `@warn`)
29+
/// to the specified WarningControlRegionTree.
30+
extension WarningControlDeclSyntax {
31+
func addWarningControlRegionsToTree(_ tree: inout WarningControlRegionTree) {
32+
for control in self.allWarningGroupControls() {
33+
tree.insert(
34+
WarningControlRegion(
35+
start: self.position,
36+
end: self.endPosition,
37+
diagnosticGroupIdentifier: control.key,
38+
behavior: control.value
39+
)
40+
)
41+
}
42+
}
43+
}
44+
45+
/// Helper class that walks a syntax tree looking for warning behavior control regions.
46+
private class WarningControlRegionVisitor: SyntaxVisitor {
47+
/// The tree of warning control regions we have found so far
48+
var tree: WarningControlRegionTree
49+
50+
init(start: AbsolutePosition, end: AbsolutePosition) {
51+
self.tree = WarningControlRegionTree(start: start, end: end)
52+
super.init(viewMode: .fixedUp)
53+
}
54+
55+
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
56+
node.addWarningControlRegionsToTree(&tree)
57+
return .visitChildren
58+
}
59+
60+
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
61+
node.addWarningControlRegionsToTree(&tree)
62+
return .visitChildren
63+
}
64+
65+
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
66+
node.addWarningControlRegionsToTree(&tree)
67+
return .visitChildren
68+
}
69+
70+
override func visit(_ node: DeinitializerDeclSyntax) -> SyntaxVisitorContinueKind {
71+
node.addWarningControlRegionsToTree(&tree)
72+
return .visitChildren
73+
}
74+
75+
override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind {
76+
node.addWarningControlRegionsToTree(&tree)
77+
return .visitChildren
78+
}
79+
80+
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
81+
node.addWarningControlRegionsToTree(&tree)
82+
return .visitChildren
83+
}
84+
85+
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
86+
node.addWarningControlRegionsToTree(&tree)
87+
return .visitChildren
88+
}
89+
90+
override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind {
91+
node.addWarningControlRegionsToTree(&tree)
92+
return .visitChildren
93+
}
94+
95+
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
96+
node.addWarningControlRegionsToTree(&tree)
97+
return .visitChildren
98+
}
99+
100+
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
101+
node.addWarningControlRegionsToTree(&tree)
102+
return .visitChildren
103+
}
104+
105+
override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind {
106+
node.addWarningControlRegionsToTree(&tree)
107+
return .visitChildren
108+
}
109+
110+
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
111+
node.addWarningControlRegionsToTree(&tree)
112+
return .visitChildren
113+
}
114+
115+
override func visit(_ node: AccessorDeclSyntax) -> SyntaxVisitorContinueKind {
116+
node.addWarningControlRegionsToTree(&tree)
117+
return .visitChildren
118+
}
119+
}

0 commit comments

Comments
 (0)