Skip to content

Comments

Add redundant_final_actor opt-in rule#6504

Open
William-Laverty wants to merge 3 commits intorealm:mainfrom
William-Laverty:feat/redundant-final-actor-rule
Open

Add redundant_final_actor opt-in rule#6504
William-Laverty wants to merge 3 commits intorealm:mainfrom
William-Laverty:feat/redundant-final-actor-rule

Conversation

@William-Laverty
Copy link

Summary

Implements #6407.

Actors in Swift cannot be subclassed (SE-0306), so the final modifier is always redundant on actor declarations. The compiler doesn't warn about this, but it's unnecessary noise similar to redundant internal access control.

New Rule: redundant_final_actor

  • Kind: Idiomatic
  • Default severity: Warning
  • Opt-in: Yes (since it doesn't affect logic, but may surprise developers unfamiliar with actor semantics)
  • Auto-correctable: Yes — removes the redundant final modifier

Examples

// Triggering
final actor MyActor {}           // → actor MyActor {}
public final actor DataStore {}  // → public actor DataStore {}

// Non-triggering
actor MyActor {}
final class MyClass {}

Changes

  • Added RedundantFinalActorRule.swift with visitor + rewriter
  • Registered in BuiltInRules.swift

@SwiftLintBot
Copy link

SwiftLintBot commented Feb 19, 2026

15 Warnings
⚠️ If this is a user-facing change, please include a CHANGELOG entry to credit yourself!
You can find it at CHANGELOG.md.
⚠️ This PR introduced a violation in DuckDuckGo: /SharedPackages/VPN/Sources/VPN/Diagnostics/NetworkProtectionConnectionBandwidthAnalyzer.swift:26:1: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in DuckDuckGo: /SharedPackages/VPN/Sources/VPN/Diagnostics/KeyExpirationTester.swift:27:1: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in DuckDuckGo: /SharedPackages/BrowserServicesKit/Sources/BrowserServicesKit/Watchdog/Watchdog.swift:26:8: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in DuckDuckGo: /SharedPackages/BrowserServicesKit/Sources/Networking/Auth/OAuthClient.swift:151:1: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in DuckDuckGo: /iOS/DuckDuckGo/AdAttribution/AdAttributionPixelReporter.swift:25:1: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in DuckDuckGo: /iOS/SharedTestUtils/Mocks/Core/Statistics/MockPixelFiring.swift:42:1: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in Swift: /stdlib/public/Concurrency/MainActor.swift:20:21: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in Swift: /stdlib/public/Concurrency/MainActor.swift:42:21: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in Wire: /wire-ios/Wire-iOS/Sources/UserInterface/Conversation/MessagesIndividualUpdatesFactory.swift:23:1: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in Wire: /WireMessaging/Sources/WireMessagingData/WireDrive/WireDriveNodeUploadManager.swift:23:9: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in Wire: /WireMessaging/Sources/WireMessagingData/WireDrive/NodesAPI/NodesAPI.swift:28:9: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in Wire: /wire-ios-data-model/Source/MLS/SubconversationGroupIDRepository.swift:68:8: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in WordPress: /WordPress/Classes/Services/BlockEditorCache.swift:6:1: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
⚠️ This PR introduced a violation in WordPress: /Tests/KeystoneTests/Tests/Utility/ApplicationPasswordsRepositoryTests.swift:599:9: Warning: final is redundant on an actor declaration because actors cannot be subclassed (redundant_final_actor)
19 Messages
📖 Building this branch resulted in a binary size of 27283.73 KiB vs 27274.32 KiB when built on main (0% larger).
📖 Linting Aerial with this PR took 0.81 s vs 0.83 s on main (2% faster).
📖 Linting Alamofire with this PR took 1.07 s vs 1.06 s on main (0% slower).
📖 Linting Brave with this PR took 7.22 s vs 7.23 s on main (0% faster).
📖 Linting DuckDuckGo with this PR took 25.76 s vs 25.7 s on main (0% slower).
📖 Linting Firefox with this PR took 11.84 s vs 11.81 s on main (0% slower).
📖 Linting Kickstarter with this PR took 8.09 s vs 8.06 s on main (0% slower).
📖 Linting Moya with this PR took 0.48 s vs 0.45 s on main (6% slower).
📖 Linting NetNewsWire with this PR took 2.46 s vs 2.46 s on main (0% slower).
📖 Linting Nimble with this PR took 0.7 s vs 0.7 s on main (0% slower).
📖 Linting PocketCasts with this PR took 7.78 s vs 7.6 s on main (2% slower).
📖 Linting Quick with this PR took 0.46 s vs 0.45 s on main (2% slower).
📖 Linting Realm with this PR took 3.02 s vs 2.99 s on main (1% slower).
📖 Linting Sourcery with this PR took 1.88 s vs 1.86 s on main (1% slower).
📖 Linting Swift with this PR took 4.63 s vs 4.63 s on main (0% slower).
📖 Linting SwiftLintPerformanceTests with this PR took 0.37 s vs 0.35 s on main (5% slower).
📖 Linting VLC with this PR took 1.19 s vs 1.18 s on main (0% slower).
📖 Linting Wire with this PR took 18.54 s vs 18.53 s on main (0% slower).
📖 Linting WordPress with this PR took 12.57 s vs 12.56 s on main (0% slower).

Here's an example of your CHANGELOG entry:

* Add `redundant_final_actor` opt-in rule.  
  [William-Laverty](https://github.com/William-Laverty)
  [#issue_number](https://github.com/realm/SwiftLint/issues/issue_number)

note: There are two invisible spaces after the entry's text.

Generated by 🚫 Danger

Actors in Swift cannot be subclassed (SE-0306), making the `final`
modifier redundant on actor declarations. This new opt-in rule detects
and auto-corrects `final actor` to just `actor`.

Examples:
  - `final actor MyActor {}` → `actor MyActor {}`
  - `public final actor DataStore {}` → `public actor DataStore {}`

Closes realm#6407
- Use @SwiftSyntaxRule(explicitRewriter: true, optIn: true) instead of
  directly conforming to OptInRule, which caused a type mismatch
- Rebase on latest upstream main
- Re-run rules and reporters register
@William-Laverty William-Laverty force-pushed the feat/redundant-final-actor-rule branch from 2d01a01 to f655ffa Compare February 22, 2026 09:07
Copy link
Collaborator

@SimplyDanny SimplyDanny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea!

Have in mind, however, that actors don't support inheritance now. That is open to change in the future. We should perhaps mention that in a rational. RuleDescription support a rational value we can use to explain that a little and consider it a caveat.

What about final methods and properties?

actor A {
  final func f() {}
}

final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override func visitPost(_ node: ActorDeclSyntax) {
if let finalModifier = node.modifiers.first(where: { $0.name.text == "final" }) {
violations.append(finalModifier.positionAfterSkippingLeadingTrivia)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can append violations objects here which can contain a correction. It's a simple range-replace-based correction, but should be sufficient for this use case. If something is possible at all, it is often much easier than an explicit rewriter.

Address SimplyDanny's suggestion to use range-replace correction
appended to the violation, removing the need for a separate Rewriter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants