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 @Test bindsMissingTypeInReturnTypeHierarchy()280 public void bindsMissingTypeInReturnTypeHierarchy() { 281 Source module = 282 CompilerTests.javaSource( 283 "test.TestComponent", 284 "package test;", 285 "", 286 "import dagger.Binds;", 287 "", 288 moduleAnnotation, 289 "interface TestModule {", 290 " @Binds Child<String> bindChild(String str);", 291 "}"); 292 293 Source child = 294 CompilerTests.javaSource( 295 "test.Child", 296 "package test;", 297 "", 298 "class Child<T> extends Parent<T> {}"); 299 300 Source parent = 301 CompilerTests.javaSource( 302 "test.Parent", 303 "package test;", 304 "", 305 "class Parent<T> extends MissingType {}"); 306 307 CompilerTests.daggerCompiler(module, child, parent) 308 .compile( 309 subject -> { 310 switch (CompilerTests.backend(subject)) { 311 case JAVAC: 312 subject.hasErrorCount(3); 313 subject.hasErrorContaining( 314 "cannot find symbol" 315 + "\n symbol: class MissingType"); 316 break; 317 case KSP: 318 subject.hasErrorCount(2); 319 break; 320 } 321 // TODO(b/248552462): Javac and KSP should match once this bug is fixed. 322 boolean isJavac = CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC; 323 subject.hasErrorContaining( 324 String.format( 325 "ModuleProcessingStep was unable to process 'test.TestModule' because '%s' " 326 + "could not be resolved.", 327 isJavac ? "MissingType" : "error.NonExistentClass")); 328 subject.hasErrorContaining( 329 String.format( 330 "BindingMethodProcessingStep was unable to process " 331 + "'bindChild(java.lang.String)' because '%1$s' could not be" 332 + " resolved." 333 + "\n " 334 + "\n Dependency trace:" 335 + "\n => element (INTERFACE): test.TestModule" 336 + "\n => element (METHOD): bindChild(java.lang.String)" 337 + "\n => type (DECLARED return type): test.Child<java.lang.String>" 338 + "\n => type (DECLARED supertype): test.Parent<java.lang.String>" 339 + "\n => type (ERROR supertype): %1$s", 340 isJavac ? "MissingType" : "error.NonExistentClass")); 341 }); 342 } 343 344 @Test bindsNullableToNonNullable_fails()345 public void bindsNullableToNonNullable_fails() { 346 Source module = 347 CompilerTests.javaSource( 348 "test.TestComponent", 349 "package test;", 350 "", 351 "import dagger.Binds;", 352 "import dagger.Module;", 353 "import javax.annotation.Nullable;", 354 "", 355 "@Module", 356 "interface TestModule {", 357 " @Binds Object bind(@Nullable String str);", 358 "}"); 359 360 CompilerTests.daggerCompiler(module) 361 .compile( 362 subject -> { 363 subject.hasErrorCount(1); 364 subject.hasErrorContaining( 365 "@Binds methods' nullability must match the nullability of its parameter"); 366 }); 367 } 368 369 @Test bindsNonNullableToNullable_fails()370 public void bindsNonNullableToNullable_fails() { 371 Source module = 372 CompilerTests.javaSource( 373 "test.TestComponent", 374 "package test;", 375 "", 376 "import dagger.Binds;", 377 "import dagger.Module;", 378 "import javax.annotation.Nullable;", 379 "", 380 "@Module", 381 "interface TestModule {", 382 " @Binds @Nullable Object bind(String str);", 383 "}"); 384 385 CompilerTests.daggerCompiler(module) 386 .compile( 387 subject -> { 388 subject.hasErrorCount(1); 389 subject.hasErrorContaining( 390 "@Binds methods' nullability must match the nullability of its parameter"); 391 }); 392 } 393 394 // This is a regression test for b/370367984. 395 @Test bindsMapKVAndRequestMapKProviderV_failsWithMissingBindingError()396 public void bindsMapKVAndRequestMapKProviderV_failsWithMissingBindingError() { 397 Source component = 398 CompilerTests.javaSource( 399 "test.TestComponent", 400 "package test;", 401 "import dagger.Component;", 402 "import javax.inject.Provider;", 403 "import java.util.Map;", 404 "", 405 "@Component(modules = {TestModule.class})", 406 "interface TestComponent {", 407 " Map<K, Provider<V>> getMap();", 408 "}"); 409 Source module = 410 CompilerTests.javaSource( 411 "test.TestModule", 412 "package test;", 413 "import dagger.Binds;", 414 "import dagger.Module;", 415 "import dagger.Provides;", 416 "import java.util.Map;", 417 "", 418 "@Module", 419 "interface TestModule {", 420 " @Binds Map<K, V> bind(@TestQualifier Map<K, V> impl);", 421 "", 422 " @Provides", 423 " @TestQualifier", 424 " static Map<K, V> provideMap() {", 425 " return (Map<K, V>) null;", 426 " }", 427 "}"); 428 Source qualifier = 429 CompilerTests.javaSource( 430 "test.TestQualifier", 431 "package test;", 432 "import javax.inject.Qualifier;", 433 "", 434 "@Qualifier @interface TestQualifier {}"); 435 Source k = CompilerTests.javaSource("test.K", "package test;", "interface K {}"); 436 Source v = CompilerTests.javaSource("test.V", "package test;", "interface V {}"); 437 CompilerTests.daggerCompiler(component, module, qualifier, k, v) 438 .compile( 439 subject -> { 440 subject.hasErrorCount(1); 441 subject.hasErrorContaining("Map<test.K,Provider<test.V>> cannot be provided"); 442 }); 443 } 444 assertThatMethod(String method)445 private DaggerModuleMethodSubject assertThatMethod(String method) { 446 return assertThatModuleMethod(method).withDeclaration(moduleDeclaration); 447 } 448 449 @Qualifier 450 @Retention(RUNTIME) 451 public @interface Qualifier1 {} 452 453 @Qualifier 454 @Retention(RUNTIME) 455 public @interface Qualifier2 {} 456 } 457