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

Generated parent component factory has an incorrect return type #84

Open
jonamireh opened this issue Dec 3, 2024 · 3 comments
Open

Comments

@jonamireh
Copy link

jonamireh commented Dec 3, 2024

Summary

When an @MergeSubcomponent is declared with an inner parent component interface, Anvil fails to generate a proper subclass of the parent component interface with the correct return type during component merging. What a mouthful, so let's look at an example 😅

Context

Consider the following, within the :sample:app module of this repository:

@SingleIn(LoggedInScope::class)
@MergeSubcomponent(LoggedInScope::class)
interface LoggedInComponent {
    @ContributesTo(AppScope::class)
    interface ParentBindings {
        fun createLoggedInComponent(): LoggedInComponent
    }
}

When Anvil generates the subcomponent, it will create the following subclass of ParentBindings where createLoggedInComponent is now overridded to the merged type MergedLoggedInComponent:

@Subcomponent(modules = [MergedLoggedInComponent.BindingModule::class])
public interface MergedLoggedInComponent : LoggedInComponent {
  @Module
  public interface BindingModule {
    @Binds
    public fun bindMergedLoggedInComponent(`impl`: MergedLoggedInComponent): LoggedInComponent
  }

  public interface ParentComponent : LoggedInComponent.ParentBindings {
    override fun createLoggedInComponent(): MergedLoggedInComponent
  }
}

When Anvil later performs component merging for AppScope, it will attempt to generate the following:

public interface MergedAppComponent : AppComponent, LoggedInComponent.ParentBindings,
    MergedLoggedInComponent.ParentComponent {
  override fun createLoggedInComponent(): LoggedInComponent = createLoggedInComponent()
}

MergedAppComponent#createLoggedInComponent cannot be overridden with a return type of LoggedInComponent because MergedLoggedInComponent.ParentComponent already overrides it to explicitly be MergedLoggedInComponent:

MergedAppComponent.kt:44:43 Return type of 'createLoggedInComponent' is not a subtype of the return type of the overridden member 'public abstract fun createLoggedInComponent(): MergedLoggedInComponent defined in com.squareup.anvil.sample.MergedLoggedInComponent.ParentComponent'

A patch is available to reproduce this example: issue.patch

Workaround

If you declare a @MergeComponent.Factory and return it from ParentBindings instead, the issue is no longer present:

@SingleIn(LoggedInScope::class)
@MergeSubcomponent(LoggedInScope::class)
interface LoggedInComponent {
    @MergeSubcomponent.Factory
    interface Factory {
        fun create(): LoggedInComponent
    }

    @ContributesTo(AppScope::class)
    interface ParentBindings {
        fun createLoggedInComponentFactory(): Factory
    }
}
@ZacSweers
Copy link
Owner

That's a tricky one, I'll try to look into it when I have time. In the meantime - PRs welcome and hopefully that workaround suits for now?

@jonamireh
Copy link
Author

What's the purpose of the ParentComponent interface, if there's already a generated @Binds for the concrete subcomponent? It seems like if that didn't exist, everything would compile?

@ZacSweers
Copy link
Owner

It's hard to say off-hand without digging into the code (this all gets really murky with all the various vague names floating around) but it might be that we already generate it and it's a bit of the left hand not knowing about the right hand in this area of code gen

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

No branches or pull requests

2 participants