Skip to content

Commit

Permalink
Ensure correct dependency code-generation ordering (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
dfed authored Jan 20, 2024
1 parent a2d9db4 commit f530029
Show file tree
Hide file tree
Showing 6 changed files with 375 additions and 28 deletions.
6 changes: 2 additions & 4 deletions Sources/SafeDICore/Generators/DependencyTreeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ public final class DependencyTreeGenerator {

// Populate the propertiesToGenerate on each scope.
for scope in Set(typeDescriptionToScopeMap.values) {
var propertiesToGenerate = [Scope.PropertyToGenerate]()
for dependency in scope.instantiable.dependencies {
switch dependency.source {
case .instantiated:
Expand All @@ -238,20 +237,19 @@ public final class DependencyTreeGenerator {
case .constant, .instantiator:
break
}
propertiesToGenerate.append(.instantiated(
scope.propertiesToGenerate.append(.instantiated(
dependency.property,
instantiatedScope
))
case let .aliased(fulfillingProperty):
propertiesToGenerate.append(.aliased(
scope.propertiesToGenerate.append(.aliased(
dependency.property,
fulfilledBy: fulfillingProperty
))
case .forwarded, .received:
continue
}
}
scope.propertiesToGenerate.append(contentsOf: propertiesToGenerate)
}
return typeDescriptionToScopeMap
}
Expand Down
43 changes: 35 additions & 8 deletions Sources/SafeDICore/Generators/ScopeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,16 +245,43 @@ actor ScopeGenerator {
private var generateCodeTask: Task<String, Error>?

private func generateProperties(leadingMemberWhitespace: String) async throws -> [String] {
var generatedProperties = [String]()
let orderedPropertiesToGenerate = propertiesToGenerate.sorted(by: { lhs, rhs in
guard let lhsProperty = lhs.property else {
return true
guard var orderedPropertiesToGenerate = List(propertiesToGenerate) else { return [] }
let propertiesToGenerate = Set(propertiesToGenerate.compactMap(\.property))
for propertyToGenerateNode in orderedPropertiesToGenerate {
let hasDependenciesGeneratedByCurrentScope = !propertyToGenerateNode
.value
.requiredReceivedProperties
.isDisjoint(with: propertiesToGenerate)
guard hasDependenciesGeneratedByCurrentScope else {
// This property does not have received dependencies generated by this scope, therefore its ordering is irrelevant.
continue
}
var lastDependency: List<ScopeGenerator>?
for nextPropertyToGenerate in propertyToGenerateNode.dropFirst() {
if
let nextProperty = nextPropertyToGenerate.value.property,
// The property to generate depends on the next property!
propertyToGenerateNode
.value
.requiredReceivedProperties
.contains(nextProperty)
{
lastDependency = nextPropertyToGenerate
}
}

if let lastDependency {
// We depend on a (at least one) item further ahead in the list!
// Make sure we are created after our dependencies.
lastDependency.insert(propertyToGenerateNode.value)
if let head = propertyToGenerateNode.remove() {
orderedPropertiesToGenerate = head
}
}
// We must generate properties that are required by other properties first
return rhs.requiredReceivedProperties.contains(lhsProperty)
})
}

for childGenerator in orderedPropertiesToGenerate {
var generatedProperties = [String]()
for childGenerator in orderedPropertiesToGenerate.map(\.value) {
generatedProperties.append(
try await childGenerator
.generateCode(leadingWhitespace: leadingMemberWhitespace)
Expand Down
98 changes: 98 additions & 0 deletions Sources/SafeDICore/Models/List.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

public final class List<Element>: Sequence {

// MARK: Initialization

public init(value: Element, previous: List? = nil, next: List? = nil) {
self.value = value
self.previous = previous
self.next = next
}

public convenience init?(_ collection: some Collection<Element>) {
guard let first = collection.first else { return nil }
self.init(first: first, remaining: collection.dropFirst())
}

public convenience init(first: Element, remaining: some Collection<Element>) {
self.init(value: first)
var next = self
for element in remaining {
next = next.insert(element)
}
}

// MARK: Public

public let value: Element

/// Inserts the value after the current element.
/// - Parameter value: The value to insert into the list.
/// - Returns: The inserted element in the list.
@discardableResult
public func insert(_ value: Element) -> List<Element> {
let next = next

let nextToInsert = List(value: value)
self.next = nextToInsert

nextToInsert.next = next
nextToInsert.previous = self

next?.previous = nextToInsert

return nextToInsert
}

/// Removes the receiver from the list.
/// - Returns: The next element in the list, if the current element is the head of the list.
@discardableResult
public func remove() -> List<Element>? {
previous?.next = next
next?.previous = previous
return previous == nil ? next : nil
}

// MARK: Sequence

public func makeIterator() -> Iterator {
Iterator(node: self)
}

public struct Iterator: IteratorProtocol {
init(node: List?) {
self.node = node
}

public mutating func next() -> List? {
defer { node = node?.next }
return node
}

private var node: List?
}

// MARK: Private

private var next: List? = nil
private var previous: List? = nil
}
167 changes: 167 additions & 0 deletions Tests/SafeDICoreTests/ListTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import XCTest

@testable import SafeDICore

final class ListTests: XCTestCase {

func test_nonEmptyInit_createsListFromCollection() throws {
XCTAssertEqual(
try XCTUnwrap(List([1, 2, 3, 4, 5])).map(\.value),
[1, 2, 3, 4, 5]
)
}

func test_insert_onFirstElementInList_insertsElementAfterFirstElement() throws {
let systemUnderTest = try XCTUnwrap(List([1, 3, 4, 5]))
systemUnderTest.insert(2)
XCTAssertEqual(
systemUnderTest.map(\.value),
[1, 2, 3, 4, 5]
)
}

func test_insert_onLaterItemsInList_insertsElementAfterCurrentElement() {
let systemUnderTest = List(value: 1)
var last = systemUnderTest.insert(2)
last = last.insert(3)
last = last.insert(4)
last = last.insert(5)
XCTAssertEqual(
systemUnderTest.map(\.value),
[1, 2, 3, 4, 5]
)
}

func test_remove_onFirstElementInList_removesFirstElementAndReturnsNewFirstElement() throws {
let systemUnderTest = try XCTUnwrap(List([1, 2, 3, 4, 5]))
XCTAssertEqual(
systemUnderTest.remove()?.map(\.value),
[2, 3, 4, 5]
)
}

func test_remove_onItemThatWasInsertedAfterListCreation_removesItem() {
let systemUnderTest = List(value: 1)
let two = systemUnderTest.insert(2)
let four = two.insert(4)
four.insert(5)
two.insert(3).remove()

XCTAssertEqual(
systemUnderTest.map(\.value),
[1, 2, 4, 5]
)
}

func test_remove_onItemBeforeItemInsertedAfterListCreation_removesItem() {
let systemUnderTest = List(value: 1)
let two = systemUnderTest.insert(2)
let four = two.insert(4)
four.insert(5)
two.insert(3)
two.remove()

XCTAssertEqual(
systemUnderTest.map(\.value),
[1, 3, 4, 5]
)
}

func test_remove_onItemAfterItemInsertedAfterListCreation_removesItem() {
let systemUnderTest = List(value: 1)
let two = systemUnderTest.insert(2)
let four = two.insert(4)
four.insert(5)
two.insert(3)
four.remove()

XCTAssertEqual(
systemUnderTest.map(\.value),
[1, 2, 3, 5]
)
}

func test_remove_onLaterItemsInList_removesElementAndReturnsNil() {
let systemUnderTest = List(value: 1)
let two = systemUnderTest.insert(2)
let three = two.insert(3)
let four = three.insert(4)
four.insert(5)
XCTAssertNil(four.remove())
XCTAssertEqual(
systemUnderTest.map(\.value),
[1, 2, 3, 5]
)
}

func test_remove_onLastInList_removesElement() throws {
let systemUnderTest = try XCTUnwrap(List([1, 2, 3, 4]))
let lastElement = systemUnderTest.insert(5)
lastElement.remove()
XCTAssertEqual(
systemUnderTest.map(\.value),
[1, 2, 3, 4]
)
}

func test_insert_andThenRemoveItemBeforeInsertion_insertsAndThenRemoves() {
let systemUnderTest = List(value: 1)
let two = systemUnderTest.insert(2)
let three = two.insert(3)
let four = three.insert(4)
let secondFour = four.insert(4)
secondFour.insert(5)
four.remove()
XCTAssertEqual(
systemUnderTest.map(\.value),
[1, 2, 3, 4, 5]
)
}

func test_insert_andThenRemoveItem_insertsAndThenRemoves() {
let systemUnderTest = List(value: 1)
let two = systemUnderTest.insert(2)
let three = two.insert(3)
let four = three.insert(4)
four.remove()
three.insert(5)
XCTAssertEqual(
systemUnderTest.map(\.value),
[1, 2, 3, 5]
)
}

func test_insert_andThenRemoveItemAfterInsertion_insertsAndThenRemoves() {
let systemUnderTest = List(value: 1)
let two = systemUnderTest.insert(2)
let three = two.insert(3)
let four = three.insert(4)
four.insert(5)
four.remove()
three.insert(4)
XCTAssertEqual(
systemUnderTest.map(\.value),
[1, 2, 3, 4, 5]
)
}
}
13 changes: 4 additions & 9 deletions Tests/SafeDICoreTests/UnorderedEquatingCollectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation
import SwiftParser
import SwiftSyntax
import XCTest

@testable import SafeDICore
Expand All @@ -29,15 +26,13 @@ final class UnorderedEquatingCollectionTests: XCTestCase {

func test_makeIterator_iteratesInOrder() {
for (index, value) in UnorderedEquatingCollection([1, 2, 3]).enumerated() {
switch index {
case 0:
if index == 0 {
XCTAssertEqual(value, 1)
case 1:
} else if index == 1 {
XCTAssertEqual(value, 2)
case 2:
} else {
XCTAssertEqual(index, 2)
XCTAssertEqual(value, 3)
case _:
XCTFail("Unexpected index \(index)")
}
}
}
Expand Down
Loading

0 comments on commit f530029

Please sign in to comment.