diff --git a/Sources/MockingClient/MethodOverloads.swift b/Sources/MockingClient/MethodOverloads.swift index 9e0cf46b..88578a46 100644 --- a/Sources/MockingClient/MethodOverloads.swift +++ b/Sources/MockingClient/MethodOverloads.swift @@ -4,6 +4,8 @@ // Copyright © 2025 Fetch. // +// swiftformat:disable opaqueGenericParameters + import Foundation public import Mocking diff --git a/Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift b/Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift index 582cf567..5ec35fba 100644 --- a/Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift +++ b/Sources/MockingMacros/Macros/MockedMacro/MockedMacro.swift @@ -120,13 +120,16 @@ extension MockedMacro { /// Returns the generic parameter clause to apply to the mock declaration, /// generated from the associated types defined by the provided protocol. /// + /// The clause supports associated types with comma-separated constraints, + /// composition (`&`), or a combination of both. + /// /// ```swift /// @Mocked /// protocol Dependency { - /// associatedtype Item: Equatable, Identifiable + /// associatedtype Item: Equatable & Identifiable, Sendable /// } /// - /// final class DependencyMock: Dependency {} + /// final class DependencyMock: Dependency {} /// ``` /// /// - Parameter protocolDeclaration: The protocol to which the mock must @@ -148,16 +151,30 @@ extension MockedMacro { return GenericParameterClauseSyntax { for associatedTypeDeclaration in associatedTypeDeclarations { let genericParameterName = associatedTypeDeclaration.name.trimmed - let genericInheritedType = associatedTypeDeclaration.inheritanceClause? - .inheritedTypes(ofType: IdentifierTypeSyntax.self) - .map(\.name.text) - .joined(separator: " & ") - if let genericInheritedType { + if let inheritanceClause = associatedTypeDeclaration.inheritanceClause { + let commaSeparatedInheritedTypes = inheritanceClause + .inheritedTypes(ofType: IdentifierTypeSyntax.self) + .compactMap { CompositionTypeElementSyntax(type: $0) } + + let composedInheritedTypes = inheritanceClause + .inheritedTypes(ofType: CompositionTypeSyntax.self) + .flatMap(\.elements) + + let inheritedTypes = commaSeparatedInheritedTypes + composedInheritedTypes + let lastIndex = inheritedTypes.count - 1 + let inheritedTypeElements = CompositionTypeElementListSyntax { + for (index, inheritedType) in inheritedTypes.enumerated() { + inheritedType + .trimmed + .with(\.ampersand, index < lastIndex ? .binaryOperator("&") : nil) + } + } + GenericParameterSyntax( name: genericParameterName, colon: .colonToken(), - inheritedType: TypeSyntax(stringLiteral: genericInheritedType) + inheritedType: CompositionTypeSyntax(elements: inheritedTypeElements) ) } else { GenericParameterSyntax(name: genericParameterName) diff --git a/Tests/MockingMacrosTests/Macros/MockedMacro/Mocked_AssociatedTypeTests.swift b/Tests/MockingMacrosTests/Macros/MockedMacro/Mocked_AssociatedTypeTests.swift index c2ee1ffd..cf9770d2 100644 --- a/Tests/MockingMacrosTests/Macros/MockedMacro/Mocked_AssociatedTypeTests.swift +++ b/Tests/MockingMacrosTests/Macros/MockedMacro/Mocked_AssociatedTypeTests.swift @@ -36,7 +36,7 @@ struct Mocked_AssociatedTypeTests { } @Test(arguments: mockedTestConfigurations) - func protocolAssociatedTypeInheritanceWithMultipleInheritedTypes( + func protocolAssociatedTypeInheritanceWithMultipleCommaSeparatedInheritedTypes( interface: InterfaceConfiguration, mock: MockConfiguration ) { @@ -61,6 +61,56 @@ struct Mocked_AssociatedTypeTests { ) } + @Test(arguments: mockedTestConfigurations) + func protocolAssociatedTypeInheritanceWithMultipleComposedInheritedTypes( + interface: InterfaceConfiguration, + mock: MockConfiguration + ) { + assertMocked( + """ + \(interface.accessLevel) protocol Dependency { + associatedtype A: Hashable & Identifiable + associatedtype B: Comparable & Equatable & RawRepresentable + } + """, + generates: """ + #if SWIFT_MOCKING_ENABLED + @MockedMembers + \(mock.modifiers)\ + class DependencyMock<\ + A: Hashable & Identifiable, \ + B: Comparable & Equatable & RawRepresentable\ + >: Dependency { + } + #endif + """ + ) + } + + @Test(arguments: mockedTestConfigurations) + func protocolAssociatedTypeInheritanceWithMultipleMixedInheritedTypes( + interface: InterfaceConfiguration, + mock: MockConfiguration + ) { + assertMocked( + """ + \(interface.accessLevel) protocol Dependency { + associatedtype A: Sendable, Hashable & Identifiable + } + """, + generates: """ + #if SWIFT_MOCKING_ENABLED + @MockedMembers + \(mock.modifiers)\ + class DependencyMock<\ + A: Sendable & Hashable & Identifiable\ + >: Dependency { + } + #endif + """ + ) + } + // MARK: Associated Type Generic Where Clauses Tests @Test(arguments: mockedTestConfigurations)