aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad Corso <bcorso@google.com>2023-12-06 09:42:25 -0800
committerDagger Team <dagger-dev+copybara@google.com>2023-12-06 09:45:43 -0800
commit668269ddfec64dad8685b2f38f66cc8227dd3c8a (patch)
treef2f9fffd47aef385b91240000a8fc93891fc1185
parent344e135248379567488b32ab148a8a4544d16533 (diff)
downloaddagger2-668269ddfec64dad8685b2f38f66cc8227dd3c8a.tar.gz
Update hjar generators to output valid method bodies for methods and constructors.
This CL updates Dagger's hjar generators to have valid return statements for methods with non-`void` return types and valid super calls for constructors. For example: ``` // Example of hjar generated class with valid constructor and method bodies. class Foo extends FooSuper { Foo() { super(null, 0, null); } String stringMethod() { return null; } int intMethod() { return 0; } } ``` Note that this isn't required when compiling with Turbine, but is necessary when compiling the Hjar stubs in Javac. RELNOTES=N/A PiperOrigin-RevId: 588456719
-rw-r--r--java/dagger/internal/codegen/DelegateComponentProcessor.java32
-rw-r--r--java/dagger/internal/codegen/base/SourceFileHjarGenerator.java211
-rw-r--r--java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java28
-rw-r--r--java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java122
-rw-r--r--javatests/dagger/internal/codegen/HjarTest.java92
5 files changed, 344 insertions, 141 deletions
diff --git a/java/dagger/internal/codegen/DelegateComponentProcessor.java b/java/dagger/internal/codegen/DelegateComponentProcessor.java
index b3e2c2fd7..8f8d5040b 100644
--- a/java/dagger/internal/codegen/DelegateComponentProcessor.java
+++ b/java/dagger/internal/codegen/DelegateComponentProcessor.java
@@ -32,6 +32,7 @@ import dagger.Provides;
import dagger.internal.codegen.base.ClearableCache;
import dagger.internal.codegen.base.SourceFileGenerationException;
import dagger.internal.codegen.base.SourceFileGenerator;
+import dagger.internal.codegen.base.SourceFileHjarGenerator;
import dagger.internal.codegen.binding.BindingGraphFactory;
import dagger.internal.codegen.binding.InjectBindingRegistry;
import dagger.internal.codegen.binding.MembersInjectionBinding;
@@ -53,7 +54,6 @@ import dagger.internal.codegen.validation.InjectBindingRegistryModule;
import dagger.internal.codegen.validation.InjectValidator;
import dagger.internal.codegen.validation.ValidationBindingGraphPlugins;
import dagger.internal.codegen.writing.FactoryGenerator;
-import dagger.internal.codegen.writing.HjarSourceFileGenerator;
import dagger.internal.codegen.writing.MembersInjectorGenerator;
import dagger.internal.codegen.writing.ModuleGenerator;
import dagger.internal.codegen.writing.ModuleProxies.ModuleConstructorProxyGenerator;
@@ -191,34 +191,44 @@ final class DelegateComponentProcessor {
interface SourceFileGeneratorsModule {
@Provides
static SourceFileGenerator<ProvisionBinding> factoryGenerator(
- FactoryGenerator generator, CompilerOptions compilerOptions) {
- return hjarWrapper(generator, compilerOptions);
+ FactoryGenerator generator,
+ CompilerOptions compilerOptions,
+ XProcessingEnv processingEnv) {
+ return hjarWrapper(generator, compilerOptions, processingEnv);
}
@Provides
static SourceFileGenerator<ProductionBinding> producerFactoryGenerator(
- ProducerFactoryGenerator generator, CompilerOptions compilerOptions) {
- return hjarWrapper(generator, compilerOptions);
+ ProducerFactoryGenerator generator,
+ CompilerOptions compilerOptions,
+ XProcessingEnv processingEnv) {
+ return hjarWrapper(generator, compilerOptions, processingEnv);
}
@Provides
static SourceFileGenerator<MembersInjectionBinding> membersInjectorGenerator(
- MembersInjectorGenerator generator, CompilerOptions compilerOptions) {
- return hjarWrapper(generator, compilerOptions);
+ MembersInjectorGenerator generator,
+ CompilerOptions compilerOptions,
+ XProcessingEnv processingEnv) {
+ return hjarWrapper(generator, compilerOptions, processingEnv);
}
@Provides
@ModuleGenerator
static SourceFileGenerator<XTypeElement> moduleConstructorProxyGenerator(
- ModuleConstructorProxyGenerator generator, CompilerOptions compilerOptions) {
- return hjarWrapper(generator, compilerOptions);
+ ModuleConstructorProxyGenerator generator,
+ CompilerOptions compilerOptions,
+ XProcessingEnv processingEnv) {
+ return hjarWrapper(generator, compilerOptions, processingEnv);
}
}
private static <T> SourceFileGenerator<T> hjarWrapper(
- SourceFileGenerator<T> generator, CompilerOptions compilerOptions) {
+ SourceFileGenerator<T> generator,
+ CompilerOptions compilerOptions,
+ XProcessingEnv processingEnv) {
return compilerOptions.headerCompilation()
- ? HjarSourceFileGenerator.wrap(generator)
+ ? SourceFileHjarGenerator.wrap(generator, processingEnv)
: generator;
}
}
diff --git a/java/dagger/internal/codegen/base/SourceFileHjarGenerator.java b/java/dagger/internal/codegen/base/SourceFileHjarGenerator.java
new file mode 100644
index 000000000..6857c366f
--- /dev/null
+++ b/java/dagger/internal/codegen/base/SourceFileHjarGenerator.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2017 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.base;
+
+import static com.squareup.javapoet.MethodSpec.constructorBuilder;
+import static com.squareup.javapoet.MethodSpec.methodBuilder;
+import static com.squareup.javapoet.TypeSpec.classBuilder;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom;
+import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
+import static javax.lang.model.element.Modifier.PRIVATE;
+
+import androidx.room.compiler.processing.XConstructorElement;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XExecutableParameterElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XType;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import dagger.internal.codegen.javapoet.CodeBlocks;
+import dagger.internal.codegen.javapoet.TypeNames;
+import java.util.Optional;
+import javax.lang.model.element.Modifier;
+
+/**
+ * A source file generator that only writes the relevant code necessary for Bazel to create a
+ * correct header (ABI) jar.
+ */
+public final class SourceFileHjarGenerator<T> extends SourceFileGenerator<T> {
+ public static <T> SourceFileGenerator<T> wrap(
+ SourceFileGenerator<T> delegate, XProcessingEnv processingEnv) {
+ return new SourceFileHjarGenerator<>(delegate, processingEnv);
+ }
+
+ private final SourceFileGenerator<T> delegate;
+ private final XProcessingEnv processingEnv;
+
+ private SourceFileHjarGenerator(SourceFileGenerator<T> delegate, XProcessingEnv processingEnv) {
+ super(delegate);
+ this.delegate = delegate;
+ this.processingEnv = processingEnv;
+ }
+
+ @Override
+ public XElement originatingElement(T input) {
+ return delegate.originatingElement(input);
+ }
+
+ @Override
+ public ImmutableList<TypeSpec.Builder> topLevelTypes(T input) {
+ String packageName = closestEnclosingTypeElement(originatingElement(input)).getPackageName();
+ return delegate.topLevelTypes(input).stream()
+ .map(completeType -> skeletonType(packageName, completeType.build()))
+ .collect(toImmutableList());
+ }
+
+ private TypeSpec.Builder skeletonType(String packageName, TypeSpec completeType) {
+ TypeSpec.Builder skeleton =
+ classBuilder(completeType.name)
+ .addSuperinterfaces(completeType.superinterfaces)
+ .addTypeVariables(completeType.typeVariables)
+ .addModifiers(completeType.modifiers.toArray(new Modifier[0]))
+ .addAnnotations(completeType.annotations);
+
+ if (!completeType.superclass.equals(ClassName.OBJECT)) {
+ skeleton.superclass(completeType.superclass);
+ }
+
+ completeType.methodSpecs.stream()
+ .filter(method -> !method.modifiers.contains(PRIVATE) || method.isConstructor())
+ .map(completeMethod -> skeletonMethod(packageName, completeType, completeMethod))
+ .forEach(skeleton::addMethod);
+
+ completeType.fieldSpecs.stream()
+ .filter(field -> !field.modifiers.contains(PRIVATE))
+ .map(this::skeletonField)
+ .forEach(skeleton::addField);
+
+ completeType.typeSpecs.stream()
+ .map(type -> skeletonType(packageName, type).build())
+ .forEach(skeleton::addType);
+
+ completeType.alwaysQualifiedNames
+ .forEach(skeleton::alwaysQualify);
+
+ return skeleton;
+ }
+
+ private MethodSpec skeletonMethod(
+ String packageName, TypeSpec completeType, MethodSpec completeMethod) {
+ MethodSpec.Builder skeleton =
+ completeMethod.isConstructor()
+ ? constructorBuilder()
+ : methodBuilder(completeMethod.name).returns(completeMethod.returnType);
+
+ if (completeMethod.isConstructor()) {
+ getRequiredSuperCall(packageName, completeType)
+ .ifPresent(superCall -> skeleton.addStatement("$L", superCall));
+ } else if (!completeMethod.returnType.equals(TypeName.VOID)) {
+ skeleton.addStatement("return $L", getDefaultValueCodeBlock(completeMethod.returnType));
+ }
+
+ return skeleton
+ .addModifiers(completeMethod.modifiers)
+ .addTypeVariables(completeMethod.typeVariables)
+ .addParameters(completeMethod.parameters)
+ .addExceptions(completeMethod.exceptions)
+ .varargs(completeMethod.varargs)
+ .addAnnotations(completeMethod.annotations)
+ .build();
+ }
+
+ private Optional<CodeBlock> getRequiredSuperCall(String packageName, TypeSpec completeType) {
+ if (completeType.superclass.equals(TypeName.OBJECT)) {
+ return Optional.empty();
+ }
+
+ ClassName rawSuperClass = (ClassName) TypeNames.rawTypeName(completeType.superclass);
+ XTypeElement superTypeElement =
+ processingEnv.requireTypeElement(rawSuperClass.canonicalName());
+
+ ImmutableSet<XConstructorElement> accessibleConstructors =
+ superTypeElement.getConstructors().stream()
+ .filter(
+ constructor ->
+ // isElementAccessibleFrom doesn't take protected into account so check manually
+ constructor.isProtected()
+ || isElementAccessibleFrom(constructor, packageName))
+ .collect(toImmutableSet());
+
+ // If there's an accessible default constructor we don't need to call super() manually.
+ if (accessibleConstructors.isEmpty()
+ || accessibleConstructors.stream()
+ .anyMatch(constructor -> constructor.getParameters().isEmpty())) {
+ return Optional.empty();
+ }
+
+ return Optional.of(
+ CodeBlock.of(
+ "super($L)",
+ CodeBlocks.makeParametersCodeBlock(
+ // We just choose the first constructor (it doesn't really matter since we're just
+ // trying to ensure the constructor body compiles).
+ accessibleConstructors.stream().findFirst().get().getParameters().stream()
+ .map(XExecutableParameterElement::getType)
+ .map(XType::getTypeName)
+ .map(SourceFileHjarGenerator::getDefaultValueCodeBlock)
+ .collect(toImmutableList()))));
+ }
+
+ /**
+ * Returns a {@link CodeBlock} containing the default value for the given {@code typeName}.
+ *
+ * <p>See https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html.
+ */
+ private static CodeBlock getDefaultValueCodeBlock(TypeName typeName) {
+ if (typeName.isPrimitive()) {
+ if (typeName.equals(TypeName.BOOLEAN)) {
+ return CodeBlock.of("false");
+ } else if (typeName.equals(TypeName.CHAR)) {
+ return CodeBlock.of("'\u0000'");
+ } else if (typeName.equals(TypeName.BYTE)) {
+ return CodeBlock.of("0");
+ } else if (typeName.equals(TypeName.SHORT)) {
+ return CodeBlock.of("0");
+ } else if (typeName.equals(TypeName.INT)) {
+ return CodeBlock.of("0");
+ } else if (typeName.equals(TypeName.LONG)) {
+ return CodeBlock.of("0L");
+ } else if (typeName.equals(TypeName.FLOAT)) {
+ return CodeBlock.of("0.0f");
+ } else if (typeName.equals(TypeName.DOUBLE)) {
+ return CodeBlock.of("0.0d");
+ } else {
+ throw new AssertionError("Unexpected type: " + typeName);
+ }
+ }
+ return CodeBlock.of("null");
+ }
+
+ private FieldSpec skeletonField(FieldSpec completeField) {
+ return FieldSpec.builder(
+ completeField.type,
+ completeField.name,
+ completeField.modifiers.toArray(new Modifier[0]))
+ .addAnnotations(completeField.annotations)
+ .build();
+ }
+}
diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java b/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java
index 179c411e4..a071eaeb2 100644
--- a/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java
+++ b/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java
@@ -16,9 +16,12 @@
package dagger.internal.codegen.componentgenerator;
+import androidx.room.compiler.processing.XProcessingEnv;
import dagger.Binds;
import dagger.Module;
+import dagger.Provides;
import dagger.internal.codegen.base.SourceFileGenerator;
+import dagger.internal.codegen.base.SourceFileHjarGenerator;
import dagger.internal.codegen.binding.BindingGraph;
import dagger.internal.codegen.binding.ComponentDescriptor;
@@ -29,14 +32,23 @@ public interface ComponentGeneratorModule {
@Binds
abstract SourceFileGenerator<BindingGraph> componentGenerator(ComponentGenerator generator);
- // The HjarSourceFileGenerator wrapper first generates the entire TypeSpec before stripping out
+ // The SourceFileHjarGenerator wrapper first generates the entire TypeSpec before stripping out
// things that aren't needed for the hjar. However, this can be really expensive for the component
// because it is usually the most expensive file to generate, and most of its content is not
- // needed in the hjar. Thus, instead of wrapping the ComponentGenerator in HjarSourceFileGenerator
- // we provide a completely separate processing step, ComponentHjarProcessingStep, and generator,
- // ComponentHjarGenerator, for when generating hjars for components, which can avoid generating
- // the parts of the component that would have been stripped out by the HjarSourceFileGenerator.
- @Binds
- abstract SourceFileGenerator<ComponentDescriptor> componentHjarGenerator(
- ComponentHjarGenerator hjarGenerator);
+ // needed in the hjar. Thus, we provide a completely separate processing step,
+ // ComponentHjarProcessingStep and ComponentHjarGenerator, for when generating hjars for
+ // components, which can avoid generating the parts of the component that would have been stripped
+ // out by the HjarSourceFileGenerator anyway. Note that we still wrap ComponentHjarGenerator in
+ // SourceFileHjarGenerator because it adds in constructor and method bodies that are needed for
+ // Javac to compile correctly, e.g. super(...) calls in the constructor and return statements in
+ // methods.
+ @Provides
+ static SourceFileGenerator<ComponentDescriptor> componentHjarGenerator(
+ XProcessingEnv processingEnv,
+ ComponentHjarGenerator hjarGenerator) {
+ // Note: technically the ComponentHjarGenerator is already in hjar form, but the
+ // SourceFileHjarGenerator wrapper adds in proper method bodies, e.g. constructors that require
+ // super() calls or methods that require return statements.
+ return SourceFileHjarGenerator.wrap(hjarGenerator, processingEnv);
+ }
}
diff --git a/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java b/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java
deleted file mode 100644
index 4bafb5aff..000000000
--- a/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2017 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.writing;
-
-import static com.squareup.javapoet.MethodSpec.constructorBuilder;
-import static com.squareup.javapoet.MethodSpec.methodBuilder;
-import static com.squareup.javapoet.TypeSpec.classBuilder;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
-import static javax.lang.model.element.Modifier.PRIVATE;
-
-import androidx.room.compiler.processing.XElement;
-import com.google.common.collect.ImmutableList;
-import com.squareup.javapoet.ClassName;
-import com.squareup.javapoet.FieldSpec;
-import com.squareup.javapoet.MethodSpec;
-import com.squareup.javapoet.TypeSpec;
-import dagger.internal.codegen.base.SourceFileGenerator;
-import javax.lang.model.element.Modifier;
-
-/**
- * A source file generator that only writes the relevant code necessary for Bazel to create a
- * correct header (ABI) jar.
- */
-public final class HjarSourceFileGenerator<T> extends SourceFileGenerator<T> {
- private final SourceFileGenerator<T> delegate;
-
- private HjarSourceFileGenerator(SourceFileGenerator<T> delegate) {
- super(delegate);
- this.delegate = delegate;
- }
-
- public static <T> SourceFileGenerator<T> wrap(SourceFileGenerator<T> delegate) {
- return new HjarSourceFileGenerator<>(delegate);
- }
-
- @Override
- public XElement originatingElement(T input) {
- return delegate.originatingElement(input);
- }
-
- @Override
- public ImmutableList<TypeSpec.Builder> topLevelTypes(T input) {
- return delegate.topLevelTypes(input).stream()
- .map(completeType -> skeletonType(completeType.build()))
- .collect(toImmutableList());
- }
-
- private TypeSpec.Builder skeletonType(TypeSpec completeType) {
- TypeSpec.Builder skeleton =
- classBuilder(completeType.name)
- .addSuperinterfaces(completeType.superinterfaces)
- .addTypeVariables(completeType.typeVariables)
- .addModifiers(completeType.modifiers.toArray(new Modifier[0]))
- .addAnnotations(completeType.annotations);
-
- if (!completeType.superclass.equals(ClassName.OBJECT)) {
- skeleton.superclass(completeType.superclass);
- }
-
- completeType.methodSpecs.stream()
- .filter(method -> !method.modifiers.contains(PRIVATE) || method.isConstructor())
- .map(this::skeletonMethod)
- .forEach(skeleton::addMethod);
-
- completeType.fieldSpecs.stream()
- .filter(field -> !field.modifiers.contains(PRIVATE))
- .map(this::skeletonField)
- .forEach(skeleton::addField);
-
- completeType.typeSpecs.stream()
- .map(type -> skeletonType(type).build())
- .forEach(skeleton::addType);
-
- return skeleton;
- }
-
- private MethodSpec skeletonMethod(MethodSpec completeMethod) {
- MethodSpec.Builder skeleton =
- completeMethod.isConstructor()
- ? constructorBuilder()
- : methodBuilder(completeMethod.name).returns(completeMethod.returnType);
-
- if (completeMethod.isConstructor()) {
- // Code in Turbine must (for technical reasons in javac) have a valid super() call for
- // constructors, otherwise javac will bark, and Turbine has no way to avoid this. So we retain
- // constructor method bodies if they do exist
- skeleton.addCode(completeMethod.code);
- }
-
- return skeleton
- .addModifiers(completeMethod.modifiers)
- .addTypeVariables(completeMethod.typeVariables)
- .addParameters(completeMethod.parameters)
- .addExceptions(completeMethod.exceptions)
- .varargs(completeMethod.varargs)
- .addAnnotations(completeMethod.annotations)
- .build();
- }
-
- private FieldSpec skeletonField(FieldSpec completeField) {
- return FieldSpec.builder(
- completeField.type,
- completeField.name,
- completeField.modifiers.toArray(new Modifier[0]))
- .addAnnotations(completeField.annotations)
- .build();
- }
-}
diff --git a/javatests/dagger/internal/codegen/HjarTest.java b/javatests/dagger/internal/codegen/HjarTest.java
new file mode 100644
index 000000000..e8f67b725
--- /dev/null
+++ b/javatests/dagger/internal/codegen/HjarTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 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.ImmutableMap;
+import dagger.testing.compile.CompilerTests;
+import dagger.testing.compile.CompilerTests.DaggerCompiler;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests compilation with the {@code experimental_turbine_hjar} flag enabled. */
+@RunWith(JUnit4.class)
+public class HjarTest {
+ /** Returns a {@link DaggerCompiler} with hjar generation enabled. */
+ private static DaggerCompiler daggerCompiler(Source... sources) {
+ return CompilerTests.daggerCompiler(sources)
+ .withProcessingOptions(ImmutableMap.of("experimental_turbine_hjar", ""));
+ }
+
+ @Test
+ public void componentTest() {
+ Source component =
+ CompilerTests.javaSource(
+ "test.MyComponent",
+ "package test;",
+ "",
+ "import dagger.Component;",
+ "",
+ "@Component",
+ "interface MyComponent {",
+ " String getString();",
+ " int getInt();",
+ " void inject(String str);",
+ "}");
+ daggerCompiler(component).compile(subject -> subject.hasErrorCount(0));
+ }
+
+ @Test
+ public void moduleTest() {
+ Source module =
+ CompilerTests.javaSource(
+ "test.MyModule",
+ "package test;",
+ "",
+ "import dagger.Module;",
+ "import dagger.Provides;",
+ "",
+ "@Module",
+ "interface MyModule {",
+ " @Provides static int provideInt() { return 0; }",
+ " @Provides static String provideString() { return null; }",
+ " @Provides static String[] provideStringArray() { return null; }",
+ " @Provides static int[] provideIntArray() { return null; }",
+ " @Provides static boolean provideBoolean() { return false; }",
+ "}");
+ daggerCompiler(module).compile(subject -> subject.hasErrorCount(0));
+ }
+
+ @Test
+ public void producerModuleTest() {
+ Source module =
+ CompilerTests.javaSource(
+ "test.MyModule",
+ "package test;",
+ "",
+ "import com.google.common.util.concurrent.ListenableFuture;",
+ "import dagger.producers.ProducerModule;",
+ "import dagger.producers.Produces;",
+ "",
+ "@ProducerModule",
+ "interface MyModule {",
+ " @Produces static ListenableFuture<String> produceString() { return null; }",
+ "}");
+ daggerCompiler(module).compile(subject -> subject.hasErrorCount(0));
+ }
+}