Skip to content

Commit

Permalink
At most one @forwarded property per @INSTANTIABLE
Browse files Browse the repository at this point in the history
  • Loading branch information
dfed committed Dec 9, 2023
1 parent f3125c6 commit a7ba52c
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 136 deletions.
14 changes: 7 additions & 7 deletions Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,23 @@
/// Instantiation is thread-safe.
///
/// - SeeAlso: `Instantiator`
/// - Note: This class is the sole means for instantiating an `@Instantiable` type with `@Forwarded`
/// properties within the SafeDI framework.
public final class ForwardingInstantiator<ArgumentsToForward, InstantiableType> {
/// - Note: This class is the sole means for instantiating an `@Instantiable` type with a `@Forwarded`
/// property within the SafeDI framework.
public final class ForwardingInstantiator<ArgumentToForward, InstantiableType> {
/// Initializes a new forwarding instantiator with the provided instantiation closure.
///
/// - Parameter instantiator: A closure that takes `ArgumentsToForward` and returns an instance of `InstantiableType`.
public init(_ instantiator: @escaping (ArgumentsToForward) -> InstantiableType) {
public init(_ instantiator: @escaping (ArgumentToForward) -> InstantiableType) {
self.instantiator = instantiator
}

/// Instantiates and returns a new instance of the `@Instantiable` type, using the provided arguments.
///
/// - Parameter arguments: Arguments required for instantiation.
/// - Returns: An `InstantiableType` instance.
public func instantiate(_ arguments: ArgumentsToForward) -> InstantiableType {
instantiator(arguments)
public func instantiate(_ argument: ArgumentToForward) -> InstantiableType {
instantiator(argument)
}

private let instantiator: (ArgumentsToForward) -> InstantiableType
private let instantiator: (ArgumentToForward) -> InstantiableType
}
23 changes: 8 additions & 15 deletions Sources/SafeDICore/Models/ScopeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ actor ScopeGenerator {
self.propertiesToGenerate = propertiesToGenerate
self.receivedProperties = receivedProperties

forwardedProperties = instantiable
forwardedProperty = instantiable
.dependencies
// Instantiated properties will self-resolve.
.filter { $0.source == .forwarded }
.map(\.property)
// Our @Instantiable macro enforces that we have at most one forwarded property.
.first

propertiesMadeAvailableByChildren = Set(
instantiable
Expand Down Expand Up @@ -78,20 +80,12 @@ actor ScopeGenerator {
let isConstant: Bool
let propertyDeclaration: String
let leadingConcreteTypeName: String
let closureArguments: String
let closureArguments = if let forwardedProperty { " \(forwardedProperty.label) in" } else { "" }
switch property.propertyType {
case .forwardingInstantiator:
case .instantiator, .lazy, .forwardingInstantiator:
isConstant = false
propertyDeclaration = "let \(property.label)"
leadingConcreteTypeName = property.typeDescription.asSource
// TODO: Would be better to match types rather than assuming property order for the forwarded properties.
// TODO: Throw error if forwardedProperties has multiple of the same type.
closureArguments = " \(forwardedProperties.map(\.label).joined(separator: ", ")) in"
case .instantiator, .lazy:
isConstant = false
propertyDeclaration = "let \(property.label)"
leadingConcreteTypeName = property.typeDescription.asSource
closureArguments = ""
case .constant:
isConstant = true
if concreteTypeName == property.typeDescription.asSource {
Expand All @@ -100,7 +94,6 @@ actor ScopeGenerator {
propertyDeclaration = "let \(property.label): \(property.typeDescription.asSource)"
}
leadingConcreteTypeName = ""
closureArguments = ""
}

let leadingMemberWhitespace = " "
Expand Down Expand Up @@ -153,7 +146,7 @@ actor ScopeGenerator {
private let property: Property?
private let receivedProperties: Set<Property>
private let propertiesToGenerate: [ScopeGenerator]
private let forwardedProperties: [Property]
private let forwardedProperty: Property?
private let requiredReceivedProperties: Set<Property>
private let propertiesMadeAvailableByChildren: Set<Property>

Expand Down Expand Up @@ -203,13 +196,13 @@ actor ScopeGenerator {
.requiredReceivedProperties
.contains(where: {
!isPropertyResolved($0)
&& !propertyToGenerate.forwardedProperties.contains($0)
&& propertyToGenerate.forwardedProperty != $0
})
}

private func isPropertyResolved(_ property: Property) -> Bool {
resolvedProperties.contains(property)
|| receivedProperties.contains(property)
|| forwardedProperties.contains(property)
|| forwardedProperty == property
}
}
9 changes: 7 additions & 2 deletions Sources/SafeDIMacros/Macros/InstantiableMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ public struct InstantiableMacro: MemberMacro {
context.diagnose(diagnostic)
}

// TODO: Do not allow having multiple @Forwarded properties of the same type.
guard visitor.dependencies.filter({ $0.source == .forwarded }).count <= 1 else {
throw InstantiableError.tooManyForwardedProperties
}

let hasMemberwiseInitializerForInjectableProperties = visitor
.initializers
Expand Down Expand Up @@ -80,11 +82,14 @@ public struct InstantiableMacro: MemberMacro {

private enum InstantiableError: Error, CustomStringConvertible {
case decoratingIncompatibleType
case tooManyForwardedProperties

var description: String {
switch self {
case .decoratingIncompatibleType:
return "@\(InstantiableVisitor.macroName) must decorate a class, struct, or actor"
"@\(InstantiableVisitor.macroName) must decorate a class, struct, or actor"
case .tooManyForwardedProperties:
"An @\(InstantiableVisitor.macroName) type must have at most one @\(Dependency.Source.forwarded.rawValue) property"
}
}
}
Expand Down
Loading

0 comments on commit a7ba52c

Please sign in to comment.