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