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