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