From 03153742bcfe334295a7882fa0432e7e8674a1f8 Mon Sep 17 00:00:00 2001 From: Ralf Wondratschek Date: Tue, 10 Jan 2023 21:42:43 -0800 Subject: [PATCH 1/3] Make the `declaringClass` in `TypeReference` nullable. This is necessary to support top-level properties and functions. --- .../internal/reference/ClassReference.kt | 4 +- .../internal/reference/FunctionReference.kt | 4 +- .../internal/reference/ParameterReference.kt | 4 +- .../internal/reference/PropertyReference.kt | 4 +- .../reference/TypeParameterReference.kt | 6 +-- .../internal/reference/TypeReference.kt | 54 ++++++++++++------- .../codegen/dagger/BindsMethodValidator.kt | 2 +- 7 files changed, 48 insertions(+), 30 deletions(-) diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ClassReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ClassReference.kt index 91d9bf407..d5507b538 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ClassReference.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ClassReference.kt @@ -167,7 +167,7 @@ public sealed class ClassReference : Comparable, AnnotatedRefere } private val directSuperTypeReferences: List by lazy(NONE) { - clazz.superTypeListEntries.mapNotNull { it.typeReference?.toTypeReference(this) } + clazz.superTypeListEntries.mapNotNull { it.typeReference?.toTypeReference(this, module) } } private val enclosingClassesWithSelf by lazy(NONE) { @@ -267,7 +267,7 @@ public sealed class ClassReference : Comparable, AnnotatedRefere } private val directSuperTypeReferences: List by lazy(NONE) { - clazz.typeConstructor.supertypes.map { it.toTypeReference(this) } + clazz.typeConstructor.supertypes.map { it.toTypeReference(this, module) } .filterNot { it.asClassReference().fqName.asString() == "kotlin.Any" } } diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionReference.kt index e281b9b35..09834d23c 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionReference.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionReference.kt @@ -97,7 +97,7 @@ public sealed class FunctionReference : AnnotatedReference { } override val returnType: TypeReference.Psi? by lazy(NONE) { - function.typeReference?.toTypeReference(declaringClass) + function.typeReference?.toTypeReference(declaringClass, module) } override val parameters: List by lazy(NONE) { @@ -141,7 +141,7 @@ public sealed class FunctionReference : AnnotatedReference { } override val returnType: TypeReference.Descriptor? by lazy(NONE) { - function.returnType?.toTypeReference(declaringClass) + function.returnType?.toTypeReference(declaringClass, module) } internal val overriddenFunctions by lazy(NONE) { diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ParameterReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ParameterReference.kt index f3781c361..d08498865 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ParameterReference.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ParameterReference.kt @@ -77,7 +77,7 @@ public sealed class ParameterReference : AnnotatedReference { } override val type: TypeReference.Psi? by lazy(NONE) { - parameter.typeReference?.toTypeReference(declaringFunction.declaringClass) + parameter.typeReference?.toTypeReference(declaringFunction.declaringClass, module) } } @@ -94,7 +94,7 @@ public sealed class ParameterReference : AnnotatedReference { } override val type: TypeReference.Descriptor? by lazy(NONE) { - parameter.type.toTypeReference(declaringFunction.declaringClass) + parameter.type.toTypeReference(declaringFunction.declaringClass, module) } } } diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt index 88ce4daf3..b82675ea7 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt @@ -90,7 +90,7 @@ public sealed class PropertyReference : AnnotatedReference { } override val type: TypeReference? by lazy(NONE) { - property.typeReference?.toTypeReference(declaringClass) + property.typeReference?.toTypeReference(declaringClass, module) } override val setterAnnotations: List by lazy(NONE) { @@ -172,7 +172,7 @@ public sealed class PropertyReference : AnnotatedReference { } override val type: TypeReference by lazy(NONE) { - property.type.toTypeReference(declaringClass) + property.type.toTypeReference(declaringClass, module) } override fun visibility(): Visibility { diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeParameterReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeParameterReference.kt index 833147c9e..9f872d00a 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeParameterReference.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeParameterReference.kt @@ -47,7 +47,7 @@ public fun ClassReference.Psi.getTypeParameterReferences(): List { ?.filter { it.fqNameOrNull(module) == null } ?.associateTo(mutableMapOf()) { parameter -> val variableName = parameter.nameAsSafeName.asString() - val extendsBound = parameter.extendsBound?.toTypeReference(this) + val extendsBound = parameter.extendsBound?.toTypeReference(this, module) variableName to listOfNotNull(extendsBound).toMutableList() } ?: mutableMapOf() @@ -59,7 +59,7 @@ public fun ClassReference.Psi.getTypeParameterReferences(): List { val variableName = constraint.subjectTypeParameterName ?.getReferencedName() ?: return@forEach - val extendsBound = constraint.boundTypeReference?.toTypeReference(this) + val extendsBound = constraint.boundTypeReference?.toTypeReference(this, module) ?: return@forEach boundsByVariableName @@ -78,7 +78,7 @@ public fun TypeParameterDescriptor.toTypeParameterReference( ): Descriptor { return Descriptor( name = name.asString(), - upperBounds = upperBounds.map { it.toTypeReference(declaringClass) }, + upperBounds = upperBounds.map { it.toTypeReference(declaringClass, declaringClass.module) }, declaringClass = declaringClass ) } diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeReference.kt index 98d448ded..de759bb12 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeReference.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TypeReference.kt @@ -50,7 +50,7 @@ import kotlin.LazyThreadSafetyMode.NONE @ExperimentalAnvilApi public sealed class TypeReference { - public abstract val declaringClass: ClassReference + public abstract val declaringClass: ClassReference? /** * If the type refers to class and is not a generic type, then the returned reference @@ -67,7 +67,7 @@ public sealed class TypeReference { */ public abstract val unwrappedTypes: List - public val module: AnvilModuleDescriptor get() = declaringClass.module + public abstract val module: AnvilModuleDescriptor public fun asClassReferenceOrNull(): ClassReference? = classReference public fun asClassReference(): ClassReference = classReference @@ -107,7 +107,8 @@ public sealed class TypeReference { public class Psi internal constructor( public val type: KtTypeReference, - override val declaringClass: ClassReference.Psi + override val declaringClass: ClassReference.Psi?, + override val module: AnvilModuleDescriptor, ) : TypeReference() { override val classReference: ClassReference? by lazy(NONE) { type.fqNameOrNull(module)?.toClassReference(module) @@ -137,12 +138,12 @@ public sealed class TypeReference { if (typeProjection.children.isEmpty() && typeProjection.text == "*") { // Resolve `*` star projections to kotlin.Any similar to the descriptor APIs. val anyDescriptor = module.asAnvilModuleDescriptor().resolveFqNameOrNull(anyFqName)!! - anyDescriptor.defaultType.toTypeReference(declaringClass) + anyDescriptor.defaultType.toTypeReference(declaringClass, module) } else { typeProjection.children .filterIsInstance() .single() - .toTypeReference(declaringClass) + .toTypeReference(declaringClass, module) } } ?: emptyList() @@ -192,9 +193,14 @@ public sealed class TypeReference { type.typeElement?.fqNameOrNull(module) ?.let { return this } + // That's a top level function and there's nothing to resolve. + if (declaringClass == null) return this + // When we fail to resolve the generic type here, we're potentially at the end of a chain of // generics, so we return the current type instead of null. - return resolveGenericTypeReference(implementingClass, declaringClass, type.text) ?: this + return resolveGenericTypeReference( + implementingClass, declaringClass, type.text, module + ) ?: this } override fun isFunctionType(): Boolean = type.typeElement is KtFunctionType @@ -340,7 +346,7 @@ public sealed class TypeReference { if (index >= 0) { unwrappedTypes[index] } else { - typeProjection.type.toTypeReference(declaringClass) + typeProjection.type.toTypeReference(declaringClass, module) } } } @@ -348,7 +354,8 @@ public sealed class TypeReference { public class Descriptor internal constructor( public val type: KotlinType, - override val declaringClass: ClassReference + override val declaringClass: ClassReference?, + override val module: AnvilModuleDescriptor, ) : TypeReference() { override val classReference: ClassReference? by lazy(NONE) { type.classDescriptorOrNull()?.toClassReference(module) @@ -362,11 +369,11 @@ public sealed class TypeReference { get() = typeNameOrNull ?: throw AnvilCompilationExceptionTypReference( typeReference = this, message = "Unable to convert the Kotlin type $type to a type name for declaring class " + - "${declaringClass.fqName}." + "${declaringClass?.fqName}." ) override val unwrappedTypes: List by lazy(NONE) { - type.arguments.map { it.type.toTypeReference(declaringClass) } + type.arguments.map { it.type.toTypeReference(declaringClass, module) } } override fun resolveGenericTypeOrNull(implementingClass: ClassReference): TypeReference? { @@ -418,10 +425,14 @@ public sealed class TypeReference { else -> type } + // That's a top level function and there's nothing to resolve. + if (declaringClass == null) return null + return resolveGenericTypeReference( implementingClass = implementingClass, declaringClass = declaringClass, - parameterName = parameterKotlinType.toString() + parameterName = parameterKotlinType.toString(), + module = module ) } @@ -454,13 +465,19 @@ public sealed class TypeReference { } @ExperimentalAnvilApi -public fun KtTypeReference.toTypeReference(declaringClass: ClassReference.Psi): Psi { - return Psi(this, declaringClass) +public fun KtTypeReference.toTypeReference( + declaringClass: ClassReference.Psi?, + module: AnvilModuleDescriptor +): Psi { + return Psi(this, declaringClass, module) } @ExperimentalAnvilApi -public fun KotlinType.toTypeReference(declaringClass: ClassReference): Descriptor { - return Descriptor(this, declaringClass) +public fun KotlinType.toTypeReference( + declaringClass: ClassReference?, + module: AnvilModuleDescriptor +): Descriptor { + return Descriptor(this, declaringClass, module) } @ExperimentalAnvilApi @@ -487,7 +504,8 @@ public fun AnvilCompilationExceptionTypReference( private fun resolveGenericTypeReference( implementingClass: ClassReference, declaringClass: ClassReference, - parameterName: String + parameterName: String, + module: AnvilModuleDescriptor, ): TypeReference? { // If the class/interface declaring the generic is the receiver class, // then the generic hasn't been set to a concrete type and can't be resolved. @@ -515,7 +533,7 @@ private fun resolveGenericTypeReference( ?.toClassReference(implementingClass.module) ?.let { resolvedDeclaringClass -> resolvedTypeReference - .toTypeReference(resolvedDeclaringClass) + .toTypeReference(resolvedDeclaringClass, module) .resolveTypeReference(implementingClass) } } @@ -530,7 +548,7 @@ private fun resolveGenericTypeReference( ?.containingClassReference(implementingClass) ?.let { resolvedDeclaringClass -> resolvedTypeReference.toTypeReference( - resolvedDeclaringClass + resolvedDeclaringClass, module ).resolveGenericKotlinTypeOrNull(implementingClass) } } diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/BindsMethodValidator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/BindsMethodValidator.kt index 67db5c504..4007ecbc3 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/BindsMethodValidator.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/BindsMethodValidator.kt @@ -116,7 +116,7 @@ internal class BindsMethodValidator : PrivateCodeGenerator() { private fun FunctionReference.Psi.receiverSuperTypes(): Sequence? { return function.receiverTypeReference - ?.toTypeReference(declaringClass) + ?.toTypeReference(declaringClass, module) ?.asClassReference() ?.allSuperTypeClassReferences(includeSelf = true) } From 5bb89d9c3025ad276b47cdb5ac5858c2fc7622c3 Mon Sep 17 00:00:00 2001 From: Ralf Wondratschek Date: Tue, 10 Jan 2023 22:45:46 -0800 Subject: [PATCH 2/3] Allow to query top level functions as references for code generators. Fixes #644 --- CHANGELOG.md | 2 + .../reference/AnvilModuleDescriptor.kt | 11 ++ .../internal/reference/FunctionReference.kt | 61 +----- .../internal/reference/FunctionalReference.kt | 55 ++++++ .../internal/reference/ParameterReference.kt | 28 ++- .../reference/TopLevelFunctionReference.kt | 123 ++++++++++++ .../dagger/AssistedFactoryGenerator.kt | 6 +- .../codegen/dagger/BindsMethodValidator.kt | 18 +- .../dagger/ProvidesMethodFactoryGenerator.kt | 10 +- .../reference/RealAnvilModuleDescriptor.kt | 16 ++ .../TopLevelFunctionReferenceTest.kt | 187 ++++++++++++++++++ .../java/com/squareup/anvil/test/Trigger.kt | 3 + .../squareup/anvil/test/GeneratedCodeTest.kt | 6 + .../squareup/anvil/test/TestCodeGenerator.kt | 26 +++ 14 files changed, 477 insertions(+), 75 deletions(-) create mode 100644 compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionalReference.kt create mode 100644 compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TopLevelFunctionReference.kt create mode 100644 compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TopLevelFunctionReferenceTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 1434d5a09..b6b6efdcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ ### Custom Code Generator +- Add ability to query top-level functions. This allows you write code generators for top-level functions, see #644. + ## [2.4.3] - 2022-12-16 ### Added diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnvilModuleDescriptor.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnvilModuleDescriptor.kt index f263c9e65..1309ee0fb 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnvilModuleDescriptor.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnvilModuleDescriptor.kt @@ -29,6 +29,8 @@ public interface AnvilModuleDescriptor : ModuleDescriptor { public fun getClassAndInnerClassReferences(ktFile: KtFile): List + public fun getTopLevelFunctionReferences(ktFile: KtFile): List + public fun getClassReference(clazz: KtClassOrObject): Psi public fun getClassReference(descriptor: ClassDescriptor): Descriptor @@ -58,3 +60,12 @@ public fun Collection.classAndInnerClassReferences( module.asAnvilModuleDescriptor().getClassAndInnerClassReferences(it) } } + +@ExperimentalAnvilApi +public fun Collection.topLevelFunctionReferences( + module: ModuleDescriptor +): Sequence { + return asSequence().flatMap { + module.asAnvilModuleDescriptor().getTopLevelFunctionReferences(it) + } +} diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionReference.kt index 09834d23c..0d762b907 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionReference.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionReference.kt @@ -1,7 +1,6 @@ package com.squareup.anvil.compiler.internal.reference import com.squareup.anvil.annotations.ExperimentalAnvilApi -import com.squareup.anvil.compiler.api.AnvilCompilationException import com.squareup.anvil.compiler.internal.reference.FunctionReference.Descriptor import com.squareup.anvil.compiler.internal.reference.FunctionReference.Psi import com.squareup.anvil.compiler.internal.reference.Visibility.INTERNAL @@ -30,29 +29,18 @@ import kotlin.LazyThreadSafetyMode.NONE * [FunctionDescriptor] references, to streamline parsing. */ @ExperimentalAnvilApi -public sealed class FunctionReference : AnnotatedReference { +public sealed class FunctionReference : AnnotatedReference, FunctionalReference { - public abstract val fqName: FqName public abstract val declaringClass: ClassReference - public val name: String get() = fqName.shortName().asString() - public val module: AnvilModuleDescriptor get() = declaringClass.module - - public abstract val parameters: List + public override val module: AnvilModuleDescriptor get() = declaringClass.module protected abstract val returnType: TypeReference? - public fun returnTypeOrNull(): TypeReference? = returnType - public fun returnType(): TypeReference = returnType - ?: throw AnvilCompilationExceptionFunctionReference( - functionReference = this, - message = "Unable to get the return type for function $fqName." - ) + public override fun returnTypeOrNull(): TypeReference? = returnType public abstract fun isAbstract(): Boolean public abstract fun isConstructor(): Boolean - public abstract fun visibility(): Visibility - public fun resolveGenericReturnTypeOrNull( implementingClass: ClassReference ): ClassReference? { @@ -63,8 +51,8 @@ public sealed class FunctionReference : AnnotatedReference { public fun resolveGenericReturnType(implementingClass: ClassReference): ClassReference = resolveGenericReturnTypeOrNull(implementingClass) - ?: throw AnvilCompilationExceptionFunctionReference( - functionReference = this, + ?: throw AnvilCompilationExceptionFunctionalReference( + functionalReference = this, message = "Unable to resolve return type for function $fqName with the implementing " + "class ${implementingClass.fqName}." ) @@ -73,7 +61,7 @@ public sealed class FunctionReference : AnnotatedReference { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is ClassReference) return false + if (other !is FunctionReference) return false if (fqName != other.fqName) return false @@ -85,10 +73,10 @@ public sealed class FunctionReference : AnnotatedReference { } public class Psi internal constructor( - public val function: KtFunction, + public override val function: KtFunction, override val declaringClass: ClassReference.Psi, override val fqName: FqName - ) : FunctionReference() { + ) : FunctionReference(), FunctionalReference.Psi { override val annotations: List by lazy(NONE) { function.annotationEntries.map { @@ -125,10 +113,10 @@ public sealed class FunctionReference : AnnotatedReference { } public class Descriptor internal constructor( - public val function: FunctionDescriptor, + public override val function: FunctionDescriptor, override val declaringClass: ClassReference.Descriptor, override val fqName: FqName = function.fqNameSafe - ) : FunctionReference() { + ) : FunctionReference(), FunctionalReference.Descriptor { override val annotations: List by lazy(NONE) { function.annotations.map { @@ -144,16 +132,6 @@ public sealed class FunctionReference : AnnotatedReference { function.returnType?.toTypeReference(declaringClass, module) } - internal val overriddenFunctions by lazy(NONE) { - generateSequence( - function.overriddenDescriptors - ) { overriddenFunctions -> - overriddenFunctions - .flatMap { it.overriddenDescriptors } - .takeIf { it.isNotEmpty() } - }.flatten().toList() - } - override fun isAbstract(): Boolean = function.modality == ABSTRACT override fun isConstructor(): Boolean = function is ClassConstructorDescriptor @@ -192,22 +170,3 @@ public fun FunctionDescriptor.toFunctionReference( ): Descriptor { return Descriptor(this, declaringClass) } - -@ExperimentalAnvilApi -@Suppress("FunctionName") -public fun AnvilCompilationExceptionFunctionReference( - functionReference: FunctionReference, - message: String, - cause: Throwable? = null -): AnvilCompilationException = when (functionReference) { - is Psi -> AnvilCompilationException( - element = functionReference.function, - message = message, - cause = cause - ) - is Descriptor -> AnvilCompilationException( - functionDescriptor = functionReference.function, - message = message, - cause = cause - ) -} diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionalReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionalReference.kt new file mode 100644 index 000000000..ce0a028f5 --- /dev/null +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/FunctionalReference.kt @@ -0,0 +1,55 @@ +package com.squareup.anvil.compiler.internal.reference + +import com.squareup.anvil.annotations.ExperimentalAnvilApi +import com.squareup.anvil.compiler.api.AnvilCompilationException +import com.squareup.anvil.compiler.internal.reference.FunctionalReference.Descriptor +import com.squareup.anvil.compiler.internal.reference.FunctionalReference.Psi +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFunction + +@ExperimentalAnvilApi +public sealed interface FunctionalReference { + public val fqName: FqName + public val name: String get() = fqName.shortName().asString() + + public val module: AnvilModuleDescriptor + + public val parameters: List + + public fun returnTypeOrNull(): TypeReference? + public fun returnType(): TypeReference = returnTypeOrNull() + ?: throw AnvilCompilationExceptionFunctionalReference( + functionalReference = this, + message = "Unable to get the return type for function $fqName." + ) + + public fun visibility(): Visibility + + public sealed interface Psi : FunctionalReference { + public val function: KtFunction + } + + public sealed interface Descriptor : FunctionalReference { + public val function: FunctionDescriptor + } +} + +@ExperimentalAnvilApi +@Suppress("FunctionName") +public fun AnvilCompilationExceptionFunctionalReference( + functionalReference: FunctionalReference, + message: String, + cause: Throwable? = null +): AnvilCompilationException = when (functionalReference) { + is Psi -> AnvilCompilationException( + element = functionalReference.function, + message = message, + cause = cause + ) + is Descriptor -> AnvilCompilationException( + functionDescriptor = functionalReference.function, + message = message, + cause = cause + ) +} diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ParameterReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ParameterReference.kt index d08498865..1a420f0c7 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ParameterReference.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/ParameterReference.kt @@ -13,11 +13,13 @@ import kotlin.LazyThreadSafetyMode.NONE public sealed class ParameterReference : AnnotatedReference { public abstract val name: String - public abstract val declaringFunction: FunctionReference + public abstract val declaringFunction: FunctionalReference public val module: AnvilModuleDescriptor get() = declaringFunction.module protected abstract val type: TypeReference? + protected abstract val declaringClass: ClassReference? + /** * The type can be null for generic type parameters like `T`. In this case try to resolve the * type with [TypeReference.resolveGenericTypeOrNull]. @@ -66,7 +68,7 @@ public sealed class ParameterReference : AnnotatedReference { public class Psi( public val parameter: KtParameter, - override val declaringFunction: FunctionReference.Psi + override val declaringFunction: FunctionalReference.Psi ) : ParameterReference() { override val name: String = parameter.nameAsSafeName.asString() @@ -77,13 +79,19 @@ public sealed class ParameterReference : AnnotatedReference { } override val type: TypeReference.Psi? by lazy(NONE) { - parameter.typeReference?.toTypeReference(declaringFunction.declaringClass, module) + parameter.typeReference?.toTypeReference(declaringClass, module) } + + override val declaringClass: ClassReference.Psi? + get() = when (declaringFunction) { + is TopLevelFunctionReference.Psi -> null + is FunctionReference.Psi -> declaringFunction.declaringClass + } } public class Descriptor( public val parameter: ValueParameterDescriptor, - override val declaringFunction: FunctionReference.Descriptor + override val declaringFunction: FunctionalReference.Descriptor ) : ParameterReference() { override val name: String = parameter.name.asString() @@ -94,21 +102,27 @@ public sealed class ParameterReference : AnnotatedReference { } override val type: TypeReference.Descriptor? by lazy(NONE) { - parameter.type.toTypeReference(declaringFunction.declaringClass, module) + parameter.type.toTypeReference(declaringClass, module) } + + override val declaringClass: ClassReference.Descriptor? + get() = when (declaringFunction) { + is TopLevelFunctionReference.Descriptor -> null + is FunctionReference.Descriptor -> declaringFunction.declaringClass + } } } @ExperimentalAnvilApi public fun KtParameter.toParameterReference( - declaringFunction: FunctionReference.Psi + declaringFunction: FunctionalReference.Psi ): Psi { return Psi(this, declaringFunction) } @ExperimentalAnvilApi public fun ValueParameterDescriptor.toParameterReference( - declaringFunction: FunctionReference.Descriptor + declaringFunction: FunctionalReference.Descriptor ): Descriptor { return Descriptor(this, declaringFunction) } diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TopLevelFunctionReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TopLevelFunctionReference.kt new file mode 100644 index 000000000..1a22783ea --- /dev/null +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TopLevelFunctionReference.kt @@ -0,0 +1,123 @@ +package com.squareup.anvil.compiler.internal.reference + +import com.squareup.anvil.annotations.ExperimentalAnvilApi +import com.squareup.anvil.compiler.internal.reference.TopLevelFunctionReference.Descriptor +import com.squareup.anvil.compiler.internal.reference.TopLevelFunctionReference.Psi +import com.squareup.anvil.compiler.internal.reference.Visibility.INTERNAL +import com.squareup.anvil.compiler.internal.reference.Visibility.PRIVATE +import com.squareup.anvil.compiler.internal.reference.Visibility.PROTECTED +import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC +import com.squareup.anvil.compiler.internal.requireFqName +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import kotlin.LazyThreadSafetyMode.NONE + +@ExperimentalAnvilApi +public sealed class TopLevelFunctionReference : AnnotatedReference, FunctionalReference { + + protected abstract val returnType: TypeReference? + + public override fun returnTypeOrNull(): TypeReference? = returnType + + override fun toString(): String = "$fqName()" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is TopLevelFunctionReference) return false + + if (fqName != other.fqName) return false + + return true + } + + override fun hashCode(): Int { + return fqName.hashCode() + } + + public class Psi internal constructor( + public override val function: KtFunction, + override val fqName: FqName, + override val module: AnvilModuleDescriptor, + ) : TopLevelFunctionReference(), FunctionalReference.Psi { + + override val annotations: List by lazy(NONE) { + function.annotationEntries.map { + it.toAnnotationReference(declaringClass = null, module) + } + } + + override val returnType: TypeReference.Psi? by lazy(NONE) { + function.typeReference?.toTypeReference(declaringClass = null, module) + } + + override val parameters: List by lazy(NONE) { + function.valueParameters.map { it.toParameterReference(this) } + } + + override fun visibility(): Visibility { + return when (val visibility = function.visibilityModifierTypeOrDefault()) { + KtTokens.PUBLIC_KEYWORD -> PUBLIC + KtTokens.INTERNAL_KEYWORD -> INTERNAL + KtTokens.PROTECTED_KEYWORD -> PROTECTED + KtTokens.PRIVATE_KEYWORD -> PRIVATE + else -> throw AnvilCompilationExceptionFunctionalReference( + functionalReference = this, + message = "Couldn't get visibility $visibility for function $fqName." + ) + } + } + } + + public class Descriptor internal constructor( + public override val function: FunctionDescriptor, + override val fqName: FqName = function.fqNameSafe, + override val module: AnvilModuleDescriptor, + ) : TopLevelFunctionReference(), FunctionalReference.Descriptor { + + override val annotations: List by lazy(NONE) { + function.annotations.map { + it.toAnnotationReference(declaringClass = null, module) + } + } + + override val parameters: List by lazy(NONE) { + function.valueParameters.map { it.toParameterReference(this) } + } + + override val returnType: TypeReference.Descriptor? by lazy(NONE) { + function.returnType?.toTypeReference(declaringClass = null, module) + } + + override fun visibility(): Visibility { + return when (val visibility = function.visibility) { + DescriptorVisibilities.PUBLIC -> PUBLIC + DescriptorVisibilities.INTERNAL -> INTERNAL + DescriptorVisibilities.PROTECTED -> PROTECTED + DescriptorVisibilities.PRIVATE -> PRIVATE + else -> throw AnvilCompilationExceptionFunctionalReference( + functionalReference = this, + message = "Couldn't get visibility $visibility for function $fqName." + ) + } + } + } +} + +@ExperimentalAnvilApi +public fun KtFunction.toTopLevelFunctionReference( + module: AnvilModuleDescriptor, +): Psi { + return Psi(function = this, fqName = requireFqName(), module = module) +} + +@ExperimentalAnvilApi +public fun FunctionDescriptor.toTopLevelFunctionReference( + module: AnvilModuleDescriptor, +): Descriptor { + return Descriptor(function = this, module = module) +} diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/AssistedFactoryGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/AssistedFactoryGenerator.kt index 50c533fa7..b3a425394 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/AssistedFactoryGenerator.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/AssistedFactoryGenerator.kt @@ -15,7 +15,7 @@ import com.squareup.anvil.compiler.codegen.dagger.AssistedFactoryGenerator.Assis import com.squareup.anvil.compiler.internal.asClassName import com.squareup.anvil.compiler.internal.buildFile import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference -import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionFunctionReference +import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionFunctionalReference import com.squareup.anvil.compiler.internal.reference.ClassReference import com.squareup.anvil.compiler.internal.reference.FunctionReference import com.squareup.anvil.compiler.internal.reference.ParameterReference @@ -73,10 +73,10 @@ internal class AssistedFactoryGenerator : PrivateCodeGenerator() { function.function.resolveGenericReturnType(clazz) } catch (e: AnvilCompilationException) { // Catch the exception and throw the same error that Dagger would. - throw AnvilCompilationExceptionFunctionReference( + throw AnvilCompilationExceptionFunctionalReference( message = "Invalid return type: ${clazz.fqName}. An assisted factory's " + "abstract method must return a type with an @AssistedInject-annotated constructor.", - functionReference = function.function, + functionalReference = function.function, cause = e ) } diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/BindsMethodValidator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/BindsMethodValidator.kt index 4007ecbc3..9a0eda313 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/BindsMethodValidator.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/BindsMethodValidator.kt @@ -6,7 +6,7 @@ import com.squareup.anvil.compiler.api.CodeGenerator import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator import com.squareup.anvil.compiler.daggerBindsFqName import com.squareup.anvil.compiler.daggerModuleFqName -import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionFunctionReference +import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionFunctionalReference import com.squareup.anvil.compiler.internal.reference.ClassReference import com.squareup.anvil.compiler.internal.reference.FunctionReference import com.squareup.anvil.compiler.internal.reference.allSuperTypeClassReferences @@ -53,9 +53,9 @@ internal class BindsMethodValidator : PrivateCodeGenerator() { private fun validateBindsFunction(function: FunctionReference.Psi) { if (!function.isAbstract()) { - throw AnvilCompilationExceptionFunctionReference( + throw AnvilCompilationExceptionFunctionalReference( message = "@Binds methods must be abstract", - functionReference = function + functionalReference = function ) } @@ -63,16 +63,16 @@ internal class BindsMethodValidator : PrivateCodeGenerator() { (function.parameters.size == 1 && !function.function.isExtensionDeclaration()) || (function.parameters.isEmpty() && function.function.isExtensionDeclaration()) if (!hasSingleBindingParameter) { - throw AnvilCompilationExceptionFunctionReference( + throw AnvilCompilationExceptionFunctionalReference( message = "@Binds methods must have exactly one parameter, " + "whose type is assignable to the return type", - functionReference = function + functionalReference = function ) } - function.returnTypeOrNull() ?: throw AnvilCompilationExceptionFunctionReference( + function.returnTypeOrNull() ?: throw AnvilCompilationExceptionFunctionalReference( message = "@Binds methods must return a value (not void)", - functionReference = function + functionalReference = function ) if (!function.parameterMatchesReturnType() && !function.receiverMatchesReturnType()) { @@ -86,11 +86,11 @@ internal class BindsMethodValidator : PrivateCodeGenerator() { } else { "only has the following supertypes: ${paramSuperTypes.drop(1)}" } - throw AnvilCompilationExceptionFunctionReference( + throw AnvilCompilationExceptionFunctionalReference( message = "@Binds methods' parameter type must be assignable to the return type. " + "Expected binding of type $returnType but impl parameter of type " + "${paramSuperTypes.first()} $superTypesMessage", - functionReference = function + functionalReference = function ) } } diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt index 34d380264..5896fe565 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/ProvidesMethodFactoryGenerator.kt @@ -12,7 +12,7 @@ import com.squareup.anvil.compiler.daggerProvidesFqName import com.squareup.anvil.compiler.internal.buildFile import com.squareup.anvil.compiler.internal.capitalize import com.squareup.anvil.compiler.internal.reference.AnnotatedReference -import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionFunctionReference +import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionFunctionalReference import com.squareup.anvil.compiler.internal.reference.ClassReference import com.squareup.anvil.compiler.internal.reference.FunctionReference import com.squareup.anvil.compiler.internal.reference.PropertyReference @@ -301,9 +301,9 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() { clazz: ClassReference.Psi, function: FunctionReference.Psi ) { - fun fail(): Nothing = throw AnvilCompilationExceptionFunctionReference( + fun fail(): Nothing = throw AnvilCompilationExceptionFunctionalReference( message = "@Provides methods cannot be abstract", - functionReference = function + functionalReference = function ) // If the function is abstract, then it's an error. @@ -358,10 +358,10 @@ internal class ProvidesMethodFactoryGenerator : PrivateCodeGenerator() { function?.parameters?.mapToConstructorParameters() ?: emptyList() val type: TypeReference = function?.let { - it.returnTypeOrNull() ?: throw AnvilCompilationExceptionFunctionReference( + it.returnTypeOrNull() ?: throw AnvilCompilationExceptionFunctionalReference( message = "Dagger provider methods must specify the return type explicitly when using " + "Anvil. The return type cannot be inferred implicitly.", - functionReference = it + functionalReference = it ) } ?: property!!.type() val annotationReference: AnnotatedReference = function ?: property!! diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/reference/RealAnvilModuleDescriptor.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/reference/RealAnvilModuleDescriptor.kt index 12046df8a..c73bccc65 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/reference/RealAnvilModuleDescriptor.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/reference/RealAnvilModuleDescriptor.kt @@ -9,6 +9,8 @@ import com.squareup.anvil.compiler.internal.reference.AnvilModuleDescriptor import com.squareup.anvil.compiler.internal.reference.ClassReference import com.squareup.anvil.compiler.internal.reference.ClassReference.Descriptor import com.squareup.anvil.compiler.internal.reference.ClassReference.Psi +import com.squareup.anvil.compiler.internal.reference.TopLevelFunctionReference +import com.squareup.anvil.compiler.internal.reference.toTopLevelFunctionReference import com.squareup.anvil.compiler.internal.requireFqName import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -20,6 +22,7 @@ import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe @@ -32,6 +35,9 @@ class RealAnvilModuleDescriptor private constructor( private val allPsiClassReferences: Sequence get() = ktFileToClassReferenceMap.values.asSequence().flatten() + private val ktFileToTopLevelFunctionReferenceMap = + mutableMapOf>() + private val resolveDescriptorCache = mutableMapOf() private val resolveClassIdCache = mutableMapOf() private val classReferenceCache = mutableMapOf() @@ -66,6 +72,12 @@ class RealAnvilModuleDescriptor private constructor( } } + override fun getTopLevelFunctionReferences(ktFile: KtFile): List { + return ktFileToTopLevelFunctionReferenceMap.getOrPut(ktFile.identifier) { + ktFile.topLevelFunctions().map { it.toTopLevelFunctionReference(this) } + } + } + override fun resolveClassIdOrNull(classId: ClassId): FqName? = resolveClassIdCache.getOrPut(classId) { val fqName = classId.asSingleFqName() @@ -166,6 +178,10 @@ private fun KtFile.classesAndInnerClasses(): List { }.flatten().toList() } +private fun KtFile.topLevelFunctions(): List { + return findChildrenByClass(KtFunction::class.java).toList() +} + private fun KtClassOrObject.toClassId(): ClassId { val className = parentsWithSelf.filterIsInstance() .toList() diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TopLevelFunctionReferenceTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TopLevelFunctionReferenceTest.kt new file mode 100644 index 000000000..d834094d7 --- /dev/null +++ b/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TopLevelFunctionReferenceTest.kt @@ -0,0 +1,187 @@ +package com.squareup.anvil.compiler.internal.reference + +import com.google.common.truth.Truth.assertThat +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.compile +import com.squareup.anvil.compiler.internal.reference.Visibility.INTERNAL +import com.squareup.anvil.compiler.internal.reference.Visibility.PRIVATE +import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC +import com.tschuchort.compiletesting.KotlinCompilation.ExitCode.OK +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.resolve.source.getPsi +import org.junit.Test +import java.io.File + +class TopLevelFunctionReferenceTest { + + @Test fun `top level functions are parsed correctly`() { + functionTest( + """ + package com.squareup.test + + private fun abc() = Unit + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.abc")) + assertThat(ref.parameters).isEmpty() + assertThat(ref.annotations).isEmpty() + assertThat(ref.visibility()).isEqualTo(PRIVATE) + when (ref) { + is TopLevelFunctionReference.Psi -> + assertThat(ref.returnTypeOrNull()).isNull() + + is TopLevelFunctionReference.Descriptor -> + assertThat(ref.returnType().asClassReference().fqName.asString()) + .isEqualTo("kotlin.Unit") + } + } + } + + @Test fun `top level functions parameters and types are parsed correctly`() { + functionTest( + """ + package com.squareup.test + + fun abc(string: String): String = string + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.abc")) + assertThat(ref.parameters.single().type().asClassReference().fqName.asString()) + .isEqualTo("kotlin.String") + assertThat(ref.annotations).isEmpty() + assertThat(ref.visibility()).isEqualTo(PUBLIC) + assertThat(ref.returnType().asClassReference().fqName.asString()) + .isEqualTo("kotlin.String") + } + } + + @Test fun `top level functions annotations are parsed correctly`() { + functionTest( + """ + package com.squareup.test + + @PublishedApi + internal fun abc(): Int? = null + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.abc")) + assertThat(ref.parameters).isEmpty() + assertThat(ref.annotations.single().fqName.asString()) + .isEqualTo("kotlin.PublishedApi") + assertThat(ref.visibility()).isEqualTo(INTERNAL) + assertThat(ref.returnType().asClassReference().fqName.asString()) + .isEqualTo("kotlin.Int") + assertThat(ref.returnType().isNullable()).isTrue() + } + } + + @Test fun `top level functions generic return types are parsed correctly`() { + functionTest( + """ + package com.squareup.test + + fun abc(): T = throw NotImplementedError() + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.abc")) + assertThat(ref.parameters).isEmpty() + assertThat(ref.annotations).isEmpty() + assertThat(ref.visibility()).isEqualTo(PUBLIC) + assertThat(ref.returnType().isGenericType()).isTrue() + } + } + + @Test fun `top level functions generic and lambda parameters are parsed correctly`() { + functionTest( + """ + package com.squareup.test + + fun abc(param1: T, param2: () -> T): T? = null + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.abc")) + assertThat(ref.parameters).hasSize(2) + assertThat(ref.parameters[0].type().isGenericType()).isTrue() + assertThat(ref.parameters[1].type().isFunctionType()).isTrue() + assertThat(ref.annotations).isEmpty() + assertThat(ref.visibility()).isEqualTo(PUBLIC) + assertThat(ref.returnType().isGenericType()).isTrue() + assertThat(ref.returnType().isNullable()).isTrue() + } + } + + private fun functionTest( + @Language("kotlin") vararg sources: String, + assert: (TopLevelFunctionReference) -> Unit + ) { + compile( + *sources, + allWarningsAsErrors = false, + codeGenerators = listOf( + object : CodeGenerator { + override fun isApplicable(context: AnvilContext): Boolean = true + + override fun generateCode( + codeGenDir: File, + module: ModuleDescriptor, + projectFiles: Collection + ): Collection { + projectFiles + .topLevelFunctionReferences(module) + .flatMap { listOf(it.toPsiReference(), it.toDescriptorReference()) } + .forEach(assert) + + return emptyList() + } + } + ) + ) { + assertThat(exitCode).isEqualTo(OK) + } + } +} + +fun TopLevelFunctionReference.toDescriptorReference(): TopLevelFunctionReference.Descriptor { + return when (this) { + is TopLevelFunctionReference.Descriptor -> this + is TopLevelFunctionReference.Psi -> { + // Force using the descriptor. + module.getPackage(fqName.parent()).memberScope + .getContributedDescriptors(DescriptorKindFilter.FUNCTIONS) + .filterIsInstance() + .single { it.name.asString() == name } + .toTopLevelFunctionReference(module) + .also { descriptorReference -> + assertThat(descriptorReference) + .isInstanceOf(TopLevelFunctionReference.Descriptor::class.java) + + assertThat(this).isEqualTo(descriptorReference) + assertThat(this.fqName).isEqualTo(descriptorReference.fqName) + } + } + } +} + +fun TopLevelFunctionReference.toPsiReference(): TopLevelFunctionReference.Psi { + return when (this) { + is TopLevelFunctionReference.Psi -> this + is TopLevelFunctionReference.Descriptor -> { + // Force using Psi. + (function.source.getPsi() as KtFunction).toTopLevelFunctionReference(module) + .also { psiReference -> + assertThat(psiReference).isInstanceOf(TopLevelFunctionReference.Psi::class.java) + + assertThat(this).isEqualTo(psiReference) + assertThat(this.fqName).isEqualTo(psiReference.fqName) + } + } + } +} diff --git a/integration-tests/code-generator-tests/src/main/java/com/squareup/anvil/test/Trigger.kt b/integration-tests/code-generator-tests/src/main/java/com/squareup/anvil/test/Trigger.kt index 7171617f9..bfd0e314b 100644 --- a/integration-tests/code-generator-tests/src/main/java/com/squareup/anvil/test/Trigger.kt +++ b/integration-tests/code-generator-tests/src/main/java/com/squareup/anvil/test/Trigger.kt @@ -4,3 +4,6 @@ annotation class Trigger @Trigger class AnyClass + +@Trigger +fun abc() = Unit diff --git a/integration-tests/code-generator-tests/src/test/java/com/squareup/anvil/test/GeneratedCodeTest.kt b/integration-tests/code-generator-tests/src/test/java/com/squareup/anvil/test/GeneratedCodeTest.kt index 6ba707d23..bb7996176 100644 --- a/integration-tests/code-generator-tests/src/test/java/com/squareup/anvil/test/GeneratedCodeTest.kt +++ b/integration-tests/code-generator-tests/src/test/java/com/squareup/anvil/test/GeneratedCodeTest.kt @@ -78,6 +78,12 @@ class GeneratedCodeTest { assertThat(int).isEqualTo(7) } + @Test fun `the function class is generated`() { + val generatedClass = + Class.forName("generated.test.com.squareup.anvil.test.GeneratedFunctionClass") + assertThat(generatedClass).isNotNull() + } + @MergeComponent(Unit::class) interface AppComponent { fun otherClass(): OtherClass diff --git a/integration-tests/code-generator/src/main/java/com/squareup/anvil/test/TestCodeGenerator.kt b/integration-tests/code-generator/src/main/java/com/squareup/anvil/test/TestCodeGenerator.kt index e57074c7d..28d831095 100644 --- a/integration-tests/code-generator/src/main/java/com/squareup/anvil/test/TestCodeGenerator.kt +++ b/integration-tests/code-generator/src/main/java/com/squareup/anvil/test/TestCodeGenerator.kt @@ -6,6 +6,7 @@ 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.internal.reference.classAndInnerClassReferences +import com.squareup.anvil.compiler.internal.reference.topLevelFunctionReferences import com.squareup.anvil.compiler.internal.safePackageString import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -173,6 +174,31 @@ class TestCodeGenerator : CodeGenerator { ), ) } + .plus( + projectFiles + .topLevelFunctionReferences(module) + .filter { it.isAnnotatedWith(FqName("com.squareup.anvil.test.Trigger")) } + .flatMap { + val generatedPackage = "generated.test" + it.fqName.parent() + .safePackageString(dotPrefix = true, dotSuffix = false) + + @Language("kotlin") + val generatedClass = """ + package $generatedPackage + + class GeneratedFunctionClass + """.trimIndent() + + listOf( + createGeneratedFile( + codeGenDir = codeGenDir, + packageName = generatedPackage, + fileName = "GeneratedFunctionClass", + content = generatedClass + ), + ) + } + ) .toList() } } From 4739afeacbdf3ec7a51d19a538c16af800352a03 Mon Sep 17 00:00:00 2001 From: Ralf Wondratschek Date: Tue, 10 Jan 2023 23:42:40 -0800 Subject: [PATCH 3/3] Allow to query top level properties as references for code generators. --- CHANGELOG.md | 2 +- .../reference/AnvilModuleDescriptor.kt | 11 + .../internal/reference/PropertyReference.kt | 48 +--- .../reference/TopLevelPropertyReference.kt | 196 ++++++++++++++++ .../internal/reference/VariableReference.kt | 58 +++++ .../codegen/dagger/DaggerGenerationUtils.kt | 6 +- .../reference/RealAnvilModuleDescriptor.kt | 16 ++ .../TopLevelPropertyReferenceTest.kt | 222 ++++++++++++++++++ .../java/com/squareup/anvil/test/Trigger.kt | 3 + .../squareup/anvil/test/GeneratedCodeTest.kt | 6 + .../squareup/anvil/test/TestCodeGenerator.kt | 26 ++ 11 files changed, 550 insertions(+), 44 deletions(-) create mode 100644 compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TopLevelPropertyReference.kt create mode 100644 compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/VariableReference.kt create mode 100644 compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TopLevelPropertyReferenceTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index b6b6efdcd..82d906087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ ### Custom Code Generator -- Add ability to query top-level functions. This allows you write code generators for top-level functions, see #644. +- Add ability to query top-level functions and properties. The entry point is `projectFiles.topLevelFunctionReferences(module)` and `projectFiles.topLevelPropertyReferences(module)`. This allows you write code generators reacting to top-level functions and properties and not only classes, see #644. ## [2.4.3] - 2022-12-16 diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnvilModuleDescriptor.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnvilModuleDescriptor.kt index 1309ee0fb..c44cc9762 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnvilModuleDescriptor.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/AnvilModuleDescriptor.kt @@ -31,6 +31,8 @@ public interface AnvilModuleDescriptor : ModuleDescriptor { public fun getTopLevelFunctionReferences(ktFile: KtFile): List + public fun getTopLevelPropertyReferences(ktFile: KtFile): List + public fun getClassReference(clazz: KtClassOrObject): Psi public fun getClassReference(descriptor: ClassDescriptor): Descriptor @@ -69,3 +71,12 @@ public fun Collection.topLevelFunctionReferences( module.asAnvilModuleDescriptor().getTopLevelFunctionReferences(it) } } + +@ExperimentalAnvilApi +public fun Collection.topLevelPropertyReferences( + module: ModuleDescriptor +): Sequence { + return asSequence().flatMap { + module.asAnvilModuleDescriptor().getTopLevelPropertyReferences(it) + } +} diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt index b82675ea7..f8370cf74 100644 --- a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/PropertyReference.kt @@ -26,36 +26,23 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import kotlin.LazyThreadSafetyMode.NONE @ExperimentalAnvilApi -public sealed class PropertyReference : AnnotatedReference { +public sealed class PropertyReference : AnnotatedReference, VariableReference { - public abstract val fqName: FqName public abstract val declaringClass: ClassReference - public val module: AnvilModuleDescriptor get() = declaringClass.module + public override val module: AnvilModuleDescriptor get() = declaringClass.module - public abstract val name: String public val memberName: MemberName get() = MemberName(declaringClass.asClassName(), name) protected abstract val type: TypeReference? - public abstract val setterAnnotations: List - public abstract val getterAnnotations: List - - public abstract fun visibility(): Visibility - public abstract fun isLateinit(): Boolean - - public fun typeOrNull(): TypeReference? = type - public fun type(): TypeReference = type - ?: throw AnvilCompilationExceptionPropertyReference( - propertyReference = this, - message = "Unable to get type for property $fqName." - ) + public override fun typeOrNull(): TypeReference? = type override fun toString(): String = "$fqName" override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is ClassReference) return false + if (other !is PropertyReference) return false if (fqName != other.fqName) return false @@ -72,11 +59,11 @@ public sealed class PropertyReference : AnnotatedReference { * common interface, but it's also implemented by other types like `KtConstructor`. */ public class Psi private constructor( - public val property: KtCallableDeclaration, + public override val property: KtCallableDeclaration, override val declaringClass: ClassReference.Psi, override val fqName: FqName, override val name: String - ) : PropertyReference() { + ) : PropertyReference(), VariableReference.Psi { override val annotations: List by lazy(NONE) { property.annotationEntries @@ -143,11 +130,11 @@ public sealed class PropertyReference : AnnotatedReference { } public class Descriptor internal constructor( - public val property: PropertyDescriptor, + public override val property: PropertyDescriptor, override val declaringClass: ClassReference.Descriptor, override val fqName: FqName = property.fqNameSafe, override val name: String = fqName.shortName().asString() - ) : PropertyReference() { + ) : PropertyReference(), VariableReference.Descriptor { override val annotations: List by lazy(NONE) { property.annotations @@ -214,22 +201,3 @@ public fun KtProperty.toPropertyReference( public fun PropertyDescriptor.toPropertyReference( declaringClass: ClassReference.Descriptor ): Descriptor = Descriptor(this, declaringClass) - -@ExperimentalAnvilApi -@Suppress("FunctionName") -public fun AnvilCompilationExceptionPropertyReference( - propertyReference: PropertyReference, - message: String, - cause: Throwable? = null -): AnvilCompilationException = when (propertyReference) { - is Psi -> AnvilCompilationException( - element = propertyReference.property, - message = message, - cause = cause - ) - is Descriptor -> AnvilCompilationException( - propertyDescriptor = propertyReference.property, - message = message, - cause = cause - ) -} diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TopLevelPropertyReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TopLevelPropertyReference.kt new file mode 100644 index 000000000..8c51fd909 --- /dev/null +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/TopLevelPropertyReference.kt @@ -0,0 +1,196 @@ +package com.squareup.anvil.compiler.internal.reference + +import com.squareup.anvil.annotations.ExperimentalAnvilApi +import com.squareup.anvil.compiler.api.AnvilCompilationException +import com.squareup.anvil.compiler.internal.reference.TopLevelPropertyReference.Descriptor +import com.squareup.anvil.compiler.internal.reference.TopLevelPropertyReference.Psi +import com.squareup.anvil.compiler.internal.reference.Visibility.INTERNAL +import com.squareup.anvil.compiler.internal.reference.Visibility.PRIVATE +import com.squareup.anvil.compiler.internal.reference.Visibility.PROTECTED +import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC +import com.squareup.anvil.compiler.internal.requireFqName +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget.PROPERTY_GETTER +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget.PROPERTY_SETTER +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtValVarKeywordOwner +import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter +import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierTypeOrDefault +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import kotlin.LazyThreadSafetyMode.NONE + +@ExperimentalAnvilApi +public sealed class TopLevelPropertyReference : AnnotatedReference, VariableReference { + + protected abstract val type: TypeReference? + + public override fun typeOrNull(): TypeReference? = type + + override fun toString(): String = "$fqName" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is TopLevelPropertyReference) return false + + if (fqName != other.fqName) return false + + return true + } + + override fun hashCode(): Int { + return fqName.hashCode() + } + + /** + * @param property In practice, this is either a [KtProperty], or a [KtParameter] for which + * [KtParameter.isPropertyParameter()] is true. [KtCallableDeclaration] is the most applicable + * common interface, but it's also implemented by other types like `KtConstructor`. + */ + public class Psi private constructor( + public override val property: KtCallableDeclaration, + override val fqName: FqName, + override val name: String, + override val module: AnvilModuleDescriptor, + ) : TopLevelPropertyReference(), VariableReference.Psi { + + override val annotations: List by lazy(NONE) { + property.annotationEntries + .filter { + val annotationUseSiteTarget = it.useSiteTarget?.getAnnotationUseSiteTarget() + annotationUseSiteTarget != PROPERTY_SETTER && annotationUseSiteTarget != PROPERTY_GETTER + } + .map { it.toAnnotationReference(null, module) } + .plus(setterAnnotations) + .plus(getterAnnotations) + } + + override val type: TypeReference? by lazy(NONE) { + property.typeReference?.toTypeReference(null, module) + } + + override val setterAnnotations: List by lazy(NONE) { + property.annotationEntries + .filter { it.useSiteTarget?.getAnnotationUseSiteTarget() == PROPERTY_SETTER } + .plus((property as? KtProperty)?.setter?.annotationEntries ?: emptyList()) + .map { it.toAnnotationReference(null, module) } + } + + override val getterAnnotations: List by lazy(NONE) { + property.annotationEntries + .filter { it.useSiteTarget?.getAnnotationUseSiteTarget() == PROPERTY_GETTER } + .plus((property as? KtProperty)?.getter?.annotationEntries ?: emptyList()) + .map { it.toAnnotationReference(null, module) } + } + + override fun visibility(): Visibility { + return when (val visibility = property.visibilityModifierTypeOrDefault()) { + KtTokens.PUBLIC_KEYWORD -> PUBLIC + KtTokens.INTERNAL_KEYWORD -> INTERNAL + KtTokens.PROTECTED_KEYWORD -> PROTECTED + KtTokens.PRIVATE_KEYWORD -> PRIVATE + else -> throw AnvilCompilationExceptionVariableReference( + variableReference = this, + message = "Couldn't get visibility $visibility for property $fqName." + ) + } + } + + override fun isLateinit(): Boolean { + return property.modifierList?.hasModifier(KtTokens.LATEINIT_KEYWORD) ?: false + } + + internal companion object { + // There's no single applicable type for a PSI property. The multiple generic bounds prevent + // us from creating a property out of a function, constructor, or destructuring declaration. + internal operator fun invoke( + property: T, + fqName: FqName = property.requireFqName(), + name: String = fqName.shortName().asString(), + module: AnvilModuleDescriptor, + ): Psi where T : KtCallableDeclaration, + T : KtValVarKeywordOwner = Psi( + property = property, + fqName = fqName, + name = name, + module = module + ) + } + } + + public class Descriptor internal constructor( + public override val property: PropertyDescriptor, + override val fqName: FqName = property.fqNameSafe, + override val name: String = fqName.shortName().asString(), + override val module: AnvilModuleDescriptor, + ) : TopLevelPropertyReference(), VariableReference.Descriptor { + + override val annotations: List by lazy(NONE) { + property.annotations + .plus(property.backingField?.annotations ?: emptyList()) + .map { it.toAnnotationReference(null, module) } + .plus(setterAnnotations) + .plus(getterAnnotations) + } + + override val setterAnnotations: List by lazy(NONE) { + property.setter + ?.annotations + ?.map { it.toAnnotationReference(null, module) } + .orEmpty() + } + + override val getterAnnotations: List by lazy(NONE) { + property.getter + ?.annotations + ?.map { it.toAnnotationReference(null, module) } + .orEmpty() + } + + override val type: TypeReference by lazy(NONE) { + property.type.toTypeReference(null, module) + } + + override fun visibility(): Visibility { + return when (val visibility = property.visibility) { + DescriptorVisibilities.PUBLIC -> PUBLIC + DescriptorVisibilities.INTERNAL -> INTERNAL + DescriptorVisibilities.PROTECTED -> PROTECTED + DescriptorVisibilities.PRIVATE -> PRIVATE + else -> throw AnvilCompilationExceptionVariableReference( + variableReference = this, + message = "Couldn't get visibility $visibility for property $fqName." + ) + } + } + + override fun isLateinit(): Boolean = property.isLateInit + } +} + +@ExperimentalAnvilApi +public fun KtParameter.toTopLevelPropertyReference( + module: AnvilModuleDescriptor, +): Psi { + if (!isPropertyParameter()) { + throw AnvilCompilationException( + element = this, + message = "A KtParameter may only be turned into a PropertyReference if it's a val or var." + ) + } + return Psi(property = this, module = module) +} + +@ExperimentalAnvilApi +public fun KtProperty.toTopLevelPropertyReference( + module: AnvilModuleDescriptor, +): Psi = Psi(property = this, module = module) + +@ExperimentalAnvilApi +public fun PropertyDescriptor.toTopLevelPropertyReference( + module: AnvilModuleDescriptor, +): Descriptor = Descriptor(property = this, module = module) diff --git a/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/VariableReference.kt b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/VariableReference.kt new file mode 100644 index 000000000..7908b7c81 --- /dev/null +++ b/compiler-utils/src/main/java/com/squareup/anvil/compiler/internal/reference/VariableReference.kt @@ -0,0 +1,58 @@ +package com.squareup.anvil.compiler.internal.reference + +import com.squareup.anvil.annotations.ExperimentalAnvilApi +import com.squareup.anvil.compiler.api.AnvilCompilationException +import com.squareup.anvil.compiler.internal.reference.VariableReference.Descriptor +import com.squareup.anvil.compiler.internal.reference.VariableReference.Psi +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtCallableDeclaration + +public sealed interface VariableReference { + + public val fqName: FqName + + public val module: AnvilModuleDescriptor + + public val name: String + + public val setterAnnotations: List + public val getterAnnotations: List + + public fun visibility(): Visibility + public fun isLateinit(): Boolean + + public fun typeOrNull(): TypeReference? + public fun type(): TypeReference = typeOrNull() + ?: throw AnvilCompilationExceptionVariableReference( + variableReference = this, + message = "Unable to get type for property $fqName." + ) + + public sealed interface Psi : VariableReference { + public val property: KtCallableDeclaration + } + + public sealed interface Descriptor : VariableReference { + public val property: PropertyDescriptor + } +} + +@ExperimentalAnvilApi +@Suppress("FunctionName") +public fun AnvilCompilationExceptionVariableReference( + variableReference: VariableReference, + message: String, + cause: Throwable? = null +): AnvilCompilationException = when (variableReference) { + is Psi -> AnvilCompilationException( + element = variableReference.property, + message = message, + cause = cause + ) + is Descriptor -> AnvilCompilationException( + propertyDescriptor = variableReference.property, + message = message, + cause = cause + ) +} diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/DaggerGenerationUtils.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/DaggerGenerationUtils.kt index 36ff67dc6..2ec0618c5 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/DaggerGenerationUtils.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/dagger/DaggerGenerationUtils.kt @@ -6,7 +6,7 @@ import com.squareup.anvil.compiler.daggerLazyFqName import com.squareup.anvil.compiler.injectFqName import com.squareup.anvil.compiler.internal.capitalize import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionClassReference -import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionPropertyReference +import com.squareup.anvil.compiler.internal.reference.AnvilCompilationExceptionVariableReference import com.squareup.anvil.compiler.internal.reference.ClassReference import com.squareup.anvil.compiler.internal.reference.FunctionReference import com.squareup.anvil.compiler.internal.reference.ParameterReference @@ -208,8 +208,8 @@ private fun PropertyReference.toMemberInjectParameter( ) { // Technically this works with Anvil and we could remove this check. But we prefer consistency // with Dagger. - throw AnvilCompilationExceptionPropertyReference( - propertyReference = this, + throw AnvilCompilationExceptionVariableReference( + variableReference = this, message = "Dagger does not support injection into private fields. Either use a " + "'lateinit var' or '@JvmField'." ) diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/reference/RealAnvilModuleDescriptor.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/reference/RealAnvilModuleDescriptor.kt index c73bccc65..e8aa3037f 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/reference/RealAnvilModuleDescriptor.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/reference/RealAnvilModuleDescriptor.kt @@ -10,7 +10,9 @@ import com.squareup.anvil.compiler.internal.reference.ClassReference import com.squareup.anvil.compiler.internal.reference.ClassReference.Descriptor import com.squareup.anvil.compiler.internal.reference.ClassReference.Psi import com.squareup.anvil.compiler.internal.reference.TopLevelFunctionReference +import com.squareup.anvil.compiler.internal.reference.TopLevelPropertyReference import com.squareup.anvil.compiler.internal.reference.toTopLevelFunctionReference +import com.squareup.anvil.compiler.internal.reference.toTopLevelPropertyReference import com.squareup.anvil.compiler.internal.requireFqName import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -23,6 +25,7 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe @@ -38,6 +41,9 @@ class RealAnvilModuleDescriptor private constructor( private val ktFileToTopLevelFunctionReferenceMap = mutableMapOf>() + private val ktFileToTopLevelPropertyReferenceMap = + mutableMapOf>() + private val resolveDescriptorCache = mutableMapOf() private val resolveClassIdCache = mutableMapOf() private val classReferenceCache = mutableMapOf() @@ -78,6 +84,12 @@ class RealAnvilModuleDescriptor private constructor( } } + override fun getTopLevelPropertyReferences(ktFile: KtFile): List { + return ktFileToTopLevelPropertyReferenceMap.getOrPut(ktFile.identifier) { + ktFile.topLevelProperties().map { it.toTopLevelPropertyReference(this) } + } + } + override fun resolveClassIdOrNull(classId: ClassId): FqName? = resolveClassIdCache.getOrPut(classId) { val fqName = classId.asSingleFqName() @@ -182,6 +194,10 @@ private fun KtFile.topLevelFunctions(): List { return findChildrenByClass(KtFunction::class.java).toList() } +private fun KtFile.topLevelProperties(): List { + return findChildrenByClass(KtProperty::class.java).toList() +} + private fun KtClassOrObject.toClassId(): ClassId { val className = parentsWithSelf.filterIsInstance() .toList() diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TopLevelPropertyReferenceTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TopLevelPropertyReferenceTest.kt new file mode 100644 index 000000000..0620337e7 --- /dev/null +++ b/compiler/src/test/java/com/squareup/anvil/compiler/internal/reference/TopLevelPropertyReferenceTest.kt @@ -0,0 +1,222 @@ +package com.squareup.anvil.compiler.internal.reference + +import com.google.common.truth.Truth.assertThat +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.compile +import com.squareup.anvil.compiler.internal.reference.Visibility.INTERNAL +import com.squareup.anvil.compiler.internal.reference.Visibility.PRIVATE +import com.squareup.anvil.compiler.internal.reference.Visibility.PUBLIC +import com.tschuchort.compiletesting.KotlinCompilation.ExitCode.OK +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.resolve.source.getPsi +import org.junit.Test +import java.io.File + +class TopLevelPropertyReferenceTest { + + @Test fun `top level properties are parsed`() { + propertyTest( + """ + package com.squareup.test + + private val prop = Unit + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.prop")) + assertThat(ref.annotations).isEmpty() + assertThat(ref.getterAnnotations).isEmpty() + assertThat(ref.setterAnnotations).isEmpty() + assertThat(ref.isLateinit()).isFalse() + assertThat(ref.visibility()).isEqualTo(PRIVATE) + when (ref) { + is TopLevelPropertyReference.Psi -> + assertThat(ref.typeOrNull()).isNull() + + is TopLevelPropertyReference.Descriptor -> + assertThat(ref.type().asClassReference().fqName.asString()) + .isEqualTo("kotlin.Unit") + } + } + } + + @Test fun `for top level properties types are resolved`() { + propertyTest( + """ + package com.squareup.test + + var prop: String = "" + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.prop")) + assertThat(ref.annotations).isEmpty() + assertThat(ref.getterAnnotations).isEmpty() + assertThat(ref.setterAnnotations).isEmpty() + assertThat(ref.isLateinit()).isFalse() + assertThat(ref.visibility()).isEqualTo(PUBLIC) + assertThat(ref.type().asClassReference().fqName.asString()) + .isEqualTo("kotlin.String") + } + } + + @Test fun `for top level properties annotations are parsed`() { + propertyTest( + """ + package com.squareup.test + + @PublishedApi + internal val prop: Int? = null + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.prop")) + assertThat(ref.annotations.single().fqName.asString()) + .isEqualTo("kotlin.PublishedApi") + assertThat(ref.getterAnnotations).isEmpty() + assertThat(ref.setterAnnotations).isEmpty() + assertThat(ref.isLateinit()).isFalse() + assertThat(ref.visibility()).isEqualTo(INTERNAL) + assertThat(ref.type().asClassReference().fqName.asString()) + .isEqualTo("kotlin.Int") + assertThat(ref.type().isNullable()).isTrue() + } + } + + @Test fun `for top level properties setter annotations are parsed`() { + propertyTest( + """ + package com.squareup.test + + import javax.inject.Inject + + @set:Inject var prop: String = "" + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.prop")) + assertThat(ref.annotations.single().fqName.asString()) + .isEqualTo("javax.inject.Inject") + assertThat(ref.getterAnnotations).isEmpty() + assertThat(ref.setterAnnotations.single().fqName.asString()) + .isEqualTo("javax.inject.Inject") + assertThat(ref.isLateinit()).isFalse() + assertThat(ref.visibility()).isEqualTo(PUBLIC) + assertThat(ref.type().asClassReference().fqName.asString()) + .isEqualTo("kotlin.String") + } + } + + @Test fun `for top level properties getter annotations are parsed`() { + propertyTest( + """ + package com.squareup.test + + import javax.inject.Inject + + @get:Inject var prop: String = "" + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.prop")) + assertThat(ref.annotations.single().fqName.asString()) + .isEqualTo("javax.inject.Inject") + assertThat(ref.getterAnnotations.single().fqName.asString()) + .isEqualTo("javax.inject.Inject") + assertThat(ref.setterAnnotations).isEmpty() + assertThat(ref.isLateinit()).isFalse() + assertThat(ref.visibility()).isEqualTo(PUBLIC) + assertThat(ref.type().asClassReference().fqName.asString()) + .isEqualTo("kotlin.String") + } + } + + @Test fun `lateinit top level properties are parsed`() { + propertyTest( + """ + package com.squareup.test + + lateinit var prop6: String + """ + ) { ref -> + assertThat(ref.fqName).isEqualTo(FqName("com.squareup.test.prop6")) + assertThat(ref.annotations).isEmpty() + assertThat(ref.getterAnnotations).isEmpty() + assertThat(ref.setterAnnotations).isEmpty() + assertThat(ref.isLateinit()).isTrue() + assertThat(ref.visibility()).isEqualTo(PUBLIC) + assertThat(ref.type().asClassReference().fqName.asString()) + .isEqualTo("kotlin.String") + } + } + + private fun propertyTest( + @Language("kotlin") vararg sources: String, + assert: (TopLevelPropertyReference) -> Unit + ) { + compile( + sources = sources, + allWarningsAsErrors = false, + codeGenerators = listOf( + object : CodeGenerator { + override fun isApplicable(context: AnvilContext): Boolean = true + + override fun generateCode( + codeGenDir: File, + module: ModuleDescriptor, + projectFiles: Collection + ): Collection { + projectFiles + .topLevelPropertyReferences(module) + .flatMap { listOf(it.toPsiReference(), it.toDescriptorReference()) } + .forEach(assert) + + return emptyList() + } + } + ) + ) { + assertThat(exitCode).isEqualTo(OK) + } + } +} + +fun TopLevelPropertyReference.toDescriptorReference(): TopLevelPropertyReference.Descriptor { + return when (this) { + is TopLevelPropertyReference.Descriptor -> this + is TopLevelPropertyReference.Psi -> { + // Force using the descriptor. + module.getPackage(fqName.parent()).memberScope + .getContributedDescriptors(DescriptorKindFilter.VARIABLES) + .filterIsInstance() + .single { it.name.asString() == name } + .toTopLevelPropertyReference(module) + .also { descriptorReference -> + assertThat(descriptorReference) + .isInstanceOf(TopLevelPropertyReference.Descriptor::class.java) + + assertThat(this).isEqualTo(descriptorReference) + assertThat(this.fqName).isEqualTo(descriptorReference.fqName) + } + } + } +} + +fun TopLevelPropertyReference.toPsiReference(): TopLevelPropertyReference.Psi { + return when (this) { + is TopLevelPropertyReference.Psi -> this + is TopLevelPropertyReference.Descriptor -> { + // Force using Psi. + (property.source.getPsi() as KtProperty).toTopLevelPropertyReference(module) + .also { psiReference -> + assertThat(psiReference).isInstanceOf(TopLevelPropertyReference.Psi::class.java) + + assertThat(this).isEqualTo(psiReference) + assertThat(this.fqName).isEqualTo(psiReference.fqName) + } + } + } +} diff --git a/integration-tests/code-generator-tests/src/main/java/com/squareup/anvil/test/Trigger.kt b/integration-tests/code-generator-tests/src/main/java/com/squareup/anvil/test/Trigger.kt index bfd0e314b..df77477d1 100644 --- a/integration-tests/code-generator-tests/src/main/java/com/squareup/anvil/test/Trigger.kt +++ b/integration-tests/code-generator-tests/src/main/java/com/squareup/anvil/test/Trigger.kt @@ -7,3 +7,6 @@ class AnyClass @Trigger fun abc() = Unit + +@Trigger +val def = Unit diff --git a/integration-tests/code-generator-tests/src/test/java/com/squareup/anvil/test/GeneratedCodeTest.kt b/integration-tests/code-generator-tests/src/test/java/com/squareup/anvil/test/GeneratedCodeTest.kt index bb7996176..74baaac5a 100644 --- a/integration-tests/code-generator-tests/src/test/java/com/squareup/anvil/test/GeneratedCodeTest.kt +++ b/integration-tests/code-generator-tests/src/test/java/com/squareup/anvil/test/GeneratedCodeTest.kt @@ -84,6 +84,12 @@ class GeneratedCodeTest { assertThat(generatedClass).isNotNull() } + @Test fun `the property class is generated`() { + val generatedClass = + Class.forName("generated.test.com.squareup.anvil.test.GeneratedPropertyClass") + assertThat(generatedClass).isNotNull() + } + @MergeComponent(Unit::class) interface AppComponent { fun otherClass(): OtherClass diff --git a/integration-tests/code-generator/src/main/java/com/squareup/anvil/test/TestCodeGenerator.kt b/integration-tests/code-generator/src/main/java/com/squareup/anvil/test/TestCodeGenerator.kt index 28d831095..f9e07f81d 100644 --- a/integration-tests/code-generator/src/main/java/com/squareup/anvil/test/TestCodeGenerator.kt +++ b/integration-tests/code-generator/src/main/java/com/squareup/anvil/test/TestCodeGenerator.kt @@ -7,6 +7,7 @@ import com.squareup.anvil.compiler.api.GeneratedFile import com.squareup.anvil.compiler.api.createGeneratedFile import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences import com.squareup.anvil.compiler.internal.reference.topLevelFunctionReferences +import com.squareup.anvil.compiler.internal.reference.topLevelPropertyReferences import com.squareup.anvil.compiler.internal.safePackageString import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -199,6 +200,31 @@ class TestCodeGenerator : CodeGenerator { ) } ) + .plus( + projectFiles + .topLevelPropertyReferences(module) + .filter { it.isAnnotatedWith(FqName("com.squareup.anvil.test.Trigger")) } + .flatMap { + val generatedPackage = "generated.test" + it.fqName.parent() + .safePackageString(dotPrefix = true, dotSuffix = false) + + @Language("kotlin") + val generatedClass = """ + package $generatedPackage + + class GeneratedPropertyClass + """.trimIndent() + + listOf( + createGeneratedFile( + codeGenDir = codeGenDir, + packageName = generatedPackage, + fileName = "GeneratedPropertyClass", + content = generatedClass + ), + ) + } + ) .toList() } }