-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new redundant_sendable
rule
#5902
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import SwiftSyntax | ||
|
||
@SwiftSyntaxRule(explicitRewriter: true) | ||
struct RedundantSendableRule: Rule { | ||
var configuration = RedundantSendableConfiguration() | ||
|
||
static let description = RuleDescription( | ||
identifier: "redundant_sendable", | ||
name: "Redundant Sendable", | ||
description: "Sendable conformance is redundant on an actor-isolated type", | ||
kind: .lint, | ||
nonTriggeringExamples: [ | ||
Example("struct S: Sendable {}"), | ||
Example("class C: Sendable {}"), | ||
Example("actor A {}"), | ||
Example("@MainActor struct S {}"), | ||
Example("@MyActor enum E: Sendable { case a }"), | ||
], | ||
triggeringExamples: [ | ||
Example("@MainActor struct ↓S: Sendable {}"), | ||
Example("actor ↓A: Sendable {}"), | ||
Example("@MyActor enum ↓E: Sendable { case a }", configuration: ["global_actors": ["MyActor"]]), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I think I get it, the actor has to be defined first? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the rule only operates on syntax-level, it needs to have global actors defined by name. There's no other way to know if the attribute is an actor or something else. |
||
], | ||
corrections: [ | ||
Example("@MainActor struct S: Sendable {}"): | ||
Example("@MainActor struct S {}"), | ||
Example("actor A: Sendable {}"): | ||
Example("actor A {}"), | ||
Example("@MyActor enum E: Sendable { case a }", configuration: ["global_actors": ["MyActor"]]): | ||
Example("@MyActor enum E { case a }"), | ||
Example("actor A: B, Sendable, C {}"): | ||
Example("actor A: B, C {}"), | ||
] | ||
) | ||
} | ||
|
||
private extension RedundantSendableRule { | ||
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> { | ||
override func visitPost(_ node: ActorDeclSyntax) { | ||
if node.conformsToSendable { | ||
violations.append(at: node.name.positionAfterSkippingLeadingTrivia) | ||
} | ||
} | ||
|
||
override func visitPost(_ node: ClassDeclSyntax) { | ||
collectViolations(in: node) | ||
} | ||
|
||
override func visitPost(_ node: EnumDeclSyntax) { | ||
collectViolations(in: node) | ||
} | ||
|
||
override func visitPost(_ node: ProtocolDeclSyntax) { | ||
collectViolations(in: node) | ||
} | ||
|
||
override func visitPost(_ node: StructDeclSyntax) { | ||
collectViolations(in: node) | ||
} | ||
|
||
private func collectViolations(in decl: some DeclGroupSyntax & NamedDeclSyntax) { | ||
if decl.conformsToSendable, decl.isIsolatedToActor(actors: configuration.globalActors) { | ||
violations.append(at: decl.name.positionAfterSkippingLeadingTrivia) | ||
} | ||
} | ||
} | ||
|
||
final class Rewriter: ViolationsSyntaxRewriter<ConfigurationType> { | ||
override func visit(_ node: ActorDeclSyntax) -> DeclSyntax { | ||
if node.conformsToSendable { | ||
correctionPositions.append(node.name.positionAfterSkippingLeadingTrivia) | ||
return super.visit(node.withoutSendable) | ||
} | ||
return super.visit(node) | ||
} | ||
|
||
override func visit(_ node: ClassDeclSyntax) -> DeclSyntax { | ||
super.visit(removeRedundantSendable(from: node)) | ||
} | ||
|
||
override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { | ||
super.visit(removeRedundantSendable(from: node)) | ||
} | ||
|
||
override func visit(_ node: ProtocolDeclSyntax) -> DeclSyntax { | ||
super.visit(removeRedundantSendable(from: node)) | ||
} | ||
|
||
override func visit(_ node: StructDeclSyntax) -> DeclSyntax { | ||
super.visit(removeRedundantSendable(from: node)) | ||
} | ||
|
||
private func removeRedundantSendable<T: DeclGroupSyntax & NamedDeclSyntax>(from decl: T) -> T { | ||
if decl.conformsToSendable, decl.isIsolatedToActor(actors: configuration.globalActors) { | ||
correctionPositions.append(decl.name.positionAfterSkippingLeadingTrivia) | ||
return decl.withoutSendable | ||
} | ||
return decl | ||
} | ||
} | ||
} | ||
|
||
private extension DeclGroupSyntax where Self: NamedDeclSyntax { | ||
var conformsToSendable: Bool { | ||
inheritanceClause?.inheritedTypes.contains(where: \.isSendable) == true | ||
} | ||
|
||
func isIsolatedToActor(actors: Set<String>) -> Bool { | ||
attributes.contains(attributeNamed: "MainActor") || actors.contains { attributes.contains(attributeNamed: $0) } | ||
} | ||
|
||
var withoutSendable: Self { | ||
guard let inheritanceClause else { | ||
return self | ||
} | ||
let inheritedTypes = inheritanceClause.inheritedTypes.filter { !$0.isSendable } | ||
if inheritedTypes.isEmpty { | ||
return with(\.inheritanceClause, nil) | ||
.with(\.name.trailingTrivia, inheritanceClause.leadingTrivia + inheritanceClause.trailingTrivia) | ||
} | ||
return with(\.inheritanceClause, inheritanceClause | ||
.with(\.inheritedTypes, inheritedTypes)) | ||
} | ||
} | ||
|
||
private extension InheritedTypeSyntax { | ||
var isSendable: Bool { | ||
type.as(IdentifierTypeSyntax.self)?.name.text == "Sendable" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import SwiftLintCore | ||
|
||
@AutoConfigParser | ||
struct RedundantSendableConfiguration: SeverityBasedRuleConfiguration { | ||
typealias Parent = RedundantSendableRule | ||
|
||
@ConfigurationElement(key: "severity") | ||
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning) | ||
@ConfigurationElement(key: "global_actors") | ||
private(set) var globalActors = Set<String>() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why should this last one not trigger?