SwiftLint icon indicating copy to clipboard operation
SwiftLint copied to clipboard

Rule to check for Swift 5-mode-incompatible API

Open mattmassicotte opened this issue 9 months ago • 7 comments

New Issue Checklist

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!

mattmassicotte avatar Feb 01 '25 11:02 mattmassicotte

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")

mattmassicotte avatar Feb 01 '25 12:02 mattmassicotte

Thank you for the idea a the examples! What would be a good name for the new rule? Probably the hardest part ... 😅

SimplyDanny avatar Feb 01 '25 15:02 SimplyDanny

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?

mattmassicotte avatar Feb 01 '25 15:02 mattmassicotte

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.

SimplyDanny avatar Feb 01 '25 15:02 SimplyDanny

Do you need support with the implementation, @mattmassicotte?

SimplyDanny avatar Feb 15 '25 16:02 SimplyDanny

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!

mattmassicotte avatar Feb 17 '25 11:02 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!

Cool! So please go ahead and let me know if you need help with anything.

SimplyDanny avatar Feb 17 '25 13:02 SimplyDanny

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.

SimplyDanny avatar Oct 10 '25 21:10 SimplyDanny