• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 androidx.room.compiler.processing.XTypeKt.isVoid;
21 import static com.google.common.base.Verify.verifyNotNull;
22 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
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.MapKeys.getMapKeys;
26 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
27 import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive;
28 import static dagger.internal.codegen.xprocessing.XTypes.isTypeVariable;
29 
30 import androidx.room.compiler.processing.XAnnotation;
31 import androidx.room.compiler.processing.XElement;
32 import androidx.room.compiler.processing.XType;
33 import androidx.room.compiler.processing.XTypeElement;
34 import com.google.common.collect.ImmutableSet;
35 import com.google.errorprone.annotations.FormatMethod;
36 import com.squareup.javapoet.ClassName;
37 import dagger.internal.codegen.base.ContributionType;
38 import dagger.internal.codegen.base.FrameworkTypes;
39 import dagger.internal.codegen.base.SetType;
40 import dagger.internal.codegen.binding.InjectionAnnotations;
41 import dagger.internal.codegen.javapoet.TypeNames;
42 import dagger.internal.codegen.model.Key;
43 import dagger.internal.codegen.model.Scope;
44 import dagger.internal.codegen.xprocessing.XElements;
45 import java.util.Formatter;
46 import java.util.HashMap;
47 import java.util.Map;
48 import java.util.Optional;
49 import javax.inject.Qualifier;
50 
51 /** A validator for elements that represent binding declarations. */
52 public abstract class BindingElementValidator<E extends XElement> {
53   private static final ImmutableSet<ClassName> MULTIBINDING_ANNOTATIONS =
54       ImmutableSet.of(TypeNames.INTO_SET, TypeNames.ELEMENTS_INTO_SET, TypeNames.INTO_MAP);
55 
56   private final AllowsMultibindings allowsMultibindings;
57   private final AllowsScoping allowsScoping;
58   private final Map<E, ValidationReport> cache = new HashMap<>();
59   private final InjectionAnnotations injectionAnnotations;
60 
61   /** Creates a validator object. */
62   // TODO(bcorso): Consider reworking BindingElementValidator and all subclasses to use composition
63   // rather than inheritance. The web of inheritance makes it difficult to track what implementation
64   // of a method is actually being used.
BindingElementValidator( AllowsMultibindings allowsMultibindings, AllowsScoping allowsScoping, InjectionAnnotations injectionAnnotations)65   protected BindingElementValidator(
66       AllowsMultibindings allowsMultibindings,
67       AllowsScoping allowsScoping,
68       InjectionAnnotations injectionAnnotations) {
69     this.allowsMultibindings = allowsMultibindings;
70     this.allowsScoping = allowsScoping;
71     this.injectionAnnotations = injectionAnnotations;
72   }
73 
74   /** Returns a {@link ValidationReport} for {@code element}. */
validate(E element)75   public final ValidationReport validate(E element) {
76     return reentrantComputeIfAbsent(cache, element, this::validateUncached);
77   }
78 
validateUncached(E element)79   private ValidationReport validateUncached(E element) {
80     return elementValidator(element).validate();
81   }
82 
83   /**
84    * Returns an error message of the form "&lt;{@link #bindingElements()}&gt; <i>rule</i>", where
85    * <i>rule</i> comes from calling {@link String#format(String, Object...)} on {@code ruleFormat}
86    * and the other arguments.
87    */
88   @FormatMethod
bindingElements(String ruleFormat, Object... args)89   protected final String bindingElements(String ruleFormat, Object... args) {
90     return new Formatter().format("%s ", bindingElements()).format(ruleFormat, args).toString();
91   }
92 
93   /**
94    * The kind of elements that this validator validates. Should be plural. Used for error reporting.
95    */
bindingElements()96   protected abstract String bindingElements();
97 
98   /** The verb describing the {@link ElementValidator#bindingElementType()} in error messages. */
99   // TODO(ronshapiro,dpb): improve the name of this method and it's documentation.
bindingElementTypeVerb()100   protected abstract String bindingElementTypeVerb();
101 
102   /** The error message when a binding element has a bad type. */
badTypeMessage()103   protected String badTypeMessage() {
104     return bindingElements(
105         "must %s a primitive, an array, a type variable, or a declared type",
106         bindingElementTypeVerb());
107   }
108 
109   /**
110    * The error message when a the type for a binding element with {@link
111    * dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is a not set type.
112    */
elementsIntoSetNotASetMessage()113   protected String elementsIntoSetNotASetMessage() {
114     return bindingElements(
115         "annotated with @ElementsIntoSet must %s a Set", bindingElementTypeVerb());
116   }
117 
118   /**
119    * The error message when a the type for a binding element with {@link
120    * dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is a raw set.
121    */
elementsIntoSetRawSetMessage()122   protected String elementsIntoSetRawSetMessage() {
123     return bindingElements(
124         "annotated with @ElementsIntoSet cannot %s a raw Set", bindingElementTypeVerb());
125   }
126 
127   /*** Returns an {@link ElementValidator} for validating the given {@code element}. */
elementValidator(E element)128   protected abstract ElementValidator elementValidator(E element);
129 
130   /** Validator for a single binding element. */
131   protected abstract class ElementValidator {
132     private final E element;
133     protected final ValidationReport.Builder report;
134     private final ImmutableSet<XAnnotation> qualifiers;
135 
ElementValidator(E element)136     protected ElementValidator(E element) {
137       this.element = element;
138       this.report = ValidationReport.about(element);
139       qualifiers = injectionAnnotations.getQualifiers(element);
140     }
141 
142     /** Checks the element for validity. */
validate()143     private ValidationReport validate() {
144       checkType();
145       checkQualifiers();
146       checkMapKeys();
147       checkMultibindings();
148       checkScopes();
149       checkAdditionalProperties();
150       return report.build();
151     }
152 
153     /** Check any additional properties of the element. Does nothing by default. */
checkAdditionalProperties()154     protected void checkAdditionalProperties() {}
155 
156     /**
157      * The type declared by this binding element. This may differ from a binding's {@link
158      * Key#type()}, for example in multibindings. An {@link Optional#empty()} return value indicates
159      * that the contributed type is ambiguous or missing, i.e. a {@code @BindsInstance} method with
160      * zero or many parameters.
161      */
162     // TODO(dpb): should this be an ImmutableList<XType>, with this class checking the size?
bindingElementType()163     protected abstract Optional<XType> bindingElementType();
164 
165     /**
166      * Adds an error if the {@link #bindingElementType() binding element type} is not appropriate.
167      *
168      * <p>Adds an error if the type is not a primitive, array, declared type, or type variable.
169      *
170      * <p>If the binding is not a multibinding contribution, adds an error if the type is a
171      * framework type.
172      *
173      * <p>If the element has {@link dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code
174      * SET_VALUES}, adds an error if the type is not a {@code Set<T>} for some {@code T}
175      */
checkType()176     protected void checkType() {
177       switch (ContributionType.fromBindingElement(element)) {
178         case UNIQUE:
179           // Validate that a unique binding is not attempting to bind a framework type. This
180           // validation is only appropriate for unique bindings because multibindings may collect
181           // framework types.  E.g. Set<Provider<Foo>> is perfectly reasonable.
182           checkFrameworkType();
183 
184           // Validate that a unique binding is not attempting to bind an unqualified assisted type.
185           // This validation is only appropriate for unique bindings because multibindings may
186           // collect assisted types.
187           checkAssistedType();
188           // fall through
189 
190         case SET:
191         case MAP:
192           bindingElementType().ifPresent(this::checkKeyType);
193           break;
194 
195         case SET_VALUES:
196           checkSetValuesType();
197       }
198     }
199 
200     /**
201      * Adds an error if {@code keyType} is not a primitive, declared type, array, or type variable.
202      */
checkKeyType(XType keyType)203     protected void checkKeyType(XType keyType) {
204       if (isVoid(keyType)) {
205         report.addError(bindingElements("must %s a value (not void)", bindingElementTypeVerb()));
206       } else if (!(isPrimitive(keyType)
207           || isDeclared(keyType)
208           || isArray(keyType)
209           || isTypeVariable(keyType))) {
210         report.addError(badTypeMessage());
211       }
212     }
213 
214     /** Adds errors for unqualified assisted types. */
checkAssistedType()215     private void checkAssistedType() {
216       if (qualifiers.isEmpty()
217           && bindingElementType().isPresent()
218           && isDeclared(bindingElementType().get())) {
219         XTypeElement keyElement = bindingElementType().get().getTypeElement();
220         if (isAssistedInjectionType(keyElement)) {
221           report.addError("Dagger does not support providing @AssistedInject types.", keyElement);
222         }
223         if (isAssistedFactoryType(keyElement)) {
224           report.addError("Dagger does not support providing @AssistedFactory types.", keyElement);
225         }
226       }
227     }
228 
229     /**
230      * Adds an error if the type for an element with {@link
231      * dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is not a a
232      * {@code Set<T>} for a reasonable {@code T}.
233      */
234     // TODO(gak): should we allow "covariant return" for set values?
checkSetValuesType()235     protected void checkSetValuesType() {
236       bindingElementType().ifPresent(this::checkSetValuesType);
237     }
238 
239     /** Adds an error if {@code type} is not a {@code Set<T>} for a reasonable {@code T}. */
checkSetValuesType(XType type)240     protected final void checkSetValuesType(XType type) {
241       if (!SetType.isSet(type)) {
242         report.addError(elementsIntoSetNotASetMessage());
243       } else {
244         SetType setType = SetType.from(type);
245         if (setType.isRawType()) {
246           report.addError(elementsIntoSetRawSetMessage());
247         } else {
248           checkKeyType(setType.elementType());
249         }
250       }
251     }
252 
253     /**
254      * Adds an error if the element has more than one {@linkplain Qualifier qualifier} annotation.
255      */
checkQualifiers()256     private void checkQualifiers() {
257       if (qualifiers.size() > 1) {
258         for (XAnnotation qualifier : qualifiers) {
259           report.addError(
260               bindingElements("may not use more than one @Qualifier"),
261               element,
262               qualifier);
263         }
264       }
265     }
266 
267     /**
268      * Adds an error if an {@link dagger.multibindings.IntoMap @IntoMap} element doesn't have
269      * exactly one {@link dagger.MapKey @MapKey} annotation, or if an element that is {@link
270      * dagger.multibindings.IntoMap @IntoMap} has any.
271      */
checkMapKeys()272     private void checkMapKeys() {
273       if (!allowsMultibindings.allowsMultibindings()) {
274         return;
275       }
276       ImmutableSet<XAnnotation> mapKeys = getMapKeys(element);
277       if (ContributionType.fromBindingElement(element).equals(ContributionType.MAP)) {
278         switch (mapKeys.size()) {
279           case 0:
280             report.addError(bindingElements("of type map must declare a map key"));
281             break;
282           case 1:
283             break;
284           default:
285             report.addError(bindingElements("may not have more than one map key"));
286             break;
287         }
288       } else if (!mapKeys.isEmpty()) {
289         report.addError(bindingElements("of non map type cannot declare a map key"));
290       }
291     }
292 
293     /**
294      * Adds errors if:
295      *
296      * <ul>
297      *   <li>the element doesn't allow {@linkplain MultibindingAnnotations multibinding annotations}
298      *       and has any
299      *   <li>the element does allow them but has more than one
300      *   <li>the element has a multibinding annotation and its {@link dagger.Provides} or {@link
301      *       dagger.producers.Produces} annotation has a {@code type} parameter.
302      * </ul>
303      */
checkMultibindings()304     private void checkMultibindings() {
305       ImmutableSet<XAnnotation> multibindingAnnotations =
306           XElements.getAllAnnotations(element, MULTIBINDING_ANNOTATIONS);
307 
308       switch (allowsMultibindings) {
309         case NO_MULTIBINDINGS:
310           for (XAnnotation annotation : multibindingAnnotations) {
311             report.addError(
312                 bindingElements("cannot have multibinding annotations"),
313                 element,
314                 annotation);
315           }
316           break;
317 
318         case ALLOWS_MULTIBINDINGS:
319           if (multibindingAnnotations.size() > 1) {
320             for (XAnnotation annotation : multibindingAnnotations) {
321               report.addError(
322                   bindingElements("cannot have more than one multibinding annotation"),
323                   element,
324                   annotation);
325             }
326           }
327           break;
328       }
329     }
330 
331     /**
332      * Adds an error if the element has a scope but doesn't allow scoping, or if it has more than
333      * one {@linkplain Scope scope} annotation.
334      */
checkScopes()335     private void checkScopes() {
336       ImmutableSet<Scope> scopes = injectionAnnotations.getScopes(element);
337       String error = null;
338       switch (allowsScoping) {
339         case ALLOWS_SCOPING:
340           if (scopes.size() <= 1) {
341             return;
342           }
343           error = bindingElements("cannot use more than one @Scope");
344           break;
345         case NO_SCOPING:
346           error = bindingElements("cannot be scoped");
347           break;
348       }
349       verifyNotNull(error);
350       for (Scope scope : scopes) {
351         report.addError(error, element, scope.scopeAnnotation().xprocessing());
352       }
353     }
354 
355     /**
356      * Adds an error if the {@link #bindingElementType() type} is a {@linkplain FrameworkTypes
357      * framework type}.
358      */
checkFrameworkType()359     private void checkFrameworkType() {
360       if (bindingElementType().filter(FrameworkTypes::isFrameworkType).isPresent()) {
361         report.addError(bindingElements("must not %s framework types", bindingElementTypeVerb()));
362       }
363     }
364   }
365 
366   /** Whether to check multibinding annotations. */
367   enum AllowsMultibindings {
368     /**
369      * This element disallows multibinding annotations, so don't bother checking for their validity.
370      * {@link MultibindingAnnotationsProcessingStep} will add errors if the element has any
371      * multibinding annotations.
372      */
373     NO_MULTIBINDINGS,
374 
375     /** This element allows multibinding annotations, so validate them. */
376     ALLOWS_MULTIBINDINGS,
377     ;
378 
allowsMultibindings()379     private boolean allowsMultibindings() {
380       return this == ALLOWS_MULTIBINDINGS;
381     }
382   }
383 
384   /** How to check scoping annotations. */
385   enum AllowsScoping {
386     /** This element disallows scoping, so check that no scope annotations are present. */
387     NO_SCOPING,
388 
389     /** This element allows scoping, so validate that there's at most one scope annotation. */
390     ALLOWS_SCOPING,
391     ;
392   }
393 }
394