From fc34c2e7fcc5180431074dec7c2bc1a5e0ced28c Mon Sep 17 00:00:00 2001 From: Artem Chikin Date: Tue, 2 Dec 2025 15:31:15 -0800 Subject: [PATCH] [SwiftWarningControl] Specify control inputs to tree node in order Specifically for global controls, the ordering is important because later controls override earlier controls. Similarly, this will result in the same semantic for syntactic controls, with the first attribute being able to be overriden by subsequent attributes. The control configuration logic as-is handles that correctly *except* for iterating over input controls which are supplied in an unordered dictionary. This refactors the API surface to accept an array of pairs '(groupIdentifier, groupControl)', instead of a dictionary. --- .../SyntaxProtocol+WarningControl.swift | 2 +- .../WarningControlDeclSyntax.swift | 6 +-- .../WarningControlRegionBuilder.swift | 4 +- .../WarningControlRegions.swift | 2 +- .../WarningControlTests.swift | 44 +++++++++++++++++-- 5 files changed, 47 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftWarningControl/SyntaxProtocol+WarningControl.swift b/Sources/SwiftWarningControl/SyntaxProtocol+WarningControl.swift index 2b058d721a0..c3d8a4fd305 100644 --- a/Sources/SwiftWarningControl/SyntaxProtocol+WarningControl.swift +++ b/Sources/SwiftWarningControl/SyntaxProtocol+WarningControl.swift @@ -27,7 +27,7 @@ extension SyntaxProtocol { @_spi(ExperimentalLanguageFeatures) public func warningGroupControl( for diagnosticGroupIdentifier: DiagnosticGroupIdentifier, - globalControls: [DiagnosticGroupIdentifier: WarningGroupControl] = [:], + globalControls: [(DiagnosticGroupIdentifier, WarningGroupControl)] = [], groupInheritanceTree: DiagnosticGroupInheritanceTree? = nil ) -> WarningGroupControl? { let warningControlRegions = root.warningGroupControlRegionTreeImpl( diff --git a/Sources/SwiftWarningControl/WarningControlDeclSyntax.swift b/Sources/SwiftWarningControl/WarningControlDeclSyntax.swift index 6480600645a..dbc406e45f9 100644 --- a/Sources/SwiftWarningControl/WarningControlDeclSyntax.swift +++ b/Sources/SwiftWarningControl/WarningControlDeclSyntax.swift @@ -15,8 +15,8 @@ import SwiftSyntax extension WithAttributesSyntax { /// Compute a dictionary of all `@warn` diagnostic group behavior controls /// specified on this warning control declaration scope. - var allWarningGroupControls: [DiagnosticGroupIdentifier: WarningGroupControl] { - attributes.reduce(into: [DiagnosticGroupIdentifier: WarningGroupControl]()) { result, attr in + var allWarningGroupControls: [(DiagnosticGroupIdentifier, WarningGroupControl)] { + attributes.reduce(into: [(DiagnosticGroupIdentifier, WarningGroupControl)]()) { result, attr in // `@warn` attributes guard case .attribute(let attributeSyntax) = attr, attributeSyntax.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "warn" @@ -50,7 +50,7 @@ extension WithAttributesSyntax { else { return } - result[DiagnosticGroupIdentifier(diagnosticGroupID)] = control + result.append((DiagnosticGroupIdentifier(diagnosticGroupID), control)) } } } diff --git a/Sources/SwiftWarningControl/WarningControlRegionBuilder.swift b/Sources/SwiftWarningControl/WarningControlRegionBuilder.swift index 285864bd2ee..fc156a262c7 100644 --- a/Sources/SwiftWarningControl/WarningControlRegionBuilder.swift +++ b/Sources/SwiftWarningControl/WarningControlRegionBuilder.swift @@ -16,7 +16,7 @@ import SwiftSyntax extension SyntaxProtocol { @_spi(ExperimentalLanguageFeatures) public func warningGroupControlRegionTree( - globalControls: [DiagnosticGroupIdentifier: WarningGroupControl] = [:], + globalControls: [(DiagnosticGroupIdentifier, WarningGroupControl)] = [], groupInheritanceTree: DiagnosticGroupInheritanceTree? = nil ) -> WarningControlRegionTree { return warningGroupControlRegionTreeImpl( @@ -30,7 +30,7 @@ extension SyntaxProtocol { /// a specific absolute position - meant to speed up tree generation for individual /// queries. func warningGroupControlRegionTreeImpl( - globalControls: [DiagnosticGroupIdentifier: WarningGroupControl], + globalControls: [(DiagnosticGroupIdentifier, WarningGroupControl)], groupInheritanceTree: DiagnosticGroupInheritanceTree?, containing position: AbsolutePosition? = nil ) -> WarningControlRegionTree { diff --git a/Sources/SwiftWarningControl/WarningControlRegions.swift b/Sources/SwiftWarningControl/WarningControlRegions.swift index 8faf4c5901b..ce4d84b6c3d 100644 --- a/Sources/SwiftWarningControl/WarningControlRegions.swift +++ b/Sources/SwiftWarningControl/WarningControlRegions.swift @@ -123,7 +123,7 @@ public struct WarningControlRegionTree { /// Add a warning control region to the tree mutating func addWarningGroupControls( range: Range, - controls: [DiagnosticGroupIdentifier: WarningGroupControl] + controls: [(DiagnosticGroupIdentifier, WarningGroupControl)] ) { guard !controls.isEmpty else { return } let newNode = WarningControlRegionNode(range: range) diff --git a/Tests/SwiftWarningControlTest/WarningControlTests.swift b/Tests/SwiftWarningControlTest/WarningControlTests.swift index acf23b2012d..8afa9baf0c1 100644 --- a/Tests/SwiftWarningControlTest/WarningControlTests.swift +++ b/Tests/SwiftWarningControlTest/WarningControlTests.swift @@ -272,7 +272,7 @@ public class WarningGroupControlTests: XCTestCase { 1️⃣let x = 1 } """, - globalControls: ["GroupID": .warning], + globalControls: [("GroupID", .warning)], diagnosticGroupID: "GroupID", states: [ "1️⃣": .error @@ -289,7 +289,7 @@ public class WarningGroupControlTests: XCTestCase { } } """, - globalControls: ["GroupID": .error], + globalControls: [("GroupID", .error)], diagnosticGroupID: "GroupID", states: [ "1️⃣": .error, @@ -306,7 +306,7 @@ public class WarningGroupControlTests: XCTestCase { 1️⃣let x = 1 } """, - globalControls: ["GroupID": .warning], + globalControls: [("GroupID", .warning)], diagnosticGroupID: "GroupID", states: [ "1️⃣": .warning @@ -351,13 +351,49 @@ public class WarningGroupControlTests: XCTestCase { ) } } + + func testOrderedGlobalControls() throws { + // Parent group is ignored, followed by sub-group treated as warning + try assertWarningGroupControl( + """ + func foo() { + 1️⃣let x = 1 + } + """, + globalControls: [("SuperGroupID", .ignored), ("GroupID", .warning)], + groupInheritanceTree: DiagnosticGroupInheritanceTree(subGroups: [ + "SuperGroupID": ["GroupID"] + ]), + diagnosticGroupID: "GroupID", + states: [ + "1️⃣": .warning + ] + ) + + // Parent group is treated as warning, followed by ignored sub-group + try assertWarningGroupControl( + """ + func foo() { + 1️⃣let x = 1 + } + """, + globalControls: [("SuperGroupID", .warning), ("GroupID", .ignored)], + groupInheritanceTree: DiagnosticGroupInheritanceTree(subGroups: [ + "SuperGroupID": ["GroupID"] + ]), + diagnosticGroupID: "GroupID", + states: [ + "1️⃣": .ignored + ] + ) + } } /// Assert that the various marked positions in the source code have the /// expected warning behavior controls. private func assertWarningGroupControl( _ markedSource: String, - globalControls: [DiagnosticGroupIdentifier: WarningGroupControl] = [:], + globalControls: [(DiagnosticGroupIdentifier, WarningGroupControl)] = [], groupInheritanceTree: DiagnosticGroupInheritanceTree? = nil, diagnosticGroupID: DiagnosticGroupIdentifier, states: [String: WarningGroupControl?],