• 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.XTypeKt.isArray;
20 import static com.google.common.base.Preconditions.checkArgument;
21 import static dagger.internal.codegen.xprocessing.XTypes.asArray;
22 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
23 import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive;
24 import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType;
25 
26 import androidx.room.compiler.processing.XAnnotation;
27 import androidx.room.compiler.processing.XElement;
28 import androidx.room.compiler.processing.XMethodElement;
29 import androidx.room.compiler.processing.XType;
30 import dagger.internal.codegen.binding.InjectionAnnotations;
31 import dagger.internal.codegen.xprocessing.XTypes;
32 import javax.inject.Inject;
33 
34 /**
35  * Validates members injection requests (members injection methods on components and requests for
36  * {@code MembersInjector<Foo>}).
37  */
38 final class MembersInjectionValidator {
39   private final InjectionAnnotations injectionAnnotations;
40 
41   @Inject
MembersInjectionValidator(InjectionAnnotations injectionAnnotations)42   MembersInjectionValidator(InjectionAnnotations injectionAnnotations) {
43     this.injectionAnnotations = injectionAnnotations;
44   }
45 
46   /** Reports errors if a request for a {@code MembersInjector<Foo>}) is invalid. */
validateMembersInjectionRequest( XElement requestElement, XType membersInjectedType)47   ValidationReport validateMembersInjectionRequest(
48       XElement requestElement, XType membersInjectedType) {
49     ValidationReport.Builder report = ValidationReport.about(requestElement);
50     checkQualifiers(report, requestElement);
51     checkMembersInjectedType(report, membersInjectedType);
52     return report.build();
53   }
54 
55   /**
56    * Reports errors if a members injection method on a component is invalid.
57    *
58    * @throws IllegalArgumentException if the method doesn't have exactly one parameter
59    */
validateMembersInjectionMethod( XMethodElement method, XType membersInjectedType)60   ValidationReport validateMembersInjectionMethod(
61       XMethodElement method, XType membersInjectedType) {
62     checkArgument(
63         method.getParameters().size() == 1, "expected a method with one parameter: %s", method);
64 
65     ValidationReport.Builder report = ValidationReport.about(method);
66     checkQualifiers(report, method);
67     checkQualifiers(report, method.getParameters().get(0));
68     checkMembersInjectedType(report, membersInjectedType);
69     return report.build();
70   }
71 
checkQualifiers(ValidationReport.Builder report, XElement element)72   private void checkQualifiers(ValidationReport.Builder report, XElement element) {
73     for (XAnnotation qualifier : injectionAnnotations.getQualifiers(element)) {
74       report.addError("Cannot inject members into qualified types", element, qualifier);
75       break; // just report on the first qualifier, in case there is more than one
76     }
77   }
78 
checkMembersInjectedType(ValidationReport.Builder report, XType type)79   private void checkMembersInjectedType(ValidationReport.Builder report, XType type) {
80     // Only declared types can be members-injected.
81     if (!isDeclared(type)) {
82       report.addError("Cannot inject members into " + XTypes.toStableString(type));
83       return;
84     }
85 
86     // If the type is the erasure of a generic type, that means the user referred to
87     // Foo<T> as just 'Foo', which we don't allow.  (This is a judgement call; we
88     // *could* allow it and instantiate the type bounds, but we don't.)
89     if (isRawParameterizedType(type)) {
90       report.addError("Cannot inject members into raw type " + XTypes.toStableString(type));
91       return;
92     }
93 
94     // If the type has arguments, validate that each type argument is declared.
95     // Otherwise the type argument may be a wildcard (or other type), and we can't
96     // resolve that to actual types.  For array type arguments, validate the type of the array.
97     if (!type.getTypeArguments().stream().allMatch(this::isResolvableTypeArgument)) {
98       report.addError(
99           "Cannot inject members into types with unbounded type arguments: "
100               + XTypes.toStableString(type));
101     }
102   }
103 
104   // TODO(dpb): Can this be inverted so it explicitly rejects wildcards or type variables?
105   // This logic is hard to describe.
isResolvableTypeArgument(XType type)106   private boolean isResolvableTypeArgument(XType type) {
107     return isDeclared(type)
108         || (isArray(type) && isResolvableArrayComponentType(asArray(type).getComponentType()));
109   }
110 
isResolvableArrayComponentType(XType type)111   private boolean isResolvableArrayComponentType(XType type) {
112     if (isDeclared(type)) {
113       return type.getTypeArguments().stream().allMatch(this::isResolvableTypeArgument);
114     } else if (isArray(type)) {
115       return isResolvableArrayComponentType(asArray(type).getComponentType());
116     }
117     return isPrimitive(type);
118   }
119 }
120