typealiased/mockingbird

Generated mocks "Unable to infer complex closure return type"

daniel-beard opened this issue · 4 comments

New Issue Checklist

Overview

Generated mocks are not able to be compiled in some cases:

Unable to infer complex closure return type; add explicit type to disambiguate

Screen Shot 2022-01-28 at 9 30 32 AM

The method that is being mocked here is removeOverrides() which is a public func on a public class, that conforms to a protocol (of which removeOverrides() is a member)

Example

public protocol SomeProtocol {
  func removeOverrides()
}

public class MyClass: SomeProtocol {
    public func removeOverrides() {
        UserDefaults.standard.removeObject(forKey: overridesKey)
     }
}

A minimal example of the code being tested along with the test case.

Expected Behavior

Should be able to compile the generated mocks

Environment

  • Mockingbird CLI version (mockingbird version)
    0.19.2
  • Xcode and Swift version (swift --version)
    swift-driver version: 1.26.21 Apple Swift version 5.5.2 (swiftlang-1300.0.47.5 clang-1300.0.29.30)
  • Package manager (CocoaPods, Carthage, SPM project, SPM package)
    SwiftPM
  • Unit testing framework (XCTest, Quick/Nimble)
    XCTest

Thanks for reporting, Daniel. I’m not able to repro this with the provided example, but I noticed that the generated code in your screenshot looks different from the current output of 0.19.2:

image

Could you provide additional context or the full output, assuming that the generated code was modified by hand?

The example was simplified as I'm not able to provide the original code. The output was not modified, here's the full generated file:

DevModeMocks.generated.swift
//
//  DevModeMocks.generated.swift
//  DevMode
//
//  Generated by Mockingbird v0.19.2.
//  DO NOT EDIT
//

import Combine
@testable import DevMode
import Foundation
@testable import Mockingbird
import Swift
import SwiftUI

private let genericStaticMockContext = Mockingbird.GenericStaticMockContext()

// MARK: - Mocked ExperimentsProviding

public final class ExperimentsProvidingMock: DevMode.ExperimentsProviding, Mockingbird.Mock {
    typealias MockingbirdSupertype = DevMode.ExperimentsProviding
    static let staticMock = Mockingbird.StaticMock()
    public let mockingbirdContext = Mockingbird.Context(["generator_version": "0.19.2", "module_name": "DevMode"])

    fileprivate init(sourceLocation: Mockingbird.SourceLocation) {
        self.mockingbirdContext.sourceLocation = sourceLocation
        ExperimentsProvidingMock.staticMock.mockingbirdContext.sourceLocation = sourceLocation
    }

    // MARK: Mocked `catfoodOverride`(`experimentID`: String)

    public func catfoodOverride(experimentID: String) -> DevMode.ExperimentCatfoodData {
        self.mockingbirdContext.mocking.didInvoke(Mockingbird.SwiftInvocation(selectorName: "`catfoodOverride`(`experimentID`: String) -> DevMode.ExperimentCatfoodData", selectorType: Mockingbird.SelectorType.method, arguments: [Mockingbird.ArgumentMatcher(experimentID)], returnType: Swift.ObjectIdentifier((DevMode.ExperimentCatfoodData).self))) {
            self.mockingbirdContext.recordInvocation($0)
            let mkbImpl = self.mockingbirdContext.stubbing.implementation(for: $0)
            if let mkbImpl = mkbImpl as? (String) -> DevMode.ExperimentCatfoodData { return mkbImpl(experimentID) }
            if let mkbImpl = mkbImpl as? () -> DevMode.ExperimentCatfoodData { return mkbImpl() }
            for mkbTargetBox in self.mockingbirdContext.proxy.targets(for: $0) {
                switch mkbTargetBox.target {
                case .super:
                    break
                case let .object(mkbObject):
                    guard var mkbObject = mkbObject as? MockingbirdSupertype else { break }
                    let mkbValue: DevMode.ExperimentCatfoodData = mkbObject.catfoodOverride(experimentID: experimentID)
                    self.mockingbirdContext.proxy.updateTarget(&mkbObject, in: mkbTargetBox)
                    return mkbValue
                }
            }
            if let mkbValue = self.mockingbirdContext.stubbing.defaultValueProvider.value.provideValue(for: (DevMode.ExperimentCatfoodData).self) { return mkbValue }
            self.mockingbirdContext.stubbing.failTest(for: $0, at: self.mockingbirdContext.sourceLocation)
        }
    }

    public func catfoodOverride(experimentID: @autoclosure () -> String) -> Mockingbird.Mockable<Mockingbird.FunctionDeclaration, (String) -> DevMode.ExperimentCatfoodData, DevMode.ExperimentCatfoodData> {
        Mockingbird.Mockable<Mockingbird.FunctionDeclaration, (String) -> DevMode.ExperimentCatfoodData, DevMode.ExperimentCatfoodData>(mock: self, invocation: Mockingbird.SwiftInvocation(selectorName: "`catfoodOverride`(`experimentID`: String) -> DevMode.ExperimentCatfoodData", selectorType: Mockingbird.SelectorType.method, arguments: [Mockingbird.resolve(experimentID)], returnType: Swift.ObjectIdentifier((DevMode.ExperimentCatfoodData).self)))
    }

    // MARK: Mocked `removeOverrides`()

    public func removeOverrides() {
        self.mockingbirdContext.mocking.didInvoke(Mockingbird.SwiftInvocation(selectorName: "`removeOverrides`() -> Void", selectorType: Mockingbird.SelectorType.method, arguments: [], returnType: Swift.ObjectIdentifier(Void.self))) {
            self.mockingbirdContext.recordInvocation($0)
            let mkbImpl = self.mockingbirdContext.stubbing.implementation(for: $0)
            if let mkbImpl = mkbImpl as? () -> Void { return mkbImpl() }
            for mkbTargetBox in self.mockingbirdContext.proxy.targets(for: $0) {
                switch mkbTargetBox.target {
                case .super:
                    break
                case let .object(mkbObject):
                    guard var mkbObject = mkbObject as? MockingbirdSupertype else { break }
                    let mkbValue: Void = mkbObject.removeOverrides()
                    self.mockingbirdContext.proxy.updateTarget(&mkbObject, in: mkbTargetBox)
                    return mkbValue
                }
            }
            if let mkbValue = self.mockingbirdContext.stubbing.defaultValueProvider.value.provideValue(for: Void.self) { return mkbValue }
            self.mockingbirdContext.stubbing.failTest(for: $0, at: self.mockingbirdContext.sourceLocation)
        }
    }

    public func removeOverrides() -> Mockingbird.Mockable<Mockingbird.FunctionDeclaration, () -> Void, Void> {
        Mockingbird.Mockable<Mockingbird.FunctionDeclaration, () -> Void, Void>(mock: self, invocation: Mockingbird.SwiftInvocation(selectorName: "`removeOverrides`() -> Void", selectorType: Mockingbird.SelectorType.method, arguments: [], returnType: Swift.ObjectIdentifier(Void.self)))
    }

    // MARK: Mocked `save`(`experiments`: [DevMode.Experiment])

    public func save(experiments: [DevMode.Experiment]) {
        self.mockingbirdContext.mocking.didInvoke(Mockingbird.SwiftInvocation(selectorName: "`save`(`experiments`: [DevMode.Experiment]) -> Void", selectorType: Mockingbird.SelectorType.method, arguments: [Mockingbird.ArgumentMatcher(experiments)], returnType: Swift.ObjectIdentifier(Void.self))) {
            self.mockingbirdContext.recordInvocation($0)
            let mkbImpl = self.mockingbirdContext.stubbing.implementation(for: $0)
            if let mkbImpl = mkbImpl as? ([DevMode.Experiment]) -> Void { return mkbImpl(experiments) }
            if let mkbImpl = mkbImpl as? () -> Void { return mkbImpl() }
            for mkbTargetBox in self.mockingbirdContext.proxy.targets(for: $0) {
                switch mkbTargetBox.target {
                case .super:
                    break
                case let .object(mkbObject):
                    guard var mkbObject = mkbObject as? MockingbirdSupertype else { break }
                    let mkbValue: Void = mkbObject.save(experiments: experiments)
                    self.mockingbirdContext.proxy.updateTarget(&mkbObject, in: mkbTargetBox)
                    return mkbValue
                }
            }
            if let mkbValue = self.mockingbirdContext.stubbing.defaultValueProvider.value.provideValue(for: Void.self) { return mkbValue }
            self.mockingbirdContext.stubbing.failTest(for: $0, at: self.mockingbirdContext.sourceLocation)
        }
    }

    public func save(experiments: @autoclosure () -> [DevMode.Experiment]) -> Mockingbird.Mockable<Mockingbird.FunctionDeclaration, ([DevMode.Experiment]) -> Void, Void> {
        Mockingbird.Mockable<Mockingbird.FunctionDeclaration, ([DevMode.Experiment]) -> Void, Void>(mock: self, invocation: Mockingbird.SwiftInvocation(selectorName: "`save`(`experiments`: [DevMode.Experiment]) -> Void", selectorType: Mockingbird.SelectorType.method, arguments: [Mockingbird.resolve(experiments)], returnType: Swift.ObjectIdentifier(Void.self)))
    }

    // MARK: Mocked `fetchExperiments`()

    public func fetchExperiments() -> [DevMode.Experiment] {
        self.mockingbirdContext.mocking.didInvoke(Mockingbird.SwiftInvocation(selectorName: "`fetchExperiments`() -> [DevMode.Experiment]", selectorType: Mockingbird.SelectorType.method, arguments: [], returnType: Swift.ObjectIdentifier([DevMode.Experiment].self))) {
            self.mockingbirdContext.recordInvocation($0)
            let mkbImpl = self.mockingbirdContext.stubbing.implementation(for: $0)
            if let mkbImpl = mkbImpl as? () -> [DevMode.Experiment] { return mkbImpl() }
            for mkbTargetBox in self.mockingbirdContext.proxy.targets(for: $0) {
                switch mkbTargetBox.target {
                case .super:
                    break
                case let .object(mkbObject):
                    guard var mkbObject = mkbObject as? MockingbirdSupertype else { break }
                    let mkbValue: [DevMode.Experiment] = mkbObject.fetchExperiments()
                    self.mockingbirdContext.proxy.updateTarget(&mkbObject, in: mkbTargetBox)
                    return mkbValue
                }
            }
            if let mkbValue = self.mockingbirdContext.stubbing.defaultValueProvider.value.provideValue(for: [DevMode.Experiment].self) { return mkbValue }
            self.mockingbirdContext.stubbing.failTest(for: $0, at: self.mockingbirdContext.sourceLocation)
        }
    }

    public func fetchExperiments() -> Mockingbird.Mockable<Mockingbird.FunctionDeclaration, () -> [DevMode.Experiment], [DevMode.Experiment]> {
        Mockingbird.Mockable<Mockingbird.FunctionDeclaration, () -> [DevMode.Experiment], [DevMode.Experiment]>(mock: self, invocation: Mockingbird.SwiftInvocation(selectorName: "`fetchExperiments`() -> [DevMode.Experiment]", selectorType: Mockingbird.SelectorType.method, arguments: [], returnType: Swift.ObjectIdentifier([DevMode.Experiment].self)))
    }
}

/// Returns a concrete mock of `ExperimentsProviding`.
public func mock(_ type: DevMode.ExperimentsProviding.Protocol, file: StaticString = #file, line: UInt = #line) -> ExperimentsProvidingMock {
    ExperimentsProvidingMock(sourceLocation: Mockingbird.SourceLocation(file, line))
}

Thanks for the full file. Based on the output it looks like a code formatter is being run on the output, maybe SwiftLint or swift-format. If it’s SwiftLint, you can specify --disable-swiftlint as a generator flag to disable SwiftLint rules on mock files. If it’s swift-format, you should configure your project to not run the tool against *.generated.swift files.

Thanks, it was swift format. You can consider this resolved.