Skip to content

Commit

Permalink
Add associated values to Dependency.Source to uniformly capture the d…
Browse files Browse the repository at this point in the history
…ata we need
  • Loading branch information
dfed committed Jan 19, 2024
1 parent 78b7e98 commit 0d3ebd4
Show file tree
Hide file tree
Showing 15 changed files with 399 additions and 214 deletions.
2 changes: 1 addition & 1 deletion Sources/SafeDICore/Errors/FixableInstantiableError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public enum FixableInstantiableError: DiagnosticError {
public var description: String {
switch self {
case .dependencyHasTooManyAttributes:
"Dependency can have at most one of @\(Dependency.Source.instantiated), @\(Dependency.Source.received), or @\(Dependency.Source.forwarded) attached macro"
"Dependency can have at most one of @\(Dependency.Source.instantiatedRawValue), @\(Dependency.Source.receivedRawValue), or @\(Dependency.Source.forwardedRawValue) attached macro"
case .dependencyHasInitializer:
"Dependency must not have hand-written initializer"
case .missingPublicOrOpenAttribute:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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 SwiftSyntax

extension AttributeListSyntax.Element {

public var instantiableMacro: AttributeSyntax? {
switch self {
case let .attribute(attribute):
guard IdentifierTypeSyntax(attribute.attributeName)?.name.text == InstantiableVisitor.macroName else {
return nil
}
return attribute

case .ifConfigDecl:
return nil
}
}

public var instantiatedMacro: AttributeSyntax? {
switch self {
case let .attribute(attribute):
guard IdentifierTypeSyntax(attribute.attributeName)?.name.text == Dependency.Source.instantiatedRawValue else {
return nil
}
return attribute

case .ifConfigDecl:
return nil
}
}

public var receivedMacro: AttributeSyntax? {
switch self {
case let .attribute(attribute):
guard IdentifierTypeSyntax(attribute.attributeName)?.name.text == Dependency.Source.receivedRawValue else {
return nil
}
return attribute
case .ifConfigDecl:
return nil
}
}

public var forwardedMacro: AttributeSyntax? {
switch self {
case let .attribute(attribute):
guard IdentifierTypeSyntax(attribute.attributeName)?.name.text == Dependency.Source.forwardedRawValue else {
return nil
}
return attribute
case .ifConfigDecl:
return nil
}
}

}
42 changes: 9 additions & 33 deletions Sources/SafeDICore/Extensions/AttributeListSyntaxExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,7 @@ extension AttributeListSyntax {

public var instantiableMacro: AttributeSyntax? {
guard let attribute = first(where: { element in
switch element {
case let .attribute(attribute):
return IdentifierTypeSyntax(attribute.attributeName)?.name.text == InstantiableVisitor.macroName
case .ifConfigDecl:
return false
}
element.instantiableMacro != nil
}) else {
return nil
}
Expand All @@ -38,12 +33,7 @@ extension AttributeListSyntax {

public var instantiatedMacro: AttributeSyntax? {
guard let attribute = first(where: { element in
switch element {
case let .attribute(attribute):
return IdentifierTypeSyntax(attribute.attributeName)?.name.text == Dependency.Source.instantiated.rawValue
case .ifConfigDecl:
return false
}
element.instantiatedMacro != nil
}) else {
return nil
}
Expand All @@ -52,38 +42,24 @@ extension AttributeListSyntax {

public var receivedMacro: AttributeSyntax? {
guard let attribute = first(where: { element in
switch element {
case let .attribute(attribute):
return IdentifierTypeSyntax(attribute.attributeName)?.name.text == Dependency.Source.received.rawValue
case .ifConfigDecl:
return false
}
element.receivedMacro != nil
}) else {
return nil
}
return AttributeSyntax(attribute)
}

public var attributedNodes: [(attribute: String, node: AttributeListSyntax.Element)] {
compactMap { element in
switch element {
case let .attribute(attribute):
guard let identifierText = IdentifierTypeSyntax(attribute.attributeName)?.name.text else {
public var dependencySources: [(source: Dependency.Source, node: AttributeListSyntax.Element)] {
compactMap {
switch $0 {
case .attribute:
guard let source = Dependency.Source(node: $0) else {
return nil
}
return (attribute: identifierText, node: element)
return (source: source, node: $0)
case .ifConfigDecl:
return nil
}
}
}

public var dependencySources: [(source: Dependency.Source, node: AttributeListSyntax.Element)] {
attributedNodes.compactMap {
guard let source = Dependency.Source.init(rawValue: $0.attribute) else {
return nil
}
return (source: source, node: $0.node)
}
}
}
65 changes: 36 additions & 29 deletions Sources/SafeDICore/Generators/DependencyTreeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public final class DependencyTreeGenerator {
var description: String {
switch self {
case let .noInstantiableFound(typeDescription):
"No `@\(InstantiableVisitor.macroName)`-decorated type or extension found to fulfill `@\(Dependency.Source.instantiated.rawValue)`-decorated property with type `\(typeDescription.asSource)`"
"No `@\(InstantiableVisitor.macroName)`-decorated type or extension found to fulfill `@\(Dependency.Source.instantiatedRawValue)`-decorated property with type `\(typeDescription.asSource)`"
case let .unfulfillableProperties(unfulfillableProperties):
"""
The following @Received properties were never @Instantiated or @Forwarded:
Expand Down Expand Up @@ -216,33 +216,40 @@ public final class DependencyTreeGenerator {
// Populate the propertiesToInstantiate on each scope.
for scope in typeDescriptionToScopeMap.values {
var additionalPropertiesToInstantiate = [Scope.PropertyToInstantiate]()
for instantiatedDependency in scope.instantiable.instantiatedDependencies {
let instantiatedType = instantiatedDependency.asInstantiatedType
guard
let instantiable = typeDescriptionToFulfillingInstantiableMap[instantiatedType],
let instantiatedScope = typeDescriptionToScopeMap[instantiatedType]
else {
assertionFailure("Invalid state. Could not look up info for \(instantiatedType)")
continue
}
let type = instantiatedDependency.property.propertyType
switch type {
case .forwardingInstantiator:
guard !instantiable.dependencies.filter(\.isForwarded).isEmpty else {
throw DependencyTreeGeneratorError.forwardingInstantiatorInstntiableHasNoForwardedProperty(
property: instantiatedDependency.property,
instantiableWithoutForwardedProperty: instantiable)
for instantiatedDependency in scope.instantiable.dependencies {
switch instantiatedDependency.source {
case .instantiated:
let instantiatedType = instantiatedDependency.asInstantiatedType
guard
let instantiable = typeDescriptionToFulfillingInstantiableMap[instantiatedType],
let instantiatedScope = typeDescriptionToScopeMap[instantiatedType]
else {
assertionFailure("Invalid state. Could not look up info for \(instantiatedType)")
continue
}

case .constant, .instantiator:
break
let type = instantiatedDependency.property.propertyType
switch type {
case .forwardingInstantiator:
guard !instantiable.dependencies.filter(\.isForwarded).isEmpty else {
throw DependencyTreeGeneratorError.forwardingInstantiatorInstntiableHasNoForwardedProperty(
property: instantiatedDependency.property,
instantiableWithoutForwardedProperty: instantiable)
}
case .constant, .instantiator:
break
}
additionalPropertiesToInstantiate.append(.instantiated(
instantiatedDependency.property,
instantiatedScope
))
case let .aliased(fulfillingProperty):
additionalPropertiesToInstantiate.append(.aliased(
instantiatedDependency.property,
fulfilledBy: fulfillingProperty
))
case .forwarded, .received:
continue
}
additionalPropertiesToInstantiate.append(Scope.PropertyToInstantiate(
property: instantiatedDependency.property,
instantiable: instantiable,
scope: instantiatedScope,
type: type
))
}
scope.propertiesToInstantiate.append(contentsOf: additionalPropertiesToInstantiate)
}
Expand Down Expand Up @@ -275,7 +282,7 @@ public final class DependencyTreeGenerator {
}
}

for childScope in scope.propertiesToInstantiate.map(\.scope) {
for childScope in scope.propertiesToInstantiate.compactMap(\.scope) {
guard !instantiables.contains(childScope.instantiable) else {
// We've previously visited this child scope.
// There is a cycle in our scope tree. Do not re-enter it.
Expand Down Expand Up @@ -322,15 +329,15 @@ extension Dependency {
switch source {
case .instantiated:
return true
case .forwarded, .received:
case .aliased, .forwarded, .received:
return false
}
}
fileprivate var isForwarded: Bool {
switch source {
case .forwarded:
return true
case .instantiated, .received:
case .aliased, .instantiated, .received:
return false
}
}
Expand Down
28 changes: 21 additions & 7 deletions Sources/SafeDICore/Generators/ScopeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,16 @@ actor ScopeGenerator {
instantiable
.dependencies
.filter {
// If the source is not received, the property is being made available.
$0.source != .received
// If the dependency has a fulfilling property, the property is being aliased.
|| $0.fulfillingProperty != nil
switch $0.source {
case .instantiated, .forwarded:
// The source is being injected into the dependency tree.
return true
case .aliased:
// This property is being re-injected into the dependency tree under a new alias.
return true
case .received:
return false
}
}
.map(\.property)
).union(propertiesToGenerate
Expand All @@ -64,8 +70,16 @@ actor ScopeGenerator {
requiredReceivedProperties = Set(
instantiable
.dependencies
.filter { $0.source == .received }
.map(\.property)
.compactMap {
switch $0.source {
case .instantiated, .forwarded:
return nil
case .received:
return $0.property
case let .aliased(fulfillingProperty):
return fulfillingProperty
}
}
).union(propertiesToGenerate.flatMap(\.requiredReceivedProperties))
.subtracting(propertiesMadeAvailableByChildren)
}
Expand Down Expand Up @@ -278,7 +292,7 @@ actor ScopeGenerator {
!propertyToGenerate
.requiredReceivedProperties
.contains(where: {
!isPropertyResolved($0)
!isPropertyResolved($0)
&& !propertyToGenerate.forwardedProperties.contains($0)
})
}
Expand Down
65 changes: 39 additions & 26 deletions Sources/SafeDICore/Models/Dependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,18 @@ public struct Dependency: Codable, Hashable {

public init(
property: Property,
source: Dependency.Source,
fulfillingPropertyName: String? = nil,
fulfillingTypeDescription: TypeDescription? = nil
source: Dependency.Source
) {
self.property = property
self.source = source
self.fulfillingPropertyName = fulfillingPropertyName
self.fulfillingTypeDescription = fulfillingTypeDescription

if let fulfillingPropertyName, let fulfillingTypeDescription {
fulfillingProperty = Property(
label: fulfillingPropertyName,
typeDescription: fulfillingTypeDescription)
} else {
fulfillingProperty = nil
switch source {
case .received, .forwarded:
asInstantiatedType = property.typeDescription.asInstantiatedType
case let .instantiated(fulfillingTypeDescription):
asInstantiatedType = (fulfillingTypeDescription ?? property.typeDescription).asInstantiatedType
case let .aliased(fulfillingProperty):
asInstantiatedType = fulfillingProperty.typeDescription.asInstantiatedType
}
asInstantiatedType = (fulfillingTypeDescription ?? property.typeDescription).asInstantiatedType
}

// MARK: Public
Expand All @@ -53,24 +48,42 @@ public struct Dependency: Codable, Hashable {
public let property: Property
/// The source of the dependency within the dependency tree.
public let source: Source
/// The name of the property that will be used to fulfill this property.
public let fulfillingPropertyName: String?
/// The type description of the type that will be used to fulfill this property.
/// This type must be the same as or a parent type of `property.typeDescription`.
public let fulfillingTypeDescription: TypeDescription?
/// The property that will be used to fulfill this property.
public let fulfillingProperty: Property?
/// The receiver's type description as an `@Instantiable`-decorated type.
public let asInstantiatedType: TypeDescription

public enum Source: String, CustomStringConvertible, Codable, Hashable {
case instantiated = "Instantiated"
case received = "Received"
case forwarded = "Forwarded"
public enum Source: Codable, Hashable {
case instantiated(fulfillingTypeDescription: TypeDescription?)
case received
case aliased(fulfillingProperty: Property)
case forwarded

public var description: String {
rawValue
public init?(node: AttributeListSyntax.Element) {
if let instantiatedMacro = node.instantiatedMacro {
self = .instantiated(fulfillingTypeDescription: instantiatedMacro.fulfillingTypeDescription)
} else if let receivedMacro = node.receivedMacro {
if
let fulfillingPropertyName = receivedMacro.fulfillingPropertyName,
let fulfillingTypeDescription = receivedMacro.fulfillingTypeDescription
{
self = .aliased(
fulfillingProperty: Property(
label: fulfillingPropertyName,
typeDescription: fulfillingTypeDescription
)
)
} else {
self = .received
}
} else if node.forwardedMacro != nil {
self = .forwarded
} else {
return nil
}
}

public static let instantiatedRawValue = "Instantiated"
public static let receivedRawValue = "Received"
public static let forwardedRawValue = "Forwarded"
}

// MARK: Internal
Expand Down
Loading

0 comments on commit 0d3ebd4

Please sign in to comment.