Skip to content

Commit

Permalink
[Refactor]: Move ProvisionMethods#create()/`InjectionSiteMethods#cr…
Browse files Browse the repository at this point in the history
…eate()` 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: 700035303
  • Loading branch information
bcorso authored and Dagger Team committed Nov 25, 2024
1 parent 2b84e57 commit fdbc63e
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 256 deletions.
43 changes: 43 additions & 0 deletions java/dagger/internal/codegen/binding/SourceFiles.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@
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;
import static javax.lang.model.SourceVersion.isName;

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;
Expand All @@ -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;
Expand Down Expand Up @@ -144,6 +147,23 @@ public ImmutableMap<DependencyRequest, CodeBlock> 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()) {
Expand Down Expand Up @@ -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());
}
Expand Down
114 changes: 106 additions & 8 deletions java/dagger/internal/codegen/writing/FactoryGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<TypeName> getThrownTypes(XExecutableElement executable) {
return executable.getThrownTypes().stream().map(XType::getTypeName).collect(toImmutableList());
}

private AnnotationSpec scopeMetadataAnnotation(ContributionBinding binding) {
Expand Down
Loading

0 comments on commit fdbc63e

Please sign in to comment.