SwiftLint
SwiftLint copied to clipboard
Rule to check for Swift 5-mode-incompatible API
New Issue Checklist
- [x] I've Updated SwiftLint to the latest version.
- [x] I've searched for existing GitHub issues.
Feature or Enhancement Proposal
There are a number of ways to break compatibility with Swift 5 mode (strict concurrency off) that are difficult to notice. Here are some examples:
@MainActor
public class MainThreadOnly {}
public func takeBlock(_ block: @Sendable () - Void)
public func takeGlobalBlock(_ block: @MainActor () - Void)
In all of these cases, to preserve compatibility you must use @preconcurrency.
@preconcurrency @MainActor
public class MainThreadOnly {}
@preconcurrency
public func takeBlock(_ block: @Sendable () - Void)
@preconcurrency
public func takeGlobalBlock(_ block: @MainActor () - Void)
I think this could be a great new rule!
Ok I'm trying to come up with examples. You have to catch public API that uses global actors, @Sendable closures, Sendable generic constraints, and the use of sending parameters (sending returns are not a problem).
@preconcurrency can address all of these, except for sending parameters for which there is no mechanism to preserve source compatibility.
non-triggering:
Example("public struct S: Sendable {}"),
Example("public class C: Sendable {}"),
Example("public actor A {}"),
Example("public @MyActor enum E { case a }"),
Example("private @MainActtor struct S { }"),
Example("@MainActtor struct S { }"),
triggering:
Example("↓@MainActor public struct S {}"),
Example("↓@MainActor public func globalActor()"),
Example("public func sendableClosure(_ block: ↓@Sendable () -> Void)"),
Example("public func globalActorClosure(_ block: ↓@MainActor () -> Void)"),
Example(
"public func globalActorClosure(_ block: ↓@MyActor () -> Void)",
configuration: ["global_actors": ["MyActor"]]
),
Example("public func sendingParameter(p: sending NonSendable)"),
Example("↓@MainActor public protocol GlobalActor {}"),
Example("public struct S { public func sendableClosure(_ block: ↓@Sendable () -> Void) }"),
Example("↓@MyActor public struct S {}", configuration: ["global_actors": ["MyActor"]]),
Example("public func generic<T> where T: ↓Sendable")
Thank you for the idea a the examples! What would be a good name for the new rule? Probably the hardest part ... 😅
I started playing around with an implementation, and I used "IncompatibleConcurrencyAnnotationRule". But I rapidly realized that I don't have enough Swift syntax knowledge.
What do you think of that one?
With "incompatible" meaning "not back-deployable"? But that's probably too long and a too weird combination of words. I think "IncompatibleConcurrencyAnnotationRule" is good with an explanation of what it means in the description.
But I rapidly realized that I don't have enough Swift syntax knowledge.
It's a hurdle to get used to it. But with some experience it's fun to work with it.
Do you need support with the implementation, @mattmassicotte?
Thanks for the offer! I spent about an hour playing around to try to get a feel for the system. I kind of ran out of time. But I'm still very interested!
Thanks for the offer! I spent about an hour playing around to try to get a feel for the system. I kind of ran out of time. But I'm still very interested!
Cool! So please go ahead and let me know if you need help with anything.
Hey @mattmassicotte, I gave an implementation a try in #6290. I'd appreciate your review. Especially the rational might miss an important aspect or could be more extensive, and there might be even more examples we'd need to cover.