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