Skip to content

Commit 4280857

Browse files
committed
Introduce UnorderedComparingArray
1 parent d2e2fcb commit 4280857

File tree

2 files changed

+48
-16
lines changed

2 files changed

+48
-16
lines changed

Sources/SafeDICore/Models/TypeDescription.swift

+39-5
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public enum TypeDescription: Codable, Hashable, Comparable, Sendable {
2727
/// A nested type with possible generics. e.g. Array.Element or Swift.Array<Element>
2828
indirect case nested(name: String, parentType: TypeDescription, generics: [TypeDescription])
2929
/// A composed type. e.g. Identifiable & Equatable
30-
indirect case composition(Set<TypeDescription>)
30+
indirect case composition(UnorderedComparingArray<TypeDescription>)
3131
/// An optional type. e.g. Int?
3232
indirect case optional(TypeDescription)
3333
/// An implicitly unwrapped optional type. e.g. Int!
@@ -73,8 +73,6 @@ public enum TypeDescription: Codable, Hashable, Comparable, Sendable {
7373
case let .composition(types):
7474
return types
7575
.map { $0.asSource }
76-
// Sort the result to ensure stable code generation.
77-
.sorted()
7876
.joined(separator: " & ")
7977
case let .optional(type):
8078
return "\(type.asSource)?"
@@ -201,6 +199,8 @@ public enum TypeDescription: Codable, Hashable, Comparable, Sendable {
201199
}
202200
}
203201

202+
// MARK: - TypeSyntax
203+
204204
extension TypeSyntax {
205205

206206
/// - Returns: the type description for the receiver.
@@ -225,7 +225,7 @@ extension TypeSyntax {
225225
generics: genericTypeVisitor.genericArguments)
226226

227227
} else if let typeIdentifiers = CompositionTypeSyntax(self) {
228-
return .composition(Set(typeIdentifiers.elements.map { $0.type.typeDescription }))
228+
return .composition(UnorderedComparingArray(typeIdentifiers.elements.map { $0.type.typeDescription }))
229229

230230
} else if let typeIdentifier = OptionalTypeSyntax(self) {
231231
return .optional(typeIdentifier.wrappedType.typeDescription)
@@ -290,6 +290,8 @@ extension TypeSyntax {
290290
}
291291
}
292292

293+
// MARK: - ExprSyntax
294+
293295
extension ExprSyntax {
294296
public var typeDescription: TypeDescription {
295297
if let typeExpr = TypeExprSyntax(self) {
@@ -359,7 +361,7 @@ extension ExprSyntax {
359361
}
360362
} else if let sequenceExpr = SequenceExprSyntax(self) {
361363
if sequenceExpr.elements.contains(where: { BinaryOperatorExprSyntax($0) != nil }) {
362-
return .composition(Set(
364+
return .composition(UnorderedComparingArray(
363365
sequenceExpr
364366
.elements
365367
.filter { BinaryOperatorExprSyntax($0) == nil }
@@ -406,6 +408,38 @@ extension ExprSyntax {
406408
}
407409
}
408410

411+
// MARK: - UnorderedComparingArray
412+
413+
public struct UnorderedComparingArray<Element: Codable & Hashable & Sendable>: Codable, Hashable, Sendable, Collection {
414+
415+
init(_ array: [Element]) {
416+
self.array = array
417+
set = Set(array)
418+
}
419+
420+
let array: [Element]
421+
private let set: Set<Element>
422+
423+
public static func == (lhs: UnorderedComparingArray, rhs: UnorderedComparingArray) -> Bool {
424+
lhs.set == rhs.set
425+
}
426+
427+
public func makeIterator() -> IndexingIterator<Array<Element>> {
428+
array.makeIterator()
429+
}
430+
public var startIndex: Int { array.startIndex }
431+
public var endIndex: Int { array.endIndex }
432+
public func index(after i: Int) -> Int {
433+
array.index(after: i)
434+
}
435+
436+
public subscript(position: Int) -> Element {
437+
array[position]
438+
}
439+
}
440+
441+
// MARK: - GenericArgumentVisitor
442+
409443
private final class GenericArgumentVisitor: SyntaxVisitor {
410444

411445
private(set) var genericArguments = [TypeDescription]()

Tests/SafeDICoreTests/TypeDescriptionTests.swift

+9-11
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,7 @@ final class TypeDescriptionTests: XCTestCase {
104104
XCTAssertFalse(typeDescription.isUnknown, "Type description is not of known type!")
105105
XCTAssertEqual(
106106
typeDescription.asSource,
107-
// Composition elements are sorted when creating source code in order to
108-
// ensure stable code generation, since composition ordering is arbitrary.
109-
"Bar & Foo"
107+
"Foo & Bar"
110108
)
111109
}
112110

@@ -532,27 +530,27 @@ final class TypeDescriptionTests: XCTestCase {
532530

533531
func test_equality_isTrueWhenComparingLexigraphicallyEquivalentCompositions() {
534532
XCTAssertEqual(
535-
TypeDescription.composition([
533+
TypeDescription.composition(.init([
536534
.simple(name: "Foo"),
537535
.simple(name: "Bar"),
538-
]),
539-
TypeDescription.composition([
536+
])),
537+
TypeDescription.composition(.init([
540538
.simple(name: "Foo"),
541539
.simple(name: "Bar"),
542-
])
540+
]))
543541
)
544542
}
545543

546544
func test_equality_isTrueWhenComparingReversedCompositions() {
547545
XCTAssertEqual(
548-
TypeDescription.composition([
546+
TypeDescription.composition(.init([
549547
.simple(name: "Foo"),
550548
.simple(name: "Bar"),
551-
]),
552-
TypeDescription.composition([
549+
])),
550+
TypeDescription.composition(.init([
553551
.simple(name: "Bar"),
554552
.simple(name: "Foo"),
555-
])
553+
]))
556554
)
557555
}
558556

0 commit comments

Comments
 (0)