Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion stitch/api/stitch.api
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public final class com/harrytmthy/stitch/api/Injector$DefaultImpls {
}

public final class com/harrytmthy/stitch/api/Module {
public fun <init> (ZLkotlin/jvm/functions/Function1;)V
public fun <init> (Z)V
public final fun define (Lkotlin/reflect/KClass;Lcom/harrytmthy/stitch/api/Qualifier;Lcom/harrytmthy/stitch/internal/DefinitionType;ZLjava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/harrytmthy/stitch/api/Bindable;
public final fun register ()V
}
Expand Down Expand Up @@ -66,6 +66,7 @@ public final class com/harrytmthy/stitch/api/QualifierKt {
public final class com/harrytmthy/stitch/api/ResolutionContext {
public final fun getComponent ()Lcom/harrytmthy/stitch/api/Component;
public final fun getScope ()Lcom/harrytmthy/stitch/api/Scope;
public final fun setScope (Lcom/harrytmthy/stitch/api/Scope;)V
}

public final class com/harrytmthy/stitch/api/Scope {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ class Component internal constructor() {
Registry.singletons[canonicalType]?.get(qualifierKey)?.let { return it as T }
}

// Build with cycle guard, cache under canonical key
val resolving = resolutionContext ?: ResolutionContext(this, owningScope)
// Build with cycle guard, cache under canonical key.
// Factory lambdas resolve in the context of the owning scope, not the requesting scope,
// so `scoped(activityScope) { A(get<B>()) }` resolves B within activityScope's visibility,
// not the child scope that triggered the request.
val resolving = resolutionContext?.apply { this.scope = owningScope }
?: ResolutionContext(this, owningScope)
resolving.enter(node)
try {
return when (node.definitionType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import kotlin.reflect.KClass
* @param forceEager When true, all singletons in this module are eagerly initialized on
* [Stitch.register], regardless of individual `eager` flags.
*/
class Module(private val forceEager: Boolean, onInit: Module.() -> Unit) {
class Module(private val forceEager: Boolean) {

private val definitions = HashSet<Node>()

Expand All @@ -50,10 +50,12 @@ class Module(private val forceEager: Boolean, onInit: Module.() -> Unit) {

private val eagerNodes = ArrayList<Node>()

init {
onInit(this)
}

/**
* Registers this module, making its bindings available for resolution.
*
* Equivalent to [Stitch.register] for a single module. Eager singletons are warmed up
* immediately after registration.
*/
fun register() {
for (node in definitions) {
Registry.definitions[node.type]?.let { nodeByQualifier ->
Expand Down Expand Up @@ -207,5 +209,5 @@ class Module(private val forceEager: Boolean, onInit: Module.() -> Unit) {
* @param forceEager When true, all singletons defined in this module are eagerly initialized
* on [Stitch.register].
*/
fun module(forceEager: Boolean = false, onInit: Module.() -> Unit): Module =
Module(forceEager, onInit)
inline fun module(forceEager: Boolean = false, crossinline onInit: Module.() -> Unit): Module =
Module(forceEager).also(onInit)
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import com.harrytmthy.stitch.internal.Node
*
* @throws com.harrytmthy.stitch.exception.CycleException if a dependency cycle is detected.
*/
class ResolutionContext internal constructor(val component: Component, val scope: Scope?) {
class ResolutionContext internal constructor(val component: Component, var scope: Scope?) {

private val stack = ArrayDeque<Node>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -913,4 +913,30 @@ class StitchTest {
val fragmentScopeInstance = fragmentScope.createScope().apply { open() }
assertFailsWith<MissingBindingException> { fragmentScopeInstance.get<Logger>() }
}

@Test
fun `get inside scoped binding definitions should resolve upstream`() {
val activityScope = scope("activity")
val fragmentScope = scope("fragment").dependsOn(activityScope)
module {
singleton { A(null) }
singleton { B(get()) }
scoped(activityScope) { A(get()) }
scoped(fragmentScope) { B(get()) }
scoped(fragmentScope) { C(get(), get()) }
}.register()

val activityScopeInstance = activityScope.createScope().apply { open() }
val fragmentScopeInstance = fragmentScope.createScope(activityScopeInstance)
.apply { open() }

val fragmentScopedA = fragmentScopeInstance.get<A>()
val singletonScopedA = Stitch.get<A>()
assertNotSame(fragmentScopedA, singletonScopedA)

val fragmentScopedC = fragmentScopeInstance.get<C>()
assertSame(fragmentScopedC.a, activityScopeInstance.get<A>())
assertSame(fragmentScopedC.b, fragmentScopeInstance.get<B>())
assertSame(activityScopeInstance.get<B>(), Stitch.get<B>())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ class LoadUseCase(val dao: Dao)

class NeedsMissing(val repo: Repo) // used to trigger MissingBindingException

class A(val b: B)
class A(val b: B?)
class B(val a: A) // used to trigger CycleException

class C(val a: A, val b: B)

class UsesLazyFactory(val barLazy: Lazy<Bar>)
class Bar

Expand Down
Loading