/* * Copyright (C) 2018 The Dagger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dagger.internal.codegen.validation; import static androidx.room.compiler.processing.XTypeKt.isArray; import static com.google.common.base.Preconditions.checkArgument; import static dagger.internal.codegen.xprocessing.XTypes.asArray; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType; import androidx.room.compiler.processing.XAnnotation; import androidx.room.compiler.processing.XElement; import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XType; import dagger.internal.codegen.binding.InjectionAnnotations; import dagger.internal.codegen.xprocessing.XTypes; import javax.inject.Inject; /** * Validates members injection requests (members injection methods on components and requests for * {@code MembersInjector}). */ final class MembersInjectionValidator { private final InjectionAnnotations injectionAnnotations; @Inject MembersInjectionValidator(InjectionAnnotations injectionAnnotations) { this.injectionAnnotations = injectionAnnotations; } /** Reports errors if a request for a {@code MembersInjector}) is invalid. */ ValidationReport validateMembersInjectionRequest( XElement requestElement, XType membersInjectedType) { ValidationReport.Builder report = ValidationReport.about(requestElement); checkQualifiers(report, requestElement); checkMembersInjectedType(report, membersInjectedType); return report.build(); } /** * Reports errors if a members injection method on a component is invalid. * * @throws IllegalArgumentException if the method doesn't have exactly one parameter */ ValidationReport validateMembersInjectionMethod( XMethodElement method, XType membersInjectedType) { checkArgument( method.getParameters().size() == 1, "expected a method with one parameter: %s", method); ValidationReport.Builder report = ValidationReport.about(method); checkQualifiers(report, method); checkQualifiers(report, method.getParameters().get(0)); checkMembersInjectedType(report, membersInjectedType); return report.build(); } private void checkQualifiers(ValidationReport.Builder report, XElement element) { for (XAnnotation qualifier : injectionAnnotations.getQualifiers(element)) { report.addError("Cannot inject members into qualified types", element, qualifier); break; // just report on the first qualifier, in case there is more than one } } private void checkMembersInjectedType(ValidationReport.Builder report, XType type) { // Only declared types can be members-injected. if (!isDeclared(type)) { report.addError("Cannot inject members into " + XTypes.toStableString(type)); return; } // If the type is the erasure of a generic type, that means the user referred to // Foo as just 'Foo', which we don't allow. (This is a judgement call; we // *could* allow it and instantiate the type bounds, but we don't.) if (isRawParameterizedType(type)) { report.addError("Cannot inject members into raw type " + XTypes.toStableString(type)); return; } // If the type has arguments, validate that each type argument is declared. // Otherwise the type argument may be a wildcard (or other type), and we can't // resolve that to actual types. For array type arguments, validate the type of the array. if (!type.getTypeArguments().stream().allMatch(this::isResolvableTypeArgument)) { report.addError( "Cannot inject members into types with unbounded type arguments: " + XTypes.toStableString(type)); } } // TODO(dpb): Can this be inverted so it explicitly rejects wildcards or type variables? // This logic is hard to describe. private boolean isResolvableTypeArgument(XType type) { return isDeclared(type) || (isArray(type) && isResolvableArrayComponentType(asArray(type).getComponentType())); } private boolean isResolvableArrayComponentType(XType type) { if (isDeclared(type)) { return type.getTypeArguments().stream().allMatch(this::isResolvableTypeArgument); } else if (isArray(type)) { return isResolvableArrayComponentType(asArray(type).getComponentType()); } return isPrimitive(type); } }