/* * 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 androidx.room.compiler.processing.util.Source; import com.google.common.collect.ImmutableCollection; import dagger.testing.compile.CompilerTests; 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 parameters() { return CompilerMode.TEST_PARAMETERS; } private final CompilerMode compilerMode; public AssistedFactoryErrorsTest(CompilerMode compilerMode) { this.compilerMode = compilerMode; } @Test public void testFactoryNotAbstract() { Source foo = CompilerTests.javaSource( "test.Factory", "package test;", "", "import dagger.assisted.AssistedFactory;", "", "@AssistedFactory class Factory {}"); CompilerTests.daggerCompiler(foo) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "The @AssistedFactory-annotated type must be either an abstract class or " + "interface."); }); } @Test public void testNestedFactoryNotStatic() { Source foo = CompilerTests.javaSource( "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);", " }", "}"); CompilerTests.daggerCompiler(foo) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining("Nested @AssistedFactory-annotated types must be static."); }); } @Test public void testFactoryMissingAbstractMethod() { Source factory = CompilerTests.javaSource( "test.Factory", "package test;", "", "import dagger.assisted.AssistedFactory;", "", "@AssistedFactory interface Factory {}"); CompilerTests.daggerCompiler(factory) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "The @AssistedFactory-annotated type is missing an abstract, non-default method " + "whose return type matches the assisted injection type."); }); } @Test public void testFactoryReturnsNonDeclaredType() { Source noInject = CompilerTests.javaSource( "test.NoInject", "package test;", "", "final class NoInject {}"); Source noAssistedParam = CompilerTests.javaSource( "test.NoAssistedParam", "package test;", "", "import dagger.assisted.AssistedInject;", "", "final class NoAssistedParam {", " @AssistedInject NoAssistedParam() {}", "}"); Source factory = CompilerTests.javaSource( "test.Factory", "package test;", "", "import dagger.assisted.AssistedFactory;", "", "@AssistedFactory", "interface Factory {", " int createInt();", // Fails return type not @AssistedInject "", " NoInject createNoInject();", // Fails return type not @AssistedInject "", " NoAssistedParam createNoAssistedParam();", // Succeeds "", " T createT();", // Fails return type not @AssistedInject "}"); CompilerTests.daggerCompiler(factory, noInject, noAssistedParam) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(4); subject.hasErrorContaining( "The @AssistedFactory-annotated type should contain a single abstract, " + "non-default method but found multiple: [" + "test.Factory.createInt(), " + "test.Factory.createNoInject(), " + "test.Factory.createNoAssistedParam(), " + "test.Factory.createT()" + "]") .onSource(factory) .onLine(6); subject.hasErrorContaining( "Invalid return type: int. " + "An assisted factory's abstract method must return a type with an " + "@AssistedInject-annotated constructor.") .onSource(factory) .onLine(7); subject.hasErrorContaining( "Invalid return type: test.NoInject. " + "An assisted factory's abstract method must return a type with an " + "@AssistedInject-annotated constructor.") .onSource(factory) .onLine(9); subject.hasErrorContaining( "Invalid return type: T. " + "An assisted factory's abstract method must return a type with an " + "@AssistedInject-annotated constructor.") .onSource(factory) .onLine(13); }); } @Test public void testFactoryMultipleAbstractMethods() { Source foo = CompilerTests.javaSource( "test.Foo", "package test;", "", "import dagger.assisted.Assisted;", "import dagger.assisted.AssistedInject;", "", "class Foo {", " @AssistedInject Foo(@Assisted int i) {}", "}"); Source fooFactoryInterface = CompilerTests.javaSource( "test.FooFactoryInterface", "package test;", "", "interface FooFactoryInterface {", " Foo createFoo1(int i);", "}"); Source fooFactory = CompilerTests.javaSource( "test.FooFactory", "package test;", "", "import dagger.assisted.AssistedFactory;", "", "@AssistedFactory", "interface FooFactory extends FooFactoryInterface {", " Foo createFoo2(int i);", "", " Foo createFoo3(int i);", "}"); CompilerTests.daggerCompiler(foo, fooFactory, fooFactoryInterface) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "The @AssistedFactory-annotated type should contain a single abstract, " + "non-default method but found multiple: [" + "test.FooFactoryInterface.createFoo1(int), " + "test.FooFactory.createFoo2(int), " + "test.FooFactory.createFoo3(int)" + "]"); }); } @Test public void testFactoryMismatchingParameter() { Source foo = CompilerTests.javaSource( "test.Foo", "package test;", "", "import dagger.assisted.Assisted;", "import dagger.assisted.AssistedInject;", "", "class Foo {", " @AssistedInject Foo(@Assisted int i) {}", "}"); Source fooFactory = CompilerTests.javaSource( "test.FooFactory", "package test;", "", "import dagger.assisted.AssistedFactory;", "", "@AssistedFactory", "interface FooFactory {", " Foo create(String i);", "}"); CompilerTests.daggerCompiler(foo, fooFactory) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "The parameters in the factory method must match the @Assisted parameters in " + "test.Foo."); subject.hasErrorContaining(" Actual: test.FooFactory#create(java.lang.String)"); subject.hasErrorContaining("Expected: test.FooFactory#create(int)"); }); } @Test public void testFactoryMismatchingGenericParameter() { Source foo = CompilerTests.javaSource( "test.Foo", "package test;", "", "import dagger.assisted.Assisted;", "import dagger.assisted.AssistedInject;", "", "class Foo {", " @AssistedInject Foo(@Assisted T t) {}", "}"); Source fooFactory = CompilerTests.javaSource( "test.FooFactory", "package test;", "", "import dagger.assisted.AssistedFactory;", "", "@AssistedFactory", "interface FooFactory {", " Foo create(String str);", "}"); CompilerTests.daggerCompiler(foo, fooFactory) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "The parameters in the factory method must match the @Assisted parameters in " + "test.Foo."); subject.hasErrorContaining(" Actual: test.FooFactory#create(java.lang.String)"); subject.hasErrorContaining("Expected: test.FooFactory#create(T)"); }); } @Test public void testFactoryDuplicateGenericParameter() { Source foo = CompilerTests.javaSource( "test.Foo", "package test;", "", "import dagger.assisted.Assisted;", "import dagger.assisted.AssistedInject;", "", "class Foo {", " @AssistedInject Foo(@Assisted String str, @Assisted T t) {}", "}"); Source fooFactory = CompilerTests.javaSource( "test.FooFactory", "package test;", "", "import dagger.assisted.AssistedFactory;", "", "@AssistedFactory", "interface FooFactory {", " Foo create(String str1, String str2);", "}"); CompilerTests.daggerCompiler(foo, fooFactory) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "@AssistedFactory method has duplicate @Assisted types: " + "@Assisted java.lang.String"); }); } @Test public void testAssistedInjectionRequest() { Source foo = CompilerTests.javaSource( "test.Foo", "package test;", "", "import dagger.assisted.Assisted;", "import dagger.assisted.AssistedInject;", "", "class Foo {", " @AssistedInject Foo(@Assisted String str) {}", "}"); Source bar = CompilerTests.javaSource( "test.Bar", "package test;", "", "import javax.inject.Inject;", "import javax.inject.Provider;", "", "class Bar {", " @Inject", " Bar(Foo foo, Provider fooProvider) {}", "}"); Source module = CompilerTests.javaSource( "test.FooModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Provider;", "", "@Module", "class FooModule {", " @Provides", " static int provideInt(Foo foo, Provider fooProvider) {", " return 0;", " }", "}"); Source component = CompilerTests.javaSource( "test.FooComponent", "package test;", "", "import dagger.Component;", "import javax.inject.Provider;", "", "@Component", "interface FooComponent {", " Foo foo();", "", " Provider fooProvider();", "}"); CompilerTests.daggerCompiler(foo, bar, module, component) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(6); String fooError = "Dagger does not support injecting @AssistedInject type, test.Foo. " + "Did you mean to inject its assisted factory type instead?"; subject.hasErrorContaining(fooError).onSource(bar).onLine(8); subject.hasErrorContaining(fooError).onSource(module).onLine(10); subject.hasErrorContaining(fooError).onSource(component).onLine(8); String fooProviderError = "Dagger does not support injecting @AssistedInject type, " + "javax.inject.Provider. " + "Did you mean to inject its assisted factory type instead?"; subject.hasErrorContaining(fooProviderError).onSource(bar).onLine(8); subject.hasErrorContaining(fooProviderError).onSource(module).onLine(10); subject.hasErrorContaining(fooProviderError).onSource(component).onLine(10); }); } @Test public void testProvidesAssistedBindings() { Source foo = CompilerTests.javaSource( "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);", " }", "}"); Source module = CompilerTests.javaSource( "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;", " }", "}"); CompilerTests.daggerCompiler(foo, module) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(2); subject.hasErrorContaining( "[test.Foo] Dagger does not support providing @AssistedInject types.") .onSource(module) .onLine(10); subject.hasErrorContaining( "[test.Foo.Factory] Dagger does not support providing @AssistedFactory " + "types.") .onSource(module) .onLine(15); }); } @Test public void testProvidesAssistedBindingsAsFactoryBindsInstance() { Source foo = CompilerTests.javaSource( "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);", " }", "}"); Source component = CompilerTests.javaSource( "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);", " }", "}"); CompilerTests.daggerCompiler(foo, component) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(2); subject.hasErrorContaining( "[test.Foo] Dagger does not support providing @AssistedInject types.") .onSource(component) .onLine(11); subject.hasErrorContaining( "[test.Foo.Factory] Dagger does not support providing @AssistedFactory " + "types.") .onSource(component) .onLine(12); }); } @Test public void testProvidesAssistedBindingsAsBuilderBindsInstance() { Source foo = CompilerTests.javaSource( "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);", " }", "}"); Source component = CompilerTests.javaSource( "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();", " }", "}"); CompilerTests.daggerCompiler(foo, component) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(2); subject.hasErrorContaining( "[test.Foo] Dagger does not support providing @AssistedInject types.") .onSource(component) .onLine(10); subject.hasErrorContaining( "[test.Foo.Factory] Dagger does not support providing @AssistedFactory " + "types.") .onSource(component) .onLine(11); }); } @Test public void testProvidesAssistedBindingsAsOptional() { Source foo = CompilerTests.javaSource( "test.Foo", "package test;", "", "import dagger.assisted.Assisted;", "import dagger.assisted.AssistedInject;", "import dagger.assisted.AssistedFactory;", "", "class Foo {", " @AssistedInject Foo() {}", "", " @AssistedFactory", " interface Factory {", " Foo create();", " }", "}"); Source module = CompilerTests.javaSource( "test.FooModule", "package test;", "", "import dagger.BindsOptionalOf;", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "interface FooModule {", " @BindsOptionalOf Foo optionalFoo();", "", " @BindsOptionalOf Foo.Factory optionalFooFactory();", "}"); CompilerTests.daggerCompiler(foo, module) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(2); subject.hasErrorContaining( "[test.Foo] Dagger does not support providing @AssistedInject types.") .onSource(module) .onLine(9); subject.hasErrorContaining( "[test.Foo.Factory] Dagger does not support providing @AssistedFactory " + "types.") .onSource(module) .onLine(11); }); } @Test public void testInjectsLazyOfAssistedFactory() { Source foo = CompilerTests.javaSource( "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);", " }", "}"); Source bar = CompilerTests.javaSource( "test.Bar", "package test;", "", "import dagger.Lazy;", "import javax.inject.Inject;", "", "class Bar {", " @Inject", " Bar(Foo.Factory fooFactory, Lazy fooFactoryLazy) {}", "}"); CompilerTests.daggerCompiler(foo, bar) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "Dagger does not support injecting Lazy, Producer, or Produced " + "when T is an @AssistedFactory-annotated type such as test.Foo.Factory") .onSource(bar) .onLine(8); }); } @Test public void testScopedAssistedInjection() { Source foo = CompilerTests.javaSource( "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);", " }", "}"); CompilerTests.daggerCompiler(foo) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> // Don't assert on the number of errors as Foo_Factory_Impl can also be created // and have errors from the missing Foo_Factory. // TODO(erichang): don't generate the factory impls if there are errors with the // assisted type subject .hasErrorContaining( "A type with an @AssistedInject-annotated constructor cannot be scoped") .onSource(foo) .onLine(8)); } @Test public void testMultipleInjectAnnotations() { Source foo = CompilerTests.javaSource( "test.Foo", "package test;", "", "import dagger.assisted.Assisted;", "import dagger.assisted.AssistedInject;", "import javax.inject.Inject;", "", "class Foo {", " @Inject", " @AssistedInject", " Foo(@Assisted int i) {}", "}"); CompilerTests.daggerCompiler(foo) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "Constructors cannot be annotated with both @Inject and @AssistedInject"); }); } @Test public void testAssistedInjectWithNoAssistedParametersIsNotInjectable() { Source foo = CompilerTests.javaSource( "test.Foo", "package test;", "", "import javax.inject.Inject;", "", "class Foo {", " @Inject", " Foo(Bar bar) {}", "}"); Source bar = CompilerTests.javaSource( "test.Bar", "package test;", "", "import dagger.assisted.AssistedInject;", "import javax.inject.Inject;", "", "class Bar {", " @AssistedInject", " Bar() {}", "}"); Source component = CompilerTests.javaSource( "test.FooComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface FooComponent {", " Foo foo();", "}"); CompilerTests.daggerCompiler(foo, bar, component) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(2); subject.hasErrorContaining( "Dagger does not support injecting @AssistedInject type, test.Bar. " + "Did you mean to inject its assisted factory type instead?") .onSource(foo) .onLine(7); subject.hasErrorContaining( "\033[1;31m[Dagger/MissingBinding]\033[0m " + "Foo cannot be provided without an @Inject constructor or an " + "@Provides-annotated method."); }); } @Test public void testInaccessibleFoo() { Source foo = CompilerTests.javaSource( "test.subpackage.InaccessibleFoo", "package test.subpackage;", "", "import dagger.assisted.Assisted;", "import dagger.assisted.AssistedInject;", "", "class InaccessibleFoo {", " @AssistedInject InaccessibleFoo(@Assisted int i) {}", "}"); Source fooFactory = CompilerTests.javaSource( "test.subpackage.InaccessibleFooFactory", "package test.subpackage;", "", "import dagger.assisted.AssistedFactory;", "", "@AssistedFactory", "public interface InaccessibleFooFactory {", " InaccessibleFoo create(int i);", "}"); Source component = CompilerTests.javaSource( "test.FooFactoryComponent", "package test;", "", "import dagger.Component;", "import test.subpackage.InaccessibleFooFactory;", "", "@Component", "interface FooFactoryComponent {", " InaccessibleFooFactory inaccessibleFooFactory();", "}"); CompilerTests.DaggerCompiler daggerCompiler = CompilerTests.daggerCompiler(foo, fooFactory, component) .withProcessingOptions(compilerMode.processorOptions()); if (compilerMode == CompilerMode.FAST_INIT_MODE) { // TODO(bcorso): Remove once we fix inaccessible assisted factory imlementation for fastInit. daggerCompiler.compile( subject -> { // TODO(bcorso): We don't report the error count here because javac reports // the error once, whereas ksp reports the error twice. subject .hasErrorContaining( "test.subpackage.InaccessibleFoo is not public in test.subpackage; cannot be " + "accessed from outside package"); }); } else { daggerCompiler.compile(subject -> subject.hasErrorCount(0)); } } @Test public void testAssistedFactoryMethodWithTypeParametersFails() { Source foo = CompilerTests.javaSource( "test.Foo", "package test;", "", "import dagger.assisted.AssistedInject;", "import dagger.assisted.AssistedFactory;", "", "class Foo {", " @AssistedInject", " Foo() {}", "", " @AssistedFactory", " interface FooFactory {", " Foo create();", " }", "}"); CompilerTests.daggerCompiler(foo) .withProcessingOptions(compilerMode.processorOptions()) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "@AssistedFactory does not currently support type parameters in the creator " + "method.") .onSource(foo) .onLine(12); }); } }