square / anvil

A Kotlin compiler plugin to make dependency injection with Dagger 2 easier.
Apache License 2.0
1.31k stars 82 forks source link

(WIP) Support Dagger KSP #713

Closed ZacSweers closed 5 months ago

ZacSweers commented 1 year ago

Ref #704

This implements Anvil support for Dagger KSP. The implementation is a little roundabout, as it works by decorating Dagger's KspComponentProcessor and intercepting its process() calls to decorate resolved annotated elements.

The primary entry-point is InterceptingKspComponentProcessor, which wraps the real KspComponentProcessor. It forwards process() calls onto it, but decorates the KSP Resolver it receives in one that intercepts calls getSymbolsWithAnnotation() that look for Dagger's component annotations.

When it sees one, it reroutes to instead look for the corresponding @Merge* annotation instead. On the incoming KSAnnotated nodes, it inspects their scopes and performs module and interface merging. Once both are done (more below), a KSClassDeclaration instance is created that uses the new created annotation + merged interfaces, and that is what's returned to the real KspComponentProcessor.

Component annotation creation

Once module merging is done and all the types are assembled, a new KSAnnotation is created for the target Dagger annotation (@Component, etc). This annotation is then included in the returned created KSClassDeclaration.

Component interface merging

Once component interface merging is done and all the types are assembled, these types are added to the return created KSClassDeclaration's supertypes property.

Diagram

Attempted a diagram of the flow here with an example AppComponent, hopefully this makes sense.

image

Testing

This PR adds support in Anvil's native testing infrastructure for controlling the dagger processing mode via new DaggerAnnotationProcessingMode enum, which can be KSP or Kapt. This replaces the "useDagger" boolean in most places.

Alternative Design

Another design option would be to generate an intermediate component interface from the original @MergeComponent-annotated component.

// Source
@MergeComponent
interface AppComponent

// Generated by Anvil
@Component(<merged modules>)
interface MergedAppComponent : <merged interfaces>

// Generated by Dagger
class DaggerMergedAppComponent

This approach has some pros and cons

Pros

Cons

TODO

ZacSweers commented 10 months ago

notbad.gif. 72 failing tests out of 1525

[root]
AssistedFactoryGeneratorTest
the implementation function name matches the factory name[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
two factory functions aren't supported - extended class[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
equal types must use an identifier[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
the factory function may require a lambda type[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
the factory function may require a suspend lambda type[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an implementation for an inner class is generated[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
two factory functions aren't supported - interface from different module[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
assisted covariant parameters are supported[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
the factory function may be provided by a generic super type from another module[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
a protected factory function in an abstract class is supported[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
assisted provider parameters are supported[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
a different order for the parameters of the factory function is allowed for type parameters[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
assisted lazy parameters are supported[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
the factory function may be provided by a generic super type[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
nullability is preserved for lambda types[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an implementation for a factory class with a type parameter bound by a generic[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
two factory functions aren't supported[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an implementation for a factory class is generated with intermixed assisted parameters[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
a different order for the parameters of the factory function is allowed[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an implementation for a factory class with type parameters is generated[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an implementation for a factory class with a type parameter bound by a where clause[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
the factory function may substitute a lambda type with a Function type[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an implementation for a factory class with nullable parameters is generated[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
the factory function may be provided by a generic super type with generic parameter[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
the factory function is allowed to be provided by a super type[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
a different order for the parameters of the factory function is allowed for parameters[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an implementation for a factory class is generated[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an abstract class is supported[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an implementation for a factory class with generic parameters is generated[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
the factory function may require a Function type[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
a different order for the parameters of the factory function is allowed for generic types[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
the factory function may substitute a Function type with a lambda type[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an implementation for an inner factory class is generated[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
an implementation for a factory class is generated without a package[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
the implementation function name matches the factory name[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
two factory functions aren't supported - extended class[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
equal types must use an identifier[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
the factory function may require a lambda type[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
the factory function may require a suspend lambda type[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an implementation for an inner class is generated[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
two factory functions aren't supported - interface from different module[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
assisted covariant parameters are supported[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
the factory function may be provided by a generic super type from another module[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
a protected factory function in an abstract class is supported[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
assisted provider parameters are supported[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
a different order for the parameters of the factory function is allowed for type parameters[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
assisted lazy parameters are supported[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
the factory function may be provided by a generic super type[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
nullability is preserved for lambda types[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an implementation for a factory class with a type parameter bound by a generic[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
two factory functions aren't supported[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an implementation for a factory class is generated with intermixed assisted parameters[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
a different order for the parameters of the factory function is allowed[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an implementation for a factory class with type parameters is generated[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an implementation for a factory class with a type parameter bound by a where clause[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
the factory function may substitute a lambda type with a Function type[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an implementation for a factory class with nullable parameters is generated[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
the factory function may be provided by a generic super type with generic parameter[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
the factory function is allowed to be provided by a super type[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
a different order for the parameters of the factory function is allowed for parameters[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an implementation for a factory class is generated[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an abstract class is supported[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an implementation for a factory class with generic parameters is generated[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
the factory function may require a Function type[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
a different order for the parameters of the factory function is allowed for generic types[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
the factory function may substitute a Function type with a lambda type[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an implementation for an inner factory class is generated[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
an implementation for a factory class is generated without a package[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
AssistedInjectGeneratorTest
one inject and one assisted inject constructor aren't supported[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
two assisted inject constructors aren't supported[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
InjectConstructorFactoryGeneratorTest
two inject constructors aren't supported[Dagger Processing Mode: KSP, mode: Ksp(symbolProcessorProviders=[])]
two inject constructors aren't supported[Dagger Processing Mode: KAPT, mode: Embedded(codeGenerators=[])]
ZacSweers commented 10 months ago

Actually looks like most of those failures are due to a subtle change in what dagger generates in 2.50. I'll PR those separately first

iterable was: [public static javax.inject.Provider AssistedServiceFactory_Impl.create(AssistedService_Factory), public static dagger.internal.Provider AssistedServiceFactory_Impl.createFactoryProvider(AssistedService_Factory)]

ZacSweers commented 10 months ago

Will pull the Dagger 2.50 update back after #830 is merged

ZacSweers commented 5 months ago

Rehashing a fresh impl in #1001