Skip to content
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

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

Open
iDylanK opened this issue Oct 6, 2024 · 4 comments
Open

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

iDylanK opened this issue Oct 6, 2024 · 4 comments

Comments

@iDylanK
Copy link
Contributor

iDylanK commented Oct 6, 2024

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:

  • No Automatic Type Inference: The type of the variable isn't automatically inferred, meaning it must be explicitly annotated.
  • Variable Naming Convention: The variable name must match the name used in the container, or potentially, the inherited protocol could be used instead.
  • Importing the Injected Package: The Injected package has to be imported every time the macro is used. Alternatively, @_exported import could be used to simplify this.

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

@jimmya
Copy link
Owner

jimmya commented Oct 7, 2024

Are you using the latest version of main? c053e11 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
Copy link
Contributor Author

iDylanK commented Oct 7, 2024

Yes I am.

The problem is with property wrappers. So I'am afraid Factory won't be able to fix it too:
hmlongco/Factory#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
Copy link
Owner

jimmya commented Oct 7, 2024

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: pointfreeco/swift-dependencies#204 (reply in thread)

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
Copy link
Contributor Author

iDylanK commented Oct 7, 2024

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

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

No branches or pull requests

2 participants