/* * Copyright (C) 2018 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.internal.codegen; import static dagger.internal.codegen.TestUtils.message; import static org.junit.Assume.assumeFalse; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.util.Source; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import dagger.testing.compile.CompilerTests; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class DuplicateBindingsValidationTest { @Parameters(name = "fullBindingGraphValidation={0}") public static ImmutableList parameters() { return ImmutableList.copyOf(new Object[][] {{false}, {true}}); } private final boolean fullBindingGraphValidation; public DuplicateBindingsValidationTest(boolean fullBindingGraphValidation) { this.fullBindingGraphValidation = fullBindingGraphValidation; } @Test public void duplicateExplicitBindings_ProvidesAndComponentProvision() { assumeFalse(fullBindingGraphValidation); Source component = CompilerTests.javaSource( "test.Outer", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "", "final class Outer {", " interface A {}", "", " interface B {}", "", " @Module", " static class AModule {", " @Provides String provideString() { return \"\"; }", " @Provides A provideA(String s) { return new A() {}; }", " }", "", " @Component(modules = AModule.class)", " interface Parent {", " A getA();", " }", "", " @Module", " static class BModule {", " @Provides B provideB(A a) { return new B() {}; }", " }", "", " @Component(dependencies = Parent.class, modules = { BModule.class, AModule.class})", " interface Child {", " B getB();", " }", "}"); CompilerTests.daggerCompiler(component) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.AModule.provideA(String)", " Outer.A Outer.Parent.getA()")) .onSource(component) .onLineContaining("interface Child"); }); } @Test public void duplicateExplicitBindings_TwoProvidesMethods() { Source component = CompilerTests.javaSource( "test.Outer", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Inject;", "", "final class Outer {", " interface A {}", "", " static class B {", " @Inject B(A a) {}", " }", "", " @Module", " static class Module1 {", " @Provides A provideA1() { return new A() {}; }", " }", "", " @Module", " static class Module2 {", " @Provides String provideString() { return \"\"; }", " @Provides A provideA2(String s) { return new A() {}; }", " }", "", " @Module(includes = { Module1.class, Module2.class})", " abstract static class Module3 {}", "", " @Component(modules = { Module1.class, Module2.class})", " interface TestComponent {", " A getA();", " B getB();", " }", "}"); CompilerTests.daggerCompiler(component) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { // The duplicate bindngs are also requested from B, but we don't want to report them // again. subject.hasErrorCount(fullBindingGraphValidation ? 2 : 1); subject.hasErrorContaining( message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.Module1.provideA1()", " @Provides Outer.A Outer.Module2.provideA2(String)")) .onSource(component) .onLineContaining("interface TestComponent"); if (fullBindingGraphValidation) { subject.hasErrorContaining( message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.Module1.provideA1()", " @Provides Outer.A Outer.Module2.provideA2(String)")) .onSource(component) .onLineContaining("class Module3"); } }); } @Test public void duplicateExplicitBindings_ProvidesVsBinds() { Source component = CompilerTests.javaSource( "test.Outer", "package test;", "", "import dagger.Binds;", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Inject;", "", "final class Outer {", " interface A {}", "", " static final class B implements A {", " @Inject B() {}", " }", "", " @Module", " static class Module1 {", " @Provides A provideA1() { return new A() {}; }", " }", "", " @Module", " static abstract class Module2 {", " @Binds abstract A bindA2(B b);", " }", "", " @Module(includes = { Module1.class, Module2.class})", " abstract static class Module3 {}", "", " @Component(modules = { Module1.class, Module2.class})", " interface TestComponent {", " A getA();", " }", "}"); CompilerTests.daggerCompiler(component) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { String errorMessage = message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.Module1.provideA1()", " @Binds Outer.A Outer.Module2.bindA2(Outer.B)"); if (fullBindingGraphValidation) { subject.hasErrorCount(2); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("class Module3"); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); } else { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); } }); } @Test public void duplicateExplicitBindings_multibindingsAndExplicitSets() { Source component = CompilerTests.javaSource( "test.Outer", "package test;", "", "import dagger.Binds;", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.IntoSet;", "import java.util.HashSet;", "import java.util.Set;", "import javax.inject.Qualifier;", "", "final class Outer {", " @Qualifier @interface SomeQualifier {}", "", " @Module", " abstract static class TestModule1 {", " @Provides @IntoSet static String stringSetElement() { return \"\"; }", "", " @Binds", " @IntoSet abstract String bindStringSetElement(@SomeQualifier String value);", "", " @Provides @SomeQualifier", " static String provideSomeQualifiedString() { return \"\"; }", " }", "", " @Module", " static class TestModule2 {", " @Provides Set stringSet() { return new HashSet(); }", " }", "", " @Module(includes = { TestModule1.class, TestModule2.class})", " abstract static class TestModule3 {}", "", " @Component(modules = { TestModule1.class, TestModule2.class })", " interface TestComponent {", " Set getStringSet();", " }", "}"); CompilerTests.daggerCompiler(component) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { String errorMessage = message( "Set has incompatible bindings or declarations:", " Set bindings and declarations:", " @Binds @IntoSet String " + "Outer.TestModule1.bindStringSetElement(@Outer.SomeQualifier String)", " @Provides @IntoSet String " + "Outer.TestModule1.stringSetElement()", " Unique bindings and declarations:", " @Provides Set Outer.TestModule2.stringSet()"); if (fullBindingGraphValidation) { subject.hasErrorCount(2); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("class TestModule3"); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); } else { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); } }); } @Test public void duplicateExplicitBindings_multibindingsAndExplicitMaps() { Source component = CompilerTests.javaSource( "test.Outer", "package test;", "", "import dagger.Binds;", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.IntoMap;", "import dagger.multibindings.StringKey;", "import java.util.HashMap;", "import java.util.Map;", "import javax.inject.Qualifier;", "", "final class Outer {", " @Qualifier @interface SomeQualifier {}", "", " @Module", " abstract static class TestModule1 {", " @Provides @IntoMap", " @StringKey(\"foo\")", " static String stringMapEntry() { return \"\"; }", "", " @Binds @IntoMap @StringKey(\"bar\")", " abstract String bindStringMapEntry(@SomeQualifier String value);", "", " @Provides @SomeQualifier", " static String provideSomeQualifiedString() { return \"\"; }", " }", "", " @Module", " static class TestModule2 {", " @Provides Map stringMap() {", " return new HashMap();", " }", " }", "", " @Module(includes = { TestModule1.class, TestModule2.class})", " abstract static class TestModule3 {}", "", " @Component(modules = { TestModule1.class, TestModule2.class })", " interface TestComponent {", " Map getStringMap();", " }", "}"); CompilerTests.daggerCompiler(component) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { String errorMessage = message( "Map has incompatible bindings or declarations:", " Map bindings and declarations:", " @Binds @IntoMap @StringKey(\"bar\") String" + " Outer.TestModule1.bindStringMapEntry(@Outer.SomeQualifier String)", " @Provides @IntoMap @StringKey(\"foo\") String" + " Outer.TestModule1.stringMapEntry()", " Unique bindings and declarations:", " @Provides Map Outer.TestModule2.stringMap()"); if (fullBindingGraphValidation) { subject.hasErrorCount(2); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("class TestModule3"); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); } else { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); } }); } @Test public void duplicateExplicitBindings_UniqueBindingAndMultibindingDeclaration_Set() { Source component = CompilerTests.javaSource( "test.Outer", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.Multibinds;", "import java.util.HashSet;", "import java.util.Set;", "", "final class Outer {", " @Module", " abstract static class TestModule1 {", " @Multibinds abstract Set stringSet();", " }", "", " @Module", " static class TestModule2 {", " @Provides Set stringSet() { return new HashSet(); }", " }", "", " @Module(includes = { TestModule1.class, TestModule2.class})", " abstract static class TestModule3 {}", "", " @Component(modules = { TestModule1.class, TestModule2.class })", " interface TestComponent {", " Set getStringSet();", " }", "}"); CompilerTests.daggerCompiler(component) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { String errorMessage = message( "Set has incompatible bindings or declarations:", " Set bindings and declarations:", " @Multibinds Set Outer.TestModule1.stringSet()", " Unique bindings and declarations:", " @Provides Set Outer.TestModule2.stringSet()"); if (fullBindingGraphValidation) { subject.hasErrorCount(2); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("class TestModule3"); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); } else { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); } }); } @Test public void duplicateExplicitBindings_UniqueBindingAndMultibindingDeclaration_Map() { Source component = CompilerTests.javaSource( "test.Outer", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.Multibinds;", "import java.util.HashMap;", "import java.util.Map;", "", "final class Outer {", " @Module", " abstract static class TestModule1 {", " @Multibinds abstract Map stringMap();", " }", "", " @Module", " static class TestModule2 {", " @Provides Map stringMap() {", " return new HashMap();", " }", " }", "", " @Module(includes = { TestModule1.class, TestModule2.class})", " abstract static class TestModule3 {}", "", " @Component(modules = { TestModule1.class, TestModule2.class })", " interface TestComponent {", " Map getStringMap();", " }", "}"); CompilerTests.daggerCompiler(component) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { String errorMessage = message( "Map has incompatible bindings or declarations:", " Map bindings and declarations:", " @Multibinds Map Outer.TestModule1.stringMap()", " Unique bindings and declarations:", " @Provides Map Outer.TestModule2.stringMap()"); if (fullBindingGraphValidation) { subject.hasErrorCount(2); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("class TestModule3"); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); } else { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); } }); } @Test public void duplicateBindings_TruncateAfterLimit() { Source component = CompilerTests.javaSource( "test.Outer", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Inject;", "", "final class Outer {", " interface A {}", "", " @Module", " static class Module01 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module02 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module03 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module04 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module05 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module06 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module07 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module08 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module09 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module10 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module11 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module12 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module(includes = {", " Module01.class,", " Module02.class,", " Module03.class,", " Module04.class,", " Module05.class,", " Module06.class,", " Module07.class,", " Module08.class,", " Module09.class,", " Module10.class,", " Module11.class,", " Module12.class", " })", " abstract static class Modules {}", "", " @Component(modules = {", " Module01.class,", " Module02.class,", " Module03.class,", " Module04.class,", " Module05.class,", " Module06.class,", " Module07.class,", " Module08.class,", " Module09.class,", " Module10.class,", " Module11.class,", " Module12.class", " })", " interface TestComponent {", " A getA();", " }", "}"); CompilerTests.daggerCompiler(component) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { subject.hasErrorCount(fullBindingGraphValidation ? 2 : 1); String errorMessage = message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.Module01.provideA()", " @Provides Outer.A Outer.Module02.provideA()", " @Provides Outer.A Outer.Module03.provideA()", " @Provides Outer.A Outer.Module04.provideA()", " @Provides Outer.A Outer.Module05.provideA()", " @Provides Outer.A Outer.Module06.provideA()", " @Provides Outer.A Outer.Module07.provideA()", " @Provides Outer.A Outer.Module08.provideA()", " @Provides Outer.A Outer.Module09.provideA()", " @Provides Outer.A Outer.Module10.provideA()", " and 2 others"); subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("interface TestComponent"); if (fullBindingGraphValidation) { subject.hasErrorContaining(errorMessage) .onSource(component) .onLineContaining("class Modules"); } }); } @Test public void childBindingConflictsWithParent() { Source aComponent = CompilerTests.javaSource( "test.A", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "", "@Component(modules = A.AModule.class)", "interface A {", " Object conflict();", "", " B.Builder b();", "", " @Module(subcomponents = B.class)", " static class AModule {", " @Provides static Object abConflict() {", " return \"a\";", " }", " }", "}"); Source bComponent = CompilerTests.javaSource( "test.B", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = B.BModule.class)", "interface B {", " Object conflict();", "", " @Subcomponent.Builder", " interface Builder {", " B build();", " }", "", " @Module", " static class BModule {", " @Provides static Object abConflict() {", " return \"b\";", " }", " }", "}"); CompilerTests.daggerCompiler(aComponent, bComponent) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { String errorMessage = message( "Object is bound multiple times:", " @Provides Object test.A.AModule.abConflict()", " @Provides Object test.B.BModule.abConflict()"); if (fullBindingGraphValidation) { subject.hasErrorCount(2); subject.hasErrorContaining("test.A.AModule has errors") .onSource(aComponent) .onLineContaining("@Component("); subject.hasErrorContaining(errorMessage) .onSource(aComponent) .onLineContaining("class AModule"); } else { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage) .onSource(aComponent) .onLineContaining("interface A {"); } }); } @Test public void grandchildBindingConflictsWithGrandparent() { Source aComponent = CompilerTests.javaSource( "test.A", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "", "@Component(modules = A.AModule.class)", "interface A {", " Object conflict();", "", " B.Builder b();", "", " @Module(subcomponents = B.class)", " static class AModule {", " @Provides static Object acConflict() {", " return \"a\";", " }", " }", "}"); Source bComponent = CompilerTests.javaSource( "test.B", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent", "interface B {", " C.Builder c();", "", " @Subcomponent.Builder", " interface Builder {", " B build();", " }", "}"); Source cComponent = CompilerTests.javaSource( "test.C", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = C.CModule.class)", "interface C {", " Object conflict();", "", " @Subcomponent.Builder", " interface Builder {", " C build();", " }", "", " @Module", " static class CModule {", " @Provides static Object acConflict() {", " return \"c\";", " }", " }", "}"); CompilerTests.daggerCompiler(aComponent, bComponent, cComponent) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { String errorMessage = message( "Object is bound multiple times:", " @Provides Object test.A.AModule.acConflict()", " @Provides Object test.C.CModule.acConflict()"); if (fullBindingGraphValidation) { subject.hasErrorCount(2); subject.hasErrorContaining("test.A.AModule has errors") .onSource(aComponent) .onLineContaining("@Component("); subject.hasErrorContaining(errorMessage) .onSource(aComponent) .onLineContaining("class AModule"); } else { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage) .onSource(aComponent) .onLineContaining("interface A {"); } }); } @Test public void grandchildBindingConflictsWithChild() { Source aComponent = CompilerTests.javaSource( "test.A", "package test;", "", "import dagger.Component;", "", "@Component", "interface A {", " B b();", "}"); Source bComponent = CompilerTests.javaSource( "test.B", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = B.BModule.class)", "interface B {", " Object conflict();", "", " C.Builder c();", "", " @Module(subcomponents = C.class)", " static class BModule {", " @Provides static Object bcConflict() {", " return \"b\";", " }", " }", "}"); Source cComponent = CompilerTests.javaSource( "test.C", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = C.CModule.class)", "interface C {", " Object conflict();", "", " @Subcomponent.Builder", " interface Builder {", " C build();", " }", "", " @Module", " static class CModule {", " @Provides static Object bcConflict() {", " return \"c\";", " }", " }", "}"); CompilerTests.daggerCompiler(aComponent, bComponent, cComponent) .withProcessingOptions( ImmutableMap.builder() .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { String errorMessage = message( "Object is bound multiple times:", " @Provides Object test.B.BModule.bcConflict()", " @Provides Object test.C.CModule.bcConflict()"); if (fullBindingGraphValidation) { subject.hasErrorCount(2); subject.hasErrorContaining("test.B.BModule has errors") .onSource(bComponent) .onLineContaining("@Subcomponent(modules = B.BModule.class)"); subject.hasErrorContaining(errorMessage) .onSource(bComponent) .onLineContaining("class BModule"); } else { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage) .onSource(aComponent) .onLineContaining("interface A {"); } }); } @Test public void childProvidesConflictsWithParentInjects() { assumeFalse(fullBindingGraphValidation); Source foo = CompilerTests.javaSource( "test.Foo", "package test;", "", "import java.util.Set;", "import javax.inject.Inject;", "", "final class Foo {", " @Inject Foo(Set strings) {}", "}"); Source injected1 = CompilerTests.javaSource( "test.Injected1", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.IntoSet;", "import java.util.Set;", "", "@Component(modules = Injected1.Injected1Module.class)", "interface Injected1 {", " Foo foo();", " Injected2 injected2();", "", " @Module", " interface Injected1Module {", " @Provides @IntoSet static String string() {", " return \"injected1\";", " }", " }", "}"); Source injected2 = CompilerTests.javaSource( "test.Injected2", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "import dagger.multibindings.IntoSet;", "import java.util.Set;", "", "@Subcomponent(modules = Injected2.Injected2Module.class)", "interface Injected2 {", " Foo foo();", " Provided1 provided1();", "", " @Module", " interface Injected2Module {", " @Provides @IntoSet static String string() {", " return \"injected2\";", " }", " }", "}"); Source provided1 = CompilerTests.javaSource( "test.Provided1", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "import dagger.multibindings.IntoSet;", "import java.util.Set;", "", "@Subcomponent(modules = Provided1.Provided1Module.class)", "interface Provided1 {", " Foo foo();", " Provided2 provided2();", "", " @Module", " static class Provided1Module {", " @Provides static Foo provideFoo(Set strings) {", " return new Foo(strings);", " }", "", " @Provides @IntoSet static String string() {", " return \"provided1\";", " }", " }", "}"); Source provided2 = CompilerTests.javaSource( "test.Provided2", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "import dagger.multibindings.IntoSet;", "", "@Subcomponent(modules = Provided2.Provided2Module.class)", "interface Provided2 {", " Foo foo();", "", " @Module", " static class Provided2Module {", " @Provides @IntoSet static String string() {", " return \"provided2\";", " }", " }", "}"); CompilerTests.daggerCompiler(foo, injected1, injected2, provided1, provided2) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( message( "Foo is bound multiple times:", " @Inject Foo(Set) [Injected1]", " @Provides Foo Provided1.Provided1Module.provideFoo(Set) " + "[Injected1 → Injected2 → Provided1]")) .onSource(injected1) .onLineContaining("interface Injected1 {"); }); } @Test public void grandchildBindingConflictsWithParentWithNullableViolationAsWarning() { Source parentConflictsWithChild = CompilerTests.javaSource( "test.ParentConflictsWithChild", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import javax.annotation.Nullable;", "", "@Component(modules = ParentConflictsWithChild.ParentModule.class)", "interface ParentConflictsWithChild {", " Child.Builder child();", "", " @Module(subcomponents = Child.class)", " static class ParentModule {", " @Provides @Nullable static Object nullableParentChildConflict() {", " return \"parent\";", " }", " }", "}"); Source child = CompilerTests.javaSource( "test.Child", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = Child.ChildModule.class)", "interface Child {", " Object parentChildConflictThatViolatesNullability();", "", " @Subcomponent.Builder", " interface Builder {", " Child build();", " }", "", " @Module", " static class ChildModule {", " @Provides static Object nonNullableParentChildConflict() {", " return \"child\";", " }", " }", "}"); CompilerTests.daggerCompiler(parentConflictsWithChild, child) .withProcessingOptions( ImmutableMap.builder() .put("dagger.nullableValidation", "WARNING") .putAll(fullBindingGraphValidationOption()) .buildOrThrow()) .compile( subject -> { String errorMessage = message( "Object is bound multiple times:", " @Provides Object Child.ChildModule.nonNullableParentChildConflict()", " @Provides @Nullable Object" + " ParentConflictsWithChild.ParentModule.nullableParentChildConflict()"); if (fullBindingGraphValidation) { subject.hasErrorCount(2); subject.hasErrorContaining(errorMessage) .onSource(parentConflictsWithChild) .onLineContaining("class ParentModule"); subject.hasErrorContaining( "Object is not nullable, but is being provided by @Provides @Nullable " + "Object") .onSource(parentConflictsWithChild) .onLineContaining("class ParentModule"); } else { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage) .onSource(parentConflictsWithChild) .onLineContaining("interface ParentConflictsWithChild"); } }); } private ImmutableMap fullBindingGraphValidationOption() { return ImmutableMap.of( "dagger.fullBindingGraphValidation", fullBindingGraphValidation ? "ERROR" : "NONE"); } @Test public void reportedInParentAndChild() { Source parent = CompilerTests.javaSource( "test.Parent", "package test;", "", "import dagger.Component;", "", "@Component(modules = ParentModule.class)", "interface Parent {", " Child.Builder childBuilder();", " String duplicated();", "}"); Source parentModule = CompilerTests.javaSource( "test.ParentModule", "package test;", "", "import dagger.BindsOptionalOf;", "import dagger.Module;", "import dagger.Provides;", "import java.util.Optional;", "", "@Module", "interface ParentModule {", " @Provides static String one(Optional optional) { return \"one\"; }", " @Provides static String two() { return \"two\"; }", " @BindsOptionalOf Object optional();", "}"); Source child = CompilerTests.javaSource( "test.Child", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent(modules = ChildModule.class)", "interface Child {", " String duplicated();", "", " @Subcomponent.Builder", " interface Builder {", " Child build();", " }", "}"); Source childModule = CompilerTests.javaSource( "test.ChildModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import java.util.Optional;", "", "@Module", "interface ChildModule {", " @Provides static Object object() { return \"object\"; }", "}"); CompilerTests.daggerCompiler(parent, parentModule, child, childModule) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining("String is bound multiple times") .onSource(parent) .onLineContaining("interface Parent"); }); } // Tests the format of the error for a somewhat complex binding method. @Test public void formatTest() { Source modules = CompilerTests.javaSource( "test.Modules", "package test;", "", "import com.google.common.collect.ImmutableList;", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Singleton;", "", "interface Modules {", " @interface Foo {", " Class bar();", " }", "", " @Module", " interface Module1 {", " @Provides", " @Singleton", " @Foo(bar = String.class)", " static String foo(", " @SuppressWarnings(\"unused\") int a,", " @SuppressWarnings(\"unused\") ImmutableList blah) {", " return \"\";", " }", " }", "", " @Module", " interface Module2 {", " @Provides", " @Singleton", " @Foo(bar = String.class)", " static String foo(", " @SuppressWarnings(\"unused\") int a,", " @SuppressWarnings(\"unused\") ImmutableList blah) {", " return \"\";", " }", " }", "}"); Source component = CompilerTests.javaSource( "test.TestComponent", "package test;", "", "import dagger.BindsInstance;", "import dagger.Component;", "import javax.inject.Singleton;", "", "@Singleton", "@Component(modules = {Modules.Module1.class, Modules.Module2.class})", "interface TestComponent {", " @Modules.Foo(bar = String.class) String foo();", "}"); CompilerTests.daggerCompiler(modules, component) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( String.format( String.join( "\n", "String is bound multiple times:", " @Provides @Singleton @Modules.Foo(%1$s) String " + "Modules.Module1.foo(int, ImmutableList)", " @Provides @Singleton @Modules.Foo(%1$s) String " + "Modules.Module2.foo(int, ImmutableList)"), // TODO(b/241293838): KSP and java should match after this is fixed. CompilerTests.backend(subject) == XProcessingEnv.Backend.KSP ? "bar=String" : "bar = String.class")); }); } }