[Ahead-of-time Subcomponents] Support for Optional bindings. This includes
support for replacing private binding methods with public modifiable binding
methods, and wrapping all modifiable bindings not already wrapped by a
modifiable binding method.
This also includes a consolidation of the functional tests.
RELNOTES=n/a
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=211634962
diff --git a/java/dagger/internal/codegen/ComponentBindingExpressions.java b/java/dagger/internal/codegen/ComponentBindingExpressions.java
index 8f4b5b3..beed699 100644
--- a/java/dagger/internal/codegen/ComponentBindingExpressions.java
+++ b/java/dagger/internal/codegen/ComponentBindingExpressions.java
@@ -43,6 +43,7 @@
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
+import dagger.model.BindingKind;
import dagger.model.DependencyRequest;
import dagger.model.Key;
import dagger.model.RequestKind;
@@ -246,18 +247,27 @@
Optional<MethodSpec> getComponentMethod(ComponentMethodDescriptor componentMethod) {
checkArgument(componentMethod.dependencyRequest().isPresent());
DependencyRequest dependencyRequest = componentMethod.dependencyRequest().get();
- MethodSpec.Builder methodBuilder =
+ MethodSpec method =
MethodSpec.overriding(
- componentMethod.methodElement(),
- MoreTypes.asDeclared(graph.componentType().asType()),
- types);
+ componentMethod.methodElement(),
+ MoreTypes.asDeclared(graph.componentType().asType()),
+ types)
+ .addCode(
+ getBindingExpression(dependencyRequest.key(), dependencyRequest.kind())
+ .getComponentMethodImplementation(componentMethod, generatedComponentModel))
+ .build();
- ModifiableBindingType type =
+ ModifiableBindingType modifiableBindingType =
getModifiableBindingType(dependencyRequest.key(), dependencyRequest.kind());
- if (type.isModifiable()) {
+ if (modifiableBindingType.isModifiable()) {
generatedComponentModel.registerModifiableBindingMethod(
- type, dependencyRequest.key(), dependencyRequest.kind(), methodBuilder.build());
- if (!type.hasBaseClassImplementation()) {
+ modifiableBindingType,
+ dependencyRequest.key(),
+ dependencyRequest.kind(),
+ method,
+ newModifiableBindingWillBeFinalized(
+ modifiableBindingType, dependencyRequest.key(), dependencyRequest.kind()));
+ if (!modifiableBindingType.hasBaseClassImplementation()) {
// A component method should not be emitted if it encapsulates a modifiable binding that
// cannot be satisfied by the abstract base class implementation of a subcomponent.
checkState(
@@ -268,39 +278,71 @@
}
}
- return Optional.of(
- methodBuilder
- .addCode(
- getBindingExpression(dependencyRequest.key(), dependencyRequest.kind())
- .getComponentMethodImplementation(componentMethod, generatedComponentModel))
- .build());
+ return Optional.of(method);
}
/**
- * Returns the implementation of a method encapsulating a modifiable binding in a supertype
+ * Returns the implementation of a modifiable binding method originally defined in a supertype
* implementation of this subcomponent. Returns {@link Optional#empty()} when the binding cannot
* or should not be modified by the current binding graph. This is only relevant for ahead-of-time
* subcomponents.
*/
- Optional<MethodSpec> getModifiableBindingMethod(ModifiableBindingMethod modifiableBindingMethod) {
- if (shouldOverrideModifiableBindingMethod(modifiableBindingMethod)) {
- Expression bindingExpression =
- getDependencyExpression(
- modifiableBindingMethod.key(),
- modifiableBindingMethod.kind(),
- generatedComponentModel.name());
- MethodSpec baseMethod = modifiableBindingMethod.baseMethod();
+ Optional<ModifiableBindingMethod> getModifiableBindingMethod(
+ ModifiableBindingMethod modifiableBindingMethod) {
+ if (shouldModifyKnownBinding(modifiableBindingMethod)) {
+ Key key = modifiableBindingMethod.key();
+ RequestKind requestKind = modifiableBindingMethod.kind();
+ MethodSpec baseMethod = modifiableBindingMethod.methodSpec();
return Optional.of(
- MethodSpec.methodBuilder(baseMethod.name)
- .addModifiers(PUBLIC)
- .returns(baseMethod.returnType)
- .addAnnotation(Override.class)
- .addStatement("return $L", bindingExpression.codeBlock())
- .build());
+ ModifiableBindingMethod.implement(
+ modifiableBindingMethod,
+ MethodSpec.methodBuilder(baseMethod.name)
+ .addModifiers(PUBLIC)
+ .returns(baseMethod.returnType)
+ .addAnnotation(Override.class)
+ .addCode(
+ getBindingExpression(key, requestKind)
+ .getModifiableBindingMethodImplementation(
+ modifiableBindingMethod, generatedComponentModel))
+ .build(),
+ knownModifiableBindingWillBeFinalized(modifiableBindingMethod)));
}
return Optional.empty();
}
+ /**
+ * Returns true if a modifiable binding method that was registered in a superclass implementation
+ * of this subcomponent should be marked as "finalized" if it is being overridden by this
+ * subcomponent implementation. "Finalized" means we should not attempt to modify the binding in
+ * any subcomponent subclass. This is only relevant for ahead-of-time subcomponents.
+ */
+ // TODO(user): extract a ModifiableBindingExpressions class? This may need some dependencies
+ // (like the GCM) but could remove some concerns from this class
+ private boolean knownModifiableBindingWillBeFinalized(
+ ModifiableBindingMethod modifiableBindingMethod) {
+ ModifiableBindingType newModifiableBindingType =
+ getModifiableBindingType(modifiableBindingMethod.key(), modifiableBindingMethod.kind());
+ if (!newModifiableBindingType.isModifiable()) {
+ // If a modifiable binding has become non-modifiable it is final by definition.
+ return true;
+ }
+ // All currently supported modifiable types are finalized upon modification.
+ return shouldModifyBinding(
+ newModifiableBindingType, modifiableBindingMethod.key(), modifiableBindingMethod.kind());
+ }
+
+ /**
+ * Returns true if a newly discovered modifiable binding method, once it is defined in this
+ * subcomponent implementation, should be marked as "finalized", meaning we should not attempt to
+ * modify the binding in any subcomponent subclass. This is only relevant for ahead-of-time
+ * subcomponents.
+ */
+ private boolean newModifiableBindingWillBeFinalized(
+ ModifiableBindingType modifiableBindingType, Key key, RequestKind requestKind) {
+ // All currently supported modifiable types are finalized upon modification.
+ return shouldModifyBinding(modifiableBindingType, key, requestKind);
+ }
+
private BindingExpression getBindingExpression(Key key, RequestKind requestKind) {
if (expressions.contains(key, requestKind)) {
return expressions.get(key, requestKind);
@@ -352,13 +394,34 @@
*/
private BindingExpression createModifiableBindingExpression(
ModifiableBindingType type, Key key, RequestKind requestKind) {
+ ResolvedBindings resolvedBindings = graph.resolvedBindings(requestKind, key);
+ Optional<ModifiableBindingMethod> matchingModifiableBindingMethod =
+ generatedComponentModel.getModifiableBindingMethod(key, requestKind);
+ Optional<ComponentMethodDescriptor> matchingComponentMethod =
+ findMatchingComponentMethod(key, requestKind);
switch (type) {
case GENERATED_INSTANCE:
- ResolvedBindings resolvedBindings = graph.resolvedBindings(requestKind, key);
return new GeneratedInstanceBindingExpression(
- generatedComponentModel, resolvedBindings, requestKind);
+ generatedComponentModel,
+ resolvedBindings,
+ requestKind,
+ matchingModifiableBindingMethod,
+ matchingComponentMethod);
case MISSING:
- return new MissingBindingExpression(generatedComponentModel, key, requestKind);
+ return new MissingBindingExpression(
+ generatedComponentModel,
+ key,
+ requestKind,
+ matchingModifiableBindingMethod,
+ matchingComponentMethod);
+ case OPTIONAL:
+ BindingExpression expression = createBindingExpression(resolvedBindings, requestKind);
+ // If the expression hasn't already been registered as a modifiable binding method then wrap
+ // the binding here.
+ if (!generatedComponentModel.getModifiableBindingMethod(key, requestKind).isPresent()) {
+ return wrapInMethod(resolvedBindings, requestKind, expression);
+ }
+ return expression;
default:
throw new IllegalStateException(
String.format(
@@ -393,6 +456,10 @@
if (binding.requiresGeneratedInstance()) {
return ModifiableBindingType.GENERATED_INSTANCE;
}
+
+ if (binding.kind().equals(BindingKind.OPTIONAL)) {
+ return ModifiableBindingType.OPTIONAL;
+ }
} else if (!resolvableBinding(key, requestKind)) {
return ModifiableBindingType.MISSING;
}
@@ -403,11 +470,29 @@
/**
* Returns true if the current binding graph can, and should, modify a binding by overriding a
- * modfiable binding method. This is only relevant for ahead-of-time subcomponents.
+ * modifiable binding method. This is only relevant for ahead-of-time subcomponents.
*/
- private boolean shouldOverrideModifiableBindingMethod(
- ModifiableBindingMethod modifiableBindingMethod) {
- switch (modifiableBindingMethod.type()) {
+ private boolean shouldModifyKnownBinding(ModifiableBindingMethod modifiableBindingMethod) {
+ ModifiableBindingType newModifiableBindingType =
+ getModifiableBindingType(modifiableBindingMethod.key(), modifiableBindingMethod.kind());
+ if (!newModifiableBindingType.equals(modifiableBindingMethod.type())) {
+ // It is possible that a binding can change types, in which case we should always modify the
+ // binding.
+ return true;
+ }
+ return shouldModifyBinding(
+ modifiableBindingMethod.type(),
+ modifiableBindingMethod.key(),
+ modifiableBindingMethod.kind());
+ }
+
+ /**
+ * Returns true if the current binding graph can, and should, modify a binding by overriding a
+ * modifiable binding method. This is only relevant for ahead-of-time subcomponents.
+ */
+ private boolean shouldModifyBinding(
+ ModifiableBindingType modifiableBindingType, Key key, RequestKind requestKind) {
+ switch (modifiableBindingType) {
case GENERATED_INSTANCE:
return !generatedComponentModel.isAbstract();
case MISSING:
@@ -415,12 +500,16 @@
// ancestors satisfy missing bindings of their children with their own missing binding
// methods so that we can minimize the cases where we need to reach into doubly-nested
// descendant component implementations
- return resolvableBinding(modifiableBindingMethod.key(), modifiableBindingMethod.kind());
+ return resolvableBinding(key, requestKind);
+ case OPTIONAL:
+ // Only override optional binding methods if we have a non-empty binding.
+ ResolvedBindings resolvedBindings = graph.resolvedBindings(requestKind, key);
+ return !resolvedBindings.contributionBinding().dependencies().isEmpty();
default:
throw new IllegalStateException(
String.format(
"Overriding modifiable binding method with unsupported ModifiableBindingType [%s].",
- modifiableBindingMethod.type()));
+ modifiableBindingType));
}
}
@@ -764,10 +853,10 @@
/**
* Returns {@code true} if the binding should use the static factory creation strategy.
*
- * In default mode, we always use the static factory creation strategy. In fastInit mode, we
+ * <p>In default mode, we always use the static factory creation strategy. In fastInit mode, we
* prefer to use a SwitchingProvider instead of static factories in order to reduce class loading;
- * however, we allow static factories that can reused across multiple bindings, e.g.
- * {@code MapFactory} or {@code SetFactory}.
+ * however, we allow static factories that can reused across multiple bindings, e.g. {@code
+ * MapFactory} or {@code SetFactory}.
*/
private boolean useStaticFactoryCreation(ContributionBinding binding) {
return !(compilerOptions.experimentalAndroidMode2() || compilerOptions.fastInit())
@@ -791,8 +880,10 @@
/**
* Returns a binding expression that uses a given one as the body of a method that users call. If
- * a component provision method matches it, it will be the method implemented. If not, a new
- * private method will be written.
+ * a component provision method matches it, it will be the method implemented. If it does not
+ * match a component provision method and the binding is modifiable the a new public modifiable
+ * binding method will be written. If the binding doesn't match a component method nor is it
+ * modifiable, then a new private method will be written.
*/
private BindingExpression wrapInMethod(
ResolvedBindings resolvedBindings,
@@ -800,8 +891,24 @@
BindingExpression bindingExpression) {
BindingMethodImplementation methodImplementation =
methodImplementation(resolvedBindings, requestKind, bindingExpression);
+ Optional<ComponentMethodDescriptor> matchingComponentMethod =
+ findMatchingComponentMethod(resolvedBindings.key(), requestKind);
- return findMatchingComponentMethod(resolvedBindings.key(), requestKind)
+ ModifiableBindingType modifiableBindingType =
+ getModifiableBindingType(resolvedBindings.key(), requestKind);
+ if (modifiableBindingType.isModifiable() && !matchingComponentMethod.isPresent()) {
+ return new ModifiableConcreteMethodBindingExpression(
+ resolvedBindings,
+ requestKind,
+ modifiableBindingType,
+ methodImplementation,
+ generatedComponentModel,
+ generatedComponentModel.getModifiableBindingMethod(resolvedBindings.key(), requestKind),
+ newModifiableBindingWillBeFinalized(
+ modifiableBindingType, resolvedBindings.key(), requestKind));
+ }
+
+ return matchingComponentMethod
.<BindingExpression>map(
componentMethod ->
new ComponentMethodBindingExpression(