/* * Copyright (C) 2021 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.base; import static androidx.room.compiler.processing.XElementKt.isMethod; import static androidx.room.compiler.processing.XElementKt.isTypeElement; import static androidx.room.compiler.processing.XElementKt.isVariableElement; import static androidx.room.compiler.processing.XTypeKt.isArray; import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.xprocessing.XAnnotationValues.getKindName; import static dagger.internal.codegen.xprocessing.XElements.asEnumEntry; import static dagger.internal.codegen.xprocessing.XElements.asExecutable; import static dagger.internal.codegen.xprocessing.XElements.asMethod; import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; import static dagger.internal.codegen.xprocessing.XElements.asTypeParameter; import static dagger.internal.codegen.xprocessing.XElements.asVariable; import static dagger.internal.codegen.xprocessing.XElements.getKindName; import static dagger.internal.codegen.xprocessing.XElements.isEnumEntry; import static dagger.internal.codegen.xprocessing.XElements.isExecutable; import static dagger.internal.codegen.xprocessing.XElements.isTypeParameter; import static dagger.internal.codegen.xprocessing.XExecutableTypes.asMethodType; import static dagger.internal.codegen.xprocessing.XExecutableTypes.getKindName; import static dagger.internal.codegen.xprocessing.XExecutableTypes.isMethodType; import static dagger.internal.codegen.xprocessing.XTypes.asArray; import static dagger.internal.codegen.xprocessing.XTypes.getKindName; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; import androidx.room.compiler.processing.XAnnotation; import androidx.room.compiler.processing.XAnnotationValue; import androidx.room.compiler.processing.XElement; import androidx.room.compiler.processing.XExecutableElement; import androidx.room.compiler.processing.XExecutableType; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XProcessingEnv.Backend; import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; import androidx.room.compiler.processing.compat.XConverters; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import dagger.Reusable; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.xprocessing.XAnnotationValues; import dagger.internal.codegen.xprocessing.XAnnotations; import dagger.internal.codegen.xprocessing.XElements; import dagger.internal.codegen.xprocessing.XExecutableTypes; import dagger.internal.codegen.xprocessing.XTypes; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; import javax.inject.Inject; /** * A fork of {@link com.google.auto.common.SuperficialValidation}. * *
This fork makes a couple changes from the original: * *
Validating the type also validates any types it references, such as any type arguments or * type bounds. For an {@link XExecutableType}, the parameter and return types must be fully * defined, as must types declared in a {@code throws} clause or in the bounds of any type * parameters. */ public void validateTypeOf(XElement element) { try { // In XProcessing, there is no generic way to get an element "asType" so we break this down // differently for different element kinds. if (isTypeElement(element)) { validateType(Ascii.toLowerCase(getKindName(element)), asTypeElement(element).getType()); } else if (isVariableElement(element)) { validateType( Ascii.toLowerCase(getKindName(element)) + " type", asVariable(element).getType()); } else if (isExecutable(element)) { validateExecutableType(asExecutable(element).getExecutableType()); } else if (isEnumEntry(element)) { validateType( Ascii.toLowerCase(getKindName(element)), asEnumEntry(element).getEnumTypeElement().getType()); } } catch (RuntimeException exception) { throw ValidationException.from(exception).append(element); } } /** * Validates the {@link XElement#getSuperType()} type of the given element. * *
Validating the type also validates any types it references, such as any type arguments or * type bounds. */ public void validateSuperTypeOf(XTypeElement element) { try { validateType("superclass", element.getSuperType()); } catch (RuntimeException exception) { throw ValidationException.from(exception).append(element); } } /** * Validates the {@link XExecutableElement#getThrownTypes()} types of the given element. * *
Validating the type also validates any types it references, such as any type arguments or * type bounds. */ public void validateThrownTypesOf(XExecutableElement element) { try { validateTypes("thrown type", element.getThrownTypes()); } catch (RuntimeException exception) { throw ValidationException.from(exception).append(element); } } /** * Validates the annotation types of the given element. * *
Note: this method does not validate annotation values. This method is useful if you care * about the annotation's annotations (e.g. to check for {@code Scope} or {@code Qualifier}). In * such cases, we just need to validate the annotation's type. */ public void validateAnnotationTypesOf(XElement element) { element .getAllAnnotations() .forEach(annotation -> validateAnnotationTypeOf(element, annotation)); } /** * Validates the type of the given annotation. * *
The annotation is assumed to be annotating the given element, but this is not checked. The * element is only in the error message if a {@link ValidatationException} is thrown. * *
Note: this method does not validate annotation values. This method is useful if you care * about the annotation's annotations (e.g. to check for {@code Scope} or {@code Qualifier}). In * such cases, we just need to validate the annotation's type. */ // TODO(bcorso): See CL/427767370 for suggestions to make this API clearer. public void validateAnnotationTypeOf(XElement element, XAnnotation annotation) { try { validateType("annotation type", annotation.getType()); } catch (RuntimeException exception) { throw ValidationException.from(exception).append(annotation).append(element); } } /** Validate the annotations of the given element. */ public void validateAnnotationsOf(XElement element) { try { validateAnnotations(element.getAllAnnotations()); } catch (RuntimeException exception) { throw ValidationException.from(exception).append(element); } } public void validateAnnotationOf(XElement element, XAnnotation annotation) { try { validateAnnotation(annotation); } catch (RuntimeException exception) { throw ValidationException.from(exception).append(element); } } /** * Validate the type hierarchy for the given type (with the given type description) within the * given element. * *
Validation includes all superclasses, interfaces, and type parameters of those types.
*/
public void validateTypeHierarchyOf(String typeDescription, XElement element, XType type) {
try {
validateTypeHierarchy(typeDescription, type);
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(element);
}
}
private void validateTypeHierarchy(String desc, XType type) {
validateType(desc, type);
try {
type.getSuperTypes().forEach(supertype -> validateTypeHierarchy("supertype", supertype));
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(desc, type);
}
}
/**
* Returns true if all of the given elements return true from {@link #validateElement(XElement)}.
*/
private void validateElements(Collection extends XElement> elements) {
elements.forEach(this::validateElement);
}
/**
* Returns true if all types referenced by the given element are defined. The exact meaning of
* this depends on the kind of element. For packages, it means that all annotations on the package
* are fully defined. For other element kinds, it means that types referenced by the element,
* anything it contains, and any of its annotations element are all defined.
*/
public void validateElement(XElement element) {
checkNotNull(element);
// Validate the annotations first since these are common to all element kinds. We don't
// need to wrap these in try-catch because the *Of() methods are already wrapped.
validateAnnotationsOf(element);
// Validate enclosed elements based on the given element's kind.
try {
if (isTypeElement(element)) {
XTypeElement typeElement = asTypeElement(element);
validateElements(typeElement.getTypeParameters());
validateTypes("interface", typeElement.getSuperInterfaces());
if (typeElement.getSuperType() != null) {
validateType("superclass", typeElement.getSuperType());
}
// TODO (b/286313067) move the logic to ComponentValidator once the validation logic is
// split into individual validators to satisfy different needs.
// Dagger doesn't use components' static method, therefore, they shouldn't be validated to
// be able to stop component generation.
if (typeElement.hasAnnotation(TypeNames.COMPONENT)) {
validateElements(
typeElement.getEnclosedElements().stream()
.filter(member -> !XElements.isStatic(member))
.collect(toImmutableList()));
} else {
validateElements(typeElement.getEnclosedElements());
}
} else if (isExecutable(element)) {
if (isMethod(element)) {
validateType("return type", asMethod(element).getReturnType());
}
XExecutableElement executableElement = asExecutable(element);
validateTypes("thrown type", executableElement.getThrownTypes());
validateElements(executableElement.getTypeParameters());
validateElements(executableElement.getParameters());
} else if (isTypeParameter(element)) {
validateTypes("bound type", asTypeParameter(element).getBounds());
}
} catch (RuntimeException exception) {
throw ValidationException.from(exception).append(element);
}
// Validate the type last. This allows errors on more specific elements to be caught above.
// E.g. errors on parameters will be attributed to the parameter elements rather than the method
// type, which generally leads to nicer error messages. We don't need to wrap these in try-catch
// because the *Of() methods are already wrapped.
validateTypeOf(element);
}
private void validateTypes(String desc, Collection extends XType> types) {
types.forEach(type -> validateType(desc, type));
}
/**
* Returns true if the given type is fully defined. This means that the type itself is defined, as
* are any types it references, such as any type arguments or type bounds.
*/
private void validateType(String desc, XType type) {
checkNotNull(type);
// TODO(b/242569252): Due to a bug in kotlinc, a TypeName may incorrectly contain a "$" instead
// of "." if the TypeName is requested before the type has been resolved. Furthermore,
// XProcessing will cache the incorrect TypeName so that further calls will still contain the
// "$" even after the type has been resolved. Thus, we try to resolve the type as early as
// possible to prevent using/caching the incorrect TypeName.
XTypes.resolveIfNeeded(type);
try {
if (isArray(type)) {
validateType("array component type", asArray(type).getComponentType());
} else if (isDeclared(type)) {
if (isStrictValidationEnabled) {
// There's a bug in TypeVisitor which will visit the visitDeclared() method rather than
// visitError() even when it's an ERROR kind. Thus, we check the kind directly here and
// fail validation if it's an ERROR kind (see b/213880825).
if (isErrorKind(type)) {
throw new ValidationException.KnownErrorType(type);
}
}
type.getTypeArguments().forEach(typeArg -> validateType("type argument", typeArg));
} else if (isWildcard(type)) {
if (type.extendsBound() != null) {
validateType("extends bound type", type.extendsBound());
}
} else if (isErrorKind(type)) {
throw new ValidationException.KnownErrorType(type);
}
} catch (RuntimeException e) {
throw ValidationException.from(e).append(desc, type);
}
}
// TODO(bcorso): Consider moving this over to XProcessing. There's some complication due to
// b/248552462 and the fact that XProcessing also uses the error.NonExistentClass type for invalid
// types in KSP, which we may want to keep as error kinds in KSP.
private boolean isErrorKind(XType type) {
// https://youtrack.jetbrains.com/issue/KT-34193/Kapt-CorrectErrorTypes-doesnt-work-for-generics
// XProcessing treats 'error.NonExistentClass' as an error type. However, due to the bug in KAPT
// (linked above), 'error.NonExistentClass' can still be referenced in the stub classes even
// when 'correctErrorTypes=true' is enabled. Thus, we can't treat 'error.NonExistentClass' as an
// actual error type, as that would completely prevent processing of stubs that exhibit this
// bug. This behavior also matches how things work in Javac, as 'error.NonExistentClass' is
// treated as a TypeKind.DECLARED rather than a TypeKind.ERROR since the type is a real class
// that exists on the classpath.
return type.isError()
&& !(processingEnv.getBackend() == Backend.JAVAC
&& type.getTypeName().toString().contentEquals("error.NonExistentClass"));
}
/**
* Returns true if the given type is fully defined. This means that the parameter and return types
* must be fully defined, as must types declared in a {@code throws} clause or in the bounds of
* any type parameters.
*/
private void validateExecutableType(XExecutableType type) {
try {
validateTypes("parameter type", type.getParameterTypes());
validateTypes("thrown type", type.getThrownTypes());
validateTypes("type variable", getTypeVariables(type));
if (isMethodType(type)) {
validateType("return type", asMethodType(type).getReturnType());
}
} catch (RuntimeException e) {
throw ValidationException.from(e).append(type);
}
}
private ImmutableList