Skip to content

Commit

Permalink
consider ignoreQualifier arguments when checking for duplicate bind…
Browse files Browse the repository at this point in the history
…ings

fixes #986
  • Loading branch information
RBusarow committed Jul 16, 2024
1 parent dacb051 commit 2f9061f
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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

Expand Down Expand Up @@ -223,7 +224,7 @@ internal fun KSClassDeclaration.checkNotGeneric(
}

internal fun KSAnnotation.qualifierKey(): String {
return shortName.asString() +
return annotationType.resolve().toTypeName().toString() +
arguments.joinToString(separator = "") { argument ->
val valueString = when (val value = argument.value) {
is KSType -> value.resolveKSClassDeclaration()!!.qualifiedName!!.asString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.squareup.anvil.compiler.internal.reference.argumentAt
import com.squareup.anvil.compiler.mergeComponentFqName
import com.squareup.anvil.compiler.mergeModulesFqName
import com.squareup.anvil.compiler.mergeSubcomponentFqName
import com.squareup.anvil.compiler.qualifierKey
import org.jetbrains.kotlin.name.FqName

internal fun AnnotationReference.parentScope(): ClassReference {
Expand Down Expand Up @@ -135,7 +136,14 @@ internal fun <T : AnnotationReference> List<T>.checkNoDuplicateScopeAndBoundType
if (size < 2) return
if (size == 2 && this[0].scope() != this[1].scope()) return

val duplicateScopes = groupBy { it.scope() }
val clazz = this[0].declaringClass()
val qualifierKey = clazz.qualifierAnnotation()?.qualifierKey()

val duplicateScopes = groupBy { annotation ->
// If there's a qualifier and this annotation isn't using `ignoreQualifier`,
// we need to include that qualifier in the duplicate check.
annotation.scope() to qualifierKey?.takeIf { !annotation.ignoreQualifier() }
}
.filterValues { it.size > 1 }
.ifEmpty { return }

Expand All @@ -145,7 +153,6 @@ internal fun <T : AnnotationReference> List<T>.checkNoDuplicateScopeAndBoundType
.ifEmpty { return }
.keys

val clazz = this[0].declaringClass()
throw AnvilCompilationExceptionClassReference(
classReference = clazz,
message = "${clazz.fqName} contributes multiple times to the same scope using the same " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ internal sealed class Contribution {
originType = origin,
boundType = boundType,
scopeType = scope,
qualifierKeyOrNull = qualifier?.key,
suffix = bindingModuleNameSuffix,
)
}
Expand Down Expand Up @@ -204,15 +205,23 @@ internal sealed class Contribution {
originType: ClassName,
boundType: ClassName,
scopeType: ClassName,
qualifierKeyOrNull: String?,
suffix: String,
): ClassName {
val types = listOf(originType, boundType, scopeType)

return ClassName(
packageName = originType.packageName,
simpleNames = types.map { it.simpleName } + suffix,
)
.joinSimpleNamesAndTruncate(
hashParams = types + suffix,
hashParams = listOfNotNull(
originType,
boundType,
scopeType,
qualifierKeyOrNull,
suffix,
),
separator = "_",
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.google.devtools.ksp.symbol.KSValueArgument
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.compiler.internal.mapKeyFqName
import com.squareup.anvil.compiler.qualifierFqName
import com.squareup.anvil.compiler.qualifierKey
import com.squareup.kotlinpoet.ksp.toClassName
import org.jetbrains.kotlin.name.FqName

Expand Down Expand Up @@ -53,7 +54,13 @@ internal fun <T : KSAnnotation> List<T>.checkNoDuplicateScopeAndBoundType(
if (size < 2) return
if (size == 2 && this[0].scope() != this[1].scope()) return

val duplicateScopes = groupBy { it.scope() }
val qualifierKey = annotatedType.qualifierAnnotation()?.qualifierKey()

val duplicateScopes = groupBy { annotation ->
// If there's a qualifier and this annotation isn't using `ignoreQualifier`,
// we need to include that qualifier in the duplicate check.
annotation.scope() to qualifierKey?.takeIf { !annotation.ignoreQualifier() }
}
.filterValues { it.size > 1 }
.ifEmpty { return }

Expand Down
37 changes: 37 additions & 0 deletions compiler/src/test/java/com/squareup/anvil/compiler/TestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import org.intellij.lang.annotations.Language
import org.junit.Assume.assumeTrue
import java.io.File
import kotlin.reflect.KClass
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.test.fail

internal fun compile(
Expand Down Expand Up @@ -194,13 +197,47 @@ private fun Class<*>.generatedBindingModules(
originType = kotlin.asClassName(),
boundType = boundType,
scopeType = scope,
qualifierKeyOrNull = qualifierKey(bindingAnnotation),
suffix = suffix,
)

classLoader.loadClass(typeName.toString())
}
}

private fun Class<*>.qualifierKey(bindingAnnotation: Annotation): String? {

val ignoreQualifier = bindingAnnotation::class.memberProperties
.firstOrNull { it.name == "ignoreQualifier" }
?.call(bindingAnnotation)

if (ignoreQualifier == true) return null

// For each annotation on the receiver class, check its class declaration
// to see if it has the `@Qualifier` annotation.
val qualifierAnnotation = annotations
.firstOrNull { it.annotationClass.hasAnnotation<javax.inject.Qualifier>() }
// If there is no qualifier annotation, there's no key
?: return null

val qualifierFqName = qualifierAnnotation.annotationClass.qualifiedName!!

val joinedArgs = qualifierAnnotation.annotationClass
.declaredMemberProperties
.joinToString("") { property ->

val valueString = when (val argument = property.call(qualifierAnnotation)) {
is Enum<*> -> "${argument::class.qualifiedName}.${argument.name}"
is Class<*> -> argument.kotlin.qualifiedName
is KClass<*> -> argument.qualifiedName
else -> argument.toString()
}
property.name + valueString
}

return qualifierFqName + joinedArgs
}

internal val Class<*>.bindingOriginKClass: KClass<*>?
get() {
return resolveOriginClass(ContributesBinding::class)
Expand Down
Loading

0 comments on commit 2f9061f

Please sign in to comment.