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