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