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.bindinggraphvalidation;
18 
19 import static com.google.common.base.Verify.verify;
20 import static dagger.internal.codegen.base.Keys.isValidImplicitProvisionKey;
21 import static dagger.internal.codegen.base.Keys.isValidMembersInjectionKey;
22 import static dagger.internal.codegen.base.RequestKinds.canBeSatisfiedByProductionBinding;
23 import static dagger.internal.codegen.extension.DaggerStreams.instancesOf;
24 import static javax.tools.Diagnostic.Kind.ERROR;
25 
26 import dagger.internal.codegen.binding.InjectBindingRegistry;
27 import dagger.internal.codegen.langmodel.DaggerTypes;
28 import dagger.model.BindingGraph;
29 import dagger.model.BindingGraph.ComponentNode;
30 import dagger.model.BindingGraph.DependencyEdge;
31 import dagger.model.BindingGraph.MissingBinding;
32 import dagger.model.BindingGraph.Node;
33 import dagger.model.Key;
34 import dagger.spi.BindingGraphPlugin;
35 import dagger.spi.DiagnosticReporter;
36 import javax.inject.Inject;
37 import javax.lang.model.type.TypeKind;
38 
39 /** Reports errors for missing bindings. */
40 final class MissingBindingValidator implements BindingGraphPlugin {
41 
42   private final DaggerTypes types;
43   private final InjectBindingRegistry injectBindingRegistry;
44 
45   @Inject
MissingBindingValidator( DaggerTypes types, InjectBindingRegistry injectBindingRegistry)46   MissingBindingValidator(
47       DaggerTypes types, InjectBindingRegistry injectBindingRegistry) {
48     this.types = types;
49     this.injectBindingRegistry = injectBindingRegistry;
50   }
51 
52   @Override
pluginName()53   public String pluginName() {
54     return "Dagger/MissingBinding";
55   }
56 
57   @Override
visitGraph(BindingGraph graph, DiagnosticReporter diagnosticReporter)58   public void visitGraph(BindingGraph graph, DiagnosticReporter diagnosticReporter) {
59     // Don't report missing bindings when validating a full binding graph or a graph built from a
60     // subcomponent.
61     if (graph.isFullBindingGraph() || graph.rootComponentNode().isSubcomponent()) {
62       return;
63     }
64     graph
65         .missingBindings()
66         .forEach(missingBinding -> reportMissingBinding(missingBinding, graph, diagnosticReporter));
67   }
68 
reportMissingBinding( MissingBinding missingBinding, BindingGraph graph, DiagnosticReporter diagnosticReporter)69   private void reportMissingBinding(
70       MissingBinding missingBinding, BindingGraph graph, DiagnosticReporter diagnosticReporter) {
71     diagnosticReporter.reportBinding(
72         ERROR, missingBinding, missingBindingErrorMessage(missingBinding, graph));
73   }
74 
missingBindingErrorMessage(MissingBinding missingBinding, BindingGraph graph)75   private String missingBindingErrorMessage(MissingBinding missingBinding, BindingGraph graph) {
76     Key key = missingBinding.key();
77     StringBuilder errorMessage = new StringBuilder();
78     // Wildcards should have already been checked by DependencyRequestValidator.
79     verify(!key.type().getKind().equals(TypeKind.WILDCARD), "unexpected wildcard request: %s", key);
80     // TODO(ronshapiro): replace "provided" with "satisfied"?
81     errorMessage.append(key).append(" cannot be provided without ");
82     if (isValidImplicitProvisionKey(key, types)) {
83       errorMessage.append("an @Inject constructor or ");
84     }
85     errorMessage.append("an @Provides-"); // TODO(dpb): s/an/a
86     if (allIncomingDependenciesCanUseProduction(missingBinding, graph)) {
87       errorMessage.append(" or @Produces-");
88     }
89     errorMessage.append("annotated method.");
90     if (isValidMembersInjectionKey(key) && typeHasInjectionSites(key)) {
91       errorMessage.append(
92           " This type supports members injection but cannot be implicitly provided.");
93     }
94     graph.bindings(key).stream()
95         .map(binding -> binding.componentPath().currentComponent())
96         .distinct()
97         .forEach(
98             component ->
99                 errorMessage
100                     .append("\nA binding with matching key exists in component: ")
101                     .append(component.getQualifiedName()));
102     return errorMessage.toString();
103   }
104 
allIncomingDependenciesCanUseProduction( MissingBinding missingBinding, BindingGraph graph)105   private boolean allIncomingDependenciesCanUseProduction(
106       MissingBinding missingBinding, BindingGraph graph) {
107     return graph.network().inEdges(missingBinding).stream()
108         .flatMap(instancesOf(DependencyEdge.class))
109         .allMatch(edge -> dependencyCanBeProduction(edge, graph));
110   }
111 
112   // TODO(ronshapiro): merge with
113   // ProvisionDependencyOnProduerBindingValidator.dependencyCanUseProduction
dependencyCanBeProduction(DependencyEdge edge, BindingGraph graph)114   private boolean dependencyCanBeProduction(DependencyEdge edge, BindingGraph graph) {
115     Node source = graph.network().incidentNodes(edge).source();
116     if (source instanceof ComponentNode) {
117       return canBeSatisfiedByProductionBinding(edge.dependencyRequest().kind());
118     }
119     if (source instanceof dagger.model.Binding) {
120       return ((dagger.model.Binding) source).isProduction();
121     }
122     throw new IllegalArgumentException(
123         "expected a dagger.model.Binding or ComponentNode: " + source);
124   }
125 
typeHasInjectionSites(Key key)126   private boolean typeHasInjectionSites(Key key) {
127     return injectBindingRegistry
128         .getOrFindMembersInjectionBinding(key)
129         .map(binding -> !binding.injectionSites().isEmpty())
130         .orElse(false);
131   }
132 }
133