Skip to content

Commit f2874bd

Browse files
authored
Merge pull request #26 from strvcom/dc/feat/debug-improvements
Improve DI debugging by replacing ObjectIdentifier(...) in ResolutionError messages with readable Swift type names, and document that argument matching uses compile-time types.
2 parents e70e77a + a2d097e commit f2874bd

16 files changed

Lines changed: 150 additions & 9 deletions

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ __Sections__
1111
- `Removed` for deprecated features removed in this release.
1212
- `Fixed` for any bug fixes.
1313

14+
## [2.0.0]
15+
16+
### Added
17+
18+
- Support for Swift parameter packs
19+
- Tests converted to new Swift Testing framework
20+
21+
### Fixed
22+
23+
- Resolving of shared instances
24+
1425
## [1.0.5]
1526

1627
### Added

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,20 @@ container.register { container, number in
8686
}
8787
```
8888

89+
Argument matching is based on the compile-time type of each argument. That means `ConcreteType` and `any SomeProtocol` are different registrations even if the concrete value conforms to the protocol:
90+
```swift
91+
let container = Container()
92+
container.register { _, dependency: any DIProtocol in
93+
DependencyWithProtocolParameter(subDependency: dependency)
94+
}
95+
96+
let concrete = StructureDependency(property1: "42")
97+
let existential: any DIProtocol = concrete
98+
99+
let service: DependencyWithProtocolParameter = container.resolve(arguments: existential) // works
100+
// container.resolve(arguments: concrete) throws because StructureDependency != any DIProtocol
101+
```
102+
89103
### Autoregistration
90104

91105
Let's have look at an example from above:

Sources/Container/Async/AsyncContainer.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin
6363
/// The arguments are typically parameters in an initializer of the dependency that are not registered in the same container,
6464
/// therefore, they need to be passed in `resolve` call. This registration method doesn't have any scope parameter for a reason - the container
6565
/// should always return a new instance for dependencies with arguments.
66+
/// Argument matching is based on compile-time types, so registering with `ConcreteType` and resolving with `any Protocol`
67+
/// (or the other way around) creates different registrations.
6668
///
6769
/// - Parameters:
6870
/// - type: Type of the dependency to register
@@ -95,6 +97,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin
9597
/// Uses Swift parameter packs to support 1-3 arguments with a single method signature.
9698
/// If a dependency of the given type with the given arguments wasn't registered before this method call
9799
/// the method throws ``ResolutionError.dependencyNotRegistered``
100+
/// Argument matching is based on compile-time types, so `ConcreteType` and `any Protocol` are treated as different argument lists.
98101
///
99102
/// - Parameters:
100103
/// - type: Type of the dependency that should be resolved

Sources/Container/Sync/Container.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ open class Container: DependencyAutoregistering, DependencyResolving, Dependency
5656
/// The arguments are typically parameters in an initializer of the dependency that are not registered in the same container,
5757
/// therefore, they need to be passed in `resolve` call. This registration method doesn't have any scope parameter for a reason - the container
5858
/// should always return a new instance for dependencies with arguments.
59+
/// Argument matching is based on compile-time types, so registering with `ConcreteType` and resolving with `any Protocol`
60+
/// (or the other way around) creates different registrations.
5961
///
6062
/// - Parameters:
6163
/// - type: Type of the dependency to register
@@ -88,6 +90,7 @@ open class Container: DependencyAutoregistering, DependencyResolving, Dependency
8890
/// Uses Swift parameter packs to support 1-3 arguments with a single method signature.
8991
/// If a dependency of the given type with the given arguments wasn't registered before this method call
9092
/// the method throws ``ResolutionError.dependencyNotRegistered``
93+
/// Argument matching is based on compile-time types, so `ConcreteType` and `any Protocol` are treated as different argument lists.
9194
///
9295
/// - Parameters:
9396
/// - type: Type of the dependency that should be resolved

Sources/Models/Async/AsyncRegistration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ struct AsyncRegistration: Sendable {
3333
asyncRegistrationFactory = { resolver, arg in
3434
guard let arguments = arg as? (repeat each Argument) else {
3535
throw ResolutionError.unmatchingArgumentType(
36-
message: "Registration of type \(registrationIdentifier.description) doesn't accept arguments of type \(Swift.type(of: arg))"
36+
message: "Registration of type \(registrationIdentifier.description) doesn't accept arguments of type \(runtimeTypeDescription(of: arg))"
3737
)
3838
}
3939

Sources/Models/RegistrationIdentifier.swift

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ enum RegistrationIdentifierConstant {
1616
struct RegistrationIdentifier: Sendable {
1717
let typeIdentifier: ObjectIdentifier
1818
let argumentIdentifiers: [ObjectIdentifier]
19+
let typeDescription: String
20+
let argumentTypeDescriptions: [String]
1921

2022
/// Number of argument types (used to enforce maximum at resolve time)
2123
var argumentCount: Int { argumentIdentifiers.count }
@@ -28,17 +30,23 @@ struct RegistrationIdentifier: Sendable {
2830
/// - argumentTypes: Variadic argument types using parameter packs. Only 1-3 arguments are supported. Entering more arguments will cause error in runtime.
2931
init<Dependency, each Argument>(type: Dependency.Type, argumentTypes _: repeat (each Argument).Type) {
3032
typeIdentifier = ObjectIdentifier(type)
33+
typeDescription = String(reflecting: type)
3134

3235
var identifiers: [ObjectIdentifier] = []
36+
var descriptions: [String] = []
3337
repeat identifiers.append(ObjectIdentifier((each Argument).self))
38+
repeat descriptions.append(String(reflecting: (each Argument).self))
3439

3540
argumentIdentifiers = identifiers
41+
argumentTypeDescriptions = descriptions
3642
}
3743

3844
/// Convenience initializer for dependencies without arguments
3945
init<Dependency>(type: Dependency.Type) {
4046
typeIdentifier = ObjectIdentifier(type)
47+
typeDescription = String(reflecting: type)
4148
argumentIdentifiers = []
49+
argumentTypeDescriptions = []
4250
}
4351
}
4452

@@ -48,14 +56,18 @@ extension RegistrationIdentifier: Hashable {}
4856
// MARK: Debug information
4957
extension RegistrationIdentifier: CustomStringConvertible {
5058
var description: String {
51-
let argumentsDescription: String = if argumentIdentifiers.isEmpty {
52-
"nil"
59+
if argumentTypeDescriptions.isEmpty {
60+
typeDescription
5361
} else {
54-
argumentIdentifiers.map(\.debugDescription).joined(separator: ", ")
62+
"\(typeDescription)(\(argumentTypeDescriptions.joined(separator: ", ")))"
5563
}
56-
return """
57-
Type: \(typeIdentifier.debugDescription)
58-
Arguments: \(argumentsDescription)
59-
"""
6064
}
6165
}
66+
67+
func runtimeTypeDescription(of value: Any?) -> String {
68+
guard let value else {
69+
return "nil"
70+
}
71+
72+
return String(reflecting: Swift.type(of: value))
73+
}

Sources/Models/Sync/Registration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ struct Registration {
3131
self.factory = { resolver, arg in
3232
guard let arguments = arg as? (repeat each Argument) else {
3333
throw ResolutionError.unmatchingArgumentType(
34-
message: "Registration of type \(registrationIdentifier.description) doesn't accept arguments of type \(Swift.type(of: arg))"
34+
message: "Registration of type \(registrationIdentifier.description) doesn't accept arguments of type \(runtimeTypeDescription(of: arg))"
3535
)
3636
}
3737

Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public protocol AsyncDependencyRegistering {
2929
/// The arguments are typically parameters in an initializer of the dependency that are not registered in the same resolver (i.e. container),
3030
/// therefore, they need to be passed in `resolve` call. This registration method doesn't have any scope parameter for a reason - the container
3131
/// should always return a new instance for dependencies with arguments.
32+
/// Argument matching is based on compile-time types, so `ConcreteType` and `any Protocol` are different registrations.
3233
///
3334
/// - Parameters:
3435
/// - type: Type of the dependency to register
@@ -53,6 +54,7 @@ public extension AsyncDependencyRegistering {
5354
/// The arguments are typically parameters in an initializer of the dependency that are not registered in the same resolver (i.e. container),
5455
/// therefore, they need to be passed in `resolve` call. This registration method doesn't have any scope parameter for a reason - the container
5556
/// should always return a new instance for dependencies with arguments.
57+
/// Argument matching is based on compile-time types, so `ConcreteType` and `any Protocol` are different registrations.
5658
///
5759
/// - Parameters:
5860
/// - factory: Closure that is called when the dependency is being resolved

Sources/Protocols/Registration/Sync/DependencyRegistering.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public protocol DependencyRegistering {
2929
/// The arguments are typically parameters in an initializer of the dependency that are not registered in the same resolver (i.e. container),
3030
/// therefore, they need to be passed in `resolve` call. This registration method doesn't have any scope parameter for a reason - the container
3131
/// should always return a new instance for dependencies with arguments.
32+
/// Argument matching is based on compile-time types, so `ConcreteType` and `any Protocol` are different registrations.
3233
///
3334
/// - Parameters:
3435
/// - type: Type of the dependency to register
@@ -53,6 +54,7 @@ public extension DependencyRegistering {
5354
/// The arguments are typically parameters in an initializer of the dependency that are not registered in the same resolver (i.e. container),
5455
/// therefore, they need to be passed in `resolve` call. This registration method doesn't have any scope parameter for a reason - the container
5556
/// should always return a new instance for dependencies with arguments.
57+
/// Argument matching is based on compile-time types, so `ConcreteType` and `any Protocol` are different registrations.
5658
///
5759
/// - Parameters:
5860
/// - factory: Closure that is called when the dependency is being resolved

Sources/Protocols/Resolution/Async/AsyncDependencyResolving.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public protocol AsyncDependencyResolving {
2222
/// Uses Swift parameter packs to support 1-3 arguments with a single method signature.
2323
/// If the container doesn't contain any registration for a dependency with the given type
2424
/// or if arguments of different types than expected are passed, ``ResolutionError`` is thrown
25+
/// Argument matching is based on compile-time types, so `ConcreteType` and `any Protocol` are treated as different argument lists.
2526
///
2627
/// - Parameters:
2728
/// - type: Type of the dependency that should be resolved
@@ -52,6 +53,7 @@ public extension AsyncDependencyResolving {
5253
/// Uses Swift parameter packs to support 1-3 arguments with a single method signature.
5354
/// If the container doesn't contain any registration for a dependency with the given type
5455
/// or if arguments of different types than expected are passed, a runtime error occurs
56+
/// Argument matching is based on compile-time types, so `ConcreteType` and `any Protocol` are treated as different argument lists.
5557
///
5658
/// - Parameters:
5759
/// - type: Type of the dependency that should be resolved
@@ -66,6 +68,7 @@ public extension AsyncDependencyResolving {
6668
/// Uses Swift parameter packs to support 1-3 arguments with a single method signature.
6769
/// If the container doesn't contain any registration for a dependency with the given type
6870
/// or if arguments of different types than expected are passed, a runtime error occurs
71+
/// Argument matching is based on compile-time types, so `ConcreteType` and `any Protocol` are treated as different argument lists.
6972
///
7073
/// - Parameters:
7174
/// - arguments: Arguments that will be passed as input parameters to the factory method (1-3 arguments supported)

0 commit comments

Comments
 (0)