jimmya / sourcery-templates

MIT License
7 stars 6 forks source link

Looking for Suggestions on Fully Supporting Swift 6’s Sendable #28

Open iDylanK opened 1 month ago

iDylanK commented 1 month ago

I’m exploring ways to fully support Swift 6’s Sendable using a @UseCase attribute. Currently, property wrapper are mutable and, therefore, can't conform to Sendable.

To overcome this, I’ve started looking into the use of custom macros. One idea is to create a macro that would expand:

@UseCase private var getHomeUseCase: GetHomeUseCaseProtocol

to

private var getHomeUseCase: GetHomeUseCaseProtocol {
    get {
        UseCaseContainer.shared.getHomeUseCase().resolve()
    }
}

Using a Swift template, I could generate the corresponding macro package. The template would look something like this:

<%- includeFile("AutoInjectable.swift") -%>

<% let containers = AutoInjectable.getContainers(types: types) -%>

// sourcery:file:Package.swift

<%- include("AutoInjectablePackage.swift") %>

// sourcery:end

// sourcery:file:Sources/Injected/Injected.swift

<%= AutoInjectable.generateExternalMacros(from: containers) -%>

// sourcery:end

// sourcery:file:Sources/InjectedMacros/InjectedMacros.swift

// swiftlint:disable all

<%= AutoInjectable.generateMacros(from: containers) -%>
// swiftlint:enable all

// sourcery:end

The generated macros will look like:

public struct UseCaseMacro: AccessorMacro {
    public static func expansion(
        of node: SwiftSyntax.AttributeSyntax,
        providingAccessorsOf declaration: some SwiftSyntax.DeclSyntaxProtocol,
        in context: some SwiftSyntaxMacros.MacroExpansionContext
    ) throws -> [SwiftSyntax.AccessorDeclSyntax] {
        guard
            let variableDecl = declaration.as(VariableDeclSyntax.self),
            let name = variableDecl.bindings.first?.pattern.description.trimmingCharacters(in: .whitespacesAndNewlines)
        else { return [] }

        return [
            """
            get {
                UseCaseContainer.shared.\(raw: name).resolve()
            }
            """
        ]
    }
}

Downsides:

Do you have any suggestions or ideas to improve this approach? Thanks!

jimmya commented 1 month ago

Are you using the latest version of main? https://github.com/jimmya/sourcery-templates/commit/c053e11888c62781023c5e9e894e777fc7708dc2 I think this should fix the issue.

It's marked as @unchecked Sendable since Factory is thread safe by itself so this should be fine.

iDylanK commented 1 month ago

Yes I am.

The problem is with property wrappers. So I'am afraid Factory won't be able to fix it too: https://github.com/hmlongco/Factory/issues/168

He mentions there is no easy fix, but I'm afraid a fix isn't possible.

image

There are multiple threads on swift forums about this issue as well:

https://forums.swift.org/t/static-property-wrappers-and-strict-concurrency-in-5-10/70116/26

At the moment, the only solution appears to be a Macro.

jimmya commented 1 month ago

Looks like the fix I mentioned above only applies when the property wrapper is used in a struct.

I think we should note this down as a current limitation of swift and not try to work around it within the templates. Or wait until the Factory library has a InjectedSendable property wrapper (if even possible). Or until the property wrapper let proposal is implemented and merged.

Until that time I'd suggest to go with the manual computed property approach. There is some syntactic sugar to make it a bit easier to work with:

private var getHomeUseCase: GetHomeUseCaseProtocol {
    UseCase.make(\. getHomeUseCase)
}

Otherwise try any of the suggestions in this thread: https://github.com/pointfreeco/swift-dependencies/discussions/204#discussioncomment-9054649

Personally I'd make the thing you're using the property wrapper in a struct if possible. Or mark your class @unchecked Sendable. As mentioned above, Factory internals are threadsafe.

iDylanK commented 1 month ago

Okay, we will use your example and hope the proposal gets implemented and merged. Thanks! 🙏🏻