• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen;
18 
19 import static com.google.common.base.Verify.verify;
20 import static dagger.internal.codegen.DaggerStreams.instancesOf;
21 import static dagger.internal.codegen.Keys.isValidImplicitProvisionKey;
22 import static dagger.internal.codegen.Keys.isValidMembersInjectionKey;
23 import static dagger.internal.codegen.RequestKinds.canBeSatisfiedByProductionBinding;
24 import static javax.tools.Diagnostic.Kind.ERROR;
25 
26 import dagger.internal.codegen.langmodel.DaggerTypes;
27 import dagger.model.BindingGraph;
28 import dagger.model.BindingGraph.ComponentNode;
29 import dagger.model.BindingGraph.DependencyEdge;
30 import dagger.model.BindingGraph.MissingBinding;
31 import dagger.model.BindingGraph.Node;
32 import dagger.model.Key;
33 import dagger.spi.BindingGraphPlugin;
34 import dagger.spi.DiagnosticReporter;
35 import javax.inject.Inject;
36 import javax.lang.model.type.TypeKind;
37 
38 /** Reports errors for missing bindings. */
39 final class MissingBindingValidator implements BindingGraphPlugin {
40 
41   private final DaggerTypes types;
42   private final InjectBindingRegistry injectBindingRegistry;
43 
44   @Inject
MissingBindingValidator( DaggerTypes types, InjectBindingRegistry injectBindingRegistry)45   MissingBindingValidator(
46       DaggerTypes types, InjectBindingRegistry injectBindingRegistry) {
47     this.types = types;
48     this.injectBindingRegistry = injectBindingRegistry;
49   }
50 
51   @Override
pluginName()52   public String pluginName() {
53     return "Dagger/MissingBinding";
54   }
55 
56   @Override
visitGraph(BindingGraph graph, DiagnosticReporter diagnosticReporter)57   public void visitGraph(BindingGraph graph, DiagnosticReporter diagnosticReporter) {
58     // Don't report missing bindings when validating a full binding graph or a graph built from a
59     // subcomponent.
60     if (graph.isFullBindingGraph() || graph.rootComponentNode().isSubcomponent()) {
61       return;
62     }
63     graph
64         .missingBindings()
65         .forEach(missingBinding -> reportMissingBinding(missingBinding, graph, diagnosticReporter));
66   }
67 
reportMissingBinding( MissingBinding missingBinding, BindingGraph graph, DiagnosticReporter diagnosticReporter)68   private void reportMissingBinding(
69       MissingBinding missingBinding, BindingGraph graph, DiagnosticReporter diagnosticReporter) {
70     diagnosticReporter.reportBinding(
71         ERROR, missingBinding, missingBindingErrorMessage(missingBinding, graph));
72   }
73 
missingBindingErrorMessage(MissingBinding missingBinding, BindingGraph graph)74   private String missingBindingErrorMessage(MissingBinding missingBinding, BindingGraph graph) {
75     Key key = missingBinding.key();
76     StringBuilder errorMessage = new StringBuilder();
77     // Wildcards should have already been checked by DependencyRequestValidator.
78     verify(!key.type().getKind().equals(TypeKind.WILDCARD), "unexpected wildcard request: %s", key);
79     // TODO(ronshapiro): replace "provided" with "satisfied"?
80     errorMessage.append(key).append(" cannot be provided without ");
81     if (isValidImplicitProvisionKey(key, types)) {
82       errorMessage.append("an @Inject constructor or ");
83     }
84     errorMessage.append("an @Provides-"); // TODO(dpb): s/an/a
85     if (allIncomingDependenciesCanUseProduction(missingBinding, graph)) {
86       errorMessage.append(" or @Produces-");
87     }
88     errorMessage.append("annotated method.");
89     if (isValidMembersInjectionKey(key) && typeHasInjectionSites(key)) {
90       errorMessage.append(
91           " This type supports members injection but cannot be implicitly provided.");
92     }
93     graph.bindings(key).stream()
94         .map(binding -> binding.componentPath().currentComponent())
95         .distinct()
96         .forEach(
97             component ->
98                 errorMessage
99                     .append("\nA binding with matching key exists in component: ")
100                     .append(component.getQualifiedName()));
101     return errorMessage.toString();
102   }
103 
allIncomingDependenciesCanUseProduction( MissingBinding missingBinding, BindingGraph graph)104   private boolean allIncomingDependenciesCanUseProduction(
105       MissingBinding missingBinding, BindingGraph graph) {
106     return graph.network().inEdges(missingBinding).stream()
107         .flatMap(instancesOf(DependencyEdge.class))
108         .allMatch(edge -> dependencyCanBeProduction(edge, graph));
109   }
110 
111   // TODO(ronshapiro): merge with
112   // ProvisionDependencyOnProduerBindingValidator.dependencyCanUseProduction
dependencyCanBeProduction(DependencyEdge edge, BindingGraph graph)113   private boolean dependencyCanBeProduction(DependencyEdge edge, BindingGraph graph) {
114     Node source = graph.network().incidentNodes(edge).source();
115     if (source instanceof ComponentNode) {
116       return canBeSatisfiedByProductionBinding(edge.dependencyRequest().kind());
117     }
118     if (source instanceof dagger.model.Binding) {
119       return ((dagger.model.Binding) source).isProduction();
120     }
121     throw new IllegalArgumentException(
122         "expected a dagger.model.Binding or ComponentNode: " + source);
123   }
124 
typeHasInjectionSites(Key key)125   private boolean typeHasInjectionSites(Key key) {
126     return injectBindingRegistry
127         .getOrFindMembersInjectionBinding(key)
128         .map(binding -> !binding.injectionSites().isEmpty())
129         .orElse(false);
130   }
131 }
132