diff --git a/Sources/SafeDICore/Models/TypeDescription.swift b/Sources/SafeDICore/Models/TypeDescription.swift index 1d885ac9..fc33d84c 100644 --- a/Sources/SafeDICore/Models/TypeDescription.swift +++ b/Sources/SafeDICore/Models/TypeDescription.swift @@ -27,7 +27,7 @@ public enum TypeDescription: Codable, Hashable, Comparable, Sendable { /// A nested type with possible generics. e.g. Array.Element or Swift.Array indirect case nested(name: String, parentType: TypeDescription, generics: [TypeDescription]) /// A composed type. e.g. Identifiable & Equatable - indirect case composition(Set) + indirect case composition(UnorderedComparingArray) /// An optional type. e.g. Int? indirect case optional(TypeDescription) /// An implicitly unwrapped optional type. e.g. Int! @@ -201,6 +201,8 @@ public enum TypeDescription: Codable, Hashable, Comparable, Sendable { } } +// MARK: - TypeSyntax + extension TypeSyntax { /// - Returns: the type description for the receiver. @@ -225,7 +227,7 @@ extension TypeSyntax { generics: genericTypeVisitor.genericArguments) } else if let typeIdentifiers = CompositionTypeSyntax(self) { - return .composition(Set(typeIdentifiers.elements.map { $0.type.typeDescription })) + return .composition(UnorderedComparingArray(typeIdentifiers.elements.map { $0.type.typeDescription })) } else if let typeIdentifier = OptionalTypeSyntax(self) { return .optional(typeIdentifier.wrappedType.typeDescription) @@ -290,6 +292,8 @@ extension TypeSyntax { } } +// MARK: - ExprSyntax + extension ExprSyntax { public var typeDescription: TypeDescription { if let typeExpr = TypeExprSyntax(self) { @@ -359,7 +363,7 @@ extension ExprSyntax { } } else if let sequenceExpr = SequenceExprSyntax(self) { if sequenceExpr.elements.contains(where: { BinaryOperatorExprSyntax($0) != nil }) { - return .composition(Set( + return .composition(UnorderedComparingArray( sequenceExpr .elements .filter { BinaryOperatorExprSyntax($0) == nil } @@ -406,6 +410,38 @@ extension ExprSyntax { } } +// MARK: - UnorderedComparingArray + +public struct UnorderedComparingArray: Codable, Hashable, Sendable, Collection { + + init(_ array: [Element]) { + self.array = array + set = Set(array) + } + + let array: [Element] + private let set: Set + + public static func == (lhs: UnorderedComparingArray, rhs: UnorderedComparingArray) -> Bool { + lhs.set == rhs.set + } + + public func makeIterator() -> IndexingIterator> { + array.makeIterator() + } + public var startIndex: Int { array.startIndex } + public var endIndex: Int { array.endIndex } + public func index(after i: Int) -> Int { + array.index(after: i) + } + + public subscript(position: Int) -> Element { + array[position] + } +} + +// MARK: - GenericArgumentVisitor + private final class GenericArgumentVisitor: SyntaxVisitor { private(set) var genericArguments = [TypeDescription]() diff --git a/Tests/SafeDICoreTests/TypeDescriptionTests.swift b/Tests/SafeDICoreTests/TypeDescriptionTests.swift index 5f684941..118da88a 100644 --- a/Tests/SafeDICoreTests/TypeDescriptionTests.swift +++ b/Tests/SafeDICoreTests/TypeDescriptionTests.swift @@ -532,27 +532,27 @@ final class TypeDescriptionTests: XCTestCase { func test_equality_isTrueWhenComparingLexigraphicallyEquivalentCompositions() { XCTAssertEqual( - TypeDescription.composition([ + TypeDescription.composition(.init([ .simple(name: "Foo"), .simple(name: "Bar"), - ]), - TypeDescription.composition([ + ])), + TypeDescription.composition(.init([ .simple(name: "Foo"), .simple(name: "Bar"), - ]) + ])) ) } func test_equality_isTrueWhenComparingReversedCompositions() { XCTAssertEqual( - TypeDescription.composition([ + TypeDescription.composition(.init([ .simple(name: "Foo"), .simple(name: "Bar"), - ]), - TypeDescription.composition([ + ])), + TypeDescription.composition(.init([ .simple(name: "Bar"), .simple(name: "Foo"), - ]) + ])) ) }