Skip to content

Commit bd53a85

Browse files
authored
Merge pull request #3174 from artemcm/WarningControlScopes
Add 'SwiftWarningControl' library for source-level lexical warning group behavior settings
2 parents ef033f4 + a92c94c commit bd53a85

File tree

11 files changed

+812
-0
lines changed

11 files changed

+812
-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.warningGroupControlRegionTreeImpl(containing: self.position)
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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
return warningGroupControlRegionTreeImpl()
20+
}
21+
22+
/// Implementation of constructing a region tree with an optional parameter
23+
/// to specify that the constructed tree must only contain nodes which contain
24+
/// a specific absolute position - meant to speed up tree generation for individual
25+
/// queries.
26+
func warningGroupControlRegionTreeImpl(containing position: AbsolutePosition? = nil) -> WarningControlRegionTree {
27+
let visitor = WarningControlRegionVisitor(self.range, containing: position)
28+
visitor.walk(self)
29+
return visitor.tree
30+
}
31+
}
32+
33+
/// Add this warning control decl syntax node warning group controls (as specified with `@warn`)
34+
/// to the tree.
35+
extension WarningControlRegionTree {
36+
mutating func addWarningControlRegions(for syntax: some WithAttributesSyntax) {
37+
addWarningGroupControls(
38+
range: syntax.range,
39+
controls: syntax.allWarningGroupControls
40+
)
41+
}
42+
}
43+
44+
/// Helper class that walks a syntax tree looking for warning behavior control regions.
45+
private class WarningControlRegionVisitor: SyntaxAnyVisitor {
46+
/// The tree of warning control regions we have found so far
47+
var tree: WarningControlRegionTree
48+
let containingPosition: AbsolutePosition?
49+
50+
init(_ topLevelRange: Range<AbsolutePosition>, containing position: AbsolutePosition? = nil) {
51+
self.tree = WarningControlRegionTree(range: topLevelRange)
52+
containingPosition = position
53+
super.init(viewMode: .fixedUp)
54+
}
55+
56+
override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
57+
if let containingPosition,
58+
!node.range.contains(containingPosition)
59+
{
60+
return .skipChildren
61+
}
62+
if let withAttributesSyntax = node.asProtocol(WithAttributesSyntax.self) {
63+
tree.addWarningControlRegions(for: withAttributesSyntax)
64+
}
65+
return .visitChildren
66+
}
67+
}

0 commit comments

Comments
 (0)