diff --git a/java/dagger/internal/codegen/binding/BindingGraphFactory.java b/java/dagger/internal/codegen/binding/BindingGraphFactory.java index b55ec0b8b0e..e74efa8a0a1 100644 --- a/java/dagger/internal/codegen/binding/BindingGraphFactory.java +++ b/java/dagger/internal/codegen/binding/BindingGraphFactory.java @@ -278,18 +278,19 @@ ResolvedBindings lookUpBindings(Key requestKey) { Set subcomponentDeclarations = new LinkedHashSet<>(); // Gather all bindings, multibindings, optional, and subcomponent declarations/contributions. - ImmutableSet keysMatchingRequest = keysMatchingRequest(requestKey); + ImmutableSet multibindingKeysMatchingRequest = + multibindingKeysMatchingRequest(requestKey); for (Resolver resolver : getResolverLineage()) { bindings.addAll(resolver.getLocalExplicitBindings(requestKey)); + subcomponentDeclarations.addAll(resolver.declarations.subcomponents(requestKey)); + // The optional binding declarations are keyed by the unwrapped type. + keyFactory.unwrapOptional(requestKey) + .map(resolver.declarations::optionalBindings) + .ifPresent(optionalBindingDeclarations::addAll); - for (Key key : keysMatchingRequest) { + for (Key key : multibindingKeysMatchingRequest) { multibindingContributions.addAll(resolver.getLocalMultibindingContributions(key)); multibindingDeclarations.addAll(resolver.declarations.multibindings(key)); - subcomponentDeclarations.addAll(resolver.declarations.subcomponents(key)); - // The optional binding declarations are keyed by the unwrapped type. - keyFactory.unwrapOptional(key) - .map(resolver.declarations::optionalBindings) - .ifPresent(optionalBindingDeclarations::addAll); } } @@ -425,12 +426,12 @@ private void addSubcomponentToOwningResolver(ContributionBinding subcomponentCre * javac users) * */ - private ImmutableSet keysMatchingRequest(Key requestKey) { + private ImmutableSet multibindingKeysMatchingRequest(Key requestKey) { return keysMatchingRequestCache.computeIfAbsent( - requestKey, this::keysMatchingRequestUncached); + requestKey, this::multibindingKeysMatchingRequestUncached); } - private ImmutableSet keysMatchingRequestUncached(Key requestKey) { + private ImmutableSet multibindingKeysMatchingRequestUncached(Key requestKey) { ImmutableSet.Builder keys = ImmutableSet.builder(); keys.add(requestKey); keyFactory.unwrapSetKey(requestKey, TypeNames.PRODUCED).ifPresent(keys::add); @@ -867,7 +868,7 @@ private boolean hasLocalBindings(ResolvedBindings resolvedBindings) { * this component's modules that matches the key. */ private boolean hasLocalMultibindingContributions(Key requestKey) { - return keysMatchingRequest(requestKey).stream() + return multibindingKeysMatchingRequest(requestKey).stream() .anyMatch( multibindingKey -> !declarations.multibindingContributions(multibindingKey).isEmpty() diff --git a/java/dagger/internal/codegen/xprocessing/XTypes.java b/java/dagger/internal/codegen/xprocessing/XTypes.java index 7f4b77aacc6..62d9dba65bd 100644 --- a/java/dagger/internal/codegen/xprocessing/XTypes.java +++ b/java/dagger/internal/codegen/xprocessing/XTypes.java @@ -250,7 +250,11 @@ public static boolean isRawParameterizedType(XType type) { case JAVAC: return isDeclared(type) && type.getTypeArguments().isEmpty() - && !type.getTypeElement().getType().getTypeArguments().isEmpty(); + // TODO(b/353979671): We previously called: + // type.getTypeElement().getType().getTypeArguments().isEmpty() + // which is a bit more symmetric to the call above, but that resulted in b/353979671, so + // we've switched to checking `XTypeElement#getTypeParameters()` until the bug is fixed. + && !type.getTypeElement().getTypeParameters().isEmpty(); case KSP: return isDeclared(type) // TODO(b/245619245): Due to the bug in XProcessing, the logic used for Javac won't work diff --git a/javatests/dagger/internal/codegen/MultibindingTest.java b/javatests/dagger/internal/codegen/MultibindingTest.java index 1fee6f2c68b..60cbfeff896 100644 --- a/javatests/dagger/internal/codegen/MultibindingTest.java +++ b/javatests/dagger/internal/codegen/MultibindingTest.java @@ -16,6 +16,7 @@ package dagger.internal.codegen; +import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.util.Source; import dagger.testing.compile.CompilerTests; import org.junit.Test; @@ -299,4 +300,230 @@ public void provideExplicitSetInParent_AndMultibindingContributionInChild() { .onLineContaining("interface Parent"); }); } + + // Regression test for b/352142595. + @Test + public void testMultibindingMapWithKotlinSource() { + Source parent = + CompilerTests.kotlinSource( + "test.Parent.kt", + "package test", + "", + "import dagger.Component", + "", + "@Component(modules = [ParentModule::class])", + "interface Parent {", + " fun usage(): Usage", + "}"); + Source usage = + CompilerTests.kotlinSource( + "test.Usage.kt", + "package test", + "", + "import javax.inject.Inject", + "", + "class Usage @Inject constructor(map: Map)"); + Source parentModule = + CompilerTests.kotlinSource( + "test.ParentModule.kt", + "@file:Suppress(\"INLINE_FROM_HIGHER_PLATFORM\")", // Required to use TODO() + "package test", + "", + "import dagger.Module", + "import dagger.Provides", + "import dagger.multibindings.IntoMap", + "import dagger.multibindings.StringKey", + "", + "@Module", + "class ParentModule {", + " @Provides", + " @IntoMap", + " @StringKey(\"key\")", + " fun provideMyInterface(): MyInterface = TODO()", + "}"); + Source myInterface = + CompilerTests.kotlinSource( + "test.MyInterface.kt", + "package test", + "", + "interface MyInterface"); + + CompilerTests.daggerCompiler(parent, parentModule, myInterface, usage) + .compile( + subject -> { + // TODO(b/284207175): Due to a bug in our KSP implementation, KSP and Javac behave + // differently. Rather than cause a breaking change by fixing this bug directly, we've + // decided to wait until b/284207175 is implemented. + if (CompilerTests.backend(subject) == XProcessingEnv.Backend.KSP) { + subject.hasErrorCount(0); + } else { + subject.hasErrorCount(1); + subject.hasErrorContaining("Map cannot be provided"); + } + }); + } + + // Regression test for b/352142595. + @Test + public void testMultibindingMapProviderWithKotlinSource() { + Source parent = + CompilerTests.kotlinSource( + "test.Parent.kt", + "package test", + "", + "import dagger.Component", + "", + "@Component(modules = [ParentModule::class])", + "interface Parent {", + " fun usage(): Usage", + "}"); + Source usage = + CompilerTests.kotlinSource( + "test.Usage.kt", + "package test", + "", + "import javax.inject.Inject", + "import javax.inject.Provider", + "", + "class Usage @Inject constructor(map: Map>)"); + Source parentModule = + CompilerTests.kotlinSource( + "test.ParentModule.kt", + "@file:Suppress(\"INLINE_FROM_HIGHER_PLATFORM\")", // Required to use TODO() + "package test", + "", + "import dagger.Module", + "import dagger.Provides", + "import dagger.multibindings.IntoMap", + "import dagger.multibindings.StringKey", + "", + "@Module", + "class ParentModule {", + " @Provides", + " @IntoMap", + " @StringKey(\"key\")", + " fun provideMyInterface(): MyInterface = TODO()", + "}"); + Source myInterface = + CompilerTests.kotlinSource( + "test.MyInterface.kt", + "package test", + "", + "interface MyInterface"); + + CompilerTests.daggerCompiler(parent, parentModule, myInterface, usage) + .compile( + subject -> { + subject.hasErrorCount(1); + subject.hasErrorContaining( + "Map> cannot be provided"); + }); + } + + // Regression test for b/352142595. + @Test + public void testMultibindingSetWithKotlinSource() { + Source parent = + CompilerTests.kotlinSource( + "test.Parent.kt", + "package test", + "", + "import dagger.Component", + "", + "@Component(modules = [ParentModule::class])", + "interface Parent {", + " fun usage(): Usage", + "}"); + Source usage = + CompilerTests.kotlinSource( + "test.Usage.kt", + "package test", + "", + "import javax.inject.Inject", + "", + "class Usage @Inject constructor(set: Set)"); + Source parentModule = + CompilerTests.kotlinSource( + "test.ParentModule.kt", + "@file:Suppress(\"INLINE_FROM_HIGHER_PLATFORM\")", // Required to use TODO() + "package test", + "", + "import dagger.Module", + "import dagger.Provides", + "import dagger.multibindings.IntoSet", + "", + "@Module", + "class ParentModule {", + " @Provides", + " @IntoSet", + " fun provideMyInterface(): MyInterface = TODO()", + "}"); + Source myInterface = + CompilerTests.kotlinSource( + "test.MyInterface.kt", + "package test", + "", + "interface MyInterface"); + + CompilerTests.daggerCompiler(parent, parentModule, myInterface, usage) + .compile( + subject -> { + subject.hasErrorCount(1); + subject.hasErrorContaining("Set cannot be provided"); + }); + } + + // Regression test for b/352142595. + @Test + public void testMultibindingSetProviderWithKotlinSource() { + Source parent = + CompilerTests.kotlinSource( + "test.Parent.kt", + "package test", + "", + "import dagger.Component", + "", + "@Component(modules = [ParentModule::class])", + "interface Parent {", + " fun usage(): Usage", + "}"); + Source usage = + CompilerTests.kotlinSource( + "test.Usage.kt", + "package test", + "", + "import javax.inject.Inject", + "import javax.inject.Provider", + "", + "class Usage @Inject constructor(set: Set>)"); + Source parentModule = + CompilerTests.kotlinSource( + "test.ParentModule.kt", + "@file:Suppress(\"INLINE_FROM_HIGHER_PLATFORM\")", // Required to use TODO() + "package test", + "", + "import dagger.Module", + "import dagger.Provides", + "import dagger.multibindings.IntoSet", + "", + "@Module", + "class ParentModule {", + " @Provides", + " @IntoSet", + " fun provideMyInterface(): MyInterface = TODO()", + "}"); + Source myInterface = + CompilerTests.kotlinSource( + "test.MyInterface.kt", + "package test", + "", + "interface MyInterface"); + + CompilerTests.daggerCompiler(parent, parentModule, myInterface, usage) + .compile( + subject -> { + subject.hasErrorCount(1); + subject.hasErrorContaining("Set> cannot be provided"); + }); + } }