201 lines
8.2 KiB
Java
201 lines
8.2 KiB
Java
|
|
/*
|
||
|
|
* 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 androidx.room.compiler.processing.compat.XConverters.toJavac;
|
||
|
|
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.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.spi.model.RequestKind;
|
||
|
|
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. */
|
||
|
|
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(toJavac(fieldElement))
|
||
|
|
&& metadataUtil.isMissingSyntheticPropertyForAnnotations(toJavac(fieldElement))) {
|
||
|
|
Optional<XTypeElement> 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 XType keyType;
|
||
|
|
private final ImmutableSet<XAnnotation> qualifiers;
|
||
|
|
|
||
|
|
Validator(ValidationReport.Builder report, XElement requestElement, XType requestType) {
|
||
|
|
this.report = report;
|
||
|
|
this.requestElement = requestElement;
|
||
|
|
this.requestType = requestType;
|
||
|
|
this.keyType = extractKeyType(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 (qualifiers.isEmpty() && isDeclared(keyType)) {
|
||
|
|
XTypeElement typeElement = keyType.getTypeElement();
|
||
|
|
if (isAssistedInjectionType(typeElement)) {
|
||
|
|
report.addError(
|
||
|
|
"Dagger does not support injecting @AssistedInject type, "
|
||
|
|
+ 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<T>, Producer<T>, "
|
||
|
|
+ "or Produced<T> when T is an @AssistedFactory-annotated type such as "
|
||
|
|
+ keyType,
|
||
|
|
requestElement);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (isWildcard(keyType)) {
|
||
|
|
// TODO(ronshapiro): Explore creating this message using RequestKinds.
|
||
|
|
report.addError(
|
||
|
|
"Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, "
|
||
|
|
+ "or Produced<T> when T is a wildcard type such as "
|
||
|
|
+ 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}.
|
||
|
|
*
|
||
|
|
* <p>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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|