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.isField; 20 import static androidx.room.compiler.processing.XElementKt.isTypeElement; 21 import static androidx.room.compiler.processing.compat.XConverters.toJavac; 22 import static dagger.internal.codegen.base.RequestKinds.extractKeyType; 23 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType; 24 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedInjectionType; 25 import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; 26 import static dagger.internal.codegen.xprocessing.XElements.asField; 27 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; 28 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 29 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; 30 import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; 31 import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; 32 33 import androidx.room.compiler.processing.XAnnotation; 34 import androidx.room.compiler.processing.XElement; 35 import androidx.room.compiler.processing.XFieldElement; 36 import androidx.room.compiler.processing.XProcessingEnv; 37 import androidx.room.compiler.processing.XType; 38 import androidx.room.compiler.processing.XTypeElement; 39 import androidx.room.compiler.processing.XVariableElement; 40 import com.google.common.collect.ImmutableSet; 41 import dagger.internal.codegen.base.FrameworkTypes; 42 import dagger.internal.codegen.base.RequestKinds; 43 import dagger.internal.codegen.binding.InjectionAnnotations; 44 import dagger.internal.codegen.javapoet.TypeNames; 45 import dagger.internal.codegen.kotlin.KotlinMetadataUtil; 46 import dagger.spi.model.RequestKind; 47 import java.util.Optional; 48 import javax.inject.Inject; 49 50 /** Validation for dependency requests. */ 51 final class DependencyRequestValidator { 52 private final XProcessingEnv processingEnv; 53 private final MembersInjectionValidator membersInjectionValidator; 54 private final InjectionAnnotations injectionAnnotations; 55 private final KotlinMetadataUtil metadataUtil; 56 57 @Inject DependencyRequestValidator( XProcessingEnv processingEnv, MembersInjectionValidator membersInjectionValidator, InjectionAnnotations injectionAnnotations, KotlinMetadataUtil metadataUtil)58 DependencyRequestValidator( 59 XProcessingEnv processingEnv, 60 MembersInjectionValidator membersInjectionValidator, 61 InjectionAnnotations injectionAnnotations, 62 KotlinMetadataUtil metadataUtil) { 63 this.processingEnv = processingEnv; 64 this.membersInjectionValidator = membersInjectionValidator; 65 this.injectionAnnotations = injectionAnnotations; 66 this.metadataUtil = metadataUtil; 67 } 68 69 /** 70 * Adds an error if the given dependency request has more than one qualifier annotation or is a 71 * non-instance request with a wildcard type. 72 */ validateDependencyRequest( ValidationReport.Builder report, XElement requestElement, XType requestType)73 void validateDependencyRequest( 74 ValidationReport.Builder report, XElement requestElement, XType requestType) { 75 if (requestElement.hasAnnotation(TypeNames.ASSISTED)) { 76 // Don't validate assisted parameters. These are not dependency requests. 77 return; 78 } 79 if (missingQualifierMetadata(requestElement)) { 80 report.addError( 81 "Unable to read annotations on an injected Kotlin property. " 82 + "The Dagger compiler must also be applied to any project containing @Inject " 83 + "properties.", 84 requestElement); 85 86 // Skip any further validation if we don't have valid metadata for a type that needs it. 87 return; 88 } 89 90 new Validator(report, requestElement, requestType).validate(); 91 } 92 93 /** Returns {@code true} if a kotlin inject field is missing metadata about its qualifiers. */ missingQualifierMetadata(XElement requestElement)94 private boolean missingQualifierMetadata(XElement requestElement) { 95 if (isField(requestElement)) { 96 XFieldElement fieldElement = asField(requestElement); 97 // static/top-level injected fields are not supported, 98 // so no need to get qualifier from kotlin metadata 99 if ((!fieldElement.isStatic() || !isTypeElement(fieldElement.getEnclosingElement())) 100 && metadataUtil.hasMetadata(toJavac(fieldElement)) 101 && metadataUtil.isMissingSyntheticPropertyForAnnotations(toJavac(fieldElement))) { 102 Optional<XTypeElement> membersInjector = 103 Optional.ofNullable( 104 processingEnv.findTypeElement( 105 membersInjectorNameForType(asTypeElement(fieldElement.getEnclosingElement())))); 106 return !membersInjector.isPresent(); 107 } 108 } 109 return false; 110 } 111 112 private final class Validator { 113 private final ValidationReport.Builder report; 114 private final XElement requestElement; 115 private final XType requestType; 116 private final XType keyType; 117 private final ImmutableSet<XAnnotation> qualifiers; 118 Validator(ValidationReport.Builder report, XElement requestElement, XType requestType)119 Validator(ValidationReport.Builder report, XElement requestElement, XType requestType) { 120 this.report = report; 121 this.requestElement = requestElement; 122 this.requestType = requestType; 123 this.keyType = extractKeyType(requestType); 124 this.qualifiers = injectionAnnotations.getQualifiers(requestElement); 125 } 126 validate()127 void validate() { 128 checkQualifiers(); 129 checkType(); 130 } 131 checkQualifiers()132 private void checkQualifiers() { 133 if (qualifiers.size() > 1) { 134 for (XAnnotation qualifier : qualifiers) { 135 report.addError( 136 "A single dependency request may not use more than one @Qualifier", 137 requestElement, 138 qualifier); 139 } 140 } 141 } 142 checkType()143 private void checkType() { 144 if (qualifiers.isEmpty() && isDeclared(keyType)) { 145 XTypeElement typeElement = keyType.getTypeElement(); 146 if (isAssistedInjectionType(typeElement)) { 147 report.addError( 148 "Dagger does not support injecting @AssistedInject type, " 149 + requestType 150 + ". Did you mean to inject its assisted factory type instead?", 151 requestElement); 152 } 153 RequestKind requestKind = RequestKinds.getRequestKind(requestType); 154 if (!(requestKind == RequestKind.INSTANCE || requestKind == RequestKind.PROVIDER) 155 && isAssistedFactoryType(typeElement)) { 156 report.addError( 157 "Dagger does not support injecting Lazy<T>, Producer<T>, " 158 + "or Produced<T> when T is an @AssistedFactory-annotated type such as " 159 + keyType, 160 requestElement); 161 } 162 } 163 if (isWildcard(keyType)) { 164 // TODO(ronshapiro): Explore creating this message using RequestKinds. 165 report.addError( 166 "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, " 167 + "or Produced<T> when T is a wildcard type such as " 168 + keyType, 169 requestElement); 170 } 171 if (isTypeOf(keyType, TypeNames.MEMBERS_INJECTOR)) { 172 if (keyType.getTypeArguments().isEmpty()) { 173 report.addError("Cannot inject a raw MembersInjector", requestElement); 174 } else { 175 report.addSubreport( 176 membersInjectionValidator.validateMembersInjectionRequest( 177 requestElement, keyType.getTypeArguments().get(0))); 178 } 179 } 180 } 181 } 182 183 /** 184 * Adds an error if the given dependency request is for a {@link dagger.producers.Producer} or 185 * {@link dagger.producers.Produced}. 186 * 187 * <p>Only call this when processing a provision binding. 188 */ 189 // TODO(dpb): Should we disallow Producer entry points in non-production components? checkNotProducer(ValidationReport.Builder report, XVariableElement requestElement)190 void checkNotProducer(ValidationReport.Builder report, XVariableElement requestElement) { 191 XType requestType = requestElement.getType(); 192 if (FrameworkTypes.isProducerType(requestType)) { 193 report.addError( 194 String.format( 195 "%s may only be injected in @Produces methods", 196 getSimpleName(requestType.getTypeElement())), 197 requestElement); 198 } 199 } 200 } 201