From c8b03373b5ad8f61d8a1609f76f8721bfde00cb1 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 31 Aug 2024 15:07:26 -0400 Subject: [PATCH] Introduce multi-level caching (#68) * Use classname for scope types * Pre-filter annotation types * Finish making hint cache not hold symbols * Finish multi-level caching for hint loading * Formatting + API * Remove computed scopes check * Filter scopes sooner * Formatting * Fix cache merging * format * Don't defer every time, gather in-round as needed * Clean up ClassName/FqName lookups for declarations --- CHANGELOG.md | 3 + annotations/api/annotations.api | 3 + .../internal/InternalAnvilHintMarker.kt | 7 + .../internal/InternalMergedTypeMarker.kt | 2 +- .../anvil/compiler/ClassScannerKsp.kt | 221 ++++++++++++++---- .../compiler/ClassScanningKspProcessor.kt | 12 +- .../anvil/compiler/KspContributionMerger.kt | 134 ++++++----- .../squareup/anvil/compiler/RecordingCache.kt | 6 +- .../java/com/squareup/anvil/compiler/Utils.kt | 3 + .../codegen/ContributesSubcomponentCodeGen.kt | 3 + .../compiler/codegen/ContributesToCodeGen.kt | 3 + ...butesSubcomponentHandlerSymbolProcessor.kt | 47 ++-- .../anvil/compiler/codegen/ksp/KSCallable.kt | 4 +- .../anvil/compiler/codegen/ksp/KspUtil.kt | 62 ++++- 14 files changed, 371 insertions(+), 139 deletions(-) create mode 100644 annotations/src/main/java/com/squareup/anvil/annotations/internal/InternalAnvilHintMarker.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a66f8c82..eb3b59e1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ -------------- - **New**: Add option to disable contributes subcomponent handling. This can be useful if working in a codebase or project that doesn't use `@ContributeSubcomponent` and thus doesn't need to scan the classpath for them while merging. More details can be found in the `## Options` section of `FORK.md`. +- **Enhancement**: Improve hint caching during contribution merging. Hints from the classpath are now only searched for once rather than every round. +- **Enhancement**: Improve error messaging when class lookups fail. +- **Fix**: Don't use `ClassName.toString()` for `KSClassDeclaration` lookups. - **Fix**: Ensure round processing is correctly reset if no `@ContributeSubcomponent` triggers are found in a given round. This was an edge case that affected projects with custom code generators that generated triggers in a later round. 0.2.6 diff --git a/annotations/api/annotations.api b/annotations/api/annotations.api index d67854a4a..d01187981 100644 --- a/annotations/api/annotations.api +++ b/annotations/api/annotations.api @@ -134,6 +134,9 @@ public abstract interface annotation class com/squareup/anvil/annotations/compat public abstract fun value ()[Lcom/squareup/anvil/annotations/compat/MergeModules; } +public abstract interface annotation class com/squareup/anvil/annotations/internal/InternalAnvilHintMarker : java/lang/annotation/Annotation { +} + public abstract interface annotation class com/squareup/anvil/annotations/internal/InternalBindingMarker : java/lang/annotation/Annotation { public abstract fun isMultibinding ()Z public abstract fun originClass ()Ljava/lang/Class; diff --git a/annotations/src/main/java/com/squareup/anvil/annotations/internal/InternalAnvilHintMarker.kt b/annotations/src/main/java/com/squareup/anvil/annotations/internal/InternalAnvilHintMarker.kt new file mode 100644 index 000000000..5ed74793a --- /dev/null +++ b/annotations/src/main/java/com/squareup/anvil/annotations/internal/InternalAnvilHintMarker.kt @@ -0,0 +1,7 @@ +package com.squareup.anvil.annotations.internal + +/** + * Indicates the annotated property is an anvil hint, used for in-round processing. + */ +@Target(AnnotationTarget.PROPERTY) +public annotation class InternalAnvilHintMarker diff --git a/annotations/src/main/java/com/squareup/anvil/annotations/internal/InternalMergedTypeMarker.kt b/annotations/src/main/java/com/squareup/anvil/annotations/internal/InternalMergedTypeMarker.kt index a4600ad91..9c5322361 100644 --- a/annotations/src/main/java/com/squareup/anvil/annotations/internal/InternalMergedTypeMarker.kt +++ b/annotations/src/main/java/com/squareup/anvil/annotations/internal/InternalMergedTypeMarker.kt @@ -3,7 +3,7 @@ package com.squareup.anvil.annotations.internal import kotlin.reflect.KClass /** - * Metadata bout the origin of a merged type. Useful for testing and can be discarded in production. + * Metadata about the origin of a merged type. Useful for testing and can be discarded in production. */ @Target(AnnotationTarget.CLASS) public annotation class InternalMergedTypeMarker( diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/ClassScannerKsp.kt b/compiler/src/main/java/com/squareup/anvil/compiler/ClassScannerKsp.kt index d0ecf567c..3f634f365 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/ClassScannerKsp.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/ClassScannerKsp.kt @@ -10,6 +10,7 @@ import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.KSTypeReference import com.google.devtools.ksp.symbol.Origin import com.google.devtools.ksp.symbol.Visibility +import com.squareup.anvil.annotations.internal.InternalAnvilHintMarker import com.squareup.anvil.compiler.ClassScannerKsp.GeneratedProperty.ReferenceProperty import com.squareup.anvil.compiler.ClassScannerKsp.GeneratedProperty.ScopeProperty import com.squareup.anvil.compiler.api.AnvilCompilationException @@ -19,20 +20,24 @@ import com.squareup.anvil.compiler.codegen.ksp.KspTracer import com.squareup.anvil.compiler.codegen.ksp.contextualToClassName import com.squareup.anvil.compiler.codegen.ksp.fqName import com.squareup.anvil.compiler.codegen.ksp.getAllCallables +import com.squareup.anvil.compiler.codegen.ksp.getClassDeclarationByName import com.squareup.anvil.compiler.codegen.ksp.isAbstract import com.squareup.anvil.compiler.codegen.ksp.isInterface +import com.squareup.anvil.compiler.codegen.ksp.parentScope import com.squareup.anvil.compiler.codegen.ksp.resolvableAnnotations import com.squareup.anvil.compiler.codegen.ksp.resolveKSClassDeclaration -import com.squareup.anvil.compiler.codegen.ksp.scope +import com.squareup.anvil.compiler.codegen.ksp.scopeClassName import com.squareup.anvil.compiler.codegen.ksp.trace import com.squareup.anvil.compiler.codegen.ksp.type +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ksp.toClassName import org.jetbrains.kotlin.name.FqName internal class ClassScannerKsp( tracer: KspTracer, ) : KspTracer by tracer { private val _hintCache = - RecordingCache>>("Generated Property") + RecordingCache>>("Generated Property") private val parentComponentCache = RecordingCache("ParentComponent") @@ -43,10 +48,26 @@ internal class ClassScannerKsp( * Externally-contributed contributions, which are important to track so that we don't try to * add originating files for them when generating code. */ - private val externalContributions = mutableSetOf() + private val externalContributions = mutableSetOf() + + private var classpathHintCacheWarmed = false + private var inRoundClasspathHintCacheWarmed = false + private var ensureInRoundHintsCaptured = false + private var roundStarted = false + private var roundResolver: Resolver? = null + private val resolver: Resolver get() = roundResolver ?: error("Round not started!") + private var round = 0 fun isExternallyContributed(declaration: KSClassDeclaration): Boolean { - return declaration.fqName in externalContributions + return declaration.toClassName() in externalContributions + } + + /** + * If called, instructs this scanner to capture in-round hints. Should usually be called if the + * consuming processor knows it will have deferred elements in a future round. + */ + fun ensureInRoundHintsCaptured() { + ensureInRoundHintsCaptured = true } private fun KSTypeReference.resolveKClassType(): KSType { @@ -54,34 +75,80 @@ internal class ClassScannerKsp( .arguments.single().type!!.resolve() } - private var hintCacheWarmer: (() -> Unit)? = null - private val hintCache: RecordingCache>> - get() { - hintCacheWarmer?.invoke() - hintCacheWarmer = null - return _hintCache + /** + * In order to limit classpath scanning, we cache the contributed hints from the classpath once + * in a KSP-compatible format (i.e. no holding onto symbols). Separately, we annotate hints + * with [InternalAnvilHintMarker] to pick them up in the current round. + * + * [ClassScanningKspProcessor] in turn ensures that any [InternalAnvilHintMarker]-annotated + * symbols in a given round are passed on to the next round. + * + * The end result is every hint is only processed once into our cache. + */ + @OptIn(KspExperimental::class) + private fun hintCache(): RecordingCache>> { + if (!classpathHintCacheWarmed) { + val newHints = trace("Warming classpath hint cache") { + generateHintCache( + resolver.getDeclarationsFromPackage(HINT_PACKAGE) + .filterIsInstance(), + isClassPathScan = true, + ).also { + log( + "Loaded ${it.values.flatMap { it.values.flatten() }.size} contributed hints from the classpath.", + ) + } + } + mergeNewHints(newHints) + classpathHintCacheWarmed = true } - private var roundStarted = false + findInRoundHints() + return _hintCache + } + + private fun findInRoundHints() { + if (!inRoundClasspathHintCacheWarmed) { + val newHints = trace("Warming in-round hint cache") { + generateHintCache( + resolver.getSymbolsWithAnnotation(internalAnvilHintMarkerClassName.canonicalName) + .filterIsInstance(), + isClassPathScan = false, + ) + } + mergeNewHints(newHints) + inRoundClasspathHintCacheWarmed = true + } + } fun startRound(resolver: Resolver) { if (roundStarted) return + round++ roundStarted = true - hintCacheWarmer = { - _hintCache += trace("Warming hint cache") { - generateHintCache(resolver) + roundResolver = resolver + } + + private fun mergeNewHints( + newHints: Map>>, + ) { + for ((annotation, hints) in newHints) { + for ((scope, contributedTypes) in hints) { + _hintCache.mutate { rawCache -> + rawCache.getOrPut(annotation, ::mutableMapOf) + .getOrPut(scope, ::mutableSetOf) + .addAll(contributedTypes) + } } } } - @OptIn(KspExperimental::class) private fun generateHintCache( - resolver: Resolver, - ): MutableMap>> { - val contributedTypes = resolver.getDeclarationsFromPackage(HINT_PACKAGE) - .filterIsInstance() + properties: Sequence, + isClassPathScan: Boolean, + ): MutableMap>> { + val contributedTypes = properties .mapNotNull(GeneratedProperty::from) .groupBy(GeneratedProperty::baseName) - .map { (name, properties) -> + .mapNotNull { (name, properties) -> val refProp = properties.filterIsInstance() // In some rare cases we can see a generated property for the same identifier. // Filter them just in case, see https://github.com/square/anvil/issues/460 and @@ -98,27 +165,67 @@ internal class ClassScannerKsp( message = "Couldn't find any scope for a generated hint: ${properties[0].baseName}.", ) } - .mapTo(mutableSetOf()) { + .mapToSet { it.declaration.type.resolveKClassType() + .contextualToClassName(it.declaration) + } + + val declaration = refProp.declaration.type + .resolveKClassType() + .resolveKSClassDeclaration()!! + + val className = declaration.toClassName() + + if (isClassPathScan && (declaration.origin == Origin.KOTLIN_LIB || declaration.origin == Origin.JAVA_LIB)) { + externalContributions += className + } + + var contributedSubcomponentData: ContributedType.ContributedSubcomponentData? = null + val contributingAnnotationTypes = mutableSetOf() + val contributesToData = mutableSetOf() + var isDaggerModule = false + + declaration.resolvableAnnotations + .forEach { annotation -> + val type = annotation.annotationType + .contextualToClassName().fqName + if (type == daggerModuleFqName) { + isDaggerModule = true + } else if (type in CONTRIBUTION_ANNOTATIONS) { + contributingAnnotationTypes += type + if (type == contributesSubcomponentFqName) { + val scope = annotation.scopeClassName() + val parentScope = annotation.parentScope().toClassName() + contributedSubcomponentData = ContributedType.ContributedSubcomponentData( + scope = scope, + parentScope = parentScope, + ) + } else if (type == contributesToFqName) { + val scope = annotation.scopeClassName() + contributesToData += ContributedType.ContributesToData(scope = scope) + } + } } + if (contributingAnnotationTypes.isEmpty()) return@mapNotNull null + ContributedType( baseName = name, - reference = refProp.declaration.type - .resolveKClassType() - .resolveKSClassDeclaration()!!, + className = className, scopes = scopes, + contributingAnnotationTypes = contributingAnnotationTypes, + isInterface = declaration.isInterface(), + isDaggerModule = isDaggerModule, + contributedSubcomponentData = contributedSubcomponentData, + contributesToData = contributesToData, ) } val contributedTypesByAnnotation = - mutableMapOf>>() + mutableMapOf>>() for (contributed in contributedTypes) { - contributed.reference.resolvableAnnotations - .forEach { annotation -> - val type = annotation.annotationType - .contextualToClassName().fqName - if (type !in CONTRIBUTION_ANNOTATIONS) return@forEach + contributed.contributingAnnotationTypes + .forEach { type -> for (scope in contributed.scopes) { contributedTypesByAnnotation.getOrPut(type, ::mutableMapOf) .getOrPut(scope, ::mutableSetOf) @@ -131,15 +238,35 @@ internal class ClassScannerKsp( data class ContributedType( val baseName: String, - val reference: KSClassDeclaration, - val scopes: Set, - ) + val className: ClassName, + val scopes: Set, + val contributingAnnotationTypes: Set, + val isInterface: Boolean, + val isDaggerModule: Boolean, + val contributedSubcomponentData: ContributedSubcomponentData?, + val contributesToData: Set, + ) { + data class ContributesToData( + val scope: ClassName, + ) + + data class ContributedSubcomponentData( + val scope: ClassName, + val parentScope: ClassName, + ) + } fun endRound() { - hintCacheWarmer = null + // If we generate any hint markers, we need to pass them on to the next round for the class + // scanner + if (ensureInRoundHintsCaptured) { + findInRoundHints() + } roundStarted = false + roundResolver = null + inRoundClasspathHintCacheWarmed = false + ensureInRoundHintsCaptured = false log(_hintCache.statsString()) - _hintCache.clear() } /** @@ -148,21 +275,17 @@ internal class ClassScannerKsp( */ fun findContributedClasses( annotation: FqName, - scope: KSType?, - ): Sequence { + scope: ClassName?, + ): Sequence { return trace("Processing contributed classes for ${annotation.shortName().asString()}") { - val typesByScope = hintCache[annotation] ?: emptyMap() - typesByScope.filterKeys { scope == null || it == scope } + val typesByScope = hintCache()[annotation] ?: emptyMap() + typesByScope.filterKeys { + scope == null || it == scope + } .values .asSequence() .flatten() - .map { it.reference } - .distinctBy { it.qualifiedName?.asString() } - .onEach { clazz -> - if (clazz.origin == Origin.KOTLIN_LIB || clazz.origin == Origin.JAVA_LIB) { - externalContributions.add(clazz.fqName) - } - } + .distinctBy { it.className } } } @@ -219,7 +342,7 @@ internal class ClassScannerKsp( resolver: Resolver, componentClass: KSClassDeclaration, creatorClass: KSClassDeclaration?, - parentScopeType: KSType?, + parentScopeType: ClassName?, ): KSClassDeclaration? = trace( "Finding parent component interface for ${componentClass.simpleName.asString()}", ) { @@ -228,7 +351,7 @@ internal class ClassScannerKsp( // Can't use getOrPut because it doesn't differentiate between absent and null if (fqName in parentComponentCache) { parentComponentCache.hit() - return parentComponentCache[fqName]?.let { resolver.getClassDeclarationByName(it.asString()) } + return parentComponentCache[fqName]?.let { resolver.getClassDeclarationByName(it) } } else { parentComponentCache.miss() } @@ -240,7 +363,7 @@ internal class ClassScannerKsp( .filter { nestedClass -> nestedClass.resolvableAnnotations .any { - it.fqName == contributesToFqName && (if (parentScopeType != null) it.scope() == parentScopeType else true) + it.fqName == contributesToFqName && (if (parentScopeType != null) it.scopeClassName() == parentScopeType else true) } } .toList() diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/ClassScanningKspProcessor.kt b/compiler/src/main/java/com/squareup/anvil/compiler/ClassScanningKspProcessor.kt index ccd3b5f83..a37331aa9 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/ClassScanningKspProcessor.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/ClassScanningKspProcessor.kt @@ -45,8 +45,9 @@ internal class ClassScanningKspProcessor( // Extensions to run val extensions = extensions(env, context) - val enableContributesSubcomponentHandling = env.options[OPTION_ENABLE_CONTRIBUTES_SUBCOMPONENT_MERGING] - ?.toBoolean() ?: true + val enableContributesSubcomponentHandling = + env.options[OPTION_ENABLE_CONTRIBUTES_SUBCOMPONENT_MERGING] + ?.toBoolean() ?: true // ContributesSubcomponent handler, which will always be run but needs to conditionally run // within KspContributionMerger if it's going to run. @@ -80,7 +81,12 @@ internal class ClassScanningKspProcessor( override fun processChecked(resolver: Resolver): List { classScanner.startRound(resolver) return delegates.flatMap { it.process(resolver) } - .also { classScanner.endRound() } + .also { + if (it.isNotEmpty()) { + classScanner.ensureInRoundHintsCaptured() + } + classScanner.endRound() + } } override fun finish() { diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/KspContributionMerger.kt b/compiler/src/main/java/com/squareup/anvil/compiler/KspContributionMerger.kt index 34998032a..8e1b1dbd4 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/KspContributionMerger.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/KspContributionMerger.kt @@ -50,6 +50,7 @@ import com.squareup.anvil.compiler.codegen.ksp.find import com.squareup.anvil.compiler.codegen.ksp.findAll import com.squareup.anvil.compiler.codegen.ksp.fqName import com.squareup.anvil.compiler.codegen.ksp.getAllCallables +import com.squareup.anvil.compiler.codegen.ksp.getClassDeclarationByName import com.squareup.anvil.compiler.codegen.ksp.getClassesWithAnnotations import com.squareup.anvil.compiler.codegen.ksp.getKSAnnotationsByType import com.squareup.anvil.compiler.codegen.ksp.includes @@ -61,10 +62,12 @@ import com.squareup.anvil.compiler.codegen.ksp.modules import com.squareup.anvil.compiler.codegen.ksp.parentScope import com.squareup.anvil.compiler.codegen.ksp.replaces import com.squareup.anvil.compiler.codegen.ksp.reportableReturnTypeNode +import com.squareup.anvil.compiler.codegen.ksp.requireClassDeclaration import com.squareup.anvil.compiler.codegen.ksp.resolvableAnnotations import com.squareup.anvil.compiler.codegen.ksp.resolveKSClassDeclaration import com.squareup.anvil.compiler.codegen.ksp.returnTypeOrNull import com.squareup.anvil.compiler.codegen.ksp.scope +import com.squareup.anvil.compiler.codegen.ksp.scopeClassName import com.squareup.anvil.compiler.codegen.ksp.superTypesExcludingAny import com.squareup.anvil.compiler.codegen.ksp.toFunSpec import com.squareup.anvil.compiler.codegen.ksp.toPropertySpec @@ -76,6 +79,7 @@ import com.squareup.anvil.compiler.internal.createAnvilSpec import com.squareup.anvil.compiler.internal.findRawType import com.squareup.anvil.compiler.internal.joinSimpleNames import com.squareup.anvil.compiler.internal.mergedClassName +import com.squareup.anvil.compiler.internal.reference.asClassId import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock @@ -181,17 +185,17 @@ internal class KspContributionMerger( // we need to process now and just point at what will eventually be generated // Mapping of scopes to contributing interfaces in this round - val contributedInterfacesInRound = mutableMapOf>() + val contributedInterfacesInRound = mutableMapOf>() // Mapping of scopes to contributing modules in this round - val contributedModulesInRound = mutableMapOf>() + val contributedModulesInRound = mutableMapOf>() trace("Finding in-round contributions") { resolver.getClassesWithAnnotations(contributesToFqName) .forEach { contributedTypeInRound -> val contributesToScopes = contributedTypeInRound.getKSAnnotationsByType(ContributesTo::class) - .map(KSAnnotation::scope) + .map(KSAnnotation::scopeClassName) .toSet() if (contributesToScopes.isNotEmpty()) { val isModule = contributedTypeInRound.isAnnotationPresent() || @@ -270,8 +274,8 @@ internal class KspContributionMerger( private fun processClass( resolver: Resolver, mergeAnnotatedClass: KSClassDeclaration, - contributedInterfacesInRound: Map>, - contributedModulesInRound: Map>, + contributedInterfacesInRound: Map>, + contributedModulesInRound: Map>, ): KSAnnotated? = trace("Processing ${mergeAnnotatedClass.simpleName.asString()}") { val mergeComponentAnnotations = mergeAnnotatedClass .findAll(mergeComponentFqName.asString(), mergeSubcomponentFqName.asString()) @@ -370,7 +374,7 @@ internal class KspContributionMerger( mergeAnnotatedClass: KSClassDeclaration, interfaceMergerAnnotations: List, originatingDeclarations: MutableList, - contributedInterfacesInRound: Map>, + contributedInterfacesInRound: Map>, ): ContributedInterfacesResult { if (interfaceMergerAnnotations.isEmpty()) { return ContributedInterfacesResult.EMPTY @@ -428,12 +432,12 @@ internal class KspContributionMerger( resolver: Resolver, declaration: KSClassDeclaration, isModule: Boolean, - contributedModulesInRound: Map>, + contributedModulesInRound: Map>, directMergeSubcomponents: List, ): AnnotationSpec { val daggerAnnotationClassName = annotations[0].daggerAnnotationClassName - val scopes = annotations.map(KSAnnotation::scope) + val scopes = annotations.map(KSAnnotation::scopeClassName) // Any modules that are @MergeModules, we need to include their generated modules instead val predefinedModules = if (isModule) { annotations.flatMap(KSAnnotation::includes) @@ -447,12 +451,12 @@ internal class KspContributionMerger( moduleClassName } else { moduleClassName.mergedClassName().also { mergedModuleClass -> - if (includedModule.asType(emptyList()) in contributedModulesInRound) { + if (includedModule.toClassName() in contributedModulesInRound) { // Module is in this round, so the root module is the one we should add originatingDeclarations += includedModule } else { // Not in this round, try to look up the merged class - resolver.getClassDeclarationByName(mergedModuleClass.canonicalName)?.let { + resolver.getClassDeclarationByName(mergedModuleClass)?.let { originatingDeclarations += it } } @@ -482,12 +486,19 @@ internal class KspContributionMerger( val allContributesAnnotations: List = annotations .asSequence() .flatMap { annotation -> - val scope = annotation.scope() + val scope = annotation.scopeClassName() classScanner .findContributedClasses( annotation = contributesToFqName, scope = scope, ) + .filter { + it.contributesToData.isNotEmpty() && + it.scopes.any { it in scopes } + } + .map { + resolver.requireClassDeclaration(it.className, node = null) + } .plus( contributorsInRound(resolver, contributedModulesInRound, scope, isModule = true), ) @@ -495,7 +506,7 @@ internal class KspContributionMerger( .flatMap { contributedClass -> contributedClass .find(annotationName = contributesToFqName.asString()) - .filter { it.scope() in scopes } + .filter { it.scopeClassName() in scopes } } .filter { contributesAnnotation -> val contributedClass = contributesAnnotation.declaringClass @@ -551,11 +562,11 @@ internal class KspContributionMerger( contributesBindingFqName.asString(), contributesMultibindingFqName.asString(), ) - .map(KSAnnotation::scope) + .map(KSAnnotation::scopeClassName) .plus( excludedClass .find(contributesSubcomponentFqName.asString()) - .map(KSAnnotation::parentScope), + .map { it.parentScope().toClassName() }, ) .any { scope -> scope in scopes } @@ -567,7 +578,7 @@ internal class KspContributionMerger( scopes.joinToString( prefix = "[", postfix = "]", - ) { it.resolveKSClassDeclaration()?.qualifiedName?.asString()!! } + ) { it.toString() } } " + "wants to exclude ${excludedClass.qualifiedName?.asString()}, but the excluded class isn't " + "contributed to the same scope.", @@ -700,7 +711,7 @@ internal class KspContributionMerger( .flatten() .map(ContributedBinding<*>::bindingModule) .map { bindingModule -> - resolver.getClassDeclarationByName(bindingModule.canonicalName)!! + resolver.requireClassDeclaration(bindingModule, node = null) .also { originatingDeclarations += it } @@ -765,31 +776,44 @@ internal class KspContributionMerger( originatingDeclarations: MutableList, resolver: Resolver, mergeAnnotatedClass: KSClassDeclaration, - contributedInterfacesInRound: Map>, + contributedInterfacesInRound: Map>, ): List { - val scopes: Set = mergeAnnotations.mapTo(mutableSetOf(), KSAnnotation::scope) + val scopes: Set = mergeAnnotations.mapToSet(transform = KSAnnotation::scopeClassName) val contributesAnnotations = trace("Finding contributed interfaces from ClassScanner") { mergeAnnotations .flatMap { annotation -> - val scope = annotation.scope() + val scope = annotation.scopeClassName() classScanner .findContributedClasses( annotation = contributesToFqName, scope = scope, ) + .filter { contribution -> + contribution.isInterface && !contribution.isDaggerModule + } + .filter { it.scopes.any { it in scopes } } + .filter { it.contributesToData.isNotEmpty() } + .flatMap { + val declaration = resolver.requireClassDeclaration( + it.className, + node = { annotation }, + ) + declaration.findAll(contributesToFqName.asString()) + } .plus( - contributorsInRound(resolver, contributedInterfacesInRound, scope, isModule = false), + contributorsInRound(resolver, contributedInterfacesInRound, scope, isModule = false) + .filter { clazz -> + clazz.isInterface() && clazz.findAll(daggerModuleFqName.asString()) + .singleOrNull() == null + } + .flatMap { clazz -> + clazz + .findAll(contributesToFqName.asString()) + .filter { it.scopeClassName() in scopes } + }, ) } .asSequence() - .filter { clazz -> - clazz.isInterface() && clazz.findAll(daggerModuleFqName.asString()).singleOrNull() == null - } - .flatMap { clazz -> - clazz - .findAll(contributesToFqName.asString()) - .filter { it.scope() in scopes } - } .onEach { contributeAnnotation -> val contributedClass = contributeAnnotation.declaringClass if (contributedClass.getVisibility() != Visibility.PUBLIC) { @@ -837,7 +861,7 @@ internal class KspContributionMerger( contributesBindingFqName.asString(), contributesMultibindingFqName.asString(), ) - .map(KSAnnotation::scope) + .map(KSAnnotation::scopeClassName) .any { scope -> scope in scopes } if (!contributesToOurScope) { @@ -848,7 +872,7 @@ internal class KspContributionMerger( scopes.joinToString( prefix = "[", postfix = "]", - ) { it.resolveKSClassDeclaration()?.qualifiedName?.asString()!! } + ) { it.toString() } } " + "wants to replace ${classToReplace.qualifiedName?.asString()}, but the replaced class isn't " + "contributed to the same scope.", @@ -873,10 +897,10 @@ internal class KspContributionMerger( contributesBindingFqName.asString(), contributesMultibindingFqName.asString(), ) - .map(KSAnnotation::scope) + .map(KSAnnotation::scopeClassName) .plus( excludedClass.findAll(contributesSubcomponentFqName.asString()) - .map(KSAnnotation::parentScope), + .map { it.parentScope().toClassName() }, ) .any { scope -> scope in scopes } @@ -887,7 +911,7 @@ internal class KspContributionMerger( scopes.joinToString( prefix = "[", postfix = "]", - ) { it.resolveKSClassDeclaration()?.qualifiedName?.asString()!! } + ) { it.toString() } } " + "wants to exclude ${excludedClass.qualifiedName?.asString()}, but the excluded class isn't " + "contributed to the same scope.", @@ -950,7 +974,7 @@ internal class KspContributionMerger( resolver = resolver, ).onEach { contributedInterface -> // If it's resolvable (may not be yet in this round), add it to originating declarations - resolver.getClassDeclarationByName(contributedInterface.canonicalName)?.let { + resolver.getClassDeclarationByName(contributedInterface)?.let { originatingDeclarations += it } }, @@ -1060,7 +1084,7 @@ internal class KspContributionMerger( // We have to do this wonky name resolution because the parent component of the target // merged subcomponent may not be generated yet. val declarationToSearch = contributedSubcomponentData?.originClass?.let { - resolver.getClassDeclarationByName(it.canonicalName) + resolver.getClassDeclarationByName(it) } ?: targetOrigin // If we have a contributed subcomponent for this type, we can use its known // subcomponent data @@ -1194,7 +1218,10 @@ internal class KspContributionMerger( if (contributedSubcomponentData != null) { val contributor = contributedSubcomponentData.contributor if (contributor != null) { - val parentClass = resolver.getClassDeclarationByName(contributor.canonicalName)!! + val parentClass = resolver.requireClassDeclaration( + contributor, + node = { mergeAnnotatedClass }, + ) originatingDeclarations += parentClass var returnType = generatedComponentClassName if (contributedSubcomponentData.componentFactory != null && mergedFactoryClassName != null) { @@ -1317,7 +1344,7 @@ internal class KspContributionMerger( private fun findContributedSubcomponentModules( declaration: KSClassDeclaration, - scopes: List, + scopes: List, resolver: Resolver, ): Sequence { return classScanner @@ -1325,9 +1352,8 @@ internal class KspContributionMerger( annotation = contributesSubcomponentFqName, scope = null, ) - .filter { clazz -> - clazz.find(contributesSubcomponentFqName.asString()) - .any { it.parentScope().asType(emptyList()) in scopes } + .filter { contribution -> + contribution.contributedSubcomponentData!!.parentScope in scopes } .flatMap { contributedSubcomponent -> // @@ -1338,7 +1364,7 @@ internal class KspContributionMerger( // @MergeSubcomponent(scope = Any::class) // interface UserComponent_0536E4Be : UserComponent // - val generatedMergeSubcomponentId = contributedSubcomponent.classId + val generatedMergeSubcomponentId = contributedSubcomponent.className.asClassId() .generatedAnvilSubcomponentClassId(declaration.classId) val generatedMergeSubcomponentDeclaration = resolver.getClassDeclarationByName( @@ -1405,7 +1431,7 @@ internal class KspContributionMerger( val contributedCallables = contributedInterfaces.asSequence() .mapNotNull { contributedInterface -> - resolver.getClassDeclarationByName(contributedInterface.canonicalName) + resolver.getClassDeclarationByName(contributedInterface) } .flatMap { it.getAllCallables() } return (mergeAnnotatedComponent.getAllCallables() + contributedCallables) @@ -1457,7 +1483,7 @@ internal class KspContributionMerger( private fun findContributedSubcomponentParentInterfaces( clazz: KSClassDeclaration, - scopes: Set, + scopes: Set, resolver: Resolver, ): Sequence = trace("Finding contributed subcomponent parent interfaces") { return classScanner @@ -1466,8 +1492,7 @@ internal class KspContributionMerger( scope = null, ) .filter { - it.atLeastOneAnnotation(contributesSubcomponentFqName.asString()).single() - .parentScope().asType(emptyList()) in scopes + it.contributedSubcomponentData!!.parentScope in scopes } .flatMap { contributedSubcomponent -> // @@ -1479,7 +1504,7 @@ internal class KspContributionMerger( // interface UserComponent_0536E4Be : UserComponent // - val generatedMergeSubcomponentId = contributedSubcomponent.classId + val generatedMergeSubcomponentId = contributedSubcomponent.className.asClassId() .generatedAnvilSubcomponentClassId(clazz.classId) val generatedMergeSubcomponentDeclaration = resolver.getClassDeclarationByName( @@ -1533,7 +1558,7 @@ internal class KspContributionMerger( private fun checkSameScope( contributedClass: KSClassDeclaration, classToReplace: KSClassDeclaration, - scopes: List, + scopes: List, ) { val contributesToOurScope = classToReplace .findAll( @@ -1541,7 +1566,7 @@ private fun checkSameScope( contributesBindingFqName.asString(), contributesMultibindingFqName.asString(), ) - .map(KSAnnotation::scope) + .map(KSAnnotation::scopeClassName) .any { scope -> scope in scopes } if (!contributesToOurScope) { @@ -1553,7 +1578,7 @@ private fun checkSameScope( scopes.joinToString( prefix = "[", postfix = "]", - ) { it.resolveKSClassDeclaration()?.qualifiedName?.asString()!! } + ) { it.toString() } } " + "wants to replace ${classToReplace.qualifiedName?.asString()}, but the replaced class isn't " + "contributed to the same scope.", @@ -1699,7 +1724,7 @@ private fun KSClassDeclaration.resolveMergedType(resolver: Resolver): KSClassDec isAnnotationPresent() return if (isMergedType) { resolver.getClassDeclarationByName( - toClassName().mergedClassName().canonicalName, + toClassName().mergedClassName(), ) ?: throw KspAnvilException( message = "Could not find merged module/interface for ${qualifiedName?.asString()}", node = this, @@ -1711,11 +1736,12 @@ private fun KSClassDeclaration.resolveMergedType(resolver: Resolver): KSClassDec private fun contributorsInRound( resolver: Resolver, - contributedSymbolsInRound: Map>, - scope: KSType, + contributedSymbolsInRound: Map>, + scope: ClassName, isModule: Boolean, -): List { +): Sequence { return contributedSymbolsInRound[scope].orEmpty() + .asSequence() .map { contributedSymbol -> val mergeAnnotation = contributedSymbol .getKSAnnotationsByType(MergeModules::class) @@ -2018,7 +2044,7 @@ private data class ContributedSubcomponentData( fun resolveCreatorDeclaration(resolver: Resolver): KSClassDeclaration? { val creator = componentFactory ?: return null - return resolver.getClassDeclarationByName(creator.canonicalName) + return resolver.getClassDeclarationByName(creator) } companion object { diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/RecordingCache.kt b/compiler/src/main/java/com/squareup/anvil/compiler/RecordingCache.kt index 0427f92d8..dde787dd1 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/RecordingCache.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/RecordingCache.kt @@ -3,7 +3,7 @@ package com.squareup.anvil.compiler import kotlin.math.roundToInt internal class RecordingCache(private val name: String) { - val cache: MutableMap = mutableMapOf() + private val cache: MutableMap = mutableMapOf() private var hits = 0 private var misses = 0 @@ -53,4 +53,8 @@ internal class RecordingCache(private val name: String) { operator fun plusAssign(values: Map) { cache += values } + + fun mutate(mutation: (MutableMap) -> Unit) { + mutation(cache) + } } diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt b/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt index bde13022b..1412ea698 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt @@ -8,6 +8,7 @@ import com.squareup.anvil.annotations.MergeComponent import com.squareup.anvil.annotations.MergeSubcomponent import com.squareup.anvil.annotations.compat.MergeInterfaces import com.squareup.anvil.annotations.compat.MergeModules +import com.squareup.anvil.annotations.internal.InternalAnvilHintMarker import com.squareup.anvil.annotations.internal.InternalBindingMarker import com.squareup.anvil.compiler.api.AnvilCompilationException import com.squareup.anvil.compiler.internal.fqName @@ -54,6 +55,8 @@ internal val contributesSubcomponentFactoryFqName = ContributesSubcomponent.Fact internal val contributesSubcomponentFactoryClassName = ContributesSubcomponent.Factory::class.asClassName() internal val internalBindingMarkerFqName = InternalBindingMarker::class.fqName +internal val internalAnvilHintMarkerFqName = InternalAnvilHintMarker::class.fqName +internal val internalAnvilHintMarkerClassName = InternalAnvilHintMarker::class.asClassName() internal val daggerComponentFqName = Component::class.fqName internal val daggerComponentFactoryFqName = Component.Factory::class.fqName internal val daggerComponentBuilderFqName = Component.Builder::class.fqName 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 d0edc7642..71dbc5864 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 @@ -40,6 +40,7 @@ import com.squareup.anvil.compiler.internal.reference.ClassReference import com.squareup.anvil.compiler.internal.reference.Visibility import com.squareup.anvil.compiler.internal.reference.asClassName import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences +import com.squareup.anvil.compiler.internalAnvilHintMarkerClassName import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.KModifier.PUBLIC @@ -436,6 +437,7 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { name = propertyName + REFERENCE_SUFFIX, type = KClass::class.asClassName().parameterizedBy(className), ) + .addAnnotation(internalAnvilHintMarkerClassName) .initializer("%T::class", className) .addModifiers(PUBLIC) .build(), @@ -447,6 +449,7 @@ internal object ContributesSubcomponentCodeGen : AnvilApplicabilityChecker { name = propertyName + SCOPE_SUFFIX, type = KClass::class.asClassName().parameterizedBy(parentScope), ) + .addAnnotation(internalAnvilHintMarkerClassName) .initializer("%T::class", parentScope) .addModifiers(PUBLIC) .build(), diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesToCodeGen.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesToCodeGen.kt index bea50f574..7dc145c87 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesToCodeGen.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ContributesToCodeGen.kt @@ -37,6 +37,7 @@ import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionC import com.squareup.anvil.compiler.internal.reference.Visibility import com.squareup.anvil.compiler.internal.reference.asClassName import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences +import com.squareup.anvil.compiler.internalAnvilHintMarkerClassName import com.squareup.anvil.compiler.mergeModulesFqName import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec @@ -77,6 +78,7 @@ internal object ContributesToCodeGen : AnvilApplicabilityChecker { name = propertyName + REFERENCE_SUFFIX, type = KClass::class.asClassName().parameterizedBy(className), ) + .addAnnotation(internalAnvilHintMarkerClassName) .initializer("%T::class", className) .addModifiers(PUBLIC) .build(), @@ -89,6 +91,7 @@ internal object ContributesToCodeGen : AnvilApplicabilityChecker { name = propertyName + SCOPE_SUFFIX + index, type = KClass::class.asClassName().parameterizedBy(scope), ) + .addAnnotation(internalAnvilHintMarkerClassName) .initializer("%T::class", scope) .addModifiers(PUBLIC) .build(), diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KspContributesSubcomponentHandlerSymbolProcessor.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KspContributesSubcomponentHandlerSymbolProcessor.kt index 1103ad6ff..8d74af480 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KspContributesSubcomponentHandlerSymbolProcessor.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/KspContributesSubcomponentHandlerSymbolProcessor.kt @@ -1,5 +1,6 @@ package com.squareup.anvil.compiler.codegen +import com.google.devtools.ksp.getClassDeclarationByName import com.google.devtools.ksp.isAbstract import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessorEnvironment @@ -8,7 +9,6 @@ import com.google.devtools.ksp.symbol.KSAnnotation import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSFunctionDeclaration import com.google.devtools.ksp.symbol.KSName -import com.google.devtools.ksp.symbol.KSType import com.squareup.anvil.annotations.ContributesSubcomponent import com.squareup.anvil.annotations.MergeSubcomponent import com.squareup.anvil.annotations.internal.InternalContributedSubcomponentMarker @@ -21,7 +21,6 @@ import com.squareup.anvil.compiler.PARENT_COMPONENT import com.squareup.anvil.compiler.codegen.ksp.AnvilSymbolProcessor import com.squareup.anvil.compiler.codegen.ksp.KspAnvilException import com.squareup.anvil.compiler.codegen.ksp.classId -import com.squareup.anvil.compiler.codegen.ksp.contextualToClassName import com.squareup.anvil.compiler.codegen.ksp.declaringClass import com.squareup.anvil.compiler.codegen.ksp.exclude import com.squareup.anvil.compiler.codegen.ksp.find @@ -33,19 +32,22 @@ import com.squareup.anvil.compiler.codegen.ksp.isInterface import com.squareup.anvil.compiler.codegen.ksp.modules import com.squareup.anvil.compiler.codegen.ksp.parentScope import com.squareup.anvil.compiler.codegen.ksp.replaces +import com.squareup.anvil.compiler.codegen.ksp.requireClassDeclaration import com.squareup.anvil.compiler.codegen.ksp.resolvableAnnotations import com.squareup.anvil.compiler.codegen.ksp.resolveKSClassDeclaration import com.squareup.anvil.compiler.codegen.ksp.returnTypeOrNull -import com.squareup.anvil.compiler.codegen.ksp.scope +import com.squareup.anvil.compiler.codegen.ksp.scopeClassName import com.squareup.anvil.compiler.codegen.ksp.trace import com.squareup.anvil.compiler.contributesSubcomponentFqName import com.squareup.anvil.compiler.daggerBindingModuleSpec import com.squareup.anvil.compiler.defaultParentComponentFunctionName +import com.squareup.anvil.compiler.fqName import com.squareup.anvil.compiler.internal.asClassName import com.squareup.anvil.compiler.internal.createAnvilSpec import com.squareup.anvil.compiler.internal.joinSimpleNamesAndTruncate import com.squareup.anvil.compiler.internal.reference.asClassId import com.squareup.anvil.compiler.internal.safePackageString +import com.squareup.anvil.compiler.mapToSet import com.squareup.anvil.compiler.mergeComponentFqName import com.squareup.anvil.compiler.mergeInterfacesFqName import com.squareup.anvil.compiler.mergeSubcomponentFqName @@ -169,7 +171,7 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( .builder(MergeSubcomponent::class) .addMember( "scope = %T::class", - contribution.scope.contextualToClassName(contribution.annotation), + contribution.scope, ) .apply { fun addClassArrayMember( @@ -202,7 +204,7 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( resolver, contribution.clazz, factoryClass?.originalReference, - contribution.parentScopeType, + contribution.parentScope, ) addAnnotation( AnnotationSpec.builder(InternalContributedSubcomponentMarker::class) @@ -289,7 +291,12 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( .flatMap { annotatedClass -> annotatedClass.find(generationTrigger.asString()) .map { annotation -> - Trigger(annotatedClass, annotation.scope(), annotation.exclude().toSet()) + Trigger( + annotatedClass, annotation.scopeClassName(), + annotation.exclude().mapToSet { + it.toClassName() + }, + ) } } } @@ -297,7 +304,7 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( if (triggers.isNotEmpty() && !hasComputedInitialContributions) { hasComputedInitialContributions = true - populateInitialContributions() + populateInitialContributions(resolver) } // Find new contributed subcomponents in this module. If there's a trigger for them, then we @@ -332,7 +339,7 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( .flatMap { contribution -> triggers .filter { trigger -> - trigger.scope == contribution.parentScopeType && contribution.clazz !in trigger.exclusions + trigger.scope == contribution.parentScope && contribution.classClassName !in trigger.exclusions } .map { trigger -> GenerateCodeEvent(trigger, contribution) @@ -431,7 +438,7 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( private fun checkReplacedSubcomponentWasNotAlreadyGenerated( contributedReference: KSClassDeclaration, - replacedReferences: Collection, + replacedReferences: Set, ) { replacedReferences.forEach { replacedReference -> if (processedContributionClasses.any { it == replacedReference }) { @@ -445,7 +452,9 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( } } - private fun populateInitialContributions() = trace("Populate initial contributions") { + private fun populateInitialContributions(resolver: Resolver) = trace( + "Populate initial contributions", + ) { // Find all contributed subcomponents from precompiled dependencies and generate the // necessary code eventually if there's a trigger. contributions += classScanner @@ -453,7 +462,9 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( annotation = contributesSubcomponentFqName, scope = null, ) - .map { clazz -> + .map { contribution -> + // TODO can we push up this data into ContributedType? + val clazz = resolver.requireClassDeclaration(contribution.className, node = null) Contribution( annotation = clazz.resolvableAnnotations.single { it.fqName == contributesSubcomponentFqName }, ) @@ -483,8 +494,8 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( private class Trigger( val clazz: KSClassDeclaration, - val scope: KSType, - val exclusions: Set, + val scope: ClassName, + val exclusions: Set, ) { val clazzFqName = clazz.fqName @@ -515,11 +526,11 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( private class Contribution(val annotation: KSAnnotation) { val clazz = annotation.declaringClass val classClassName = clazz.toClassName() - val scope = annotation.scope() - val parentScopeType = annotation.parentScope().asType(emptyList()) + val scope = annotation.scopeClassName() + val parentScope = annotation.parentScope().toClassName() override fun toString(): String { - return "Contribution(class=$classClassName, scope=$scope, parentScope=$parentScopeType)" + return "Contribution(class=$classClassName, scope=$scope, parentScope=$parentScope)" } override fun equals(other: Any?): Boolean { @@ -529,7 +540,7 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( other as Contribution if (scope != other.scope) return false - if (parentScopeType != other.parentScopeType) return false + if (parentScope != other.parentScope) return false if (classClassName != other.classClassName) return false return true @@ -537,7 +548,7 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor( override fun hashCode(): Int { var result = scope.hashCode() - result = 31 * result + parentScopeType.hashCode() + result = 31 * result + parentScope.hashCode() result = 31 * result + classClassName.hashCode() return result } diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSCallable.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSCallable.kt index 03459a745..07a98667f 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSCallable.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSCallable.kt @@ -1,6 +1,5 @@ package com.squareup.anvil.compiler.codegen.ksp -import com.google.devtools.ksp.getClassDeclarationByName import com.google.devtools.ksp.isAbstract import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.KSClassDeclaration @@ -22,8 +21,7 @@ internal sealed class KSCallable( val isFunction: Boolean, ) { fun materialize(resolver: Resolver): KSCallable { - val clazz = resolver.getClassDeclarationByName(containingClass.asString()) - ?: error("Could not materialize class $containingClass") + val clazz = resolver.requireClassDeclaration(containingClass, node = null) return if (isFunction) { val declaration = clazz.getAllFunctions().find { it.qualifiedName?.asString() == declarationName } diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KspUtil.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KspUtil.kt index 136ee201e..b1823d34e 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KspUtil.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KspUtil.kt @@ -1,6 +1,7 @@ package com.squareup.anvil.compiler.codegen.ksp import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getClassDeclarationByName import com.google.devtools.ksp.getDeclaredFunctions import com.google.devtools.ksp.isAnnotationPresent import com.google.devtools.ksp.isConstructor @@ -329,18 +330,21 @@ internal fun KSAnnotated.mergeAnnotations(): List { /** * Returns a sequence of [KSAnnotation] types that are not error types. */ -internal val KSAnnotated.resolvableAnnotations: Sequence get() = annotations - .filterNot { it.annotationType.resolve().isError } +internal val KSAnnotated.resolvableAnnotations: Sequence + get() = annotations + .filterNot { it.annotationType.resolve().isError } -internal val KSAnnotation.fqName: FqName get() = - annotationType.resolve().resolveKSClassDeclaration()!!.fqName +internal val KSAnnotation.fqName: FqName + get() = + annotationType.resolve().resolveKSClassDeclaration()!!.fqName internal val KSType.fqName: FqName get() = resolveKSClassDeclaration()!!.fqName -internal val KSClassDeclaration.fqName: FqName get() { - // Call resolveKSClassDeclaration to ensure we follow typealiases - return resolveKSClassDeclaration()!! - .toClassName() - .fqName -} +internal val KSClassDeclaration.fqName: FqName + get() { + // Call resolveKSClassDeclaration to ensure we follow typealiases + return resolveKSClassDeclaration()!! + .toClassName() + .fqName + } /** * A contextual alternative to [KSTypeReference.toTypeName] that uses [KSType.contextualToTypeName] @@ -392,3 +396,41 @@ private fun KSType.checkErrorType(origin: KSNode) { internal val KSFunctionDeclaration.reportableReturnTypeNode: KSNode get() = returnType ?: this + +internal fun Resolver.getClassDeclarationByName(className: ClassName): KSClassDeclaration? { + return getClassDeclarationByName(className.canonicalName) +} + +internal fun Resolver.requireClassDeclaration(className: ClassName, node: (() -> KSNode)?): KSClassDeclaration { + return getClassDeclarationByName(className) + ?: run { + val message = "Could not find class '$className'" + if (node != null) { + throw KspAnvilException( + message = message, + node = node(), + ) + } else { + error(message) + } + } +} + +internal fun Resolver.getClassDeclarationByName(fqName: FqName): KSClassDeclaration? { + return getClassDeclarationByName(fqName.asString()) +} + +internal fun Resolver.requireClassDeclaration(fqName: FqName, node: KSNode?): KSClassDeclaration { + return getClassDeclarationByName(fqName) + ?: run { + val message = "Could not find class '${fqName.asString()}'" + if (node != null) { + throw KspAnvilException( + message = message, + node = node, + ) + } else { + error(message) + } + } +}