Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

:compiler-k2-utils and complementary testing apis #1116

Merged
merged 4 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions compiler-k2-utils/api/compiler-k2-utils.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
public final class com/squareup/anvil/compiler/k2/utils/fir/FirAnnotationKt {
public static final fun createFirAnnotation (Lorg/jetbrains/kotlin/name/ClassId;Lorg/jetbrains/kotlin/fir/expressions/FirAnnotationArgumentMapping;Lorg/jetbrains/kotlin/KtSourceElement;Lorg/jetbrains/kotlin/descriptors/annotations/AnnotationUseSiteTarget;)Lorg/jetbrains/kotlin/fir/expressions/FirAnnotation;
public static synthetic fun createFirAnnotation$default (Lorg/jetbrains/kotlin/name/ClassId;Lorg/jetbrains/kotlin/fir/expressions/FirAnnotationArgumentMapping;Lorg/jetbrains/kotlin/KtSourceElement;Lorg/jetbrains/kotlin/descriptors/annotations/AnnotationUseSiteTarget;ILjava/lang/Object;)Lorg/jetbrains/kotlin/fir/expressions/FirAnnotation;
}

public final class com/squareup/anvil/compiler/k2/utils/fir/ResolveKt {
public static final fun classLikeSymbolOrNull (Lorg/jetbrains/kotlin/name/ClassId;Lorg/jetbrains/kotlin/fir/FirSession;)Lorg/jetbrains/kotlin/fir/symbols/impl/FirClassLikeSymbol;
public static final fun regularClassSymbolOrNull (Lorg/jetbrains/kotlin/name/ClassId;Lorg/jetbrains/kotlin/fir/FirSession;)Lorg/jetbrains/kotlin/fir/symbols/impl/FirRegularClassSymbol;
public static final fun requireClassLikeSymbol (Lorg/jetbrains/kotlin/name/ClassId;Lorg/jetbrains/kotlin/fir/FirSession;)Lorg/jetbrains/kotlin/fir/symbols/impl/FirClassLikeSymbol;
public static final fun requireRegularClassSymbol (Lorg/jetbrains/kotlin/name/ClassId;Lorg/jetbrains/kotlin/fir/FirSession;)Lorg/jetbrains/kotlin/fir/symbols/impl/FirRegularClassSymbol;
}

public final class com/squareup/anvil/compiler/k2/utils/names/ClassIds {
public static final field INSTANCE Lcom/squareup/anvil/compiler/k2/utils/names/ClassIds;
public final fun getAnvilContributesBinding ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getAnvilContributesMultibinding ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getAnvilContributesSubcomponent ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getAnvilContributesTo ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getAnvilMergeComponent ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getAnvilMergeInterfaces ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getAnvilMergeModules ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getAnvilMergeSubcomponent ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getDaggerBinds ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getDaggerComponent ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getDaggerFactory ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getDaggerLazy ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getDaggerProvider ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getDaggerSubcomponent ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getJavaxInject ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getJavaxProvider ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getJavaxQualifier ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getJavaxScope ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getKotlinJvmStatic ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getKotlinJvmSuppressWildcards ()Lorg/jetbrains/kotlin/name/ClassId;
public final fun getKotlinPublishedApi ()Lorg/jetbrains/kotlin/name/ClassId;
}

public final class com/squareup/anvil/compiler/k2/utils/names/ClassIdsKt {
public static final fun child (Lorg/jetbrains/kotlin/name/ClassId;Ljava/lang/String;)Lorg/jetbrains/kotlin/name/ClassId;
public static final fun getBindingModuleSibling (Lorg/jetbrains/kotlin/name/ClassId;)Lorg/jetbrains/kotlin/name/ClassId;
public static final fun getCompanion (Lorg/jetbrains/kotlin/name/ClassId;)Lorg/jetbrains/kotlin/name/ClassId;
public static final fun getFactorySibling (Lorg/jetbrains/kotlin/name/ClassId;)Lorg/jetbrains/kotlin/name/ClassId;
public static final fun nested (Lorg/jetbrains/kotlin/name/ClassId;Ljava/lang/String;)Lorg/jetbrains/kotlin/name/ClassId;
public static final fun sibling (Lorg/jetbrains/kotlin/name/ClassId;Ljava/lang/String;)Lorg/jetbrains/kotlin/name/ClassId;
}

public final class com/squareup/anvil/compiler/k2/utils/names/Names {
public static final field INSTANCE Lcom/squareup/anvil/compiler/k2/utils/names/Names;
public final fun getDependencies ()Lorg/jetbrains/kotlin/name/Name;
public final fun getModules ()Lorg/jetbrains/kotlin/name/Name;
public final fun getScope ()Lorg/jetbrains/kotlin/name/Name;
}

24 changes: 24 additions & 0 deletions compiler-k2-utils/build.gradle.kts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not something that we need to decide now but I'm wondering if we want to treat this module similarly to the existing compiler-utils module where it's expressly not a stable API but still made available for consumers' convenience

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was my thinking as well.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
plugins {
id("conventions.library")
id("conventions.publish")
}

publish {
configurePom(
artifactId = "compiler-k2-utils",
pomName = "Anvil Compiler Utils for K2",
pomDescription = "Optional utility and extension functions for working with FIR and IR, " +
"designed to simplify code generation tasks in Anvil",
)
}

dependencies {

api(libs.kotlin.compiler.embeddable)
api(libs.kotlin.reflect)

api(project(":annotations"))
api(project(":compiler-k2-api"))

testImplementation(project(":compiler-testing"))
}
8 changes: 8 additions & 0 deletions compiler-k2-utils/dependencies/runtimeClasspath.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
org.jetbrains.intellij.deps:trove4j:1.0.20200330
org.jetbrains.kotlin:kotlin-compiler-embeddable:2.1.0
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.1.0
org.jetbrains.kotlin:kotlin-reflect:2.1.0
org.jetbrains.kotlin:kotlin-script-runtime:2.1.0
org.jetbrains.kotlin:kotlin-stdlib:2.1.0
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4
org.jetbrains:annotations:13.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.squareup.anvil.compiler.k2.utils.fir

import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirAnnotationArgumentMapping
import org.jetbrains.kotlin.fir.expressions.builder.buildAnnotation
import org.jetbrains.kotlin.fir.expressions.impl.FirEmptyAnnotationArgumentMapping
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.constructClassLikeType
import org.jetbrains.kotlin.name.ClassId

/**
* Creates a [FirAnnotation] instance with the specified type, argument mapping, source, and use-site target.
*
* @param type The [ClassId] representing the annotation type.
* @param argumentMapping The mapping of arguments for the annotation. Defaults to an empty mapping.
* @param source The optional [KtSourceElement] representing the source of the annotation.
* @param useSiteTarget The optional [AnnotationUseSiteTarget] specifying the use-site target of the annotation.
* @return A [FirAnnotation] instance.
*/
public fun createFirAnnotation(
type: ClassId,
argumentMapping: FirAnnotationArgumentMapping = FirEmptyAnnotationArgumentMapping,
source: KtSourceElement? = null,
useSiteTarget: AnnotationUseSiteTarget? = null,
): FirAnnotation = buildAnnotation {
this.argumentMapping = argumentMapping
this.source = source
this.useSiteTarget = useSiteTarget
annotationTypeRef = buildResolvedTypeRef {
coneType = type.constructClassLikeType()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.squareup.anvil.compiler.k2.utils.fir

import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.name.ClassId

/**
* Retrieves the [FirRegularClassSymbol] associated with this [ClassId].
*
* @param session The [FirSession] used for symbol resolution.
* @return The [FirRegularClassSymbol] corresponding to the given [ClassId].
* @throws ClassCastException if the resolved symbol is not a [FirRegularClassSymbol].
*/
public fun ClassId.requireRegularClassSymbol(session: FirSession): FirRegularClassSymbol =
requireClassLikeSymbol(session) as FirRegularClassSymbol

/**
* Retrieves the [FirClassLikeSymbol] associated with this [ClassId].
*
* @param session The [FirSession] used for symbol resolution.
* @return The [FirClassLikeSymbol] corresponding to the given [ClassId].
* @throws IllegalArgumentException if no symbol is found for the given [ClassId].
*/
public fun ClassId.requireClassLikeSymbol(session: FirSession): FirClassLikeSymbol<*> =
requireNotNull(session.symbolProvider.getClassLikeSymbolByClassId(this)) {
"No class like symbol found for class ID: $this"
}

/**
* Attempts to retrieve the [FirRegularClassSymbol] associated with this [ClassId].
*
* @param session The [FirSession] used for symbol resolution.
* @return The [FirRegularClassSymbol] if found, or `null` if not present.
*/
public fun ClassId.regularClassSymbolOrNull(session: FirSession): FirRegularClassSymbol? =
session.symbolProvider.getClassLikeSymbolByClassId(this) as? FirRegularClassSymbol

/**
* Attempts to retrieve the [FirClassLikeSymbol] associated with this [ClassId].
*
* @param session The [FirSession] used for symbol resolution.
* @return The [FirClassLikeSymbol] if found, or `null` if not present.
*/
public fun ClassId.classLikeSymbolOrNull(session: FirSession): FirClassLikeSymbol<*>? =
session.symbolProvider.getClassLikeSymbolByClassId(this)
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package com.squareup.anvil.compiler.k2.utils.names

import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name

/** Well-known class ids used by Anvil. */
public object ClassIds {

/** `javax.inject.Inject` */
public val javaxInject: ClassId = classId("javax.inject", "Inject")

/** `javax.inject.Provider` */
public val javaxProvider: ClassId = classId("javax.inject", "Provider")

/** `javax.inject.Qualifier` */
public val javaxQualifier: ClassId = classId("javax.inject", "Qualifier")

/** `javax.inject.Scope` */
public val javaxScope: ClassId = classId("javax.inject", "Scope")

/** `kotlin.jvm.JvmStatic` */
public val kotlinJvmStatic: ClassId = classId("kotlin.jvm", "JvmStatic")

/** `kotlin.jvm.JvmSuppressWildcards` */
public val kotlinJvmSuppressWildcards: ClassId = classId("kotlin.jvm", "JvmSuppressWildcards")

/** `kotlin.PublishedApi` */
public val kotlinPublishedApi: ClassId = classId("kotlin", "PublishedApi")

/** `com.squareup.anvil.annotations.ContributesBinding` */
public val anvilContributesBinding: ClassId =
classId("com.squareup.anvil.annotations", "ContributesBinding")

/** `com.squareup.anvil.annotations.ContributesMultibinding` */
public val anvilContributesMultibinding: ClassId =
classId("com.squareup.anvil.annotations", "ContributesMultibinding")

/** `com.squareup.anvil.annotations.ContributesSubcomponent` */
public val anvilContributesSubcomponent: ClassId =
classId("com.squareup.anvil.annotations", "ContributesSubcomponent")

/** `com.squareup.anvil.annotations.ContributesTo` */
public val anvilContributesTo: ClassId =
classId("com.squareup.anvil.annotations", "ContributesTo")

/** `com.squareup.anvil.annotations.MergeComponent` */
public val anvilMergeComponent: ClassId =
classId("com.squareup.anvil.annotations", "MergeComponent")

/** `com.squareup.anvil.annotations.MergeSubcomponent` */
public val anvilMergeSubcomponent: ClassId =
classId("com.squareup.anvil.annotations", "MergeSubcomponent")

/** `com.squareup.anvil.annotations.compat.MergeInterfaces` */
public val anvilMergeInterfaces: ClassId =
classId("com.squareup.anvil.annotations.compat", "MergeInterfaces")

/** `com.squareup.anvil.annotations.compat.MergeModules` */
public val anvilMergeModules: ClassId =
classId("com.squareup.anvil.annotations.compat", "MergeModules")

/** `dagger.Binds` */
public val daggerBinds: ClassId = classId("dagger", "Binds")

/** `dagger.Component` */
public val daggerComponent: ClassId = classId("dagger", "Component")

/** `dagger.internal.Factory` */
public val daggerFactory: ClassId = classId("dagger.internal", "Factory")

/** `dagger.internal.Provider` */
public val daggerProvider: ClassId = classId("dagger.internal", "Provider")

/** `dagger.Lazy` */
public val daggerLazy: ClassId = classId("dagger", "Lazy")

/** `dagger.Subcomponent` */
public val daggerSubcomponent: ClassId = classId("dagger", "Subcomponent")
}

private fun classId(
packageFqName: String,
relativeName: String,
isLocal: Boolean = false,
): ClassId = ClassId(
packageFqName = FqName(packageFqName),
relativeClassName = FqName(relativeName),
isLocal = isLocal,
)

/**
* ```
* given: `com.example.SomeClass
* output: `com.example.SomeClass_BindingModule`
* ```
*/
public val ClassId.bindingModuleSibling: ClassId
get() = sibling("${shortClassName.asString()}_BindingModule")

/**
* ```
* given: `com.example.SomeClass
* output: `com.example.SomeClass_Factory`
* ```
*/
public val ClassId.factorySibling: ClassId
get() = sibling("${shortClassName.asString()}_Factory")

public val ClassId.companion: ClassId get() = child("Companion")

/**
* If the receiver ClassId is a top-level class, this function returns a new ClassId with the same
* package and the given name.
*
* ```
* val topLevel = ClassId.topLevel(FqName("com.example.SomeClass"))
*
* topLevel.sibling("SiblingClass").asFqNameString() shouldBe "com.example.SiblingClass"
* ```
*
* If the receiver ClassId is a nested class, this function returns a new ClassId
* with the same package and the given name, but nested inside the receiver ClassId.
*
* ```
* val innerClass1 = ClassId(
* FqName("com.example"),
* Name.identifier("SomeClass.InnerClass1")
* )
*
* innerClass1.sibling("InnerClass2").asFqNameString() shouldBe "com.example.SomeClass.InnerClass2"
* ```
*/
public fun ClassId.sibling(nameString: String): ClassId {
return parentClassId?.child(nameString)
?: ClassId(packageFqName, Name.identifier(nameString))
}

/**
* alias for [com.squareup.anvil.compiler.testing.nested]
*
* ```
* val kotlinMap = ClassId.fromString("kotlin/collections.Map")
*
* val entry by kotlinMap.child()
*
* entry.asFqNameString() shouldBe "kotlin.collections.Map.Entry"
* ```
* @see ClassId.createNestedClassId
*/
public fun ClassId.child(nameString: String): ClassId = nested(nameString)

/**
* alias for [com.squareup.anvil.compiler.testing.nested]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this may be copy pasta / could use an update

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep... I'll get this at some point soon.

*
* ```
* val kotlinMap = ClassId.fromString("kotlin/collections.Map")
*
* val entry by kotlinMap.nested()
*
* entry.asFqNameString() shouldBe "kotlin.collections.Map.Entry"
* ```
* @see ClassId.createNestedClassId
*/
public fun ClassId.nested(nameString: String): ClassId {
return createNestedClassId(Name.identifier(nameString))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.squareup.anvil.compiler.k2.utils.names

import org.jetbrains.kotlin.name.Name

public object Names {
public val scope: Name = Name.identifier("scope")
public val modules: Name = Name.identifier("modules")
public val dependencies: Name = Name.identifier("dependencies")
}
1 change: 1 addition & 0 deletions compiler-k2/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
compileOnly(libs.auto.service.annotations)
api(project(":annotations"))
api(project(":compiler-k2-api"))
api(project(":compiler-k2-utils"))
api(libs.kotlin.compiler)

kapt(libs.auto.service.processor)
Expand Down
2 changes: 1 addition & 1 deletion compiler-k2/dependencies/runtimeClasspath.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
org.jetbrains.intellij.deps:trove4j:1.0.20200330
org.jetbrains.kotlin:kotlin-compiler-embeddable:2.1.0
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.1.0
org.jetbrains.kotlin:kotlin-reflect:1.6.10
org.jetbrains.kotlin:kotlin-reflect:2.1.0
org.jetbrains.kotlin:kotlin-script-runtime:2.1.0
org.jetbrains.kotlin:kotlin-stdlib:2.1.0
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.squareup.anvil.compiler.k2.constructor.inject

import com.squareup.anvil.compiler.k2.fir.AnvilFirContext
import com.squareup.anvil.compiler.k2.fir.AnvilFirDeclarationGenerationExtension
import com.squareup.anvil.compiler.k2.names.Names
import com.squareup.anvil.compiler.k2.utils.names.ClassIds
import org.jetbrains.kotlin.GeneratedDeclarationKey
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
Expand Down Expand Up @@ -184,7 +184,7 @@ internal class FirInjectConstructorFactoryGenerationExtension(

companion object Key : GeneratedDeclarationKey() {
private val injectAnnotationPredicate = LookupPredicate.create {
annotated(Names.javaxInject)
annotated(ClassIds.javaxInject.asSingleFqName())
}
}
}
Loading
Loading