diff --git a/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/kapt/processing/KaptClassDeclaration.kt b/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/kapt/processing/KaptClassDeclaration.kt index 164232b13..a0200741c 100644 --- a/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/kapt/processing/KaptClassDeclaration.kt +++ b/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/kapt/processing/KaptClassDeclaration.kt @@ -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 @@ -19,7 +21,30 @@ data class KaptClassDeclaration( override val node: TypeElement, override val resolver: KaptResolver, ) : KaptNode(), ClassDeclaration { - 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) } @@ -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) diff --git a/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/ksp/processing/KspClassDeclaration.kt b/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/ksp/processing/KspClassDeclaration.kt index 8f284757a..c3b503d66 100644 --- a/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/ksp/processing/KspClassDeclaration.kt +++ b/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/ksp/processing/KspClassDeclaration.kt @@ -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 { + 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() } @@ -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? get() = supertypes.find { it.declaration.node.classKind == ClassKind.CLASS } diff --git a/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/processing/AutoDaggerResolver.kt b/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/processing/AutoDaggerResolver.kt index fad1ea9e0..5b215c9bc 100644 --- a/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/processing/AutoDaggerResolver.kt +++ b/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/processing/AutoDaggerResolver.kt @@ -17,4 +17,4 @@ fun AutoDaggerResolver AutoDaggerResolver .nodesAnnotatedWith(annotation: KClass): Sequence> = - nodesAnnotatedWith(annotation.java.name) \ No newline at end of file + nodesAnnotatedWith(annotation.java.canonicalName) \ No newline at end of file diff --git a/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/processing/types.kt b/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/processing/types.kt index 58e1af5d4..8aeaecdc7 100644 --- a/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/processing/types.kt +++ b/compiler/common/src/main/kotlin/se/ansman/dagger/auto/compiler/common/processing/types.kt @@ -50,15 +50,25 @@ interface Function : interface ClassDeclaration : Node { + val kind: Kind val className: ClassName val supertypes: List> val declaredNodes: List> - val isObject: Boolean - val isCompanionObject: Boolean val isGeneric: Boolean - val isInterface: Boolean + val isAbstract: Boolean + val isSealedClass: Boolean val superclass: Type? fun asType(): Type + + enum class Kind { + Class, + Interface, + EnumClass, + EnumEntry, + AnnotationClass, + Object, + CompanionObject + } } interface AnnotationModel { diff --git a/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/Errors.kt b/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/Errors.kt index 3d15df5d0..fba024452 100644 --- a/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/Errors.kt +++ b/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/Errors.kt @@ -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 { diff --git a/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/androidx/room/AndroidXRoomProcessor.kt b/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/androidx/room/AndroidXRoomProcessor.kt index ffa590bea..4fec00ed0 100644 --- a/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/androidx/room/AndroidXRoomProcessor.kt +++ b/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/androidx/room/AndroidXRoomProcessor.kt @@ -27,7 +27,7 @@ class AndroidXRoomProcessor { private val logger = environment.logger.withTag("androidx.room") override val annotations: Set = setOf( - AutoProvideDaos::class.java.name, + AutoProvideDaos::class.java.canonicalName, ) override fun process(resolver: AutoDaggerResolver) { diff --git a/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autobind/AutoBindProcessor.kt b/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autobind/AutoBindProcessor.kt index f8787f7b5..feca085d1 100644 --- a/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autobind/AutoBindProcessor.kt +++ b/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autobind/AutoBindProcessor.kt @@ -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 @@ -40,15 +41,19 @@ class AutoBindProcessor { private val logger = environment.logger.withTag("auto-bind").takeIf { logging } - override val annotations: Set = 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 = autoBindAnnotations + setOf( + BindGenericAs.Default::class.java.canonicalName ) override fun process(resolver: AutoDaggerResolver) { logger?.info("AutoBind processing started") - annotations.asSequence() + autoBindAnnotations.asSequence() .onEach { logger?.info("Looking for annotation $it") } .flatMap { resolver.nodesAnnotatedWith(it) } .distinct() @@ -60,6 +65,32 @@ class AutoBindProcessor + node.validateBindGenericAsDefault() + } + logger?.info("AutoBind processing finished") + } + + private fun ClassDeclaration.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.process( @@ -147,28 +178,35 @@ class AutoBindProcessor + val bindGenericAs = annotation.getValue("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)) } } @@ -176,7 +214,7 @@ class AutoBindProcessor.getDefaultBindGenericAs(): BindGenericAs? = + declaration + ?.getAnnotation(BindGenericAs.Default::class) + ?.getValue("value") + private fun ClassDeclaration.guessComponent(): ClassName { val scope = annotations.find { it.isAnnotatedWith(Scope::class) } ?: return environment.className(SingletonComponent::class) @@ -222,7 +265,7 @@ class AutoBindProcessor, component: ClassDeclaration, ) { - if (!component.validateComponent(this, logger)){ + if (!component.validateComponent(this, logger)) { return } val scope = annotations.find { it.isAnnotatedWith(Scope::class) } @@ -297,8 +340,8 @@ class AutoBindProcessor.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" -> diff --git a/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autobind/models/AutoBindObjectModule.kt b/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autobind/models/AutoBindObjectModule.kt index ba41a3105..a8f608db1 100644 --- a/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autobind/models/AutoBindObjectModule.kt +++ b/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autobind/models/AutoBindObjectModule.kt @@ -13,6 +13,6 @@ data class AutoBindObjectModule>, ) : HiltModule { - fun withTypesAdded(types: List>) = + fun withTypesAdded(types: Collection>) = copy(boundTypes = boundTypes + types) } \ No newline at end of file diff --git a/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autoinitialize/AutoInitializeProcessor.kt b/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autoinitialize/AutoInitializeProcessor.kt index 7a4effd2f..b269322f3 100644 --- a/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autoinitialize/AutoInitializeProcessor.kt +++ b/compiler/src/main/kotlin/se/ansman/dagger/auto/compiler/autoinitialize/AutoInitializeProcessor.kt @@ -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 @@ -39,7 +40,7 @@ class AutoInitializeProcessor, F>, ) : Processor { private val logger = environment.logger.withTag("auto-initialize") - override val annotations: Set = setOf(AutoInitialize::class.java.name) + override val annotations: Set = setOf(AutoInitialize::class.java.canonicalName) override fun process(resolver: AutoDaggerResolver) { val initializableObjectsByModule = @@ -113,7 +114,7 @@ class AutoInitializeProcessor( private val environment: AutoDaggerEnvironment, @@ -31,7 +31,7 @@ class ReplacesProcessor = setOf(Replaces::class.java.name) + override val annotations: Set = setOf(Replaces::class.java.canonicalName) override fun process(resolver: AutoDaggerResolver) { logger.info("@Replaces processing started") @@ -110,7 +110,7 @@ class ReplacesProcessor( } private val logger = environment.logger.withTag("retrofit") override val annotations: Set = setOf( - AutoProvideService::class.java.name, + AutoProvideService::class.java.canonicalName, ) override fun process(resolver: AutoDaggerResolver) { @@ -73,7 +74,7 @@ class RetrofitProcessor( } private fun ClassDeclaration.validateService() { - if (!isInterface) logger.error(Errors.Retrofit.nonInterface, node) + if (!(kind == Kind.Interface)) logger.error(Errors.Retrofit.nonInterface, node) if (isGeneric) logger.error(Errors.genericType(AutoProvideService::class), node) if (isFullyPrivate) logger.error(Errors.Retrofit.privateType, node) if (declaredNodes.isEmpty()) logger.error(Errors.Retrofit.emptyService, node) diff --git a/compiler/src/test/kotlin/se/ansman/dagger/auto/compiler/AutoBindTest.kt b/compiler/src/test/kotlin/se/ansman/dagger/auto/compiler/AutoBindTest.kt index 3870b5495..13a302278 100644 --- a/compiler/src/test/kotlin/se/ansman/dagger/auto/compiler/AutoBindTest.kt +++ b/compiler/src/test/kotlin/se/ansman/dagger/auto/compiler/AutoBindTest.kt @@ -1,5 +1,7 @@ package se.ansman.dagger.auto.compiler +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ArgumentsSource @@ -162,4 +164,114 @@ class AutoBindTest { ) .assertFailedWithMessage(Errors.AutoBind.parentComponent("SingletonComponent", "FragmentComponent")) } + + @Nested + @DisplayName("auto bind generic as default") + inner class AutoBindGenericAsDefault { + + @ParameterizedTest + @ArgumentsSource(AutoDaggerCompilationFactoryProvider::class) + fun `target must be generic`(compilationFactory: Compilation.Factory) { + compilationFactory.create(tempDirectory) + .compile( + """ + package se.ansman + + @se.ansman.dagger.auto.BindGenericAs.Default(se.ansman.dagger.auto.BindGenericAs.Wildcard) + interface SomeThing + """ + ) + .assertFailedWithMessage(Errors.AutoBind.BindGenericAsDefault.nonGenericType) + } + + @ParameterizedTest + @ArgumentsSource(AutoDaggerCompilationFactoryProvider::class) + fun `cannot be applied to final classes`(compilationFactory: Compilation.Factory) { + compilationFactory.create(tempDirectory) + .compile( + """ + package se.ansman + + @se.ansman.dagger.auto.BindGenericAs.Default(se.ansman.dagger.auto.BindGenericAs.Wildcard) + class SomeThing + """ + ) + .assertFailedWithMessage(Errors.AutoBind.BindGenericAsDefault.nonAbstractType) + } + + @ParameterizedTest + @ArgumentsSource(AutoDaggerCompilationFactoryProvider::class) + fun `can be applied to interfaces`(compilationFactory: Compilation.Factory) { + compilationFactory.create(tempDirectory) + .compile( + """ + package se.ansman + + @se.ansman.dagger.auto.BindGenericAs.Default(se.ansman.dagger.auto.BindGenericAs.Wildcard) + interface SomeThing + """ + ) + .assertIsSuccessful() + } + + @ParameterizedTest + @ArgumentsSource(AutoDaggerCompilationFactoryProvider::class) + fun `can be applied to abstract classes`(compilationFactory: Compilation.Factory) { + compilationFactory.create(tempDirectory) + .compile( + """ + package se.ansman + + @se.ansman.dagger.auto.BindGenericAs.Default(se.ansman.dagger.auto.BindGenericAs.Wildcard) + abstract class SomeThing + """ + ) + .assertIsSuccessful() + } + + @ParameterizedTest + @ArgumentsSource(AutoDaggerCompilationFactoryProvider::class) + fun `can be applied to sealed classes`(compilationFactory: Compilation.Factory) { + compilationFactory.create(tempDirectory) + .compile( + """ + package se.ansman + + @se.ansman.dagger.auto.BindGenericAs.Default(se.ansman.dagger.auto.BindGenericAs.Wildcard) + sealed class SomeThing + """ + ) + .assertIsSuccessful() + } + + @ParameterizedTest + @ArgumentsSource(AutoDaggerCompilationFactoryProvider::class) + fun `cannot be applied to open classes`(compilationFactory: Compilation.Factory) { + compilationFactory.create(tempDirectory) + .compile( + """ + package se.ansman + + @se.ansman.dagger.auto.BindGenericAs.Default(se.ansman.dagger.auto.BindGenericAs.Wildcard) + open class SomeThing + """ + ) + .assertFailedWithMessage(Errors.AutoBind.BindGenericAsDefault.nonAbstractType) + } + + @ParameterizedTest + @ArgumentsSource(AutoDaggerCompilationFactoryProvider::class) + fun `cannot be applied to objects`(compilationFactory: Compilation.Factory) { + compilationFactory.create(tempDirectory) + .compile( + """ + package se.ansman + + @se.ansman.dagger.auto.BindGenericAs.Default(se.ansman.dagger.auto.BindGenericAs.Wildcard) + object SomeThing + """ + ) + .assertFailedWithMessage(Errors.AutoBind.BindGenericAsDefault.nonAbstractType) + } + } } \ No newline at end of file diff --git a/compiler/src/test/resources/tests/auto_bind/into_map_default/Resource.kt b/compiler/src/test/resources/tests/auto_bind/into_map_default/Resource.kt new file mode 100644 index 000000000..4cf947f82 --- /dev/null +++ b/compiler/src/test/resources/tests/auto_bind/into_map_default/Resource.kt @@ -0,0 +1,21 @@ +package tests.auto_bind.into_map_default + +import dagger.multibindings.StringKey +import se.ansman.dagger.auto.AutoBindIntoMap +import se.ansman.dagger.auto.BindGenericAs +import javax.inject.Inject +import javax.inject.Singleton + +@BindGenericAs.Default(BindGenericAs.Wildcard) +interface Resource { + fun produce(): T + fun close() +} + +@AutoBindIntoMap +@StringKey("test") +@Singleton +class SomeResource @Inject constructor() : Resource { + override fun produce(): String = "" + override fun close() {} +} \ No newline at end of file diff --git a/compiler/src/test/resources/tests/auto_bind/into_map_default/kapt/AutoBindSomeResourceSingletonModule.java.txt b/compiler/src/test/resources/tests/auto_bind/into_map_default/kapt/AutoBindSomeResourceSingletonModule.java.txt new file mode 100644 index 000000000..34dd82b2d --- /dev/null +++ b/compiler/src/test/resources/tests/auto_bind/into_map_default/kapt/AutoBindSomeResourceSingletonModule.java.txt @@ -0,0 +1,26 @@ +// Code generated by Auto Dagger. Do not edit. +package tests.auto_bind.into_map_default; + +import dagger.Binds; +import dagger.Module; +import dagger.hilt.InstallIn; +import dagger.hilt.codegen.OriginatingElement; +import dagger.hilt.components.SingletonComponent; +import dagger.multibindings.IntoMap; +import dagger.multibindings.StringKey; + +@Module +@InstallIn(SingletonComponent.class) +@OriginatingElement( + topLevelClass = SomeResource.class +) +public abstract class AutoBindSomeResourceSingletonModule { + private AutoBindSomeResourceSingletonModule() { + } + + @Binds + @IntoMap + @StringKey("test") + public abstract Resource bindSomeResourceAsResourceOfWildcardIntoMap( + SomeResource someResource); +} \ No newline at end of file diff --git a/compiler/src/test/resources/tests/auto_bind/into_map_default/ksp/AutoBindSomeResourceSingletonModule.kt.txt b/compiler/src/test/resources/tests/auto_bind/into_map_default/ksp/AutoBindSomeResourceSingletonModule.kt.txt new file mode 100644 index 000000000..9a502e40f --- /dev/null +++ b/compiler/src/test/resources/tests/auto_bind/into_map_default/ksp/AutoBindSomeResourceSingletonModule.kt.txt @@ -0,0 +1,21 @@ +// Code generated by Auto Dagger. Do not edit. +package tests.auto_bind.into_map_default + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.codegen.OriginatingElement +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +@InstallIn(SingletonComponent::class) +@OriginatingElement(topLevelClass = SomeResource::class) +public abstract class AutoBindSomeResourceSingletonModule private constructor() { + @Binds + @IntoMap + @StringKey("test") + public abstract fun bindSomeResourceAsResourceOfStarIntoMap(someResource: SomeResource): + Resource<*> +} \ No newline at end of file diff --git a/compiler/src/test/resources/tests/auto_bind/into_map_wildcard/Repository.kt b/compiler/src/test/resources/tests/auto_bind/into_map_wildcard/Repository.kt index 5f33f7cb2..ee61ea9a8 100644 --- a/compiler/src/test/resources/tests/auto_bind/into_map_wildcard/Repository.kt +++ b/compiler/src/test/resources/tests/auto_bind/into_map_wildcard/Repository.kt @@ -12,13 +12,13 @@ class StringProvider @Inject constructor() : Callable { override fun call(): String = "Hello" } -@AutoBindIntoSet(bindGenericAs = BindGenericAs.TypeAndWildcard) +@AutoBindIntoSet(bindGenericAs = BindGenericAs.ExactTypeAndWildcard) @StringKey("int") class IntProvider @Inject constructor() : Callable { override fun call(): Int = 4711 } -@AutoBindIntoSet(bindGenericAs = BindGenericAs.Type) +@AutoBindIntoSet(bindGenericAs = BindGenericAs.ExactType) @StringKey("boolean") class BooleanProvider @Inject constructor() : Callable { override fun call(): Boolean = true diff --git a/compiler/src/test/resources/tests/auto_bind/into_set_default/Resource.kt b/compiler/src/test/resources/tests/auto_bind/into_set_default/Resource.kt new file mode 100644 index 000000000..c1132ad4c --- /dev/null +++ b/compiler/src/test/resources/tests/auto_bind/into_set_default/Resource.kt @@ -0,0 +1,19 @@ +package tests.auto_bind.into_set_default + +import se.ansman.dagger.auto.AutoBindIntoSet +import se.ansman.dagger.auto.BindGenericAs +import javax.inject.Inject +import javax.inject.Singleton + +@BindGenericAs.Default(BindGenericAs.Wildcard) +interface Resource { + fun produce(): T + fun close() +} + +@AutoBindIntoSet +@Singleton +class SomeResource @Inject constructor() : Resource { + override fun produce(): String = "" + override fun close() {} +} \ No newline at end of file diff --git a/compiler/src/test/resources/tests/auto_bind/into_set_default/kapt/AutoBindSomeResourceSingletonModule.java.txt b/compiler/src/test/resources/tests/auto_bind/into_set_default/kapt/AutoBindSomeResourceSingletonModule.java.txt new file mode 100644 index 000000000..74e516a14 --- /dev/null +++ b/compiler/src/test/resources/tests/auto_bind/into_set_default/kapt/AutoBindSomeResourceSingletonModule.java.txt @@ -0,0 +1,24 @@ +// Code generated by Auto Dagger. Do not edit. +package tests.auto_bind.into_set_default; + +import dagger.Binds; +import dagger.Module; +import dagger.hilt.InstallIn; +import dagger.hilt.codegen.OriginatingElement; +import dagger.hilt.components.SingletonComponent; +import dagger.multibindings.IntoSet; + +@Module +@InstallIn(SingletonComponent.class) +@OriginatingElement( + topLevelClass = SomeResource.class +) +public abstract class AutoBindSomeResourceSingletonModule { + private AutoBindSomeResourceSingletonModule() { + } + + @Binds + @IntoSet + public abstract Resource bindSomeResourceAsResourceOfWildcardIntoSet( + SomeResource someResource); +} \ No newline at end of file diff --git a/compiler/src/test/resources/tests/auto_bind/into_set_default/ksp/AutoBindSomeResourceSingletonModule.kt.txt b/compiler/src/test/resources/tests/auto_bind/into_set_default/ksp/AutoBindSomeResourceSingletonModule.kt.txt new file mode 100644 index 000000000..535d62420 --- /dev/null +++ b/compiler/src/test/resources/tests/auto_bind/into_set_default/ksp/AutoBindSomeResourceSingletonModule.kt.txt @@ -0,0 +1,19 @@ +// Code generated by Auto Dagger. Do not edit. +package tests.auto_bind.into_set_default + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.codegen.OriginatingElement +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.IntoSet + +@Module +@InstallIn(SingletonComponent::class) +@OriginatingElement(topLevelClass = SomeResource::class) +public abstract class AutoBindSomeResourceSingletonModule private constructor() { + @Binds + @IntoSet + public abstract fun bindSomeResourceAsResourceOfStarIntoSet(someResource: SomeResource): + Resource<*> +} \ No newline at end of file diff --git a/compiler/src/test/resources/tests/auto_bind/into_set_wildcard/Repository.kt b/compiler/src/test/resources/tests/auto_bind/into_set_wildcard/Repository.kt index dc5d282ee..2fe9e0250 100644 --- a/compiler/src/test/resources/tests/auto_bind/into_set_wildcard/Repository.kt +++ b/compiler/src/test/resources/tests/auto_bind/into_set_wildcard/Repository.kt @@ -10,12 +10,12 @@ class StringProvider @Inject constructor() : Callable { override fun call(): String = "Hello" } -@AutoBindIntoSet(bindGenericAs = BindGenericAs.TypeAndWildcard) +@AutoBindIntoSet(bindGenericAs = BindGenericAs.ExactTypeAndWildcard) class IntProvider @Inject constructor() : Callable { override fun call(): Int = 4711 } -@AutoBindIntoSet(bindGenericAs = BindGenericAs.Type) +@AutoBindIntoSet(bindGenericAs = BindGenericAs.ExactType) class BooleanProvider @Inject constructor() : Callable { override fun call(): Boolean = true } \ No newline at end of file diff --git a/compiler/src/test/resources/tests/replaces/auto_bind.with_wildcard/Repository.kt b/compiler/src/test/resources/tests/replaces/auto_bind.with_wildcard/Repository.kt index ef52a1c52..f6a06fa18 100644 --- a/compiler/src/test/resources/tests/replaces/auto_bind.with_wildcard/Repository.kt +++ b/compiler/src/test/resources/tests/replaces/auto_bind.with_wildcard/Repository.kt @@ -18,7 +18,7 @@ interface Repository @AutoBindIntoMap( inComponent = FragmentComponent::class, asTypes = [Callable::class], - bindGenericAs = BindGenericAs.TypeAndWildcard + bindGenericAs = BindGenericAs.ExactTypeAndWildcard ) @StringKey("repository") @Singleton diff --git a/core/api/core.api b/core/api/core.api index 95735d235..bb5f16f1a 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -48,14 +48,18 @@ public final class se/ansman/dagger/auto/AutoInitialize$Companion { } public final class se/ansman/dagger/auto/BindGenericAs : java/lang/Enum { - public static final field Type Lse/ansman/dagger/auto/BindGenericAs; - public static final field TypeAndWildcard Lse/ansman/dagger/auto/BindGenericAs; + public static final field ExactType Lse/ansman/dagger/auto/BindGenericAs; + public static final field ExactTypeAndWildcard Lse/ansman/dagger/auto/BindGenericAs; public static final field Wildcard Lse/ansman/dagger/auto/BindGenericAs; public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lse/ansman/dagger/auto/BindGenericAs; public static fun values ()[Lse/ansman/dagger/auto/BindGenericAs; } +public abstract interface annotation class se/ansman/dagger/auto/BindGenericAs$Default : java/lang/annotation/Annotation { + public abstract fun value ()Lse/ansman/dagger/auto/BindGenericAs; +} + public final class se/ansman/dagger/auto/HiltWrapper_AutoDaggerModule { public fun ()V } diff --git a/core/src/main/kotlin/se/ansman/dagger/auto/AutoBindIntoMap.kt b/core/src/main/kotlin/se/ansman/dagger/auto/AutoBindIntoMap.kt index ef1de08dc..8722a610e 100644 --- a/core/src/main/kotlin/se/ansman/dagger/auto/AutoBindIntoMap.kt +++ b/core/src/main/kotlin/se/ansman/dagger/auto/AutoBindIntoMap.kt @@ -17,10 +17,12 @@ import kotlin.reflect.KClass * * @param inComponent Which component to install the binding in. Defaults to being inferred based on the scope. * @param asTypes Specifies which supertypes to bind the object as. Required if there are multiple supertypes. - * @param bindGenericAs Specifies how generic supertypes should be bound. Defaults to [BindGenericAs.Type]. + * @param bindGenericAs Specifies how generic supertypes should be bound. Defaults to [BindGenericAs.ExactType] or if the + * target type is annotated with [BindGenericAs.Default]. * @see AutoBind * @see AutoBindIntoSet * @see BindGenericAs + * @see BindGenericAs.Default * @see MapKey * @see IntoMap * @since 0.2 @@ -31,5 +33,5 @@ import kotlin.reflect.KClass public annotation class AutoBindIntoMap( val inComponent: KClass<*> = Nothing::class, val asTypes: Array> = [], - val bindGenericAs: BindGenericAs = BindGenericAs.Type, + val bindGenericAs: BindGenericAs = BindGenericAs.ExactType, ) diff --git a/core/src/main/kotlin/se/ansman/dagger/auto/AutoBindIntoSet.kt b/core/src/main/kotlin/se/ansman/dagger/auto/AutoBindIntoSet.kt index 20d73c193..336cb6b1d 100644 --- a/core/src/main/kotlin/se/ansman/dagger/auto/AutoBindIntoSet.kt +++ b/core/src/main/kotlin/se/ansman/dagger/auto/AutoBindIntoSet.kt @@ -13,11 +13,13 @@ import kotlin.reflect.KClass * * @param inComponent Which component to install the binding in. Defaults to being inferred based on the scope. * @param asTypes Specifies which supertypes to bind the object as. Required if there are multiple supertypes. - * @param bindGenericAs Specifies how generic supertypes should be bound. Defaults to [BindGenericAs.Type]. + * @param bindGenericAs Specifies how generic supertypes should be bound. Defaults to [BindGenericAs.ExactType] or if the + * target type is annotated with [BindGenericAs.Default]. * @see AutoBind * @see AutoBindIntoSet * @see IntoSet * @see BindGenericAs + * @see BindGenericAs.Default * @since 0.2 */ @Target(AnnotationTarget.CLASS) @@ -26,5 +28,5 @@ import kotlin.reflect.KClass public annotation class AutoBindIntoSet( val inComponent: KClass<*> = Nothing::class, val asTypes: Array> = [], - val bindGenericAs: BindGenericAs = BindGenericAs.Type, + val bindGenericAs: BindGenericAs = BindGenericAs.ExactType, ) diff --git a/core/src/main/kotlin/se/ansman/dagger/auto/BindGenericAs.kt b/core/src/main/kotlin/se/ansman/dagger/auto/BindGenericAs.kt index 0ccf7eea9..f54c29f6f 100644 --- a/core/src/main/kotlin/se/ansman/dagger/auto/BindGenericAs.kt +++ b/core/src/main/kotlin/se/ansman/dagger/auto/BindGenericAs.kt @@ -2,14 +2,19 @@ package se.ansman.dagger.auto /** * How generic types are bound when using multibinding such as [AutoBindIntoSet] or [AutoBindIntoMap]. + * + * The default is [ExactType] unless the bound type is annotated with [Default] in which case that is used + * as the default. + * + * @since 0.6.0 */ public enum class BindGenericAs { /** * Only the exact supertype is bound. For example, if the type is `List` then only `List` is bound. * - * This is the default. + * This is the default unless the target type is annotated with [Default]. */ - Type, + ExactType, /** * The type is bound as a wildcard type. For example, if the type is `List` then it's bound as `List<*>`. @@ -20,5 +25,68 @@ public enum class BindGenericAs { * The type is bound as the exact supertype and as a wildcard type. For example, if the type is `List` then * it's bound as both `List` and `List<*>`. */ - TypeAndWildcard + ExactTypeAndWildcard; + + /** + * Specifies the default [AutoBindIntoSet.bindGenericAs] and [AutoBindIntoMap.bindGenericAs] for the annotated type. + * + * This is useful if you know that the generic is always unique or if it's always shared. + * For example, an `Interceptor` should probably be bound as [ExactType] because you want to get all + * interceptors for a given type where as `Resource` should probably be bound as [Wildcard] since + * you're likely interested in getting all resources because there are no duplicates. + * + * You can only add this to generic types. + * + * Example when using wildcard: + * ``` + * @BindGenericAs.Default(BindGenericAs.Wildcard) + * interface Resource { + * fun produce(): T + * fun close() + * } + * + * @AutoBindIntoSet // Will bind this as Resource<*> instead of Resource + * class SomeResource @Inject constructor() : Resource { + * ... + * override fun close() { ... } + * } + * + * class ResourceCloser @Inject constructor( + * val resources: Set<@JvmSuppressWildcards Resource<*>> + * ) { + * fun closeAllResources() { + * resources.forEach { it.close() } + * } + * } + * ``` + * + * Example when using exact type: + * ``` + * @BindGenericAs.Default(BindGenericAs.Type) + * interface Interceptor { + * fun intercept(value: T): T + * } + * + * @AutoBindIntoSet // Will bind this as Interceptor + * class SomeInterceptor @Inject constructor() : Interceptor { + * ... + * } + * + * class SomeOperation @Inject constructor( + * val interceptors: Set<@JvmSuppressWildcards Interceptor> + * ) { + * fun performOperation(value: SomeThing): SomeThing { + * return interceptors.fold(value) { v, interceptor -> interceptor.intercept(v) } + * } + * } + * ``` + * + * @see AutoBindIntoSet + * @see AutoBindIntoMap + * @since 0.10.0 + */ + @MustBeDocumented + @Target(AnnotationTarget.CLASS) + @Retention(AnnotationRetention.BINARY) + public annotation class Default(val value: BindGenericAs) } \ No newline at end of file diff --git a/core/src/main/kotlin/se/ansman/dagger/auto/DefaultBindGenericAs.kt b/core/src/main/kotlin/se/ansman/dagger/auto/DefaultBindGenericAs.kt new file mode 100644 index 000000000..36255a164 --- /dev/null +++ b/core/src/main/kotlin/se/ansman/dagger/auto/DefaultBindGenericAs.kt @@ -0,0 +1,2 @@ +package se.ansman.dagger.auto + diff --git a/src/doc/docs/usage/auto-bind.md b/src/doc/docs/usage/auto-bind.md index 144020306..786c017da 100644 --- a/src/doc/docs/usage/auto-bind.md +++ b/src/doc/docs/usage/auto-bind.md @@ -94,21 +94,34 @@ object DirectExecutor : Executor { ``` ## Generics -When using multibindings, it's sometimes useful to be able to bind generic types as wildcard. +When using multibindings, it's often useful to be able to bind generic types as wildcard. Auto Dagger will, by default, bind the exact type of the supertype. But using the `bindGenericAs` parameter you can chose to bind it as a wildcard instead. +For types you own, it's sometimes useful to specify a different default for `bindGenericAs`. For example, say you have +a `Resource` interface. You probably want to always bind this using wildcards since every resource will have a unique +type for `T`. So you can annotate `Resource` with `@BindGenericAs.Default(BindGenericAs.Wildcard)` to indicate +that, unless specified explicitly, it should be bound as `Resource<*>`. + ```kotlin // This will bind StringCallable as Callable<*> @AutoBindIntoSet(bindGenericAs = BindGenericAs.Wildcard) class StringCallable @Inject constructor() : Callable { override fun call(): String = "" } + +// For types you own you can specify @BindGenericAs.Default to change the default. +@BindGenericAs.Default(BindGenericAs.Wildcard) +interface Resource + +// This will be bound as Resource<*> since the default has been set to wildcard for Resource. +@AutoBindIntoSet +class SomeResource @Inject constructor() : Resource ``` There are 3 options for `bindGenericAs`: -- `Type` - Binds the object to the exact supertype (`Callable` in the example above). This is the default. +- `Type` - Binds the object to the exact supertype (`Callable` in the example above). This is the default unless set with `@BindGenericAs.Default`. - `Wildcard` - Binds the object using wildcards (`Callable<*>` in the example above). - `TypeAndWildcard` - Binds the object as both the exact type and using wildcards. \ No newline at end of file