Sourcery
Sourcery copied to clipboard
Unstable AutoMockable output when protocols have associated type
Hey 👋, thank you for this fantastic tool! Sourcery has significantly improved our workflow.
Issue
I've encountered an issue that first appeared in Sourcery version 2.2.5. The problem occurs during mock generation with AutoMockable
for protocols that contain associatedtype
. The generated mocks are inconsistent — sometimes the mock is generated correctly, and other times it isn’t generated at all.
Below, you can found a small code example that illustrates this behavior.
protocol ProtocolWithAssociatedType {
associatedtype Value: AnotherProtocol
var value: Value { get }
}
// sourcery:AutoMockable
protocol AnotherProtocol {
func foo() -> String
}
Sometimes, the mock is generated correctly:
class AnotherProtocolMock: AnotherProtocol {
//MARK: - foo
var fooStringCallsCount = 0
var fooStringCalled: Bool {
return fooStringCallsCount > 0
}
var fooStringReturnValue: String!
var fooStringClosure: (() -> String)?
func foo() -> String {
fooStringCallsCount += 1
if let fooStringClosure = fooStringClosure {
return fooStringClosure()
} else {
return fooStringReturnValue
}
}
}
but in about 50% of runs (without any changes to the code) mock is not generated.
Investigation
This issue appeared after commit 7153768c655c5e2435323cd08f01ed8b2d13f765, specifically due to changes in ParserResultsComposed.swift
/// Map associated types
associatedTypes.forEach {
typeMap[$0.key] = $0.value.type
}
These lines insert associated types to typeMap
in a random order, as the order in Dictionary
is not guaranteed.
And it seems likely that this code conflicts with unifyTypes
function, which removes types with duplicate globalName
from the typeMap
.
If the original type is listed before the associated type in the typeMap
, the generation works correctly:
[
"AnotherProtocol": AnotherProtocol.self, // type with sourcery annotation
"ProtocolWithAssociatedType": ProtocolWithAssociatedType.self,
"Value": AnotherProtocol.self // associated type
]
However, if the associated type is listed before the original type, the original type gets removed from the typeMap
, and the mock is not generated.
[
"ProtocolWithAssociatedType": ProtocolWithAssociatedType.self,
"Value": AnotherProtocol.self, // associated type
"AnotherProtocol": AnotherProtocol.self // type with sourcery annotation
]