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