• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 androidx.room.compiler.processing.compat.XConverters.toKS;
21 import static com.google.common.base.Preconditions.checkArgument;
22 import static com.google.common.base.Preconditions.checkNotNull;
23 import static com.google.common.base.Preconditions.checkState;
24 import static dagger.internal.codegen.base.Keys.isValidImplicitProvisionKey;
25 import static dagger.internal.codegen.base.Keys.isValidMembersInjectionKey;
26 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors;
27 import static dagger.internal.codegen.binding.InjectionAnnotations.hasInjectAnnotation;
28 import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors;
29 import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding;
30 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
31 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
32 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
33 import static dagger.internal.codegen.xprocessing.XTypes.erasedTypeName;
34 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
35 import static dagger.internal.codegen.xprocessing.XTypes.nonObjectSuperclass;
36 import static dagger.internal.codegen.xprocessing.XTypes.unwrapType;
37 
38 import androidx.room.compiler.processing.XConstructorElement;
39 import androidx.room.compiler.processing.XFieldElement;
40 import androidx.room.compiler.processing.XMessager;
41 import androidx.room.compiler.processing.XMethodElement;
42 import androidx.room.compiler.processing.XProcessingEnv;
43 import androidx.room.compiler.processing.XType;
44 import androidx.room.compiler.processing.XTypeElement;
45 import com.google.common.collect.Maps;
46 import com.google.common.collect.Sets;
47 import com.google.devtools.ksp.symbol.Origin;
48 import com.google.errorprone.annotations.CanIgnoreReturnValue;
49 import com.squareup.javapoet.ClassName;
50 import dagger.Component;
51 import dagger.Provides;
52 import dagger.internal.codegen.base.SourceFileGenerationException;
53 import dagger.internal.codegen.base.SourceFileGenerator;
54 import dagger.internal.codegen.binding.AssistedInjectionBinding;
55 import dagger.internal.codegen.binding.Binding;
56 import dagger.internal.codegen.binding.BindingFactory;
57 import dagger.internal.codegen.binding.ContributionBinding;
58 import dagger.internal.codegen.binding.InjectBindingRegistry;
59 import dagger.internal.codegen.binding.InjectionBinding;
60 import dagger.internal.codegen.binding.KeyFactory;
61 import dagger.internal.codegen.binding.MembersInjectionBinding;
62 import dagger.internal.codegen.binding.MembersInjectorBinding;
63 import dagger.internal.codegen.compileroption.CompilerOptions;
64 import dagger.internal.codegen.javapoet.TypeNames;
65 import dagger.internal.codegen.model.Key;
66 import java.util.ArrayDeque;
67 import java.util.Deque;
68 import java.util.Map;
69 import java.util.Optional;
70 import java.util.Set;
71 import java.util.stream.Stream;
72 import javax.inject.Inject;
73 import javax.inject.Singleton;
74 import javax.tools.Diagnostic.Kind;
75 
76 /**
77  * Maintains the collection of provision bindings from {@link Inject} constructors and members
78  * injection bindings from {@link Inject} fields and methods known to the annotation processor. Note
79  * that this registry <b>does not</b> handle any explicit bindings (those from {@link Provides}
80  * methods, {@link Component} dependencies, etc.).
81  */
82 @Singleton
83 final class InjectBindingRegistryImpl implements InjectBindingRegistry {
84   private final XProcessingEnv processingEnv;
85   private final XMessager messager;
86   private final InjectValidator injectValidator;
87   private final KeyFactory keyFactory;
88   private final BindingFactory bindingFactory;
89   private final CompilerOptions compilerOptions;
90 
91   private final class BindingsCollection<B extends Binding> {
92     private final ClassName factoryClass;
93     private final Map<Key, B> bindingsByKey = Maps.newLinkedHashMap();
94     private final Deque<B> bindingsRequiringGeneration = new ArrayDeque<>();
95     private final Set<Key> materializedBindingKeys = Sets.newLinkedHashSet();
96 
BindingsCollection(ClassName factoryClass)97     BindingsCollection(ClassName factoryClass) {
98       this.factoryClass = factoryClass;
99     }
100 
generateBindings(SourceFileGenerator<B> generator)101     void generateBindings(SourceFileGenerator<B> generator) throws SourceFileGenerationException {
102       for (B binding = bindingsRequiringGeneration.poll();
103           binding != null;
104           binding = bindingsRequiringGeneration.poll()) {
105         checkState(!binding.unresolved().isPresent());
106         XType type = binding.key().type().xprocessing();
107         if (!isDeclared(type)
108                 || injectValidator.validateWhenGeneratingCode(type.getTypeElement()).isClean()) {
109           generator.generate(binding);
110         }
111         materializedBindingKeys.add(binding.key());
112       }
113       // Because Elements instantiated across processing rounds are not guaranteed to be equals() to
114       // the logically same element, clear the cache after generating
115       bindingsByKey.clear();
116     }
117 
118     /** Returns a previously cached binding. */
getBinding(Key key)119     B getBinding(Key key) {
120       return bindingsByKey.get(key);
121     }
122 
123     /** Caches the binding and generates it if it needs generation. */
tryRegisterBinding(B binding, boolean isCalledFromInjectProcessor)124     void tryRegisterBinding(B binding, boolean isCalledFromInjectProcessor) {
125       if (processingEnv.getBackend() == XProcessingEnv.Backend.KSP) {
126         Origin origin =
127             toKS(closestEnclosingTypeElement(binding.bindingElement().get())).getOrigin();
128         // If the origin of the element is from a source file in the current compilation unit then
129         // we're guaranteed that the InjectProcessor should run over the element so only generate
130         // the Factory/MembersInjector if we're being called from the InjectProcessor.
131         //
132         // TODO(bcorso): generally, this isn't something we would need to keep track of manually.
133         // However, KSP incremental processing has a bug that will overwrite the cache for the
134         // element if we generate files for it, which can lead to missing generated files from
135         // other processors. See https://github.com/google/dagger/issues/4063 and
136         // https://github.com/google/dagger/issues/4054. Remove this once that bug is fixed.
137         if (!isCalledFromInjectProcessor && (origin == Origin.JAVA || origin == Origin.KOTLIN)) {
138           return;
139         }
140       }
141       tryToCacheBinding(binding);
142 
143       @SuppressWarnings("unchecked")
144       B maybeUnresolved =
145           binding.unresolved().isPresent() ? (B) binding.unresolved().get() : binding;
146       tryToGenerateBinding(maybeUnresolved, isCalledFromInjectProcessor);
147     }
148 
149     /**
150      * Tries to generate a binding, not generating if it already is generated. For resolved
151      * bindings, this will try to generate the unresolved version of the binding.
152      */
tryToGenerateBinding(B binding, boolean isCalledFromInjectProcessor)153     void tryToGenerateBinding(B binding, boolean isCalledFromInjectProcessor) {
154       if (shouldGenerateBinding(binding)) {
155         bindingsRequiringGeneration.offer(binding);
156         if (compilerOptions.warnIfInjectionFactoryNotGeneratedUpstream()
157                 && !isCalledFromInjectProcessor) {
158           messager.printMessage(
159               Kind.NOTE,
160               String.format(
161                   "Generating a %s for %s. "
162                       + "Prefer to run the dagger processor over that class instead.",
163                   factoryClass.simpleName(),
164                   // erasure to strip <T> from msgs.
165                   erasedTypeName(binding.key().type().xprocessing())));
166         }
167       }
168     }
169 
170     /** Returns true if the binding needs to be generated. */
shouldGenerateBinding(B binding)171     private boolean shouldGenerateBinding(B binding) {
172       if (binding instanceof MembersInjectionBinding) {
173         MembersInjectionBinding membersInjectionBinding = (MembersInjectionBinding) binding;
174         // Empty members injection bindings are special and don't need source files.
175         if (membersInjectionBinding.injectionSites().isEmpty()) {
176           return false;
177         }
178         // Members injectors for classes with no local injection sites and no @Inject
179         // constructor are unused.
180         boolean hasInjectConstructor =
181             !(injectedConstructors(membersInjectionBinding.membersInjectedType()).isEmpty()
182                 && assistedInjectedConstructors(
183                     membersInjectionBinding.membersInjectedType()).isEmpty());
184         if (!membersInjectionBinding.hasLocalInjectionSites() && !hasInjectConstructor) {
185           return false;
186         }
187       }
188       return !binding.unresolved().isPresent()
189           && !materializedBindingKeys.contains(binding.key())
190           && !bindingsRequiringGeneration.contains(binding)
191           && processingEnv.findTypeElement(generatedClassNameForBinding(binding)) == null;
192     }
193 
194     /** Caches the binding for future lookups by key. */
tryToCacheBinding(B binding)195     private void tryToCacheBinding(B binding) {
196       // We only cache resolved bindings or unresolved bindings w/o type arguments.
197       // Unresolved bindings w/ type arguments aren't valid for the object graph.
198       if (binding.unresolved().isPresent()
199           || binding.bindingTypeElement().get().getType().getTypeArguments().isEmpty()) {
200         Key key = binding.key();
201         Binding previousValue = bindingsByKey.put(key, binding);
202         checkState(previousValue == null || binding.equals(previousValue),
203             "couldn't register %s. %s was already registered for %s",
204             binding, previousValue, key);
205       }
206     }
207   }
208 
209   private final BindingsCollection<ContributionBinding> injectionBindings =
210       new BindingsCollection<>(TypeNames.PROVIDER);
211   private final BindingsCollection<MembersInjectionBinding> membersInjectionBindings =
212       new BindingsCollection<>(TypeNames.MEMBERS_INJECTOR);
213 
214   @Inject
InjectBindingRegistryImpl( XProcessingEnv processingEnv, XMessager messager, InjectValidator injectValidator, KeyFactory keyFactory, BindingFactory bindingFactory, CompilerOptions compilerOptions)215   InjectBindingRegistryImpl(
216       XProcessingEnv processingEnv,
217       XMessager messager,
218       InjectValidator injectValidator,
219       KeyFactory keyFactory,
220       BindingFactory bindingFactory,
221       CompilerOptions compilerOptions) {
222     this.processingEnv = processingEnv;
223     this.messager = messager;
224     this.injectValidator = injectValidator;
225     this.keyFactory = keyFactory;
226     this.bindingFactory = bindingFactory;
227     this.compilerOptions = compilerOptions;
228   }
229 
230   // TODO(dpb): make the SourceFileGenerators fields so they don't have to be passed in
231   @Override
generateSourcesForRequiredBindings( SourceFileGenerator<ContributionBinding> factoryGenerator, SourceFileGenerator<MembersInjectionBinding> membersInjectorGenerator)232   public void generateSourcesForRequiredBindings(
233       SourceFileGenerator<ContributionBinding> factoryGenerator,
234       SourceFileGenerator<MembersInjectionBinding> membersInjectorGenerator)
235       throws SourceFileGenerationException {
236     injectionBindings.generateBindings(factoryGenerator);
237     membersInjectionBindings.generateBindings(membersInjectorGenerator);
238   }
239 
240   @Override
tryRegisterInjectConstructor( XConstructorElement constructorElement)241   public Optional<ContributionBinding> tryRegisterInjectConstructor(
242       XConstructorElement constructorElement) {
243     return tryRegisterConstructor(
244         constructorElement,
245         Optional.empty(),
246         /* isCalledFromInjectProcessor= */ true);
247   }
248 
249   @CanIgnoreReturnValue
tryRegisterConstructor( XConstructorElement constructorElement, Optional<XType> resolvedType, boolean isCalledFromInjectProcessor)250   private Optional<ContributionBinding> tryRegisterConstructor(
251       XConstructorElement constructorElement,
252       Optional<XType> resolvedType,
253       boolean isCalledFromInjectProcessor) {
254     XTypeElement typeElement = constructorElement.getEnclosingElement();
255 
256     // Validating here shouldn't have a performance penalty because the validator caches its reports
257     ValidationReport report = injectValidator.validate(typeElement);
258     report.printMessagesTo(messager);
259     if (!report.isClean()) {
260       return Optional.empty();
261     }
262 
263     XType type = typeElement.getType();
264     Key key = keyFactory.forInjectConstructorWithResolvedType(type);
265     ContributionBinding cachedBinding = injectionBindings.getBinding(key);
266     if (cachedBinding != null) {
267       return Optional.of(cachedBinding);
268     }
269 
270     if (hasInjectAnnotation(constructorElement)) {
271       InjectionBinding binding = bindingFactory.injectionBinding(constructorElement, resolvedType);
272       injectionBindings.tryRegisterBinding(binding, isCalledFromInjectProcessor);
273       if (!binding.injectionSites().isEmpty()) {
274         tryRegisterMembersInjectedType(typeElement, resolvedType, isCalledFromInjectProcessor);
275       }
276       return Optional.of(binding);
277     } else if (constructorElement.hasAnnotation(TypeNames.ASSISTED_INJECT)) {
278       AssistedInjectionBinding binding =
279           bindingFactory.assistedInjectionBinding(constructorElement, resolvedType);
280       injectionBindings.tryRegisterBinding(binding, isCalledFromInjectProcessor);
281       if (!binding.injectionSites().isEmpty()) {
282         tryRegisterMembersInjectedType(typeElement, resolvedType, isCalledFromInjectProcessor);
283       }
284       return Optional.of(binding);
285     }
286     throw new AssertionError(
287         "Expected either an @Inject or @AssistedInject annotated constructor: "
288             + constructorElement.getEnclosingElement().getQualifiedName());
289   }
290 
291   @Override
tryRegisterInjectField(XFieldElement fieldElement)292   public Optional<MembersInjectionBinding> tryRegisterInjectField(XFieldElement fieldElement) {
293     // TODO(b/204116636): Add a test for this once we're able to test kotlin sources.
294     // TODO(b/204208307): Add validation for KAPT to test if this came from a top-level field.
295     if (!isTypeElement(fieldElement.getEnclosingElement())) {
296       messager.printMessage(
297           Kind.ERROR,
298           "@Inject fields must be enclosed in a type.",
299           fieldElement);
300     }
301     return tryRegisterMembersInjectedType(
302         asTypeElement(fieldElement.getEnclosingElement()),
303         Optional.empty(),
304         /* isCalledFromInjectProcessor= */ true);
305   }
306 
307   @Override
tryRegisterInjectMethod(XMethodElement methodElement)308   public Optional<MembersInjectionBinding> tryRegisterInjectMethod(XMethodElement methodElement) {
309     // TODO(b/204116636): Add a test for this once we're able to test kotlin sources.
310     // TODO(b/204208307): Add validation for KAPT to test if this came from a top-level method.
311     if (!isTypeElement(methodElement.getEnclosingElement())) {
312       messager.printMessage(
313           Kind.ERROR,
314           "@Inject methods must be enclosed in a type.",
315           methodElement);
316     }
317     return tryRegisterMembersInjectedType(
318         asTypeElement(methodElement.getEnclosingElement()),
319         Optional.empty(),
320         /* isCalledFromInjectProcessor= */ true);
321   }
322 
323   @CanIgnoreReturnValue
tryRegisterMembersInjectedType( XTypeElement typeElement, Optional<XType> resolvedType, boolean isCalledFromInjectProcessor)324   private Optional<MembersInjectionBinding> tryRegisterMembersInjectedType(
325       XTypeElement typeElement,
326       Optional<XType> resolvedType,
327       boolean isCalledFromInjectProcessor) {
328     // Validating here shouldn't have a performance penalty because the validator caches its reports
329     ValidationReport report = injectValidator.validateForMembersInjection(typeElement);
330     report.printMessagesTo(messager);
331     if (!report.isClean()) {
332       return Optional.empty();
333     }
334 
335     XType type = typeElement.getType();
336     Key key = keyFactory.forInjectConstructorWithResolvedType(type);
337     MembersInjectionBinding cachedBinding = membersInjectionBindings.getBinding(key);
338     if (cachedBinding != null) {
339       return Optional.of(cachedBinding);
340     }
341 
342     MembersInjectionBinding binding = bindingFactory.membersInjectionBinding(type, resolvedType);
343     membersInjectionBindings.tryRegisterBinding(binding, isCalledFromInjectProcessor);
344     for (Optional<XType> supertype = nonObjectSuperclass(type);
345         supertype.isPresent();
346         supertype = nonObjectSuperclass(supertype.get())) {
347       getOrFindMembersInjectionBinding(keyFactory.forMembersInjectedType(supertype.get()));
348     }
349     return Optional.of(binding);
350   }
351 
352   @CanIgnoreReturnValue
353   @Override
getOrFindInjectionBinding(Key key)354   public Optional<ContributionBinding> getOrFindInjectionBinding(Key key) {
355     checkNotNull(key);
356     if (!isValidImplicitProvisionKey(key)) {
357       return Optional.empty();
358     }
359     ContributionBinding binding = injectionBindings.getBinding(key);
360     if (binding != null) {
361       return Optional.of(binding);
362     }
363 
364     XType type = key.type().xprocessing();
365     XTypeElement element = type.getTypeElement();
366 
367     ValidationReport report = injectValidator.validate(element);
368     report.printMessagesTo(messager);
369     if (!report.isClean()) {
370       return Optional.empty();
371     }
372 
373     return Stream.concat(
374             injectedConstructors(element).stream(),
375             assistedInjectedConstructors(element).stream())
376         // We're guaranteed that there's at most 1 @Inject constructors from above validation.
377         .collect(toOptional())
378         .flatMap(
379             constructor ->
380                 tryRegisterConstructor(
381                     constructor,
382                     Optional.of(type),
383                     /* isCalledFromInjectProcessor= */ false));
384   }
385 
386   @CanIgnoreReturnValue
387   @Override
getOrFindMembersInjectionBinding(Key key)388   public Optional<MembersInjectionBinding> getOrFindMembersInjectionBinding(Key key) {
389     checkNotNull(key);
390     // TODO(gak): is checking the kind enough?
391     checkArgument(isValidMembersInjectionKey(key));
392     MembersInjectionBinding binding = membersInjectionBindings.getBinding(key);
393     if (binding != null) {
394       return Optional.of(binding);
395     }
396     return tryRegisterMembersInjectedType(
397         key.type().xprocessing().getTypeElement(),
398         Optional.of(key.type().xprocessing()),
399         /* isCalledFromInjectProcessor= */ false);
400   }
401 
402   @Override
getOrFindMembersInjectorBinding(Key key)403   public Optional<MembersInjectorBinding> getOrFindMembersInjectorBinding(Key key) {
404     if (!isValidMembersInjectionKey(key)) {
405       return Optional.empty();
406     }
407     Key membersInjectionKey =
408         keyFactory.forMembersInjectedType(unwrapType(key.type().xprocessing()));
409     return getOrFindMembersInjectionBinding(membersInjectionKey)
410         .map(binding -> bindingFactory.membersInjectorBinding(key, binding));
411   }
412 }
413