Skip to content

Commit

Permalink
Merge pull request #157 from square/ralf/properties
Browse files Browse the repository at this point in the history
Support property as providers in Dagger modules.
  • Loading branch information
vRallev authored Nov 12, 2020
2 parents a43c94f + fd5e6ab commit d70538e
Show file tree
Hide file tree
Showing 3 changed files with 418 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtNullableType
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtTypeArgumentList
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtUserType
Expand Down Expand Up @@ -355,15 +356,18 @@ internal fun ModuleDescriptor.findClassOrTypeAlias(

internal fun KtClassOrObject.functions(
includeCompanionObjects: Boolean
): List<KtNamedFunction> {
): List<KtNamedFunction> = classBodies(includeCompanionObjects).flatMap { it.functions }

internal fun KtClassOrObject.properties(
includeCompanionObjects: Boolean
): List<KtProperty> = classBodies(includeCompanionObjects).flatMap { it.properties }

private fun KtClassOrObject.classBodies(includeCompanionObjects: Boolean): List<KtClassBody> {
val elements = children.toMutableList()
if (includeCompanionObjects) {
elements += companionObjects.flatMap { it.children.toList() }
}

return elements
.filterIsInstance<KtClassBody>()
.flatMap { it.functions }
return elements.filterIsInstance<KtClassBody>()
}

fun KtTypeReference.isNullable(): Boolean = typeElement is KtNullableType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import com.squareup.anvil.compiler.codegen.addGeneratedByComment
import com.squareup.anvil.compiler.codegen.asArgumentList
import com.squareup.anvil.compiler.codegen.asClassName
import com.squareup.anvil.compiler.codegen.classesAndInnerClasses
import com.squareup.anvil.compiler.codegen.findAnnotation
import com.squareup.anvil.compiler.codegen.functions
import com.squareup.anvil.compiler.codegen.hasAnnotation
import com.squareup.anvil.compiler.codegen.isNullable
import com.squareup.anvil.compiler.codegen.mapToParameter
import com.squareup.anvil.compiler.codegen.properties
import com.squareup.anvil.compiler.codegen.requireFqName
import com.squareup.anvil.compiler.codegen.requireTypeName
import com.squareup.anvil.compiler.codegen.requireTypeReference
Expand All @@ -32,10 +34,11 @@ import com.squareup.kotlinpoet.jvm.jvmStatic
import dagger.internal.Factory
import dagger.internal.Preconditions
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.parents
import java.io.File
import java.util.Locale.US
Expand Down Expand Up @@ -73,6 +76,17 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() {
.forEach { function ->
generateFactoryClass(codeGenDir, module, clazz, function)
}

clazz
.properties(includeCompanionObjects = true)
.asSequence()
.filter { property ->
// Must be '@get:Provides'.
property.findAnnotation(daggerProvidesFqName)?.useSiteTarget?.text == "get"
}
.forEach { property ->
generateFactoryClass(codeGenDir, module, clazz, property)
}
}
}

Expand All @@ -81,30 +95,48 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() {
codeGenDir: File,
module: ModuleDescriptor,
clazz: KtClassOrObject,
function: KtNamedFunction
declaration: KtCallableDeclaration
): GeneratedFile {
val isCompanionObject = function.parents
val isCompanionObject = declaration.parents
.filterIsInstance<KtObjectDeclaration>()
.firstOrNull()
?.isCompanion()
?: false
val isObject = isCompanionObject || clazz is KtObjectDeclaration

val isProperty = declaration is KtProperty

val packageName = clazz.containingKtFile.packageFqName.asString()
val className = "${clazz.generateClassName()}_" +
(if (isCompanionObject) "Companion_" else "") +
"${function.requireFqName().shortName().asString().capitalize(US)}Factory"
val functionName = function.nameAsSafeName.asString()
val className = buildString {
append(clazz.generateClassName())
append('_')
if (isCompanionObject) {
append("Companion_")
}
if (isProperty) {
append("Get")
}
append(declaration.requireFqName().shortName().asString().capitalize(US))
append("Factory")
}

val parameters = function.valueParameters.mapToParameter(module)
val callableName = declaration.nameAsSafeName.asString()

val returnType = function.requireTypeReference().requireTypeName(module)
.withJvmSuppressWildcardsIfNeeded(function)
val returnTypeIsNullable = function.typeReference?.isNullable() ?: false
val parameters = declaration.valueParameters.mapToParameter(module)

val returnType = declaration.requireTypeReference().requireTypeName(module)
.withJvmSuppressWildcardsIfNeeded(declaration)
val returnTypeIsNullable = declaration.typeReference?.isNullable() ?: false

val factoryClass = ClassName(packageName, className)
val moduleClass = clazz.asClassName()

val byteCodeFunctionName = if (isProperty) {
"get" + callableName.capitalize(US)
} else {
callableName
}

val content = FileSpec.builder(packageName, className)
.apply {
val canGenerateAnObject = isObject && parameters.isEmpty()
Expand Down Expand Up @@ -158,7 +190,7 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() {
asProvider = true,
includeModule = !isObject
)
addStatement("return $functionName($argumentList)")
addStatement("return $byteCodeFunctionName($argumentList)")
}
.build()
)
Expand Down Expand Up @@ -191,7 +223,7 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() {
.build()
)
.addFunction(
FunSpec.builder(functionName)
FunSpec.builder(byteCodeFunctionName)
.jvmStatic()
.apply {
if (!isObject) {
Expand All @@ -204,30 +236,35 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() {
type = parameter.originalTypeName
)
}
val argumentsWithoutModule = parameters.joinToString { it.name }

val argumentsWithoutModule = if (isProperty) {
""
} else {
"(${parameters.joinToString { it.name }})"
}

when {
isObject && returnTypeIsNullable ->
addStatement(
"return %T.$functionName($argumentsWithoutModule)",
"return %T.$callableName$argumentsWithoutModule",
moduleClass
)
isObject && !returnTypeIsNullable ->
addStatement(
"return %T.checkNotNull(%T.$functionName" +
"($argumentsWithoutModule), %S)",
"return %T.checkNotNull(%T.$callableName" +
"$argumentsWithoutModule, %S)",
Preconditions::class,
moduleClass,
"Cannot return null from a non-@Nullable @Provides method"
)
!isObject && returnTypeIsNullable ->
addStatement(
"return module.$functionName($argumentsWithoutModule)"
"return module.$callableName$argumentsWithoutModule"
)
!isObject && !returnTypeIsNullable ->
addStatement(
"return %T.checkNotNull(module.$functionName" +
"($argumentsWithoutModule), %S)",
"return %T.checkNotNull(module.$callableName" +
"$argumentsWithoutModule, %S)",
Preconditions::class,
"Cannot return null from a non-@Nullable @Provides method"
)
Expand Down
Loading

0 comments on commit d70538e

Please sign in to comment.