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