dagger.internal.codegen.validation.DependencyRequestValidator Maven / Gradle / Ivy
Show all versions of dagger-compiler Show documentation
/*
* 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.XElementKt.isField;
import static androidx.room.compiler.processing.XElementKt.isTypeElement;
import static dagger.internal.codegen.base.FrameworkTypes.isFrameworkType;
import static dagger.internal.codegen.base.RequestKinds.extractKeyType;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedInjectionType;
import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType;
import static dagger.internal.codegen.xprocessing.XElements.asField;
import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType;
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.XElement;
import androidx.room.compiler.processing.XFieldElement;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import androidx.room.compiler.processing.XVariableElement;
import com.google.common.collect.ImmutableSet;
import dagger.internal.codegen.base.FrameworkTypes;
import dagger.internal.codegen.base.RequestKinds;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.model.RequestKind;
import dagger.internal.codegen.xprocessing.XTypes;
import java.util.Optional;
import javax.inject.Inject;
/** Validation for dependency requests. */
final class DependencyRequestValidator {
private final XProcessingEnv processingEnv;
private final MembersInjectionValidator membersInjectionValidator;
private final InjectionAnnotations injectionAnnotations;
private final KotlinMetadataUtil metadataUtil;
@Inject
DependencyRequestValidator(
XProcessingEnv processingEnv,
MembersInjectionValidator membersInjectionValidator,
InjectionAnnotations injectionAnnotations,
KotlinMetadataUtil metadataUtil) {
this.processingEnv = processingEnv;
this.membersInjectionValidator = membersInjectionValidator;
this.injectionAnnotations = injectionAnnotations;
this.metadataUtil = metadataUtil;
}
/**
* Adds an error if the given dependency request has more than one qualifier annotation or is a
* non-instance request with a wildcard type.
*/
void validateDependencyRequest(
ValidationReport.Builder report, XElement requestElement, XType requestType) {
if (requestElement.hasAnnotation(TypeNames.ASSISTED)) {
// Don't validate assisted parameters. These are not dependency requests.
return;
}
if (missingQualifierMetadata(requestElement)) {
report.addError(
"Unable to read annotations on an injected Kotlin property. "
+ "The Dagger compiler must also be applied to any project containing @Inject "
+ "properties.",
requestElement);
// Skip any further validation if we don't have valid metadata for a type that needs it.
return;
}
new Validator(report, requestElement, requestType).validate();
}
/**
* Returns {@code true} if a kotlin inject field is missing metadata about its qualifiers.
*
* See https://youtrack.jetbrains.com/issue/KT-34684.
*/
private boolean missingQualifierMetadata(XElement requestElement) {
if (isField(requestElement)) {
XFieldElement fieldElement = asField(requestElement);
// static/top-level injected fields are not supported,
// so no need to get qualifier from kotlin metadata
if (!fieldElement.isStatic()
&& isTypeElement(fieldElement.getEnclosingElement())
&& metadataUtil.hasMetadata(fieldElement)
&& metadataUtil.isMissingSyntheticPropertyForAnnotations(fieldElement)) {
Optional membersInjector =
Optional.ofNullable(
processingEnv.findTypeElement(
membersInjectorNameForType(asTypeElement(fieldElement.getEnclosingElement()))));
return !membersInjector.isPresent();
}
}
return false;
}
private final class Validator {
private final ValidationReport.Builder report;
private final XElement requestElement;
private final XType requestType;
private final ImmutableSet qualifiers;
Validator(ValidationReport.Builder report, XElement requestElement, XType requestType) {
this.report = report;
this.requestElement = requestElement;
this.requestType = requestType;
this.qualifiers = injectionAnnotations.getQualifiers(requestElement);
}
void validate() {
checkQualifiers();
checkType();
}
private void checkQualifiers() {
if (qualifiers.size() > 1) {
for (XAnnotation qualifier : qualifiers) {
report.addError(
"A single dependency request may not use more than one @Qualifier",
requestElement,
qualifier);
}
}
}
private void checkType() {
if (isFrameworkType(requestType) && isRawParameterizedType(requestType)) {
report.addError(
"Dagger does not support injecting raw type: " + XTypes.toStableString(requestType),
requestElement);
// If the requested type is a raw framework type then skip the remaining checks as they
// will just be noise.
return;
}
XType keyType = extractKeyType(requestType);
if (qualifiers.isEmpty() && isDeclared(keyType)) {
XTypeElement typeElement = keyType.getTypeElement();
if (isAssistedInjectionType(typeElement)) {
report.addError(
"Dagger does not support injecting @AssistedInject type, "
+ XTypes.toStableString(requestType)
+ ". Did you mean to inject its assisted factory type instead?",
requestElement);
}
RequestKind requestKind = RequestKinds.getRequestKind(requestType);
if (!(requestKind == RequestKind.INSTANCE || requestKind == RequestKind.PROVIDER)
&& isAssistedFactoryType(typeElement)) {
report.addError(
"Dagger does not support injecting Lazy, Producer, "
+ "or Produced when T is an @AssistedFactory-annotated type such as "
+ XTypes.toStableString(keyType),
requestElement);
}
}
if (isWildcard(keyType)) {
// TODO(ronshapiro): Explore creating this message using RequestKinds.
report.addError(
"Dagger does not support injecting Provider, Lazy, Producer, "
+ "or Produced when T is a wildcard type such as "
+ XTypes.toStableString(keyType),
requestElement);
}
if (isTypeOf(keyType, TypeNames.MEMBERS_INJECTOR)) {
if (keyType.getTypeArguments().isEmpty()) {
report.addError("Cannot inject a raw MembersInjector", requestElement);
} else {
report.addSubreport(
membersInjectionValidator.validateMembersInjectionRequest(
requestElement, keyType.getTypeArguments().get(0)));
}
}
}
}
/**
* Adds an error if the given dependency request is for a {@link dagger.producers.Producer} or
* {@link dagger.producers.Produced}.
*
* Only call this when processing a provision binding.
*/
// TODO(dpb): Should we disallow Producer entry points in non-production components?
void checkNotProducer(ValidationReport.Builder report, XVariableElement requestElement) {
XType requestType = requestElement.getType();
if (FrameworkTypes.isProducerType(requestType)) {
report.addError(
String.format(
"%s may only be injected in @Produces methods",
getSimpleName(requestType.getTypeElement())),
requestElement);
}
}
}