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