Skip to content

Commit 82e9d6c

Browse files
committed
[SwiftWarningControl] Add support for file-scoped 'using @warn()'
These declarations may appear as top-level statements and affect the entire source file.
1 parent ca0480b commit 82e9d6c

File tree

5 files changed

+130
-38
lines changed

5 files changed

+130
-38
lines changed

Sources/SwiftWarningControl/WarningControlDeclSyntax.swift

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,67 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import SwiftSyntax
13+
@_spi(ExperimentalLanguageFeatures) import SwiftSyntax
14+
15+
extension AttributeSyntax {
16+
var warningGroupControl: (DiagnosticGroupIdentifier, WarningGroupControl)? {
17+
guard attributeName.as(IdentifierTypeSyntax.self)?.name.text == "warn"
18+
else {
19+
return nil
20+
}
21+
22+
// First argument is the unquoted diagnostic group identifier
23+
guard
24+
let diagnosticGroupID = arguments?
25+
.as(LabeledExprListSyntax.self)?.first?.expression
26+
.as(DeclReferenceExprSyntax.self)?.baseName.text
27+
else {
28+
return nil
29+
}
30+
31+
// Second argument is the `as: ` behavior control specifier
32+
guard
33+
let asParamExprSyntax = arguments?.as(LabeledExprListSyntax.self)?
34+
.dropFirst().first
35+
else {
36+
return nil
37+
}
38+
guard
39+
asParamExprSyntax.label?.text == "as",
40+
let controlText = asParamExprSyntax
41+
.expression.as(DeclReferenceExprSyntax.self)?
42+
.baseName.text,
43+
let control = WarningGroupControl(rawValue: controlText)
44+
else {
45+
return nil
46+
}
47+
48+
return (DiagnosticGroupIdentifier(diagnosticGroupID), control)
49+
}
50+
}
1451

1552
extension WithAttributesSyntax {
1653
/// Compute a dictionary of all `@warn` diagnostic group behavior controls
1754
/// specified on this warning control declaration scope.
1855
var allWarningGroupControls: [(DiagnosticGroupIdentifier, WarningGroupControl)] {
1956
attributes.reduce(into: [(DiagnosticGroupIdentifier, WarningGroupControl)]()) { result, attr in
20-
// `@warn` attributes
2157
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
58+
let warningGroupControl = attributeSyntax.warningGroupControl
3259
else {
3360
return
3461
}
62+
result.append(warningGroupControl)
63+
}
64+
}
65+
}
3566

36-
// Second argument is the `as: ` behavior control 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 controlText = asParamExprSyntax
47-
.expression.as(DeclReferenceExprSyntax.self)?
48-
.baseName.text,
49-
let control = WarningGroupControl(rawValue: controlText)
50-
else {
51-
return
52-
}
53-
result.append((DiagnosticGroupIdentifier(diagnosticGroupID), control))
67+
extension UsingDeclSyntax {
68+
var warningControl: (DiagnosticGroupIdentifier, WarningGroupControl)? {
69+
guard case .attribute(let attributeSyntax) = self.specifier,
70+
let warningGroupControl = attributeSyntax.warningGroupControl
71+
else {
72+
return nil
5473
}
74+
return warningGroupControl
5575
}
5676
}

Sources/SwiftWarningControl/WarningControlRegionBuilder.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import SwiftSyntax
13+
@_spi(ExperimentalLanguageFeatures) import SwiftSyntax
1414

1515
/// Compute the full set of warning control regions in this syntax node
1616
extension SyntaxProtocol {
@@ -73,14 +73,21 @@ private class WarningControlRegionVisitor: SyntaxAnyVisitor {
7373
}
7474

7575
override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
76+
if let withAttributesSyntax = node.asProtocol(WithAttributesSyntax.self) {
77+
tree.addWarningControlRegions(for: withAttributesSyntax)
78+
}
79+
if let usingAttributedSyntax = node.as(UsingDeclSyntax.self),
80+
let usingWarningControl = usingAttributedSyntax.warningControl
81+
{
82+
tree.addRootWarningGroupControls(controls: [usingWarningControl])
83+
}
84+
7685
if let containingPosition,
86+
node.isProtocol(DeclSyntaxProtocol.self),
7787
!node.range.contains(containingPosition)
7888
{
7989
return .skipChildren
8090
}
81-
if let withAttributesSyntax = node.asProtocol(WithAttributesSyntax.self) {
82-
tree.addWarningControlRegions(for: withAttributesSyntax)
83-
}
8491
return .visitChildren
8592
}
8693
}

Sources/SwiftWarningControl/WarningControlRegions.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ public struct WarningControlRegionTree {
149149
insertIntoSubtree(newNode, parent: rootRegionNode)
150150
}
151151

152+
/// Add warning control regions to the tree root node
153+
mutating func addRootWarningGroupControls(controls: [(DiagnosticGroupIdentifier, WarningGroupControl)]) {
154+
addWarningGroupControls(range: rootRegionNode.range, controls: controls)
155+
}
156+
152157
/// Insert a region node into the appropriate position in a subtree.
153158
/// During top-down traversal of the syntax tree, nodes that are visited
154159
/// later should never contain any of the previously visited nodes,

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3693,6 +3693,39 @@ final class UsingDeclarationTests: ParserTestCase {
36933693
)
36943694
)
36953695

3696+
assertParse(
3697+
"using @warn(DiagGroupID, as: warning)",
3698+
substructure: UsingDeclSyntax(
3699+
usingKeyword: .keyword(.using),
3700+
specifier: .attribute(
3701+
AttributeSyntax(
3702+
attributeName: IdentifierTypeSyntax(
3703+
name: .identifier("warn")
3704+
),
3705+
leftParen: .leftParenToken(),
3706+
arguments: .argumentList(
3707+
LabeledExprListSyntax([
3708+
LabeledExprSyntax(
3709+
expression: DeclReferenceExprSyntax(
3710+
baseName: .identifier("DiagGroupID")
3711+
),
3712+
trailingComma: .commaToken()
3713+
),
3714+
LabeledExprSyntax(
3715+
label: .identifier("as"),
3716+
colon: .colonToken(),
3717+
expression: DeclReferenceExprSyntax(
3718+
baseName: .identifier("warning")
3719+
)
3720+
),
3721+
])
3722+
),
3723+
rightParen: .rightParenToken()
3724+
)
3725+
)
3726+
)
3727+
)
3728+
36963729
assertParse(
36973730
"""
36983731
nonisolated

Tests/SwiftWarningControlTest/WarningControlTests.swift

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import SwiftParser
14-
import SwiftSyntax
13+
@_spi(ExperimentalLanguageFeatures) import SwiftParser
14+
@_spi(ExperimentalLanguageFeatures) import SwiftSyntax
1515
@_spi(ExperimentalLanguageFeatures) import SwiftWarningControl
1616
import XCTest
1717
import _SwiftSyntaxGenericTestSupport
@@ -387,6 +387,32 @@ public class WarningGroupControlTests: XCTestCase {
387387
]
388388
)
389389
}
390+
391+
func testFileScopeUsingWarningGroupControl() throws {
392+
try assertWarningGroupControl(
393+
"""
394+
0️⃣let x = 1
395+
@warn(GroupID, as: warning)
396+
struct Bar {
397+
1️⃣let y = 1
398+
}
399+
struct Foo {
400+
@warn(GroupID, as: ignored)
401+
var property: Int {
402+
2️⃣return 11
403+
}
404+
}
405+
using @warn(GroupID, as: error)
406+
""",
407+
experimentalFeatures: [.defaultIsolationPerFile],
408+
diagnosticGroupID: "GroupID",
409+
states: [
410+
"0️⃣": .error,
411+
"1️⃣": .warning,
412+
"2️⃣": .ignored,
413+
]
414+
)
415+
}
390416
}
391417

392418
/// Assert that the various marked positions in the source code have the
@@ -395,15 +421,16 @@ private func assertWarningGroupControl(
395421
_ markedSource: String,
396422
globalControls: [(DiagnosticGroupIdentifier, WarningGroupControl)] = [],
397423
groupInheritanceTree: DiagnosticGroupInheritanceTree? = nil,
424+
experimentalFeatures: Parser.ExperimentalFeatures? = nil,
398425
diagnosticGroupID: DiagnosticGroupIdentifier,
399426
states: [String: WarningGroupControl?],
400427
file: StaticString = #filePath,
401428
line: UInt = #line
402429
) throws {
403430
// Pull out the markers that we'll use to dig out nodes to query.
404431
let (markerLocations, source) = extractMarkers(markedSource)
405-
406-
var parser = Parser(source)
432+
let experimentalFeatures = experimentalFeatures ?? Parser.ExperimentalFeatures(rawValue: 0)
433+
var parser = Parser(source, experimentalFeatures: experimentalFeatures)
407434
let tree = SourceFileSyntax.parse(from: &parser)
408435
for (marker, location) in markerLocations {
409436
guard let expectedState = states[marker] else {

0 commit comments

Comments
 (0)