From 593b99cdae116cc79da7e9d438a597b2c6564206 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:22:01 +0000 Subject: [PATCH 1/5] Initial plan From 7edc5acc50e865f925bb1f52e300020c74d83e7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:33:33 +0000 Subject: [PATCH 2/5] Fix async_without_await false positive for override functions Co-authored-by: SimplyDanny <16365760+SimplyDanny@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ .../Rules/Lint/AsyncWithoutAwaitRule.swift | 2 ++ .../Lint/AsyncWithoutAwaitRuleExamples.swift | 24 +++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3df538cf3..1811c02fc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ ### Bug Fixes +* Fix false positive in `async_without_await` rule for `override` functions that + must keep the `async` keyword to properly override the parent's async function. + [copilot](https://github.com/copilot) + * Fix false positive in `unneeded_escaping` rule when an escaping closure is used in a nested closure preceded by another closure. [SimplyDanny](https://github.com/SimplyDanny) diff --git a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift index 7b8b0755b6..38cbd71b6a 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift @@ -31,7 +31,9 @@ private extension AsyncWithoutAwaitRule { } // @concurrent functions require the async keyword even without await calls + // Override functions must keep async to properly override parent's async function let asyncToken = node.attributes.contains(attributeNamed: "concurrent") + || node.modifiers.contains(keyword: .override) ? nil : node.signature.effectSpecifiers?.asyncSpecifier functionScopes.push(.init(asyncToken: asyncToken)) diff --git a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRuleExamples.swift b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRuleExamples.swift index 91b4a0112c..6da8239f35 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRuleExamples.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRuleExamples.swift @@ -188,6 +188,30 @@ internal struct AsyncWithoutAwaitRuleExamples { let c: () async -> Int = { @concurrent in 1 } } """), + Example(""" + class A { + func test() async { + await foo() + } + } + class B: A { + override func test() async { + print("B") + } + } + """), + Example(""" + class Parent { + func asyncMethod() async throws { + try await someAsyncWork() + } + } + class Child: Parent { + override func asyncMethod() async throws { + print("Child implementation") + } + } + """), ] static let triggeringExamples = [ From e17bd2e32af75cfc79fb20d8210e67fa3c45b7a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:46:43 +0000 Subject: [PATCH 3/5] Add override support for accessors and initializers in async_without_await rule Co-authored-by: SimplyDanny <16365760+SimplyDanny@users.noreply.github.com> --- .../Rules/Lint/AsyncWithoutAwaitRule.swift | 19 ++++++++++++- .../Lint/AsyncWithoutAwaitRuleExamples.swift | 28 +++++++++++-------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift index 38cbd71b6a..58018265ce 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift @@ -68,7 +68,10 @@ private extension AsyncWithoutAwaitRule { return .visitChildren } - let asyncToken = node.effectSpecifiers?.asyncSpecifier + // Check if the parent variable/subscript has override modifier + let hasOverride = Syntax(node).closestVariableOrSubscript()?.contains(keyword: .override) ?? false + let asyncToken = hasOverride + ? nil : node.effectSpecifiers?.asyncSpecifier functionScopes.push(.init(asyncToken: asyncToken)) return .visitChildren @@ -86,7 +89,9 @@ private extension AsyncWithoutAwaitRule { } // @concurrent can be applied to initializers + // Override initializers must keep async to properly override parent's async initializer let asyncToken = node.attributes.contains(attributeNamed: "concurrent") + || node.modifiers.contains(keyword: .override) ? nil : node.signature.effectSpecifiers?.asyncSpecifier functionScopes.push(.init(asyncToken: asyncToken)) @@ -165,3 +170,15 @@ private extension TypeSyntax { return nil } } + +private extension Syntax { + func closestVariableOrSubscript() -> DeclModifierListSyntax? { + if let variableDecl = `as`(VariableDeclSyntax.self) { + return variableDecl.modifiers + } + if let subscriptDecl = `as`(SubscriptDeclSyntax.self) { + return subscriptDecl.modifiers + } + return parent?.closestVariableOrSubscript() + } +} diff --git a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRuleExamples.swift b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRuleExamples.swift index 6da8239f35..f1bdd32449 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRuleExamples.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRuleExamples.swift @@ -189,29 +189,33 @@ internal struct AsyncWithoutAwaitRuleExamples { } """), Example(""" - class A { - func test() async { - await foo() - } + class Parent { + func test() async { await foo() } } - class B: A { - override func test() async { - print("B") - } + class Child: Parent { + override func test() async { print("Child") } } """), Example(""" class Parent { - func asyncMethod() async throws { - try await someAsyncWork() + var prop: Int { + get async { await fetchValue() } } } class Child: Parent { - override func asyncMethod() async throws { - print("Child implementation") + override var prop: Int { + get async { return 2 } } } """), + Example(""" + class Base { + init() async { await setup() } + } + class Derived: Base { + override init() async { print("Derived") } + } + """), ] static let triggeringExamples = [ From 5a13db436e16540c423cf106a803a6f989dfcbc8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:52:28 +0000 Subject: [PATCH 4/5] Add documentation and issue reference placeholder Co-authored-by: SimplyDanny <16365760+SimplyDanny@users.noreply.github.com> --- CHANGELOG.md | 1 + .../Rules/Lint/AsyncWithoutAwaitRule.swift | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1811c02fc7..58647b2c22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * Fix false positive in `async_without_await` rule for `override` functions that must keep the `async` keyword to properly override the parent's async function. [copilot](https://github.com/copilot) + [#ISSUE_NUMBER](https://github.com/realm/SwiftLint/issues/ISSUE_NUMBER) * Fix false positive in `unneeded_escaping` rule when an escaping closure is used in a nested closure preceded by another closure. diff --git a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift index 58018265ce..e308ab3da2 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift @@ -172,6 +172,8 @@ private extension TypeSyntax { } private extension Syntax { + /// Traverses up the syntax tree to find the nearest variable or subscript declaration. + /// - Returns: The modifiers of the closest variable or subscript declaration, or `nil` if none is found. func closestVariableOrSubscript() -> DeclModifierListSyntax? { if let variableDecl = `as`(VariableDeclSyntax.self) { return variableDecl.modifiers From 33c486a45ea3bdeb6b9ab9a57ecc612f95667513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danny=20M=C3=B6sch?= Date: Tue, 6 Jan 2026 22:45:38 +0100 Subject: [PATCH 5/5] Refactor --- CHANGELOG.md | 7 +-- .../Rules/Lint/AsyncWithoutAwaitRule.swift | 58 +++++++------------ 2 files changed, 24 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58647b2c22..a19859df87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,9 @@ ### Bug Fixes -* Fix false positive in `async_without_await` rule for `override` functions that - must keep the `async` keyword to properly override the parent's async function. - [copilot](https://github.com/copilot) - [#ISSUE_NUMBER](https://github.com/realm/SwiftLint/issues/ISSUE_NUMBER) +* Ignore `override` functions in `async_without_await` rule. + [SimplyDanny](https://github.com/SimplyDanny) + [#6416](https://github.com/realm/SwiftLint/issues/6416) * Fix false positive in `unneeded_escaping` rule when an escaping closure is used in a nested closure preceded by another closure. diff --git a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift index e308ab3da2..b1be636fcf 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Lint/AsyncWithoutAwaitRule.swift @@ -26,17 +26,10 @@ private extension AsyncWithoutAwaitRule { private var pendingAsync: TokenSyntax? override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { - guard node.body != nil else { - return .visitChildren + if node.body != nil { + let asyncToken = node.needsToKeepAsync ? nil : node.signature.effectSpecifiers?.asyncSpecifier + functionScopes.push(.init(asyncToken: asyncToken)) } - - // @concurrent functions require the async keyword even without await calls - // Override functions must keep async to properly override parent's async function - let asyncToken = node.attributes.contains(attributeNamed: "concurrent") - || node.modifiers.contains(keyword: .override) - ? nil : node.signature.effectSpecifiers?.asyncSpecifier - functionScopes.push(.init(asyncToken: asyncToken)) - return .visitChildren } @@ -47,7 +40,7 @@ private extension AsyncWithoutAwaitRule { } override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind { - // @concurrent closures require the async keyword even without await calls + // @concurrent closures require the async keyword even without await calls, let asyncToken = (node.signature?.attributes.contains(attributeNamed: "concurrent") ?? false) ? nil : pendingAsync functionScopes.push(.init(asyncToken: asyncToken)) @@ -64,16 +57,10 @@ private extension AsyncWithoutAwaitRule { } override func visit(_ node: AccessorDeclSyntax) -> SyntaxVisitorContinueKind { - guard node.body != nil else { - return .visitChildren + if node.body != nil { + let asyncToken = node.needsToKeepAsync ? nil : node.effectSpecifiers?.asyncSpecifier + functionScopes.push(.init(asyncToken: asyncToken)) } - - // Check if the parent variable/subscript has override modifier - let hasOverride = Syntax(node).closestVariableOrSubscript()?.contains(keyword: .override) ?? false - let asyncToken = hasOverride - ? nil : node.effectSpecifiers?.asyncSpecifier - functionScopes.push(.init(asyncToken: asyncToken)) - return .visitChildren } @@ -84,17 +71,10 @@ private extension AsyncWithoutAwaitRule { } override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { - guard node.body != nil else { - return .visitChildren + if node.body != nil { + let asyncToken = node.needsToKeepAsync ? nil : node.signature.effectSpecifiers?.asyncSpecifier + functionScopes.push(.init(asyncToken: asyncToken)) } - - // @concurrent can be applied to initializers - // Override initializers must keep async to properly override parent's async initializer - let asyncToken = node.attributes.contains(attributeNamed: "concurrent") - || node.modifiers.contains(keyword: .override) - ? nil : node.signature.effectSpecifiers?.asyncSpecifier - functionScopes.push(.init(asyncToken: asyncToken)) - return .visitChildren } @@ -171,16 +151,20 @@ private extension TypeSyntax { } } -private extension Syntax { - /// Traverses up the syntax tree to find the nearest variable or subscript declaration. - /// - Returns: The modifiers of the closest variable or subscript declaration, or `nil` if none is found. - func closestVariableOrSubscript() -> DeclModifierListSyntax? { +private extension WithModifiersSyntax where Self: WithAttributesSyntax { + var needsToKeepAsync: Bool { + attributes.contains(attributeNamed: "concurrent") || modifiers.contains(keyword: .override) + } +} + +private extension SyntaxProtocol { + var needsToKeepAsync: Bool { if let variableDecl = `as`(VariableDeclSyntax.self) { - return variableDecl.modifiers + return variableDecl.needsToKeepAsync } if let subscriptDecl = `as`(SubscriptDeclSyntax.self) { - return subscriptDecl.modifiers + return subscriptDecl.needsToKeepAsync } - return parent?.closestVariableOrSubscript() + return parent?.needsToKeepAsync ?? false } }