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 "<{@link #bindingElements()}> <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