Skip to content
This repository was archived by the owner on Aug 20, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions integration-tests/src/test/java/com/example/IntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,27 @@ public void multibindingSetElements() {
assertThat(component.values()).containsExactly("one", "two");
}

@Test
@IgnoreCodegen
public void multibindingSetConflictingExplicitAndImplicit() {
try {
backend.create(MultibindingSetConflict.class);
fail();
} catch (IllegalStateException e) {
// Note: using only one of each IntoSet and ElementsIntoSet to make sure the test is stable,
// because Class.getDeclaredMethods() doesn't guarantee ordering.
assertThat(e)
.hasMessageThat()
.isEqualTo(
"java.util.Set<java.lang.String> has incompatible bindings or declarations:\n"
+ " Set bindings and declarations:\n"
+ " [single ] @Provides[com.example.MultibindingSetConflict$Module1.one(…)]\n"
+ " [multiple] @Provides[com.example.MultibindingSetConflict$Module1.two(…)]\n"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about "instance" and "elements"? We can re-use terminology like "elements" which is in the API of Scope, I just don't want explicitly mechanisms of Dagger like @ElementsIntoSet (unless we sneak them into the Binding's toString(), then we can have as much Dagger-specific things as we like).

+ " Unique bindings and declarations:\n"
+ " @Provides[com.example.MultibindingSetConflict$Module1.explicit(…)]\n");
}
}

@Test
public void multibindingSetPrimitive() {
MultibindingSetPrimitive component = backend.create(MultibindingSetPrimitive.class);
Expand Down Expand Up @@ -895,6 +916,26 @@ public void multibindingMapNoUnwrap() {
Annotations.tableKey(2, 3), "two");
}

@Test
@IgnoreCodegen
public void multibindingMapConflictingExplicitAndImplicit() {
try {
backend.create(MultibindingMapConflict.class);
fail();
} catch (IllegalStateException e) {
// Note: using only one IntoMap to make sure the test is stable,
// because Class.getDeclaredMethods() doesn't guarantee ordering.
assertThat(e)
.hasMessageThat()
.isEqualTo(
"java.util.Map<java.lang.String, java.lang.Integer> has incompatible bindings or declarations:\n"
+ " Map bindings and declarations:\n"
+ " first = @Provides[com.example.MultibindingMapConflict$Module1.one(…)]\n"
+ " Unique bindings and declarations:\n"
+ " @Provides[com.example.MultibindingMapConflict$Module1.explicit(…)]\n");
}
}

@Test
public void multibindingProviderMap() {
MultibindingProviderMap component = backend.create(MultibindingProviderMap.class);
Expand Down Expand Up @@ -922,6 +963,26 @@ public void multibindingMapProvider() {
assertThat(values.get("1").get()).isEqualTo("one");
}

@Test
@IgnoreCodegen
public void multibindingMapProviderConflictingExplicitAndImplicit() {
try {
backend.create(MultibindingMapProviderConflict.class);
fail();
} catch (IllegalStateException e) {
// Note: using only one IntoMap to make sure the test is stable,
// because Class.getDeclaredMethods() doesn't guarantee ordering.
assertThat(e)
.hasMessageThat()
.isEqualTo(
"java.util.Map<java.lang.String, javax.inject.Provider<java.lang.Integer>> has incompatible bindings or declarations:\n"
+ " Map bindings and declarations:\n"
+ " first = @Provides[com.example.MultibindingMapProviderConflict$Module1.one(…)]\n"
+ " Unique bindings and declarations:\n"
+ " @Provides[com.example.MultibindingMapProviderConflict$Module1.explicit(…)]\n");
}
}

@Test
public void moduleClass() {
ModuleClass component = backend.create(ModuleClass.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.example;

import dagger.Component;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
import java.util.Collections;
import java.util.Map;

@Component(modules = MultibindingMapConflict.Module1.class)
interface MultibindingMapConflict {
Map<String, Integer> values();

@Module
abstract class Module1 {
@Provides
@IntoMap
@StringKey("first")
static Integer one() {
return 1;
}

@Provides
static Map<String, Integer> explicit() {
return Collections.emptyMap();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example;

import dagger.Component;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
import java.util.Collections;
import java.util.Map;
import javax.inject.Provider;

@Component(modules = MultibindingMapProviderConflict.Module1.class)
interface MultibindingMapProviderConflict {
Map<String, Provider<Integer>> values();

@Module
abstract class Module1 {
@Provides
@IntoMap
@StringKey("first")
static Integer one() {
return 1;
}

@Provides
static Map<String, Provider<Integer>> explicit() {
return Collections.emptyMap();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.example;

import dagger.Component;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
import dagger.multibindings.IntoSet;
import java.util.Collections;
import java.util.Set;

@Component(modules = MultibindingSetConflict.Module1.class)
interface MultibindingSetConflict {
Set<String> values();

@Module
abstract class Module1 {
@Provides
@IntoSet
static String one() {
return "one";
}

@Provides
@ElementsIntoSet
static Set<String> two() {
return Collections.singleton("two");
}

@Provides
static Set<String> explicit() {
return Collections.emptySet();
}
}
}
43 changes: 40 additions & 3 deletions reflect/src/main/java/dagger/reflect/Scope.java
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@ Scope build() {
Binding replaced =
allBindings.put(key, new UnlinkedSetBinding(elementBindings, elementsBindings));
if (replaced != null) {
throw new IllegalStateException(); // TODO implicit set binding duplicates explicit one.
throw new IllegalStateException(
buildDuplicateSetBindingMessage(key, setBindings, replaced));
}
}

Expand All @@ -361,19 +362,55 @@ Scope build() {
Binding replaced =
allBindings.put(mapOfValueKey, new UnlinkedMapOfValueBinding(mapOfProviderKey));
if (replaced != null) {
throw new IllegalStateException(); // TODO implicit map binding duplicates explicit one.
throw new IllegalStateException(
buildDuplicateMapBindingMessage(mapOfValueKey, entryBindings, replaced));
}

replaced =
allBindings.put(mapOfProviderKey, new UnlinkedMapOfProviderBinding(entryBindings));
if (replaced != null) {
throw new IllegalStateException(); // TODO implicit map binding duplicates explicit one.
throw new IllegalStateException(
buildDuplicateMapBindingMessage(mapOfProviderKey, entryBindings, replaced));
}
}

return new Scope(allBindings, jitLookupFactory, annotations, parent);
}

private static String buildDuplicateSetBindingMessage(
Key key, SetBindings setBindings, Binding replaced) {
StringBuilder builder = new StringBuilder();
builder.append(key).append(" has incompatible bindings or declarations:\n");
builder.append(" Set bindings and declarations:\n");
for (Binding elementBinding : setBindings.elementBindings) {
builder.append(" [single ] ").append(elementBinding).append("\n");
}
for (Binding elementsBinding : setBindings.elementsBindings) {
builder.append(" [multiple] ").append(elementsBinding).append("\n");
}
builder.append(" Unique bindings and declarations:\n");
builder.append(" ").append(replaced).append("\n");
return builder.toString();
}

private static String buildDuplicateMapBindingMessage(
Key key, Map<Object, Binding> entryBindings, Binding replaced) {
StringBuilder builder = new StringBuilder();
builder.append(key).append(" has incompatible bindings or declarations:\n");
builder.append(" Map bindings and declarations:\n");
for (Map.Entry<Object, Binding> entry : entryBindings.entrySet()) {
builder
.append(" ")
.append(entry.getKey())
.append(" = ")
.append(entry.getValue())
.append("\n");
}
builder.append(" Unique bindings and declarations:\n");
builder.append(" ").append(replaced).append("\n");
return builder.toString();
}

private static final class SetBindings {
/** Bindings which produce a single element for the target set. */
final List<Binding> elementBindings = new ArrayList<>();
Expand Down