Code Monkey home page Code Monkey logo

yatagan's Introduction

Yatagan

Maven Central CI codecov License

Yatagan is a fast Dependency Injection framework based on Google's Dagger2 API.

Yatagan is optimized for fast builds and supports managing large dynamic dependency graphs by introducing Conditions and Variants. It's primary goal is to improve build speed in large complex projects, which already use Dagger. Though it might be useful for others.

All core Dagger API is supported with little changes. Yet dagger-android, Hilt and a couple of less popular features are not supported. See comparative API reference for full info.

Yatagan can work in multiple modes (use different backends):

  • With code generation
    • APT/KAPT - classic mode.
    • KSP - leverages new Google KSP framework. Experimental, see KSP support status.
  • Via runtime Java reflection - a backend designed for fast local debug builds, see specific notes.

All backends are designed to be working identically, so one can easily switch backends depending on the working environment and requirements. Any inconsistencies between backends ought to be reported and fixed.

Motivation

One can consider migrating to Yatagan from vanilla Dagger if at least one of the points is true for their project:

  1. The project uses Dagger and has build performance issues because of it.
  2. The project needs/extensively uses dynamic optional dependencies in its DI graphs.

Yatagan tackles both of these issues.

Read more and dive into technical details in the Medium article.

Performance

As of the first point, performance gains can vary per project due to specific details and Yatagan usage configuration. Yatagan allows clients to apply processing to fewer project modules in comparison to Dagger. Yatagan processing should only be applied to project modules, that contain root components, and shouldn't be applied at all in case of reflection mode usage.

Thus, a project will have the biggest performance gain from using Yatagan, if the majority of the project modules have only one annotation processor - Dagger. Then upon migration to Yatagan project modules without root components can have kapt completely switched off with it remaining in root-component modules. Furthermore, root-component modules can also become kapt-free with Reflection mode. In a good scenario Yatagan can make incremental builds up to two times faster.

If other annotation processors besides Dagger are used throughout the project in KAPT mode, then performance gain from using Yatagan will be lower. One can try and reorganise the project to limit other annotation processors appliance to small modules or use them in KSP mode if supported. Reflection mode is also supported for some frameworks that feature code generation. It can be enabled in debug builds if this allows to eliminate KAPT from the project module.

The general idea is to remove KAPT from as many modules as possible, with large modules yielding more profit, so feel free to experiment with what Yatagan offers for this task.

In the worst case scenario, where using Yatagan doesn't remove KAPT from any of the modules, profits can still be around ~ 10% due to Yatagan optimizations.

Runtime conditions

The second point can be clarified by the following code snippet:

@Singleton
class MyClass @Inject constructor(
    /**
     * Dynamic optional dependency, that is present/absent in DI-graph based on declared runtime condition.
     */
    val myOptionalDependency: Optional<MyClassUnderRuntimeCondition>,
)

This is one of the approaches that can be taken into coding optional dependencies. Naturally, such thing can be written with Dagger's @Provides Optional<MyClassUnderRuntimeCondition> provide(..) { .. } yet such code is arguably difficult to maintain, verbose, and scales poorly with the growing number of conditions and classes under them.

Yatagan solves this by introducing first-class runtime condition support with compile-time condition validation. See Conditions/Variants APIs.

Usage (Gradle)

Code generation dependency is only required for project modules, that contain root component declarations (@Component(isRoot = true/* default */)). For modules, that contain classes with @Inject, @Provides, etc.. no dependencies but "api" ones are required. This is different for Dagger, which requires you to apply annotation processing in every module with DI code to allow Gradle incremental compilation/annotation processing to work correctly.

Yatagan can be used in various configurations. Choose one, that suits your project. See the following Gradle buildscript usage snippets (code is assumed to be inside a dependencies {} block).

For kotlin-only/mixed project using kapt:

// Ensure `kotlin-kapt` plugin is applied
api("com.yandex.yatagan:api-compiled:${yataganVer}")
// kapt is slow but generally reliable for mixed projects.
kapt("com.yandex.yatagan:processor-jap:${yataganVer}")

For kotlin-only/mixed project using KSP (use with caution for Java code): (How to apply KSP plugin)

// Ensure `com.google.devtools.ksp` plugin is applied
api("com.yandex.yatagan:api-compiled:${yataganVer}")
// KSP implementation is unstable. Works best for pure-Kotlin projects.
ksp("com.yandex.yatagan:processor-ksp:${yataganVer}")

To dramatically speed up build one can use runtime reflection instead of codegen:

// No codegen dependency is required, the reflection engine comes as a dependency of the `api-dynamic` artifact.
api("com.yandex.yatagan:api-dynamic:${yataganVer}")

For java-only project:

api("com.yandex.yatagan:api-compiled:${yataganVer}")
// best codegen backend for Java-only, no need to use kapt/ksp.
annotationProcessor("com.yandex.yatagan:processor-jap:${yataganVer}")

Android projects are advised to follow the same usage guidelines, though make sure to read the notes on reflection on Android. An example of a recommended way to use Yatagan for Android projects:

// Use reflection in debug builds.
debugApi("com.yandex.yatagan:api-dynamic:${yataganVer}")

// Use codegen in releases
releaseApi("com.yandex.yatagan:api-compiled:${yataganVer}")
if (kspEnabled) {
    kspRelease("com.yandex.yatagan:processor-ksp:${yataganVer}")
} else {
    kaptRelease("com.yandex.yatagan:processor-jap:${yataganVer}")
}

One may want to create a shared library that exposes a piece of Yatagan graph, yet doesn't create any root components itself. In this case, the library can depend on com.yandex.yatagan:api-public, which provides pure Yatagan API and no backend-specific entry-points.

Backends

KAPT/APT

APT or KAPT (Yatagan qualifies the artifacts with jap, java annotation processing) is a legacy backend, though it's stable and can be reliably used by default.

KSP support

Yatagan supports KSP in experimental mode. This is mostly due to the fact that Yatagan operates in terms of Java type system and is very sensitive to type equality. In Kotlin, Collection and MutableCollection are different types, though in Java it's the same type. From the other hand, Kotlin's Int is represented in Java as int and Integer. Choosing Java types to maintain semantic compatibility with Dagger, Yatagan converts Kotlin types into Java ones. KSP API related to JVM is explicitly marked as @KspExperimental, and practice shows KSP support for modeling Java code is at least inconsistent.

Thus, KSP can be adopted for Kotlin-only projects, or projects whose DI-code is mostly Kotlin. Additional care should be taken with Java projects.

Also, KSP strictly depends on Kotlin compiler version, used in your project, so using KSP may force you to keep updating Kotlin compiler version frequently.

Reflection support

Reflection support is considered stable in Yatagan. There's already a very similar project for the vanilla Dagger - dagger-reflect. However, Reflection mode in Yatagan has fist-class support and guaranteed to behave the same way, as generated implementation would. If a new feature is implemented in Yatagan, reflection automatically works with it.

Technically, reflection mode can be used in production, though it's advised not to do so, as code generation naturally produces much more performant code. Also, reflection mode is broken by code minifiers, such as Proguard or R8.

Read more in reflection backend specific notes.

Android

Reflection backend fully supports Android applications starting with minSdk = 24. Below that, static methods in interfaces are not directly supported in Android and have to be "desugared" by AGP. Yatagan Reflection doesn't currently read such desugared methods as they have no stable ABI and reading them will bring performance penalties. So consider using minSdk = 24 at least for debug build type to safely use Yatagan with Reflection.

Yatagan vs Dagger API reference

Dagger2 API (dagger.**) Status in Yatagan Notes
@Component ๐ŸŸข as is
@Component.Builder ๐ŸŸข as is supports factory method as well
@Component.Factory ๐ŸŸก converged functionality merged into @Builder
@Subcomponent ๐ŸŸก converged replaced by Component(isRoot = false)
@Subcomponent.{Builder/Factory} ๐ŸŸก converged replaced by Component.Builder
Lazy ๐ŸŸข as is now also extends javax.inject.Provider
@Module ๐ŸŸข as is
@Binds ๐ŸŸก tweaked can bind zero/multiple alternatives
@BindsInstance ๐ŸŸข as is
@Provides ๐ŸŸข as is supports conditional provision
@BindsOptionalOf ๐ŸŸก replaced replaced with Variants API
@Reusable ๐ŸŸข as is
MembersInjector ๐Ÿ”ด unsupported
@MapKey ๐ŸŸก renamed* IntoMap.Key, *unwrap=false is unsupported
@multibindings.IntoSet ๐ŸŸข as is
@multibindings.ElementsIntoSet ๐ŸŸก converged IntoSet(flatten = true)
@multibindings.Multibinds ๐ŸŸข as is
@multibindings.IntoMap ๐ŸŸข as is
@multibindings.{Int,Class,String}Key ๐ŸŸข as is
@multibindings.LongKey ๐Ÿ”ด removed can be declared manually if required
assisted.* ๐ŸŸข as is
producers.* ๐Ÿ”ด unsupported
android.* ๐Ÿ”ด unsupported
grpc.* ๐Ÿ”ด unsupported
hilt.** ๐Ÿ”ด unsupported
spi.* ๐ŸŸก replaced Yatagan has its own model for SPI

Other behavioral changes:

  • @Binds can't be scoped (scope rebind is not allowed). Use scope on the implementation. Also, Yatagan supports declaring multiple scopes on bindings, so the binding is compatible with every scope declared. Dagger only allowed doing so for components.

  • Yatagan requires components, builders, assisted inject factories to be declared as interfaces. Abstract classes are forbidden. This is due to the limitations of RT mode. Dagger-reflect has the same limitation.

  • If codegen is used, generated component implementations are not named Dagger<component-name>, their names are mangled, and the access should be made via Yatagan.builder()/Yatagan.create() entry-point invocations. This is made to support reflection backend. Actual Yatagan implementations are provided within com.yandex.yatagan:api-dynamic and com.yandex.yatagan:api-compiled artifacts.

  • Yatagan does not support @Nullable provisions. If a binding returns null, or a @BindsInstance is supplied with null, an error will be thrown at run-time. Currently, no compile-time validation is done in the matter.

  • Automatic component factory/builder generation is not supported - an explicit one must be written if required.

  • Member inject in Kotlin code should be used with care: @Inject lateinit var prop: SomeClass will work as expected, though @Inject @Named("id") lateinit var prop: SomeClass will not - qualifier annotation will go to the property instead of field, and Yatagan will not be able to see it. In fact vanilla Dagger will also fail to see it in some scenarios, though it tries to do so on the best-effort basis. Yatagan can't read annotations from Kotlin properties, so the following explicit forms should be used instead: @Inject @field:Named("id") lateinit var prop: SomeClass to inject directly to the field, or @set:Inject @set:Named("id") lateinit var prop: SomeClass to inject via setter.

Yatagan was written from scratch, and as major known inconsistencies are documented here, there is a possibility for differences that are overlooked. If you happen to discover one, please report it.

Migration from Dagger

Strictly speaking, Yatagan and Dagger are not directly compatible. Yatagan uses a separate binary-incompatible set of annotations and helpers to give it a degree of freedom to extend and enhance the API.

Yet for the majority of cases, as documented in the api reference, annotations and classes differ only in package names, which makes migration from Dagger to Yatagan somewhat trivial.

The general idea of steps one needs to take to migrate from Yatagan to Dagger:

  1. Replace import dagger\.multibindings\. -> import com.yandex.yatagan.
  2. Replace import dagger\.assisted\. -> import com.yandex.yatagan.
  3. Replace import dagger\. -> import com.yandex.yatagan.
  4. Replace @Subcomponent annotations with @Component(isRoot = false) ones.
  5. Replace @Component.Factory with @Component.Builder.
  6. Get rid of all nullable provisions. Yatagan does not support them.
  7. Replace DaggerMyComponent.builder() with Yatagan.builder(MyComponent.Builder::class.java) or similar.
  8. Mark all components, that are accessed from multiple threads as @Component(.., multiThreadAccess = true). If you are unsure, if a component is accessed from a single thread, but ideally it should be, you can set up a check with Yatagan.setThreadAsserter().
  9. Run build and fix all remaining inconsistencies (like implicitly included subcomponents, etc..).

Added APIs

Yatagan introduces the following new APIs, that can be utilized to work with conditional bindings

The first one is @Condition. With this annotation, one can declare a runtime condition that can be evaluated and its value will determine the presence/absence of a binding under the condition.

To put a binding under a given condition, one must use @Conditional annotation on a binding or a class with @Inject-annotated constructor.

Variant API ideally replaces Dagger's @BindsOptionalOf and makes it more powerful. It's very alike to how Android works with flavors and dimensions, only here we can declare components having such flavors and include/exclude bindings based on them. To use that, one can employ @Conditional(.., onlyIn = ...) and @Component(variant = ...) attributes.

Feel free to read a small tutorial doc, that includes how to use conditions and variants.

Plugins

One can write an extension for validation pipeline for Yatagan to implement one's custom graph inspections. No additional code generation is currently supported for plugins, and they can not modify graphs under inspection. This works, as for Dagger, via SPI. Read more here.

Options

Yatagan has some options, that tweak its behavior. They are provided as normal annotation processor options. However, reflection backend requires a different approach in specifying them, as documented here.

Option key Default value Description
yatagan.enableStrictMode true if enabled, every mandatory warning is reported as an error
yatagan.maxIssueEncounterPaths 5 the max number of places Encountered in in an error message to be mentioned
yatagan.usePlainOutput false if enabled, reporting is done in plain text, without ANSI coloring

yatagan's People

Contributors

bacecek avatar dependabot[bot] avatar jeffset avatar nevack avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yatagan's Issues

Remove dependency on ANTLR

Original report (from gradle build):

Task :<redacted>:<redacted>:kaptReleaseKotlin FAILED
ANTLR Tool version 4.11.1 used for code generation does not match the current runtime version 4.7.2ANTLR Runtime version 4.11.1 used for parser compilation does not match the current runtime version 4.7.2

This happens because yatagan uses LogicNG library for condition validation, which depends on ANTLR framework for formula parsing. LogicNG's builtin formula parsing is not used in yatagan, but the dependency remains and may conflict in build/runtime classpath.

The goal of the issue here is to remove ANTLR from yatagan's (transitive) dependencies.

Add a mandatory warning about unreachable conditions in `@Binds` with multiple alternatives.

Let's consider the following code:

@Binds fun myApiWithMultipleImplementations(
    first: FirstImpl,
    second: SecondImpl
): MyApi

If FirstImpl is under condition, that is implied by the SecondImpl's condition (or simply without condition) then the SecondImpl is unreachable and never will be bound to MyApi. This reasoning can be applied likewise to more than two alternatives.

It would be nice to warn the user about such situations, as it's most likely not what the user wanted to achieve.

Try to distinguish between boxed and primitive types by nullability

In order to correctly override methods in Java code in KSP mode, Yatagan uses KSP's experimental APIs to obtain JVM signatures to distinguish between boxed/unboxed java types (e.g. java.lang.Integer vs int).
As per google/ksp#870 (comment), there can be no need for that, nullability info alone may be sufficient to make the distinction.

Let's give it a shot. If it's indeed possible, removing cumbersome JVM-signature obtaining/parsing will greatly simplify the implementation while likely improving performance.

Caching omission optimization doesn't trigger for some corner cases.

Consider the following code:

import com.yandex.yatagan.*
import javax.inject.*

interface Api

class Foo : Api
class Bar : Api

@Singleton class ListConsumer @Inject constructor(val list: List<Api>)

@Module
class MyModule {
  @[Provides Singleton IntoList]
  fun provideFooIntoApis(): Api = Foo()
  @[Provides Singleton IntoList]
  fun provideBarIntoApis(): Api = Bar()
}

@Singleton
@Component(modules = [MyModule::class])
interface MyComponent {
  val consumer: ListConsumer
}

This code generates caching for MyModule::provideFooIntoApis(), MyModule::provideBarIntoApis(), but they are only used in the List<Api> which is not cached but used only in cached ListConsumer. So it's safe to omit caching for the provisions.

This is rather a missed opportunity than a real bug, as it doesn't harm the correctness of the generated code, so low priority.

Allow for @Builder's factory methods to return supertypes

In Yatagan component's factory must return type of the component. It will be nice to support the same possibility in Yatagan

interface PublicComponent

@Component
internal interface ComponentInternal : PublicComponent {

    @Component.Builder
    interface Factory {

        // Work in Dagger. Yatagan doesn't work
        fun create(): PublicComponent
    }
}
interface PublicComponent

@Component
internal interface ComponentInternal : PublicComponent {

    @Component.Builder
    interface Factory {

        // Both Dagger and Yatagan work
        fun create(): ComponentInternal
    }
}

Support Kotlin code generation

As of now, Yatagan can only generate Java code both in KAPT and in KSP.

In order to support KMP, Yatagan should be able to generate platform-agnostic Kotlin code.
There're some ways to design that. The way, most likely to be taken, is to generate Kotlin code only with another "pure-KSP" backend, that has limited compatibility with Java-oriented KAPT/KSP backends.

The nature of changes to :lang layer to support that is yet to be determined.

Duplicate `@Binds` are not reported in some cases.

The title.

import com.yandex.yatagan.*
import javax.inject.*

interface Api
class Impl1 @Inject constructor(): Api
class Impl2 @Inject constructor(): Api

@Module interface MyModule {
  @Binds fun bind1(i: Impl1): Api
  @Binds fun bind2(i: Impl2): Api
}

@Component(modules = [MyModule::class])
interface MyComponent {
  val api: Api
}

The code compiles OK and runs (Impl1 is always used for KAPT, KSP and RT - tehnically full UB).
Unacceptable.

Add performance tests

Develop performance testing for all backends.

We want to measure the following metrics:

  1. Build Time (graph parsing/building/generation time)
  2. Startup Time (component implementation creation time)
  3. Responsiveness (graph operation efficiency)

Write more `:lang`-level tests

As of now, the majority of test coverage is done by integration tests in :testings:tests. Yet :lang level deserves its own, more narrow, set of tests to ensure that all backends work identically. This way there'll be a possibility to remove some intergration tests that try to test :lang-specific quirks.

Some :lang tests are already written, need to complete the process.

Binding from the same module, included into a parent and a child components is presumed duplicated with itself.

import com.yandex.yatagan.*
import javax.inject.*

class TestClass @Inject constructor()

@Module interface MyModule {
    @Binds fun binds(i: TestClass): Any
}

@Component(modules = [MyModule::class])
interface TestComponent {
    val obj: Any
    fun createSub(): SubComponent
}

@Component(isRoot = false, modules = [MyModule::class])
interface SubComponent {
    val obj: Any
}

Produces

Conflicting bindings for `java.lang.Object`
NOTE: Conflicting binding: `alias test.MyModule::binds(test.TestClass)`
NOTE: Conflicting binding: `alias test.MyModule::binds(test.TestClass)`

Add an "extended validation" mode, where every framework annotation is queried and checked for valid usage

Currently, one can use any framework annotation in any place where annotation target allows, but this "odd" usages are not validated by the framework. For example, if one uses @Conditional on a class without an inject constructor or on a builder input, or anywhere else, Yatagan won't complain, it will simply ignore such usages, because it never queries anything besides @Component annotation.

Querying all the framework annotations to validate their usage is a prudent thing to do, yet it can penalize code generation performance and build "incrementality".

The solution would be to introduce an annotation processing option, specific to the codegen backends, that enables such extensive validation. Such option can be enabled in environments less sensitive to build performance and incremental rebuilds, like CI checks.

Reversed annotations order in KAPT and KT-51087

As https://youtrack.jetbrains.com/issue/KT-51087 was fixed in 1.8.20, and Yatagan unconditionally reverses annotations order for Kotlin elements, it's high time to fix that properly.

The proposed solution is to read Kotlin metadata version and base the decision to reverse annotations' order on that.

Generally, it's not a big issue, however in Yatagan we have poorly designed Conditions API (1.0, 2.0 is much better and is not affected by this issue) which requires annotation order to be well-defined.

Generate cascading switch statements when number of bindings is large

Yatagan, as Dagger in fastInit uses "switching providers" mechanism to reduce classloading. This means that for every binding, that can't be created inline (has Lazy/Provider usages) there's an index assigned. And every provider/lazy object instantiates the binding using the index and a lookup (switch) table.

Now, the hypothesis is, that when the lookup table becomes very large, the performance is going to be penalized. Yatagan currently generates plain switch statement, while Dagger did so with only up to 100 objects, after that it generated another switch level with index % 100 labels.

Let's try to investigate and possibly introduce the optimization.

Might be reasonable to do when the performance testing is implemented in #7

Implement Gradle plugin that would take care of configuring dependencies

Yatagan has multiple modes of operation with multiple backends. Configuring them correctly is not a trivial task and requires consulting the docs. It would be great to have a Gradle plugin that would help clients to do just that.

Depends on #30 as of now it's not really possible to write a general configuring logic.

Provide correct type names for unresolved types instead of `error.UnresolvedCla$$`

Currently Yatagan yields fake error.UnresolvedCla$$ stub name for every unresolved type. This is the case for both KSP and KAPT.

This is the uniformized behavior introduced because:

  1. KSP doesn't provide real unresolved type name at all, only generic <ERROR TYPE> string can be obtained
  2. KAPT requires correctErrorTypes option to be set, otherwise it provides error.NonExistentClass.

So, Yatagan detects those cases and provides error.UnresolvedCla$$ for every backend.

But this is really not an acceptable behavior, as sometimes there's no way to identify what types are actually missing/unresolved, if they are not used in hand-written code though are missing from the compilation classpath (typically when users use implementation instead of api in Gradle).

So there's actually several problems here:

  1. No names for unresolved/missing types at all.
  2. Yatagan doesn't explicitly check for error types in graphs, and may even be able to generate code with them. The code will then fail to compile, leaving the user with syntactic errors concerning error.UnresolvedCla$$ and with no info on what those types actually are and from where they are referenced from.

In order to support error type names in KSP, the google/ksp#1232 needs to be resolved first.
Support from the KAPT backend can be added already, but there's no way to test this, as correctErrorTypes option is inaccessible in room-compile-testing, so the issue https://issuetracker.google.com/u/2/issues/248552462 has to be resolved for that.

Yatagan error types detection can (and should) be implemented now, nothing blocks it.

  1. Implement error type detection in validation pipeline (in order to provide the user with the info on where the unresolved types are referenced)
  2. Maybe implement correct error types with KAPT backend (no test coverage possible though?)
  3. Wait for KSP support for error type names and add it later.

Crash: Required value was null (at com.yandex.yatagan.codegen.impl.ComponentFactoryGenerator.fieldNameFor(ComponentFactoryGenerator.kt:85))

Yatagan version: 1.2.1

Crash occures when the following situation arises:
Given the condition A; given three components C0, C1, C2, such as
C0 (root) <- C1 <- C2 in terms of hierarchy.
And the condition A is used in C0, not used in C1, and again used in C2.

java.lang.IllegalStateException: Required value was null.
	at com.yandex.yatagan.codegen.impl.ComponentFactoryGenerator.fieldNameFor(ComponentFactoryGenerator.kt:82)
	at com.yandex.yatagan.codegen.impl.GeneratorUtilsKt.componentInstance(generatorUtils.kt:42)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator$EagerAccessStrategy.access(ConditionGenerator.kt:139)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.access(ConditionGenerator.kt:58)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.access(ConditionGenerator.kt:64)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.access(ConditionGenerator.kt:64)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.access(ConditionGenerator.kt:64)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.access$access(ConditionGenerator.kt:34)
	at com.yandex.yatagan.codegen.impl.ConditionGenerator.expression-wmBEi3A(ConditionGenerator.kt:91)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitAlternatives(generateCreation.kt:124)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitAlternatives(generateCreation.kt:45)
	at com.yandex.yatagan.core.graph.impl.bindings.AlternativesBindingImpl.accept(AlternativesBindingImpl.kt:52)
	at com.yandex.yatagan.codegen.impl.GenerateCreationKt.generateCreation(generateCreation.kt:231)
	at com.yandex.yatagan.codegen.impl.InlineCreationStrategy.generateAccess(accessStrategies.kt:170)
	at com.yandex.yatagan.codegen.impl.GeneratorUtilsKt.generateAccess(generatorUtils.kt:66)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor$visitProvision$1$1.genArgs(generateCreation.kt:61)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor$visitProvision$1$1.visitConstructor(generateCreation.kt:87)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor$visitProvision$1$1.visitConstructor(generateCreation.kt:58)
	at com.yandex.yatagan.lang.common.ConstructorBase.accept(ConstructorBase.kt:24)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitProvision(generateCreation.kt:58)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitProvision(generateCreation.kt:45)
	at com.yandex.yatagan.core.graph.impl.bindings.InjectConstructorProvisionBindingImpl.accept(InjectConstructorProvisionBindingImpl.kt:61)
	at com.yandex.yatagan.codegen.impl.GenerateCreationKt.generateCreation(generateCreation.kt:231)
	at com.yandex.yatagan.codegen.impl.CachingStrategySingleThread.generateInComponent(accessStrategies.kt:85)
	at com.yandex.yatagan.codegen.impl.CompositeStrategy.generateInComponent(accessStrategies.kt:232)
	at com.yandex.yatagan.codegen.impl.AccessStrategyManager.generate(AccessStrategyManager.kt:176)
	at com.yandex.yatagan.codegen.impl.ComponentGenerator.generate(ComponentGenerator.kt:233)
	at com.yandex.yatagan.codegen.impl.ComponentGenerator.generate(ComponentGenerator.kt:228)
	at com.yandex.yatagan.codegen.impl.ComponentGenerator.generate(ComponentGenerator.kt:228)
	at com.yandex.yatagan.codegen.impl.ComponentGenerator.generate(ComponentGenerator.kt:228)
	at com.yandex.yatagan.codegen.impl.ComponentGeneratorFacade.generateTo(ComponentGeneratorFacade.kt:38)
	at com.yandex.yatagan.processor.common.ProcessKt.process(process.kt:100)
	at com.yandex.yatagan.processor.ksp.KspYataganProcessor.process(KspYataganProcessor.kt:47)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$6$1.invoke(KotlinSymbolProcessingExtension.kt:291)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$6$1.invoke(KotlinSymbolProcessingExtension.kt:289)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:394)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:289)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:123)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:99)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:257)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:42)
	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:115)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:248)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:88)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:47)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:167)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:53)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:101)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:47)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1645)
	at jdk.internal.reflect.GeneratedMethodAccessor111.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

Do not specify ThreadAsserter globally

Now, Yatagan.threadAsserter property (Yatagan.setThreadAsserter() method for 1.x.y) sets thread asserted globally. This is a poor design choice considering a project may depend on libraries or frameworks, that use Yatagan internally; as all the graphs will share the same global Yatagan state, setting a thread asserter globally is intrusive to all the other graphs and may disrupt their behavior.

Here I propose to introduce a new API for 2.0 that will not have these issues - the thread asserter must be specified explicitly for every graph, not globally.

Support Dagger's @Reusable annotation

I am migrating project from Google Dagger (Dagger 2) to Yatagan and we have a lot of usages of @Reusable annotation. It will be good to have an alternative in Yatagan

Compose "api-*" artifacts contents (in terms of backend choice) more practical

As of now, there are the following "public api" artifacts:

  1. api-public - just the annotations (API), no entry-point (EP - Yatagan object).
  2. api-compiled - depends on api-public, adds Yatagan which loads compiled implementations.
  3. api-dynamic - depends on api-public, adds Yatagan which loads dynamic implementations (can load compiled if configured) and comes with extra API for configuring reflection-specific things.

Now this "layout" seems to work, but apparently has some drawbacks. There's no real problem for the application developers, as they can just use the correct backend-specific artifact and be done with it. But when it comes to library development, some unpleasant things begin to surface: turns out it's not okay for library authors to depend on any backend-specific artifact, as they would actually force the backend usage on all the clients this way. One can't have multiple backends on the runtime classpath, as they would conflict. And the only backend-agnostic artifact is api-public which doesn't contain any Yatagan EPs, so there's no way for a library to create its own components in its code. If the library is not published and serves only as a way to organize code in the multi-module project, developers can get away with twiddling with tools like compileOnly/runtimeOnly and so on, but it is in no way a general solution.

So here I propose to rearrange the api/EP/loader code so its more practical to use and general integration logic can be formulated, maybe even with Gradle plugin.

New structure (draft)

  1. api:public - contains both API and EP*. EP has the following behavior - they try to locate a dynamic loader class (once, the result is cached - very fast). If the loader is present, EP delegates to it. If not - EP loads the compiled implementations.
  2. rt:loader - the dynamic loader class and the reflection engine - loads and serves reflection-based implementations.

*It's not yet clear how to handle reflection-specific API - should they be included into api:public and be no-op by default and delegate to the rt:loader?

This way every project module can depend on api:public and its code will compile regardless. And then, depending on the usage and settings, additional configuration can be done - adding code generators or rt:loader as runtimeOnly. This way published libraries can depend on api:public and ship with generated implementations of their internal components. While clients can still include rt:loader and use reflection backend for themselves.

If the project depends on a library, that uses yatagan internally and the project itself chooses to use rt:loader, then we probably don't want for this to affect the library behavior. In other words, how exactly should api:public's loader logic operate in regards to compiled implementations? Should it load them unconditionally if detected and try to use dynamic loader only if no compiled implementation is present? This works well in theory and preserves library behavior (if it comes with generated impls). But practice shows that build systems can leave stale generated code in the runtime classpath after a developer switched to reflection, e.g. via a build flag, then the loader would still use compiled impl instead of reflection. Need to give this some more thought.

Provide more convenient alternative to `@Condition`-like annotations

Currently, there are several annotations, that can be used to build boolean expressions:

  • Condition - condition literal (may be negated)
  • AnyCondition - OR for Condition
  • AnyConditions - AND (ups!) for AnyCondition (Container annotation).
  • AllConditions - AND for Condition (Container annotation).

Java 8+ repeatable annotations are implemented via container annotations (@JvmRepeatable in new versions of Kotlin). Kotlin's original repeatable annotations, that can only have source retention, are not suitable for our needs. Also there's a Kotlin version dependent ordering bug for repeatable annotations in KAPT, so in Kotlin it became required to use container annotations explicitly.

The first problem here, is that some annotations names (I'm looking at you, AnyConditions) turned out to be very confusing.

Here I'd like to devise new "@Condition" API for Yatagan 2.0, that can supersede the original one.

Provide dedicated Android support in some form

Dagger2 has dagger-android and Hilt, which offer dedicated Android support and simplify injecting dependencies into platform classes.

It's sane to expect Yatagan to provide dedicated Android support in some way. It probably will not be directly compatible with either dagger-android or Hilt, because both are implemented as a separate annotation-processors, which introduces multiple rounds of processing and penalize the build speed. Moreover, Hilt is an aggregating AP so implementing something like that with runtime reflection is impossible.

If Yatagan adds specific support for Android, it would be more close to dagger-android approach, then to Hilt's, though anything can change.

TL; DR; Yatagan has plans to support Android and discussions about the design and the API are going to be happening here.

All @Inject fields/properties must be public

I use Dagger 2 and have a lot of Java fields with Inject annotation and package-private or protected access. With Yatagan isn't possible to have the same restrictions in access and I need to make all fields like this public. I found out that the restriction connected with reflection mode, but reflection API has possibility to allow modify not public fields

Relax subcomponents' builder requirement, simplify their creation

Dagger allows @Subcomponents without an explicit builder if it doesn't have inputs, so the subcomponent's instance can be injected directly as a normal graph node.

Also Dagger doesn't require subcomponents to be declared in @Module(subcomponents = ...), they (or their builders) can be declared as entry-points in a parent component to be recognized as children.

Makes sense to support these cases in Yatagan:

  1. Support child components without an explicit builder
  2. Support including child components that are declared as entry-points in the parent component.

Provide a stable API for plugins.

Currently, Yatagan exposes its :lang, :model:core, :model:graph API which is great and powerful, but it has no stability guarantees, it is an internal-ish API essentially. So, currently its problematic to develop and publish plugins for it, because of this lack of stable API. We have two options

  1. Continue to expose the "internal" API but with some stability guarantees (mark unstable things with opt-ins and such).
  2. Write a separate API specifically for plugins, that'll be inherently stable.

Support KMP in `:api`

Introduce Kotlin Multi Platform, write expect/actual aliases for Provider<T>, @Inject, @Scope, etc. from javax.inject in order to make API support multiplatform.

Provide a way to omit component creator declarations when possible to deduce one.

Dagger2 can generate a Builder declaration that accepts all necessary modules and dependencies and produces a component instance, when an explicit declaration is omitted. While this is a convenient and type-safe way to reduce boilerplate code, Yatagan can't do just that - the reflection backend implementation obviously can't generate an API by itself, it can only implement an existing interface.

Thus let's try to think of some way to allow users to omit builder declarations for components (and maybe non-root components too). It's probably impossible to do this in a type-safe manner though.

IllegalArgumentException: No enclosing component class found for interface

Stacktrace:

Caused by: java.lang.IllegalArgumentException: No enclosing component class found for interface com.example.ExampleComponent$Builder
  at com.yandex.yatagan.common.Loader__LoadImplementationKt.loadImplementationByBuilderClass(SourceFile:31)
  at com.yandex.yatagan.common.Loader.loadImplementationByBuilderClass(SourceFile:1)
  at com.yandex.yatagan.Yatagan.builder(SourceFile:51)

Usage:
Component:

package com.example

@Component
interface ExampleComponent {
    
   @Component.Builder
    interface Builder {
        fun build()
    }
}

Component usage:

val component = Yatagan.builder(ExampleComponent::class.java).build()

Environment:
Android Gradle Plugin 8.1.0
R8 8.1.59
openjdk 17.0.8.1

Multi-bindings with inheritance report `conflicting bindings` error if injected in sub-component via `List<? extends T>` type.

The code to triger this is as follows:

import com.yandex.yatagan.*
import javax.inject.*
            
@Module object RootModule {
    @[Provides IntoList] fun two(): Number = 2.0
}

@Module object SubModule {
    @[Provides IntoList] fun four(): Number = 4L
}

@Component(modules = [RootModule::class])
interface RootComponent {
    fun createSub(): SubComponent
    fun numbers(): List<Number>
}

@Component(isRoot = false, modules = [SubModule::class])
interface SubComponent {
    val consumer: Consumer
}

class Consumer @Inject constructor(val numbers: List<Number>)

The code is valid, however Conflicting bindings error is issued.

Check generated code against golden files

Now it's hard to track changes in code, that Yatagan generates, but it would be useful to do so. The idea is to introduce testing generated code against the .golden files for KAPT and KSP.

The problem is KAPT and KSP can differ in declarations order (slightly), which woud lead to comparison failure. The presence of these differences is by itself not a very good thing, so the decision should be made:

  1. Remove the differences entirely and use fully unified set of golden files
  2. Have a set of golden files per backend

Improve error description in @Provides method misplace

I am using ksp in my project. If i place @Provides method in my interface marked with @Module, build fails, but the error may be unclear:
non-static method <methodName> cannot be referenced from a static context and it occures in generated code.
Is it possible to check this and show better error description without even generating code?

example:

@Module
interface Foo {
    @Provides
    fun provideBar(): Bar {
        return Bar()
    }
}

Optimize thread assertions (cache, allow omission)

Try to optimize thread assertions. Now each time the assertion is done, a volatile field needs to be read, even in single-thread environments. It's better to query it once and cache it in the private final field inside the component implementation. It'll change the current behavior, however it seems nobody would possibly want to change thread asserter mid-execution.

Another thing to do, would be to give an (experimental) option to fully omit generation of thread assertions, if a client is sure that everything is okay and wants to squeeze maximum performance.

Crash: Not reached: unreported empty/missing binding: `MissingBindingImpl(<...>)`

Full stack trace:

java.lang.AssertionError: Not reached: unreported empty/missing binding: `MissingBindingImpl(target=com.yandex.yatagan.core.model.impl.NodeModelImpl@46a7b47, owner=com.yandex.yatagan.core.graph.impl.BindingGraphImpl@6d340d36)`
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitEmpty(generateCreation.kt:268)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitEmpty(generateCreation.kt:44)
	at com.yandex.yatagan.core.graph.impl.bindings.MissingBindingImpl.accept(MissingBindingImpl.kt:50)
	at com.yandex.yatagan.codegen.impl.GenerateCreationKt.generateCreation(generateCreation.kt:285)
	at com.yandex.yatagan.codegen.impl.InlineCreationStrategy.generateAccess(accessStrategies.kt:175)
	at com.yandex.yatagan.codegen.impl.GeneratorUtilsKt.generateAccess(generatorUtils.kt:68)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor$visitProvision$1$1.genArgs(generateCreation.kt:60)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor$visitProvision$1$1.visitConstructor(generateCreation.kt:92)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor$visitProvision$1$1.visitConstructor(generateCreation.kt:57)
	at com.yandex.yatagan.lang.common.ConstructorBase.accept(ConstructorBase.kt:24)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitProvision(generateCreation.kt:57)
	at com.yandex.yatagan.codegen.impl.CreationGeneratorVisitor.visitProvision(generateCreation.kt:44)
	at com.yandex.yatagan.core.graph.impl.bindings.InjectConstructorProvisionBindingImpl.accept(InjectConstructorProvisionBindingImpl.kt:61)
	at com.yandex.yatagan.codegen.impl.GenerateCreationKt.generateCreation(generateCreation.kt:285)
	at com.yandex.yatagan.codegen.impl.InlineCreationStrategy.generateAccess(accessStrategies.kt:175)
	at com.yandex.yatagan.codegen.impl.GeneratorUtilsKt.generateAccess(generatorUtils.kt:68)
	at com.yandex.yatagan.codegen.impl.ComponentGenerator.generate(ComponentGenerator.kt:89)
	at com.yandex.yatagan.codegen.impl.ComponentGenerator.generate(ComponentGenerator.kt:166)
	at com.yandex.yatagan.codegen.impl.ComponentGeneratorFacade.generateTo(ComponentGeneratorFacade.kt:47)
	at com.yandex.yatagan.processor.common.ProcessKt.process(process.kt:104)
	at com.yandex.yatagan.processor.ksp.KspYataganProcessor.process(KspYataganProcessor.kt:47)
        ...

Code for reproduction:

import com.yandex.yatagan.*
import javax.inject.*
interface Api
interface Api2
class Impl @Inject constructor(api2: Api2): Api
class Impl2 @Inject constructor(): Api2

@Module interface MyModule1 {
    @Binds fun api(i: Impl): Api
    @Binds fun api2(i: Impl2): Api2
}
@Module interface MyModule2 {
    @Binds fun api(i: Impl): Api
}

@Component interface RootComponent {
    fun createSub2(): SubComponent2
    fun createSub1(): SubComponent1
}

@Component(isRoot = false, modules = [MyModule1::class]) interface SubComponent1 {
    val api: Api
}
@Component(isRoot = false, modules = [MyModule2::class]) interface SubComponent2 {
    val api: Api
}

The problem is, that specific equals/hashCode for AliasBindingImpl was written to avoid reporting equivalent @Binds as duplicates (was done in #61). But the validation pipeline puts visited nodes to hash sets. And in this specific case equivalent (but physically different) aliases may end up hiding some graph errors, as validiator thinks that is already visited the alias, but in reaility it visited its "twin". This bug may lead to incorrect graph being generated or crash the generator.

Broken dependency loop validation

Dependency loops are not being correctly validated in some cases. One of the cases being the dependency loop is accessible only via Provider/Lazy edge. There may be other cases, need to investigate.

`IllegalStateException: Not reached: missing binding` with alias included into parent component

Crash

Caused by: java.lang.IllegalStateException: Not reached: missing binding for <...>
        at com.yandex.yatagan.core.graph.impl.BindingGraphImpl.resolveBindingRaw(BindingGraphImpl.kt:142)
        at com.yandex.yatagan.core.graph.impl.BindingGraphImpl.resolveBindingRaw(BindingGraphImpl.kt:141)
        at com.yandex.yatagan.core.graph.impl.BindingGraphImpl$aliasResolveVisitor$1.visitAlias(BindingGraphImpl.kt:131)
        at com.yandex.yatagan.core.graph.impl.BindingGraphImpl$aliasResolveVisitor$1.visitAlias(BindingGraphImpl.kt:130)
        at com.yandex.yatagan.core.graph.impl.bindings.AliasBindingImpl.accept(AliasBindingImpl.kt:74)
        at com.yandex.yatagan.core.graph.impl.BindingGraphImpl.resolveBinding(BindingGraphImpl.kt:136)
        at com.yandex.yatagan.core.graph.impl.TopologicalSortKt.topologicalSort$visit(topologicalSort.kt:55)
        at com.yandex.yatagan.core.graph.impl.TopologicalSortKt.topologicalSort$visit(topologicalSort.kt:55)
        at com.yandex.yatagan.core.graph.impl.TopologicalSortKt.topologicalSort(topologicalSort.kt:68)
        at com.yandex.yatagan.core.graph.impl.bindings.MultiBindingImpl$contributions$2.invoke(MultiBindingImpl.kt:60)
        at com.yandex.yatagan.core.graph.impl.bindings.MultiBindingImpl$contributions$2.invoke(MultiBindingImpl.kt:53)
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
        at com.yandex.yatagan.core.graph.impl.bindings.MultiBindingImpl.getContributions(MultiBindingImpl.kt:53)
        at com.yandex.yatagan.core.graph.impl.bindings.MultiBindingImpl.toString(MultiBindingImpl.kt:92)
        at com.yandex.yatagan.core.graph.impl.bindings.MultiBindingImpl.toString(MultiBindingImpl.kt:34)
        at com.yandex.yatagan.validation.impl.ValidationKt.validate(validation.kt:88)
        at com.yandex.yatagan.processor.common.ProcessKt.process(process.kt:59)
        at com.yandex.yatagan.processor.jap.JapYataganProcessor.process(JapYataganProcessor.kt:80)
        at org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor.process(incrementalProcessors.kt:90)
        at org.jetbrains.kotlin.kapt3.base.ProcessorWrapper.process(annotationProcessing.kt:197)
        at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:985)
        ... 42 more

The crash occurs when a module with an alias (@Binds) is included into a parent component, while the alias' target is materialized in a child component.

java.lang.AssertionError: not reached at com.yandex.yatagan.core.model.impl.ConditionalHoldingModelImpl$ConditionalWithFlavorConstraintsModelImpl.toString(ConditionalHoldingModelImpl.kt:70)

Caused by: java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing(annotationProcessing.kt:90)
	at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing$default(annotationProcessing.kt:31)
	at org.jetbrains.kotlin.kapt3.base.Kapt.kapt(Kapt.kt:47)
	... 33 more
Caused by: com.sun.tools.javac.processing.AnnotationProcessingError: java.lang.AssertionError: not reached
	at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:997)
	at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:901)
	at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1227)
	at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1340)
	at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1258)
	... 39 more
Caused by: java.lang.AssertionError: not reached
	at com.yandex.yatagan.core.model.impl.ConditionalHoldingModelImpl$ConditionalWithFlavorConstraintsModelImpl.toString(ConditionalHoldingModelImpl.kt:70)
	at com.yandex.yatagan.core.model.impl.ConditionalHoldingModelImpl$ConditionalWithFlavorConstraintsModelImpl.toString(ConditionalHoldingModelImpl.kt:57)
	at com.yandex.yatagan.validation.format.Format__FormatKt.append(format.kt:130)
	at com.yandex.yatagan.validation.format.Format.append(Unknown Source)
	at com.yandex.yatagan.validation.format.Strings$Errors.variantMatchingAmbiguity-vkkwLqc(Strings.kt:296)
	at com.yandex.yatagan.core.graph.impl.ConditionScopeForKt.VariantMatch(conditionScopeFor.kt:75)
	at com.yandex.yatagan.core.graph.impl.bindings.InjectConstructorProvisionBindingImpl$variantMatch$2.invoke(InjectConstructorProvisionBindingImpl.kt:42)
	at com.yandex.yatagan.core.graph.impl.bindings.InjectConstructorProvisionBindingImpl$variantMatch$2.invoke(InjectConstructorProvisionBindingImpl.kt:42)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at com.yandex.yatagan.core.graph.impl.bindings.InjectConstructorProvisionBindingImpl.getVariantMatch(InjectConstructorProvisionBindingImpl.kt:42)
	at com.yandex.yatagan.core.graph.impl.bindings.ConditionalBindingMixin.getConditionScope-s2kRxq8(mixins.kt:126)
	at com.yandex.yatagan.core.graph.impl.bindings.InjectConstructorProvisionBindingImpl$dependencies$2.invoke(InjectConstructorProvisionBindingImpl.kt:48)
	at com.yandex.yatagan.core.graph.impl.bindings.InjectConstructorProvisionBindingImpl$dependencies$2.invoke(InjectConstructorProvisionBindingImpl.kt:47)
	at kotlin.SafePublicationLazyImpl.getValue(LazyJVM.kt:107)
	at com.yandex.yatagan.core.graph.impl.bindings.InjectConstructorProvisionBindingImpl.getDependencies(InjectConstructorProvisionBindingImpl.kt:47)
	at com.yandex.yatagan.core.graph.impl.BindingGraphImpl.<init>(BindingGraphImpl.kt:202)
	at com.yandex.yatagan.core.graph.impl.BindingGraphImpl.<init>(BindingGraphImpl.kt)
	at com.yandex.yatagan.core.graph.impl.BindingGraphImpl$3.invoke(BindingGraphImpl.kt:155)
	at com.yandex.yatagan.core.graph.impl.BindingGraphImpl$3.invoke(BindingGraphImpl.kt:154)
	at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:210)
	at kotlin.sequences.SequencesKt___SequencesKt.toCollection(_Sequences.kt:787)
	at kotlin.sequences.SequencesKt___SequencesKt.toMutableList(_Sequences.kt:817)
	at kotlin.sequences.SequencesKt___SequencesKt.toList(_Sequences.kt:808)
	at com.yandex.yatagan.core.graph.impl.BindingGraphImpl.<init>(BindingGraphImpl.kt:165)
	at com.yandex.yatagan.core.graph.impl.BindingGraphImpl.<init>(BindingGraphImpl.kt)
	at com.yandex.yatagan.core.graph.impl.BindingGraphImpl.<init>(BindingGraphImpl.kt:51)
	at com.yandex.yatagan.core.graph.impl.BindingGraphKt.BindingGraph(BindingGraph.kt:30)
	at com.yandex.yatagan.processor.common.ProcessKt.process(process.kt:52)
	at com.yandex.yatagan.processor.jap.JapYataganProcessor.process(JapYataganProcessor.kt:80)
	at org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor.process(incrementalProcessors.kt:90)
	at org.jetbrains.kotlin.kapt3.base.ProcessorWrapper.process(annotationProcessing.kt:197)
	at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:985)
	... 43 more

Add `autoBuilder` to minifier config

autoBuilder was added in #34 but wasn't added to minifier config, instead of create method. Minified builds with Yatagan.autoBuilder() calls would not work.

Calls with Yatagan.create() would also stop working, as they are based on autoBuilder now :(

Introduce `@ValueOf(ConditionExpression(...))` to inject condition values.

An example of the proposed API:

class ClassA @Inject constructor(
  @ValueOf(ConditionExpression("@FeatureA", Conditions.FeatureA::class)) val flag1: Boolean,
  @ValueOf(ConditionExpression("getFeatureC.isEnabled", Features::class)) val flag2: Boolean,
  @ValueOf(ConditionExpression("isEnabledB | fooBar", Features::class)) val flag3: Provider<Boolean>,
) { /**/ }

So, special @ValueOf qualifier allows the user to inject shared condition values.
ConditionExpression is a new Conditions API described in #65 and implemented in #73

What benefits can that produce:

  1. It can improve code testability. E.g. if a class is reading static conditions in its code and one needs to mock a global environment inside a test, which is often painful. With @ValueOf one can inject boolean values directly into the @Inject constructor/@Inject members. So those values can be easily provided in tests.

  2. This allows us to make all conditions lazily computable, as it should have been from the start. Now that is not true, some conditions are computed in the component's constructor (!) and the user has little control over it. But with the introduction of @ValueOf one can explicitly request conditions if one needs to "warm them up" or ensure they are requested at a specific moment of time, so I can make everything computed lazily.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.