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