Skip to content

Commit

Permalink
Add a way to change the default BindGenericAs. (#90)
Browse files Browse the repository at this point in the history
This new annotation, `BindGenericAs.Default`, can be placed on generic
types to indicate how they should be bound by default.
  • Loading branch information
ansman authored Oct 1, 2023
1 parent ebd7ff5 commit b720672
Show file tree
Hide file tree
Showing 27 changed files with 497 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import com.squareup.javapoet.AnnotationSpec
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.TypeName
import kotlinx.metadata.ClassKind
import kotlinx.metadata.Modality
import kotlinx.metadata.Visibility
import kotlinx.metadata.kind
import kotlinx.metadata.modality
import kotlinx.metadata.visibility
import se.ansman.dagger.auto.compiler.common.processing.ClassDeclaration
import javax.lang.model.element.Element
Expand All @@ -19,7 +21,30 @@ data class KaptClassDeclaration(
override val node: TypeElement,
override val resolver: KaptResolver,
) : KaptNode(), ClassDeclaration<Element, TypeName, ClassName, AnnotationSpec> {
val kmClass by lazy { resolver.kmClassLookup[node.qualifiedName.toString()] }
private val kmClass by lazy { resolver.kmClassLookup[node.qualifiedName.toString()] }

override val kind: ClassDeclaration.Kind
get() = if (kmClass != null) {
when (kmClass!!.kind) {
ClassKind.CLASS -> ClassDeclaration.Kind.Class
ClassKind.INTERFACE -> ClassDeclaration.Kind.Interface
ClassKind.ENUM_CLASS -> ClassDeclaration.Kind.EnumClass
ClassKind.ENUM_ENTRY -> ClassDeclaration.Kind.EnumEntry
ClassKind.ANNOTATION_CLASS -> ClassDeclaration.Kind.AnnotationClass
ClassKind.OBJECT -> ClassDeclaration.Kind.Object
ClassKind.COMPANION_OBJECT -> ClassDeclaration.Kind.CompanionObject
}
} else when (node.kind) {
ElementKind.ENUM -> ClassDeclaration.Kind.EnumClass
ElementKind.CLASS -> ClassDeclaration.Kind.Class
ElementKind.ANNOTATION_TYPE -> ClassDeclaration.Kind.AnnotationClass
ElementKind.INTERFACE -> ClassDeclaration.Kind.Interface
ElementKind.ENUM_CONSTANT -> ClassDeclaration.Kind.EnumEntry
else -> {
resolver.environment.logger.error("Unsupported element kind: ${node.kind}", node)
ClassDeclaration.Kind.Class
}
}

override val className: ClassName by lazy(LazyThreadSafetyMode.NONE) { ClassName.get(node) }

Expand All @@ -38,17 +63,14 @@ data class KaptClassDeclaration(
.map { KaptFunction(it, resolver) }
}

override val isObject: Boolean
get() = kmClass?.kind == ClassKind.OBJECT

override val isCompanionObject: Boolean
get() = kmClass?.kind == ClassKind.COMPANION_OBJECT

override val isGeneric: Boolean
get() = node.typeParameters.isNotEmpty()

override val isInterface: Boolean
get() = node.kind == ElementKind.INTERFACE
override val isAbstract: Boolean
get() = Modifier.ABSTRACT in node.modifiers

override val isSealedClass: Boolean
get() = kmClass?.modality == Modality.SEALED

override val isPublic: Boolean
get() = kmClass?.let { it.visibility == Visibility.PUBLIC } ?: (Modifier.PUBLIC in node.modifiers)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
package se.ansman.dagger.auto.compiler.common.ksp.processing

import com.google.devtools.ksp.isAbstract
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.Modifier
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.ksp.toClassName
import se.ansman.dagger.auto.compiler.common.processing.ClassDeclaration
import se.ansman.dagger.auto.compiler.common.processing.ClassDeclaration.Kind
import se.ansman.dagger.auto.compiler.common.processing.Type

data class KspClassDeclaration(
override val node: KSClassDeclaration,
override val resolver: KspResolver,
) : KspNode(), ClassDeclaration<KSDeclaration, TypeName, ClassName, AnnotationSpec> {
override val kind: Kind
get() = when (node.classKind) {
ClassKind.INTERFACE -> Kind.Interface
ClassKind.CLASS -> Kind.Class
ClassKind.ENUM_CLASS -> Kind.EnumClass
ClassKind.ENUM_ENTRY -> Kind.EnumEntry
ClassKind.OBJECT -> if (node.isCompanionObject) {
Kind.CompanionObject
} else {
Kind.Object
}
ClassKind.ANNOTATION_CLASS -> Kind.AnnotationClass
}

override val className: ClassName by lazy(LazyThreadSafetyMode.NONE) {
node.toClassName()
}
Expand Down Expand Up @@ -51,17 +68,14 @@ data class KspClassDeclaration(
}
}

override val isObject: Boolean
get() = node.classKind == ClassKind.OBJECT

override val isCompanionObject: Boolean
get() = node.isCompanionObject

override val isGeneric: Boolean
get() = node.typeParameters.isNotEmpty()

override val isInterface: Boolean
get() = node.classKind == ClassKind.INTERFACE
override val isAbstract: Boolean
get() = node.isAbstract()

override val isSealedClass: Boolean
get() = Modifier.SEALED in node.modifiers

override val superclass: Type<KSDeclaration, TypeName, ClassName, AnnotationSpec>?
get() = supertypes.find { it.declaration.node.classKind == ClassKind.CLASS }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ fun <N, TypeName, ClassName : TypeName, AnnotationSpec> AutoDaggerResolver<N, Ty

fun <N, TypeName, ClassName : TypeName, AnnotationSpec> AutoDaggerResolver<N, TypeName, ClassName, AnnotationSpec>
.nodesAnnotatedWith(annotation: KClass<out Annotation>): Sequence<Node<N, TypeName, ClassName, AnnotationSpec>> =
nodesAnnotatedWith(annotation.java.name)
nodesAnnotatedWith(annotation.java.canonicalName)
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,25 @@ interface Function<N, TypeName, ClassName : TypeName, AnnotationSpec> :

interface ClassDeclaration<N, TypeName, ClassName : TypeName, AnnotationSpec> :
Node<N, TypeName, ClassName, AnnotationSpec> {
val kind: Kind
val className: ClassName
val supertypes: List<Type<N, TypeName, ClassName, AnnotationSpec>>
val declaredNodes: List<ExecutableNode<N, TypeName, ClassName, AnnotationSpec>>
val isObject: Boolean
val isCompanionObject: Boolean
val isGeneric: Boolean
val isInterface: Boolean
val isAbstract: Boolean
val isSealedClass: Boolean
val superclass: Type<N, TypeName, ClassName, AnnotationSpec>?
fun asType(): Type<N, TypeName, ClassName, AnnotationSpec>

enum class Kind {
Class,
Interface,
EnumClass,
EnumEntry,
AnnotationClass,
Object,
CompanionObject
}
}

interface AnnotationModel<ClassName, AnnotationSpec> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ object Errors {

fun invalidBindMode(bindGenericAs: BindGenericAs): String =
"Using BindGenericAs.${bindGenericAs.name} requires at least one generic supertype."

object BindGenericAsDefault {
const val nonGenericType = "@BindGenericAs.Default can only be applied to generic types."
const val nonAbstractType = "@BindGenericAs.Default can only be applied to interfaces and abstract/open classes."
}
}

object Replaces {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class AndroidXRoomProcessor<N, TypeName, ClassName : TypeName, AnnotationSpec, F
) : Processor<N, TypeName, ClassName, AnnotationSpec> {
private val logger = environment.logger.withTag("androidx.room")
override val annotations: Set<String> = setOf(
AutoProvideDaos::class.java.name,
AutoProvideDaos::class.java.canonicalName,
)

override fun process(resolver: AutoDaggerResolver<N, TypeName, ClassName, AnnotationSpec>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import se.ansman.dagger.auto.compiler.common.processing.AnnotationModel
import se.ansman.dagger.auto.compiler.common.processing.AutoDaggerEnvironment
import se.ansman.dagger.auto.compiler.common.processing.AutoDaggerResolver
import se.ansman.dagger.auto.compiler.common.processing.ClassDeclaration
import se.ansman.dagger.auto.compiler.common.processing.ClassDeclaration.Kind
import se.ansman.dagger.auto.compiler.common.processing.Type
import se.ansman.dagger.auto.compiler.common.processing.error
import se.ansman.dagger.auto.compiler.common.processing.getAnnotation
Expand All @@ -40,15 +41,19 @@ class AutoBindProcessor<N, TypeName : Any, ClassName : TypeName, AnnotationSpec,
private val logging: Boolean = true,
) : Processor<N, TypeName, ClassName, AnnotationSpec> {
private val logger = environment.logger.withTag("auto-bind").takeIf { logging }
override val annotations: Set<String> = setOf(
AutoBind::class.java.name,
AutoBindIntoSet::class.java.name,
AutoBindIntoMap::class.java.name,
private val autoBindAnnotations = setOf(
AutoBind::class.java.canonicalName,
AutoBindIntoSet::class.java.canonicalName,
AutoBindIntoMap::class.java.canonicalName,
)

override val annotations: Set<String> = autoBindAnnotations + setOf(
BindGenericAs.Default::class.java.canonicalName
)

override fun process(resolver: AutoDaggerResolver<N, TypeName, ClassName, AnnotationSpec>) {
logger?.info("AutoBind processing started")
annotations.asSequence()
autoBindAnnotations.asSequence()
.onEach { logger?.info("Looking for annotation $it") }
.flatMap { resolver.nodesAnnotatedWith(it) }
.distinct()
Expand All @@ -60,6 +65,32 @@ class AutoBindProcessor<N, TypeName : Any, ClassName : TypeName, AnnotationSpec,
return@forEach
}
}
logger?.info("Validating BindGenericAs.Default")
resolver.nodesAnnotatedWith(BindGenericAs.Default::class.java.canonicalName)
.map { it as ClassDeclaration }
.forEach { node ->
node.validateBindGenericAsDefault()
}
logger?.info("AutoBind processing finished")
}

private fun ClassDeclaration<N, TypeName, ClassName, AnnotationSpec>.validateBindGenericAsDefault() {
logger?.info("Validating $className")
if (!isGeneric) {
logger?.error(Errors.AutoBind.BindGenericAsDefault.nonGenericType, this)
}
val isSupportedType = when (kind) {
ClassDeclaration.Kind.Class -> isAbstract || isSealedClass
ClassDeclaration.Kind.Interface -> true
ClassDeclaration.Kind.EnumClass,
ClassDeclaration.Kind.EnumEntry,
ClassDeclaration.Kind.AnnotationClass,
ClassDeclaration.Kind.Object,
ClassDeclaration.Kind.CompanionObject -> false
}
if (!isSupportedType) {
logger?.error(Errors.AutoBind.BindGenericAsDefault.nonAbstractType, this)
}
}

private fun ClassDeclaration<N, TypeName, ClassName, AnnotationSpec>.process(
Expand Down Expand Up @@ -147,45 +178,52 @@ class AutoBindProcessor<N, TypeName : Any, ClassName : TypeName, AnnotationSpec,
) {
logger?.info("Processing annotation ${annotation.qualifiedName} for ${type.className}")

val bindGenericAs = annotation.getValue("bindGenericAs") ?: BindGenericAs.Type
val targetTypes = getBoundTypes(annotation)
.associateBy({it}) { boundType ->
val bindGenericAs = annotation.getValue<BindGenericAs>("bindGenericAs")
?: boundType.getDefaultBindGenericAs()
?: BindGenericAs.ExactType

val component = type.getTargetComponent(resolver, annotation)
val key = ModuleKey(targetType = type.className, targetComponent = component)
val qualifiers = type.getQualifiers()
val types = getBoundTypes(annotation)
.also { boundTypes ->
if (bindGenericAs != BindGenericAs.Type && boundTypes.none { it.isGeneric }) {
if (bindGenericAs != BindGenericAs.ExactType && !boundType.isGeneric) {
logger?.error(Errors.AutoBind.invalidBindMode(bindGenericAs), type)
}

bindGenericAs
}

val component = type.getTargetComponent(resolver, annotation)
val key = ModuleKey(targetType = type.className, targetComponent = component)
val qualifiers = type.getQualifiers()

val boundTypes = targetTypes
.asSequence()
.flatMap { boundType ->
.flatMap { (boundType, bindGenericAs) ->
val typeName = boundType.toTypeName()
when (bindGenericAs) {
BindGenericAs.Type ->
BindGenericAs.ExactType ->
sequenceOf(typeName)

BindGenericAs.Wildcard ->
sequenceOf(resolver.environment.asWildcard(typeName))

BindGenericAs.TypeAndWildcard ->
BindGenericAs.ExactTypeAndWildcard ->
sequenceOf(typeName, resolver.environment.asWildcard(typeName))
}
}
.distinct()
.map { AutoBindType(it, mode, qualifiers) }
.toList()
output[key] = output[key]
?.withTypesAdded(types)
?.withTypesAdded(boundTypes)
?: AutoBindObjectModule(
moduleName = getModuleName(type, component),
installation = HiltModuleBuilder.Installation.InstallIn(component),
originatingTopLevelClassName = environment.topLevelClassName(type.className),
originatingElement = type.node,
type = type.className,
isPublic = type.isFullyPublic,
isObject = type.isObject,
boundTypes = types,
isObject = type.kind == Kind.Object,
boundTypes = boundTypes,
)
}

Expand All @@ -209,6 +247,11 @@ class AutoBindProcessor<N, TypeName : Any, ClassName : TypeName, AnnotationSpec,
?.className
?: guessComponent()

private fun Type<N, TypeName, ClassName, AnnotationSpec>.getDefaultBindGenericAs(): BindGenericAs? =
declaration
?.getAnnotation(BindGenericAs.Default::class)
?.getValue("value")

private fun ClassDeclaration<N, TypeName, ClassName, AnnotationSpec>.guessComponent(): ClassName {
val scope = annotations.find { it.isAnnotatedWith(Scope::class) }
?: return environment.className(SingletonComponent::class)
Expand All @@ -222,7 +265,7 @@ class AutoBindProcessor<N, TypeName : Any, ClassName : TypeName, AnnotationSpec,
resolver: AutoDaggerResolver<N, TypeName, ClassName, AnnotationSpec>,
component: ClassDeclaration<N, TypeName, ClassName, AnnotationSpec>,
) {
if (!component.validateComponent(this, logger)){
if (!component.validateComponent(this, logger)) {
return
}
val scope = annotations.find { it.isAnnotatedWith(Scope::class) }
Expand Down Expand Up @@ -297,8 +340,8 @@ class AutoBindProcessor<N, TypeName : Any, ClassName : TypeName, AnnotationSpec,

private fun AnnotationModel<*, *>.guessComponent(): ClassName? =
when (qualifiedName) {
Singleton::class.java.name,
Reusable::class.java.name ->
Singleton::class.java.canonicalName,
Reusable::class.java.canonicalName ->
environment.className(SingletonComponent::class)

"dagger.hilt.android.scopes.ActivityRetainedScoped" ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ data class AutoBindObjectModule<out Node, TypeName, ClassName : TypeName, Annota
val isObject: Boolean,
val boundTypes: List<AutoBindType<TypeName, AnnotationSpec>>,
) : HiltModule<Node, ClassName> {
fun withTypesAdded(types: List<AutoBindType<TypeName, AnnotationSpec>>) =
fun withTypesAdded(types: Collection<AutoBindType<TypeName, AnnotationSpec>>) =
copy(boundTypes = boundTypes + types)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import se.ansman.dagger.auto.compiler.common.processing.AnnotationModel
import se.ansman.dagger.auto.compiler.common.processing.AutoDaggerEnvironment
import se.ansman.dagger.auto.compiler.common.processing.AutoDaggerResolver
import se.ansman.dagger.auto.compiler.common.processing.ClassDeclaration
import se.ansman.dagger.auto.compiler.common.processing.ClassDeclaration.Kind
import se.ansman.dagger.auto.compiler.common.processing.ExecutableNode
import se.ansman.dagger.auto.compiler.common.processing.Node
import se.ansman.dagger.auto.compiler.common.processing.error
Expand All @@ -39,7 +40,7 @@ class AutoInitializeProcessor<N, TypeName, ClassName : TypeName, AnnotationSpec,
private val renderer: Renderer<AutoInitializeModule<N, TypeName, ClassName, AnnotationSpec>, F>,
) : Processor<N, TypeName, ClassName, AnnotationSpec> {
private val logger = environment.logger.withTag("auto-initialize")
override val annotations: Set<String> = setOf(AutoInitialize::class.java.name)
override val annotations: Set<String> = setOf(AutoInitialize::class.java.canonicalName)

override fun process(resolver: AutoDaggerResolver<N, TypeName, ClassName, AnnotationSpec>) {
val initializableObjectsByModule =
Expand Down Expand Up @@ -113,7 +114,7 @@ class AutoInitializeProcessor<N, TypeName, ClassName : TypeName, AnnotationSpec,
) {

val module = enclosingType
?.let { if (it.isCompanionObject) it.enclosingType else it }
?.let { if (it.kind == Kind.CompanionObject) it.enclosingType else it }
?: run {
logger.error(Errors.AutoInitialize.methodInNonModule, this@processMethod)
return
Expand Down
Loading

0 comments on commit b720672

Please sign in to comment.