From 4fbc045d2ba8d65e28b23bef84a42068702a4a9e Mon Sep 17 00:00:00 2001 From: Brad Corso Date: Fri, 28 Aug 2026 10:52:50 -0700 Subject: [PATCH] Fix an issue where subcomponent factory method fails when defined with type parameters. This CL updates `BindingGraph.factoryMethodRequirements()` to explicitly resolve the factory method's parameters against the parent component's concrete type using `asMemberOf(parentType)` to properly handle cases where a component might inherit from a generic interface. For example, ``` @Component interface Parent extends SubcomponentProvider {} interface SubcomponentProvider { C createSubcomponent(M module); } ``` Previously, the compiler failed to resolve the parameter `M` to the concrete `ChildModule` type which leads to the following error: ``` java.lang.IllegalArgumentException at plugin.apt.turbine//com.google.common.base.Preconditions.checkArgument(Preconditions.java:136) at plugin.apt.turbine//dagger.internal.codegen.binding.ComponentRequirement.forModule(ComponentRequirement.java:190) at plugin.apt.turbine//dagger.internal.codegen.binding.BindingGraph.lambda$factoryMethodRequirements$0(BindingGraph.java:450) ``` RELNOTES=N/A PiperOrigin-RevId: 972676784 --- .../codegen/binding/BindingGraph.java | 8 +- .../GenericSubcomponentFactoryMethodTest.kt | 59 +++++++++++++++ .../GenericSubcomponentFactoryMethodTest.java | 73 +++++++++++++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 javatests/dagger/functional/kotlinsrc/subcomponent/GenericSubcomponentFactoryMethodTest.kt create mode 100644 javatests/dagger/functional/subcomponent/GenericSubcomponentFactoryMethodTest.java diff --git a/dagger-compiler/main/java/dagger/internal/codegen/binding/BindingGraph.java b/dagger-compiler/main/java/dagger/internal/codegen/binding/BindingGraph.java index 533b6559bf7..586d6f48581 100644 --- a/dagger-compiler/main/java/dagger/internal/codegen/binding/BindingGraph.java +++ b/dagger-compiler/main/java/dagger/internal/codegen/binding/BindingGraph.java @@ -27,7 +27,9 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import androidx.room3.compiler.processing.XExecutableElement; +import androidx.room3.compiler.processing.XExecutableType; import androidx.room3.compiler.processing.XMethodElement; +import androidx.room3.compiler.processing.XType; import androidx.room3.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; @@ -409,8 +411,10 @@ public final Optional factoryMethod() { */ // TODO(dpb): Consider disallowing modules if none of their bindings are used. public final ImmutableSet factoryMethodRequirements() { - return factoryMethod().get().getParameters().stream() - .map(parameter -> ComponentRequirement.forModule(parameter.getType())) + XType parentType = componentPath().parentComponent().xprocessing().getType(); + XExecutableType resolvedFactoryMethod = factoryMethod().get().asMemberOf(parentType); + return resolvedFactoryMethod.getParameterTypes().stream() + .map(ComponentRequirement::forModule) .collect(toImmutableSet()); } diff --git a/javatests/dagger/functional/kotlinsrc/subcomponent/GenericSubcomponentFactoryMethodTest.kt b/javatests/dagger/functional/kotlinsrc/subcomponent/GenericSubcomponentFactoryMethodTest.kt new file mode 100644 index 00000000000..30cd17de160 --- /dev/null +++ b/javatests/dagger/functional/kotlinsrc/subcomponent/GenericSubcomponentFactoryMethodTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2026 The Dagger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dagger.functional.kotlinsrc.subcomponent + +import com.google.common.truth.Truth.assertThat +import dagger.Component +import dagger.Module +import dagger.Provides +import dagger.Subcomponent +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for subcomponent factory methods defined on generic supertypes. */ +@RunWith(JUnit4::class) +class GenericSubcomponentFactoryMethodTest { + @Component(modules = [ParentModule::class]) + internal interface Parent : SubcomponentProvider {} + + @Module + internal class ParentModule { + @Provides fun provideInt(): Int = 42 + } + + @Subcomponent(modules = [ChildModule::class]) + internal interface Child { + fun string(): String + } + + @Module + internal class ChildModule(val s: String) { + @Provides fun provideString(i: Int): String = s + i + } + + interface SubcomponentProvider { + fun createSubcomponent(module: M): C + } + + @Test + fun factoryMethod_genericSupertype() { + val parent: Parent = DaggerGenericSubcomponentFactoryMethodTest_Parent.create() + val child: Child = parent.createSubcomponent(ChildModule("hello ")) + assertThat(child.string()).isEqualTo("hello 42") + } +} diff --git a/javatests/dagger/functional/subcomponent/GenericSubcomponentFactoryMethodTest.java b/javatests/dagger/functional/subcomponent/GenericSubcomponentFactoryMethodTest.java new file mode 100644 index 00000000000..cbbc7799726 --- /dev/null +++ b/javatests/dagger/functional/subcomponent/GenericSubcomponentFactoryMethodTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2026 The Dagger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dagger.functional.subcomponent; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for subcomponent factory methods defined on generic supertypes. */ +@RunWith(JUnit4.class) +public final class GenericSubcomponentFactoryMethodTest { + + @Component(modules = ParentModule.class) + interface Parent extends SubcomponentProvider {} + + @Module + static class ParentModule { + @Provides + int provideInt() { + return 42; + } + } + + @Subcomponent(modules = ChildModule.class) + interface Child { + String string(); + } + + @Module + static class ChildModule { + final String s; + + ChildModule(String s) { + this.s = s; + } + + @Provides + String provideString(int i) { + return s + i; + } + } + + public interface SubcomponentProvider { + C createSubcomponent(M module); + } + + @Test + public void factoryMethod_genericSupertype() { + Parent parent = DaggerGenericSubcomponentFactoryMethodTest_Parent.create(); + Child child = parent.createSubcomponent(new ChildModule("hello ")); + assertThat(child.string()).isEqualTo("hello 42"); + } +}