From 72e395088bec1cd7eace0f255d8307dbf414b83b Mon Sep 17 00:00:00 2001 From: Brad Corso Date: Wed, 16 Oct 2024 12:28:30 -0700 Subject: [PATCH] [Refactor]: Move `ProvisionMethods#create()`/`InjectionSiteMethods#create()` into the generator that uses it. Note: this CL shouldn't cause any changes to the generated code. `FactoryGenerator` is the only class that uses `ProvisionMethods#create()`, and `MembersInjectorGenerator` is the only class that uses `InjectionSiteMethods#create()`. Thus, I'm moving these methods into these classes for better encapsulation. This will also make the XPoet migration easier since I plan to migrate `FactoryGenerator` and `MembersInjectorGenerator` separately. Note that these methods were sharing some logic that now needs to be duplicated, but IMO this creates more readable code since we're no longer entangling the two use cases. For example, now that `ProvisionMethods` and `InjectionSiteMethods` define their own versions of `methodProxy()` method, we no longer need the multiple enums (i.e. `InstanceCastPolicy` and `CheckNotNullPolicy`) as input to these methods. RELNOTES=N/A PiperOrigin-RevId: 686601333 --- .../internal/codegen/binding/SourceFiles.java | 43 +++ .../codegen/writing/FactoryGenerator.java | 114 +++++++- .../codegen/writing/InjectionMethods.java | 258 +----------------- .../writing/MembersInjectorGenerator.java | 103 ++++++- 4 files changed, 262 insertions(+), 256 deletions(-) diff --git a/java/dagger/internal/codegen/binding/SourceFiles.java b/java/dagger/internal/codegen/binding/SourceFiles.java index 350051522f4..d776eb4a9a7 100644 --- a/java/dagger/internal/codegen/binding/SourceFiles.java +++ b/java/dagger/internal/codegen/binding/SourceFiles.java @@ -36,6 +36,7 @@ import static dagger.internal.codegen.model.BindingKind.ASSISTED_INJECTION; import static dagger.internal.codegen.model.BindingKind.INJECTION; import static dagger.internal.codegen.xprocessing.XElements.asExecutable; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames; @@ -43,6 +44,7 @@ import androidx.room.compiler.processing.XExecutableElement; import androidx.room.compiler.processing.XFieldElement; +import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -58,6 +60,7 @@ import com.squareup.javapoet.TypeVariableName; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.SetType; +import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.model.RequestKind; @@ -144,6 +147,23 @@ public ImmutableMap frameworkFieldUsages( dep -> frameworkTypeUsageStatement(CodeBlock.of("$N", fields.get(dep)), dep.kind())); } + public static String generatedProxyMethodName(ContributionBinding binding) { + switch (binding.kind()) { + case INJECTION: + case ASSISTED_INJECTION: + return "newInstance"; + case PROVISION: + XMethodElement method = asMethod(binding.bindingElement().get()); + String simpleName = getSimpleName(method); + // If the simple name is already defined in the factory, prepend "proxy" to the name. + return simpleName.contentEquals("get") || simpleName.contentEquals("create") + ? "proxy" + LOWER_CAMEL.to(UPPER_CAMEL, simpleName) + : simpleName; + default: + throw new AssertionError("Unexpected binding kind: " + binding); + } + } + /** Returns the generated factory or members injector name for a binding. */ public static ClassName generatedClassNameForBinding(Binding binding) { switch (binding.kind()) { @@ -206,6 +226,29 @@ public static String memberInjectedFieldSignatureForVariable(XFieldElement field return field.getEnclosingElement().getClassName().canonicalName() + "." + getSimpleName(field); } + /* + * TODO(ronshapiro): this isn't perfect, as collisions could still exist. Some examples: + * + * - @Inject void members() {} will generate a method that conflicts with the instance + * method `injectMembers(T)` + * - Adding the index could conflict with another member: + * @Inject void a(Object o) {} + * @Inject void a(String s) {} + * @Inject void a1(String s) {} + * + * Here, Method a(String) will add the suffix "1", which will conflict with the method + * generated for a1(String) + * - Members named "members" or "methods" could also conflict with the {@code static} injection + * method. + */ + public static String membersInjectorMethodName(InjectionSite injectionSite) { + int index = injectionSite.indexAmongAtInjectMembersWithSameSimpleName(); + String indexString = index == 0 ? "" : String.valueOf(index + 1); + return "inject" + + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(injectionSite.element())) + + indexString; + } + public static String classFileName(ClassName className) { return CLASS_FILE_NAME_JOINER.join(className.simpleNames()); } diff --git a/java/dagger/internal/codegen/writing/FactoryGenerator.java b/java/dagger/internal/codegen/writing/FactoryGenerator.java index 7fee1b394b6..3b78b969c90 100644 --- a/java/dagger/internal/codegen/writing/FactoryGenerator.java +++ b/java/dagger/internal/codegen/writing/FactoryGenerator.java @@ -24,6 +24,7 @@ import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames; import static dagger.internal.codegen.binding.SourceFiles.generateBindingFieldsForDependencies; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; +import static dagger.internal.codegen.binding.SourceFiles.generatedProxyMethodName; import static dagger.internal.codegen.binding.SourceFiles.parameterizedGeneratedTypeNameForBinding; import static dagger.internal.codegen.extension.DaggerStreams.presentValues; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; @@ -36,15 +37,26 @@ import static dagger.internal.codegen.model.BindingKind.INJECTION; import static dagger.internal.codegen.model.BindingKind.PROVISION; import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation; +import static dagger.internal.codegen.writing.InjectionMethods.copyParameter; +import static dagger.internal.codegen.writing.InjectionMethods.copyParameters; +import static dagger.internal.codegen.xprocessing.XElements.asConstructor; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; +import androidx.room.compiler.processing.XConstructorElement; import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableElement; import androidx.room.compiler.processing.XExecutableParameterElement; import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XMethodElement; 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.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -56,6 +68,7 @@ import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import dagger.internal.Preconditions; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.AssistedInjectionBinding; @@ -73,6 +86,7 @@ import dagger.internal.codegen.model.Scope; import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod; import dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod; +import dagger.internal.codegen.xprocessing.Nullability; import java.util.Optional; import java.util.stream.Stream; import javax.inject.Inject; @@ -136,7 +150,7 @@ private TypeSpec.Builder factoryBuilder(ContributionBinding binding) { return factoryBuilder .addMethod(getMethod(binding, factoryFields)) .addMethod(staticCreateMethod(binding, factoryFields)) - .addMethod(staticProvisionMethod(binding)); + .addMethod(staticProxyMethod(binding)); } // private static final class InstanceHolder { @@ -298,17 +312,101 @@ private MethodSpec getMethod(ContributionBinding binding, FactoryFields factoryF return getMethod.build(); } - // Example 1: Provision binding - // public static Foo provideFoo(FooModule module, Bar bar, Baz baz) { - // return Preconditions.checkNotNullFromProvides(module.provideFoo(bar, baz)); - // } + private MethodSpec staticProxyMethod(ContributionBinding binding) { + switch (binding.kind()) { + case INJECTION: + case ASSISTED_INJECTION: + return staticProxyMethodForInjection(binding); + case PROVISION: + return staticProxyMethodForProvision((ProvisionBinding) binding); + default: + throw new AssertionError("Unexpected binding kind: " + binding); + } + } + + // Example: // - // Example 2: Injection binding // public static Foo newInstance(Bar bar, Baz baz) { // return new Foo(bar, baz); // } - private MethodSpec staticProvisionMethod(ContributionBinding binding) { - return ProvisionMethod.create(binding, compilerOptions); + private static MethodSpec staticProxyMethodForInjection(ContributionBinding binding) { + XConstructorElement constructor = asConstructor(binding.bindingElement().get()); + XTypeElement enclosingType = constructor.getEnclosingElement(); + MethodSpec.Builder builder = + methodBuilder(generatedProxyMethodName(binding)) + .addModifiers(PUBLIC, STATIC) + .varargs(constructor.isVarArgs()) + .returns(enclosingType.getType().getTypeName()) + .addTypeVariables(typeVariableNames(enclosingType)) + .addExceptions(getThrownTypes(constructor)); + CodeBlock arguments = copyParameters(builder, new UniqueNameSet(), constructor.getParameters()); + return builder + .addStatement("return new $T($L)", enclosingType.getType().getTypeName(), arguments) + .build(); + } + + // Example: + // + // public static Foo provideFoo(FooModule module, Bar bar, Baz baz) { + // return Preconditions.checkNotNullFromProvides(module.provideFoo(bar, baz)); + // } + private MethodSpec staticProxyMethodForProvision(ProvisionBinding binding) { + XMethodElement method = asMethod(binding.bindingElement().get()); + MethodSpec.Builder builder = + methodBuilder(generatedProxyMethodName(binding)) + .addModifiers(PUBLIC, STATIC) + .varargs(method.isVarArgs()) + .addExceptions(getThrownTypes(method)); + + XTypeElement enclosingType = asTypeElement(method.getEnclosingElement()); + UniqueNameSet parameterNameSet = new UniqueNameSet(); + CodeBlock module; + if (method.isStatic() || enclosingType.isCompanionObject()) { + module = CodeBlock.of("$T", enclosingType.getClassName()); + } else if (enclosingType.isKotlinObject()) { + // Call through the singleton instance. + // See: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-methods + module = CodeBlock.of("$T.INSTANCE", enclosingType.getClassName()); + } else { + builder.addTypeVariables(typeVariableNames(enclosingType)); + module = copyInstance(builder, parameterNameSet, enclosingType.getType()); + } + CodeBlock arguments = copyParameters(builder, parameterNameSet, method.getParameters()); + CodeBlock invocation = CodeBlock.of("$L.$L($L)", module, method.getJvmName(), arguments); + + Nullability nullability = Nullability.of(method); + nullability + .nonTypeUseNullableAnnotations() + .forEach(builder::addAnnotation); + return builder + .returns( + method.getReturnType().getTypeName() + .annotated( + nullability.typeUseNullableAnnotations().stream() + .map(annotation -> AnnotationSpec.builder(annotation).build()) + .collect(toImmutableList()))) + .addStatement("return $L", maybeWrapInCheckForNull(binding, invocation)) + .build(); + } + + private CodeBlock maybeWrapInCheckForNull(ProvisionBinding binding, CodeBlock codeBlock) { + return binding.shouldCheckForNull(compilerOptions) + ? CodeBlock.of("$T.checkNotNullFromProvides($L)", Preconditions.class, codeBlock) + : codeBlock; + } + + private static CodeBlock copyInstance( + MethodSpec.Builder methodBuilder, UniqueNameSet parameterNameSet, XType type) { + return copyParameter( + methodBuilder, + type, + parameterNameSet.getUniqueName("instance"), + /* useObject= */ false, + Nullability.NOT_NULLABLE); + } + + private static ImmutableList getThrownTypes(XExecutableElement executable) { + return executable.getThrownTypes().stream().map(XType::getTypeName).collect(toImmutableList()); } private AnnotationSpec scopeMetadataAnnotation(ContributionBinding binding) { diff --git a/java/dagger/internal/codegen/writing/InjectionMethods.java b/java/dagger/internal/codegen/writing/InjectionMethods.java index f1d5e32c4b1..721cc8e983d 100644 --- a/java/dagger/internal/codegen/writing/InjectionMethods.java +++ b/java/dagger/internal/codegen/writing/InjectionMethods.java @@ -16,46 +16,27 @@ package dagger.internal.codegen.writing; -import static androidx.room.compiler.processing.XElementKt.isConstructor; -import static androidx.room.compiler.processing.XElementKt.isMethod; import static androidx.room.compiler.processing.XElementKt.isMethodParameter; -import static androidx.room.compiler.processing.XTypeKt.isVoid; -import static com.google.common.base.CaseFormat.LOWER_CAMEL; -import static com.google.common.base.CaseFormat.UPPER_CAMEL; -import static com.google.common.base.Preconditions.checkArgument; -import static com.squareup.javapoet.MethodSpec.methodBuilder; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedParameter; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; -import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable; +import static dagger.internal.codegen.binding.SourceFiles.generatedProxyMethodName; +import static dagger.internal.codegen.binding.SourceFiles.membersInjectorMethodName; import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; import static dagger.internal.codegen.javapoet.CodeBlocks.toConcatenatedCodeBlock; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; -import static dagger.internal.codegen.javapoet.TypeNames.rawTypeName; import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible; import static dagger.internal.codegen.langmodel.Accessibility.isRawTypePubliclyAccessible; -import static dagger.internal.codegen.xprocessing.XElements.asConstructor; import static dagger.internal.codegen.xprocessing.XElements.asExecutable; -import static dagger.internal.codegen.xprocessing.XElements.asField; -import static dagger.internal.codegen.xprocessing.XElements.asMethod; import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter; -import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; -import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames; import static dagger.internal.codegen.xprocessing.XTypes.erasedTypeName; -import static javax.lang.model.element.Modifier.PUBLIC; -import static javax.lang.model.element.Modifier.STATIC; -import androidx.room.compiler.processing.XAnnotation; -import androidx.room.compiler.processing.XConstructorElement; import androidx.room.compiler.processing.XExecutableElement; import androidx.room.compiler.processing.XExecutableParameterElement; -import androidx.room.compiler.processing.XFieldElement; -import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XType; -import androidx.room.compiler.processing.XTypeElement; import androidx.room.compiler.processing.XVariableElement; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -66,7 +47,6 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; -import dagger.internal.Preconditions; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.AssistedInjectionBinding; import dagger.internal.codegen.binding.ContributionBinding; @@ -74,12 +54,8 @@ import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.internal.codegen.extension.DaggerCollectors; -import dagger.internal.codegen.javapoet.TypeNames; -import dagger.internal.codegen.model.DaggerAnnotation; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.xprocessing.Nullability; -import dagger.internal.codegen.xprocessing.XAnnotations; import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -113,30 +89,6 @@ final class InjectionMethods { * */ static final class ProvisionMethod { - // These names are already defined in factories and shouldn't be used for the proxy method name. - private static final ImmutableSet BANNED_PROXY_NAMES = ImmutableSet.of("get", "create"); - - /** - * Returns a method that invokes the binding's constructor and injects the instance's members. - */ - static MethodSpec create(ContributionBinding binding, CompilerOptions compilerOptions) { - XExecutableElement executableElement = asExecutable(binding.bindingElement().get()); - if (isConstructor(executableElement)) { - return constructorProxy(asConstructor(executableElement)); - } else if (isMethod(executableElement)) { - XMethodElement method = asMethod(executableElement); - String methodName = - BANNED_PROXY_NAMES.contains(getSimpleName(method)) - ? "proxy" + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(method)) - : getSimpleName(method); - return methodProxy( - method, - methodName, - InstanceCastPolicy.IGNORE, - CheckNotNullPolicy.get(binding, compilerOptions)); - } - throw new AssertionError(executableElement); - } /** * Invokes the injection method for {@code binding}, with the dependencies transformed with the @@ -155,8 +107,8 @@ static CodeBlock invoke( .forEach(arguments::add); ClassName enclosingClass = generatedClassNameForBinding(binding); - MethodSpec methodSpec = create(binding, compilerOptions); - return invokeMethod(methodSpec, arguments.build(), enclosingClass, requestingClass); + String methodName = generatedProxyMethodName(binding); + return invokeMethod(methodName, arguments.build(), enclosingClass, requestingClass); } static ImmutableList invokeArguments( @@ -199,24 +151,6 @@ private static ImmutableSet provisionDependencies( throw new AssertionError("Unexpected binding kind: " + binding.kind()); } } - - private static MethodSpec constructorProxy(XConstructorElement constructor) { - XTypeElement enclosingType = constructor.getEnclosingElement(); - MethodSpec.Builder builder = - methodBuilder("newInstance") - .addModifiers(PUBLIC, STATIC) - .varargs(constructor.isVarArgs()) - .returns(enclosingType.getType().getTypeName()) - .addTypeVariables(typeVariableNames(enclosingType)); - - copyThrows(builder, constructor); - - CodeBlock arguments = - copyParameters(builder, new UniqueNameSet(), constructor.getParameters()); - return builder - .addStatement("return new $T($L)", enclosingType.getType().getTypeName(), arguments) - .build(); - } } /** @@ -237,35 +171,6 @@ private static MethodSpec constructorProxy(XConstructorElement constructor) { * */ static final class InjectionSiteMethod { - /** - * When a type has an inaccessible member from a supertype (e.g. an @Inject field in a parent - * that's in a different package), a method in the supertype's package must be generated to give - * the subclass's members injector a way to inject it. Each potentially inaccessible member - * receives its own method, as the subclass may need to inject them in a different order from - * the parent class. - */ - static MethodSpec create(InjectionSite injectionSite) { - String methodName = methodName(injectionSite); - switch (injectionSite.kind()) { - case METHOD: - return methodProxy( - asMethod(injectionSite.element()), - methodName, - InstanceCastPolicy.CAST_IF_NOT_PUBLIC, - CheckNotNullPolicy.IGNORE); - case FIELD: - Optional qualifier = - injectionSite.dependencies().stream() - // methods for fields have a single dependency request - .collect(DaggerCollectors.onlyElement()) - .key() - .qualifier() - .map(DaggerAnnotation::xprocessing); - return fieldProxy(asField(injectionSite.element()), methodName, qualifier); - } - throw new AssertionError(injectionSite); - } - /** * Invokes each of the injection methods for {@code injectionSites}, with the dependencies * transformed using the {@code dependencyUsage} function. @@ -318,148 +223,23 @@ private static CodeBlock invoke( .collect(toImmutableList())) .build(); ClassName enclosingClass = membersInjectorNameForType(injectionSite.enclosingTypeElement()); - MethodSpec methodSpec = create(injectionSite); - return invokeMethod(methodSpec, arguments, enclosingClass, generatedTypeName); - } - - /* - * TODO(ronshapiro): this isn't perfect, as collisions could still exist. Some examples: - * - * - @Inject void members() {} will generate a method that conflicts with the instance - * method `injectMembers(T)` - * - Adding the index could conflict with another member: - * @Inject void a(Object o) {} - * @Inject void a(String s) {} - * @Inject void a1(String s) {} - * - * Here, Method a(String) will add the suffix "1", which will conflict with the method - * generated for a1(String) - * - Members named "members" or "methods" could also conflict with the {@code static} injection - * method. - */ - private static String methodName(InjectionSite injectionSite) { - int index = injectionSite.indexAmongAtInjectMembersWithSameSimpleName(); - String indexString = index == 0 ? "" : String.valueOf(index + 1); - return "inject" - + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(injectionSite.element())) - + indexString; + String methodName = membersInjectorMethodName(injectionSite); + return invokeMethod(methodName, arguments, enclosingClass, generatedTypeName); } } - private enum InstanceCastPolicy { - CAST_IF_NOT_PUBLIC, IGNORE; - - boolean useObjectType(XType instanceType) { - return this == CAST_IF_NOT_PUBLIC && !isRawTypePubliclyAccessible(instanceType); - } - } - - private enum CheckNotNullPolicy { - IGNORE, CHECK_FOR_NULL; - - CodeBlock checkForNull(CodeBlock maybeNull) { - return this.equals(IGNORE) - ? maybeNull - : CodeBlock.of("$T.checkNotNullFromProvides($L)", Preconditions.class, maybeNull); - } - - static CheckNotNullPolicy get(ContributionBinding binding, CompilerOptions compilerOptions) { - return binding.shouldCheckForNull(compilerOptions) ? CHECK_FOR_NULL : IGNORE; - } - } - - private static MethodSpec methodProxy( - XMethodElement method, - String methodName, - InstanceCastPolicy instanceCastPolicy, - CheckNotNullPolicy checkNotNullPolicy) { - XTypeElement enclosingType = asTypeElement(method.getEnclosingElement()); - - MethodSpec.Builder builder = - methodBuilder(methodName) - .addModifiers(PUBLIC, STATIC) - .varargs(method.isVarArgs()) - .addTypeVariables(method.getExecutableType().getTypeVariableNames()); - - UniqueNameSet parameterNameSet = new UniqueNameSet(); - CodeBlock instance; - if (method.isStatic() || enclosingType.isCompanionObject()) { - instance = CodeBlock.of("$T", rawTypeName(enclosingType.getType().getTypeName())); - } else if (enclosingType.isKotlinObject()) { - // Call through the singleton instance. - // See: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-methods - instance = CodeBlock.of("$T.INSTANCE", rawTypeName(enclosingType.getType().getTypeName())); - } else { - builder.addTypeVariables(typeVariableNames(enclosingType)); - boolean useObject = instanceCastPolicy.useObjectType(enclosingType.getType()); - instance = copyInstance(builder, parameterNameSet, enclosingType.getType(), useObject); - } - CodeBlock arguments = copyParameters(builder, parameterNameSet, method.getParameters()); - CodeBlock invocation = - checkNotNullPolicy.checkForNull( - CodeBlock.of("$L.$L($L)", instance, method.getJvmName(), arguments)); - - copyThrows(builder, method); - - if (isVoid(method.getReturnType())) { - return builder.addStatement("$L", invocation).build(); - } else { - Nullability nullability = Nullability.of(method); - nullability - .nonTypeUseNullableAnnotations() - .forEach(builder::addAnnotation); - return builder - .returns( - method.getReturnType().getTypeName() - .annotated( - nullability.typeUseNullableAnnotations().stream() - .map(annotation -> AnnotationSpec.builder(annotation).build()) - .collect(toImmutableList()))) - .addStatement("return $L", invocation) - .build(); - } - } - - private static MethodSpec fieldProxy( - XFieldElement field, String methodName, Optional qualifier) { - XTypeElement enclosingType = asTypeElement(field.getEnclosingElement()); - - MethodSpec.Builder builder = - methodBuilder(methodName) - .addModifiers(PUBLIC, STATIC) - .addAnnotation( - AnnotationSpec.builder(TypeNames.INJECTED_FIELD_SIGNATURE) - .addMember("value", "$S", memberInjectedFieldSignatureForVariable(field)) - .build()) - .addTypeVariables(typeVariableNames(enclosingType)); - - qualifier.map(XAnnotations::getAnnotationSpec).ifPresent(builder::addAnnotation); - - boolean useObject = !isRawTypePubliclyAccessible(enclosingType.getType()); - UniqueNameSet parameterNameSet = new UniqueNameSet(); - CodeBlock instance = - copyInstance(builder, parameterNameSet, enclosingType.getType(), useObject); - CodeBlock argument = copyParameters(builder, parameterNameSet, ImmutableList.of(field)); - return builder.addStatement("$L.$L = $L", instance, getSimpleName(field), argument).build(); - } - private static CodeBlock invokeMethod( - MethodSpec methodSpec, + String methodName, ImmutableList parameters, ClassName enclosingClass, ClassName requestingClass) { - checkArgument(methodSpec.parameters.size() == parameters.size()); CodeBlock parameterBlock = makeParametersCodeBlock(parameters); return enclosingClass.equals(requestingClass) - ? CodeBlock.of("$L($L)", methodSpec.name, parameterBlock) - : CodeBlock.of("$T.$L($L)", enclosingClass, methodSpec.name, parameterBlock); - } - - private static void copyThrows(MethodSpec.Builder methodBuilder, XExecutableElement method) { - method.getThrownTypes().stream().map(XType::getTypeName).forEach(methodBuilder::addException); + ? CodeBlock.of("$L($L)", methodName, parameterBlock) + : CodeBlock.of("$T.$L($L)", enclosingClass, methodName, parameterBlock); } - private static CodeBlock copyParameters( + static CodeBlock copyParameters( MethodSpec.Builder methodBuilder, UniqueNameSet parameterNameSet, List parameters) { @@ -478,7 +258,7 @@ private static CodeBlock copyParameters( .collect(toParametersCodeBlock()); } - private static CodeBlock copyParameter( + static CodeBlock copyParameter( MethodSpec.Builder methodBuilder, XType type, String name, @@ -497,20 +277,4 @@ private static CodeBlock copyParameter( .build()); return useObject ? CodeBlock.of("($T) $L", type.getTypeName(), name) : CodeBlock.of("$L", name); } - - private static CodeBlock copyInstance( - MethodSpec.Builder methodBuilder, - UniqueNameSet parameterNameSet, - XType type, - boolean useObject) { - CodeBlock instance = - copyParameter( - methodBuilder, - type, - parameterNameSet.getUniqueName("instance"), - useObject, - Nullability.NOT_NULLABLE); - // If we had to cast the instance add an extra parenthesis incase we're calling a method on it. - return useObject ? CodeBlock.of("($L)", instance) : instance; - } } diff --git a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java index d019ba43f8d..81665ae4c12 100644 --- a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java +++ b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java @@ -17,11 +17,14 @@ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.getOnlyElement; 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.binding.SourceFiles.bindingTypeElementTypeVariableNames; import static dagger.internal.codegen.binding.SourceFiles.generateBindingFieldsForDependencies; +import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable; +import static dagger.internal.codegen.binding.SourceFiles.membersInjectorMethodName; import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; import static dagger.internal.codegen.binding.SourceFiles.parameterizedGeneratedTypeNameForBinding; import static dagger.internal.codegen.extension.DaggerStreams.presentValues; @@ -32,17 +35,30 @@ import static dagger.internal.codegen.javapoet.CodeBlocks.toConcatenatedCodeBlock; import static dagger.internal.codegen.javapoet.TypeNames.membersInjectorOf; import static dagger.internal.codegen.javapoet.TypeNames.rawTypeName; +import static dagger.internal.codegen.langmodel.Accessibility.isRawTypePubliclyAccessible; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation; +import static dagger.internal.codegen.writing.InjectionMethods.copyParameter; +import static dagger.internal.codegen.writing.InjectionMethods.copyParameters; +import static dagger.internal.codegen.xprocessing.XElements.asField; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; +import androidx.room.compiler.processing.XAnnotation; import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XFieldElement; import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XMethodElement; 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.ImmutableMap; import com.squareup.javapoet.AnnotationSpec; @@ -58,12 +74,16 @@ import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.MembersInjectionBinding; +import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; import dagger.internal.codegen.binding.SourceFiles; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.DaggerAnnotation; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.model.Key; import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod; +import dagger.internal.codegen.xprocessing.Nullability; +import dagger.internal.codegen.xprocessing.XAnnotations; +import java.util.Optional; import javax.inject.Inject; /** @@ -112,7 +132,7 @@ public ImmutableList topLevelTypes(MembersInjectionBinding bin binding.injectionSites().stream() .filter( site -> site.enclosingTypeElement().equals(binding.membersInjectedType())) - .map(InjectionSiteMethod::create) + .map(MembersInjectorGenerator::membersInjectionMethod) .collect(toImmutableList())); gwtIncompatibleAnnotation(binding).ifPresent(injectorTypeBuilder::addAnnotation); @@ -120,6 +140,87 @@ public ImmutableList topLevelTypes(MembersInjectionBinding bin return ImmutableList.of(injectorTypeBuilder); } + private static MethodSpec membersInjectionMethod(InjectionSite injectionSite) { + String methodName = membersInjectorMethodName(injectionSite); + switch (injectionSite.kind()) { + case METHOD: + return methodInjectionMethod(asMethod(injectionSite.element()), methodName); + case FIELD: + Optional qualifier = + // methods for fields have a single dependency request + getOnlyElement(injectionSite.dependencies()) + .key() + .qualifier() + .map(DaggerAnnotation::xprocessing); + return fieldInjectionMethod(asField(injectionSite.element()), methodName, qualifier); + } + throw new AssertionError(injectionSite); + } + + // Example: + // + // public static void injectMethod(Instance instance, Foo foo, Bar bar) { + // instance.injectMethod(foo, bar); + // } + private static MethodSpec methodInjectionMethod(XMethodElement method, String methodName) { + XTypeElement enclosingType = asTypeElement(method.getEnclosingElement()); + MethodSpec.Builder builder = + methodBuilder(methodName) + .addModifiers(PUBLIC, STATIC) + .varargs(method.isVarArgs()) + .addTypeVariables(typeVariableNames(enclosingType)) + .addExceptions(getThrownTypes(method)); + + UniqueNameSet parameterNameSet = new UniqueNameSet(); + CodeBlock instance = copyInstance(builder, parameterNameSet, enclosingType.getType()); + CodeBlock arguments = copyParameters(builder, parameterNameSet, method.getParameters()); + return builder.addStatement("$L.$L($L)", instance, method.getJvmName(), arguments).build(); + } + + // Example: + // + // public static void injectFoo(Instance instance, Foo foo) { + // instance.foo = foo; + // } + private static MethodSpec fieldInjectionMethod( + XFieldElement field, String methodName, Optional qualifier) { + XTypeElement enclosingType = asTypeElement(field.getEnclosingElement()); + + MethodSpec.Builder builder = + methodBuilder(methodName) + .addModifiers(PUBLIC, STATIC) + .addAnnotation( + AnnotationSpec.builder(TypeNames.INJECTED_FIELD_SIGNATURE) + .addMember("value", "$S", memberInjectedFieldSignatureForVariable(field)) + .build()) + .addTypeVariables(typeVariableNames(enclosingType)); + + qualifier.map(XAnnotations::getAnnotationSpec).ifPresent(builder::addAnnotation); + + UniqueNameSet parameterNameSet = new UniqueNameSet(); + CodeBlock instance = copyInstance(builder, parameterNameSet, enclosingType.getType()); + CodeBlock argument = copyParameters(builder, parameterNameSet, ImmutableList.of(field)); + return builder.addStatement("$L.$L = $L", instance, getSimpleName(field), argument).build(); + } + + private static ImmutableList getThrownTypes(XExecutableElement executable) { + return executable.getThrownTypes().stream().map(XType::getTypeName).collect(toImmutableList()); + } + + private static CodeBlock copyInstance( + MethodSpec.Builder methodBuilder, UniqueNameSet parameterNameSet, XType type) { + boolean useObject = !isRawTypePubliclyAccessible(type); + CodeBlock instance = + copyParameter( + methodBuilder, + type, + parameterNameSet.getUniqueName("instance"), + useObject, + Nullability.NOT_NULLABLE); + // If we had to cast the instance add an extra parenthesis incase we're calling a method on it. + return useObject ? CodeBlock.of("($L)", instance) : instance; + } + // MyClass( // Provider dep1Provider, // Provider dep2Provider,