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