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

(WIP) Support Dagger KSP #713

Closed
wants to merge 65 commits into from
Closed

Conversation

ZacSweers
Copy link
Collaborator

@ZacSweers ZacSweers commented May 29, 2023

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

  • No interception needed, we generate as an independent processor and don't need to trojan-horse a fake wrapping KSClassDeclaration during the processing call.
  • Better control over incrementalism. We can explicitly denote the intermediate component's originating elements.

Cons

  • Processing is always two rounds
  • The generated Dagger class is now actually slightly different than what people used before (DaggerMergedAppComponent vs DaggerAppComponent)

TODO

  • Consider incremental originating elements requirements of this. Do we need to intercept code generation and mark it as aggregating instead? Or depends on "all files"?
  • Wire controls into the Anvil gradle plugin
  • Enable in integration tests

@ZacSweers
Copy link
Collaborator Author

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
Copy link
Collaborator Author

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
Copy link
Collaborator Author

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

@ZacSweers
Copy link
Collaborator Author

Rehashing a fresh impl in #1001

@ZacSweers ZacSweers closed this May 15, 2024
@ZacSweers ZacSweers deleted the z/kspSupport branch May 15, 2024 01:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant