From e2cc60b9a6f7115d383a076bf298cbd6faded853 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 23 Dec 2023 19:44:37 -0600 Subject: [PATCH 1/8] Rename file --- ...SubcomponentGenerator.kt => ContributesSubcomponentCodeGen.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename compiler/src/main/java/com/squareup/anvil/compiler/codegen/{ContributesSubcomponentGenerator.kt => ContributesSubcomponentCodeGen.kt} (100%) diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt similarity index 100% rename from compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGenerator.kt rename to compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt From a5ea612aac86006d1eb3bbace48b01499570a771 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 23 Dec 2023 19:45:31 -0600 Subject: [PATCH 2/8] Structure for Embedded --- .../codegen/ContributesSubcomponentCodeGen.kt | 341 +++++++++--------- 1 file changed, 173 insertions(+), 168 deletions(-) diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt index 54b4e2987..b2a7fb786 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt @@ -5,6 +5,7 @@ import com.squareup.anvil.annotations.ContributesSubcomponent import com.squareup.anvil.compiler.HINT_SUBCOMPONENTS_PACKAGE_PREFIX import com.squareup.anvil.compiler.REFERENCE_SUFFIX import com.squareup.anvil.compiler.SCOPE_SUFFIX +import com.squareup.anvil.compiler.api.AnvilApplicabilityChecker import com.squareup.anvil.compiler.api.AnvilContext import com.squareup.anvil.compiler.api.CodeGenerator import com.squareup.anvil.compiler.api.GeneratedFile @@ -36,207 +37,211 @@ import kotlin.reflect.KClass * Generates a hint for each contributed subcomponent in the `anvil.hint.subcomponent` packages. * This allows the compiler plugin to find all contributed classes a lot faster. */ -@AutoService(CodeGenerator::class) -internal class ContributesSubcomponentGenerator : CodeGenerator { - +internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { override fun isApplicable(context: AnvilContext) = !context.generateFactoriesOnly - override fun generateCode( - codeGenDir: File, - module: ModuleDescriptor, - projectFiles: Collection, - ): Collection { - return projectFiles - .classAndInnerClassReferences(module) - .filter { it.isAnnotatedWith(contributesSubcomponentFqName) } - .onEach { clazz -> - if (!clazz.isInterface() && !clazz.isAbstract()) { - throw AnvilCompilationExceptionClassReference( - message = "${clazz.fqName} is annotated with " + - "@${ContributesSubcomponent::class.simpleName}, but this class is not an interface.", - classReference = clazz, - ) + @AutoService(CodeGenerator::class) + internal class Embedded : CodeGenerator { + + override fun isApplicable(context: AnvilContext) = ContributesSubcomponentCodeGen.isApplicable(context) + + override fun generateCode( + codeGenDir: File, + module: ModuleDescriptor, + projectFiles: Collection, + ): Collection { + return projectFiles + .classAndInnerClassReferences(module) + .filter { it.isAnnotatedWith(contributesSubcomponentFqName) } + .onEach { clazz -> + if (!clazz.isInterface() && !clazz.isAbstract()) { + throw AnvilCompilationExceptionClassReference( + message = "${clazz.fqName} is annotated with " + + "@${ContributesSubcomponent::class.simpleName}, but this class is not an interface.", + classReference = clazz, + ) + } + + if (clazz.visibility() != Visibility.PUBLIC) { + throw AnvilCompilationExceptionClassReference( + message = "${clazz.fqName} is contributed to the Dagger graph, but the " + + "interface is not public. Only public interfaces are supported.", + classReference = clazz, + ) + } + + clazz.annotations + .filter { it.fqName == contributesSubcomponentFqName } + .forEach { annotation -> + annotation.replaces().forEach { + it.checkUsesSameScope(annotation.scope(), clazz) + } + } } + .map { clazz -> + val fileName = clazz.generateClassName().relativeClassName.asString() + val generatedPackage = HINT_SUBCOMPONENTS_PACKAGE_PREFIX + + clazz.packageFqName.safePackageString(dotPrefix = true) + val className = clazz.asClassName() + val classFqName = clazz.fqName.toString() + val propertyName = classFqName.replace('.', '_') + val parentScopeReference = clazz.annotations + .single { it.fqName == contributesSubcomponentFqName } + .parentScope() + val parentScope = parentScopeReference.asClassName() + + clazz.checkParentComponentInterface(clazz.innerClasses(), parentScopeReference) + clazz.checkFactory(clazz.innerClasses()) + + val content = + FileSpec.buildFile(generatedPackage, fileName) { + addProperty( + PropertySpec + .builder( + name = propertyName + REFERENCE_SUFFIX, + type = KClass::class.asClassName().parameterizedBy(className), + ) + .initializer("%T::class", className) + .addModifiers(PUBLIC) + .build(), + ) + + addProperty( + PropertySpec + .builder( + name = propertyName + SCOPE_SUFFIX, + type = KClass::class.asClassName().parameterizedBy(parentScope), + ) + .initializer("%T::class", parentScope) + .addModifiers(PUBLIC) + .build(), + ) + } - if (clazz.visibility() != Visibility.PUBLIC) { - throw AnvilCompilationExceptionClassReference( - message = "${clazz.fqName} is contributed to the Dagger graph, but the " + - "interface is not public. Only public interfaces are supported.", - classReference = clazz, + createGeneratedFile( + codeGenDir = codeGenDir, + packageName = generatedPackage, + fileName = fileName, + content = content, ) } + .toList() + } - clazz.annotations - .filter { it.fqName == contributesSubcomponentFqName } - .forEach { annotation -> - annotation.replaces().forEach { - it.checkUsesSameScope(annotation.scope(), clazz) - } + private fun ClassReference.checkParentComponentInterface( + innerClasses: List, + parentScope: ClassReference, + ) { + val parentComponents = innerClasses + .filter { + it.annotations.any { annotation -> + annotation.fqName == contributesToFqName && annotation.scope() == parentScope } + } + + val componentInterface = when (parentComponents.size) { + 0 -> return + 1 -> parentComponents[0] + else -> throw AnvilCompilationExceptionClassReference( + classReference = this, + message = "Expected zero or one parent component interface within " + + "$fqName being contributed to the parent scope.", + ) } - .map { clazz -> - val fileName = clazz.generateClassName().relativeClassName.asString() - val generatedPackage = HINT_SUBCOMPONENTS_PACKAGE_PREFIX + - clazz.packageFqName.safePackageString(dotPrefix = true) - val className = clazz.asClassName() - val classFqName = clazz.fqName.toString() - val propertyName = classFqName.replace('.', '_') - val parentScopeReference = clazz.annotations - .single { it.fqName == contributesSubcomponentFqName } - .parentScope() - val parentScope = parentScopeReference.asClassName() - - clazz.checkParentComponentInterface(clazz.innerClasses(), parentScopeReference) - clazz.checkFactory(clazz.innerClasses()) - - val content = - FileSpec.buildFile(generatedPackage, fileName) { - addProperty( - PropertySpec - .builder( - name = propertyName + REFERENCE_SUFFIX, - type = KClass::class.asClassName().parameterizedBy(className), - ) - .initializer("%T::class", className) - .addModifiers(PUBLIC) - .build(), - ) - addProperty( - PropertySpec - .builder( - name = propertyName + SCOPE_SUFFIX, - type = KClass::class.asClassName().parameterizedBy(parentScope), - ) - .initializer("%T::class", parentScope) - .addModifiers(PUBLIC) - .build(), - ) - } + val functions = componentInterface.functions + .filter { it.returnType().asClassReference() == this } - createGeneratedFile( - codeGenDir = codeGenDir, - packageName = generatedPackage, - fileName = fileName, - content = content, + if (functions.size >= 2) { + throw AnvilCompilationExceptionClassReference( + classReference = componentInterface, + message = "Expected zero or one function returning the subcomponent $fqName.", ) } - .toList() - } + } - private fun ClassReference.checkParentComponentInterface( - innerClasses: List, - parentScope: ClassReference, - ) { - val parentComponents = innerClasses - .filter { - it.annotations.any { annotation -> - annotation.fqName == contributesToFqName && annotation.scope() == parentScope + private fun ClassReference.checkFactory(innerClasses: List) { + innerClasses + .firstOrNull { it.isAnnotatedWith(daggerSubcomponentFactoryFqName) } + ?.let { factoryClass -> + throw AnvilCompilationExceptionClassReference( + classReference = factoryClass, + message = "Within a class using @${ContributesSubcomponent::class.simpleName} you " + + "must use $contributesSubcomponentFactoryFqName and not " + + "$daggerSubcomponentFactoryFqName.", + ) } - } - val componentInterface = when (parentComponents.size) { - 0 -> return - 1 -> parentComponents[0] - else -> throw AnvilCompilationExceptionClassReference( - classReference = this, - message = "Expected zero or one parent component interface within " + - "$fqName being contributed to the parent scope.", - ) - } - - val functions = componentInterface.functions - .filter { it.returnType().asClassReference() == this } + innerClasses + .firstOrNull { it.isAnnotatedWith(daggerSubcomponentBuilderFqName) } + ?.let { factoryClass -> + throw AnvilCompilationExceptionClassReference( + classReference = factoryClass, + message = "Within a class using @${ContributesSubcomponent::class.simpleName} you " + + "must use $contributesSubcomponentFactoryFqName and not " + + "$daggerSubcomponentBuilderFqName. Builders aren't supported.", + ) + } - if (functions.size >= 2) { - throw AnvilCompilationExceptionClassReference( - classReference = componentInterface, - message = "Expected zero or one function returning the subcomponent $fqName.", - ) - } - } + val factories = innerClasses + .filter { it.isAnnotatedWith(contributesSubcomponentFactoryFqName) } - private fun ClassReference.checkFactory(innerClasses: List) { - innerClasses - .firstOrNull { it.isAnnotatedWith(daggerSubcomponentFactoryFqName) } - ?.let { factoryClass -> - throw AnvilCompilationExceptionClassReference( - classReference = factoryClass, - message = "Within a class using @${ContributesSubcomponent::class.simpleName} you " + - "must use $contributesSubcomponentFactoryFqName and not " + - "$daggerSubcomponentFactoryFqName.", + val factory = when (factories.size) { + 0 -> return + 1 -> factories[0] + else -> throw AnvilCompilationExceptionClassReference( + classReference = this, + message = "Expected zero or one factory within $fqName.", ) } - innerClasses - .firstOrNull { it.isAnnotatedWith(daggerSubcomponentBuilderFqName) } - ?.let { factoryClass -> + if (!factory.isInterface() && !factory.isAbstract()) { throw AnvilCompilationExceptionClassReference( - classReference = factoryClass, - message = "Within a class using @${ContributesSubcomponent::class.simpleName} you " + - "must use $contributesSubcomponentFactoryFqName and not " + - "$daggerSubcomponentBuilderFqName. Builders aren't supported.", + classReference = factory, + message = "A factory must be an interface or an abstract class.", ) } - val factories = innerClasses - .filter { it.isAnnotatedWith(contributesSubcomponentFactoryFqName) } - - val factory = when (factories.size) { - 0 -> return - 1 -> factories[0] - else -> throw AnvilCompilationExceptionClassReference( - classReference = this, - message = "Expected zero or one factory within $fqName.", - ) - } - - if (!factory.isInterface() && !factory.isAbstract()) { - throw AnvilCompilationExceptionClassReference( - classReference = factory, - message = "A factory must be an interface or an abstract class.", - ) - } - - val functions = factory.functions - .let { functions -> - if (factory.isInterface()) { - functions - } else { - functions.filter { it.isAbstract() } + val functions = factory.functions + .let { functions -> + if (factory.isInterface()) { + functions + } else { + functions.filter { it.isAbstract() } + } } - } - - if (functions.size != 1 || functions[0].returnType().asClassReference() != this) { - throw AnvilCompilationExceptionClassReference( - classReference = factory, - message = "A factory must have exactly one abstract function returning the " + - "subcomponent $fqName.", - ) - } - } - private fun ClassReference.checkUsesSameScope( - scope: ClassReference, - subcomponent: ClassReference, - ) { - annotations - .filter { it.fqName == contributesSubcomponentFqName } - .ifEmpty { + if (functions.size != 1 || functions[0].returnType().asClassReference() != this) { throw AnvilCompilationExceptionClassReference( - classReference = subcomponent, - message = "Couldn't find the annotation @ContributesSubcomponent for $fqName.", + classReference = factory, + message = "A factory must have exactly one abstract function returning the " + + "subcomponent $fqName.", ) } - .forEach { annotation -> - val otherScope = annotation.scope() - if (otherScope != scope) { + } + + private fun ClassReference.checkUsesSameScope( + scope: ClassReference, + subcomponent: ClassReference, + ) { + annotations + .filter { it.fqName == contributesSubcomponentFqName } + .ifEmpty { throw AnvilCompilationExceptionClassReference( classReference = subcomponent, - message = "${subcomponent.fqName} with scope ${scope.fqName} wants to replace " + - "$fqName with scope ${otherScope.fqName}. The replacement must use the same scope.", + message = "Couldn't find the annotation @ContributesSubcomponent for $fqName.", ) } - } + .forEach { annotation -> + val otherScope = annotation.scope() + if (otherScope != scope) { + throw AnvilCompilationExceptionClassReference( + classReference = subcomponent, + message = "${subcomponent.fqName} with scope ${scope.fqName} wants to replace " + + "$fqName with scope ${otherScope.fqName}. The replacement must use the same scope.", + ) + } + } + } } } From b2a3600e34a5e5b8d00a8c78d0bef22419cf2d5c Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 23 Dec 2023 19:51:08 -0600 Subject: [PATCH 3/8] Extract code gen logic --- .../codegen/ContributesSubcomponentCodeGen.kt | 81 +++++++++++-------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt index b2a7fb786..92e766092 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt @@ -16,6 +16,7 @@ import com.squareup.anvil.compiler.contributesToFqName import com.squareup.anvil.compiler.daggerSubcomponentBuilderFqName import com.squareup.anvil.compiler.daggerSubcomponentFactoryFqName import com.squareup.anvil.compiler.internal.buildFile +import com.squareup.anvil.compiler.internal.createAnvilSpec import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference import com.squareup.anvil.compiler.internal.reference.ClassReference import com.squareup.anvil.compiler.internal.reference.Visibility @@ -23,10 +24,12 @@ import com.squareup.anvil.compiler.internal.reference.asClassName import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences import com.squareup.anvil.compiler.internal.reference.generateClassName import com.squareup.anvil.compiler.internal.safePackageString +import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.KModifier.PUBLIC import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.asClassName import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.psi.KtFile @@ -79,50 +82,21 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { } } .map { clazz -> - val fileName = clazz.generateClassName().relativeClassName.asString() - val generatedPackage = HINT_SUBCOMPONENTS_PACKAGE_PREFIX + - clazz.packageFqName.safePackageString(dotPrefix = true) + clazz.checkFactory(clazz.innerClasses()) val className = clazz.asClassName() - val classFqName = clazz.fqName.toString() - val propertyName = classFqName.replace('.', '_') val parentScopeReference = clazz.annotations .single { it.fqName == contributesSubcomponentFqName } .parentScope() - val parentScope = parentScopeReference.asClassName() - clazz.checkParentComponentInterface(clazz.innerClasses(), parentScopeReference) - clazz.checkFactory(clazz.innerClasses()) + val parentScope = parentScopeReference.asClassName() - val content = - FileSpec.buildFile(generatedPackage, fileName) { - addProperty( - PropertySpec - .builder( - name = propertyName + REFERENCE_SUFFIX, - type = KClass::class.asClassName().parameterizedBy(className), - ) - .initializer("%T::class", className) - .addModifiers(PUBLIC) - .build(), - ) - - addProperty( - PropertySpec - .builder( - name = propertyName + SCOPE_SUFFIX, - type = KClass::class.asClassName().parameterizedBy(parentScope), - ) - .initializer("%T::class", parentScope) - .addModifiers(PUBLIC) - .build(), - ) - } + val spec = createSpec(className, parentScope) createGeneratedFile( codeGenDir = codeGenDir, - packageName = generatedPackage, - fileName = fileName, - content = content, + packageName = spec.packageName, + fileName = spec.name, + content = spec.toString(), ) } .toList() @@ -244,4 +218,41 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { } } } + + private fun createSpec( + className: ClassName, + parentScope: ClassName, + ): FileSpec { + val fileName = className.generateClassName().simpleName + val generatedPackage = HINT_SUBCOMPONENTS_PACKAGE_PREFIX + + className.packageName.safePackageString(dotPrefix = true) + val classFqName = className.canonicalName + val propertyName = classFqName.replace('.', '_') + + val spec = + FileSpec.createAnvilSpec(generatedPackage, fileName) { + addProperty( + PropertySpec + .builder( + name = propertyName + REFERENCE_SUFFIX, + type = KClass::class.asClassName().parameterizedBy(className), + ) + .initializer("%T::class", className) + .addModifiers(PUBLIC) + .build(), + ) + + addProperty( + PropertySpec + .builder( + name = propertyName + SCOPE_SUFFIX, + type = KClass::class.asClassName().parameterizedBy(parentScope), + ) + .initializer("%T::class", parentScope) + .addModifiers(PUBLIC) + .build(), + ) + } + return spec + } } From 56b08b01f81e260b1d92e1f810ea7652de8c4df9 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 23 Dec 2023 20:13:10 -0600 Subject: [PATCH 4/8] Implement KSP support --- .../codegen/ContributesSubcomponentCodeGen.kt | 197 ++++++++++++++++++ .../codegen/ksp/KSAnnotationExtensions.kt | 23 ++ 2 files changed, 220 insertions(+) diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt index 92e766092..6b07e9427 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt @@ -1,7 +1,16 @@ package com.squareup.anvil.compiler.codegen import com.google.auto.service.AutoService +import com.google.devtools.ksp.getVisibility +import com.google.devtools.ksp.isAbstract +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSType import com.squareup.anvil.annotations.ContributesSubcomponent +import com.squareup.anvil.annotations.ContributesTo import com.squareup.anvil.compiler.HINT_SUBCOMPONENTS_PACKAGE_PREFIX import com.squareup.anvil.compiler.REFERENCE_SUFFIX import com.squareup.anvil.compiler.SCOPE_SUFFIX @@ -10,6 +19,17 @@ import com.squareup.anvil.compiler.api.AnvilContext import com.squareup.anvil.compiler.api.CodeGenerator import com.squareup.anvil.compiler.api.GeneratedFile import com.squareup.anvil.compiler.api.createGeneratedFile +import com.squareup.anvil.compiler.codegen.dagger.MapKeyCreatorCodeGen +import com.squareup.anvil.compiler.codegen.ksp.AnvilSymbolProcessor +import com.squareup.anvil.compiler.codegen.ksp.AnvilSymbolProcessorProvider +import com.squareup.anvil.compiler.codegen.ksp.KspAnvilException +import com.squareup.anvil.compiler.codegen.ksp.getKSAnnotationsByType +import com.squareup.anvil.compiler.codegen.ksp.isAnnotationPresent +import com.squareup.anvil.compiler.codegen.ksp.isInterface +import com.squareup.anvil.compiler.codegen.ksp.parentScope +import com.squareup.anvil.compiler.codegen.ksp.replaces +import com.squareup.anvil.compiler.codegen.ksp.resolveKSClassDeclaration +import com.squareup.anvil.compiler.codegen.ksp.scope import com.squareup.anvil.compiler.contributesSubcomponentFactoryFqName import com.squareup.anvil.compiler.contributesSubcomponentFqName import com.squareup.anvil.compiler.contributesToFqName @@ -31,6 +51,9 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.writeTo +import dagger.Subcomponent import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.psi.KtFile import java.io.File @@ -43,6 +66,180 @@ import kotlin.reflect.KClass internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { override fun isApplicable(context: AnvilContext) = !context.generateFactoriesOnly + internal class KspGenerator( + override val env: SymbolProcessorEnvironment, + ) : AnvilSymbolProcessor() { + @AutoService(SymbolProcessorProvider::class) + class Provider : AnvilSymbolProcessorProvider(ContributesSubcomponentCodeGen, ::KspGenerator) + + override fun processChecked(resolver: Resolver): List { + resolver.getSymbolsWithAnnotation(contributesSubcomponentFqName.asString()) + .filterIsInstance() + .onEach { clazz -> + if (!clazz.isInterface() && !clazz.isAbstract()) { + throw KspAnvilException( + message = "${clazz.qualifiedName?.asString()} is annotated with " + + "@${ContributesSubcomponent::class.simpleName}, but this class is not an interface.", + node = clazz, + ) + } + + if (clazz.getVisibility() != com.google.devtools.ksp.symbol.Visibility.PUBLIC) { + throw KspAnvilException( + message = "${clazz.qualifiedName?.asString()} is contributed to the Dagger graph, but the " + + "interface is not public. Only public interfaces are supported.", + node = clazz, + ) + } + + clazz.getKSAnnotationsByType(ContributesSubcomponent::class) + .forEach { annotation -> + for (it in annotation.replaces()) { + val scope = annotation.scope().resolveKSClassDeclaration() ?: continue + it.checkUsesSameScope(scope, clazz) + } + } + } + .forEach { clazz -> + clazz.checkFactory(clazz.declarations.filterIsInstance()) + val className = clazz.toClassName() + val parentScopeDeclaration = clazz.getKSAnnotationsByType(ContributesSubcomponent::class) + .single() + .parentScope() + clazz.checkParentComponentInterface(clazz.declarations.filterIsInstance(), parentScopeDeclaration) + val parentScope = parentScopeDeclaration.toClassName() + + createSpec(className, parentScope) + .writeTo(env.codeGenerator, aggregating = false, originatingKSFiles = listOf(clazz.containingFile!!)) + } + + return emptyList() + } + + private fun KSClassDeclaration.checkParentComponentInterface( + innerClasses: Sequence, + parentScope: KSClassDeclaration, + ) { + val parentComponents = innerClasses + .filter { + it.getKSAnnotationsByType(ContributesTo::class) + .any { annotation -> + annotation.scope() == parentScope + } + } + .toList() + + val componentInterface = when (parentComponents.size) { + 0 -> return + 1 -> parentComponents[0] + else -> throw KspAnvilException( + node = this, + message = "Expected zero or one parent component interface within " + + "${qualifiedName?.asString()} being contributed to the parent scope.", + ) + } + + // TODO could just declared functions work? + val functions = componentInterface.getAllFunctions() + .filter { + it.returnType?.resolve()?.resolveKSClassDeclaration() == this + } + .toList() + + if (functions.size >= 2) { + throw KspAnvilException( + node = componentInterface, + message = "Expected zero or one function returning the subcomponent ${qualifiedName?.asString()}.", + ) + } + } + + private fun KSClassDeclaration.checkFactory(innerClasses: Sequence) { + innerClasses + .firstOrNull { it.isAnnotationPresent() } + ?.let { factoryClass -> + throw KspAnvilException( + node = factoryClass, + message = "Within a class using @${ContributesSubcomponent::class.simpleName} you " + + "must use $contributesSubcomponentFactoryFqName and not " + + "$daggerSubcomponentFactoryFqName.", + ) + } + + innerClasses + .firstOrNull { it.isAnnotationPresent() } + ?.let { factoryClass -> + throw KspAnvilException( + node = factoryClass, + message = "Within a class using @${ContributesSubcomponent::class.simpleName} you " + + "must use $contributesSubcomponentFactoryFqName and not " + + "$daggerSubcomponentBuilderFqName. Builders aren't supported.", + ) + } + + val factories = innerClasses + .filter { it.isAnnotationPresent() } + .toList() + + val factory = when (factories.size) { + 0 -> return + 1 -> factories[0] + else -> throw KspAnvilException( + node = this, + message = "Expected zero or one factory within ${qualifiedName?.asString()}.", + ) + } + + if (!factory.isInterface() && !factory.isAbstract()) { + throw KspAnvilException( + node = factory, + message = "A factory must be an interface or an abstract class.", + ) + } + + val functions = factory.getAllFunctions() + .let { functions -> + if (factory.isInterface()) { + functions + } else { + functions.filter { it.isAbstract } + } + } + .toList() + + if (functions.size != 1 || functions[0].returnType?.resolve()?.resolveKSClassDeclaration() != this) { + throw KspAnvilException( + node = factory, + message = "A factory must have exactly one abstract function returning the " + + "subcomponent ${qualifiedName?.asString()}.", + ) + } + } + + private fun KSClassDeclaration.checkUsesSameScope( + scope: KSClassDeclaration, + subcomponent: KSClassDeclaration, + ) { + getKSAnnotationsByType(ContributesSubcomponent::class) + .ifEmpty { + throw KspAnvilException( + node = subcomponent, + message = "Couldn't find the annotation @ContributesSubcomponent for ${qualifiedName?.asString()}.", + ) + } + .forEach { annotation -> + val otherScope = annotation.scope().resolveKSClassDeclaration() ?: return@forEach + if (otherScope != scope) { + throw KspAnvilException( + node = subcomponent, + message = "${subcomponent.qualifiedName?.asString()} with scope ${scope.qualifiedName?.asString()} wants to replace " + + "${qualifiedName?.asString()} with scope ${otherScope.qualifiedName?.asString()}. The replacement must use the same scope.", + ) + } + } + } + } + @AutoService(CodeGenerator::class) internal class Embedded : CodeGenerator { diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt index 1497e8fc0..e950a5f33 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt @@ -9,6 +9,12 @@ import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.KSValueArgument import com.squareup.anvil.compiler.internal.daggerScopeFqName import com.squareup.anvil.compiler.internal.mapKeyFqName +import com.squareup.anvil.compiler.internal.reference.AnnotationReference +import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionAnnotationReference +import com.squareup.anvil.compiler.internal.reference.ClassReference +import com.squareup.anvil.compiler.internal.reference.argumentAt +import com.squareup.anvil.compiler.internal.reference.excludeIndex +import com.squareup.anvil.compiler.internal.reference.replacesIndex import com.squareup.anvil.compiler.isAnvilModule import com.squareup.anvil.compiler.qualifierFqName import com.squareup.kotlinpoet.ksp.toClassName @@ -93,6 +99,23 @@ internal fun KSAnnotation.scopeOrNull(): KSType? { internal fun KSAnnotation.boundTypeOrNull(): KSType? = argumentAt("boundType")?.value as? KSType? +@Suppress("UNCHECKED_CAST") +internal fun KSAnnotation.replaces(): List = + (argumentAt("replaces")?.value as? List).orEmpty().mapNotNull { it.resolveKSClassDeclaration() } + +@Suppress("UNCHECKED_CAST") +internal fun KSAnnotation.exclude(): List = + (argumentAt("exclude")?.value as? List).orEmpty().mapNotNull { it.resolveKSClassDeclaration() } + +internal fun KSAnnotation.parentScope(): KSClassDeclaration { + return (argumentAt("parentScope") + ?.value as? KSType)?.resolveKSClassDeclaration() + ?: throw KspAnvilException( + message = "Couldn't find parentScope for $shortName.", + node = this, + ) +} + internal fun KSAnnotation.argumentAt( name: String, ): KSValueArgument? { From b53ca0f7a79478cdc7be6b29b9ed0c79f4faa7b7 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 23 Dec 2023 20:28:55 -0600 Subject: [PATCH 5/8] Update ContributesSubcomponentGeneratorTest --- .../ContributesSubcomponentGeneratorTest.kt | 68 ++++++++++++++----- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt index 387614d39..e7c1668f8 100644 --- a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt +++ b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt @@ -5,13 +5,29 @@ import com.squareup.anvil.annotations.ContributesTo import com.squareup.anvil.compiler.compile import com.squareup.anvil.compiler.hintSubcomponent import com.squareup.anvil.compiler.hintSubcomponentParentScope +import com.squareup.anvil.compiler.internal.testing.AnvilCompilationMode import com.squareup.anvil.compiler.isError import com.squareup.anvil.compiler.subcomponentInterface +import com.squareup.anvil.compiler.walkGeneratedFiles import com.tschuchort.compiletesting.JvmCompilationResult import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized import java.io.File -class ContributesSubcomponentGeneratorTest { +@RunWith(Parameterized::class) +class ContributesSubcomponentGeneratorTest( + private val mode: AnvilCompilationMode, +) { + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data(): List = listOf( + AnvilCompilationMode.Ksp(), + AnvilCompilationMode.Embedded(), + ) + } @Test fun `there is a hint for contributed subcomponents`() { compile( @@ -23,13 +39,12 @@ class ContributesSubcomponentGeneratorTest { @ContributesSubcomponent(Any::class, Unit::class) interface SubcomponentInterface """, + mode = mode, ) { assertThat(subcomponentInterface.hintSubcomponent?.java).isEqualTo(subcomponentInterface) assertThat(subcomponentInterface.hintSubcomponentParentScope).isEqualTo(Unit::class) - val generatedFile = File(outputDirectory.parent, "build/anvil") - .walk() - .single { it.isFile && it.extension == "kt" } + val generatedFile = walkGeneratedFiles(mode).single() assertThat(generatedFile.name).isEqualTo("SubcomponentInterface.kt") } @@ -45,6 +60,7 @@ class ContributesSubcomponentGeneratorTest { @ContributesSubcomponent(Any::class, Unit::class) abstract class SubcomponentInterface """, + mode = mode, ) { assertThat(subcomponentInterface.hintSubcomponent?.java).isEqualTo(subcomponentInterface) assertThat(subcomponentInterface.hintSubcomponentParentScope).isEqualTo(Unit::class) @@ -61,6 +77,7 @@ class ContributesSubcomponentGeneratorTest { @ContributesSubcomponent(parentScope = Unit::class, scope = Any::class) interface SubcomponentInterface """, + mode = mode, ) { assertThat(subcomponentInterface.hintSubcomponent?.java).isEqualTo(subcomponentInterface) assertThat(subcomponentInterface.hintSubcomponentParentScope).isEqualTo(Unit::class) @@ -79,15 +96,14 @@ class ContributesSubcomponentGeneratorTest { interface SubcomponentInterface } """, + mode = mode, ) { val subcomponentInterface = classLoader .loadClass("com.squareup.test.Outer\$SubcomponentInterface") assertThat(subcomponentInterface.hintSubcomponent?.java).isEqualTo(subcomponentInterface) assertThat(subcomponentInterface.hintSubcomponentParentScope).isEqualTo(Unit::class) - val generatedFile = File(outputDirectory.parent, "build/anvil") - .walk() - .single { it.isFile && it.extension == "kt" } + val generatedFile = walkGeneratedFiles(mode).single() assertThat(generatedFile.name).isEqualTo("Outer_SubcomponentInterface.kt") } @@ -103,6 +119,7 @@ class ContributesSubcomponentGeneratorTest { @ContributesSubcomponent(Any::class, Unit::class) class SubcomponentInterface """, + mode = mode, ) { assertThat(exitCode).isError() // Position to the class. @@ -122,6 +139,7 @@ class ContributesSubcomponentGeneratorTest { @ContributesSubcomponent(Any::class, Unit::class) object SubcomponentInterface """, + mode = mode, ) { assertThat(exitCode).isError() // Position to the class. @@ -150,6 +168,7 @@ class ContributesSubcomponentGeneratorTest { @ContributesSubcomponent(Any::class, Unit::class) $visibility interface SubcomponentInterface """, + mode = mode, ) { assertThat(exitCode).isError() // Position to the class. @@ -179,6 +198,7 @@ class ContributesSubcomponentGeneratorTest { } } """, + mode = mode, ) { val parentComponent = subcomponentInterface.parentComponentInterface assertThat(parentComponent).isNotNull() @@ -209,9 +229,10 @@ class ContributesSubcomponentGeneratorTest { } } """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:7:11") + assertThat(messages).contains("Source0.kt:7:") assertThat(messages).contains( "Expected zero or one parent component interface within " + "com.squareup.test.SubcomponentInterface being contributed to the parent scope.", @@ -237,9 +258,10 @@ class ContributesSubcomponentGeneratorTest { } } """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:9:13") + assertThat(messages).contains("Source0.kt:9:") assertThat(messages).contains( "Expected zero or one function returning the subcomponent " + "com.squareup.test.SubcomponentInterface.", @@ -269,9 +291,10 @@ class ContributesSubcomponentGeneratorTest { } } """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:8:11") + assertThat(messages).contains("Source0.kt:8:") assertThat(messages).contains( "Expected zero or one factory within com.squareup.test.SubcomponentInterface.", ) @@ -296,9 +319,10 @@ class ContributesSubcomponentGeneratorTest { } } """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:10:3") + assertThat(messages).contains("Source0.kt:10:") assertThat(messages).contains("A factory must be an interface or an abstract class.") } @@ -318,9 +342,10 @@ class ContributesSubcomponentGeneratorTest { } } """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:10:9") + assertThat(messages).contains("Source0.kt:10:") assertThat(messages).contains("A factory must be an interface or an abstract class.") } } @@ -341,9 +366,10 @@ class ContributesSubcomponentGeneratorTest { interface ComponentFactory } """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:10:13") + assertThat(messages).contains("Source0.kt:10:") assertThat(messages).contains( "A factory must have exactly one abstract function returning the subcomponent " + "com.squareup.test.SubcomponentInterface.", @@ -370,9 +396,10 @@ class ContributesSubcomponentGeneratorTest { } } """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:10:13") + assertThat(messages).contains("Source0.kt:10:") assertThat(messages).contains( "A factory must have exactly one abstract function returning the subcomponent " + "com.squareup.test.SubcomponentInterface.", @@ -398,9 +425,10 @@ class ContributesSubcomponentGeneratorTest { } } """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:10:18") + assertThat(messages).contains("Source0.kt:10:") assertThat(messages).contains( "A factory must have exactly one abstract function returning the subcomponent " + "com.squareup.test.SubcomponentInterface.", @@ -425,9 +453,10 @@ class ContributesSubcomponentGeneratorTest { } } """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:9:13") + assertThat(messages).contains("Source0.kt:9:") assertThat(messages).contains( "Within a class using @ContributesSubcomponent you must use " + "com.squareup.anvil.annotations.ContributesSubcomponent.Factory and not " + @@ -453,9 +482,10 @@ class ContributesSubcomponentGeneratorTest { } } """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:9:13") + assertThat(messages).contains("Source0.kt:9:") assertThat(messages).contains( "Within a class using @ContributesSubcomponent you must use " + "com.squareup.anvil.annotations.ContributesSubcomponent.Factory and not " + @@ -484,6 +514,7 @@ class ContributesSubcomponentGeneratorTest { ) interface SubcomponentInterface2 """, + mode = mode, ) { assertThat(subcomponentInterface1.hintSubcomponent?.java).isEqualTo(subcomponentInterface1) assertThat(subcomponentInterface1.hintSubcomponentParentScope).isEqualTo(Unit::class) @@ -513,9 +544,10 @@ class ContributesSubcomponentGeneratorTest { ) interface SubcomponentInterface2 """, + mode = mode, ) { assertThat(exitCode).isError() - assertThat(messages).contains("Source0.kt:16:11") + assertThat(messages).contains("Source0.kt:16:") assertThat(messages).contains( "com.squareup.test.SubcomponentInterface2 with scope kotlin.Any wants to replace " + "com.squareup.test.SubcomponentInterface1 with scope kotlin.Long. The replacement " + From 5e96832e09e43b56412f8e4bd08dc99751cebb12 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 23 Dec 2023 20:29:00 -0600 Subject: [PATCH 6/8] Fixes --- .../anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt index 6b07e9427..b4421a118 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt @@ -124,7 +124,7 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { .filter { it.getKSAnnotationsByType(ContributesTo::class) .any { annotation -> - annotation.scope() == parentScope + annotation.scope().resolveKSClassDeclaration() == parentScope } } .toList() @@ -167,7 +167,7 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { } innerClasses - .firstOrNull { it.isAnnotationPresent() } + .firstOrNull { it.isAnnotationPresent() } ?.let { factoryClass -> throw KspAnvilException( node = factoryClass, From 4f6f04cdb1eb10b70cd4cf5e9fc3fa26205abd58 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 23 Dec 2023 20:30:21 -0600 Subject: [PATCH 7/8] Formatting --- .../codegen/ContributesSubcomponentCodeGen.kt | 23 +++++++++++-------- .../codegen/ksp/KSAnnotationExtensions.kt | 19 +++++++-------- .../ContributesSubcomponentGeneratorTest.kt | 1 - 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt index b4421a118..79c706503 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt @@ -8,7 +8,6 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration -import com.google.devtools.ksp.symbol.KSType import com.squareup.anvil.annotations.ContributesSubcomponent import com.squareup.anvil.annotations.ContributesTo import com.squareup.anvil.compiler.HINT_SUBCOMPONENTS_PACKAGE_PREFIX @@ -19,7 +18,6 @@ import com.squareup.anvil.compiler.api.AnvilContext import com.squareup.anvil.compiler.api.CodeGenerator import com.squareup.anvil.compiler.api.GeneratedFile import com.squareup.anvil.compiler.api.createGeneratedFile -import com.squareup.anvil.compiler.codegen.dagger.MapKeyCreatorCodeGen import com.squareup.anvil.compiler.codegen.ksp.AnvilSymbolProcessor import com.squareup.anvil.compiler.codegen.ksp.AnvilSymbolProcessorProvider import com.squareup.anvil.compiler.codegen.ksp.KspAnvilException @@ -35,7 +33,6 @@ import com.squareup.anvil.compiler.contributesSubcomponentFqName import com.squareup.anvil.compiler.contributesToFqName import com.squareup.anvil.compiler.daggerSubcomponentBuilderFqName import com.squareup.anvil.compiler.daggerSubcomponentFactoryFqName -import com.squareup.anvil.compiler.internal.buildFile import com.squareup.anvil.compiler.internal.createAnvilSpec import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference import com.squareup.anvil.compiler.internal.reference.ClassReference @@ -49,7 +46,6 @@ import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.KModifier.PUBLIC import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec -import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.ksp.toClassName import com.squareup.kotlinpoet.ksp.writeTo @@ -106,11 +102,18 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { val parentScopeDeclaration = clazz.getKSAnnotationsByType(ContributesSubcomponent::class) .single() .parentScope() - clazz.checkParentComponentInterface(clazz.declarations.filterIsInstance(), parentScopeDeclaration) + clazz.checkParentComponentInterface( + clazz.declarations.filterIsInstance(), + parentScopeDeclaration, + ) val parentScope = parentScopeDeclaration.toClassName() createSpec(className, parentScope) - .writeTo(env.codeGenerator, aggregating = false, originatingKSFiles = listOf(clazz.containingFile!!)) + .writeTo( + env.codeGenerator, + aggregating = false, + originatingKSFiles = listOf(clazz.containingFile!!), + ) } return emptyList() @@ -124,8 +127,8 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { .filter { it.getKSAnnotationsByType(ContributesTo::class) .any { annotation -> - annotation.scope().resolveKSClassDeclaration() == parentScope - } + annotation.scope().resolveKSClassDeclaration() == parentScope + } } .toList() @@ -243,7 +246,9 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { @AutoService(CodeGenerator::class) internal class Embedded : CodeGenerator { - override fun isApplicable(context: AnvilContext) = ContributesSubcomponentCodeGen.isApplicable(context) + override fun isApplicable(context: AnvilContext) = ContributesSubcomponentCodeGen.isApplicable( + context, + ) override fun generateCode( codeGenDir: File, diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt index e950a5f33..40848dd32 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt @@ -9,12 +9,7 @@ import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.KSValueArgument import com.squareup.anvil.compiler.internal.daggerScopeFqName import com.squareup.anvil.compiler.internal.mapKeyFqName -import com.squareup.anvil.compiler.internal.reference.AnnotationReference -import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionAnnotationReference -import com.squareup.anvil.compiler.internal.reference.ClassReference import com.squareup.anvil.compiler.internal.reference.argumentAt -import com.squareup.anvil.compiler.internal.reference.excludeIndex -import com.squareup.anvil.compiler.internal.reference.replacesIndex import com.squareup.anvil.compiler.isAnvilModule import com.squareup.anvil.compiler.qualifierFqName import com.squareup.kotlinpoet.ksp.toClassName @@ -101,15 +96,21 @@ internal fun KSAnnotation.boundTypeOrNull(): KSType? = argumentAt("boundType")?. @Suppress("UNCHECKED_CAST") internal fun KSAnnotation.replaces(): List = - (argumentAt("replaces")?.value as? List).orEmpty().mapNotNull { it.resolveKSClassDeclaration() } + (argumentAt("replaces")?.value as? List).orEmpty().mapNotNull { + it.resolveKSClassDeclaration() + } @Suppress("UNCHECKED_CAST") internal fun KSAnnotation.exclude(): List = - (argumentAt("exclude")?.value as? List).orEmpty().mapNotNull { it.resolveKSClassDeclaration() } + (argumentAt("exclude")?.value as? List).orEmpty().mapNotNull { + it.resolveKSClassDeclaration() + } internal fun KSAnnotation.parentScope(): KSClassDeclaration { - return (argumentAt("parentScope") - ?.value as? KSType)?.resolveKSClassDeclaration() + return ( + argumentAt("parentScope") + ?.value as? KSType + )?.resolveKSClassDeclaration() ?: throw KspAnvilException( message = "Couldn't find parentScope for $shortName.", node = this, diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt index e7c1668f8..e4e4b7d02 100644 --- a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt +++ b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentGeneratorTest.kt @@ -13,7 +13,6 @@ import com.tschuchort.compiletesting.JvmCompilationResult import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -import java.io.File @RunWith(Parameterized::class) class ContributesSubcomponentGeneratorTest( From 1717ce52ff0927966bc6409565e5fc12c1959633 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 3 Feb 2024 04:16:13 -0500 Subject: [PATCH 8/8] Throw on unresolvable types --- .../compiler/codegen/ContributesSubcomponentCodeGen.kt | 5 ++++- .../anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt index 79c706503..d314b3c63 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesSubcomponentCodeGen.kt @@ -91,7 +91,10 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { clazz.getKSAnnotationsByType(ContributesSubcomponent::class) .forEach { annotation -> for (it in annotation.replaces()) { - val scope = annotation.scope().resolveKSClassDeclaration() ?: continue + val scope = annotation.scope().resolveKSClassDeclaration() ?: throw KspAnvilException( + message = "Couldn't resolve the scope for ${clazz.qualifiedName?.asString()}.", + node = clazz, + ) it.checkUsesSameScope(scope, clazz) } } diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt index 40848dd32..8987a318d 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt @@ -96,14 +96,14 @@ internal fun KSAnnotation.boundTypeOrNull(): KSType? = argumentAt("boundType")?. @Suppress("UNCHECKED_CAST") internal fun KSAnnotation.replaces(): List = - (argumentAt("replaces")?.value as? List).orEmpty().mapNotNull { - it.resolveKSClassDeclaration() + (argumentAt("replaces")?.value as? List).orEmpty().map { + it.resolveKSClassDeclaration() ?: throw KspAnvilException("Could not resolve replaces type $it}", this) } @Suppress("UNCHECKED_CAST") internal fun KSAnnotation.exclude(): List = - (argumentAt("exclude")?.value as? List).orEmpty().mapNotNull { - it.resolveKSClassDeclaration() + (argumentAt("exclude")?.value as? List).orEmpty().map { + it.resolveKSClassDeclaration() ?: throw KspAnvilException("Could not resolve exclude $it", this) } internal fun KSAnnotation.parentScope(): KSClassDeclaration {