forked from square/dagger
-
Notifications
You must be signed in to change notification settings - Fork 2k
Open
Labels
Description
Below is a test case which we were expecting to pass. In essence we were expecting that the implementation of this method
@Provides
static Map<String, Lazy<? extends X>> xs(Lazy<X1> x1, Lazy<X2> x2) {
return ImmutableMap.of("x1", x1, "x2", x2);
}transformed into Dagger would look like this:
@Binds @IntoMap @StringKey("x1") Lazy<? extends X> x1(Lazy<X1> impl);
@Binds @IntoMap @StringKey("x2") Lazy<? extends X> x2(Lazy<X2> impl);however they behave differently.
In my reading asking for a Lazy<T> as an @Inject field, @Provides parameter, or @Binds parameter should behave the same way. Currently @Binds is an outlier. The problem is somewhere near DaggerLazyTest_CIntoMap.getMapOfStringAndLazyOf calling .get(), but the providers are initialize()d with DoubleCheck.provider and not DoubleCheck.lazy or ProviderOfLazy.
dependencies {
testImplementation 'com.google.dagger:dagger:2.15'
testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.15'
}import javax.inject.*;
import dagger.*;
import dagger.multibindings.*;
import org.junit.*;
import static org.junit.Assert.*;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
public class LazyTest {
//@formatter:off
@Scope @interface MyScope {}
interface X {}
static class X1 implements X {
static int ctorCalled;
@Inject X1() { ctorCalled++; }
}
static class X2 implements X {
static int ctorCalled;
@Inject X2() { ctorCalled++; }
}
private void assertX1NotInstantiated() { assertEquals(beforeX1Count, X1.ctorCalled); }
private void assertX2NotInstantiated() { assertEquals(beforeX2Count, X2.ctorCalled); }
private void assertX1Instantiated() { assertEquals(beforeX1Count + 1, X1.ctorCalled); }
private void assertX2Instantiated() { assertEquals(beforeX2Count + 1, X2.ctorCalled); }
//@formatter:on
private int beforeX1Count;
private int beforeX2Count;
@Before
public void setUp() {
beforeX1Count = X1.ctorCalled;
beforeX2Count = X2.ctorCalled;
}
/**
* Use a Lazy directly in implementation: the consumer needs to reference implementations directly.
*/
@MyScope
@Component interface CAtUsage {
void inject(Use x);
class Use {
@Inject Lazy<X1> x1;
@Inject Lazy<X2> x2;
}
}
@Test
public void instantiatedOnDemand_lazyInjectedAtUsage() {
assertX1NotInstantiated();
assertX2NotInstantiated();
CAtUsage comp = DaggerLazyTest_CAtUsage.create();
assertX1NotInstantiated();
assertX2NotInstantiated();
CAtUsage.Use use = new CAtUsage.Use();
comp.inject(use);
assertX1NotInstantiated();
assertX2NotInstantiated();
X x1Instance1 = use.x1.get();
assertX1Instantiated();
assertX2NotInstantiated();
X x1Instance2 = use.x1.get();
assertX1Instantiated();
assertX2NotInstantiated();
assertSame(x1Instance1, x1Instance2);
X x2Instance1 = use.x2.get();
assertX1Instantiated();
assertX2Instantiated();
X x2Instance2 = use.x2.get();
assertX1Instantiated();
assertX2Instantiated();
assertSame(x2Instance1, x2Instance2);
}
/**
* Use a Lazy indirectly in module: the consumer only knows about interface.
*/
@MyScope
@Component(modules = CInModule.M.class) interface CInModule {
void inject(Use x);
class Use {
@Inject Map<String, Lazy<? extends X>> xs;
}
@Module
abstract class M {
@Provides
@MyScope
static Map<String, Lazy<? extends X>> xs(Lazy<X1> x1, Lazy<X2> x2) {
return ImmutableMap.of("x1", x1, "x2", x2);
}
}
}
@Test
public void instantiatedOnDemand_lazyInjectedInModule() {
assertX1NotInstantiated();
assertX2NotInstantiated();
CInModule comp = DaggerLazyTest_CInModule.create();
assertX1NotInstantiated();
assertX2NotInstantiated();
CInModule.Use use = new CInModule.Use();
comp.inject(use);
assertX1NotInstantiated();
assertX2NotInstantiated();
Lazy<? extends X> x1Lazy = use.xs.get("x1");
assertX1NotInstantiated();
assertX2NotInstantiated();
X x1Instance1 = x1Lazy.get();
assertX1Instantiated();
assertX2NotInstantiated();
X x1Instance2 = x1Lazy.get();
assertX1Instantiated();
assertX2NotInstantiated();
assertSame(x1Instance1, x1Instance2);
Lazy<? extends X> x2Lazy = use.xs.get("x2");
assertX1Instantiated();
assertX2NotInstantiated();
X x2Instance1 = x2Lazy.get();
assertX1Instantiated();
assertX2Instantiated();
X x2Instance2 = x2Lazy.get();
assertX1Instantiated();
assertX2Instantiated();
assertSame(x2Instance1, x2Instance2);
}
/**
* Same as {@link CInModule}, but using Dagger magic ({@link IntoMap}).
*/
@MyScope
@Component(modules = CIntoMap.M.class) interface CIntoMap {
void inject(Use x);
class Use {
@Inject Map<String, Lazy<? extends X>> xs;
}
@Module interface M {
@MyScope
@Binds @IntoMap @StringKey("x1")
Lazy<? extends X> x1(Lazy<X1> impl);
@MyScope
@Binds @IntoMap @StringKey("x2")
Lazy<? extends X> x2(Lazy<X2> impl);
}
}
@Test
public void instantiatedOnDemand_injectedWithIntoMap() {
assertX1NotInstantiated();
assertX2NotInstantiated();
CIntoMap comp = DaggerLazyTest_CIntoMap.create();
assertX1NotInstantiated();
assertX2NotInstantiated();
CIntoMap.Use use = new CIntoMap.Use();
comp.inject(use);
assertX1NotInstantiated(); // fails here
assertX2NotInstantiated();
Lazy<? extends X> x1Lazy = use.xs.get("x1");
assertX1NotInstantiated();
assertX2NotInstantiated();
X x1Instance1 = x1Lazy.get();
assertX1Instantiated();
assertX2NotInstantiated();
X x1Instance2 = x1Lazy.get();
assertX1Instantiated();
assertX2NotInstantiated();
assertSame(x1Instance1, x1Instance2);
Lazy<? extends X> x2Lazy = use.xs.get("x2");
assertX1Instantiated();
assertX2NotInstantiated();
X x2Instance1 = x2Lazy.get();
assertX1Instantiated();
assertX2Instantiated();
X x2Instance2 = x2Lazy.get();
assertX1Instantiated();
assertX2Instantiated();
assertSame(x2Instance1, x2Instance2);
}
/**
* Blind stab at working around the current behavior and fixing {@link CIntoMap} by wrapping in a provider.
*/
@MyScope
@Component(modules = CProviderIntoMap.M.class) interface CProviderIntoMap {
void inject(Use x);
class Use {
// FIXME doesn't compile:
// error: java.util.Map<java.lang.String,javax.inject.Provider<? extends dagger.Lazy<? extends com.thetrainline.tech.LazyTest.X>>>
// cannot be provided without an @Provides- or @Produces-annotated method.
// java.util.Map<java.lang.String,javax.inject.Provider<? extends dagger.Lazy<? extends com.thetrainline.tech.LazyTest.X>>>
// is injected at
// com.thetrainline.tech.LazyTest.CProviderIntoMap.Use.xs
// com.thetrainline.tech.LazyTest.CProviderIntoMap.Use is injected at
// com.thetrainline.tech.LazyTest.CProviderIntoMap.inject(x)
// @Inject Map<String, Provider<? extends Lazy<? extends X>>> xs;
}
@Module interface M {
@MyScope
@Binds @IntoMap @StringKey("x1")
Provider<? extends Lazy<? extends X>> x1(Provider<? extends Lazy<X1>> impl);
@MyScope
@Binds @IntoMap @StringKey("x2")
Provider<? extends Lazy<? extends X>> x2(Provider<? extends Lazy<X2>> impl);
}
}
}Potentially related: #218