Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .spi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ builder:
- SwiftParser
- SwiftParserDiagnostics
- SwiftRefactor
- SwiftWarningControl
- SwiftSyntaxBuilder
- SwiftSyntaxMacros
- SwiftSyntaxMacroExpansion
Expand Down
19 changes: 19 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ if buildDynamicLibrary {
.library(name: "SwiftDiagnostics", targets: ["SwiftDiagnostics"]),
.library(name: "SwiftIDEUtils", targets: ["SwiftIDEUtils"]),
.library(name: "SwiftIfConfig", targets: ["SwiftIfConfig"]),
.library(name: "SwiftWarningControl", targets: ["SwiftWarningControl"]),
.library(name: "SwiftLexicalLookup", targets: ["SwiftLexicalLookup"]),
.library(name: "SwiftOperators", targets: ["SwiftOperators"]),
.library(name: "SwiftParser", targets: ["SwiftParser"]),
Expand Down Expand Up @@ -180,6 +181,24 @@ let package = Package(
]
),

// MARK: SwiftWarningControl

.target(
name: "SwiftWarningControl",
dependencies: ["SwiftSyntax", "SwiftParser"],
exclude: ["CMakeLists.txt"]
),

.testTarget(
name: "SwiftWarningControlTest",
dependencies: [
"_SwiftSyntaxTestSupport",
"SwiftWarningControl",
"SwiftParser",
"SwiftSyntaxMacrosGenericTestSupport",
]
),

// MARK: SwiftLexicalLookup

.target(
Expand Down
1 change: 1 addition & 0 deletions Sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ add_subdirectory(SwiftSyntaxMacroExpansion)
add_subdirectory(SwiftCompilerPluginMessageHandling)
add_subdirectory(SwiftIDEUtils)
add_subdirectory(SwiftCompilerPlugin)
add_subdirectory(SwiftWarningControl)
add_subdirectory(VersionMarkerModules)
19 changes: 19 additions & 0 deletions Sources/SwiftWarningControl/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 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 http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors

add_swift_syntax_library(SwiftWarningControl
WarningGroupBehavior.swift
WarningControlDeclSyntax.swift
WarningControlRegionBuilder.swift
WarningControlRegions.swift
SyntaxProtocol+WarningControl.swift
)

target_link_swift_syntax_libraries(SwiftWarningControl PUBLIC
SwiftSyntax
SwiftParser)
30 changes: 30 additions & 0 deletions Sources/SwiftWarningControl/SwiftWarningControl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# SwiftWarningControl

A library to evaluate `@warn` diagnostic group controls within a Swift syntax tree.

## Overview

Swift provides 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 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.

```swift
@warn(Deprecate, as: error)
func foo() {
...
@warn(Deprecate, as: warning)
func bar() {
...
@warn("Deprecate", as: ignored, reason: "Foo")
func baz() {
...
}
}
}
```

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.

* `SyntaxProtocol.getWarningGroupControl(for diagnosticGroupIdentifier:)` produces the behavior specifier (`WarningGroupBehavior`: `error`, `warning`, `ignored`) which applies at this node.

* `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.
26 changes: 26 additions & 0 deletions Sources/SwiftWarningControl/SyntaxProtocol+WarningControl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//===----------------------------------------------------------------------===//
//
// 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 SwiftDiagnostics
import SwiftSyntax

extension SyntaxProtocol {
/// Get the warning emission behavior for the specified diagnostic group
/// by determining its containing `WarningControlRegion`, if one is present.
@_spi(ExperimentalLanguageFeatures)
public func warningGroupBehavior(
for diagnosticGroupIdentifier: DiagnosticGroupIdentifier
) -> WarningGroupBehavior? {
let warningControlRegions = root.warningGroupControlRegionTreeImpl(containing: self.position)
return warningControlRegions.warningGroupBehavior(at: self.position, for: diagnosticGroupIdentifier)
}
}
56 changes: 56 additions & 0 deletions Sources/SwiftWarningControl/WarningControlDeclSyntax.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//===----------------------------------------------------------------------===//
//
// 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

extension WithAttributesSyntax {
/// Compute a dictionary of all `@warn` diagnostic group behaviors
/// specified on this warning control declaration scope.
var allWarningGroupControls: [DiagnosticGroupIdentifier: WarningGroupBehavior] {
attributes.reduce(into: [DiagnosticGroupIdentifier: WarningGroupBehavior]()) { result, attr in
// `@warn` attributes
guard case .attribute(let attributeSyntax) = attr,
attributeSyntax.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "warn"
else {
return
}

// First argument is the unquoted diagnostic group identifier
guard
let diagnosticGroupID = attributeSyntax.arguments?
.as(LabeledExprListSyntax.self)?.first?.expression
.as(DeclReferenceExprSyntax.self)?.baseName.text
else {
return
}

// Second argument is the `as: ` behavior specifier
guard
let asParamExprSyntax = attributeSyntax
.arguments?.as(LabeledExprListSyntax.self)?
.dropFirst().first
else {
return
}
guard
asParamExprSyntax.label?.text == "as",
let behaviorText = asParamExprSyntax
.expression.as(DeclReferenceExprSyntax.self)?
.baseName.text,
let behavior = WarningGroupBehavior(rawValue: behaviorText)
else {
return
}
result[DiagnosticGroupIdentifier(diagnosticGroupID)] = behavior
}
}
}
67 changes: 67 additions & 0 deletions Sources/SwiftWarningControl/WarningControlRegionBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//===----------------------------------------------------------------------===//
//
// 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

/// Compute the full set of warning control regions in this syntax node
extension SyntaxProtocol {
@_spi(ExperimentalLanguageFeatures)
public func warningGroupControlRegionTree() -> WarningControlRegionTree {
return warningGroupControlRegionTreeImpl()
}

/// Implementation of constructing a region tree with an optional parameter
/// to specify that the constructed tree must only contain nodes which contain
/// a specific absolute position - meant to speed up tree generation for individual
/// queries.
func warningGroupControlRegionTreeImpl(containing position: AbsolutePosition? = nil) -> WarningControlRegionTree {
let visitor = WarningControlRegionVisitor(self.range, containing: position)
visitor.walk(self)
return visitor.tree
}
}

/// Add this warning control decl syntax node warning group controls (as specified with `@warn`)
/// to the tree.
extension WarningControlRegionTree {
mutating func addWarningControlRegions(for syntax: some WithAttributesSyntax) {
addWarningGroupControls(
range: syntax.range,
controls: syntax.allWarningGroupControls
)
}
}

/// Helper class that walks a syntax tree looking for warning behavior control regions.
private class WarningControlRegionVisitor: SyntaxAnyVisitor {
/// The tree of warning control regions we have found so far
var tree: WarningControlRegionTree
let containingPosition: AbsolutePosition?

init(_ topLevelRange: Range<AbsolutePosition>, containing position: AbsolutePosition? = nil) {
self.tree = WarningControlRegionTree(range: topLevelRange)
containingPosition = position
super.init(viewMode: .fixedUp)
}

override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
if let containingPosition,
!node.range.contains(containingPosition)
{
return .skipChildren
}
if let withAttributesSyntax = node.asProtocol(WithAttributesSyntax.self) {
tree.addWarningControlRegions(for: withAttributesSyntax)
}
return .visitChildren
}
}
Loading