1 /* 2 * Copyright (C) 2018 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.Preconditions.checkArgument; 20 import static dagger.internal.codegen.InjectionAnnotations.getQualifiers; 21 22 import com.google.auto.common.MoreElements; 23 import javax.inject.Inject; 24 import javax.lang.model.element.AnnotationMirror; 25 import javax.lang.model.element.Element; 26 import javax.lang.model.element.ExecutableElement; 27 import javax.lang.model.type.ArrayType; 28 import javax.lang.model.type.DeclaredType; 29 import javax.lang.model.type.PrimitiveType; 30 import javax.lang.model.type.TypeMirror; 31 import javax.lang.model.type.TypeVisitor; 32 import javax.lang.model.util.SimpleTypeVisitor8; 33 34 /** 35 * Validates members injection requests (members injection methods on components and requests for 36 * {@code MembersInjector<Foo>}). 37 */ 38 final class MembersInjectionValidator { 39 40 @Inject MembersInjectionValidator()41 MembersInjectionValidator() {} 42 43 /** Reports errors if a request for a {@code MembersInjector<Foo>}) is invalid. */ validateMembersInjectionRequest( Element requestElement, TypeMirror membersInjectedType)44 ValidationReport<Element> validateMembersInjectionRequest( 45 Element requestElement, TypeMirror membersInjectedType) { 46 ValidationReport.Builder<Element> report = ValidationReport.about(requestElement); 47 checkQualifiers(report, requestElement); 48 membersInjectedType.accept(VALIDATE_MEMBERS_INJECTED_TYPE, report); 49 return report.build(); 50 } 51 52 /** 53 * Reports errors if a members injection method on a component is invalid. 54 * 55 * @throws IllegalArgumentException if the method doesn't have exactly one parameter 56 */ validateMembersInjectionMethod( ExecutableElement method, TypeMirror membersInjectedType)57 ValidationReport<ExecutableElement> validateMembersInjectionMethod( 58 ExecutableElement method, TypeMirror membersInjectedType) { 59 checkArgument( 60 method.getParameters().size() == 1, "expected a method with one parameter: %s", method); 61 62 ValidationReport.Builder<ExecutableElement> report = ValidationReport.about(method); 63 checkQualifiers(report, method); 64 checkQualifiers(report, method.getParameters().get(0)); 65 membersInjectedType.accept(VALIDATE_MEMBERS_INJECTED_TYPE, report); 66 return report.build(); 67 } 68 checkQualifiers(ValidationReport.Builder<?> report, Element element)69 private void checkQualifiers(ValidationReport.Builder<?> report, Element element) { 70 for (AnnotationMirror qualifier : getQualifiers(element)) { 71 report.addError("Cannot inject members into qualified types", element, qualifier); 72 break; // just report on the first qualifier, in case there is more than one 73 } 74 } 75 76 private static final TypeVisitor<Void, ValidationReport.Builder<?>> 77 VALIDATE_MEMBERS_INJECTED_TYPE = 78 new SimpleTypeVisitor8<Void, ValidationReport.Builder<?>>() { 79 // Only declared types can be members-injected. 80 @Override 81 protected Void defaultAction(TypeMirror type, ValidationReport.Builder<?> report) { 82 report.addError("Cannot inject members into " + type); 83 return null; 84 } 85 86 @Override 87 public Void visitDeclared(DeclaredType type, ValidationReport.Builder<?> report) { 88 if (type.getTypeArguments().isEmpty()) { 89 // If the type is the erasure of a generic type, that means the user referred to 90 // Foo<T> as just 'Foo', which we don't allow. (This is a judgement call; we 91 // *could* allow it and instantiate the type bounds, but we don't.) 92 if (!MoreElements.asType(type.asElement()).getTypeParameters().isEmpty()) { 93 report.addError("Cannot inject members into raw type " + type); 94 } 95 } else { 96 // If the type has arguments, validate that each type argument is declared. 97 // Otherwise the type argument may be a wildcard (or other type), and we can't 98 // resolve that to actual types. For array type arguments, validate the type of the 99 // array. 100 for (TypeMirror arg : type.getTypeArguments()) { 101 if (!arg.accept(DECLARED_OR_ARRAY, null)) { 102 report.addError( 103 "Cannot inject members into types with unbounded type arguments: " + type); 104 } 105 } 106 } 107 return null; 108 } 109 }; 110 111 // TODO(dpb): Can this be inverted so it explicitly rejects wildcards or type variables? 112 // This logic is hard to describe. 113 private static final TypeVisitor<Boolean, Void> DECLARED_OR_ARRAY = 114 new SimpleTypeVisitor8<Boolean, Void>(false) { 115 @Override 116 public Boolean visitArray(ArrayType arrayType, Void p) { 117 return arrayType 118 .getComponentType() 119 .accept( 120 new SimpleTypeVisitor8<Boolean, Void>(false) { 121 @Override 122 public Boolean visitDeclared(DeclaredType declaredType, Void p) { 123 for (TypeMirror arg : declaredType.getTypeArguments()) { 124 if (!arg.accept(this, null)) { 125 return false; 126 } 127 } 128 return true; 129 } 130 131 @Override 132 public Boolean visitArray(ArrayType arrayType, Void p) { 133 return arrayType.getComponentType().accept(this, null); 134 } 135 136 @Override 137 public Boolean visitPrimitive(PrimitiveType primitiveType, Void p) { 138 return true; 139 } 140 }, 141 null); 142 } 143 144 @Override 145 public Boolean visitDeclared(DeclaredType t, Void p) { 146 return true; 147 } 148 }; 149 } 150