• 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.bindinggraphvalidation;
18 
19 import static dagger.internal.codegen.base.Formatter.INDENT;
20 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
21 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap;
22 import static dagger.internal.codegen.model.BindingKind.INJECTION;
23 import static dagger.internal.codegen.model.BindingKind.MEMBERS_INJECTION;
24 import static java.util.Comparator.comparing;
25 import static javax.tools.Diagnostic.Kind.ERROR;
26 
27 import androidx.room.compiler.processing.XElement;
28 import androidx.room.compiler.processing.XType;
29 import androidx.room.compiler.processing.XTypeElement;
30 import com.google.auto.value.AutoValue;
31 import com.google.common.base.Equivalence;
32 import com.google.common.collect.ImmutableCollection;
33 import com.google.common.collect.ImmutableList;
34 import com.google.common.collect.ImmutableListMultimap;
35 import com.google.common.collect.ImmutableMultiset;
36 import com.google.common.collect.ImmutableSet;
37 import com.google.common.collect.ImmutableSetMultimap;
38 import com.google.common.collect.Multimap;
39 import com.google.common.collect.Multimaps;
40 import dagger.internal.codegen.base.Formatter;
41 import dagger.internal.codegen.binding.BindingNode;
42 import dagger.internal.codegen.binding.Declaration;
43 import dagger.internal.codegen.binding.DeclarationFormatter;
44 import dagger.internal.codegen.binding.MultibindingDeclaration;
45 import dagger.internal.codegen.compileroption.CompilerOptions;
46 import dagger.internal.codegen.model.Binding;
47 import dagger.internal.codegen.model.BindingGraph;
48 import dagger.internal.codegen.model.BindingGraph.ComponentNode;
49 import dagger.internal.codegen.model.BindingKind;
50 import dagger.internal.codegen.model.ComponentPath;
51 import dagger.internal.codegen.model.DaggerAnnotation;
52 import dagger.internal.codegen.model.DaggerElement;
53 import dagger.internal.codegen.model.DaggerTypeElement;
54 import dagger.internal.codegen.model.DiagnosticReporter;
55 import dagger.internal.codegen.model.Key;
56 import dagger.internal.codegen.model.Key.MultibindingContributionIdentifier;
57 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
58 import dagger.internal.codegen.xprocessing.XTypes;
59 import java.util.Comparator;
60 import java.util.HashSet;
61 import java.util.Optional;
62 import java.util.Set;
63 import java.util.function.Predicate;
64 import javax.inject.Inject;
65 import javax.tools.Diagnostic;
66 
67 /** Reports errors for conflicting bindings with the same key. */
68 final class DuplicateBindingsValidator extends ValidationBindingGraphPlugin {
69 
70   private static final Comparator<Binding> BY_LENGTH_OF_COMPONENT_PATH =
71       comparing(binding -> binding.componentPath().components().size());
72 
73   private final DeclarationFormatter declarationFormatter;
74   private final CompilerOptions compilerOptions;
75 
76   @Inject
DuplicateBindingsValidator( DeclarationFormatter declarationFormatter, CompilerOptions compilerOptions)77   DuplicateBindingsValidator(
78       DeclarationFormatter declarationFormatter, CompilerOptions compilerOptions) {
79     this.declarationFormatter = declarationFormatter;
80     this.compilerOptions = compilerOptions;
81   }
82 
83   @Override
pluginName()84   public String pluginName() {
85     return "Dagger/DuplicateBindings";
86   }
87 
88   @Override
visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)89   public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
90     // If two unrelated subcomponents have the same duplicate bindings only because they install the
91     // same two modules, then fixing the error in one subcomponent will uncover the second
92     // subcomponent to fix.
93     // TODO(ronshapiro): Explore ways to address such underreporting without overreporting.
94     Set<ImmutableSet<BindingWithoutComponent>> reportedDuplicateBindingSets = new HashSet<>();
95     duplicateBindingSets(bindingGraph)
96         .forEach(
97             duplicateBindings -> {
98               // Only report each set of duplicate bindings once, ignoring the installed component.
99               if (reportedDuplicateBindingSets.add(duplicateBindings.keySet())) {
100                 reportErrors(duplicateBindings, bindingGraph, diagnosticReporter);
101               }
102             });
103   }
104 
105   /**
106    * Returns sets of duplicate bindings. Bindings are duplicates if they bind the same key and are
107    * visible from the same component. Two bindings that differ only in the component that owns them
108    * are not considered to be duplicates, because that means the same binding was "copied" down to a
109    * descendant component because it depends on local multibindings or optional bindings. Hence each
110    * "set" is represented as a multimap from binding element (ignoring component path) to binding.
111    */
duplicateBindingSets( BindingGraph bindingGraph)112   private ImmutableSet<ImmutableSetMultimap<BindingWithoutComponent, Binding>> duplicateBindingSets(
113       BindingGraph bindingGraph) {
114     return groupBindingsByKey(bindingGraph).stream()
115         .flatMap(bindings -> mutuallyVisibleSubsets(bindings).stream())
116         .map(BindingWithoutComponent::index)
117         .filter(duplicates -> duplicates.keySet().size() > 1)
118         .collect(toImmutableSet());
119   }
120 
groupBindingsByKey(BindingGraph bindingGraph)121   private ImmutableSet<ImmutableSet<Binding>> groupBindingsByKey(BindingGraph bindingGraph) {
122     return valueSetsForEachKey(
123         bindingGraph.bindings().stream()
124             .filter(binding -> !binding.kind().equals(MEMBERS_INJECTION))
125             .collect(
126                 toImmutableSetMultimap(
127                     binding ->
128                         // If the "ignoreProvisionKeyWildcards" flag is enabled then ignore the
129                         // variance in the key types here so that Foo<Bar> and Foo<? extends Bar>
130                         // get grouped into the same set (i.e. as duplicates).
131                         KeyWithTypeEquivalence.forKey(
132                             binding.key(),
133                             compilerOptions.ignoreProvisionKeyWildcards()
134                                 ? XTypes.equivalenceIgnoringVariance()
135                                 : XTypes.equivalence()),
136                     binding -> binding)));
137   }
138 
139   /**
140    * Returns the subsets of the input set that contain bindings that are all visible from the same
141    * component. A binding is visible from its component and all its descendants.
142    */
mutuallyVisibleSubsets( Set<Binding> duplicateBindings)143   private static ImmutableSet<ImmutableSet<Binding>> mutuallyVisibleSubsets(
144       Set<Binding> duplicateBindings) {
145     ImmutableListMultimap<ComponentPath, Binding> bindingsByComponentPath =
146         Multimaps.index(duplicateBindings, Binding::componentPath);
147     ImmutableSetMultimap.Builder<ComponentPath, Binding> mutuallyVisibleBindings =
148         ImmutableSetMultimap.builder();
149     bindingsByComponentPath
150         .asMap()
151         .forEach(
152             (componentPath, bindings) -> {
153               mutuallyVisibleBindings.putAll(componentPath, bindings);
154               for (ComponentPath ancestor = componentPath; !ancestor.atRoot(); ) {
155                 ancestor = ancestor.parent();
156                 ImmutableList<Binding> bindingsInAncestor = bindingsByComponentPath.get(ancestor);
157                 mutuallyVisibleBindings.putAll(componentPath, bindingsInAncestor);
158               }
159             });
160     return valueSetsForEachKey(mutuallyVisibleBindings.build());
161   }
162 
reportErrors( ImmutableSetMultimap<BindingWithoutComponent, Binding> duplicateBindings, BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)163   private void reportErrors(
164       ImmutableSetMultimap<BindingWithoutComponent, Binding> duplicateBindings,
165       BindingGraph bindingGraph,
166       DiagnosticReporter diagnosticReporter) {
167     if (explicitBindingConfictsWithInject(duplicateBindings.keySet())) {
168       compilerOptions
169           .explicitBindingConflictsWithInjectValidationType()
170           .diagnosticKind()
171           .ifPresent(
172               diagnosticKind ->
173                   reportExplicitBindingConflictsWithInject(
174                       duplicateBindings.values(),
175                       diagnosticReporter,
176                       diagnosticKind,
177                       bindingGraph.rootComponentNode()));
178       return;
179     }
180 
181     reportDuplicateBindings(duplicateBindings.values(), bindingGraph, diagnosticReporter);
182   }
183 
184   /**
185    * Returns {@code true} if the bindings contain one {@code @Inject} binding and one that isn't.
186    */
explicitBindingConfictsWithInject( ImmutableSet<BindingWithoutComponent> duplicateBindings)187   private static boolean explicitBindingConfictsWithInject(
188       ImmutableSet<BindingWithoutComponent> duplicateBindings) {
189     ImmutableMultiset<BindingKind> bindingKinds =
190         Multimaps.index(duplicateBindings, BindingWithoutComponent::bindingKind).keys();
191     return bindingKinds.count(INJECTION) == 1 && bindingKinds.size() == 2;
192   }
193 
reportExplicitBindingConflictsWithInject( ImmutableCollection<Binding> duplicateBindings, DiagnosticReporter diagnosticReporter, Diagnostic.Kind diagnosticKind, ComponentNode rootComponent)194   private void reportExplicitBindingConflictsWithInject(
195       ImmutableCollection<Binding> duplicateBindings,
196       DiagnosticReporter diagnosticReporter,
197       Diagnostic.Kind diagnosticKind,
198       ComponentNode rootComponent) {
199     Binding injectBinding = rootmostBindingWithKind(k -> k.equals(INJECTION), duplicateBindings);
200     Binding explicitBinding = rootmostBindingWithKind(k -> !k.equals(INJECTION), duplicateBindings);
201     StringBuilder message =
202         new StringBuilder()
203             .append(explicitBinding.key())
204             .append(" is bound multiple times:")
205             .append(formatWithComponentPath(injectBinding))
206             .append(formatWithComponentPath(explicitBinding))
207             .append(
208                 "\nThis condition was never validated before, and will soon be an error. "
209                     + "See https://dagger.dev/conflicting-inject.");
210 
211     if (compilerOptions.experimentalDaggerErrorMessages()) {
212       diagnosticReporter.reportComponent(diagnosticKind, rootComponent, message.toString());
213     } else {
214       diagnosticReporter.reportBinding(diagnosticKind, explicitBinding, message.toString());
215     }
216   }
217 
formatWithComponentPath(Binding binding)218   private String formatWithComponentPath(Binding binding) {
219     return String.format(
220         "\n%s%s [%s]",
221         Formatter.INDENT,
222         declarationFormatter.format(((BindingNode) binding).delegate()),
223         binding.componentPath());
224   }
225 
reportDuplicateBindings( ImmutableCollection<Binding> duplicateBindings, BindingGraph graph, DiagnosticReporter diagnosticReporter)226   private void reportDuplicateBindings(
227       ImmutableCollection<Binding> duplicateBindings,
228       BindingGraph graph,
229       DiagnosticReporter diagnosticReporter) {
230     StringBuilder message = new StringBuilder();
231     Binding oneBinding = duplicateBindings.asList().get(0);
232     ImmutableSet<Binding> multibindings =
233         duplicateBindings.stream()
234             .filter(binding -> binding.kind().isMultibinding())
235             .collect(toImmutableSet());
236     if (multibindings.isEmpty()) {
237       message.append(oneBinding.key()).append(" is bound multiple times:");
238       formatDeclarations(message, 2, declarations(graph, duplicateBindings));
239     } else {
240       Binding oneMultibinding = multibindings.asList().get(0);
241       message.append(oneMultibinding.key()).append(" has incompatible bindings or declarations:\n");
242       message
243           .append(INDENT)
244           .append(multibindingTypeString(oneMultibinding))
245           .append(" bindings and declarations:");
246       formatDeclarations(message, 2, declarations(graph, multibindings));
247       ImmutableSet<Declaration> uniqueBindingDeclarations =
248           duplicateBindings.stream()
249               .filter(binding -> !binding.kind().isMultibinding())
250               .flatMap(binding -> declarations(graph, binding).stream())
251               .filter(declaration -> !(declaration instanceof MultibindingDeclaration))
252               .collect(toImmutableSet());
253       if (!uniqueBindingDeclarations.isEmpty()) {
254         message.append('\n').append(INDENT).append("Unique bindings and declarations:");
255         formatDeclarations(message, 2, uniqueBindingDeclarations);
256       }
257     }
258 
259     if (compilerOptions.experimentalDaggerErrorMessages()) {
260       message.append(String.format("\n%sin component: [%s]", INDENT, oneBinding.componentPath()));
261       diagnosticReporter.reportComponent(ERROR, graph.rootComponentNode(), message.toString());
262     } else {
263       diagnosticReporter.reportBinding(ERROR, oneBinding, message.toString());
264     }
265   }
266 
formatDeclarations( StringBuilder builder, int indentLevel, Iterable<? extends Declaration> bindingDeclarations)267   private void formatDeclarations(
268       StringBuilder builder,
269       int indentLevel,
270       Iterable<? extends Declaration> bindingDeclarations) {
271     declarationFormatter.formatIndentedList(
272         builder, ImmutableList.copyOf(bindingDeclarations), indentLevel);
273   }
274 
declarations( BindingGraph graph, ImmutableCollection<Binding> bindings)275   private ImmutableSet<Declaration> declarations(
276       BindingGraph graph, ImmutableCollection<Binding> bindings) {
277     return bindings.stream()
278         .flatMap(binding -> declarations(graph, binding).stream())
279         .distinct()
280         .sorted(Declaration.COMPARATOR)
281         .collect(toImmutableSet());
282   }
283 
declarations(BindingGraph graph, Binding binding)284   private ImmutableSet<Declaration> declarations(BindingGraph graph, Binding binding) {
285     ImmutableSet.Builder<Declaration> declarations = ImmutableSet.builder();
286     BindingNode bindingNode = (BindingNode) binding;
287     bindingNode.associatedDeclarations().forEach(declarations::add);
288     if (declarationFormatter.canFormat(bindingNode.delegate())) {
289       declarations.add(bindingNode.delegate());
290     } else {
291       graph.requestedBindings(binding).stream()
292           .flatMap(requestedBinding -> declarations(graph, requestedBinding).stream())
293           .forEach(declarations::add);
294     }
295     return declarations.build();
296   }
297 
multibindingTypeString(Binding multibinding)298   private String multibindingTypeString(Binding multibinding) {
299     switch (multibinding.kind()) {
300       case MULTIBOUND_MAP:
301         return "Map";
302       case MULTIBOUND_SET:
303         return "Set";
304       default:
305         throw new AssertionError(multibinding);
306     }
307   }
308 
valueSetsForEachKey(Multimap<?, E> multimap)309   private static <E> ImmutableSet<ImmutableSet<E>> valueSetsForEachKey(Multimap<?, E> multimap) {
310     return multimap.asMap().values().stream().map(ImmutableSet::copyOf).collect(toImmutableSet());
311   }
312 
313   /** Returns the binding of the given kind that is closest to the root component. */
rootmostBindingWithKind( Predicate<BindingKind> bindingKindPredicate, ImmutableCollection<Binding> bindings)314   private static Binding rootmostBindingWithKind(
315       Predicate<BindingKind> bindingKindPredicate, ImmutableCollection<Binding> bindings) {
316     return bindings.stream()
317         .filter(b -> bindingKindPredicate.test(b.kind()))
318         .min(BY_LENGTH_OF_COMPONENT_PATH)
319         .get();
320   }
321 
322   /** The identifying information about a binding, excluding its {@link Binding#componentPath()}. */
323   @AutoValue
324   abstract static class BindingWithoutComponent {
325 
bindingKind()326     abstract BindingKind bindingKind();
327 
bindingKey()328     abstract Key bindingKey();
329 
bindingElement()330     abstract Optional<XElement> bindingElement();
331 
contributingModule()332     abstract Optional<XTypeElement> contributingModule();
333 
index(Set<Binding> bindings)334     static ImmutableSetMultimap<BindingWithoutComponent, Binding> index(Set<Binding> bindings) {
335       return bindings.stream()
336           .collect(toImmutableSetMultimap(BindingWithoutComponent::forBinding, b -> b));
337     }
338 
forBinding(Binding binding)339     private static BindingWithoutComponent forBinding(Binding binding) {
340       return new AutoValue_DuplicateBindingsValidator_BindingWithoutComponent(
341           binding.kind(),
342           binding.key(),
343           binding.bindingElement().map(DaggerElement::xprocessing),
344           binding.contributingModule().map(DaggerTypeElement::xprocessing));
345     }
346   }
347 
348 
349   /** The identifying information about a key with the given type equivalence. */
350   @AutoValue
351   abstract static class KeyWithTypeEquivalence {
qualifier()352     abstract Optional<DaggerAnnotation> qualifier();
353 
wrappedType()354     abstract Equivalence.Wrapper<XType> wrappedType();
355 
multibindingContributionIdentifier()356     abstract Optional<MultibindingContributionIdentifier> multibindingContributionIdentifier();
357 
forKey(Key key, Equivalence<XType> typeEquivalence)358     private static KeyWithTypeEquivalence forKey(Key key, Equivalence<XType> typeEquivalence) {
359       return new AutoValue_DuplicateBindingsValidator_KeyWithTypeEquivalence(
360           key.qualifier(),
361           typeEquivalence.wrap(key.type().xprocessing()),
362           key.multibindingContributionIdentifier());
363     }
364   }
365 }
366