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