Skip to content

Commit 7f981a4

Browse files
trexemDagger Team
authored andcommitted
Support Map<K, Lazy<V>> as a multibinding request type.
This change adds `dagger.Lazy` to the set of recognized framework types and allows `RequestKind.LAZY` for map multibinding values. A new test case is added to verify the generated code for injecting `Map<Long, Lazy<String>>`. Debug print statements were added to `MapType.unwrappedFrameworkValueType`. RELNOTES=Supported `Map<K, Lazy<V>>` as a multibinding request type. PiperOrigin-RevId: 843328737
1 parent 215efb9 commit 7f981a4

22 files changed

+1111
-49
lines changed

dagger-compiler/main/java/dagger/internal/codegen/base/FrameworkTypes.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public final class FrameworkTypes {
5252

5353
public static final ImmutableSet<XClassName> MAP_VALUE_FRAMEWORK_TYPES =
5454
ImmutableSet.<XClassName>builder()
55+
.add(XTypeNames.PROVIDER_OF_LAZY)
56+
.add(XTypeNames.LAZY)
5557
.addAll(providerTypeNames())
5658
.add(XTypeNames.PRODUCED)
5759
.add(XTypeNames.PRODUCER)

dagger-compiler/main/java/dagger/internal/codegen/base/MapType.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,16 @@ public static MapType from(XType type) {
5353
return new MapType(type);
5454
}
5555

56-
// TODO(b/28555349): support PROVIDER_OF_LAZY here too
5756
// TODO(b/376124787): We could consolidate this with a similar list in FrameworkTypes
5857
// if we had a better way to go from RequestKind to framework class name or vice versa
5958
/** The valid framework request kinds allowed on a multibinding map value. */
6059
private static final ImmutableSet<RequestKind> VALID_FRAMEWORK_REQUEST_KINDS =
61-
ImmutableSet.of(RequestKind.PROVIDER, RequestKind.PRODUCER, RequestKind.PRODUCED);
60+
ImmutableSet.of(
61+
RequestKind.PROVIDER,
62+
RequestKind.PRODUCER,
63+
RequestKind.PRODUCED,
64+
RequestKind.LAZY,
65+
RequestKind.PROVIDER_OF_LAZY);
6266

6367
private final XType type;
6468

@@ -124,6 +128,16 @@ public boolean valuesAreProvider() {
124128
return providerTypeNames().stream().anyMatch(this::valuesAreTypeOf);
125129
}
126130

131+
/** Returns {@code true} if the raw type of {@link #valueType()} is a lazy type. */
132+
public boolean valuesAreLazy() {
133+
return valueRequestKind().equals(RequestKind.LAZY);
134+
}
135+
136+
/** Returns {@code true} if the raw type of {@link #valueType()} is a provider of lazy type. */
137+
public boolean valuesAreProviderOfLazy() {
138+
return valueRequestKind().equals(RequestKind.PROVIDER_OF_LAZY);
139+
}
140+
127141
/**
128142
* Returns the map's {@link #valueType()} without any wrapping framework type, if one exists.
129143
*
@@ -145,6 +159,9 @@ public XType unwrappedFrameworkValueType() {
145159
}
146160

147161
public XTypeName unwrappedFrameworkValueTypeName() {
162+
if (valueRequestKind().equals(RequestKind.PROVIDER_OF_LAZY)) {
163+
return unwrap(unwrap(valueTypeName()));
164+
}
148165
return valuesAreFrameworkType() ? unwrap(valueTypeName()) : valueTypeName();
149166
}
150167

@@ -158,11 +175,6 @@ public RequestKind valueRequestKind() {
158175
RequestKind requestKind = RequestKinds.getRequestKind(valueType());
159176
if (VALID_FRAMEWORK_REQUEST_KINDS.contains(requestKind)) {
160177
return requestKind;
161-
} else if (requestKind == RequestKind.PROVIDER_OF_LAZY) {
162-
// This is kind of a weird case. We don't support Map<K, Lazy<V>>, so we also don't support
163-
// Map<K, Provider<Lazy<V>>> directly. However, if the user bound that themselves, we don't
164-
// want that to get confused as a normal instance request, so return PROVIDER here.
165-
return RequestKind.PROVIDER;
166178
} else {
167179
// Not all RequestKinds are supported, so if there's a map value that matches an unsupported
168180
// RequestKind, just treat it like it is a normal instance request.

dagger-compiler/main/java/dagger/internal/codegen/binding/ComponentDeclarations.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import static com.google.common.collect.Iterables.getOnlyElement;
2222
import static dagger.internal.codegen.binding.SourceFiles.generatedMonitoringModuleName;
2323
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
24+
import static dagger.internal.codegen.xprocessing.XTypeNames.LAZY;
25+
import static dagger.internal.codegen.xprocessing.XTypeNames.providerTypeNames;
2426

2527
import androidx.room3.compiler.codegen.XClassName;
2628
import androidx.room3.compiler.codegen.XTypeName;
@@ -353,15 +355,32 @@ private static boolean isValidSetMultibindingTypeName(TypeName typeName) {
353355
&& !(getOnlyElement(parameterizedTypeName.typeArguments) instanceof WildcardTypeName);
354356
}
355357

358+
/**
359+
* Returns the given {@code typeName} with any framework type wrappers removed.
360+
*
361+
* <p>For example, if {@code frameworkTypeNames} contains {@code Produced}, then {@code
362+
* Produced<Foo>} is unwrapped to {@code Foo}. And if {@code Provider<Lazy<Foo>>} is encountered,
363+
* we unwrap the {@code Provider} and {@code Lazy} layers and return {@code Foo}.
364+
*/
356365
private static TypeName unwrapFrameworkTypeName(
357366
TypeName typeName, ImmutableSet<XClassName> frameworkTypeNames) {
358-
if (typeName instanceof ParameterizedTypeName) {
359-
ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
360-
if (frameworkTypeNames.contains(toXPoet(parameterizedTypeName.rawType))) {
361-
typeName = getOnlyElement(parameterizedTypeName.typeArguments);
362-
}
367+
if (!(typeName instanceof ParameterizedTypeName)) {
368+
return typeName;
363369
}
364-
return typeName;
370+
ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
371+
XClassName rawType = toXPoet(parameterizedTypeName.rawType);
372+
if (!frameworkTypeNames.contains(rawType)) {
373+
return typeName;
374+
}
375+
// If we have a framework type, unwrap it.
376+
TypeName unwrappedTypeName = getOnlyElement(parameterizedTypeName.typeArguments);
377+
378+
// Account for Provider<Lazy<T>>:
379+
// If we just unwrapped Provider<T> and T is Lazy<U>, we need to unwrap Lazy<U> as well.
380+
if (providerTypeNames().contains(rawType)) {
381+
return unwrapFrameworkTypeName(unwrappedTypeName, ImmutableSet.of(LAZY));
382+
}
383+
return unwrappedTypeName;
365384
}
366385

367386
/**

dagger-compiler/main/java/dagger/internal/codegen/binding/SourceFiles.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,16 @@ public static XClassName mapFactoryClassName(MultiboundMapBinding binding) {
276276
MapType mapType = MapType.from(binding.key());
277277
switch (binding.bindingType()) {
278278
case PROVISION:
279-
return mapType.valuesAreProvider()
280-
? XTypeNames.MAP_PROVIDER_FACTORY : XTypeNames.MAP_FACTORY;
279+
if (mapType.valuesAreProviderOfLazy()) {
280+
return XTypeNames.MAP_PROVIDER_LAZY_FACTORY;
281+
}
282+
if (mapType.valuesAreLazy()) {
283+
return XTypeNames.MAP_LAZY_FACTORY;
284+
}
285+
if (mapType.valuesAreProvider()) {
286+
return XTypeNames.MAP_PROVIDER_FACTORY;
287+
}
288+
return XTypeNames.MAP_FACTORY;
281289
case PRODUCTION:
282290
return mapType.valuesAreFrameworkType()
283291
? mapType.valuesAreTypeOf(XTypeNames.PRODUCER)

dagger-compiler/main/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ final class MapFactoryCreationExpression extends MultibindingFactoryCreationExpr
7979

8080
@Override
8181
public XCodeBlock creationExpression() {
82+
// TODO(b/467104887): Revisit how DependencyRequests are generated for Map<K, Lazy<V>> and
83+
// Provider<Map<K, Lazy<V>>> to ensure consistent handling of Provider wrapping. See bug for
84+
// more details on the observed differences between direct and Provider-based map injections.
8285
return XCodeBlock.of(
8386
"%N(%L)", methodName(), parameterNames(shardImplementation.constructorParameters()));
8487
}

dagger-compiler/main/java/dagger/internal/codegen/writing/MapRequestRepresentation.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ final class MapRequestRepresentation extends RequestRepresentation {
9090

9191
@Override
9292
XExpression getDependencyExpression(XClassName requestingClass) {
93+
// TODO(b/467104887): Revisit how DependencyRequests are generated for Map<K, Lazy<V>> and
94+
// Provider<Map<K, Lazy<V>>> to ensure consistent handling of Provider wrapping. See bug for more
95+
// details on the observed differences between direct and Provider-based map injections.
9396
XExpression dependencyExpression = getUnderlyingMapExpression(requestingClass);
9497
// LazyClassKey is backed with a string map, therefore needs to be wrapped.
9598
if (useLazyClassKey) {

dagger-compiler/main/java/dagger/internal/codegen/xprocessing/XTypeNames.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ object XTypeNames {
101101
@JvmField val MAP_BUILDER = XClassName.get("dagger.internal", "MapBuilder")
102102
@JvmField val MAP_FACTORY = XClassName.get("dagger.internal", "MapFactory")
103103
@JvmField val MAP_PROVIDER_FACTORY = XClassName.get("dagger.internal", "MapProviderFactory")
104+
@JvmField val MAP_LAZY_FACTORY = XClassName.get("dagger.internal", "MapLazyFactory")
105+
@JvmField
106+
val MAP_PROVIDER_LAZY_FACTORY = XClassName.get("dagger.internal", "MapProviderLazyFactory")
104107
@JvmField val MEMBERS_INJECTOR = XClassName.get("dagger", "MembersInjector")
105108
@JvmField val MEMBERS_INJECTORS = XClassName.get("dagger.internal", "MembersInjectors")
106109
@JvmField val DAGGER_PROVIDER = XClassName.get("dagger.internal", "Provider")

javatests/dagger/functional/jakarta/JakartaProviderTest.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,6 @@ static Bar provideBar(
172172
@LazyClassKey(Bar.class)
173173
abstract Bar bindBarIntoClassMap(Bar bar);
174174

175-
// TODO(b/65118638): Use @Binds @IntoMap Lazy<T> once that works properly.
176-
@Provides
177-
@IntoMap
178-
@StringKey("bar")
179-
static Lazy<Bar> provideLazyIntoMap(Lazy<Bar> bar) {
180-
return bar;
181-
}
182-
183175
// Manually provide two Provider maps to make sure they don't conflict.
184176
@Provides
185177
static Map<Long, Provider<Long>> manuallyProvidedJakartaMap() {

javatests/dagger/functional/kotlinsrc/multibindings/LazyMaps.kt

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package dagger.functional.kotlinsrc.multibindings
1818

19+
import dagger.Binds
1920
import dagger.Component
2021
import dagger.Lazy
2122
import dagger.Module
@@ -34,31 +35,34 @@ import javax.inject.Singleton
3435
*/
3536
class LazyMaps {
3637
@Module
37-
internal object TestModule {
38-
@Provides @Singleton fun provideAtomicInteger(): AtomicInteger = AtomicInteger()
38+
abstract class TestModule {
3939

40-
@Provides
41-
fun provideString(atomicInteger: AtomicInteger): String =
42-
"value-${atomicInteger.incrementAndGet()}"
40+
@Module
41+
companion object {
42+
@Provides @Singleton fun provideAtomicInteger(): AtomicInteger = AtomicInteger()
43+
44+
@Provides
45+
fun provideString(atomicInteger: AtomicInteger): String =
46+
"value-${atomicInteger.incrementAndGet()}"
47+
48+
@Provides @IntoMap @StringKey("key") fun mapContribution(string: String): String = string
49+
}
4350

44-
/* TODO(b/65118638) Replace once @Binds @IntoMap Lazy<T> methods work properly.
4551
@Binds
4652
@IntoMap
4753
@StringKey("binds-key")
48-
abstract Lazy<String> mapContributionAsBinds(Lazy<String> lazy);
49-
*/
50-
@Provides
51-
@IntoMap
52-
@StringKey("key")
53-
fun mapContribution(lazy: Lazy<String>): Lazy<String> = lazy
54+
abstract fun mapContributionAsBinds(string: String): String
5455
}
5556

5657
@Singleton
5758
@Component(modules = [TestModule::class])
5859
interface TestComponent {
5960
fun mapOfLazy(): Map<String, Lazy<String>>
61+
6062
fun mapOfProviderOfLazy(): Map<String, Provider<Lazy<String>>>
63+
6164
fun providerForMapOfLazy(): Provider<Map<String, Lazy<String>>>
65+
6266
fun providerForMapOfProviderOfLazy(): Provider<Map<String, Provider<Lazy<String>>>>
6367
}
6468
}

javatests/dagger/functional/kotlinsrc/multibindings/LazyMapsTest.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
package dagger.functional.kotlinsrc.multibindings
1818

1919
import com.google.common.truth.Truth.assertThat
20-
import dagger.Lazy
21-
import dagger.functional.kotlinsrc.multibindings.LazyMaps.TestComponent
22-
import javax.inject.Provider
2320
import org.junit.Test
2421
import org.junit.runner.RunWith
2522
import org.junit.runners.JUnit4
@@ -35,6 +32,11 @@ class LazyMapsTest {
3532
assertThat(firstGet).isEqualTo("value-1")
3633
assertThat(firstGet).isSameInstanceAs(laziesMap["key"]!!.get())
3734
assertThat(component.mapOfLazy()["key"]!!.get()).isEqualTo("value-2")
35+
36+
val bindsGet = laziesMap["binds-key"]!!.get()
37+
assertThat(bindsGet).isEqualTo("value-3")
38+
assertThat(bindsGet).isSameInstanceAs(laziesMap["binds-key"]!!.get())
39+
assertThat(component.mapOfLazy()["binds-key"]!!.get()).isEqualTo("value-4")
3840
}
3941

4042
@Test

0 commit comments

Comments
 (0)