From 67e6d27485d6d1503ee66bf7f00f9945a551a44b Mon Sep 17 00:00:00 2001 From: Bobby Sudekum Date: Sun, 15 Feb 2026 11:18:48 +0100 Subject: [PATCH] Fix .onPreview/.onTest not working in Xcode previews Two issues prevented context factories from resolving in previews: 1. Registration and resolution of preview/test contexts were gated behind #if DEBUG. Since isPreview and isTest are runtime checks (environment variables), they don't need compile-time guards. Removed #if DEBUG for preview/test; kept it for debug context. 2. Xcode previews dynamically recompile and inject code, causing nominal types (TypedFactory) to have different type identity across module boundaries. The as? TypedFactory cast silently fails, so context factories are ignored and the default factory runs instead (often fatalError). Fix: Added untypedFactory property to AnyFactory that exposes the closure as Any. When the nominal struct cast fails, resolution falls back to casting the closure using structural function types (@Sendable (P) -> T), which are identity-independent in Swift. --- Sources/FactoryKit/FactoryKit/Modifiers.swift | 2 -- .../FactoryKit/FactoryKit/Registrations.swift | 17 ++++++++++++++--- Sources/FactoryKit/FactoryKit/Resolver.swift | 2 ++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Sources/FactoryKit/FactoryKit/Modifiers.swift b/Sources/FactoryKit/FactoryKit/Modifiers.swift index ac4ec3a3..8097ee6d 100644 --- a/Sources/FactoryKit/FactoryKit/Modifiers.swift +++ b/Sources/FactoryKit/FactoryKit/Modifiers.swift @@ -175,9 +175,7 @@ extension FactoryModifying { case .arg, .args, .device, .simulator: registration.context(context, key: registration.key, factory: factory) default: - #if DEBUG registration.context(context, key: registration.key, factory: factory) - #endif break } } diff --git a/Sources/FactoryKit/FactoryKit/Registrations.swift b/Sources/FactoryKit/FactoryKit/Registrations.swift index 06f5027b..33b16f15 100644 --- a/Sources/FactoryKit/FactoryKit/Registrations.swift +++ b/Sources/FactoryKit/FactoryKit/Registrations.swift @@ -86,11 +86,21 @@ public struct FactoryRegistration: Sendable { traceNewType = "O" // .onTest, .onDebug, etc. #endif current = found.factory + } else if let found = options?.factoryForCurrentContext()?.untypedFactory as? @Sendable (P) -> T { + #if DEBUG + traceNewType = "O" // .onTest, .onDebug, etc. (cross-module) + #endif + current = found } else if let found = manager.registrations[key] as? TypedFactory { #if DEBUG traceNewType = "R" // .register {} #endif current = found.factory + } else if let found = manager.registrations[key]?.untypedFactory as? @Sendable (P) -> T { + #if DEBUG + traceNewType = "R" // .register {} (cross-module) + #endif + current = found } else { #if DEBUG traceNewType = "F" // Factory { ... } @@ -341,14 +351,12 @@ extension FactoryOptions { } } if let contexts = contexts, !contexts.isEmpty { - #if DEBUG if FactoryContext.current.isPreview, let found = contexts["preview"] { return found } if FactoryContext.current.isTest, let found = contexts["test"] { return found } - #endif if FactoryContext.current.isSimulator, let found = contexts["simulator"] { return found } @@ -378,8 +386,11 @@ internal struct FactoryDebugInformation { #endif // Internal Factory type -internal protocol AnyFactory {} +internal protocol AnyFactory { + var untypedFactory: Any { get } +} internal struct TypedFactory: AnyFactory { let factory: @Sendable (P) -> T + var untypedFactory: Any { factory } } diff --git a/Sources/FactoryKit/FactoryKit/Resolver.swift b/Sources/FactoryKit/FactoryKit/Resolver.swift index 7fda0774..c2fb07d4 100644 --- a/Sources/FactoryKit/FactoryKit/Resolver.swift +++ b/Sources/FactoryKit/FactoryKit/Resolver.swift @@ -69,6 +69,8 @@ extension Resolving { let key = FactoryKey(type: T.self, key: globalResolverKey) if let factory = manager.registrations[key] as? TypedFactory { return Factory(FactoryRegistration(key: globalResolverKey, container: self, factory: factory.factory)) + } else if let factory = manager.registrations[key]?.untypedFactory as? @Sendable (Void) -> T { + return Factory(FactoryRegistration(key: globalResolverKey, container: self, factory: factory)) } // otherwise return nil return nil