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