1*f585d8a3SJacky Wang /* 2*f585d8a3SJacky Wang * Copyright (C) 2016 The Dagger Authors. 3*f585d8a3SJacky Wang * 4*f585d8a3SJacky Wang * Licensed under the Apache License, Version 2.0 (the "License"); 5*f585d8a3SJacky Wang * you may not use this file except in compliance with the License. 6*f585d8a3SJacky Wang * You may obtain a copy of the License at 7*f585d8a3SJacky Wang * 8*f585d8a3SJacky Wang * http://www.apache.org/licenses/LICENSE-2.0 9*f585d8a3SJacky Wang * 10*f585d8a3SJacky Wang * Unless required by applicable law or agreed to in writing, software 11*f585d8a3SJacky Wang * distributed under the License is distributed on an "AS IS" BASIS, 12*f585d8a3SJacky Wang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*f585d8a3SJacky Wang * See the License for the specific language governing permissions and 14*f585d8a3SJacky Wang * limitations under the License. 15*f585d8a3SJacky Wang */ 16*f585d8a3SJacky Wang 17*f585d8a3SJacky Wang package dagger.internal.codegen; 18*f585d8a3SJacky Wang 19*f585d8a3SJacky Wang import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass; 20*f585d8a3SJacky Wang import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatModuleMethod; 21*f585d8a3SJacky Wang import static java.lang.annotation.RetentionPolicy.RUNTIME; 22*f585d8a3SJacky Wang 23*f585d8a3SJacky Wang import androidx.room.compiler.processing.XProcessingEnv; 24*f585d8a3SJacky Wang import androidx.room.compiler.processing.util.Source; 25*f585d8a3SJacky Wang import com.google.common.collect.ImmutableList; 26*f585d8a3SJacky Wang import dagger.Module; 27*f585d8a3SJacky Wang import dagger.multibindings.IntKey; 28*f585d8a3SJacky Wang import dagger.multibindings.LongKey; 29*f585d8a3SJacky Wang import dagger.producers.ProducerModule; 30*f585d8a3SJacky Wang import dagger.testing.compile.CompilerTests; 31*f585d8a3SJacky Wang import java.lang.annotation.Annotation; 32*f585d8a3SJacky Wang import java.lang.annotation.Retention; 33*f585d8a3SJacky Wang import java.util.Collection; 34*f585d8a3SJacky Wang import javax.inject.Qualifier; 35*f585d8a3SJacky Wang import org.junit.Test; 36*f585d8a3SJacky Wang import org.junit.runner.RunWith; 37*f585d8a3SJacky Wang import org.junit.runners.Parameterized; 38*f585d8a3SJacky Wang import org.junit.runners.Parameterized.Parameters; 39*f585d8a3SJacky Wang 40*f585d8a3SJacky Wang @RunWith(Parameterized.class) 41*f585d8a3SJacky Wang public class BindsMethodValidationTest { 42*f585d8a3SJacky Wang @Parameters data()43*f585d8a3SJacky Wang public static Collection<Object[]> data() { 44*f585d8a3SJacky Wang return ImmutableList.copyOf(new Object[][] {{Module.class}, {ProducerModule.class}}); 45*f585d8a3SJacky Wang } 46*f585d8a3SJacky Wang 47*f585d8a3SJacky Wang private final String moduleAnnotation; 48*f585d8a3SJacky Wang private final String moduleDeclaration; 49*f585d8a3SJacky Wang BindsMethodValidationTest(Class<? extends Annotation> moduleAnnotation)50*f585d8a3SJacky Wang public BindsMethodValidationTest(Class<? extends Annotation> moduleAnnotation) { 51*f585d8a3SJacky Wang this.moduleAnnotation = "@" + moduleAnnotation.getCanonicalName(); 52*f585d8a3SJacky Wang moduleDeclaration = this.moduleAnnotation + " abstract class %s { %s }"; 53*f585d8a3SJacky Wang } 54*f585d8a3SJacky Wang 55*f585d8a3SJacky Wang @Test noExtensionForBinds()56*f585d8a3SJacky Wang public void noExtensionForBinds() { 57*f585d8a3SJacky Wang Source module = 58*f585d8a3SJacky Wang CompilerTests.kotlinSource( 59*f585d8a3SJacky Wang "test.TestModule.kt", 60*f585d8a3SJacky Wang "package test", 61*f585d8a3SJacky Wang "", 62*f585d8a3SJacky Wang "import dagger.Binds", 63*f585d8a3SJacky Wang "", 64*f585d8a3SJacky Wang moduleAnnotation, 65*f585d8a3SJacky Wang "interface TestModule {", 66*f585d8a3SJacky Wang " @Binds fun FooImpl.bindObject(): Foo", 67*f585d8a3SJacky Wang "}"); 68*f585d8a3SJacky Wang Source foo = 69*f585d8a3SJacky Wang CompilerTests.javaSource( 70*f585d8a3SJacky Wang "test.Foo", // Prevents formatting onto a single line 71*f585d8a3SJacky Wang "package test;", 72*f585d8a3SJacky Wang "", 73*f585d8a3SJacky Wang "interface Foo {}"); 74*f585d8a3SJacky Wang Source fooImpl = 75*f585d8a3SJacky Wang CompilerTests.javaSource( 76*f585d8a3SJacky Wang "test.FooImpl", // Prevents formatting onto a single line 77*f585d8a3SJacky Wang "package test;", 78*f585d8a3SJacky Wang "", 79*f585d8a3SJacky Wang "class FooImpl implements Foo {", 80*f585d8a3SJacky Wang " @Inject FooImpl() {}", 81*f585d8a3SJacky Wang "}"); 82*f585d8a3SJacky Wang CompilerTests.daggerCompiler(module, foo, fooImpl) 83*f585d8a3SJacky Wang .compile( 84*f585d8a3SJacky Wang subject -> { 85*f585d8a3SJacky Wang subject.hasErrorCount(1); 86*f585d8a3SJacky Wang subject.hasErrorContaining("@Binds methods can not be an extension function"); 87*f585d8a3SJacky Wang }); 88*f585d8a3SJacky Wang } 89*f585d8a3SJacky Wang 90*f585d8a3SJacky Wang @Test noExtensionForProvides()91*f585d8a3SJacky Wang public void noExtensionForProvides() { 92*f585d8a3SJacky Wang Source module = 93*f585d8a3SJacky Wang CompilerTests.kotlinSource( 94*f585d8a3SJacky Wang "test.TestModule.kt", 95*f585d8a3SJacky Wang "package test", 96*f585d8a3SJacky Wang "", 97*f585d8a3SJacky Wang "import dagger.Provides", 98*f585d8a3SJacky Wang "", 99*f585d8a3SJacky Wang moduleAnnotation, 100*f585d8a3SJacky Wang "object TestModule {", 101*f585d8a3SJacky Wang " @Provides fun Foo.providesString(): String = \"hello\"", 102*f585d8a3SJacky Wang "}"); 103*f585d8a3SJacky Wang Source foo = 104*f585d8a3SJacky Wang CompilerTests.javaSource( 105*f585d8a3SJacky Wang "test.Foo", // Prevents formatting onto a single line 106*f585d8a3SJacky Wang "package test;", 107*f585d8a3SJacky Wang "", 108*f585d8a3SJacky Wang "class Foo {", 109*f585d8a3SJacky Wang " @Inject Foo() {}", 110*f585d8a3SJacky Wang "}"); 111*f585d8a3SJacky Wang CompilerTests.daggerCompiler(module, foo) 112*f585d8a3SJacky Wang .compile( 113*f585d8a3SJacky Wang subject -> { 114*f585d8a3SJacky Wang subject.hasErrorCount(1); 115*f585d8a3SJacky Wang subject.hasErrorContaining("@Provides methods can not be an extension function"); 116*f585d8a3SJacky Wang }); 117*f585d8a3SJacky Wang } 118*f585d8a3SJacky Wang 119*f585d8a3SJacky Wang @Test nonAbstract()120*f585d8a3SJacky Wang public void nonAbstract() { 121*f585d8a3SJacky Wang assertThatMethod("@Binds Object concrete(String impl) { return null; }") 122*f585d8a3SJacky Wang .hasError("must be abstract"); 123*f585d8a3SJacky Wang } 124*f585d8a3SJacky Wang 125*f585d8a3SJacky Wang @Test notAssignable()126*f585d8a3SJacky Wang public void notAssignable() { 127*f585d8a3SJacky Wang assertThatMethod("@Binds abstract String notAssignable(Object impl);").hasError("assignable"); 128*f585d8a3SJacky Wang } 129*f585d8a3SJacky Wang 130*f585d8a3SJacky Wang @Test moreThanOneParameter()131*f585d8a3SJacky Wang public void moreThanOneParameter() { 132*f585d8a3SJacky Wang assertThatMethod("@Binds abstract Object tooManyParameters(String s1, String s2);") 133*f585d8a3SJacky Wang .hasError("one parameter"); 134*f585d8a3SJacky Wang } 135*f585d8a3SJacky Wang 136*f585d8a3SJacky Wang @Test typeParameters()137*f585d8a3SJacky Wang public void typeParameters() { 138*f585d8a3SJacky Wang assertThatMethod("@Binds abstract <S, T extends S> S generic(T t);") 139*f585d8a3SJacky Wang .hasError("type parameters"); 140*f585d8a3SJacky Wang } 141*f585d8a3SJacky Wang 142*f585d8a3SJacky Wang @Test notInModule()143*f585d8a3SJacky Wang public void notInModule() { 144*f585d8a3SJacky Wang assertThatMethodInUnannotatedClass("@Binds abstract Object bindObject(String s);") 145*f585d8a3SJacky Wang .hasError("within a @Module or @ProducerModule"); 146*f585d8a3SJacky Wang } 147*f585d8a3SJacky Wang 148*f585d8a3SJacky Wang @Test throwsException()149*f585d8a3SJacky Wang public void throwsException() { 150*f585d8a3SJacky Wang assertThatMethod("@Binds abstract Object throwsException(String s1) throws RuntimeException;") 151*f585d8a3SJacky Wang .hasError("may not throw"); 152*f585d8a3SJacky Wang } 153*f585d8a3SJacky Wang 154*f585d8a3SJacky Wang @Test returnsVoid()155*f585d8a3SJacky Wang public void returnsVoid() { 156*f585d8a3SJacky Wang assertThatMethod("@Binds abstract void returnsVoid(Object impl);").hasError("void"); 157*f585d8a3SJacky Wang } 158*f585d8a3SJacky Wang 159*f585d8a3SJacky Wang @Test tooManyQualifiersOnMethod()160*f585d8a3SJacky Wang public void tooManyQualifiersOnMethod() { 161*f585d8a3SJacky Wang assertThatMethod( 162*f585d8a3SJacky Wang "@Binds @Qualifier1 @Qualifier2 abstract String tooManyQualifiers(String impl);") 163*f585d8a3SJacky Wang .importing(Qualifier1.class, Qualifier2.class) 164*f585d8a3SJacky Wang .hasError("more than one @Qualifier"); 165*f585d8a3SJacky Wang } 166*f585d8a3SJacky Wang 167*f585d8a3SJacky Wang @Test tooManyQualifiersOnParameter()168*f585d8a3SJacky Wang public void tooManyQualifiersOnParameter() { 169*f585d8a3SJacky Wang assertThatMethod( 170*f585d8a3SJacky Wang "@Binds abstract String tooManyQualifiers(@Qualifier1 @Qualifier2 String impl);") 171*f585d8a3SJacky Wang .importing(Qualifier1.class, Qualifier2.class) 172*f585d8a3SJacky Wang .hasError("more than one @Qualifier"); 173*f585d8a3SJacky Wang } 174*f585d8a3SJacky Wang 175*f585d8a3SJacky Wang @Test noParameters()176*f585d8a3SJacky Wang public void noParameters() { 177*f585d8a3SJacky Wang assertThatMethod("@Binds abstract Object noParameters();").hasError("one parameter"); 178*f585d8a3SJacky Wang } 179*f585d8a3SJacky Wang 180*f585d8a3SJacky Wang @Test setElementsNotAssignable()181*f585d8a3SJacky Wang public void setElementsNotAssignable() { 182*f585d8a3SJacky Wang assertThatMethod( 183*f585d8a3SJacky Wang "@Binds @ElementsIntoSet abstract Set<String> bindSetOfIntegers(Set<Integer> ints);") 184*f585d8a3SJacky Wang .hasError("assignable"); 185*f585d8a3SJacky Wang } 186*f585d8a3SJacky Wang 187*f585d8a3SJacky Wang @Test setElements_primitiveArgument()188*f585d8a3SJacky Wang public void setElements_primitiveArgument() { 189*f585d8a3SJacky Wang assertThatMethod("@Binds @ElementsIntoSet abstract Set<Number> bindInt(int integer);") 190*f585d8a3SJacky Wang .hasError("assignable"); 191*f585d8a3SJacky Wang } 192*f585d8a3SJacky Wang 193*f585d8a3SJacky Wang @Test elementsIntoSet_withRawSets()194*f585d8a3SJacky Wang public void elementsIntoSet_withRawSets() { 195*f585d8a3SJacky Wang assertThatMethod("@Binds @ElementsIntoSet abstract Set bindRawSet(HashSet hashSet);") 196*f585d8a3SJacky Wang .hasError("cannot return a raw Set"); 197*f585d8a3SJacky Wang } 198*f585d8a3SJacky Wang 199*f585d8a3SJacky Wang @Test intoMap_noMapKey()200*f585d8a3SJacky Wang public void intoMap_noMapKey() { 201*f585d8a3SJacky Wang assertThatMethod("@Binds @IntoMap abstract Object bindNoMapKey(String string);") 202*f585d8a3SJacky Wang .hasError("methods of type map must declare a map key"); 203*f585d8a3SJacky Wang } 204*f585d8a3SJacky Wang 205*f585d8a3SJacky Wang @Test intoMap_multipleMapKeys()206*f585d8a3SJacky Wang public void intoMap_multipleMapKeys() { 207*f585d8a3SJacky Wang assertThatMethod( 208*f585d8a3SJacky Wang "@Binds @IntoMap @IntKey(1) @LongKey(2L) abstract Object manyMapKeys(String string);") 209*f585d8a3SJacky Wang .importing(IntKey.class, LongKey.class) 210*f585d8a3SJacky Wang .hasError("may not have more than one map key"); 211*f585d8a3SJacky Wang } 212*f585d8a3SJacky Wang 213*f585d8a3SJacky Wang @Test bindsMissingTypeInParameterHierarchy()214*f585d8a3SJacky Wang public void bindsMissingTypeInParameterHierarchy() { 215*f585d8a3SJacky Wang Source module = 216*f585d8a3SJacky Wang CompilerTests.javaSource( 217*f585d8a3SJacky Wang "test.TestComponent", 218*f585d8a3SJacky Wang "package test;", 219*f585d8a3SJacky Wang "", 220*f585d8a3SJacky Wang "import dagger.Binds;", 221*f585d8a3SJacky Wang "", 222*f585d8a3SJacky Wang moduleAnnotation, 223*f585d8a3SJacky Wang "interface TestModule {", 224*f585d8a3SJacky Wang " @Binds String bindObject(Child<String> child);", 225*f585d8a3SJacky Wang "}"); 226*f585d8a3SJacky Wang 227*f585d8a3SJacky Wang Source child = 228*f585d8a3SJacky Wang CompilerTests.javaSource( 229*f585d8a3SJacky Wang "test.Child", 230*f585d8a3SJacky Wang "package test;", 231*f585d8a3SJacky Wang "", 232*f585d8a3SJacky Wang "class Child<T> extends Parent<T> {}"); 233*f585d8a3SJacky Wang 234*f585d8a3SJacky Wang Source parent = 235*f585d8a3SJacky Wang CompilerTests.javaSource( 236*f585d8a3SJacky Wang "test.Parent", 237*f585d8a3SJacky Wang "package test;", 238*f585d8a3SJacky Wang "", 239*f585d8a3SJacky Wang "class Parent<T> extends MissingType {}"); 240*f585d8a3SJacky Wang 241*f585d8a3SJacky Wang CompilerTests.daggerCompiler(module, child, parent) 242*f585d8a3SJacky Wang .compile( 243*f585d8a3SJacky Wang subject -> { 244*f585d8a3SJacky Wang switch (CompilerTests.backend(subject)) { 245*f585d8a3SJacky Wang case JAVAC: 246*f585d8a3SJacky Wang subject.hasErrorCount(3); 247*f585d8a3SJacky Wang subject.hasErrorContaining( 248*f585d8a3SJacky Wang "cannot find symbol" 249*f585d8a3SJacky Wang + "\n symbol: class MissingType"); 250*f585d8a3SJacky Wang break; 251*f585d8a3SJacky Wang case KSP: 252*f585d8a3SJacky Wang subject.hasErrorCount(2); 253*f585d8a3SJacky Wang break; 254*f585d8a3SJacky Wang } 255*f585d8a3SJacky Wang // TODO(b/248552462): Javac and KSP should match once this bug is fixed. 256*f585d8a3SJacky Wang boolean isJavac = CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC; 257*f585d8a3SJacky Wang subject.hasErrorContaining( 258*f585d8a3SJacky Wang String.format( 259*f585d8a3SJacky Wang "ModuleProcessingStep was unable to process 'test.TestModule' because '%s' " 260*f585d8a3SJacky Wang + "could not be resolved.", 261*f585d8a3SJacky Wang isJavac ? "MissingType" : "error.NonExistentClass")); 262*f585d8a3SJacky Wang subject.hasErrorContaining( 263*f585d8a3SJacky Wang String.format( 264*f585d8a3SJacky Wang "BindingMethodProcessingStep was unable to process" 265*f585d8a3SJacky Wang + " 'bindObject(test.Child<java.lang.String>)' because '%1$s' could not " 266*f585d8a3SJacky Wang + "be resolved." 267*f585d8a3SJacky Wang + "\n " 268*f585d8a3SJacky Wang + "\n Dependency trace:" 269*f585d8a3SJacky Wang + "\n => element (INTERFACE): test.TestModule" 270*f585d8a3SJacky Wang + "\n => element (METHOD): bindObject(test.Child<java.lang.String>)" 271*f585d8a3SJacky Wang + "\n => element (PARAMETER): child" 272*f585d8a3SJacky Wang + "\n => type (DECLARED parameter): test.Child<java.lang.String>" 273*f585d8a3SJacky Wang + "\n => type (DECLARED supertype): test.Parent<java.lang.String>" 274*f585d8a3SJacky Wang + "\n => type (ERROR supertype): %1$s", 275*f585d8a3SJacky Wang isJavac ? "MissingType" : "error.NonExistentClass")); 276*f585d8a3SJacky Wang }); 277*f585d8a3SJacky Wang } 278*f585d8a3SJacky Wang 279*f585d8a3SJacky Wang 280*f585d8a3SJacky Wang @Test bindsMissingTypeInReturnTypeHierarchy()281*f585d8a3SJacky Wang public void bindsMissingTypeInReturnTypeHierarchy() { 282*f585d8a3SJacky Wang Source module = 283*f585d8a3SJacky Wang CompilerTests.javaSource( 284*f585d8a3SJacky Wang "test.TestComponent", 285*f585d8a3SJacky Wang "package test;", 286*f585d8a3SJacky Wang "", 287*f585d8a3SJacky Wang "import dagger.Binds;", 288*f585d8a3SJacky Wang "", 289*f585d8a3SJacky Wang moduleAnnotation, 290*f585d8a3SJacky Wang "interface TestModule {", 291*f585d8a3SJacky Wang " @Binds Child<String> bindChild(String str);", 292*f585d8a3SJacky Wang "}"); 293*f585d8a3SJacky Wang 294*f585d8a3SJacky Wang Source child = 295*f585d8a3SJacky Wang CompilerTests.javaSource( 296*f585d8a3SJacky Wang "test.Child", 297*f585d8a3SJacky Wang "package test;", 298*f585d8a3SJacky Wang "", 299*f585d8a3SJacky Wang "class Child<T> extends Parent<T> {}"); 300*f585d8a3SJacky Wang 301*f585d8a3SJacky Wang Source parent = 302*f585d8a3SJacky Wang CompilerTests.javaSource( 303*f585d8a3SJacky Wang "test.Parent", 304*f585d8a3SJacky Wang "package test;", 305*f585d8a3SJacky Wang "", 306*f585d8a3SJacky Wang "class Parent<T> extends MissingType {}"); 307*f585d8a3SJacky Wang 308*f585d8a3SJacky Wang CompilerTests.daggerCompiler(module, child, parent) 309*f585d8a3SJacky Wang .compile( 310*f585d8a3SJacky Wang subject -> { 311*f585d8a3SJacky Wang switch (CompilerTests.backend(subject)) { 312*f585d8a3SJacky Wang case JAVAC: 313*f585d8a3SJacky Wang subject.hasErrorCount(3); 314*f585d8a3SJacky Wang subject.hasErrorContaining( 315*f585d8a3SJacky Wang "cannot find symbol" 316*f585d8a3SJacky Wang + "\n symbol: class MissingType"); 317*f585d8a3SJacky Wang break; 318*f585d8a3SJacky Wang case KSP: 319*f585d8a3SJacky Wang subject.hasErrorCount(2); 320*f585d8a3SJacky Wang break; 321*f585d8a3SJacky Wang } 322*f585d8a3SJacky Wang // TODO(b/248552462): Javac and KSP should match once this bug is fixed. 323*f585d8a3SJacky Wang boolean isJavac = CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC; 324*f585d8a3SJacky Wang subject.hasErrorContaining( 325*f585d8a3SJacky Wang String.format( 326*f585d8a3SJacky Wang "ModuleProcessingStep was unable to process 'test.TestModule' because '%s' " 327*f585d8a3SJacky Wang + "could not be resolved.", 328*f585d8a3SJacky Wang isJavac ? "MissingType" : "error.NonExistentClass")); 329*f585d8a3SJacky Wang subject.hasErrorContaining( 330*f585d8a3SJacky Wang String.format( 331*f585d8a3SJacky Wang "BindingMethodProcessingStep was unable to process " 332*f585d8a3SJacky Wang + "'bindChild(java.lang.String)' because '%1$s' could not be" 333*f585d8a3SJacky Wang + " resolved." 334*f585d8a3SJacky Wang + "\n " 335*f585d8a3SJacky Wang + "\n Dependency trace:" 336*f585d8a3SJacky Wang + "\n => element (INTERFACE): test.TestModule" 337*f585d8a3SJacky Wang + "\n => element (METHOD): bindChild(java.lang.String)" 338*f585d8a3SJacky Wang + "\n => type (DECLARED return type): test.Child<java.lang.String>" 339*f585d8a3SJacky Wang + "\n => type (DECLARED supertype): test.Parent<java.lang.String>" 340*f585d8a3SJacky Wang + "\n => type (ERROR supertype): %1$s", 341*f585d8a3SJacky Wang isJavac ? "MissingType" : "error.NonExistentClass")); 342*f585d8a3SJacky Wang }); 343*f585d8a3SJacky Wang } 344*f585d8a3SJacky Wang assertThatMethod(String method)345*f585d8a3SJacky Wang private DaggerModuleMethodSubject assertThatMethod(String method) { 346*f585d8a3SJacky Wang return assertThatModuleMethod(method).withDeclaration(moduleDeclaration); 347*f585d8a3SJacky Wang } 348*f585d8a3SJacky Wang 349*f585d8a3SJacky Wang @Qualifier 350*f585d8a3SJacky Wang @Retention(RUNTIME) 351*f585d8a3SJacky Wang public @interface Qualifier1 {} 352*f585d8a3SJacky Wang 353*f585d8a3SJacky Wang @Qualifier 354*f585d8a3SJacky Wang @Retention(RUNTIME) 355*f585d8a3SJacky Wang public @interface Qualifier2 {} 356*f585d8a3SJacky Wang } 357