google/dagger

Stackoverflow when exposing parents of Subcomponents

Opened this issue · 7 comments

I want to expose parent reference of a subcomponent, since the generated implementation has the reference. (Trying to build a component cache that is a tree)

Simply adding a val parent: AppComponent on UserComponent worked. But unfortunately it doesn't work for subcomponent trees deeper than 2, then compilation stackoverflows.

@AppScope
@Component
interface AppComponent {
    val userComponentFactory: UserComponent.Factory

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance context: Context): AppComponent
    }
}

@UserScope
@Subcomponent
interface UserComponent {
    val parent: AppComponent
    val subscriberComponentFactory: SubscriberComponent.Factory

    @Subcomponent.Factory
    interface Factory {
        fun create(@BindsInstance userId: UserId): UserComponent
    }
}

@SubscriberScope
@Subcomponent
interface SubscriberComponent {
    val what: UserComponent .... makes compile Stackoverflow - removing this line makes it compile

    @Subcomponent.Factory
    interface Factory {
        fun create(@BindsInstance subscriberId: SubscriberId): SubscriberComponent
    }
}
Caused by: java.lang.reflect.InvocationTargetException
	at jdk.internal.reflect.GeneratedMethodAccessor1131.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing(annotationProcessing.kt:92)
	at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing$default(annotationProcessing.kt:33)
	at org.jetbrains.kotlin.kapt3.base.Kapt.kapt(Kapt.kt:47)
	... 33 more
Caused by: com.sun.tools.javac.processing.AnnotationProcessingError: java.lang.StackOverflowError
	at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(Unknown Source)
	at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(Unknown Source)
	at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(Unknown Source)
	at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(Unknown Source)
	at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.processAnnotations(Unknown Source)
	... 38 more
Caused by: java.lang.StackOverflowError
	at com.squareup.javapoet.ClassName.get(ClassName.java:176)
	at com.squareup.javapoet.TypeName.get(TypeName.java:339)
	at com.squareup.javapoet.TypeName.get(TypeName.java:323)
	at com.squareup.javapoet.WildcardTypeName.subtypeOf(WildcardTypeName.java:84)
	at com.squareup.javapoet.WildcardTypeName.get(WildcardTypeName.java:111)
	at com.squareup.javapoet.TypeName$1.visitWildcard(TypeName.java:307)
	at com.squareup.javapoet.TypeName$1.visitWildcard(TypeName.java:248)
	at jdk.compiler/com.sun.tools.javac.code.Type$WildcardType.accept(Unknown Source)
	at com.squareup.javapoet.TypeName.get(TypeName.java:248)
	at com.squareup.javapoet.TypeName$1.visitDeclared(TypeName.java:286)
	at com.squareup.javapoet.TypeName$1.visitDeclared(TypeName.java:248)
	at jdk.compiler/com.sun.tools.javac.code.Type$ClassType.accept(Unknown Source)
	at com.squareup.javapoet.TypeName.get(TypeName.java:248)
	at com.squareup.javapoet.TypeName.get(TypeName.java:243)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.JavaPoetExtKt.safeTypeName(JavaPoetExt.kt:94)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.javac.JavacType$xTypeName$2.invoke(JavacType.kt:86)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.javac.JavacType$xTypeName$2.invoke(JavacType.kt:84)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.javac.JavacType.getXTypeName(JavacType.kt:84)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.javac.JavacType.access$getXTypeName(JavacType.kt:39)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.javac.JavacType$typeName$2.invoke(JavacType.kt:81)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.javac.JavacType$typeName$2.invoke(JavacType.kt:80)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.javac.JavacType.getTypeName(JavacType.kt:80)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.InternalXAnnotationValue$Kind$Companion.of(InternalXAnnotationValue.kt:36)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.InternalXAnnotationValue$kind$2.invoke(InternalXAnnotationValue.kt:57)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.InternalXAnnotationValue$kind$2.invoke(InternalXAnnotationValue.kt:56)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.InternalXAnnotationValue.getKind(InternalXAnnotationValue.kt:56)
	at dagger.spi.internal.shaded.androidx.room.compiler.processing.InternalXAnnotationValue.hasAnnotationValue(InternalXAnnotationValue.kt:69)
	at dagger.internal.codegen.base.DaggerSuperficialValidation.validateAnnotationValue(DaggerSuperficialValidation.java:458)
	at dagger.internal.codegen.base.DaggerSuperficialValidation.validateAnnotationValues(DaggerSuperficialValidation.java:443)
	at dagger.internal.codegen.base.DaggerSuperficialValidation.validateAnnotationValue(DaggerSuperficialValidation.java:457)
	at dagger.internal.codegen.base.DaggerSuperficialValidation.validateAnnotationValues(DaggerSuperficialValidation.java:443)
	at dagger.internal.codegen.base.DaggerSuperficialValidation.validateAnnotation(DaggerSuperficialValidation.java:412)
	at dagger.internal.codegen.base.DaggerSuperficialValidation.validateAnnotationOf(DaggerSuperficialValidation.java:217)
	at dagger.internal.codegen.base.ComponentAnnotation.lambda$anyComponentAnnotation$0(ComponentAnnotation.java:180)
	at dagger.internal.codegen.base.ComponentAnnotation.anyComponentAnnotation(ComponentAnnotation.java:178)
	at dagger.internal.codegen.base.ComponentAnnotation.anyComponentAnnotation(ComponentAnnotation.java:170)
	at dagger.internal.codegen.validation.ComponentValidator$ElementValidator.componentAnnotation(ComponentValidator.java:159)
	at dagger.internal.codegen.validation.ComponentValidator$ElementValidator.validateCreators(ComponentValidator.java:226)
	at dagger.internal.codegen.validation.ComponentValidator$ElementValidator.validateElement(ComponentValidator.java:174)
	at dagger.internal.codegen.validation.ComponentValidator.validateUncached(ComponentValidator.java:136)
	at dagger.internal.codegen.base.Util.reentrantComputeIfAbsent(Util.java:33)
	at dagger.internal.codegen.validation.ComponentValidator.validate(ComponentValidator.java:132)
	at dagger.internal.codegen.validation.ComponentValidator$ElementValidator.lambda$validateSubcomponents$11(ComponentValidator.java:533)
	at com.google.common.collect.Maps$KeySet.lambda$forEach$0(Maps.java:4043)
	at com.google.common.collect.Maps$KeySet.forEach(Maps.java:4043)
	at dagger.internal.codegen.validation.ComponentValidator$ElementValidator.validateSubcomponents(ComponentValidator.java:533)
	at dagger.internal.codegen.validation.ComponentValidator$ElementValidator.validateElement(ComponentValidator.java:181)
	at dagger.internal.codegen.validation.ComponentValidator.validateUncached(ComponentValidator.java:136)
	at dagger.internal.codegen.base.Util.reentrantComputeIfAbsent(Util.java:33)
	at dagger.internal.codegen.validation.ComponentValidator.validate(ComponentValidator.java:132)
	at dagger.internal.codegen.validation.ComponentValidator$ElementValidator.lambda$validateSubcomponents$11(ComponentValidator.java:533)
	at com.google.common.collect.Maps$KeySet.lambda$forEach$0(Maps.java:4043)
	at com.google.common.collect.Maps$KeySet.forEach(Maps.java:4043)
	.... repeats

dagger 2.51.1

We should probably do better than stackoverflow, however, what's happening here is that there are (unfortunately) multiple ways to declare a subcomponent as a child of another component/subcomponent, and one of those ways is to add a entry point method on the parent component/subcomponent interface that returns the child subcomponent.

So when you add the val what: UserComponent, you're actually making the UserComponent a child of SubscriberComponent, which is what causes the infinite loop.

You can probably work around this for now by using an @Binds to bind the UserComponent binding to another interface that doesn't have the @Subcomponent on it (e.g. @Subcomponent interface UserComponent : RealUserComponent), and then make your val use the interface without the @Subcomponent.

It looks like there's a bug in our validation logic that causes this loop.

I think I follow, but if you delete SubscriberComponent altogether, then it compiles & looking at generated sources of UserComponent, the val parent: AppComponent does just expose the appComponentImpl referencd which is passed in normally via constructor..so everything as expected.

I checked in the debugger, the returned instance is the same as the one used to create UserComponent instance from..so no surprises.

Is it because AppComponent is a @Component (and not a @Subcomponent?)

Is it because AppComponent is a @component (and not a @subcomponent?)

Yea, you can't make a @Component a child of another component, so we interpret this just as any other getter.

I see. So other than fixing the overflow to get a better error message, and hacking around with @BINDS, a proper way doesnt seem feasible, since all viable syntax is already taken?

Yea, you won't be able to do exactly what you wrote in the original comment, however, we should fix the overflow. Also, I think if you did @Binds to a qualifier like @Parent UserComponent you'd still hit the overflow. I need to look into and discuss with team members on whether we want to allow a qualifier to be used here to make the @Binds case easier.

by @Binds you mean @Binds abstract fun bind(userComponent: UserComponent): RealUserComponent?

Yup, that's right.