blob: 2bcfedcc80b9d581e7d2fd738239e99143aa8a8d [file] [log] [blame]
/*
* Copyright (C) 2020 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 com.google.testing.compile.CompilationSubject.assertThat;
import static dagger.internal.codegen.Compilers.compilerWithOptions;
import com.google.common.collect.ImmutableCollection;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import javax.tools.JavaFileObject;
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 AssistedFactoryErrorsTest {
@Parameters(name = "{0}")
public static ImmutableCollection<Object[]> parameters() {
return CompilerMode.TEST_PARAMETERS;
}
private final CompilerMode compilerMode;
public AssistedFactoryErrorsTest(CompilerMode compilerMode) {
this.compilerMode = compilerMode;
}
@Test
public void testFactoryNotAbstract() {
JavaFileObject factory =
JavaFileObjects.forSourceLines(
"test.Factory",
"package test;",
"",
"import dagger.assisted.AssistedFactory;",
"",
"@AssistedFactory class Factory {}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(factory);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining(
"The @AssistedFactory-annotated type must be either an abstract class or interface.");
}
@Test
public void testNestedFactoryNotStatic() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"import dagger.assisted.AssistedFactory;",
"",
"class Foo {",
" @AssistedInject",
" Foo(@Assisted int i) {}",
"",
" @AssistedFactory",
" abstract class Factory {",
" abstract Foo create(int i);",
" }",
"}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining("Nested @AssistedFactory-annotated types must be static.");
}
@Test
public void testFactoryMissingAbstractMethod() {
JavaFileObject factory =
JavaFileObjects.forSourceLines(
"test.Factory",
"package test;",
"",
"import dagger.assisted.AssistedFactory;",
"",
"@AssistedFactory interface Factory {}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(factory);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining(
"The @AssistedFactory-annotated type is missing an abstract, non-default method whose"
+ " return type matches the assisted injection type.");
}
@Test
public void testFactoryReturnsNonDeclaredType() {
JavaFileObject noInject =
JavaFileObjects.forSourceLines(
"test.NoInject", "package test;", "", "final class NoInject {}");
JavaFileObject noAssistedParam =
JavaFileObjects.forSourceLines(
"test.NoAssistedParam",
"package test;",
"",
"import dagger.assisted.AssistedInject;",
"",
"final class NoAssistedParam {",
" @AssistedInject NoAssistedParam() {}",
"}");
JavaFileObject factory =
JavaFileObjects.forSourceLines(
"test.Factory",
"package test;",
"",
"import dagger.assisted.AssistedFactory;",
"",
"@AssistedFactory",
"interface Factory<T> {",
" int createInt();", // Fails return type not @AssistedInject
"",
" NoInject createNoInject();", // Fails return type not @AssistedInject
"",
" NoAssistedParam createNoAssistedParam();", // Succeeds
"",
" T createT();", // Fails return type not @AssistedInject
"}");
Compilation compilation =
compilerWithOptions(compilerMode.javacopts()).compile(factory, noInject, noAssistedParam);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(4);
assertThat(compilation)
.hadErrorContaining(
"The @AssistedFactory-annotated type should contain a single abstract, non-default "
+ "method but found multiple: ["
+ "createInt(), createNoInject(), createNoAssistedParam(), createT()]")
.inFile(factory)
.onLine(6);
assertThat(compilation)
.hadErrorContaining(
"Invalid return type: int. "
+ "An assisted factory's abstract method must return a type with an "
+ "@AssistedInject-annotated constructor.")
.inFile(factory)
.onLine(7);
assertThat(compilation)
.hadErrorContaining(
"Invalid return type: test.NoInject. "
+ "An assisted factory's abstract method must return a type with an "
+ "@AssistedInject-annotated constructor.")
.inFile(factory)
.onLine(9);
assertThat(compilation)
.hadErrorContaining(
"Invalid return type: T. "
+ "An assisted factory's abstract method must return a type with an "
+ "@AssistedInject-annotated constructor.")
.inFile(factory)
.onLine(13);
}
@Test
public void testFactoryMultipleAbstractMethods() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"",
"class Foo {",
" @AssistedInject Foo(@Assisted int i) {}",
"}");
JavaFileObject fooFactoryInterface =
JavaFileObjects.forSourceLines(
"test.FooFactoryInterface",
"package test;",
"",
"interface FooFactoryInterface {",
" Foo createFoo1(int i);",
"}");
JavaFileObject fooFactory =
JavaFileObjects.forSourceLines(
"test.FooFactory",
"package test;",
"",
"import dagger.assisted.AssistedFactory;",
"",
"@AssistedFactory",
"interface FooFactory extends FooFactoryInterface {",
" Foo createFoo2(int i);",
"",
" Foo createFoo3(int i);",
"}");
Compilation compilation =
compilerWithOptions(compilerMode.javacopts()).compile(foo, fooFactory, fooFactoryInterface);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining(
"The @AssistedFactory-annotated type should contain a single abstract, non-default "
+ "method but found multiple: [createFoo1(int), createFoo2(int), createFoo3(int)]");
}
@Test
public void testFactoryMismatchingParameter() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"",
"class Foo {",
" @AssistedInject Foo(@Assisted int i) {}",
"}");
JavaFileObject fooFactory =
JavaFileObjects.forSourceLines(
"test.FooFactory",
"package test;",
"",
"import dagger.assisted.AssistedFactory;",
"",
"@AssistedFactory",
"interface FooFactory {",
" Foo create(String i);",
"}");
Compilation compilation =
compilerWithOptions(compilerMode.javacopts()).compile(foo, fooFactory);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining(
"The parameters in the factory method must match the @Assisted parameters in "
+ "test.Foo.\n"
+ " Actual: test.FooFactory#create(java.lang.String)\n"
+ " Expected: test.FooFactory#create(int)");
}
@Test
public void testFactoryMismatchingGenericParameter() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"",
"class Foo<T> {",
" @AssistedInject Foo(@Assisted T t) {}",
"}");
JavaFileObject fooFactory =
JavaFileObjects.forSourceLines(
"test.FooFactory",
"package test;",
"",
"import dagger.assisted.AssistedFactory;",
"",
"@AssistedFactory",
"interface FooFactory<T> {",
" Foo<T> create(String str);",
"}");
Compilation compilation =
compilerWithOptions(compilerMode.javacopts()).compile(foo, fooFactory);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining(
"The parameters in the factory method must match the @Assisted parameters in "
+ "test.Foo<T>.\n"
+ " Actual: test.FooFactory#create(java.lang.String)\n"
+ " Expected: test.FooFactory#create(T)");
}
@Test
public void testFactoryDuplicateGenericParameter() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"",
"class Foo<T> {",
" @AssistedInject Foo(@Assisted String str, @Assisted T t) {}",
"}");
JavaFileObject fooFactory =
JavaFileObjects.forSourceLines(
"test.FooFactory",
"package test;",
"",
"import dagger.assisted.AssistedFactory;",
"",
"@AssistedFactory",
"interface FooFactory {",
" Foo<String> create(String str1, String str2);",
"}");
Compilation compilation =
compilerWithOptions(compilerMode.javacopts()).compile(foo, fooFactory);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining(
"@AssistedFactory method has duplicate @Assisted types: @Assisted java.lang.String");
}
@Test
public void testAssistedInjectionRequest() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"",
"class Foo {",
" @AssistedInject Foo(@Assisted String str) {}",
"}");
JavaFileObject bar =
JavaFileObjects.forSourceLines(
"test.Bar",
"package test;",
"",
"import javax.inject.Inject;",
"import javax.inject.Provider;",
"",
"class Bar {",
" @Inject",
" Bar(Foo foo, Provider<Foo> fooProvider) {}",
"}");
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.FooModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import javax.inject.Provider;",
"",
"@Module",
"class FooModule {",
" @Provides",
" static int provideInt(Foo foo, Provider<Foo> fooProvider) {",
" return 0;",
" }",
"}");
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.FooComponent",
"package test;",
"",
"import dagger.Component;",
"import javax.inject.Provider;",
"",
"@Component",
"interface FooComponent {",
" Foo foo();",
"",
" Provider<Foo> fooProvider();",
"}");
Compilation compilation =
compilerWithOptions(compilerMode.javacopts()).compile(foo, bar, module, component);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(6);
String fooError =
"Dagger does not support injecting @AssistedInject type, test.Foo. "
+ "Did you mean to inject its assisted factory type instead?";
assertThat(compilation).hadErrorContaining(fooError).inFile(bar).onLine(8);
assertThat(compilation).hadErrorContaining(fooError).inFile(module).onLine(10);
assertThat(compilation).hadErrorContaining(fooError).inFile(component).onLine(8);
String fooProviderError =
"Dagger does not support injecting @AssistedInject type, javax.inject.Provider<test.Foo>. "
+ "Did you mean to inject its assisted factory type instead?";
assertThat(compilation).hadErrorContaining(fooProviderError).inFile(bar).onLine(8);
assertThat(compilation).hadErrorContaining(fooProviderError).inFile(module).onLine(10);
assertThat(compilation).hadErrorContaining(fooProviderError).inFile(component).onLine(10);
}
@Test
public void testProvidesAssistedBindings() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"import dagger.assisted.AssistedFactory;",
"",
"class Foo {",
" @AssistedInject Foo(@Assisted int i) {}",
"",
" @AssistedFactory",
" interface Factory {",
" Foo create(int i);",
" }",
"}");
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.FooModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import javax.inject.Provider;",
"",
"@Module",
"class FooModule {",
" @Provides",
" static Foo provideFoo() {",
" return null;",
" }",
"",
" @Provides",
" static Foo.Factory provideFooFactory() {",
" return null;",
" }",
"}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo, module);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(2);
assertThat(compilation)
.hadErrorContaining("[test.Foo] Dagger does not support providing @AssistedInject types.")
.inFile(module)
.onLine(10);
assertThat(compilation)
.hadErrorContaining(
"[test.Foo.Factory] Dagger does not support providing @AssistedFactory types.")
.inFile(module)
.onLine(15);
}
@Test
public void testProvidesAssistedBindingsAsFactoryBindsInstance() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"import dagger.assisted.AssistedFactory;",
"",
"class Foo {",
" @AssistedInject Foo(@Assisted int i) {}",
"",
" @AssistedFactory",
" interface Factory {",
" Foo create(int i);",
" }",
"}");
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.FooComponent",
"package test;",
"",
"import dagger.Component;",
"import dagger.BindsInstance;",
"",
"@Component",
"interface FooComponent {",
" @Component.Factory",
" interface Factory {",
" FooComponent create(",
" @BindsInstance Foo foo,",
" @BindsInstance Foo.Factory fooFactory);",
" }",
"}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo, component);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(2);
assertThat(compilation)
.hadErrorContaining("[test.Foo] Dagger does not support providing @AssistedInject types.")
.inFile(component)
.onLine(11);
assertThat(compilation)
.hadErrorContaining(
"[test.Foo.Factory] Dagger does not support providing @AssistedFactory types.")
.inFile(component)
.onLine(12);
}
@Test
public void testProvidesAssistedBindingsAsBuilderBindsInstance() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"import dagger.assisted.AssistedFactory;",
"",
"class Foo {",
" @AssistedInject Foo(@Assisted int i) {}",
"",
" @AssistedFactory",
" interface Factory {",
" Foo create(int i);",
" }",
"}");
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.FooComponent",
"package test;",
"",
"import dagger.Component;",
"import dagger.BindsInstance;",
"",
"@Component",
"interface FooComponent {",
" @Component.Builder",
" interface Builder {",
" @BindsInstance Builder foo(Foo foo);",
" @BindsInstance Builder fooFactory(Foo.Factory fooFactory);",
" FooComponent build();",
" }",
"}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo, component);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(2);
assertThat(compilation)
.hadErrorContaining("[test.Foo] Dagger does not support providing @AssistedInject types.")
.inFile(component)
.onLine(10);
assertThat(compilation)
.hadErrorContaining(
"[test.Foo.Factory] Dagger does not support providing @AssistedFactory types.")
.inFile(component)
.onLine(11);
}
@Test
public void testInjectsProviderOfAssistedFactory() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"import dagger.assisted.AssistedFactory;",
"",
"class Foo {",
" @AssistedInject Foo(@Assisted int i) {}",
"",
" @AssistedFactory",
" interface Factory {",
" Foo create(int i);",
" }",
"}");
JavaFileObject bar =
JavaFileObjects.forSourceLines(
"test.Bar",
"package test;",
"",
"import javax.inject.Inject;",
"import javax.inject.Provider;",
"",
"class Bar {",
" @Inject",
" Bar(Foo.Factory fooFactory, Provider<Foo.Factory> fooFactoryProvider) {}",
"}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo, bar);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining(
"Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, or Produced<T> "
+ "when T is an @AssistedFactory-annotated type such as test.Foo.Factory")
.inFile(bar)
.onLine(8);
}
@Test
public void testScopedAssistedInjection() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"import dagger.assisted.AssistedFactory;",
"import javax.inject.Singleton;",
"",
"@Singleton",
"class Foo {",
" @AssistedInject",
" Foo(@Assisted int i) {}",
"",
" @AssistedFactory",
" interface Factory {",
" Foo create(int i);",
" }",
"}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining("A type with an @AssistedInject-annotated constructor cannot be scoped")
.inFile(foo)
.onLine(8);
}
@Test
public void testMultipleInjectAnnotations() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"import javax.inject.Inject;",
"",
"class Foo {",
" @Inject",
" @AssistedInject",
" Foo(@Assisted int i) {}",
"}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining(
"Constructors cannot be annotated with both @Inject and @AssistedInject");
}
@Test
public void testAssistedInjectNotOnConstructor() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.AssistedInject;",
"",
"class Foo {",
" @AssistedInject",
" void someMethod() {}",
"}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
// Note: this isn't actually a Dagger error, it's a javac error since @AssistedInject only
// targets constructors. However, it's good to have this test in case that ever changes.
assertThat(compilation)
.hadErrorContaining("annotation type not applicable to this kind of declaration")
.inFile(foo)
.onLine(6);
}
@Test
public void testAssistedInjectWithNoAssistedParametersIsNotInjectable() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import javax.inject.Inject;",
"",
"class Foo {",
" @Inject",
" Foo(Bar bar) {}",
"}");
JavaFileObject bar =
JavaFileObjects.forSourceLines(
"test.Bar",
"package test;",
"",
"import dagger.assisted.AssistedInject;",
"import javax.inject.Inject;",
"",
"class Bar {",
" @AssistedInject",
" Bar() {}",
"}");
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.FooComponent",
"package test;",
"",
"import dagger.Component;",
"",
"@Component",
"interface FooComponent {",
" Foo foo();",
"}");
Compilation compilation =
compilerWithOptions(compilerMode.javacopts()).compile(foo, bar, component);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(2);
assertThat(compilation)
.hadErrorContaining(
"Dagger does not support injecting @AssistedInject type, test.Bar. "
+ "Did you mean to inject its assisted factory type instead?")
.inFile(foo)
.onLine(7);
assertThat(compilation)
.hadErrorContaining(
"\033[1;31m[Dagger/MissingBinding]\033[0m "
+ "Foo cannot be provided without an @Inject constructor or an @Provides-annotated "
+ "method.");
}
@Test
public void testInaccessibleFoo() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.subpackage.InaccessibleFoo",
"package test.subpackage;",
"",
"import dagger.assisted.Assisted;",
"import dagger.assisted.AssistedInject;",
"",
"class InaccessibleFoo {",
" @AssistedInject InaccessibleFoo(@Assisted int i) {}",
"}");
JavaFileObject fooFactory =
JavaFileObjects.forSourceLines(
"test.subpackage.InaccessibleFooFactory",
"package test.subpackage;",
"",
"import dagger.assisted.AssistedFactory;",
"",
"@AssistedFactory",
"public interface InaccessibleFooFactory {",
" InaccessibleFoo create(int i);",
"}");
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.FooFactoryComponent",
"package test;",
"",
"import dagger.Component;",
"import test.subpackage.InaccessibleFooFactory;",
"",
"@Component",
"interface FooFactoryComponent {",
" InaccessibleFooFactory inaccessibleFooFactory();",
"}");
Compilation compilation =
compilerWithOptions(compilerMode.javacopts()).compile(foo, fooFactory, component);
if (compilerMode == CompilerMode.FAST_INIT_MODE) {
// TODO(bcorso): Remove once we fix inaccessible assisted factory imlementation for fastInit.
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining(
"test.subpackage.InaccessibleFoo is not public in test.subpackage; cannot be "
+ "accessed from outside package");
} else {
assertThat(compilation).succeeded();
}
}
@Test
public void testAssistedFactoryMethodWithTypeParametersFails() {
JavaFileObject foo =
JavaFileObjects.forSourceLines(
"test.Foo",
"package test;",
"",
"import dagger.assisted.AssistedInject;",
"import dagger.assisted.AssistedFactory;",
"",
"class Foo<T> {",
" @AssistedInject",
" Foo() {}",
"",
" @AssistedFactory",
" interface FooFactory {",
" <T> Foo<T> create();",
" }",
"}");
Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo);
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertThat(compilation)
.hadErrorContaining(
"@AssistedFactory does not currently support type parameters in the creator method.")
.inFile(foo)
.onLine(12);
}
}