com.squareup.anvil.compiler.ContributedBinding.kt Maven / Gradle / Ivy
package com.squareup.anvil.compiler
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.squareup.anvil.compiler.api.AnvilCompilationException
import com.squareup.anvil.compiler.codegen.ksp.KspAnvilException
import com.squareup.anvil.compiler.codegen.ksp.resolveKSClassDeclaration
import com.squareup.anvil.compiler.codegen.reference.AnvilCompilationExceptionClassReferenceIr
import com.squareup.anvil.compiler.codegen.reference.ClassReferenceIr
import com.squareup.anvil.compiler.codegen.reference.find
import com.squareup.anvil.compiler.internal.reference.AnnotationReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.ClassReference.Psi
import com.squareup.anvil.compiler.internal.requireFqName
import com.squareup.kotlinpoet.ksp.toTypeName
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
private typealias Scope = ClassReferenceIr
private typealias BindingModule = ClassReferenceIr
private typealias OriginClass = ClassReferenceIr
internal data class BindingKey(
val scope: Scope,
val boundType: ClassReferenceIr,
val qualifierKey: String,
)
/**
* A data structure for organizing all contributed bindings with layering down through scopes and
* binding keys.
*
* ```
*
* Contributed Bindings
*
* ┌──────────────────────────────────────────┐
* │ │
* │ Scope │
* │ │
* │ ┌──────────────────────────────────┐ │
* │ │ │ │
* │ │ BindingKey (type + qualifier) │ │
* │ │ │ │
* │ │ ┌────────────────────────┐ │ │
* │ │ │ │ │ │
* │ │ │ Prioritized bindings │ │ │
* │ │ │ │ │ │
* │ │ └────────────────────────┘ │ │
* │ │ │ │
* │ │ │ │
* │ └──────────────────────────────────┘ │
* │ │
* │ │
* └──────────────────────────────────────────┘
*
* ┌──────────────────────────────────────────┐
* │ │
* │ Scope2 │
* │ │
* │ ┌──────────────────────────────────┐ │
* │ │ │ │
* │ │ BindingKey (type + qualifier) │ │
* │ │ │ │
* │ │ ┌────────────────────────┐ │ │
* │ │ │ │ │ │
* │ │ │ Prioritized bindings │ │ │
* │ │ │ │ │ │
* │ │ └────────────────────────┘ │ │
* │ │ │ │
* │ │ │ │
* │ └──────────────────────────────────┘ │
* │ │
* │ │
* └──────────────────────────────────────────┘
* ```
*/
internal data class ContributedBindings(
val bindings: Map>>,
) {
companion object {
fun from(
bindings: List,
): ContributedBindings {
val groupedByScope = bindings.groupBy { it.scope }
val groupedByScopeAndKey = groupedByScope.mapValues { (_, bindings) ->
bindings.groupBy { it.bindingKey }
.mapValues innerMapValues@{ (_, bindings) ->
if (bindings.size < 2) return@innerMapValues bindings
val (multiBindings, bindingsOnly) = bindings.partition { it.isMultibinding }
if (bindingsOnly.size < 2) return@innerMapValues bindings
val highestPriorityBinding = bindingsOnly.findHighestPriorityBinding()
multiBindings + listOf(highestPriorityBinding)
}
}
return ContributedBindings(groupedByScopeAndKey)
}
}
}
internal data class ContributedBinding(
val scope: Scope,
val isMultibinding: Boolean,
val bindingModule: BindingModule,
val originClass: OriginClass,
val boundType: ClassReferenceIr,
val qualifierKey: String,
val rank: Int,
) {
val bindingKey = BindingKey(scope, boundType, qualifierKey)
val replaces = bindingModule.annotations.find(contributesToFqName).single()
.replacedClasses
}
internal fun List.findHighestPriorityBinding(): ContributedBinding {
if (size == 1) return this[0]
val bindings = groupBy { it.rank }
.toSortedMap()
.let { it.getValue(it.lastKey()) }
.distinctBy { it.originClass }
if (bindings.size > 1) {
val rankName = bindings[0].rank.toString()
throw AnvilCompilationExceptionClassReferenceIr(
bindings[0].boundType,
"There are multiple contributed bindings with the same bound type and rank. The bound type is " +
"${bindings[0].boundType.fqName.asString()}. The rank is $rankName. " +
"The contributed binding classes are: " +
bindings.joinToString(
prefix = "[",
postfix = "]",
) { it.originClass.fqName.asString() },
)
}
return bindings[0]
}
private fun genericExceptionText(
origin: String,
boundType: String,
typeString: String,
): String {
return "Class $origin binds $boundType," +
" but the bound type contains type parameter(s) $typeString." +
" Type parameters in bindings are not supported. This binding needs" +
" to be contributed in a Dagger module manually."
}
internal fun ClassReference.checkNotGeneric(
contributedClass: ClassReference,
) {
fun KotlinType.describeTypeParameters(): String = arguments
.ifEmpty { return "" }
.joinToString(prefix = "<", postfix = ">") { typeArgument ->
typeArgument.type.toString() + typeArgument.type.describeTypeParameters()
}
when (this) {
is Descriptor -> {
if (clazz.declaredTypeParameters.isNotEmpty()) {
throw AnvilCompilationException(
classDescriptor = clazz,
message = genericExceptionText(
contributedClass.fqName.asString(),
clazz.fqNameSafe.asString(),
clazz.defaultType.describeTypeParameters(),
),
)
}
}
is Psi -> {
if (clazz.typeParameters.isNotEmpty()) {
val typeString = clazz.typeParameters
.joinToString(prefix = "<", postfix = ">") { it.name!! }
throw AnvilCompilationException(
message = genericExceptionText(
contributedClass.fqName.asString(),
clazz.requireFqName().asString(),
typeString,
),
element = clazz.nameIdentifier,
)
}
}
}
}
internal fun AnnotationReference.qualifierKey(): String {
return fqName.asString() +
arguments.joinToString(separator = "") { argument ->
val valueString = when (val value = argument.value()) {
is ClassReference -> value.fqName.asString()
// TODO what if it's another annotation?
else -> value.toString()
}
argument.resolvedName + valueString
}
}
internal fun KSClassDeclaration.checkNotGeneric(
contributedClass: KSClassDeclaration,
) {
if (typeParameters.isNotEmpty()) {
val typeString = typeParameters
.joinToString(prefix = "<", postfix = ">") { it.name.asString() }
throw KspAnvilException(
message = genericExceptionText(
contributedClass.qualifiedName!!.asString(),
qualifiedName!!.asString(),
typeString,
),
node = contributedClass,
)
}
}
internal fun KSAnnotation.qualifierKey(): String {
return annotationType.resolve().toTypeName().toString() +
arguments.joinToString(separator = "") { argument ->
val valueString = when (val value = argument.value) {
is KSType -> value.resolveKSClassDeclaration()!!.qualifiedName!!.asString()
// TODO what if it's another annotation?
else -> value.toString()
}
argument.name!!.asString() + valueString
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy