• 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.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