Skip to content

Commit 43c856d

Browse files
authored
Merge pull request #361 from square/ralf/lazy-provider
Handle the special case of injecting a Provider<Lazy<Type>> properly.
2 parents 3a66dcd + 50e7218 commit 43c856d

File tree

3 files changed

+215
-11
lines changed

3 files changed

+215
-11
lines changed

compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/FqName.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.squareup.anvil.compiler.internal
22

3+
import dagger.Lazy
34
import dagger.Provides
45
import javax.inject.Inject
56
import javax.inject.Qualifier
@@ -8,4 +9,5 @@ internal val jvmSuppressWildcardsFqName = JvmSuppressWildcards::class.fqName
89
internal val publishedApiFqName = PublishedApi::class.fqName
910
internal val qualifierFqName = Qualifier::class.fqName
1011
internal val daggerProvidesFqName = Provides::class.fqName
12+
internal val daggerLazyFqName = Lazy::class.fqName
1113
internal val injectFqName = Inject::class.fqName

compiler/src/main/java/com/squareup/anvil/compiler/codegen/KotlinPoetUtils.kt

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
1515
import com.squareup.kotlinpoet.TypeName
1616
import com.squareup.kotlinpoet.asClassName
1717
import dagger.Lazy
18+
import dagger.internal.ProviderOfLazy
1819
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
1920
import org.jetbrains.kotlin.psi.KtCallableDeclaration
2021
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
2122
import org.jetbrains.kotlin.psi.KtTypeArgumentList
23+
import org.jetbrains.kotlin.psi.KtTypeElement
2224
import org.jetbrains.kotlin.psi.KtTypeProjection
2325
import org.jetbrains.kotlin.psi.KtTypeReference
2426
import org.jetbrains.kotlin.psi.KtValueArgument
@@ -31,6 +33,7 @@ internal data class Parameter(
3133
val lazyTypeName: ParameterizedTypeName,
3234
val isWrappedInProvider: Boolean,
3335
val isWrappedInLazy: Boolean,
36+
val isLazyWrappedInProvider: Boolean,
3437
val isAssisted: Boolean,
3538
val assistedIdentifier: String,
3639
val assistedParameterKey: AssistedParameterKey = AssistedParameterKey(
@@ -39,6 +42,7 @@ internal data class Parameter(
3942
)
4043
) {
4144
val originalTypeName: TypeName = when {
45+
isLazyWrappedInProvider -> lazyTypeName.wrapInProvider()
4246
isWrappedInProvider -> providerTypeName
4347
isWrappedInLazy -> lazyTypeName
4448
else -> typeName
@@ -52,6 +56,18 @@ internal data class Parameter(
5256
)
5357
}
5458

59+
private fun KtTypeElement.singleTypeArgument(): KtTypeReference {
60+
return children
61+
.filterIsInstance<KtTypeArgumentList>()
62+
.single()
63+
.children
64+
.filterIsInstance<KtTypeProjection>()
65+
.single()
66+
.children
67+
.filterIsInstance<KtTypeReference>()
68+
.single()
69+
}
70+
5571
internal fun List<KtCallableDeclaration>.mapToParameter(module: ModuleDescriptor): List<Parameter> =
5672
mapIndexed { index, parameter ->
5773
val typeElement = parameter.typeReference?.typeElement
@@ -60,21 +76,26 @@ internal fun List<KtCallableDeclaration>.mapToParameter(module: ModuleDescriptor
6076
val isWrappedInProvider = typeFqName == providerFqName
6177
val isWrappedInLazy = typeFqName == daggerLazyFqName
6278

79+
var isLazyWrappedInProvider = false
80+
6381
val typeName = when {
6482
parameter.requireTypeReference(module).isNullable() ->
6583
parameter.requireTypeReference(module).requireTypeName(module).copy(nullable = true)
6684

67-
isWrappedInProvider || isWrappedInLazy ->
68-
typeElement!!.children
69-
.filterIsInstance<KtTypeArgumentList>()
70-
.single()
71-
.children
72-
.filterIsInstance<KtTypeProjection>()
73-
.single()
74-
.children
75-
.filterIsInstance<KtTypeReference>()
76-
.single()
77-
.requireTypeName(module)
85+
isWrappedInProvider || isWrappedInLazy -> {
86+
val typeParameterReference = typeElement!!.singleTypeArgument()
87+
88+
if (isWrappedInProvider &&
89+
typeParameterReference.fqNameOrNull(module) == daggerLazyFqName
90+
) {
91+
// This is a super rare case when someone injects Provider<Lazy<Type>> that requires
92+
// special care.
93+
isLazyWrappedInProvider = true
94+
typeParameterReference.typeElement!!.singleTypeArgument().requireTypeName(module)
95+
} else {
96+
typeParameterReference.requireTypeName(module)
97+
}
98+
}
7899

79100
else -> parameter.requireTypeReference(module).requireTypeName(module)
80101
}.withJvmSuppressWildcardsIfNeeded(parameter, module)
@@ -97,6 +118,7 @@ internal fun List<KtCallableDeclaration>.mapToParameter(module: ModuleDescriptor
97118
lazyTypeName = typeName.wrapInLazy(),
98119
isWrappedInProvider = isWrappedInProvider,
99120
isWrappedInLazy = isWrappedInLazy,
121+
isLazyWrappedInProvider = isLazyWrappedInProvider,
100122
isAssisted = assistedAnnotation != null,
101123
assistedIdentifier = assistedIdentifier
102124
)
@@ -125,6 +147,8 @@ internal fun List<Parameter>.asArgumentList(
125147
if (asProvider) {
126148
list.map { parameter ->
127149
when {
150+
parameter.isLazyWrappedInProvider ->
151+
"${ProviderOfLazy::class.qualifiedName}.create(${parameter.name})"
128152
parameter.isWrappedInProvider -> parameter.name
129153
// Normally Dagger changes Lazy<Type> parameters to a Provider<Type> (usually the
130154
// container is a joined type), therefore we use `.lazy(..)` to convert the Provider

compiler/src/test/java/com/squareup/anvil/compiler/dagger/InjectConstructorFactoryGeneratorTest.kt

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.tschuchort.compiletesting.KotlinCompilation.ExitCode.OK
1111
import com.tschuchort.compiletesting.KotlinCompilation.Result
1212
import dagger.Lazy
1313
import dagger.internal.Factory
14+
import org.junit.Ignore
1415
import org.junit.Test
1516
import org.junit.runner.RunWith
1617
import org.junit.runners.Parameterized
@@ -288,6 +289,183 @@ public final class InjectClass_Factory implements Factory<InjectClass> {
288289
}
289290
}
290291

292+
@Test
293+
fun `a factory class is generated for an inject constructor with a lazy argument wrapped in a provider`() {
294+
/*
295+
package com.squareup.test;
296+
297+
import dagger.Lazy;
298+
import dagger.internal.DaggerGenerated;
299+
import dagger.internal.Factory;
300+
import dagger.internal.ProviderOfLazy;
301+
import javax.annotation.processing.Generated;
302+
import javax.inject.Provider;
303+
304+
@DaggerGenerated
305+
@Generated(
306+
value = "dagger.internal.codegen.ComponentProcessor",
307+
comments = "https://dagger.dev"
308+
)
309+
@SuppressWarnings({
310+
"unchecked",
311+
"rawtypes"
312+
})
313+
public final class InjectClass_Factory implements Factory<InjectClass> {
314+
private final Provider<String> stringProvider;
315+
316+
public InjectClass_Factory(Provider<String> stringProvider) {
317+
this.stringProvider = stringProvider;
318+
}
319+
320+
@Override
321+
public InjectClass get() {
322+
return newInstance(ProviderOfLazy.create(stringProvider));
323+
}
324+
325+
public static InjectClass_Factory create(Provider<String> stringProvider) {
326+
return new InjectClass_Factory(stringProvider);
327+
}
328+
329+
public static InjectClass newInstance(Provider<Lazy<String>> string) {
330+
return new InjectClass(string);
331+
}
332+
}
333+
*/
334+
335+
compile(
336+
"""
337+
package com.squareup.test
338+
339+
import dagger.Lazy
340+
import javax.inject.Inject
341+
import javax.inject.Provider
342+
343+
class InjectClass @Inject constructor(
344+
val string: Provider<Lazy<String>>
345+
) {
346+
override fun equals(other: Any?): Boolean {
347+
return toString() == other.toString()
348+
}
349+
override fun toString(): String {
350+
return string.get().get()
351+
}
352+
}
353+
"""
354+
) {
355+
val factoryClass = injectClass.factoryClass()
356+
357+
val constructor = factoryClass.declaredConstructors.single()
358+
assertThat(constructor.parameterTypes.toList())
359+
.containsExactly(Provider::class.java)
360+
361+
val staticMethods = factoryClass.declaredMethods.filter { it.isStatic }
362+
363+
val factoryInstance = staticMethods.single { it.name == "create" }
364+
.invoke(null, Provider { "a" })
365+
assertThat(factoryInstance::class.java).isEqualTo(factoryClass)
366+
367+
val newInstance = staticMethods.single { it.name == "newInstance" }
368+
.invoke(null, Provider { dagger.Lazy { "a" } })
369+
val getInstance = (factoryInstance as Factory<*>).get()
370+
371+
assertThat(newInstance).isNotNull()
372+
assertThat(getInstance).isNotNull()
373+
374+
assertThat(newInstance).isEqualTo(getInstance)
375+
assertThat(newInstance).isNotSameInstanceAs(getInstance)
376+
}
377+
}
378+
379+
@Test
380+
@Ignore("This test is broken with Dagger as well.")
381+
// Notice in the get() function Dagger creates a Lazy of a Provider of Provider instead of a
382+
// Lazy of a Provider.
383+
fun `a factory class is generated for an inject constructor with a provider argument wrapped in a lazy`() {
384+
/*
385+
package com.squareup.test;
386+
387+
import dagger.Lazy;
388+
import dagger.internal.DaggerGenerated;
389+
import dagger.internal.DoubleCheck;
390+
import dagger.internal.Factory;
391+
import javax.annotation.processing.Generated;
392+
import javax.inject.Provider;
393+
394+
@DaggerGenerated
395+
@Generated(
396+
value = "dagger.internal.codegen.ComponentProcessor",
397+
comments = "https://dagger.dev"
398+
)
399+
@SuppressWarnings({
400+
"unchecked",
401+
"rawtypes"
402+
})
403+
public final class InjectClass_Factory implements Factory<InjectClass> {
404+
private final Provider<Provider<String>> stringProvider;
405+
406+
public InjectClass_Factory(Provider<Provider<String>> stringProvider) {
407+
this.stringProvider = stringProvider;
408+
}
409+
410+
@Override
411+
public InjectClass get() {
412+
return newInstance(DoubleCheck.lazy(stringProvider));
413+
}
414+
415+
public static InjectClass_Factory create(Provider<Provider<String>> stringProvider) {
416+
return new InjectClass_Factory(stringProvider);
417+
}
418+
419+
public static InjectClass newInstance(Lazy<Provider<String>> string) {
420+
return new InjectClass(string);
421+
}
422+
}
423+
*/
424+
425+
compile(
426+
"""
427+
package com.squareup.test
428+
429+
import dagger.Lazy
430+
import javax.inject.Inject
431+
import javax.inject.Provider
432+
433+
class InjectClass @Inject constructor(
434+
val string: Lazy<Provider<String>>
435+
) {
436+
override fun equals(other: Any?): Boolean {
437+
return toString() == other.toString()
438+
}
439+
override fun toString(): String {
440+
return string.get().get()
441+
}
442+
}
443+
"""
444+
) {
445+
val factoryClass = injectClass.factoryClass()
446+
447+
val constructor = factoryClass.declaredConstructors.single()
448+
assertThat(constructor.parameterTypes.toList())
449+
.containsExactly(Provider::class.java)
450+
451+
val staticMethods = factoryClass.declaredMethods.filter { it.isStatic }
452+
453+
val factoryInstance = staticMethods.single { it.name == "create" }
454+
.invoke(null, Provider { "a" })
455+
assertThat(factoryInstance::class.java).isEqualTo(factoryClass)
456+
457+
val newInstance = staticMethods.single { it.name == "newInstance" }
458+
.invoke(null, dagger.Lazy { Provider { "a" } })
459+
val getInstance = (factoryInstance as Factory<*>).get()
460+
461+
assertThat(newInstance).isNotNull()
462+
assertThat(getInstance).isNotNull()
463+
464+
assertThat(newInstance).isEqualTo(getInstance)
465+
assertThat(newInstance).isNotSameInstanceAs(getInstance)
466+
}
467+
}
468+
291469
@Test fun `a factory class is generated for an inject constructor with star imports`() {
292470
/*
293471
package com.squareup.test;

0 commit comments

Comments
 (0)