Skip to content

Commit

Permalink
Merge pull request #158 from yandex/wp/rt-compat
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeffset committed Jun 10, 2024
2 parents 7672b52 + 5037e40 commit 25bb3e0
Show file tree
Hide file tree
Showing 32 changed files with 1,418 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ private const val VALIDATION_DELEGATE_CLASS_PROPERTY = "validationDelegateClass"
private const val MAX_ISSUE_ENCOUNTER_PATHS_PROPERTY = "maxIssueEncounterPaths"
private const val IS_STRICT_MODE_PROPERTY = "enableStrictMode"
private const val USE_PLAIN_OUTPUT_PROPERTY = "usePlainOutput"
private const val DAGGER_COMPATIBILITY = "enableDaggerCompatibility"

/**
* Instantiated reflectively.
Expand Down Expand Up @@ -86,6 +87,10 @@ internal class ReflectionLoader : ImplementationLoader by ReflectionLoader {
params.usePlainOutput = value.toString().toBooleanStrictOrNull()
?: throw IllegalStateException("Expected boolean for `$property`, got `$value`")
}
DAGGER_COMPATIBILITY -> {
params.enableDaggerCompatibility = value.toString().toBooleanStrictOrNull()
?: throw IllegalStateException("Expected boolean for `$property`, got `$value`")
}
else -> {
throw IllegalStateException("Unknown property `$property`")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ internal fun NodeDependency(
Names.Optional -> when (type.typeArguments.first().declaration.qualifiedName) {
Names.Lazy -> OptionalLazy
Names.Provider -> OptionalProvider
Names.LazyCompat -> if (type.isDaggerCompat()) OptionalLazy else Optional
else -> Optional
}
Names.LazyCompat -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ interface LangTestDriver : SourceSet {
(arguments.classpath + result.outputClasspath)
.mapToArray { it.toURI().toURL() }
)
val lexicalScope = RtLexicalScope(classLoader)
val lexicalScope = RtLexicalScope(
classLoader = classLoader,
daggerCompatibilityMode = false,
)
block(lexicalScope.ext.langFactory)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,22 @@ abstract class CtMethodBase : MethodBase() {
): List<T> {
return when (which) {
BuiltinAnnotation.IntoCollectionFamily -> buildList {
var hasIntoSet = false
for (annotation in annotations) {
val daggerCompat = isDaggerCompat()
when {
annotation.hasType<IntoList>() ->
add(which.modelClass.cast(CtIntoListAnnotationImpl(annotation)))
annotation.hasType<IntoSet>() ->
annotation.hasType<IntoSet>() -> {
hasIntoSet = true
add(which.modelClass.cast(CtIntoSetAnnotationImpl(annotation)))
daggerCompat && annotation.hasType(DaggerNames.INTO_SET) ->
}
}
}
if (!hasIntoSet && isDaggerCompat()) {
for (annotation in annotations) when {
annotation.hasType(DaggerNames.INTO_SET) ->
add(which.modelClass.cast(CtIntoSetAnnotationDaggerCompatImpl(annotation)))
daggerCompat && annotation.hasType(DaggerNames.ELEMENTS_INTO_SET) ->
annotation.hasType(DaggerNames.ELEMENTS_INTO_SET) ->
add(which.modelClass.cast(CtElementsIntoSetAnnotationDaggerCompatImpl(annotation)))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ import javax.lang.model.util.SimpleElementVisitor8

internal val LexicalScope.Utils: ProcessingUtils get() = ext[ProcessingUtils]

inline fun <reified T : Annotation> Element.isAnnotatedWith() = annotationMirrors.any {
it.annotationType.asTypeElement().qualifiedName.contentEquals(T::class.java.canonicalName)
inline fun <reified T : Annotation> Element.isAnnotatedWith() = isAnnotatedWith(T::class.java.canonicalName)

fun Element.isAnnotatedWith(qualifiedName: String) = annotationMirrors.any {
it.annotationType.asTypeElement().qualifiedName.contentEquals(qualifiedName)
}

@PublishedApi
Expand Down
3 changes: 3 additions & 0 deletions lang/rt/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ dependencies {

implementation(project(":api:public"))
implementation(project(":base:impl"))

// optional dependency, detected at runtime for compat mode
compileOnly(libs.testing.dagger.api)
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ internal class RtAnnotationImpl private constructor(
override fun <T : BuiltinAnnotation.CanBeCastedOut> asBuiltin(which: BuiltinAnnotation.Target.CanBeCastedOut<T>): T? {
return which.modelClass.cast(when(which) {
BuiltinAnnotation.ValueOf -> RtValueOfAnnotationImpl(impl as? ValueOf ?: return null)
BuiltinAnnotation.Reusable -> which.takeIf { impl is Reusable }
BuiltinAnnotation.Reusable -> which.takeIf { impl is Reusable || daggerCompat().isReusable(impl) }
})
}

Expand Down Expand Up @@ -136,7 +136,7 @@ internal class RtAnnotationImpl private constructor(
): T? {
val annotation: BuiltinAnnotation.OnAnnotationClass? = when(builtinAnnotation) {
BuiltinAnnotation.IntoMap.Key -> (builtinAnnotation as BuiltinAnnotation.IntoMap.Key)
.takeIf { impl.isAnnotationPresent(IntoMap.Key::class.java) }
.takeIf { impl.isAnnotationPresent(IntoMap.Key::class.java) || daggerCompat().hasMapKey(impl) }
BuiltinAnnotation.Qualifier -> (builtinAnnotation as BuiltinAnnotation.Qualifier)
.takeIf { impl.isAnnotationPresent(Qualifier::class.java) }
BuiltinAnnotation.Scope -> (builtinAnnotation as BuiltinAnnotation.Scope)
Expand Down
172 changes: 172 additions & 0 deletions lang/rt/src/main/kotlin/com/yandex/yatagan/lang/rt/RtDaggerCompat.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright 2024 Yandex LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.yandex.yatagan.lang.rt

import com.yandex.yatagan.base.api.Extensible
import com.yandex.yatagan.lang.BuiltinAnnotation
import com.yandex.yatagan.lang.scope.LexicalScope
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.MapKey
import dagger.Module
import dagger.Provides
import dagger.Reusable
import dagger.Subcomponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.multibindings.ElementsIntoSet
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet
import dagger.multibindings.Multibinds
import java.lang.reflect.AnnotatedElement

internal interface RtDaggerCompat {
fun hasAssistedInject(c: AnnotatedElement): Boolean
fun hasAssistedFactory(c: AnnotatedElement): Boolean
fun hasComponentBuilder(c: AnnotatedElement): Boolean
fun hasBinds(c: AnnotatedElement): Boolean
fun hasBindsInstance(c: AnnotatedElement): Boolean
fun hasProvides(c: AnnotatedElement): Boolean
fun hasIntoMap(c: AnnotatedElement): Boolean
fun hasMultibinds(c: AnnotatedElement): Boolean
fun hasMapKey(c: AnnotatedElement): Boolean

fun isReusable(c: ReflectAnnotation): Boolean
fun isBindsInstance(c: ReflectAnnotation): Boolean
fun asAssisted(c: ReflectAnnotation): BuiltinAnnotation.Assisted?

fun getIntoSet(c: AnnotatedElement): BuiltinAnnotation.IntoCollectionFamily.IntoSet?
fun getModule(c: AnnotatedElement): BuiltinAnnotation.Module?
fun getComponent(c: AnnotatedElement): BuiltinAnnotation.Component?

companion object : Extensible.Key<RtDaggerCompat, LexicalScope.Extensions>

// WARNING: Do not reference this class if there's no guarantee that the dagger api is in the classpath.
class Impl(
scope: LexicalScope,
) : RtDaggerCompat, LexicalScope by scope {
override fun hasAssistedInject(c: AnnotatedElement) =
c.isAnnotationPresent(AssistedInject::class.java)

override fun hasAssistedFactory(c: AnnotatedElement) =
c.isAnnotationPresent(AssistedFactory::class.java)

override fun hasComponentBuilder(c: AnnotatedElement) =
c.isAnnotationPresent(Component.Builder::class.java) ||
c.isAnnotationPresent(Component.Factory::class.java) ||
c.isAnnotationPresent(Subcomponent.Builder::class.java) ||
c.isAnnotationPresent(Subcomponent.Factory::class.java)

override fun hasBinds(c: AnnotatedElement) = c.isAnnotationPresent(Binds::class.java)
override fun hasBindsInstance(c: AnnotatedElement) = c.isAnnotationPresent(BindsInstance::class.java)
override fun hasProvides(c: AnnotatedElement) = c.isAnnotationPresent(Provides::class.java)
override fun hasIntoMap(c: AnnotatedElement) = c.isAnnotationPresent(IntoMap::class.java)
override fun hasMultibinds(c: AnnotatedElement) = c.isAnnotationPresent(Multibinds::class.java)
override fun hasMapKey(c: AnnotatedElement) = c.isAnnotationPresent(MapKey::class.java)

override fun isReusable(c: ReflectAnnotation) = c is Reusable
override fun isBindsInstance(c: ReflectAnnotation) = c is BindsInstance

override fun asAssisted(c: ReflectAnnotation): BuiltinAnnotation.Assisted? = when(c) {
is Assisted -> RtAssistedAnnotationDaggerCompatImpl(c)
else -> null
}

override fun getModule(c: AnnotatedElement): BuiltinAnnotation.Module? =
c.getDeclaredAnnotation(Module::class.java)
?.let { RtModuleAnnotationDaggerCompatImpl(it) }

override fun getComponent(c: AnnotatedElement): BuiltinAnnotation.Component? =
c.getDeclaredAnnotation(Component::class.java)
?.let { RtComponentAnnotationDaggerCompatImpl(it) }
?: c.getDeclaredAnnotation(Subcomponent::class.java)
?.let { RtSubcomponentAnnotationDaggerCompatImpl(it) }

override fun getIntoSet(c: AnnotatedElement): BuiltinAnnotation.IntoCollectionFamily.IntoSet? =
c.getDeclaredAnnotation(IntoSet::class.java)?.let { RtIntoSetAnnotationDaggerCompatImpl(it) }
?: c.getDeclaredAnnotation(ElementsIntoSet::class.java)
?.let { RtElementsIntoSetAnnotationDaggerCompatImpl(it) }

private inner class RtComponentAnnotationDaggerCompatImpl(
impl: Component,
) : RtAnnotationImplBase<Component>(impl), BuiltinAnnotation.Component, LexicalScope by this {
override val isRoot: Boolean get() = true
override val modules get() = impl.modules.map { RtTypeImpl(it.java) }
override val dependencies get() = impl.dependencies.map { RtTypeImpl(it.java) }
override val variant get() = emptyList<Nothing>()
override val multiThreadAccess: Boolean get() = true
}

private inner class RtSubcomponentAnnotationDaggerCompatImpl(
impl: Subcomponent,
) : RtAnnotationImplBase<Subcomponent>(impl), BuiltinAnnotation.Component, LexicalScope by this {
override val isRoot: Boolean get() = false
override val modules get() = impl.modules.map { RtTypeImpl(it.java) }
override val dependencies get() = emptyList<Nothing>()
override val variant get() = emptyList<Nothing>()
override val multiThreadAccess: Boolean get() = true
}

private inner class RtModuleAnnotationDaggerCompatImpl (
impl: Module,
) : RtAnnotationImplBase<Module>(impl), BuiltinAnnotation.Module, LexicalScope by this {
override val includes get() = impl.includes.map { RtTypeImpl(it.java) }
override val subcomponents get() = impl.subcomponents.map { RtTypeImpl(it.java) }
}

private class RtIntoSetAnnotationDaggerCompatImpl(
impl: IntoSet,
) : RtAnnotationImplBase<IntoSet>(impl), BuiltinAnnotation.IntoCollectionFamily.IntoSet {
override val flatten: Boolean
get() = false
}

private class RtElementsIntoSetAnnotationDaggerCompatImpl(
impl: ElementsIntoSet,
) : RtAnnotationImplBase<ElementsIntoSet>(impl), BuiltinAnnotation.IntoCollectionFamily.IntoSet {
override val flatten: Boolean
get() = true
}

private class RtAssistedAnnotationDaggerCompatImpl(
impl: Assisted,
) : RtAnnotationImplBase<Assisted>(impl), BuiltinAnnotation.Assisted {
override val value: String
get() = impl.value
}
}

class Stub : RtDaggerCompat {
override fun hasAssistedInject(c: AnnotatedElement) = false
override fun hasAssistedFactory(c: AnnotatedElement) = false
override fun hasComponentBuilder(c: AnnotatedElement) = false
override fun hasBinds(c: AnnotatedElement) = false
override fun hasBindsInstance(c: AnnotatedElement) = false
override fun hasProvides(c: AnnotatedElement) = false
override fun hasIntoMap(c: AnnotatedElement) = false
override fun hasMultibinds(c: AnnotatedElement) = false
override fun hasMapKey(c: AnnotatedElement) = false
override fun isReusable(c: ReflectAnnotation) = false
override fun isBindsInstance(c: ReflectAnnotation) = false
override fun asAssisted(c: ReflectAnnotation) = null
override fun getIntoSet(c: AnnotatedElement) = null
override fun getModule(c: AnnotatedElement) = null
override fun getComponent(c: AnnotatedElement) = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.yandex.yatagan.lang.rt

import com.yandex.yatagan.lang.LangModelFactory
import com.yandex.yatagan.lang.TypeDeclaration
import com.yandex.yatagan.lang.common.LangOptions
import com.yandex.yatagan.lang.common.scope.LexicalScopeBase
import com.yandex.yatagan.lang.scope.CachingMetaFactory
import com.yandex.yatagan.lang.scope.LexicalScope
Expand All @@ -14,13 +15,26 @@ import java.lang.ref.SoftReference
*/
class RtLexicalScope(
classLoader: ClassLoader,
daggerCompatibilityMode: Boolean,
) : LexicalScopeBase() {
init {
ext[CachingMetaFactory] = SoftReferenceCachingFactory
ext[LangModelFactory] = RtModelFactoryImpl(
lexicalScope = this,
classLoader = classLoader,
)

val useDaggerCompat = daggerCompatibilityMode && try {
// Do not use compat mechanism if the dagger api is missing
classLoader.loadClass("dagger.Component"); true
} catch (e: ClassNotFoundException) {
false
}

ext[LangOptions] = LangOptions(
daggerCompatibilityMode = useDaggerCompat,
)
ext[RtDaggerCompat] = if (useDaggerCompat) RtDaggerCompat.Impl(this) else RtDaggerCompat.Stub()
}

fun getTypeDeclaration(clazz: Class<*>): TypeDeclaration {
Expand Down
30 changes: 18 additions & 12 deletions lang/rt/src/main/kotlin/com/yandex/yatagan/lang/rt/RtMethodImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ internal class RtMethodImpl(
): T? {
val annotation: BuiltinAnnotation.OnMethod? = when (which) {
BuiltinAnnotation.Binds -> (which as BuiltinAnnotation.Binds)
.takeIf { impl.isAnnotationPresent(Binds::class.java) }
.takeIf { impl.isAnnotationPresent(Binds::class.java) || daggerCompat().hasBinds(impl) }
BuiltinAnnotation.BindsInstance -> (which as BuiltinAnnotation.BindsInstance)
.takeIf { impl.isAnnotationPresent(BindsInstance::class.java) }
.takeIf { impl.isAnnotationPresent(BindsInstance::class.java) || daggerCompat().hasBindsInstance(impl) }
BuiltinAnnotation.Provides -> (which as BuiltinAnnotation.Provides)
.takeIf { impl.isAnnotationPresent(Provides::class.java) }
.takeIf { impl.isAnnotationPresent(Provides::class.java) || daggerCompat().hasProvides(impl) }
BuiltinAnnotation.IntoMap -> (which as BuiltinAnnotation.IntoMap)
.takeIf { impl.isAnnotationPresent(IntoMap::class.java) }
.takeIf { impl.isAnnotationPresent(IntoMap::class.java) || daggerCompat().hasIntoMap(impl) }
BuiltinAnnotation.Multibinds -> (which as BuiltinAnnotation.Multibinds)
.takeIf { impl.isAnnotationPresent(Multibinds::class.java) }
.takeIf { impl.isAnnotationPresent(Multibinds::class.java) || daggerCompat().hasMultibinds(impl) }
BuiltinAnnotation.Inject -> (which as BuiltinAnnotation.Inject)
.takeIf { impl.isAnnotationPresent(Inject::class.java) }
}
Expand All @@ -92,14 +92,20 @@ internal class RtMethodImpl(
which: BuiltinAnnotation.Target.OnMethodRepeatable<T>,
): List<T> {
return when (which) {
BuiltinAnnotation.IntoCollectionFamily -> {
impl.declaredAnnotations.mapNotNull {
when (it) {
is IntoList -> which.modelClass.cast(RtIntoListAnnotationImpl(it))
is IntoSet -> which.modelClass.cast(RtIntoSetAnnotationImpl(it))
else -> null
BuiltinAnnotation.IntoCollectionFamily -> buildList {
var hasIntoSet = false
for (annotation in impl.declaredAnnotations) {
when(annotation) {
is IntoList -> add(which.modelClass.cast(RtIntoListAnnotationImpl(annotation)))
is IntoSet -> {
hasIntoSet = true
add(which.modelClass.cast(RtIntoSetAnnotationImpl(annotation)))
}
}
}
if (!hasIntoSet) {
daggerCompat().getIntoSet(impl)?.let { add(which.modelClass.cast(it)) }
}
}
BuiltinAnnotation.Conditional -> buildList {
for (annotation in impl.declaredAnnotations) when (annotation) {
Expand All @@ -114,7 +120,7 @@ internal class RtMethodImpl(
private inner class ParameterImpl(
val index: Int,
) : RtParameterBase(), LexicalScope by this {
override val parameterAnnotations: Array<kotlin.Annotation>
override val parameterAnnotations: Array<ReflectAnnotation>
get() = parametersAnnotations[index]

override val name: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,18 @@ internal abstract class RtParameterBase : ParameterBase(), LexicalScope {
final override fun <T : BuiltinAnnotation.OnParameter> getAnnotation(
which: BuiltinAnnotation.Target.OnParameter<T>,
): T? {
val daggerCompat = daggerCompat()
val annotation: BuiltinAnnotation.OnParameter? = when (which) {
BuiltinAnnotation.BindsInstance -> BuiltinAnnotation.BindsInstance.takeIf {
parameterAnnotations.any { it is BindsInstance }
parameterAnnotations.any { it is BindsInstance || daggerCompat.isBindsInstance(it) }
}

BuiltinAnnotation.Assisted -> run {
for (annotation in parameterAnnotations)
for (annotation in parameterAnnotations) {
if (annotation is Assisted)
return@run RtAssistedAnnotationImpl(annotation)
daggerCompat.asAssisted(annotation)?.let { return@run it }
}
null
}
}
Expand Down
Loading

0 comments on commit 25bb3e0

Please sign in to comment.