Skip to content

Commit

Permalink
Migrate binding priority to int-based system (#934)
Browse files Browse the repository at this point in the history
* Migrate binding priority to int-based system

This allows more granular prioritization while also being ABI-compatible. See addition to the CHANGELOG for migration steps.

* Dodge -Werror

* More dodging

* Map name accordingly

* Or not

* Ok got it

* Update compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesBindingGeneratorTest.kt

Co-authored-by: Rick Busarow <[email protected]>

---------

Co-authored-by: Rick Busarow <[email protected]>
  • Loading branch information
ZacSweers and RBusarow authored Apr 3, 2024
1 parent e5919f2 commit 8ee5871
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 66 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Deprecated

- `ContributesBinding.priority` has been deprecated (and renamed!) in favor of an int-value-based approach. This is a binary-compatible change but not a source-compatible change. The previous `ContributesBinding.priority: Priority` property has been renamed to `ContributesBinding.priorityDeprecated` and a new `ContributesBinding.priority: Int` has been introduced to replace it. This allows for more granular prioritization, rather than just the three enum entries that `ContributesBinding.Priority` offered.

### Removed

### Fixed
Expand Down
14 changes: 13 additions & 1 deletion annotations/api/annotations.api
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
public abstract interface annotation class com/squareup/anvil/annotations/ContributesBinding : java/lang/annotation/Annotation {
public static final field Companion Lcom/squareup/anvil/annotations/ContributesBinding$Companion;
public static final field PRIORITY_HIGH I
public static final field PRIORITY_HIGHEST I
public static final field PRIORITY_NORMAL I
public abstract fun boundType ()Ljava/lang/Class;
public abstract fun ignoreQualifier ()Z
public abstract fun priority ()Lcom/squareup/anvil/annotations/ContributesBinding$Priority;
public abstract fun priorityInt ()I
public abstract fun replaces ()[Ljava/lang/Class;
public abstract fun scope ()Ljava/lang/Class;
}

public final class com/squareup/anvil/annotations/ContributesBinding$Companion {
public static final field PRIORITY_HIGH I
public static final field PRIORITY_HIGHEST I
public static final field PRIORITY_NORMAL I
}

public abstract interface annotation class com/squareup/anvil/annotations/ContributesBinding$Container : java/lang/annotation/Annotation {
public abstract fun value ()[Lcom/squareup/anvil/annotations/ContributesBinding;
}
Expand All @@ -15,6 +26,7 @@ public final class com/squareup/anvil/annotations/ContributesBinding$Priority :
public static final field HIGHEST Lcom/squareup/anvil/annotations/ContributesBinding$Priority;
public static final field NORMAL Lcom/squareup/anvil/annotations/ContributesBinding$Priority;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public final fun getValue ()I
public static fun valueOf (Ljava/lang/String;)Lcom/squareup/anvil/annotations/ContributesBinding$Priority;
public static fun values ()[Lcom/squareup/anvil/annotations/ContributesBinding$Priority;
}
Expand Down Expand Up @@ -97,7 +109,7 @@ public abstract interface annotation class com/squareup/anvil/annotations/compat
public abstract interface annotation class com/squareup/anvil/annotations/internal/InternalBindingMarker : java/lang/annotation/Annotation {
public abstract fun isMultibinding ()Z
public abstract fun originClass ()Ljava/lang/Class;
public abstract fun priority ()Ljava/lang/String;
public abstract fun priority ()I
public abstract fun qualifierKey ()Ljava/lang/String;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.squareup.anvil.annotations

import com.squareup.anvil.annotations.ContributesBinding.Priority.NORMAL
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass
Expand Down Expand Up @@ -104,6 +103,17 @@ public annotation class ContributesBinding(
* use the same scope.
*/
val replaces: Array<KClass<*>> = [],
@Suppress("DEPRECATION")
@Deprecated("Use the new int-based priority", ReplaceWith("priority"))
@get:JvmName("priority")
val priorityDeprecated: Priority = Priority.NORMAL,
/**
* Whether the qualifier for this class should be included in the generated binding method. This
* parameter is only necessary to use when [ContributesBinding] and [ContributesMultibinding]
* are used together for the same class. If not, simply remove the qualifier from the class
* and don't use this parameter.
*/
val ignoreQualifier: Boolean = false,
/**
* The priority of this contributed binding. The priority should be changed only if you don't
* have access to the contributed binding class that you want to replace at compile time. If
Expand All @@ -118,24 +128,26 @@ public annotation class ContributesBinding(
* Note that [replaces] takes precedence. If you explicitly replace a binding it won't be
* considered no matter what its priority is.
*
* All contributed bindings have a [NORMAL] priority by default.
* All contributed bindings have a [PRIORITY_NORMAL] priority by default.
*/
val priority: Priority = NORMAL,
/**
* Whether the qualifier for this class should be included in the generated binding method. This
* parameter is only necessary to use when [ContributesBinding] and [ContributesMultibinding]
* are used together for the same class. If not, simply remove the qualifier from the class
* and don't use this parameter.
*/
val ignoreQualifier: Boolean = false,
@get:JvmName("priorityInt")
val priority: Int = PRIORITY_NORMAL,
) {

public companion object {
public const val PRIORITY_NORMAL: Int = Int.MIN_VALUE
public const val PRIORITY_HIGH: Int = 0
public const val PRIORITY_HIGHEST: Int = Int.MAX_VALUE
}

/**
* The priority of a contributed binding.
*/
@Deprecated("Use the new int-based priority")
@Suppress("unused")
public enum class Priority {
NORMAL,
HIGH,
HIGHEST,
public enum class Priority(public val value: Int) {
NORMAL(PRIORITY_NORMAL),
HIGH(PRIORITY_HIGH),
HIGHEST(PRIORITY_HIGHEST),
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.squareup.anvil.annotations.internal

import com.squareup.anvil.annotations.ContributesBinding.Priority
import com.squareup.anvil.annotations.ContributesBinding
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass
Expand All @@ -13,10 +13,11 @@ import kotlin.reflect.KClass
* read the bound type, possible qualifier, and possible `@MapKey`/`@IntoSet` annotations.
*
* @param originClass the origin class that contributed the binding. This is important to include
* because the contributed binding may be an `object` and cannot be specified as a
* binding parameter.
* because the contributed binding may be an `object` and cannot be specified as a
* binding parameter.
* @param isMultibinding Whether this is a multibinding.
* @param priority The priority of the contributed binding. Corresponds to a [Priority.name].
* @param priority The priority of the contributed binding. Corresponds to a
* [ContributesBinding.priority].
* @param qualifierKey The computed key of the qualifier annotation if present. Empty otherwise.
*/
@Target(CLASS)
Expand All @@ -25,6 +26,6 @@ import kotlin.reflect.KClass
public annotation class InternalBindingMarker(
val originClass: KClass<*>,
val isMultibinding: Boolean,
val priority: String = "",
val priority: Int = Int.MIN_VALUE,
val qualifierKey: String = "",
)
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ internal data class ContributedBinding(
val originClass: OriginClass,
val boundType: ClassReferenceIr,
val qualifierKey: String,
val priority: ContributesBinding.Priority,
val priority: Int,
) {
val bindingKey = BindingKey(scope, boundType, qualifierKey)
val replaces = bindingModule.annotations.find(contributesToFqName).single()
Expand All @@ -124,10 +124,17 @@ internal fun List<ContributedBinding>.findHighestPriorityBinding(): ContributedB
.distinctBy { it.originClass }

if (bindings.size > 1) {
val mappedName = when (val priority = bindings[0].priority) {
// Map to semantic names for easier error message reading
ContributesBinding.PRIORITY_NORMAL -> "NORMAL"
ContributesBinding.PRIORITY_HIGH -> "HIGH"
ContributesBinding.PRIORITY_HIGHEST -> "HIGHEST"
else -> priority.toString()
}
throw AnvilCompilationExceptionClassReferenceIr(
bindings[0].boundType,
"There are multiple contributed bindings with the same bound type and priority. The bound type is " +
"${bindings[0].boundType.fqName.asString()}. The priority is ${bindings[0].priority.name}. " +
"${bindings[0].boundType.fqName.asString()}. The priority is $mappedName. " +
"The contributed binding classes are: " +
bindings.joinToString(
prefix = "[",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,8 @@ internal class IrContributionMerger(
val qualifierKey =
internalBindingMarker.argumentOrNull("qualifierKey")?.value<String>().orEmpty()
val priority = internalBindingMarker.argumentOrNull("priority")
?.value<String>()
?.let { ContributesBinding.Priority.valueOf(it) }
?: ContributesBinding.Priority.NORMAL
?.value()
?: ContributesBinding.PRIORITY_NORMAL
val scope = contributedAnnotation.scope
ContributedBinding(
scope = scope,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.annotations.ContributesBinding.Priority
import com.squareup.anvil.annotations.ContributesBinding.Priority.NORMAL
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.compiler.contributesBindingFqName
import com.squareup.anvil.compiler.contributesMultibindingFqName
import com.squareup.anvil.compiler.contributesSubcomponentFqName
import com.squareup.anvil.compiler.daggerComponentFqName
import com.squareup.anvil.compiler.daggerModuleFqName
import com.squareup.anvil.compiler.daggerSubcomponentFqName
import com.squareup.anvil.compiler.internal.reference.AnnotationReference
import com.squareup.anvil.compiler.internal.reference.AnnotationReference.Descriptor
import com.squareup.anvil.compiler.internal.reference.AnnotationReference.Psi
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionAnnotationReference
import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference
import com.squareup.anvil.compiler.internal.reference.ClassReference
Expand Down Expand Up @@ -43,11 +44,37 @@ internal fun ClassReference.qualifierAnnotation(): AnnotationReference? {
return annotations.find { it.isQualifier() }
}

internal fun AnnotationReference.priority(): Priority {
return argumentAt("priority", index = 4)
internal fun AnnotationReference.priority(): Int {
return priorityNew() ?: priorityLegacy() ?: ContributesBinding.PRIORITY_NORMAL
}

@Suppress("DEPRECATION")
internal fun AnnotationReference.priorityLegacy(): Int? {
val priorityDeprecated = when (this) {
is Descriptor -> "priority"
is Psi -> "priorityDeprecated"
}
val priority = argumentAt(priorityDeprecated, index = 4)
?.value<FqName>()
?.let { Priority.valueOf(it.shortName().asString()) }
?: NORMAL
?.let { ContributesBinding.Priority.valueOf(it.shortName().asString()) }

return priority?.value
}

internal fun AnnotationReference.priorityNew(): Int? {
val priorityInt = when (this) {
is Descriptor -> "priorityInt"
is Psi -> "priority"
}
try {
return argumentAt(priorityInt, index = 6)
?.value()
} catch (e: ClassCastException) {
throw AnvilCompilationExceptionAnnotationReference(
message = "Could not parse priority. This can happen if you haven't migrated to the new int-based priority API",
annotationReference = this,
)
}
}

internal fun AnnotationReference.modules(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.squareup.anvil.compiler.codegen

import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.annotations.internal.InternalBindingMarker
import com.squareup.anvil.compiler.BINDING_MODULE_SUFFIX
Expand Down Expand Up @@ -80,8 +79,8 @@ internal sealed interface Contribution {
}
if (contribution is Binding) {
addMember(
"priority = %S",
contribution.priority.name,
"priority = %L",
contribution.priority,
)
}
}
Expand Down Expand Up @@ -153,7 +152,7 @@ internal sealed interface Contribution {
override val scope: ClassName,
override val isObject: Boolean,
override val boundType: ClassName,
val priority: ContributesBinding.Priority,
val priority: Int,
override val replaces: List<ClassName>,
override val qualifier: QualifierData?,
) : Contribution {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSValueArgument
import com.squareup.anvil.annotations.ContributesBinding.Priority
import com.squareup.anvil.annotations.ContributesBinding.Priority.NORMAL
import com.squareup.anvil.compiler.internal.daggerScopeFqName
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.compiler.internal.mapKeyFqName
import com.squareup.anvil.compiler.qualifierFqName
import com.squareup.kotlinpoet.ksp.toClassName
Expand Down Expand Up @@ -149,16 +147,25 @@ private fun KSAnnotation.isTypeAnnotatedWith(

internal fun KSAnnotation.isQualifier(): Boolean = isTypeAnnotatedWith(qualifierFqName)
internal fun KSAnnotation.isMapKey(): Boolean = isTypeAnnotatedWith(mapKeyFqName)
internal fun KSAnnotation.isDaggerScope(): Boolean = isTypeAnnotatedWith(daggerScopeFqName)

internal fun KSAnnotated.qualifierAnnotation(): KSAnnotation? =
annotations.singleOrNull { it.isQualifier() }

internal fun KSAnnotation.ignoreQualifier(): Boolean =
argumentAt("ignoreQualifier")?.value as? Boolean? == true

internal fun KSAnnotation.priority(): Priority {
val priorityEntry = argumentAt("priority")?.value as KSType? ?: return NORMAL
val name = priorityEntry.resolveKSClassDeclaration()?.simpleName?.asString() ?: return NORMAL
return Priority.valueOf(name)
internal fun KSAnnotation.priority(): Int {
return priorityNew() ?: priorityLegacy() ?: ContributesBinding.PRIORITY_NORMAL
}

@Suppress("DEPRECATION")
internal fun KSAnnotation.priorityLegacy(): Int? {
val priorityEntry = argumentAt("priorityDeprecated")?.value as KSType? ?: return null
val name = priorityEntry.resolveKSClassDeclaration()?.simpleName?.asString() ?: return null
val priority = ContributesBinding.Priority.valueOf(name)
return priority.value
}

internal fun KSAnnotation.priorityNew(): Int? {
return argumentAt("priority")?.value as Int?
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,16 @@ class BindingModulePriorityTest(
package com.squareup.test
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.annotations.ContributesBinding.Priority.HIGH
import com.squareup.anvil.annotations.ContributesBinding.Priority.HIGHEST
import com.squareup.anvil.annotations.ContributesBinding.Companion.PRIORITY_HIGH
import com.squareup.anvil.annotations.ContributesBinding.Companion.PRIORITY_HIGHEST
$import
interface ParentInterface
@ContributesBinding(Any::class, priority = HIGHEST)
@ContributesBinding(Any::class, priority = PRIORITY_HIGHEST)
interface ContributingInterface : ParentInterface
@ContributesBinding(Any::class, priority = HIGH)
@ContributesBinding(Any::class, priority = PRIORITY_HIGH)
interface ContributingInterface2 : ParentInterface
@ContributesBinding(Any::class)
Expand All @@ -83,16 +83,16 @@ class BindingModulePriorityTest(
package com.squareup.test
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.annotations.ContributesBinding.Priority.HIGH
import com.squareup.anvil.annotations.ContributesBinding.Priority.HIGHEST
import com.squareup.anvil.annotations.ContributesBinding.Companion.PRIORITY_HIGH
import com.squareup.anvil.annotations.ContributesBinding.Companion.PRIORITY_HIGHEST
$import
interface ParentInterface
@ContributesBinding(Any::class, priority = HIGHEST)
@ContributesBinding(Any::class, priority = PRIORITY_HIGHEST)
interface ContributingInterface : ParentInterface
@ContributesBinding(Any::class, priority = HIGH, replaces = [ContributingInterface::class])
@ContributesBinding(Any::class, priority = PRIORITY_HIGH, replaces = [ContributingInterface::class])
interface SecondContributingInterface : ParentInterface
@ContributesBinding(Any::class)
Expand Down Expand Up @@ -385,16 +385,16 @@ class BindingModulePriorityTest(
package com.squareup.test
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.annotations.ContributesBinding.Priority.HIGH
import com.squareup.anvil.annotations.ContributesBinding.Priority.HIGHEST
import com.squareup.anvil.annotations.ContributesBinding.Companion.PRIORITY_HIGH
import com.squareup.anvil.annotations.ContributesBinding.Companion.PRIORITY_HIGHEST
$import
interface ParentInterface
@ContributesBinding(Any::class, priority = HIGHEST)
@ContributesBinding(Any::class, priority = PRIORITY_HIGHEST)
interface ContributingInterface : ParentInterface
@ContributesBinding(Any::class, priority = HIGH)
@ContributesBinding(Any::class, priority = PRIORITY_HIGH)
interface ContributingInterface2 : ParentInterface
@ContributesBinding(Any::class)
Expand Down
Loading

0 comments on commit 8ee5871

Please sign in to comment.