Kolos65 / Mockable

A Swift macro driven auto-mocking library.
MIT License
199 stars 14 forks source link

Getting "Invalid redeclaration" despite having different functions in protocol #13

Closed WedgeSparda closed 4 months ago

WedgeSparda commented 5 months ago

Hi, I have this protocol with two methods with same name and params but different output and one of them is async

@Mockable
public protocol SendSignInCredentialsUseCase {
    func execute(
        signInCredentials: SignInCredentials,
        country: String?,
        signInMethod: AuthenticationMethod
    ) -> AnyPublisher<Session, SignInError>

    func execute(
        signInCredentials: SignInCredentials,
        country: String?,
        signInMethod: AuthenticationMethod
    ) async -> Result<Session, SignInError>
}

But macro is generating duplicated code at this point

public struct ActionBuilder: EffectBuilder {
        private let mocker: Mocker<MockSendSignInCredentialsUseCase>
        public init(mocker: Mocker<MockSendSignInCredentialsUseCase>) {
            self.mocker = mocker
        }
        public func execute(
                signInCredentials: Parameter<SignInCredentials>,
                country: Parameter<String?>,
                signInMethod: Parameter<AuthenticationMethod>) -> FunctionActionBuilder<MockSendSignInCredentialsUseCase, ActionBuilder> {
            .init(mocker, kind: .m2_execute(signInCredentials:
                    signInCredentials, country:
                    country, signInMethod:
                    signInMethod))
        }
        // Invalid redeclaration of 'execute(signInCredentials:country:signInMethod:)'
        public func execute(
                signInCredentials: Parameter<SignInCredentials>,
                country: Parameter<String?>,
                signInMethod: Parameter<AuthenticationMethod>) -> FunctionActionBuilder<MockSendSignInCredentialsUseCase, ActionBuilder> {
            .init(mocker, kind: .m3_execute(signInCredentials:
                    signInCredentials, country:
                    country, signInMethod:
                    signInMethod))
        }
    }
    public struct VerifyBuilder: AssertionBuilder {
        private let mocker: Mocker<MockSendSignInCredentialsUseCase>
        private let assertion: MockableAssertion
        public init(mocker: Mocker<MockSendSignInCredentialsUseCase>, assertion: @escaping MockableAssertion) {
            self.mocker = mocker
            self.assertion = assertion
        }
        public func execute(
                signInCredentials: Parameter<SignInCredentials>,
                country: Parameter<String?>,
                signInMethod: Parameter<AuthenticationMethod>) -> FunctionVerifyBuilder<MockSendSignInCredentialsUseCase, VerifyBuilder> {
            .init(mocker, kind: .m2_execute(signInCredentials:
                    signInCredentials, country:
                    country, signInMethod:
                    signInMethod), assertion: assertion)
        }
        // Invalid redeclaration of 'execute(signInCredentials:country:signInMethod:)'
        public func execute(
                signInCredentials: Parameter<SignInCredentials>,
                country: Parameter<String?>,
                signInMethod: Parameter<AuthenticationMethod>) -> FunctionVerifyBuilder<MockSendSignInCredentialsUseCase, VerifyBuilder> {
            .init(mocker, kind: .m3_execute(signInCredentials:
                    signInCredentials, country:
                    country, signInMethod:
                    signInMethod), assertion: assertion)
        }
    }

Is this something that can be avoid?

Thanks

Kolos65 commented 5 months ago

Thanks for the issue! Adding the return type as a generic parameter to every FunctionBuilder could potentially solve this issue. I will give it a try...

WedgeSparda commented 5 months ago

I was able to fix the issue, here's the PR https://github.com/Kolos65/Mockable/pull/14

Kolos65 commented 5 months ago

I have looked at this and remembered why I purposefully dropped support for this edge case:

If you have a protocol with a function that uses generic parameters in both its parameter and return clause:

@Mockable
protocol Service {
    func genericFunc<T, V>(item: T) -> V
}

You cannot use the perform and verify clauses when testing as the generic return parameter cannot be inferred there:

func test() {
        given(mock)
            .genericFunc(item: Parameter<Int>.any)
            .willReturn(3)
        when(mock)
            .genericFunc(item: Parameter<Int>.any) // Generic parameter 'V' could not be inferred
            .perform {
                print("called")
            }
        verify(mock)
            .genericFunc(item: Parameter<Int>.any) // Generic parameter 'V' could not be inferred
            .called(count: .atLeastOnce)
}

This is exactly why I removed the return type from the generics of Action and Verify builders back then.

Kolos65 commented 5 months ago

Its valid though that your use case is more common than requirements like func genericFunc<T, V>(item: T) -> V...