xref: /aosp_15_r20/external/dagger2/javatests/dagger/internal/codegen/BindsMethodValidationTest.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
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