• 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.validation;
18 
19 import static androidx.room.compiler.processing.XElementKt.isTypeElement;
20 import static com.google.common.base.Preconditions.checkState;
21 import static com.google.common.base.Predicates.equalTo;
22 import static com.google.common.base.Verify.verify;
23 import static com.google.common.collect.Iterables.filter;
24 import static com.google.common.collect.Iterables.getLast;
25 import static com.google.common.collect.Iterables.indexOf;
26 import static com.google.common.collect.Iterables.transform;
27 import static dagger.internal.codegen.base.ElementFormatter.elementToString;
28 import static dagger.internal.codegen.extension.DaggerGraphs.shortestPath;
29 import static dagger.internal.codegen.extension.DaggerStreams.instancesOf;
30 import static dagger.internal.codegen.extension.DaggerStreams.presentValues;
31 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
32 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
33 import static dagger.internal.codegen.xprocessing.XElements.asExecutable;
34 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
35 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
36 import static dagger.internal.codegen.xprocessing.XElements.isExecutable;
37 import static java.util.Collections.min;
38 import static java.util.Comparator.comparing;
39 import static java.util.Comparator.comparingInt;
40 
41 import androidx.room.compiler.processing.XElement;
42 import androidx.room.compiler.processing.XType;
43 import androidx.room.compiler.processing.XTypeElement;
44 import com.google.common.cache.CacheBuilder;
45 import com.google.common.cache.CacheLoader;
46 import com.google.common.cache.LoadingCache;
47 import com.google.common.collect.HashBasedTable;
48 import com.google.common.collect.ImmutableList;
49 import com.google.common.collect.ImmutableSet;
50 import com.google.common.collect.Iterables;
51 import com.google.common.collect.Table;
52 import dagger.internal.codegen.base.ElementFormatter;
53 import dagger.internal.codegen.base.Formatter;
54 import dagger.internal.codegen.binding.DependencyRequestFormatter;
55 import dagger.internal.codegen.model.Binding;
56 import dagger.internal.codegen.model.BindingGraph;
57 import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
58 import dagger.internal.codegen.model.BindingGraph.Edge;
59 import dagger.internal.codegen.model.BindingGraph.MaybeBinding;
60 import dagger.internal.codegen.model.BindingGraph.Node;
61 import dagger.internal.codegen.model.ComponentPath;
62 import dagger.internal.codegen.model.DaggerElement;
63 import java.util.Comparator;
64 import java.util.List;
65 import java.util.Set;
66 import java.util.function.Function;
67 import javax.inject.Inject;
68 
69 /** Helper class for generating diagnostic messages. */
70 public final class DiagnosticMessageGenerator {
71 
72   /** Injectable factory for {@code DiagnosticMessageGenerator}. */
73   public static final class Factory {
74     private final DependencyRequestFormatter dependencyRequestFormatter;
75     private final ElementFormatter elementFormatter;
76 
77     @Inject
Factory( DependencyRequestFormatter dependencyRequestFormatter, ElementFormatter elementFormatter)78     Factory(
79         DependencyRequestFormatter dependencyRequestFormatter,
80         ElementFormatter elementFormatter) {
81       this.dependencyRequestFormatter = dependencyRequestFormatter;
82       this.elementFormatter = elementFormatter;
83     }
84 
85     /** Creates a {@code DiagnosticMessageGenerator} for the given binding graph. */
create(BindingGraph graph)86     public DiagnosticMessageGenerator create(BindingGraph graph) {
87       return new DiagnosticMessageGenerator(graph, dependencyRequestFormatter, elementFormatter);
88     }
89   }
90 
91   private final BindingGraph graph;
92   private final DependencyRequestFormatter dependencyRequestFormatter;
93   private final ElementFormatter elementFormatter;
94 
95   /** A cached function from type to all of its supertypes in breadth-first order. */
96   private final Function<XTypeElement, Iterable<XTypeElement>> supertypes;
97 
98   /** The shortest path (value) from an entry point (column) to a binding (row). */
99   private final Table<MaybeBinding, DependencyEdge, ImmutableList<Node>> shortestPaths =
100       HashBasedTable.create();
101 
memoize(Function<K, V> uncached)102   private static <K, V> Function<K, V> memoize(Function<K, V> uncached) {
103     // If Android Guava is on the processor path, then c.g.c.b.Function (which LoadingCache
104     // implements) does not extend j.u.f.Function.
105     // TODO(erichang): Fix current breakages and try to remove this to enforce not having this on
106     // processor path.
107 
108     // First, explicitly convert uncached to c.g.c.b.Function because CacheLoader.from() expects
109     // one.
110     com.google.common.base.Function<K, V> uncachedAsBaseFunction = uncached::apply;
111 
112     LoadingCache<K, V> cache =
113         CacheBuilder.newBuilder().build(CacheLoader.from(uncachedAsBaseFunction));
114 
115     // Second, explicitly convert LoadingCache to j.u.f.Function.
116     @SuppressWarnings("deprecation") // uncachedAsBaseFunction throws only unchecked exceptions
117     Function<K, V> memoized = cache::apply;
118 
119     return memoized;
120   }
121 
DiagnosticMessageGenerator( BindingGraph graph, DependencyRequestFormatter dependencyRequestFormatter, ElementFormatter elementFormatter)122   private DiagnosticMessageGenerator(
123       BindingGraph graph,
124       DependencyRequestFormatter dependencyRequestFormatter,
125       ElementFormatter elementFormatter) {
126     this.graph = graph;
127     this.dependencyRequestFormatter = dependencyRequestFormatter;
128     this.elementFormatter = elementFormatter;
129     supertypes =
130         memoize(component -> transform(component.getType().getSuperTypes(), XType::getTypeElement));
131   }
132 
getMessage(MaybeBinding binding)133   public String getMessage(MaybeBinding binding) {
134     ImmutableSet<DependencyEdge> entryPoints = graph.entryPointEdgesDependingOnBinding(binding);
135     ImmutableSet<DependencyEdge> requests = requests(binding);
136     ImmutableList<DependencyEdge> dependencyTrace = dependencyTrace(binding, entryPoints);
137 
138     return getMessageInternal(dependencyTrace, requests, entryPoints);
139   }
140 
getMessage(DependencyEdge dependencyEdge)141   public String getMessage(DependencyEdge dependencyEdge) {
142     ImmutableSet<DependencyEdge> requests = ImmutableSet.of(dependencyEdge);
143 
144     ImmutableSet<DependencyEdge> entryPoints;
145     ImmutableList<DependencyEdge> dependencyTrace;
146     if (dependencyEdge.isEntryPoint()) {
147       entryPoints = ImmutableSet.of(dependencyEdge);
148       dependencyTrace = ImmutableList.of(dependencyEdge);
149     } else {
150       // It's not an entry point, so it's part of a binding
151       Binding binding = (Binding) source(dependencyEdge);
152       entryPoints = graph.entryPointEdgesDependingOnBinding(binding);
153       dependencyTrace =
154           ImmutableList.<DependencyEdge>builder()
155               .add(dependencyEdge)
156               .addAll(dependencyTrace(binding, entryPoints))
157               .build();
158     }
159 
160     return getMessageInternal(dependencyTrace, requests, entryPoints);
161   }
162 
getMessageInternal( ImmutableList<DependencyEdge> dependencyTrace, ImmutableSet<DependencyEdge> requests, ImmutableSet<DependencyEdge> entryPoints)163   private String getMessageInternal(
164       ImmutableList<DependencyEdge> dependencyTrace,
165       ImmutableSet<DependencyEdge> requests,
166       ImmutableSet<DependencyEdge> entryPoints) {
167     StringBuilder message = new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */);
168     dependencyTrace.forEach(
169         edge -> dependencyRequestFormatter.appendFormatLine(message, edge.dependencyRequest()));
170     if (!dependencyTrace.isEmpty()) {
171       appendComponentPathUnlessAtRoot(message, source(getLast(dependencyTrace)));
172     }
173     message.append(getRequestsNotInTrace(dependencyTrace, requests, entryPoints));
174     return message.toString();
175   }
176 
getRequestsNotInTrace( ImmutableList<DependencyEdge> dependencyTrace, ImmutableSet<DependencyEdge> requests, ImmutableSet<DependencyEdge> entryPoints)177   public String getRequestsNotInTrace(
178       ImmutableList<DependencyEdge> dependencyTrace,
179       ImmutableSet<DependencyEdge> requests,
180       ImmutableSet<DependencyEdge> entryPoints) {
181     StringBuilder message = new StringBuilder();
182     // Print any dependency requests that aren't shown as part of the dependency trace.
183     ImmutableSet<XElement> requestsToPrint =
184         requests.stream()
185             // if printing entry points, skip entry points and the traced request
186             .filter(request -> !request.isEntryPoint())
187             .filter(request -> !isTracedRequest(dependencyTrace, request))
188             .map(request -> request.dependencyRequest().requestElement())
189             .flatMap(presentValues())
190             .map(DaggerElement::xprocessing)
191             .collect(toImmutableSet());
192     if (!requestsToPrint.isEmpty()) {
193       message.append("\nIt is also requested at:");
194       elementFormatter.formatIndentedList(message, requestsToPrint, 1);
195     }
196 
197     // Print the remaining entry points, showing which component they're in
198     if (entryPoints.size() > 1) {
199       message.append("\nThe following other entry points also depend on it:");
200       entryPointFormatter.formatIndentedList(
201           message,
202           entryPoints.stream()
203               .filter(entryPoint -> !entryPoint.equals(getLast(dependencyTrace)))
204               .sorted(
205                   // 1. List entry points in components closest to the root first.
206                   // 2. List entry points declared in a component before those in a supertype.
207                   // 3. List entry points in declaration order in their declaring type.
208                   rootComponentFirst()
209                       .thenComparing(nearestComponentSupertypeFirst())
210                       .thenComparing(requestElementDeclarationOrder()))
211               .collect(toImmutableList()),
212           1);
213     }
214     return message.toString();
215   }
216 
appendComponentPathUnlessAtRoot(StringBuilder message, Node node)217   public void appendComponentPathUnlessAtRoot(StringBuilder message, Node node) {
218     if (!node.componentPath().equals(graph.rootComponentNode().componentPath())) {
219       message.append(String.format(" [%s]", node.componentPath()));
220     }
221   }
222 
223   private final Formatter<DependencyEdge> entryPointFormatter =
224       new Formatter<DependencyEdge>() {
225         @Override
226         public String format(DependencyEdge object) {
227           XElement requestElement = object.dependencyRequest().requestElement().get().xprocessing();
228           StringBuilder builder = new StringBuilder(elementToString(requestElement));
229 
230           // For entry points declared in subcomponents or supertypes of the root component,
231           // append the component path to make clear to the user which component it's in.
232           ComponentPath componentPath = source(object).componentPath();
233           if (!componentPath.atRoot()
234               || !requestElement
235                   .getEnclosingElement()
236                   .equals(componentPath.rootComponent().xprocessing())) {
237             builder.append(String.format(" [%s]", componentPath));
238           }
239           return builder.toString();
240         }
241       };
242 
isTracedRequest( ImmutableList<DependencyEdge> dependencyTrace, DependencyEdge request)243   private boolean isTracedRequest(
244       ImmutableList<DependencyEdge> dependencyTrace, DependencyEdge request) {
245     return !dependencyTrace.isEmpty()
246         && request.dependencyRequest().equals(dependencyTrace.get(0).dependencyRequest())
247         // Comparing the dependency request is not enough since the request is just the key.
248         // Instead, we check that the target incident node is the same.
249         && graph.network().incidentNodes(request).target()
250             .equals(graph.network().incidentNodes(dependencyTrace.get(0)).target());
251   }
252 
253   /**
254    * Returns the dependency trace from one of the {@code entryPoints} to {@code binding} to {@code
255    * message} as a list <i>ending with</i> the entry point.
256    */
257   // TODO(ronshapiro): Adding a DependencyPath type to dagger.internal.codegen.model could be
258   // useful, i.e.
259   // bindingGraph.shortestPathFromEntryPoint(DependencyEdge, MaybeBindingNode)
dependencyTrace( MaybeBinding binding, ImmutableSet<DependencyEdge> entryPoints)260   public ImmutableList<DependencyEdge> dependencyTrace(
261       MaybeBinding binding, ImmutableSet<DependencyEdge> entryPoints) {
262     // Module binding graphs may have bindings unreachable from any entry points. If there are
263     // no entry points for this DiagnosticInfo, don't try to print a dependency trace.
264     if (entryPoints.isEmpty()) {
265       return ImmutableList.of();
266     }
267     // Show the full dependency trace for one entry point.
268     DependencyEdge entryPointForTrace =
269         min(
270             entryPoints,
271             // prefer entry points in components closest to the root
272             rootComponentFirst()
273                 // then prefer entry points with a short dependency path to the error
274                 .thenComparing(shortestDependencyPathFirst(binding))
275                 // then prefer entry points declared in the component to those declared in a
276                 // supertype
277                 .thenComparing(nearestComponentSupertypeFirst())
278                 // finally prefer entry points declared first in their enclosing type
279                 .thenComparing(requestElementDeclarationOrder()));
280 
281     ImmutableList<Node> shortestBindingPath =
282         shortestPathFromEntryPoint(entryPointForTrace, binding);
283     verify(
284         !shortestBindingPath.isEmpty(),
285         "no dependency path from %s to %s in %s",
286         entryPointForTrace,
287         binding,
288         graph);
289 
290     ImmutableList.Builder<DependencyEdge> dependencyTrace = ImmutableList.builder();
291     dependencyTrace.add(entryPointForTrace);
292     for (int i = 0; i < shortestBindingPath.size() - 1; i++) {
293       Set<Edge> dependenciesBetween =
294           graph
295               .network()
296               .edgesConnecting(shortestBindingPath.get(i), shortestBindingPath.get(i + 1));
297       // If a binding requests a key more than once, any of them should be fine to get to the
298       // shortest path
299       dependencyTrace.add((DependencyEdge) Iterables.get(dependenciesBetween, 0));
300     }
301     return dependencyTrace.build().reverse();
302   }
303 
304   /** Returns all the nonsynthetic dependency requests for a binding. */
requests(MaybeBinding binding)305   public ImmutableSet<DependencyEdge> requests(MaybeBinding binding) {
306     return graph.network().inEdges(binding).stream()
307         .flatMap(instancesOf(DependencyEdge.class))
308         .filter(edge -> edge.dependencyRequest().requestElement().isPresent())
309         .sorted(requestEnclosingTypeName().thenComparing(requestElementDeclarationOrder()))
310         .collect(toImmutableSet());
311   }
312 
313   /**
314    * Returns a comparator that sorts entry points in components whose paths from the root are
315    * shorter first.
316    */
rootComponentFirst()317   private Comparator<DependencyEdge> rootComponentFirst() {
318     return comparingInt(entryPoint -> source(entryPoint).componentPath().components().size());
319   }
320 
321   /**
322    * Returns a comparator that puts entry points whose shortest dependency path to {@code binding}
323    * is shortest first.
324    */
shortestDependencyPathFirst(MaybeBinding binding)325   private Comparator<DependencyEdge> shortestDependencyPathFirst(MaybeBinding binding) {
326     return comparing(entryPoint -> shortestPathFromEntryPoint(entryPoint, binding).size());
327   }
328 
shortestPathFromEntryPoint( DependencyEdge entryPoint, MaybeBinding binding)329   private ImmutableList<Node> shortestPathFromEntryPoint(
330       DependencyEdge entryPoint, MaybeBinding binding) {
331     return shortestPaths
332         .row(binding)
333         .computeIfAbsent(
334             entryPoint,
335             ep ->
336                 shortestPath(
337                     node ->
338                         filter(graph.network().successors(node), MaybeBinding.class::isInstance),
339                     graph.network().incidentNodes(ep).target(),
340                     binding));
341   }
342 
343   /**
344    * Returns a comparator that sorts entry points in by the distance of the type that declares them
345    * from the type of the component that contains them.
346    *
347    * <p>For instance, an entry point declared directly in the component type would sort before one
348    * declared in a direct supertype, which would sort before one declared in a supertype of a
349    * supertype.
350    */
nearestComponentSupertypeFirst()351   private Comparator<DependencyEdge> nearestComponentSupertypeFirst() {
352     return comparingInt(
353         entryPoint ->
354             indexOf(
355                 supertypes.apply(componentContainingEntryPoint(entryPoint)),
356                 equalTo(typeDeclaringEntryPoint(entryPoint))));
357   }
358 
componentContainingEntryPoint(DependencyEdge entryPoint)359   private XTypeElement componentContainingEntryPoint(DependencyEdge entryPoint) {
360     return source(entryPoint).componentPath().currentComponent().xprocessing();
361   }
362 
typeDeclaringEntryPoint(DependencyEdge entryPoint)363   private XTypeElement typeDeclaringEntryPoint(DependencyEdge entryPoint) {
364     return asTypeElement(
365         entryPoint.dependencyRequest().requestElement().get().xprocessing().getEnclosingElement());
366   }
367 
368   /**
369    * Returns a comparator that sorts dependency edges lexicographically by the qualified name of the
370    * type that contains them. Only appropriate for edges with request elements.
371    */
requestEnclosingTypeName()372   private Comparator<DependencyEdge> requestEnclosingTypeName() {
373     return comparing(
374         edge ->
375             closestEnclosingTypeElement(
376                     edge.dependencyRequest().requestElement().get().xprocessing())
377                 .getQualifiedName());
378   }
379 
380   /**
381    * Returns a comparator that sorts edges in the order in which their request elements were
382    * declared in their declaring type.
383    *
384    * <p>Only useful to compare edges whose request elements were declared in the same type.
385    */
requestElementDeclarationOrder()386   private Comparator<DependencyEdge> requestElementDeclarationOrder() {
387     return comparing(
388         edge -> edge.dependencyRequest().requestElement().get().xprocessing(),
389         // TODO(bcorso): This is inefficient as it requires each element to iterate through all of
390         // its siblings to find its order. Ideally, the order of all elements would be calculated in
391         // a single pass and cached, but the organization of the current code makes that a bit
392         // difficult. I'm leaving this for now since this is only called on failures.
393         comparing(
394             element -> {
395               XElement enclosingElement = element.getEnclosingElement();
396               checkState(isTypeElement(enclosingElement) || isExecutable(enclosingElement));
397               List<? extends XElement> siblings =
398                   isTypeElement(enclosingElement)
399                       ? asTypeElement(enclosingElement).getEnclosedElements()
400                       // For parameter elements, element.getEnclosingElement().getEnclosedElements()
401                       // is empty, so instead look at the parameter list of the enclosing executable
402                       : asExecutable(enclosingElement).getParameters();
403               return siblings.indexOf(element);
404             }));
405   }
406 
source(Edge edge)407   private Node source(Edge edge) {
408     return graph.network().incidentNodes(edge).source();
409   }
410 }
411