Skip to content

Commit

Permalink
Merge pull request #361 from square/ralf/lazy-provider
Browse files Browse the repository at this point in the history
Handle the special case of injecting a Provider<Lazy<Type>> properly.
  • Loading branch information
vRallev authored Aug 27, 2021
2 parents 3a66dcd + 50e7218 commit 43c856d
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.squareup.anvil.compiler.internal

import dagger.Lazy
import dagger.Provides
import javax.inject.Inject
import javax.inject.Qualifier
Expand All @@ -8,4 +9,5 @@ internal val jvmSuppressWildcardsFqName = JvmSuppressWildcards::class.fqName
internal val publishedApiFqName = PublishedApi::class.fqName
internal val qualifierFqName = Qualifier::class.fqName
internal val daggerProvidesFqName = Provides::class.fqName
internal val daggerLazyFqName = Lazy::class.fqName
internal val injectFqName = Inject::class.fqName
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.asClassName
import dagger.Lazy
import dagger.internal.ProviderOfLazy
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.KtTypeArgumentList
import org.jetbrains.kotlin.psi.KtTypeElement
import org.jetbrains.kotlin.psi.KtTypeProjection
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtValueArgument
Expand All @@ -31,6 +33,7 @@ internal data class Parameter(
val lazyTypeName: ParameterizedTypeName,
val isWrappedInProvider: Boolean,
val isWrappedInLazy: Boolean,
val isLazyWrappedInProvider: Boolean,
val isAssisted: Boolean,
val assistedIdentifier: String,
val assistedParameterKey: AssistedParameterKey = AssistedParameterKey(
Expand All @@ -39,6 +42,7 @@ internal data class Parameter(
)
) {
val originalTypeName: TypeName = when {
isLazyWrappedInProvider -> lazyTypeName.wrapInProvider()
isWrappedInProvider -> providerTypeName
isWrappedInLazy -> lazyTypeName
else -> typeName
Expand All @@ -52,6 +56,18 @@ internal data class Parameter(
)
}

private fun KtTypeElement.singleTypeArgument(): KtTypeReference {
return children
.filterIsInstance<KtTypeArgumentList>()
.single()
.children
.filterIsInstance<KtTypeProjection>()
.single()
.children
.filterIsInstance<KtTypeReference>()
.single()
}

internal fun List<KtCallableDeclaration>.mapToParameter(module: ModuleDescriptor): List<Parameter> =
mapIndexed { index, parameter ->
val typeElement = parameter.typeReference?.typeElement
Expand All @@ -60,21 +76,26 @@ internal fun List<KtCallableDeclaration>.mapToParameter(module: ModuleDescriptor
val isWrappedInProvider = typeFqName == providerFqName
val isWrappedInLazy = typeFqName == daggerLazyFqName

var isLazyWrappedInProvider = false

val typeName = when {
parameter.requireTypeReference(module).isNullable() ->
parameter.requireTypeReference(module).requireTypeName(module).copy(nullable = true)

isWrappedInProvider || isWrappedInLazy ->
typeElement!!.children
.filterIsInstance<KtTypeArgumentList>()
.single()
.children
.filterIsInstance<KtTypeProjection>()
.single()
.children
.filterIsInstance<KtTypeReference>()
.single()
.requireTypeName(module)
isWrappedInProvider || isWrappedInLazy -> {
val typeParameterReference = typeElement!!.singleTypeArgument()

if (isWrappedInProvider &&
typeParameterReference.fqNameOrNull(module) == daggerLazyFqName
) {
// This is a super rare case when someone injects Provider<Lazy<Type>> that requires
// special care.
isLazyWrappedInProvider = true
typeParameterReference.typeElement!!.singleTypeArgument().requireTypeName(module)
} else {
typeParameterReference.requireTypeName(module)
}
}

else -> parameter.requireTypeReference(module).requireTypeName(module)
}.withJvmSuppressWildcardsIfNeeded(parameter, module)
Expand All @@ -97,6 +118,7 @@ internal fun List<KtCallableDeclaration>.mapToParameter(module: ModuleDescriptor
lazyTypeName = typeName.wrapInLazy(),
isWrappedInProvider = isWrappedInProvider,
isWrappedInLazy = isWrappedInLazy,
isLazyWrappedInProvider = isLazyWrappedInProvider,
isAssisted = assistedAnnotation != null,
assistedIdentifier = assistedIdentifier
)
Expand Down Expand Up @@ -125,6 +147,8 @@ internal fun List<Parameter>.asArgumentList(
if (asProvider) {
list.map { parameter ->
when {
parameter.isLazyWrappedInProvider ->
"${ProviderOfLazy::class.qualifiedName}.create(${parameter.name})"
parameter.isWrappedInProvider -> parameter.name
// Normally Dagger changes Lazy<Type> parameters to a Provider<Type> (usually the
// container is a joined type), therefore we use `.lazy(..)` to convert the Provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.tschuchort.compiletesting.KotlinCompilation.ExitCode.OK
import com.tschuchort.compiletesting.KotlinCompilation.Result
import dagger.Lazy
import dagger.internal.Factory
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
Expand Down Expand Up @@ -288,6 +289,183 @@ public final class InjectClass_Factory implements Factory<InjectClass> {
}
}

@Test
fun `a factory class is generated for an inject constructor with a lazy argument wrapped in a provider`() {
/*
package com.squareup.test;
import dagger.Lazy;
import dagger.internal.DaggerGenerated;
import dagger.internal.Factory;
import dagger.internal.ProviderOfLazy;
import javax.annotation.processing.Generated;
import javax.inject.Provider;
@DaggerGenerated
@Generated(
value = "dagger.internal.codegen.ComponentProcessor",
comments = "https://dagger.dev"
)
@SuppressWarnings({
"unchecked",
"rawtypes"
})
public final class InjectClass_Factory implements Factory<InjectClass> {
private final Provider<String> stringProvider;
public InjectClass_Factory(Provider<String> stringProvider) {
this.stringProvider = stringProvider;
}
@Override
public InjectClass get() {
return newInstance(ProviderOfLazy.create(stringProvider));
}
public static InjectClass_Factory create(Provider<String> stringProvider) {
return new InjectClass_Factory(stringProvider);
}
public static InjectClass newInstance(Provider<Lazy<String>> string) {
return new InjectClass(string);
}
}
*/

compile(
"""
package com.squareup.test
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Provider
class InjectClass @Inject constructor(
val string: Provider<Lazy<String>>
) {
override fun equals(other: Any?): Boolean {
return toString() == other.toString()
}
override fun toString(): String {
return string.get().get()
}
}
"""
) {
val factoryClass = injectClass.factoryClass()

val constructor = factoryClass.declaredConstructors.single()
assertThat(constructor.parameterTypes.toList())
.containsExactly(Provider::class.java)

val staticMethods = factoryClass.declaredMethods.filter { it.isStatic }

val factoryInstance = staticMethods.single { it.name == "create" }
.invoke(null, Provider { "a" })
assertThat(factoryInstance::class.java).isEqualTo(factoryClass)

val newInstance = staticMethods.single { it.name == "newInstance" }
.invoke(null, Provider { dagger.Lazy { "a" } })
val getInstance = (factoryInstance as Factory<*>).get()

assertThat(newInstance).isNotNull()
assertThat(getInstance).isNotNull()

assertThat(newInstance).isEqualTo(getInstance)
assertThat(newInstance).isNotSameInstanceAs(getInstance)
}
}

@Test
@Ignore("This test is broken with Dagger as well.")
// Notice in the get() function Dagger creates a Lazy of a Provider of Provider instead of a
// Lazy of a Provider.
fun `a factory class is generated for an inject constructor with a provider argument wrapped in a lazy`() {
/*
package com.squareup.test;
import dagger.Lazy;
import dagger.internal.DaggerGenerated;
import dagger.internal.DoubleCheck;
import dagger.internal.Factory;
import javax.annotation.processing.Generated;
import javax.inject.Provider;
@DaggerGenerated
@Generated(
value = "dagger.internal.codegen.ComponentProcessor",
comments = "https://dagger.dev"
)
@SuppressWarnings({
"unchecked",
"rawtypes"
})
public final class InjectClass_Factory implements Factory<InjectClass> {
private final Provider<Provider<String>> stringProvider;
public InjectClass_Factory(Provider<Provider<String>> stringProvider) {
this.stringProvider = stringProvider;
}
@Override
public InjectClass get() {
return newInstance(DoubleCheck.lazy(stringProvider));
}
public static InjectClass_Factory create(Provider<Provider<String>> stringProvider) {
return new InjectClass_Factory(stringProvider);
}
public static InjectClass newInstance(Lazy<Provider<String>> string) {
return new InjectClass(string);
}
}
*/

compile(
"""
package com.squareup.test
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Provider
class InjectClass @Inject constructor(
val string: Lazy<Provider<String>>
) {
override fun equals(other: Any?): Boolean {
return toString() == other.toString()
}
override fun toString(): String {
return string.get().get()
}
}
"""
) {
val factoryClass = injectClass.factoryClass()

val constructor = factoryClass.declaredConstructors.single()
assertThat(constructor.parameterTypes.toList())
.containsExactly(Provider::class.java)

val staticMethods = factoryClass.declaredMethods.filter { it.isStatic }

val factoryInstance = staticMethods.single { it.name == "create" }
.invoke(null, Provider { "a" })
assertThat(factoryInstance::class.java).isEqualTo(factoryClass)

val newInstance = staticMethods.single { it.name == "newInstance" }
.invoke(null, dagger.Lazy { Provider { "a" } })
val getInstance = (factoryInstance as Factory<*>).get()

assertThat(newInstance).isNotNull()
assertThat(getInstance).isNotNull()

assertThat(newInstance).isEqualTo(getInstance)
assertThat(newInstance).isNotSameInstanceAs(getInstance)
}
}

@Test fun `a factory class is generated for an inject constructor with star imports`() {
/*
package com.squareup.test;
Expand Down

0 comments on commit 43c856d

Please sign in to comment.