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