Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: renato-iar/MDI
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 3.0.0
Choose a base ref
...
head repository: renato-iar/MDI
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 4.0.0
Choose a head ref
  • 1 commit
  • 11 files changed
  • 1 contributor

Commits on Oct 22, 2023

  1. Add named parameters in generated factory resolves

    - Inject named parameters into factory methods
    - Fully deprecate untyped auto registration
    - Update unit tests
    - Update sample client
    - README
    renato-iar committed Oct 22, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    b25d1bd View commit details
50 changes: 42 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -21,21 +21,55 @@ Presently, the following limitations exist (due to compiler bug):
- The hub type used to register dependencies must either be a `class` or a `struct`; using `enum`s will result in the compiler failing
- All dependencies must be registered in the same file where the hub type is declared (they can be split into multiple `extension`s for organization though)

## Version 4.0.0

- Fully deprecates `@FactoryRegister(_:parameterTypes:factory:)` in favour of `@FactoryRegister(_:parameterTypes:using:)` (which allows mixing explict parameters with auto-resolved dependencies)
- Adopts named parameters when it is possible to retrieve them from the factory definition (e.g. when a method name is used such as `SomeTime.init(resolvableDependencyA:resolvableDependencyB:parameter1:parameter2)`)

## Version 3.0.0

- Add full support for registered opaque types to interplay with non-opaque registrations
- Soft deprecation of `@FactoryRegister(_:parameterTypes:factory:)`

## Version 2.2.0

- Adds support for registering dependencies into the assembly using opaque types

## Version 2.1.0

- Allows factories to be registered using mixed types: auto-resolved and parametric

## Version 2.0.2

- Remove sample client from products

## Version 2.0.1

- Fix `getPlainTypeName`, which was failing to extract simple types

## Version 2.0.0

- Improve mock functions to take parameters in factories

## Version 1.1.0

- Add mocking

## Example Usage

### Plain types & existencials

Define some type that will serve both as the "assembly hub" and the resolution entry point.
(As stated in Limitations, `enum`s are not supported).

```
```swift
enum Dependency { }
```

On this example, assemblies will be defined in extensions of `Dependency` (although this is not mandatory and can be done directly on the type declaration).
In the following example, we will assume the following types:

```
```swift
protocol ABTestingProtocol { }
protocol CodeGuardsProtocol { }
protocol ThemeProtocol { }
@@ -58,7 +92,7 @@ final class Theme: ThemeProtocol {

And then create an assembly to register them:

```
```swift
import MDI

@SingletonRegister((any ABTestingProtocol).self, using: ABTesting.init)
@@ -73,7 +107,7 @@ Note that, in the previous example, all dependencies were singletons, but this o

If instead `@AutoRegister` was used:

```
```swift
import MDI

@AutoRegister((any ABTestingProtocol).self, using: ABTesting.init)
@@ -88,7 +122,7 @@ Finally, some dependencies require parameters that cannot be resolved, but rathe
This can easily be achieved via `@FactoryRegister`.
In the following example, we can resolve `ThemeProtocol`, but not necessarily `boot: Date` or `sessionId: String`.

```
```swift
protocol AppContextProtocol {}

final class AppContext: AppContextProtocol {
@@ -110,7 +144,7 @@ final class AppContext: AppContextProtocol {

Using `@FactoryRegister` we can expose the required parameters while even leveraging `resolve(...)` in the factory method to resolve `ThemeProtocol`:

```
```swift
import MDI

@FactoryRegister(
@@ -125,8 +159,8 @@ This will expose a `resolve` method that exposes `Date` and `String` while impl

```swift
extension Dependency {
static func resolve(_: any AppContextProtocol, _ arg0: Date, _ arg1: String) -> any AppContextProtocol {
return (AppContextProtocolImpl.init(boot:sessionId:theme:))(arg0, arg1, Self.resolve())
static func resolve(_: any AppContextProtocol, boot: Date, sessionId: String) -> any AppContextProtocol {
return (AppContextProtocolImpl.init(boot:sessionId:theme:))(boot, sessionId, Self.resolve())
}
}
```
18 changes: 0 additions & 18 deletions Sources/MDI/MDI.swift
Original file line number Diff line number Diff line change
@@ -30,24 +30,6 @@ public macro SingletonRegister<each Parameter, ResolvedType>(
using factory: (repeat each Parameter) -> ResolvedType
) = #externalMacro(module: "MDIMacros", type: "DISingletonRegistration")

/**
Registers a dependency into the assembly, exposing all parameters required to build the concrete type

- parameters:
- type: The type being registered into the assembly; typically a `protocol`. Will be the return type when calling `resolve`
- parameterTypes: The types of each input necessary to execute the `factory`
- factory: The factory to be used to resolve the dependency

- note: `factory` might be a `init` for the concrete type, or a wrapper method, e.g. resolving all type dependencies via `resolve()` while exposing only non-resolvable parameters
*/
@available(*, deprecated, message: "Use FactoryRegister(_:parameterTypes:using:) instead")
@attached(member, names: arbitrary)
public macro FactoryRegister<each Parameter, ResolvedType>(
_ type: ResolvedType.Type,
parameterTypes: repeat (each Parameter).Type,
factory: (repeat each Parameter) -> ResolvedType
) = #externalMacro(module: "MDIMacros", type: "DIFactoryRegistration")

/**
Registers a dependency into the assembly

33 changes: 4 additions & 29 deletions Sources/MDIClient/main.swift
Original file line number Diff line number Diff line change
@@ -21,14 +21,6 @@ struct AppStateImpl: AppState {
self.boot = date
self.version = version
}

static func factory(date: Date, version: String) -> Self {
.init(
theme: ExistencialDependency.resolve(Theme.self),
boot: date,
version: version
)
}
}

protocol ABTests { }
@@ -47,34 +39,17 @@ struct ApplicationProxyImpl: ApplicationProxy {

// MARK: - Existencials

struct ExistencialDependency {
struct Dependency {
static func resolve() -> String { " " }
static func resolve() -> Double { 0 }
}

@FactoryRegister((any AppState).self, parameterTypes: .resolved((any Theme).self), .explicit(Date.self), .explicit(String.self), using: AppStateImpl.init(theme:boot:version:))
@SingletonRegister((any Theme).self, using: ThemeImpl.init)
@SingletonRegister((any ABTests).self, using: ABTestsImpl.init)
@SingletonRegister((any CodeGuards).self, using: CodeGuardsImpl.init)
@SingletonRegister((any ApplicationProxy).self, parameterTypes: (any ABTests).self, (any CodeGuards).self, using: ApplicationProxyImpl.init(abTests:codeGuards:))
extension ExistencialDependency { }

let existencialAppState: any AppState = ExistencialDependency.resolve(Date(), "1.0.0")
let existencialApplication: any ApplicationProxy = ExistencialDependency.resolve()

// MARK: - Opaque

struct OpaqueDependency {
static func resolve() -> String { " " }
static func resolve() -> Double { 0 }
}

@OpaqueFactoryRegister((any AppState).self, parameterTypes: .resolved((any Theme).self), .explicit(Date.self), .explicit(String.self), using: AppStateImpl.init(theme:boot:version:))
@OpaqueSingletonRegister((any Theme).self, using: ThemeImpl.init)
@OpaqueSingletonRegister((any ABTests).self, using: ABTestsImpl.init)
@OpaqueSingletonRegister((any CodeGuards).self, using: CodeGuardsImpl.init)
@OpaqueSingletonRegister((any ApplicationProxy).self, parameterTypes: (any ABTests).self, (any CodeGuards).self, using: ApplicationProxyImpl.init(abTests:codeGuards:))
extension OpaqueDependency { }
extension Dependency { }

let opaqueAppState: some AppState = OpaqueDependency.resolve((any AppState).self, Date(), "1.0.0")
let opaqueApplication: some ApplicationProxy = OpaqueDependency.resolve((any ApplicationProxy).self)
let existencialAppState: any AppState = Dependency.resolve(boot: Date(), version: "1.0.0")
let existencialApplication: any ApplicationProxy = Dependency.resolve(ApplicationProxy.self)
Original file line number Diff line number Diff line change
@@ -46,27 +46,47 @@ extension DIFactoryAutoRegistration: MemberMacro {
return []
}

let factoryNamedParameters = SyntaxUtils.getFactoryNamedParameters(from: factory)
let factoryTypesFull = SyntaxUtils.getFactoryRegistrableParameterTypes(from: node)
let factoryTypes = factoryTypesFull.compactMap { $0.resolve ? nil : $0.type }
let factoryParameters = factoryTypes
.enumerated()
.map { index, type in
"_ arg\(index): \(type)"
}
.joined(separator: ", ")
let factoryParameterNames = (0 ..< factoryTypes.count).map { "arg\($0)" }
// let factoryParameters = factoryTypes
// .enumerated()
// .map { index, type in
// "_ arg\(index): \(type)"
// }
// .joined(separator: ", ")
var argIndex = 0
let factoryArguments = factoryTypesFull
.map { resolve, type in
if resolve {
return "\(containerName).resolve(\(type).self)"
var factoryParameters: [String] = []
var factoryParameterNames: [String] = []
var factoryArguments: [String] = []

factoryTypesFull
.enumerated()
.forEach {
guard !$0.element.resolve else {
factoryArguments.append("\(containerName).resolve(\($0.element.type).self)")
return
}

let prefix: String
let parameter: String

if
let namedParameter = factoryNamedParameters[safe: $0.offset],
let namedParameter
{
prefix = ""
parameter = namedParameter
} else {
prefix = "_ "
parameter = "arg\(argIndex)"
argIndex += 1
return "arg\(argIndex-1)"
}

factoryParameters.append("\(prefix)\(parameter): \($0.element.type)")
factoryParameterNames.append(parameter)
factoryArguments.append(parameter)
}
.joined(separator: ", ")
let publicFactoryArguments = factoryParameterNames.joined(separator: ", ")

guard let returnTypePlainName = SyntaxUtils.getPlainTypeName(from: returnType) else {
context.addDiagnostics(
@@ -103,17 +123,17 @@ extension DIFactoryAutoRegistration: MemberMacro {
} else {
declarations.append(contentsOf: [
"""
static func resolve(_: \(returnType).Type, \(raw: factoryParameters)) -> \(returnType) {
static func resolve(_: \(returnType).Type, \(raw: factoryParameters.joined(separator: ", "))) -> \(returnType) {
#if DEBUG
\(raw: SyntaxUtils.generateMockFunctionCall(with: returnTypePlainName, arguments: factoryParameterNames))
#endif
return (\(factory))(\(raw: factoryArguments))
return (\(factory))(\(raw: factoryArguments.joined(separator: ", ")))
}
""",

"""
static func resolve(\(raw: factoryParameters)) -> \(returnType) {
return resolve(\(returnType).self, \(raw: publicFactoryArguments))
static func resolve(\(raw: factoryParameters.joined(separator: ", "))) -> \(returnType) {
return (\(factory))(\(raw: factoryArguments.joined(separator: ", ")))
}
"""
]
125 changes: 0 additions & 125 deletions Sources/MDIMacros/Macros/Existencial/DIFactoryRegistration.swift

This file was deleted.

Loading