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.auto.common.MoreTypes.asDeclared; 20 import static com.google.auto.common.MoreTypes.asExecutable; 21 import static com.google.auto.common.MoreTypes.asTypeElements; 22 import static com.google.common.collect.Sets.union; 23 import static dagger.internal.codegen.DaggerStreams.instancesOf; 24 import static dagger.internal.codegen.DaggerStreams.toImmutableSet; 25 import static dagger.internal.codegen.Util.componentCanMakeNewInstances; 26 import static javax.tools.Diagnostic.Kind.ERROR; 27 28 import com.google.common.base.Joiner; 29 import com.google.common.collect.ImmutableSet; 30 import com.google.common.collect.Sets; 31 import com.google.common.collect.Sets.SetView; 32 import dagger.internal.codegen.langmodel.DaggerTypes; 33 import dagger.model.BindingGraph; 34 import dagger.model.BindingGraph.ChildFactoryMethodEdge; 35 import dagger.model.BindingGraph.ComponentNode; 36 import dagger.spi.BindingGraphPlugin; 37 import dagger.spi.DiagnosticReporter; 38 import java.util.HashMap; 39 import java.util.Map; 40 import java.util.Set; 41 import java.util.function.Function; 42 import javax.inject.Inject; 43 import javax.lang.model.element.TypeElement; 44 import javax.lang.model.type.DeclaredType; 45 import javax.lang.model.type.ExecutableType; 46 47 /** Reports an error if a subcomponent factory method is missing required modules. */ 48 final class SubcomponentFactoryMethodValidator implements BindingGraphPlugin { 49 50 private final DaggerTypes types; 51 private final Map<ComponentNode, Set<TypeElement>> inheritedModulesCache = new HashMap<>(); 52 53 @Inject SubcomponentFactoryMethodValidator(DaggerTypes types)54 SubcomponentFactoryMethodValidator(DaggerTypes types) { 55 this.types = types; 56 } 57 58 @Override pluginName()59 public String pluginName() { 60 return "Dagger/SubcomponentFactoryMethodMissingModule"; 61 } 62 63 @Override visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)64 public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { 65 if (!bindingGraph.rootComponentNode().isRealComponent() 66 || bindingGraph.rootComponentNode().isSubcomponent()) { 67 // We don't know all the modules that might be owned by the child until we know the real root 68 // component, which we don't if the root component node is really a module or a subcomponent. 69 return; 70 } 71 bindingGraph.network().edges().stream() 72 .flatMap(instancesOf(ChildFactoryMethodEdge.class)) 73 .forEach( 74 edge -> { 75 ImmutableSet<TypeElement> missingModules = findMissingModules(edge, bindingGraph); 76 if (!missingModules.isEmpty()) { 77 reportMissingModuleParameters( 78 edge, missingModules, bindingGraph, diagnosticReporter); 79 } 80 }); 81 } 82 findMissingModules( ChildFactoryMethodEdge edge, BindingGraph graph)83 private ImmutableSet<TypeElement> findMissingModules( 84 ChildFactoryMethodEdge edge, BindingGraph graph) { 85 ImmutableSet<TypeElement> factoryMethodParameters = 86 subgraphFactoryMethodParameters(edge, graph); 87 ComponentNode child = (ComponentNode) graph.network().incidentNodes(edge).target(); 88 SetView<TypeElement> modulesOwnedByChild = ownedModules(child, graph); 89 return graph.bindings().stream() 90 // bindings owned by child 91 .filter(binding -> binding.componentPath().equals(child.componentPath())) 92 // that require a module instance 93 .filter(binding -> binding.requiresModuleInstance()) 94 .map(binding -> binding.contributingModule().get()) 95 .distinct() 96 // module owned by child 97 .filter(module -> modulesOwnedByChild.contains(module)) 98 // module not in the method parameters 99 .filter(module -> !factoryMethodParameters.contains(module)) 100 // module doesn't have an accessible no-arg constructor 101 .filter(moduleType -> !componentCanMakeNewInstances(moduleType)) 102 .collect(toImmutableSet()); 103 } 104 subgraphFactoryMethodParameters( ChildFactoryMethodEdge edge, BindingGraph bindingGraph)105 private ImmutableSet<TypeElement> subgraphFactoryMethodParameters( 106 ChildFactoryMethodEdge edge, BindingGraph bindingGraph) { 107 ComponentNode parent = (ComponentNode) bindingGraph.network().incidentNodes(edge).source(); 108 DeclaredType parentType = asDeclared(parent.componentPath().currentComponent().asType()); 109 ExecutableType factoryMethodType = 110 asExecutable(types.asMemberOf(parentType, edge.factoryMethod())); 111 return asTypeElements(factoryMethodType.getParameterTypes()); 112 } 113 ownedModules(ComponentNode component, BindingGraph graph)114 private SetView<TypeElement> ownedModules(ComponentNode component, BindingGraph graph) { 115 return Sets.difference( 116 ((ComponentNodeImpl) component).componentDescriptor().moduleTypes(), 117 inheritedModules(component, graph)); 118 } 119 inheritedModules(ComponentNode component, BindingGraph graph)120 private Set<TypeElement> inheritedModules(ComponentNode component, BindingGraph graph) { 121 return Util.reentrantComputeIfAbsent( 122 inheritedModulesCache, component, uncachedInheritedModules(graph)); 123 } 124 uncachedInheritedModules(BindingGraph graph)125 private Function<ComponentNode, Set<TypeElement>> uncachedInheritedModules(BindingGraph graph) { 126 return componentNode -> 127 componentNode.componentPath().atRoot() 128 ? ImmutableSet.of() 129 : graph 130 .componentNode(componentNode.componentPath().parent()) 131 .map(parent -> union(ownedModules(parent, graph), inheritedModules(parent, graph))) 132 .get(); 133 } 134 reportMissingModuleParameters( ChildFactoryMethodEdge edge, ImmutableSet<TypeElement> missingModules, BindingGraph graph, DiagnosticReporter diagnosticReporter)135 private void reportMissingModuleParameters( 136 ChildFactoryMethodEdge edge, 137 ImmutableSet<TypeElement> missingModules, 138 BindingGraph graph, 139 DiagnosticReporter diagnosticReporter) { 140 diagnosticReporter.reportSubcomponentFactoryMethod( 141 ERROR, 142 edge, 143 "%s requires modules which have no visible default constructors. " 144 + "Add the following modules as parameters to this method: %s", 145 graph 146 .network() 147 .incidentNodes(edge) 148 .target() 149 .componentPath() 150 .currentComponent() 151 .getQualifiedName(), 152 Joiner.on(", ").join(missingModules)); 153 } 154 } 155