-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Use Case
I'm trying to use @MockedMembers to generate mocks for protocols that: - Conform to Sendable - Have methods that return or accept Any or [String: Any] When @MockedMembers generates the mock, it creates MockReturningNonParameterizedMethod<[String: Any]>, which fails to compile because Any doesn't conform to Sendable.
Feature Proposal
I would like to see @MockedMembers be able to convert Any types to any Sendable when:
- The mock class conforms to
@unchecked Sendable - The protocol method uses
Any,[Any], or[String: Any]
Example Protocol
public protocol UserDefaultsProtocol: Sendable {
func dictionaryRepresentation() -> [String: Any]
}Current Behavior (Test code Fails to Compile not the mock itself)
Generated mock code:
@MockedMembers
public final class UserDefaultsMock: UserDefaultsProtocol, @unchecked Sendable {
func dictionaryRepresentation() -> [String: Any]
}
What @MockedMembers generates internally:
private let __dictionaryRepresentation = MockReturningNonParameterizedMethod<
[String: Any] // ❌ Problem: Any is not Sendable
>.makeMethod(
exposedMethodDescription: MockImplementationDescription(
type: UserDefaultsMock.self,
member: "_dictionaryRepresentation"
)
)
Public interface generated:
public var _dictionaryRepresentation: MockReturningNonParameterizedMethod<
[String: Any]
> {
self.__dictionaryRepresentation.method
}
public func dictionaryRepresentation() -> [String: Any] {
let returnValue = self.__dictionaryRepresentation.invoke()
return returnValue
}
Test code that fails:
let userDefaults = UserDefaultsMock()
let testDefaults: [String: Sendable] = [:]
userDefaults._dictionaryRepresentation.implementation = .returns(testDefaults)
// ❌ Error: Type 'Any' does not conform to the 'Sendable' protocol
Desired Behavior
Mock declaration (same as current):
@MockedMembers
public final class UserDefaultsMock: UserDefaultsProtocol, @unchecked Sendable {
}
What @MockedMembers should generate internally:
private let __dictionaryRepresentation = MockReturningNonParameterizedMethod<
[String: any Sendable] // ✅ Automatically replaced Any with any Sendable
>.makeMethod(
exposedMethodDescription: MockImplementationDescription(
type: UserDefaultsMock.self,
member: "_dictionaryRepresentation"
)
)
Public interface with Sendable type:
public var _dictionaryRepresentation: MockReturningNonParameterizedMethod<
[String: any Sendable] // ✅ Uses any Sendable
> {
self.__dictionaryRepresentation.method
}
Protocol implementation with cast:
public func dictionaryRepresentation() -> [String: Any] {
let returnValue = self.__dictionaryRepresentation.invoke()
return returnValue as [String: Any] // ✅ Cast back to protocol signature
}
Test code that works:
let userDefaults = UserDefaultsMock()
let testDefaults: [String: Sendable] = ["key": "value"]
userDefaults._dictionaryRepresentation.implementation = .returns(testDefaults)
// ✅ Compiles successfullyProposed Solutions
Option 1: Automatic Detection (Recommended)
Automatically convert Any → any Sendable when:
- The mock class has @unchecked Sendable conformance
- A protocol method uses Any, [Any], or [String: Any]
@MockedMembers // No configuration needed
public final class UserDefaultsMock: UserDefaultsProtocol, @unchecked Sendable {
// Automatically generates Sendable-safe mock code
}Pros:
- No additional configuration
- Works automatically for the common case
- Clear convention: @unchecked Sendable signals the macro to use any Sendable
Cons:
- Less explicit control
Option 2: Macro Parameter
Add explicit control via macro parameter:
This would most likely be bubbled up to the top @Mocked macro.
@MockedMembers(convertAnyToSendable: true)
public final class UserDefaultsMock: UserDefaultsProtocol, @unchecked Sendable {
// Explicitly requests Any → any Sendable conversion
}Pros:
- Explicit opt-in
- Clear intent in code
- Could default to false for backward compatibility
Cons:
- Extra boilerplate
- Easy to forget
Alternatives Considered
I've tried:
- Manually implementing the entire mock - This defeats the purpose of @MockedMembers
- Adding @preconcurrency to protocol methods - Doesn't work with generated mock code
- Current workaround - Manually implementing just the problematic method:
This requires manually expanding the entire macro, which is tedious and error-prone.
Additional Context
No response
Code of Conduct
- I agree to follow this project's Code of Conduct