All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.internal.codegen.binding.BindingGraphFactory Maven / Gradle / Ivy

There is a newer version: 2.9
Show newest version
/*
 * 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.binding;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.not;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType;
import static dagger.internal.codegen.binding.LegacyBindingGraphFactory.useLegacyBindingGraphFactory;
import static dagger.internal.codegen.extension.DaggerCollectors.onlyElement;
import static dagger.internal.codegen.extension.DaggerGraphs.unreachableNodes;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.model.BindingKind.ASSISTED_INJECTION;
import static dagger.internal.codegen.model.BindingKind.DELEGATE;
import static dagger.internal.codegen.model.BindingKind.INJECTION;
import static dagger.internal.codegen.model.BindingKind.OPTIONAL;
import static dagger.internal.codegen.model.BindingKind.SUBCOMPONENT_CREATOR;
import static dagger.internal.codegen.model.RequestKind.MEMBERS_INJECTION;
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf;

import androidx.room.compiler.processing.XTypeElement;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.graph.ImmutableNetwork;
import com.google.common.graph.MutableNetwork;
import com.google.common.graph.NetworkBuilder;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import dagger.internal.codegen.base.Keys;
import dagger.internal.codegen.base.MapType;
import dagger.internal.codegen.base.SetType;
import dagger.internal.codegen.base.TarjanSCCs;
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.model.BindingGraph.ComponentNode;
import dagger.internal.codegen.model.BindingGraph.Edge;
import dagger.internal.codegen.model.BindingGraph.MissingBinding;
import dagger.internal.codegen.model.BindingGraph.Node;
import dagger.internal.codegen.model.BindingKind;
import dagger.internal.codegen.model.ComponentPath;
import dagger.internal.codegen.model.DaggerTypeElement;
import dagger.internal.codegen.model.DependencyRequest;
import dagger.internal.codegen.model.Key;
import dagger.internal.codegen.model.Scope;
import dagger.internal.codegen.xprocessing.XTypeNames;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.tools.Diagnostic;

/** A factory for {@link BindingGraph} objects. */
public final class BindingGraphFactory {
  private final LegacyBindingGraphFactory legacyBindingGraphFactory;
  private final InjectBindingRegistry injectBindingRegistry;
  private final KeyFactory keyFactory;
  private final BindingFactory bindingFactory;
  private final BindingNode.Factory bindingNodeFactory;
  private final ComponentDeclarations.Factory componentDeclarationsFactory;
  private final CompilerOptions compilerOptions;

  @Inject
  BindingGraphFactory(
      LegacyBindingGraphFactory legacyBindingGraphFactory,
      InjectBindingRegistry injectBindingRegistry,
      KeyFactory keyFactory,
      BindingFactory bindingFactory,
      BindingNode.Factory bindingNodeFactory,
      ComponentDeclarations.Factory componentDeclarationsFactory,
      CompilerOptions compilerOptions) {
    this.legacyBindingGraphFactory = legacyBindingGraphFactory;
    this.injectBindingRegistry = injectBindingRegistry;
    this.keyFactory = keyFactory;
    this.bindingFactory = bindingFactory;
    this.bindingNodeFactory = bindingNodeFactory;
    this.componentDeclarationsFactory = componentDeclarationsFactory;
    this.compilerOptions = compilerOptions;
  }

  /**
   * Creates a binding graph for a component.
   *
   * @param createFullBindingGraph if {@code true}, the binding graph will include all bindings;
   *     otherwise it will include only bindings reachable from at least one entry point
   */
  public BindingGraph create(
      ComponentDescriptor componentDescriptor, boolean createFullBindingGraph) {
    return useLegacyBindingGraphFactory(compilerOptions, componentDescriptor)
        ? legacyBindingGraphFactory.create(componentDescriptor, createFullBindingGraph)
        : createBindingGraph(componentDescriptor, createFullBindingGraph);
  }

  private BindingGraph createBindingGraph(
      ComponentDescriptor componentDescriptor, boolean createFullBindingGraph) {
    Resolver resolver = new Resolver(componentDescriptor);
    resolver.resolve(createFullBindingGraph);

    MutableNetwork network = resolver.network;
    if (!createFullBindingGraph) {
      unreachableNodes(network.asGraph(), resolver.componentNode).forEach(network::removeNode);
    }

    network = BindingGraphTransformations.withFixedBindingTypes(network);
    return BindingGraph.create(
        ImmutableNetwork.copyOf(network),
        createFullBindingGraph);
  }

  private final class Resolver {
    final ComponentPath componentPath;
    final Optional parentResolver;
    final ComponentNode componentNode;
    final ComponentDescriptor componentDescriptor;
    final ComponentDeclarations declarations;
    final MutableNetwork network;
    final Map resolvedContributionBindings = new LinkedHashMap<>();
    final Map resolvedMembersInjectionBindings = new LinkedHashMap<>();
    final RequiresResolutionChecker requiresResolutionChecker = new RequiresResolutionChecker();
    final Queue subcomponentsToResolve = new ArrayDeque<>();

    Resolver(ComponentDescriptor componentDescriptor) {
      this(Optional.empty(), componentDescriptor);
    }

    Resolver(Resolver parentResolver, ComponentDescriptor componentDescriptor) {
      this(Optional.of(parentResolver), componentDescriptor);
    }

    private Resolver(Optional parentResolver, ComponentDescriptor componentDescriptor) {
      this.parentResolver = parentResolver;
      this.componentDescriptor = checkNotNull(componentDescriptor);
      DaggerTypeElement componentType = DaggerTypeElement.from(componentDescriptor.typeElement());
      componentPath =
          parentResolver.isPresent()
              ? parentResolver.get().componentPath.childPath(componentType)
              : ComponentPath.create(ImmutableList.of(componentType));
      this.componentNode = ComponentNodeImpl.create(componentPath, componentDescriptor);
      this.network =
          parentResolver.isPresent()
              ? parentResolver.get().network
              : NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build();
      declarations =
          componentDeclarationsFactory.create(
              parentResolver.map(parent -> parent.componentDescriptor),
              componentDescriptor);
      subcomponentsToResolve.addAll(
          componentDescriptor.childComponentsDeclaredByFactoryMethods().values());
      subcomponentsToResolve.addAll(
          componentDescriptor.childComponentsDeclaredByBuilderEntryPoints().values());
    }

    void resolve(boolean createFullBindingGraph) {
      addNode(componentNode);

      componentDescriptor.entryPointMethods().stream()
          .map(method -> method.dependencyRequest().get())
          .forEach(
              entryPoint -> {
                ResolvedBindings resolvedBindings =
                    entryPoint.kind().equals(MEMBERS_INJECTION)
                        ? resolveMembersInjectionKey(entryPoint.key())
                        : resolveContributionKey(entryPoint.key());
                addDependencyEdges(componentNode, resolvedBindings, entryPoint);
              });

      if (createFullBindingGraph) {
        // Resolve the keys for all bindings in all modules, stripping any multibinding contribution
        // identifier so that the multibinding itself is resolved.
        declarations.allDeclarations().stream()
            // TODO(b/349155899): Consider resolving all declarations in full binding graph mode,
            // not just those from modules.
            .filter(declaration -> declaration.contributingModule().isPresent())
            // @BindsOptionalOf bindings are keyed by the unwrapped type so wrap it in Optional to
            // resolve the optional type instead.
            .map(
                declaration ->
                    declaration instanceof OptionalBindingDeclaration
                        ? keyFactory.optionalOf(declaration.key())
                        : declaration.key())
            .map(Key::withoutMultibindingContributionIdentifier)
            .forEach(this::resolveContributionKey);
      }

      // Resolve all bindings for subcomponents, creating subgraphs for all subcomponents that have
      // been detected during binding resolution. If a binding for a subcomponent is never resolved,
      // no BindingGraph will be created for it and no implementation will be generated. This is
      // done in a queue since resolving one subcomponent might resolve a key for a subcomponent
      // from a parent graph. This is done until no more new subcomponents are resolved.
      Set resolvedSubcomponents = new HashSet<>();
      for (ComponentDescriptor subcomponent : Iterables.consumingIterable(subcomponentsToResolve)) {
        if (resolvedSubcomponents.add(subcomponent)) {
          Resolver subcomponentResolver = new Resolver(this, subcomponent);
          addChildFactoryMethodEdge(subcomponentResolver);
          subcomponentResolver.resolve(createFullBindingGraph);
        }
      }
    }

    /**
     * Returns the resolved contribution bindings for the given {@link Key}:
     *
     * 
    *
  • All explicit bindings for: *
      *
    • the requested key *
    • {@code Set} if the requested key's type is {@code Set>} *
    • {@code Map>} if the requested key's type is {@code Map>}. *
    *
  • An implicit {@link Inject @Inject}-annotated constructor binding if there is one and * there are no explicit bindings or synthetic bindings. *
*/ ResolvedBindings lookUpBindings(Key requestKey) { Set bindings = new LinkedHashSet<>(); Set multibindingContributions = new LinkedHashSet<>(); Set multibindingDeclarations = new LinkedHashSet<>(); Set optionalBindingDeclarations = new LinkedHashSet<>(); Set subcomponentDeclarations = new LinkedHashSet<>(); // Gather all bindings, multibindings, optional, and subcomponent declarations/contributions. for (Resolver resolver : getResolverLineage()) { bindings.addAll(resolver.getLocalExplicitBindings(requestKey)); multibindingContributions.addAll(resolver.getLocalMultibindingContributions(requestKey)); multibindingDeclarations.addAll(resolver.declarations.multibindings(requestKey)); subcomponentDeclarations.addAll(resolver.declarations.subcomponents(requestKey)); // The optional binding declarations are keyed by the unwrapped type. keyFactory.unwrapOptional(requestKey) .map(resolver.declarations::optionalBindings) .ifPresent(optionalBindingDeclarations::addAll); } // Add synthetic multibinding if (!multibindingContributions.isEmpty() || !multibindingDeclarations.isEmpty()) { if (MapType.isMap(requestKey)) { bindings.add(bindingFactory.multiboundMap(requestKey, multibindingContributions)); } else if (SetType.isSet(requestKey)) { bindings.add(bindingFactory.multiboundSet(requestKey, multibindingContributions)); } else { throw new AssertionError("Unexpected type in multibinding key: " + requestKey); } } // Add synthetic optional binding if (!optionalBindingDeclarations.isEmpty()) { ImmutableSet optionalContributions = lookUpBindings(keyFactory.unwrapOptional(requestKey).get()).bindings(); bindings.add( optionalContributions.isEmpty() ? bindingFactory.syntheticAbsentOptionalDeclaration(requestKey) : bindingFactory.syntheticPresentOptionalDeclaration( requestKey, optionalContributions)); } // Add subcomponent creator binding if (!subcomponentDeclarations.isEmpty()) { ContributionBinding binding = bindingFactory.subcomponentCreatorBinding( ImmutableSet.copyOf(subcomponentDeclarations)); bindings.add(binding); addSubcomponentToOwningResolver(binding); } // Add members injector binding if (isTypeOf(requestKey.type().xprocessing(), XTypeNames.MEMBERS_INJECTOR)) { injectBindingRegistry.getOrFindMembersInjectorBinding(requestKey).ifPresent(bindings::add); } // Add Assisted Factory binding if (isDeclared(requestKey.type().xprocessing()) && isAssistedFactoryType(requestKey.type().xprocessing().getTypeElement())) { bindings.add( bindingFactory.assistedFactoryBinding( requestKey.type().xprocessing().getTypeElement(), Optional.of(requestKey.type().xprocessing()))); } // If there are no bindings, add the implicit @Inject-constructed binding if there is one. if (bindings.isEmpty()) { injectBindingRegistry .getOrFindInjectionBinding(requestKey) .filter(this::isCorrectlyScopedInSubcomponent) .ifPresent(bindings::add); } return ResolvedBindings.create( requestKey, bindings.stream() .map( binding -> { Optional bindingNodeOwnedByAncestor = getBindingNodeOwnedByAncestor(requestKey, binding); // If a binding is owned by an ancestor we use the corresponding BindingNode // instance directly rather than creating a new instance to avoid accidentally // including additional multi/optional/subcomponent declarations that don't // exist in the ancestor's BindingNode instance. return bindingNodeOwnedByAncestor.isPresent() ? bindingNodeOwnedByAncestor.get() : bindingNodeFactory.forContributionBindings( componentPath, binding, multibindingDeclarations, optionalBindingDeclarations, subcomponentDeclarations); }) .collect(toImmutableSet())); } /** * Returns true if this binding graph resolution is for a subcomponent and the {@code @Inject} * binding's scope correctly matches one of the components in the current component ancestry. * If not, it means the binding is not owned by any of the currently known components, and will * be owned by a future ancestor (or, if never owned, will result in an incompatibly scoped * binding error at the root component). */ private boolean isCorrectlyScopedInSubcomponent(ContributionBinding binding) { checkArgument(binding.kind() == INJECTION || binding.kind() == ASSISTED_INJECTION); if (!rootComponent().isSubcomponent() || !binding.scope().isPresent() || binding.scope().get().isReusable()) { return true; } Resolver owningResolver = getOwningResolver(binding).orElse(this); ComponentDescriptor owningComponent = owningResolver.componentDescriptor; return owningComponent.scopes().contains(binding.scope().get()); } private ComponentDescriptor rootComponent() { return parentResolver.map(Resolver::rootComponent).orElse(componentDescriptor); } /** Returns the resolved members injection bindings for the given {@link Key}. */ ResolvedBindings lookUpMembersInjectionBinding(Key requestKey) { // no explicit deps for members injection, so just look it up Optional binding = injectBindingRegistry.getOrFindMembersInjectionBinding(requestKey); return binding.isPresent() ? ResolvedBindings.create( requestKey, bindingNodeFactory.forMembersInjectionBinding(componentPath, binding.get())) : ResolvedBindings.create(requestKey); } /** * When a binding is resolved for a {@link SubcomponentDeclaration}, adds corresponding {@link * ComponentDescriptor subcomponent} to a queue in the owning component's resolver. The queue * will be used to detect which subcomponents need to be resolved. */ private void addSubcomponentToOwningResolver(ContributionBinding subcomponentCreatorBinding) { checkArgument(subcomponentCreatorBinding.kind().equals(SUBCOMPONENT_CREATOR)); Resolver owningResolver = getOwningResolver(subcomponentCreatorBinding).get(); XTypeElement builderType = subcomponentCreatorBinding.key().type().xprocessing().getTypeElement(); owningResolver.subcomponentsToResolve.add( owningResolver.componentDescriptor.getChildComponentWithBuilderType(builderType)); } private ImmutableSet createDelegateBindings( ImmutableSet delegateDeclarations) { ImmutableSet.Builder builder = ImmutableSet.builder(); for (DelegateDeclaration delegateDeclaration : delegateDeclarations) { builder.add(bindingFactory.delegateBinding(delegateDeclaration)); } return builder.build(); } /** * Returns a {@link BindingNode} for the given binding that is owned by an ancestor component, * if one exists. Otherwise returns {@link Optional#empty()}. */ private Optional getBindingNodeOwnedByAncestor( Key requestKey, ContributionBinding binding) { if (canBeResolvedInParent(requestKey, binding)) { // Resolve in the parent to make sure we have the most recent multi/optional contributions. parentResolver.get().resolveContributionKey(requestKey); BindingNode previouslyResolvedBinding = getPreviouslyResolvedBindings(requestKey).get().forBinding(binding); if (!requiresResolutionChecker.requiresResolution(previouslyResolvedBinding)) { return Optional.of(previouslyResolvedBinding); } } return Optional.empty(); } private boolean canBeResolvedInParent(Key requestKey, ContributionBinding binding) { if (parentResolver.isEmpty()) { return false; } Optional owningResolver = getOwningResolver(binding); if (owningResolver.isPresent()) { return !owningResolver.get().equals(this); } return !Keys.isComponentOrCreator(requestKey) // TODO(b/305748522): Allow caching for assisted injection bindings. && binding.kind() != BindingKind.ASSISTED_INJECTION && getPreviouslyResolvedBindings(requestKey).isPresent() && getPreviouslyResolvedBindings(requestKey).get().bindings().contains(binding); } private Optional getOwningResolver(ContributionBinding binding) { // TODO(ronshapiro): extract the different pieces of this method into their own methods if ((binding.scope().isPresent() && binding.scope().get().isProductionScope()) || binding.kind().equals(BindingKind.PRODUCTION)) { for (Resolver requestResolver : getResolverLineage()) { // Resolve @Inject @ProductionScope bindings at the highest production component. if (binding.kind().equals(INJECTION) && requestResolver.componentDescriptor.isProduction()) { return Optional.of(requestResolver); } // Resolve explicit @Produces and @ProductionScope bindings at the highest component that // installs the binding. if (requestResolver.containsExplicitBinding(binding)) { return Optional.of(requestResolver); } } } if (binding.scope().isPresent() && binding.scope().get().isReusable()) { for (Resolver requestResolver : getResolverLineage().reverse()) { // If a @Reusable binding was resolved in an ancestor, use that component. ResolvedBindings resolvedBindings = requestResolver.resolvedContributionBindings.get(binding.key()); if (resolvedBindings != null && resolvedBindings.bindings().contains(binding)) { return Optional.of(requestResolver); } } // If a @Reusable binding was not resolved in any ancestor, resolve it here. return Optional.empty(); } // TODO(b/359893922): we currently iterate from child to parent to find an owning resolver, // but we probably want to iterate from parent to child to catch missing bindings in // misconfigured repeated modules. for (Resolver requestResolver : getResolverLineage().reverse()) { if (requestResolver.containsExplicitBinding(binding)) { return Optional.of(requestResolver); } } // look for scope separately. we do this for the case where @Singleton can appear twice // in the † compatibility mode Optional bindingScope = binding.scope(); if (bindingScope.isPresent()) { for (Resolver requestResolver : getResolverLineage().reverse()) { if (requestResolver.componentDescriptor.scopes().contains(bindingScope.get())) { return Optional.of(requestResolver); } } } return Optional.empty(); } private boolean containsExplicitBinding(ContributionBinding binding) { return declarations.bindings(binding.key()).contains(binding) || resolverContainsDelegateDeclarationForBinding(binding) || !declarations.subcomponents(binding.key()).isEmpty(); } /** Returns true if {@code binding} was installed in a module in this resolver's component. */ private boolean resolverContainsDelegateDeclarationForBinding(ContributionBinding binding) { if (!binding.kind().equals(DELEGATE)) { return false; } if (LegacyBindingGraphFactory.hasStrictMultibindingsExemption(compilerOptions, binding)) { return false; } return declarations.delegates(binding.key()).stream() .anyMatch( declaration -> declaration.contributingModule().equals(binding.contributingModule()) && declaration.bindingElement().equals(binding.bindingElement())); } /** Returns the resolver lineage from parent to child. */ private ImmutableList getResolverLineage() { ImmutableList.Builder resolverList = ImmutableList.builder(); for (Optional currentResolver = Optional.of(this); currentResolver.isPresent(); currentResolver = currentResolver.get().parentResolver) { resolverList.add(currentResolver.get()); } return resolverList.build().reverse(); } /** * Returns the explicit {@link ContributionBinding}s that match the {@code key} from this * resolver. */ private ImmutableSet getLocalExplicitBindings(Key key) { return ImmutableSet.builder() .addAll(declarations.bindings(key)) .addAll(createDelegateBindings(declarations.delegates(key))) .build(); } /** * Returns the explicit multibinding contributions that contribute to the map or set requested * by {@code key} from this resolver. */ private ImmutableSet getLocalMultibindingContributions(Key key) { return ImmutableSet.builder() .addAll(declarations.multibindingContributions(key)) .addAll(createDelegateBindings(declarations.delegateMultibindingContributions(key))) .build(); } /** * Returns the {@link OptionalBindingDeclaration}s that match the {@code key} from this and all * ancestor resolvers. */ private ImmutableSet getOptionalBindingDeclarations(Key key) { Optional unwrapped = keyFactory.unwrapOptional(key); if (unwrapped.isEmpty()) { return ImmutableSet.of(); } ImmutableSet.Builder declarations = ImmutableSet.builder(); for (Resolver resolver : getResolverLineage()) { declarations.addAll(resolver.declarations.optionalBindings(unwrapped.get())); } return declarations.build(); } /** * Returns the {@link ResolvedBindings} for {@code key} that was resolved in this resolver or an * ancestor resolver. Only checks for {@link ContributionBinding}s as {@link * MembersInjectionBinding}s are not inherited. */ private Optional getPreviouslyResolvedBindings(Key key) { if (parentResolver.isEmpty()) { return Optional.empty(); } // Check the parent's resolvedContributionBindings directly before calling // parentResolver.getPreviouslyResolvedBindings() otherwise the parent will skip itself. return parentResolver.get().resolvedContributionBindings.containsKey(key) ? Optional.of(parentResolver.get().resolvedContributionBindings.get(key)) : parentResolver.get().getPreviouslyResolvedBindings(key); } private ResolvedBindings resolveMembersInjectionKey(Key key) { if (resolvedMembersInjectionBindings.containsKey(key)) { return resolvedMembersInjectionBindings.get(key); } ResolvedBindings bindings = lookUpMembersInjectionBinding(key); addNodes(bindings); resolveDependencies(bindings); resolvedMembersInjectionBindings.put(key, bindings); return bindings; } @CanIgnoreReturnValue private ResolvedBindings resolveContributionKey(Key key) { if (resolvedContributionBindings.containsKey(key)) { return resolvedContributionBindings.get(key); } ResolvedBindings bindings = lookUpBindings(key); resolvedContributionBindings.put(key, bindings); addNodes(bindings); resolveDependencies(bindings); return bindings; } /** Resolves each of the dependencies of the bindings owned by this component. */ private void resolveDependencies(ResolvedBindings resolvedBindings) { for (BindingNode binding : resolvedBindings.bindingNodesOwnedBy(componentPath)) { for (DependencyRequest request : binding.dependencies()) { ResolvedBindings dependencies = resolveContributionKey(request.key()); addDependencyEdges(binding, dependencies, request); } } } private void addNodes(ResolvedBindings resolvedBindings) { if (resolvedBindings.isEmpty()) { addNode(missingBinding(resolvedBindings.key())); return; } resolvedBindings.bindingNodesOwnedBy(componentPath).forEach(this::addNode); } private void addNode(Node node) { network.addNode(node); // Subcomponent creator bindings have an implicit edge to the subcomponent they create. if (node instanceof BindingNode && ((BindingNode) node).kind() == SUBCOMPONENT_CREATOR) { addSubcomponentEdge((BindingNode) node); } } private void addDependencyEdges( Node parent, ResolvedBindings dependencies, DependencyRequest request) { if (dependencies.isEmpty()) { addDependencyEdge(parent, missingBinding(request.key()), request); } else { dependencies.bindingNodes() .forEach(dependency -> addDependencyEdge(parent, dependency, request)); } } private void addDependencyEdge(Node source, Node target, DependencyRequest request) { boolean isEntryPoint = source instanceof ComponentNode; addEdge(source, target, new DependencyEdgeImpl(request, isEntryPoint)); } private void addSubcomponentEdge(BindingNode binding) { checkState(binding.kind() == SUBCOMPONENT_CREATOR); Resolver owningResolver = getResolverLineage().reverse().stream() .filter(resolver -> resolver.componentPath.equals(binding.componentPath())) .collect(onlyElement()); ComponentDescriptor subcomponent = owningResolver.componentDescriptor.getChildComponentWithBuilderType( binding.key().type().xprocessing().getTypeElement()); ComponentNode subcomponentNode = ComponentNodeImpl.create( owningResolver.componentPath.childPath( DaggerTypeElement.from(subcomponent.typeElement())), subcomponent); addEdge( binding, subcomponentNode, new SubcomponentCreatorBindingEdgeImpl(binding.subcomponentDeclarations())); } private void addChildFactoryMethodEdge(Resolver subcomponentResolver) { componentDescriptor .getFactoryMethodForChildComponent(subcomponentResolver.componentDescriptor) .ifPresent( childFactoryMethod -> addEdge( componentNode, subcomponentResolver.componentNode, new ChildFactoryMethodEdgeImpl(childFactoryMethod.methodElement()))); } private void addEdge(Node source, Node target, Edge edge) { network.addNode(source); network.addNode(target); network.addEdge(source, target, edge); } private MissingBinding missingBinding(Key key) { // Put all missing binding nodes in the root component. This simplifies the binding graph // and produces better error messages for users since all dependents point to the same node. return MissingBindingImpl.create(rootResolver().componentPath, key); } private Resolver rootResolver() { return parentResolver.isPresent() ? parentResolver.get().rootResolver() : this; } private final class RequiresResolutionChecker { private final Map dependsOnMissingBindingCache = new HashMap<>(); private final Map dependsOnLocalBindingsCache = new HashMap<>(); boolean requiresResolution(BindingNode binding) { // If we're not allowed to float then the binding cannot be re-resolved in this component. if (isNotAllowedToFloat(binding)) { return false; } if (hasLocalBindings(binding)) { return true; } return shouldCheckDependencies(binding) // Try to re-resolving bindings that depend on missing bindings. The missing bindings // will still end up being reported for cases where the binding is not allowed to float, // but re-resolving allows cases that are allowed to float to be re-resolved which can // prevent misleading dependency traces that include all floatable bindings. // E.g. see MissingBindingSuggestionsTest#bindsMissingBinding_fails(). && (dependsOnLocalBinding(binding) || dependsOnMissingBinding(binding)); } private boolean isNotAllowedToFloat(BindingNode binding) { // In general, @Provides/@Binds/@Production bindings are allowed to float to get access to // multibinding contributions that are contributed in subcomponents. However, they aren't // allowed to float to get access to missing bindings that are installed in subcomponents, // so we prevent floating if these bindings depend on a missing binding. return binding.kind() != BindingKind.INJECTION && binding.kind() != BindingKind.ASSISTED_INJECTION && dependsOnMissingBinding(binding); } private boolean dependsOnMissingBinding(BindingNode binding) { if (!dependsOnMissingBindingCache.containsKey(binding)) { visitUncachedDependencies(binding); } return checkNotNull(dependsOnMissingBindingCache.get(binding)); } private boolean dependsOnLocalBinding(BindingNode binding) { if (!dependsOnLocalBindingsCache.containsKey(binding)) { visitUncachedDependencies(binding); } return checkNotNull(dependsOnLocalBindingsCache.get(binding)); } private void visitUncachedDependencies(BindingNode binding) { // We use Tarjan's algorithm to visit the uncached dependencies of the binding grouped by // strongly connected nodes (i.e. cycles) and iterated in reverse topological order. for (ImmutableSet cycleNodes : stronglyConnectedNodes(binding)) { // As a sanity check, verify that none of the keys in the cycle are cached yet. checkState(cycleNodes.stream().noneMatch(dependsOnLocalBindingsCache::containsKey)); checkState(cycleNodes.stream().noneMatch(dependsOnMissingBindingCache::containsKey)); boolean dependsOnMissingBinding = cycleNodes.stream().anyMatch(this::isMissingBinding) || cycleNodes.stream() .filter(this::shouldCheckDependencies) .flatMap(this::dependencyStream) .filter(not(cycleNodes::contains)) .anyMatch(dependsOnMissingBindingCache::get); // All keys in the cycle have the same cached value since they all depend on each other. cycleNodes.forEach( cycleNode -> dependsOnMissingBindingCache.put(cycleNode, dependsOnMissingBinding)); // Note that we purposely don't filter out scoped bindings below. In particular, there are // currently 3 cases where hasLocalBinding will return true: // // 1) The binding is MULTIBOUND_SET/MULTIBOUND_MAP and depends on an explicit // multibinding contributions in the current component. // 2) The binding is OPTIONAL and depends on an explicit binding contributed in the // current component. // 3) The binding has a duplicate explicit binding contributed in this component. // // For case #1 and #2 it's not actually required to check for scope because those are // synthetic bindings which are never scoped. // // For case #3 we actually want don't want to rule out a scoped binding, e.g. in the case // where we have a floating @Inject Foo(Bar bar) binding with @Singleton Bar provided in // the ParentComponent and a duplicate Bar provided in the ChildComponent we want to // reprocess Foo so that we can report the duplicate Bar binding. boolean dependsOnLocalBindings = // First, check if any of the bindings themselves depends on local bindings. cycleNodes.stream().anyMatch(this::hasLocalBindings) // Next, check if any of the dependencies (that aren't in the cycle itself) depend // on local bindings. We should be guaranteed that all dependencies are cached since // Tarjan's algorithm is traversed in reverse topological order. || cycleNodes.stream() .filter(this::shouldCheckDependencies) .flatMap(this::dependencyStream) .filter(not(cycleNodes::contains)) .anyMatch(dependsOnLocalBindingsCache::get); // All keys in the cycle have the same cached value since they all depend on each other. cycleNodes.forEach( cycleNode -> dependsOnLocalBindingsCache.put(cycleNode, dependsOnLocalBindings)); } } /** * Returns a list of strongly connected components in reverse topological order, starting from * the given {@code rootNode} and traversing its transitive dependencies. * *

Note that the returned list may not include all transitive dependencies of the {@code * rootNode} because we intentionally stop at dependencies that: * *

    *
  • Already have a cached value. *
  • Are scoped to an ancestor component (i.e. cannot depend on local bindings). *
*/ private ImmutableList> stronglyConnectedNodes(BindingNode rootNode) { return TarjanSCCs.compute( ImmutableSet.of(rootNode), node -> shouldCheckDependencies(node) ? dependencyStream(node) // Skip dependencies that are already cached .filter(dep -> !dependsOnLocalBindingsCache.containsKey(dep)) .collect(toImmutableSet()) : ImmutableSet.of()); } private Stream dependencyStream(Node node) { return network.successors(node).stream(); } private boolean shouldCheckDependencies(Node node) { if (!(node instanceof BindingNode)) { return false; } // Note: we can skip dependencies for scoped bindings because while there could be // duplicates underneath the scoped binding, those duplicates are technically unused so // Dagger shouldn't validate them. BindingNode binding = (BindingNode) node; return !isScopedToComponent(binding) // TODO(beder): Figure out what happens with production subcomponents. && !binding.kind().equals(BindingKind.PRODUCTION); } private boolean isScopedToComponent(BindingNode binding) { return binding.scope().isPresent() && !binding.scope().get().isReusable(); } private boolean isMissingBinding(Node binding) { return binding instanceof MissingBinding; } private boolean hasLocalBindings(Node node) { if (!(node instanceof BindingNode)) { return false; } BindingNode binding = (BindingNode) node; return hasLocalMultibindingContributions(binding) || hasLocalOptionalBindingContribution(binding) || hasDuplicateExplicitBinding(binding); } } /** * Returns {@code true} if there's a contribution in this component matching the given binding * key. */ private boolean hasLocalMultibindingContributions(BindingNode binding) { return !declarations.multibindingContributions(binding.key()).isEmpty() || !declarations.delegateMultibindingContributions(binding.key()).isEmpty(); } /** * Returns {@code true} if there is a contribution in this component for an {@code * Optional} key that has not been contributed in a parent. */ private boolean hasLocalOptionalBindingContribution(BindingNode binding) { if (binding.kind() == OPTIONAL) { return hasLocalExplicitBindings(keyFactory.unwrapOptional(binding.key()).get()); } else { // If a parent contributes a @Provides Optional binding and a child has a // @BindsOptionalOf Foo method, the two should conflict, even if there is no binding for // Foo on its own return !getOptionalBindingDeclarations(binding.key()).isEmpty(); } } /** * Returns {@code true} if there is at least one explicit binding that matches the given key. */ private boolean hasLocalExplicitBindings(Key requestKey) { return !declarations.bindings(requestKey).isEmpty() || !declarations.delegates(requestKey).isEmpty(); } /** Returns {@code true} if this resolver has a duplicate explicit binding to resolve. */ private boolean hasDuplicateExplicitBinding(BindingNode binding) { // By default, we don't actually report an error when an explicit binding tries to override // an injection binding (b/312202845). For now, ignore injection bindings unless we actually // will report an error, otherwise we'd end up silently overriding the binding rather than // reporting a duplicate. // TODO(b/312202845): This can be removed once b/312202845 is fixed. if (binding.kind() == BindingKind.INJECTION && !compilerOptions.explicitBindingConflictsWithInjectValidationType() .diagnosticKind() .equals(Optional.of(Diagnostic.Kind.ERROR))) { return false; } // If the current component has an explicit binding for the same key it must be a duplicate. return hasLocalExplicitBindings(binding.key()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy