From c96fc3ed86c741b00c321623e279d42e6ecfca8e Mon Sep 17 00:00:00 2001 From: danysantiago Date: Tue, 3 Dec 2019 10:36:19 -0800 Subject: [PATCH] Propagate qualifiers for field injections in MemberInjectors. Having qualifiers in the generated MemberInjectors helps the root downstream processor read Kotlin property annotations that are lost across compilations due to https://youtrack.jetbrains.com/issue/KT-34684 When Dagger is applied to all subprojects with @Inject fields or constructors then this change fixes #1659. RELNOTES=Workaround missing Kotlin qualifiers annotations across compilation units by propagating qualifiers along with MemberInjectors when Dagger processor is applied to upstream projects. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=283574407 --- WORKSPACE | 21 ++ .../internal/InjectedFieldSignature.java | 32 ++ .../codegen/base/MoreAnnotationValues.java | 27 ++ .../codegen/binding/InjectionAnnotations.java | 68 +++- .../internal/codegen/binding/SourceFiles.java | 7 + .../internal/codegen/javapoet/TypeNames.java | 3 + .../codegen/kotlin/KotlinMetadata.java | 117 ++++--- .../codegen/kotlin/KotlinMetadataUtil.java | 12 + .../DependencyRequestValidator.java | 40 ++- .../codegen/writing/InjectionMethod.java | 18 ++ .../codegen/writing/InjectionMethods.java | 18 +- javatests/dagger/functional/kotlin/BUILD | 76 +++++ .../kotlin/FooWithInjectedQualifier.kt | 25 ++ .../functional/kotlin/JavaTestQualifier.java | 26 ++ .../kotlin/JavaTestQualifierWithTarget.java | 29 ++ .../functional/kotlin/ObjectModuleTest.java | 39 +++ .../kotlin/PropertyQualifierTest.java | 40 +++ .../PublicModuleWithNonPublicInclude.java | 23 ++ .../kotlin/TestComponentWithObjectModule.kt | 70 +++++ .../kotlin/TestComponentWithQualifier.kt | 79 +++++ .../functional/kotlin/TestKotlinClasses.kt | 26 ++ javatests/dagger/internal/codegen/BUILD | 14 +- .../codegen/KotlinInjectedQualifier.kt | 26 ++ .../codegen/MembersInjectionTest.java | 296 ++++++++++++------ .../MembersInjectionValidationTest.java | 35 +++ .../codegen/MethodSignatureFormatterTest.java | 30 +- 26 files changed, 1049 insertions(+), 148 deletions(-) create mode 100644 java/dagger/internal/InjectedFieldSignature.java create mode 100644 javatests/dagger/functional/kotlin/BUILD create mode 100644 javatests/dagger/functional/kotlin/FooWithInjectedQualifier.kt create mode 100644 javatests/dagger/functional/kotlin/JavaTestQualifier.java create mode 100644 javatests/dagger/functional/kotlin/JavaTestQualifierWithTarget.java create mode 100644 javatests/dagger/functional/kotlin/ObjectModuleTest.java create mode 100644 javatests/dagger/functional/kotlin/PropertyQualifierTest.java create mode 100644 javatests/dagger/functional/kotlin/PublicModuleWithNonPublicInclude.java create mode 100644 javatests/dagger/functional/kotlin/TestComponentWithObjectModule.kt create mode 100644 javatests/dagger/functional/kotlin/TestComponentWithQualifier.kt create mode 100644 javatests/dagger/functional/kotlin/TestKotlinClasses.kt create mode 100644 javatests/dagger/internal/codegen/KotlinInjectedQualifier.kt diff --git a/WORKSPACE b/WORKSPACE index 24f3e1f671b..392e6170e31 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -60,3 +60,24 @@ maven_install( "https://maven.google.com", ], ) + +# TODO(user): Remove once Google publishes internal Kotlin rules. +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +RULES_KOTLIN_VERSION = "legacy-1.3.0-rc1" + +RULES_KOTLIN_SHA = "9de078258235ea48021830b1669bbbb678d7c3bdffd3435f4c0817c921a88e42" + +http_archive( + name = "io_bazel_rules_kotlin", + sha256 = RULES_KOTLIN_SHA, + strip_prefix = "rules_kotlin-%s" % RULES_KOTLIN_VERSION, + type = "zip", + urls = ["https://github.com/bazelbuild/rules_kotlin/archive/%s.zip" % RULES_KOTLIN_VERSION], +) + +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories", "kt_register_toolchains") + +kotlin_repositories() + +kt_register_toolchains() diff --git a/java/dagger/internal/InjectedFieldSignature.java b/java/dagger/internal/InjectedFieldSignature.java new file mode 100644 index 00000000000..55d3285c99f --- /dev/null +++ b/java/dagger/internal/InjectedFieldSignature.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a {@link dagger.MembersInjector} method for injecting a field with the signature of the + * field intended to inject. + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.METHOD) +public @interface InjectedFieldSignature { + String value(); +} diff --git a/java/dagger/internal/codegen/base/MoreAnnotationValues.java b/java/dagger/internal/codegen/base/MoreAnnotationValues.java index 463885aab2c..d2a9d33b9bd 100644 --- a/java/dagger/internal/codegen/base/MoreAnnotationValues.java +++ b/java/dagger/internal/codegen/base/MoreAnnotationValues.java @@ -16,8 +16,11 @@ package dagger.internal.codegen.base; +import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; + import com.google.common.collect.ImmutableList; import java.util.List; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValueVisitor; import javax.lang.model.type.TypeMirror; @@ -71,5 +74,29 @@ protected TypeMirror defaultAction(Object o, Void p) { } }; + /** Returns the int value of an annotation */ + public static int getIntValue(AnnotationMirror annotation, String valueName) { + return (int) getAnnotationValue(annotation, valueName).getValue(); + } + + /** Returns the String value of an annotation */ + public static String getStringValue(AnnotationMirror annotation, String valueName) { + return (String) getAnnotationValue(annotation, valueName).getValue(); + } + + /** Returns the int array value of an annotation */ + public static int[] getIntArrayValue(AnnotationMirror annotation, String valueName) { + return asAnnotationValues(getAnnotationValue(annotation, valueName)).stream() + .mapToInt(it -> (int) it.getValue()) + .toArray(); + } + + /** Returns the String array value of an annotation */ + public static String[] getStringArrayValue(AnnotationMirror annotation, String valueName) { + return asAnnotationValues(getAnnotationValue(annotation, valueName)).stream() + .map(it -> (String) it.getValue()) + .toArray(String[]::new); + } + private MoreAnnotationValues() {} } diff --git a/java/dagger/internal/codegen/binding/InjectionAnnotations.java b/java/dagger/internal/codegen/binding/InjectionAnnotations.java index 57f5319e60c..8a22d50aca7 100644 --- a/java/dagger/internal/codegen/binding/InjectionAnnotations.java +++ b/java/dagger/internal/codegen/binding/InjectionAnnotations.java @@ -16,17 +16,25 @@ package dagger.internal.codegen.binding; +import static com.google.auto.common.MoreElements.asType; +import static com.google.auto.common.MoreElements.asVariable; import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Preconditions.checkNotNull; +import static dagger.internal.codegen.base.MoreAnnotationValues.getStringValue; +import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable; +import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; +import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; import static javax.lang.model.util.ElementFilter.constructorsIn; import com.google.auto.common.AnnotationMirrors; -import com.google.auto.common.MoreElements; import com.google.auto.common.SuperficialValidation; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.MoreCollectors; import com.google.common.collect.Sets; +import dagger.internal.InjectedFieldSignature; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; +import dagger.internal.codegen.langmodel.DaggerElements; import java.util.Optional; import javax.inject.Inject; import javax.inject.Qualifier; @@ -35,13 +43,17 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.ElementFilter; /** Utilities relating to annotations defined in the {@code javax.inject} package. */ public final class InjectionAnnotations { + private final DaggerElements elements; private final KotlinMetadataUtil kotlinMetadataUtil; @Inject - InjectionAnnotations(KotlinMetadataUtil kotlinMetadataUtil) { + InjectionAnnotations(DaggerElements elements, KotlinMetadataUtil kotlinMetadataUtil) { + this.elements = elements; this.kotlinMetadataUtil = kotlinMetadataUtil; } @@ -65,11 +77,10 @@ public Optional getQualifier(Element e) { public ImmutableSet getQualifiers(Element element) { ImmutableSet qualifiers = AnnotationMirrors.getAnnotatedAnnotations(element, Qualifier.class); - if (kotlinMetadataUtil.hasMetadata(element) && element.getKind() == ElementKind.FIELD) { - return Sets.union( - qualifiers, - kotlinMetadataUtil.getSyntheticPropertyAnnotations( - MoreElements.asVariable(element), Qualifier.class)) + if (element.getKind() == ElementKind.FIELD + && isAnnotationPresent(element, Inject.class) + && kotlinMetadataUtil.hasMetadata(element)) { + return Sets.union(qualifiers, getQualifiersForKotlinProperty(asVariable(element))) .immutableCopy(); } else { return qualifiers; @@ -82,4 +93,47 @@ public static ImmutableSet injectedConstructors(TypeElement t .filter(constructor -> isAnnotationPresent(constructor, Inject.class)) .toSet(); } + + /** + * Gets the qualifiers annotation of a Kotlin Property. Finding these annotations involve finding + * the synthetic method for annotations as described by the Kotlin metadata or finding the + * corresponding MembersInjector method for the field, which also contains the qualifier + * annotation. + */ + private ImmutableSet getQualifiersForKotlinProperty( + VariableElement fieldElement) { + // TODO(user): Consider moving this to KotlinMetadataUtil + if (kotlinMetadataUtil.isMissingSyntheticPropertyForAnnotations(fieldElement)) { + // If we detect that the synthetic method for annotations is missing, possibly due to the + // element being from a compiled class, then find the MembersInjector that was generated + // for the enclosing class and extract the qualifier information from it. + TypeElement membersInjector = + elements.getTypeElement( + membersInjectorNameForType(asType(fieldElement.getEnclosingElement()))); + if (membersInjector != null) { + String memberInjectedFieldSignature = memberInjectedFieldSignatureForVariable(fieldElement); + // TODO(user): We have to iterate over all the injection methods for every qualifier + // look up. Making this N^2 when looking through all the injected fields. :( + return ElementFilter.methodsIn(membersInjector.getEnclosedElements()).stream() + .filter( + method -> + getAnnotationMirror(method, InjectedFieldSignature.class) + .map(annotation -> getStringValue(annotation, "value")) + .map(memberInjectedFieldSignature::equals) + // If a method is not an @InjectedFieldSignature method then filter it out + .orElse(false)) + .collect(MoreCollectors.toOptional()) + .map(this::getQualifiers) + .orElseThrow( + () -> + new IllegalStateException( + "No matching InjectedFieldSignature for " + memberInjectedFieldSignature)); + } else { + throw new IllegalStateException( + "No MembersInjector found for " + fieldElement.getEnclosingElement()); + } + } else { + return kotlinMetadataUtil.getSyntheticPropertyAnnotations(fieldElement, Qualifier.class); + } + } } diff --git a/java/dagger/internal/codegen/binding/SourceFiles.java b/java/dagger/internal/codegen/binding/SourceFiles.java index 3dc70a33c48..30bc3bd58c6 100644 --- a/java/dagger/internal/codegen/binding/SourceFiles.java +++ b/java/dagger/internal/codegen/binding/SourceFiles.java @@ -66,6 +66,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; /** Utilities for generating files. */ public class SourceFiles { @@ -186,6 +187,12 @@ public static ClassName membersInjectorNameForType(TypeElement typeElement) { return siblingClassName(typeElement, "_MembersInjector"); } + public static String memberInjectedFieldSignatureForVariable(VariableElement variableElement) { + return MoreElements.asType(variableElement.getEnclosingElement()).getQualifiedName() + + "." + + variableElement.getSimpleName(); + } + public static String classFileName(ClassName className) { return CLASS_FILE_NAME_JOINER.join(className.simpleNames()); } diff --git a/java/dagger/internal/codegen/javapoet/TypeNames.java b/java/dagger/internal/codegen/javapoet/TypeNames.java index 3a3d5c9b326..71f03f0e42f 100644 --- a/java/dagger/internal/codegen/javapoet/TypeNames.java +++ b/java/dagger/internal/codegen/javapoet/TypeNames.java @@ -25,6 +25,7 @@ import dagger.MembersInjector; import dagger.internal.DoubleCheck; import dagger.internal.Factory; +import dagger.internal.InjectedFieldSignature; import dagger.internal.InstanceFactory; import dagger.internal.MapFactory; import dagger.internal.MapProviderFactory; @@ -58,6 +59,8 @@ public final class TypeNames { public static final ClassName DOUBLE_CHECK = ClassName.get(DoubleCheck.class); public static final ClassName FACTORY = ClassName.get(Factory.class); public static final ClassName FUTURES = ClassName.get(Futures.class); + public static final ClassName INJECTED_FIELD_SIGNATURE = + ClassName.get(InjectedFieldSignature.class); public static final ClassName INSTANCE_FACTORY = ClassName.get(InstanceFactory.class); public static final ClassName LAZY = ClassName.get(Lazy.class); public static final ClassName LIST = ClassName.get(List.class); diff --git a/java/dagger/internal/codegen/kotlin/KotlinMetadata.java b/java/dagger/internal/codegen/kotlin/KotlinMetadata.java index 1e5e86460a7..3e010ecd2ce 100644 --- a/java/dagger/internal/codegen/kotlin/KotlinMetadata.java +++ b/java/dagger/internal/codegen/kotlin/KotlinMetadata.java @@ -16,15 +16,19 @@ package dagger.internal.codegen.kotlin; -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Preconditions.checkArgument; -import static dagger.internal.codegen.base.MoreAnnotationValues.asAnnotationValues; +import static dagger.internal.codegen.base.MoreAnnotationValues.getIntArrayValue; +import static dagger.internal.codegen.base.MoreAnnotationValues.getIntValue; +import static dagger.internal.codegen.base.MoreAnnotationValues.getStringArrayValue; +import static dagger.internal.codegen.base.MoreAnnotationValues.getStringValue; import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; +import static dagger.internal.codegen.langmodel.DaggerElements.getFieldDescriptor; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import com.google.common.collect.MoreCollectors; import dagger.internal.codegen.langmodel.DaggerElements; import java.util.ArrayList; import java.util.List; @@ -63,7 +67,7 @@ final class KotlinMetadata { private final int flags; // Map that associates @Inject field elements with its Kotlin synthetic method for annotations. - private final Supplier>> + private final Supplier>> elementFieldAnnotationMethodMap; private KotlinMetadata(TypeElement typeElement, int flags, List properties) { @@ -83,20 +87,41 @@ private KotlinMetadata(TypeElement typeElement, int flags, List proper .collect( Collectors.toMap( DaggerElements::getMethodDescriptor, Function.identity())); - return ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream() - .filter(field -> isAnnotationPresent(field, Inject.class)) - .collect( - Collectors.toMap( - Function.identity(), - field -> - Optional.ofNullable( - propertyDescriptors.get( - DaggerElements.getFieldDescriptor(field))) - .flatMap(Property::getMethodForAnnotationsSignature) - .map(methodDescriptors::get))); + return mapFieldToAnnotationMethod(propertyDescriptors, methodDescriptors); }); } + private Map> mapFieldToAnnotationMethod( + Map propertyDescriptors, Map methodDescriptors) { + return ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream() + .filter(field -> isAnnotationPresent(field, Inject.class)) + .collect( + Collectors.toMap( + Function.identity(), + field -> + findProperty(field, propertyDescriptors) + .getMethodForAnnotationsSignature() + .map( + signature -> + Optional.ofNullable(methodDescriptors.get(signature)) + .map(MethodForAnnotations::new) + // The method may be missing across different compilations. + // See https://youtrack.jetbrains.com/issue/KT-34684 + .orElse(MethodForAnnotations.MISSING)))); + } + + private Property findProperty(VariableElement field, Map propertyDescriptors) { + String fieldDescriptor = getFieldDescriptor(field); + if (propertyDescriptors.containsKey(fieldDescriptor)) { + return propertyDescriptors.get(fieldDescriptor); + } else { + // Fallback to finding property by name, see: https://youtrack.jetbrains.com/issue/KT-35124 + return propertyDescriptors.values().stream() + .filter(property -> field.getSimpleName().contentEquals(property.name)) + .collect(MoreCollectors.onlyElement()); + } + } + TypeElement getTypeElement() { return typeElement; } @@ -104,7 +129,33 @@ TypeElement getTypeElement() { /** Gets the synthetic method for annotations of a given @Inject annotated field element. */ Optional getSyntheticAnnotationMethod(VariableElement fieldElement) { checkArgument(elementFieldAnnotationMethodMap.get().containsKey(fieldElement)); - return elementFieldAnnotationMethodMap.get().get(fieldElement); + return elementFieldAnnotationMethodMap + .get() + .get(fieldElement) + .map( + methodForAnnotations -> { + if (methodForAnnotations == MethodForAnnotations.MISSING) { + throw new IllegalStateException( + "Method for annotations is missing for " + fieldElement); + } + return methodForAnnotations.getMethod(); + }); + } + + /** + * Returns true if the synthetic method for annotations is missing. This can occur when inspecting + * the Kotlin metadata of a property from another compilation unit. + */ + boolean isMissingSyntheticAnnotationMethod(VariableElement fieldElement) { + checkArgument(elementFieldAnnotationMethodMap.get().containsKey(fieldElement)); + return elementFieldAnnotationMethodMap + .get() + .get(fieldElement) + .map(methodForAnnotations -> methodForAnnotations == MethodForAnnotations.MISSING) + // This can be missing if there was no property annotation at all (e.g. no annotations or + // the qualifier is already properly attached to the field). For these cases, it isn't + // considered missing since there was no method to look for in the first place. + .orElse(false); } boolean isObjectClass() { @@ -150,26 +201,6 @@ private static Optional metadataOf(TypeElement typeEl } } - private static int getIntValue(AnnotationMirror annotation, String valueName) { - return (int) getAnnotationValue(annotation, valueName).getValue(); - } - - private static String getStringValue(AnnotationMirror annotation, String valueName) { - return getAnnotationValue(annotation, valueName).getValue().toString(); - } - - private static int[] getIntArrayValue(AnnotationMirror annotation, String valueName) { - return asAnnotationValues(getAnnotationValue(annotation, valueName)).stream() - .mapToInt(it -> (int) it.getValue()) - .toArray(); - } - - private static String[] getStringArrayValue(AnnotationMirror annotation, String valueName) { - return asAnnotationValues(getAnnotationValue(annotation, valueName)).stream() - .map(it -> it.getValue().toString()) - .toArray(String[]::new); - } - private static final class MetadataVisitor extends KmClassVisitor { int classFlags; @@ -270,4 +301,20 @@ Optional getMethodForAnnotationsSignature() { return methodForAnnotationsSignature; } } + + /* Data class that wraps the Kotlin property executable element for annotations */ + private static final class MethodForAnnotations { + + static final MethodForAnnotations MISSING = new MethodForAnnotations(null); + + private final ExecutableElement method; + + MethodForAnnotations(ExecutableElement method) { + this.method = method; + } + + public ExecutableElement getMethod() { + return method; + } + } } diff --git a/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java b/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java index 7e71ac3b671..09636f895a7 100644 --- a/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java +++ b/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java @@ -62,6 +62,18 @@ public ImmutableSet getSyntheticPropertyAnnotations( .orElse(ImmutableSet.of()); } + /** + * Returns true if the synthetic method for annotations is missing. This can occur when the Kotlin + * metadata of the property reports that it contains a synthetic method for annotations but such + * method is not found since it is synthetic and ignored by the processor. + */ + public boolean isMissingSyntheticPropertyForAnnotations(VariableElement fieldElement) { + return metadataFactory + .create(fieldElement) + .map(metadata -> metadata.isMissingSyntheticAnnotationMethod(fieldElement)) + .orElseThrow(() -> new IllegalStateException("Missing metadata for: " + fieldElement)); + } + /** Returns true if this type element is a Kotlin Object. */ public boolean isObjectClass(TypeElement typeElement) { return metadataFactory.create(typeElement).map(KotlinMetadata::isObjectClass).orElse(false); diff --git a/java/dagger/internal/codegen/validation/DependencyRequestValidator.java b/java/dagger/internal/codegen/validation/DependencyRequestValidator.java index 9a173c2316e..bc6db05c3ff 100644 --- a/java/dagger/internal/codegen/validation/DependencyRequestValidator.java +++ b/java/dagger/internal/codegen/validation/DependencyRequestValidator.java @@ -16,8 +16,11 @@ package dagger.internal.codegen.validation; +import static com.google.auto.common.MoreElements.asType; +import static com.google.auto.common.MoreElements.asVariable; import static dagger.internal.codegen.base.RequestKinds.extractKeyType; import static dagger.internal.codegen.base.RequestKinds.getRequestKind; +import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; import static javax.lang.model.type.TypeKind.WILDCARD; import com.google.auto.common.MoreTypes; @@ -25,9 +28,14 @@ import dagger.MembersInjector; import dagger.internal.codegen.base.FrameworkTypes; import dagger.internal.codegen.binding.InjectionAnnotations; +import dagger.internal.codegen.kotlin.KotlinMetadataUtil; +import dagger.internal.codegen.langmodel.DaggerElements; +import java.util.Optional; import javax.inject.Inject; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; @@ -36,13 +44,19 @@ final class DependencyRequestValidator { private final MembersInjectionValidator membersInjectionValidator; private final InjectionAnnotations injectionAnnotations; + private final KotlinMetadataUtil metadataUtil; + private final DaggerElements elements; @Inject DependencyRequestValidator( MembersInjectionValidator membersInjectionValidator, - InjectionAnnotations injectionAnnotations) { + InjectionAnnotations injectionAnnotations, + KotlinMetadataUtil metadataUtil, + DaggerElements elements) { this.membersInjectionValidator = membersInjectionValidator; this.injectionAnnotations = injectionAnnotations; + this.metadataUtil = metadataUtil; + this.elements = elements; } /** @@ -51,6 +65,27 @@ final class DependencyRequestValidator { */ void validateDependencyRequest( ValidationReport.Builder report, Element requestElement, TypeMirror requestType) { + checkQualifiers(report, requestElement); + checkType(report, requestElement, requestType); + } + + private void checkQualifiers(ValidationReport.Builder report, Element requestElement) { + if (requestElement.getKind() == ElementKind.FIELD + && metadataUtil.hasMetadata(requestElement) + && metadataUtil.isMissingSyntheticPropertyForAnnotations(asVariable(requestElement))) { + Optional membersInjector = + Optional.ofNullable( + elements.getTypeElement( + membersInjectorNameForType(asType(requestElement.getEnclosingElement())))); + if (!membersInjector.isPresent()) { + 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); + return; // finish checking qualifiers since current information is unreliable. + } + } + ImmutableSet qualifiers = injectionAnnotations.getQualifiers(requestElement); if (qualifiers.size() > 1) { @@ -61,7 +96,10 @@ void validateDependencyRequest( qualifier); } } + } + private void checkType( + ValidationReport.Builder report, Element requestElement, TypeMirror requestType) { TypeMirror keyType = extractKeyType(getRequestKind(requestType), requestType); if (keyType.getKind().equals(WILDCARD)) { // TODO(ronshapiro): Explore creating this message using RequestKinds. diff --git a/java/dagger/internal/codegen/writing/InjectionMethod.java b/java/dagger/internal/codegen/writing/InjectionMethod.java index 105ec1fb1da..047bc8bff01 100644 --- a/java/dagger/internal/codegen/writing/InjectionMethod.java +++ b/java/dagger/internal/codegen/writing/InjectionMethod.java @@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; +import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; @@ -37,6 +38,7 @@ import com.squareup.javapoet.TypeVariableName; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.javapoet.CodeBlocks; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import java.util.List; import java.util.Optional; @@ -80,6 +82,10 @@ abstract class InjectionMethod { abstract ClassName enclosingClass(); + abstract Optional injectedFieldSignature(); + + abstract Optional qualifierSpec(); + MethodSpec toMethodSpec() { MethodSpec.Builder builder = methodBuilder(name()) @@ -91,6 +97,14 @@ MethodSpec toMethodSpec() { returnType().map(TypeName::get).ifPresent(builder::returns); nullableAnnotation().ifPresent(nullableType -> CodeBlocks.addAnnotation(builder, nullableType)); exceptions().stream().map(TypeName::get).forEach(builder::addException); + injectedFieldSignature() + .ifPresent( + fieldSignature -> + builder.addAnnotation( + AnnotationSpec.builder(TypeNames.INJECTED_FIELD_SIGNATURE) + .addMember("value", "$S", fieldSignature) + .build())); + qualifierSpec().ifPresent(builder::addAnnotation); return builder.build(); } @@ -143,6 +157,10 @@ abstract static class Builder { abstract Builder methodBody(CodeBlock methodBody); + abstract Builder injectedFieldSignature(String injectedFieldSignature); + + abstract Builder qualifierSpec(AnnotationSpec qualifierSpec); + final CodeBlock.Builder methodBodyBuilder() { return methodBody; } diff --git a/java/dagger/internal/codegen/writing/InjectionMethods.java b/java/dagger/internal/codegen/writing/InjectionMethods.java index 596f8fefe03..80e1c607dc5 100644 --- a/java/dagger/internal/codegen/writing/InjectionMethods.java +++ b/java/dagger/internal/codegen/writing/InjectionMethods.java @@ -19,9 +19,11 @@ import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.MoreCollectors.onlyElement; import static dagger.internal.codegen.base.RequestKinds.requestTypeName; import static dagger.internal.codegen.binding.ConfigurationAnnotations.getNullableType; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; +import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable; import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.javapoet.CodeBlocks.toConcatenatedCodeBlock; @@ -37,6 +39,7 @@ import com.google.auto.common.MoreElements; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.ParameterSpec; @@ -55,6 +58,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -270,10 +274,16 @@ static InjectionMethod create( elements, metadataUtil); case FIELD: + Optional qualifier = + injectionSite.dependencies().stream() + .collect(onlyElement()) // methods for fields have a single dependency request + .key() + .qualifier(); return fieldProxy( proxyEnclosingClass, MoreElements.asVariable(injectionSite.element()), methodName, + qualifier, elements); } throw new AssertionError(injectionSite); @@ -545,10 +555,16 @@ private static InjectionMethod fieldProxy( ClassName proxyEnclosingClass, VariableElement field, String methodName, + Optional qualifierAnnotation, DaggerElements elements) { TypeElement enclosingType = MoreElements.asType(field.getEnclosingElement()); InjectionMethod.Builder injectionMethod = - InjectionMethod.builder(elements).name(methodName).enclosingClass(proxyEnclosingClass); + InjectionMethod.builder(elements) + .name(methodName) + .enclosingClass(proxyEnclosingClass) + .injectedFieldSignature(memberInjectedFieldSignatureForVariable(field)); + qualifierAnnotation.ifPresent( + qualifier -> injectionMethod.qualifierSpec(AnnotationSpec.get(qualifier))); injectionMethod.copyTypeParameters(enclosingType); ParameterSpec instance = diff --git a/javatests/dagger/functional/kotlin/BUILD b/javatests/dagger/functional/kotlin/BUILD new file mode 100644 index 00000000000..14dbd743eec --- /dev/null +++ b/javatests/dagger/functional/kotlin/BUILD @@ -0,0 +1,76 @@ +# Copyright (C) 2019 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. +# +# Description: +# Functional test code for Dagger-Android + +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") +load("//:test_defs.bzl", "GenJavaTests") + +package(default_visibility = ["//:src"]) + +kt_jvm_library( + name = "kotlin", + srcs = glob( + [ + "*.java", + "*.kt", + ], + exclude = [ + "*Test.java", + "JavaTestQualifier.java", + "FooWithInjectedQualifier.kt", + ], + ), + deps = [ + ":foo_with_injected_qualifier", + ":java_qualifier", + "//:dagger_with_compiler", + ], +) + +kt_jvm_library( + name = "foo_with_injected_qualifier", + srcs = ["FooWithInjectedQualifier.kt"], + deps = [ + ":java_qualifier", + "//:dagger_with_compiler", + ], +) + +java_library( + name = "java_qualifier", + srcs = ["JavaTestQualifier.java"], + deps = [ + "//:dagger_with_compiler", + ], +) + +GenJavaTests( + name = "kotlin_tests", + srcs = glob(["*Test.java"]), + functional = True, + test_only_deps = [ + "//java/dagger/internal/guava:annotations", + "//java/dagger/internal/guava:base", + "//java/dagger/internal/guava:collect", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + ], + deps = [ + ":foo_with_injected_qualifier", + ":kotlin", + "//:dagger_with_compiler", + ], +) diff --git a/javatests/dagger/functional/kotlin/FooWithInjectedQualifier.kt b/javatests/dagger/functional/kotlin/FooWithInjectedQualifier.kt new file mode 100644 index 00000000000..990d5e818e2 --- /dev/null +++ b/javatests/dagger/functional/kotlin/FooWithInjectedQualifier.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 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.functional.kotlin + +import javax.inject.Inject + +class FooWithInjectedQualifier { + @Inject + @JavaTestQualifier + lateinit var qualifiedString: String +} diff --git a/javatests/dagger/functional/kotlin/JavaTestQualifier.java b/javatests/dagger/functional/kotlin/JavaTestQualifier.java new file mode 100644 index 00000000000..156f4361619 --- /dev/null +++ b/javatests/dagger/functional/kotlin/JavaTestQualifier.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 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.functional.kotlin; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import javax.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +public @interface JavaTestQualifier {} diff --git a/javatests/dagger/functional/kotlin/JavaTestQualifierWithTarget.java b/javatests/dagger/functional/kotlin/JavaTestQualifierWithTarget.java new file mode 100644 index 00000000000..3bc2e4ea36c --- /dev/null +++ b/javatests/dagger/functional/kotlin/JavaTestQualifierWithTarget.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 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.functional.kotlin; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface JavaTestQualifierWithTarget {} diff --git a/javatests/dagger/functional/kotlin/ObjectModuleTest.java b/javatests/dagger/functional/kotlin/ObjectModuleTest.java new file mode 100644 index 00000000000..a37be44ec38 --- /dev/null +++ b/javatests/dagger/functional/kotlin/ObjectModuleTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 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.functional.kotlin; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ObjectModuleTest { + + @Test + public void verifyObjectModule() { + TestKotlinComponentWithObjectModule component = + DaggerTestKotlinComponentWithObjectModule.create(); + assertThat(component.getDataA()).isNotNull(); + assertThat(component.getDataAFromNestedModule()).isNotNull(); + assertThat(component.getDataB()).isNotNull(); + assertThat(component.getSetOfDataA()).isNotNull(); + assertThat(component.getSetOfDataA()).hasSize(1); + assertThat(component.getPrimitiveType()).isTrue(); + } +} diff --git a/javatests/dagger/functional/kotlin/PropertyQualifierTest.java b/javatests/dagger/functional/kotlin/PropertyQualifierTest.java new file mode 100644 index 00000000000..16efae70462 --- /dev/null +++ b/javatests/dagger/functional/kotlin/PropertyQualifierTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 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.functional.kotlin; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class PropertyQualifierTest { + + @Test + public void verifyQualifiedBinding() { + TestMemberInjectedClassWithQualifier injectedClass = new TestMemberInjectedClassWithQualifier(); + DaggerTestKotlinComponentWithQualifier.create().inject(injectedClass); + + assertThat(injectedClass.javaDataA).isNotNull(); + assertThat(injectedClass.javaDataB).isNotNull(); + assertThat(injectedClass.javaWithTargetDataA).isNotNull(); + assertThat(injectedClass.kotlinDataA).isNotNull(); + assertThat(injectedClass.dataWithConstructionInjection).isNotNull(); + assertThat(injectedClass.dataWithConstructionInjection.getData()).isNotNull(); + } +} diff --git a/javatests/dagger/functional/kotlin/PublicModuleWithNonPublicInclude.java b/javatests/dagger/functional/kotlin/PublicModuleWithNonPublicInclude.java new file mode 100644 index 00000000000..f927186be8c --- /dev/null +++ b/javatests/dagger/functional/kotlin/PublicModuleWithNonPublicInclude.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019 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.functional.kotlin; + +import dagger.Module; + +/** Verifies that a non-public included Kotlin object module does not fail compilation. */ +@Module(includes = {NonPublicObjectModule.class}) +public class PublicModuleWithNonPublicInclude {} diff --git a/javatests/dagger/functional/kotlin/TestComponentWithObjectModule.kt b/javatests/dagger/functional/kotlin/TestComponentWithObjectModule.kt new file mode 100644 index 00000000000..e390abaea69 --- /dev/null +++ b/javatests/dagger/functional/kotlin/TestComponentWithObjectModule.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 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.functional.kotlin + +import dagger.Component +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoSet +import javax.inject.Named + +@Component( + modules = [ + TestKotlinObjectModule::class, + TestModuleForNesting.TestNestedKotlinObjectModule::class + ] +) +interface TestKotlinComponentWithObjectModule { + fun getDataA(): TestDataA + @Named("nested-data-a") + fun getDataAFromNestedModule(): TestDataA + fun getDataB(): TestDataB + fun getSetOfDataA(): Set + fun getPrimitiveType(): Boolean +} + +@Module +object TestKotlinObjectModule { + @Provides + fun provideDataA() = TestDataA("test") + + @Provides + fun providePrimitiveType(): Boolean = true + + @Provides + @JvmStatic + fun provideDataB() = TestDataB("test") + + @Provides + @IntoSet + fun provideIntoMapDataA() = TestDataA("set-test") +} + +class TestModuleForNesting { + @Module + object TestNestedKotlinObjectModule { + @Provides + @Named("nested-data-a") + fun provideDataA() = TestDataA("test") + } +} + +@Module +private object NonPublicObjectModule { + @Provides + fun provideInt() = 42 +} diff --git a/javatests/dagger/functional/kotlin/TestComponentWithQualifier.kt b/javatests/dagger/functional/kotlin/TestComponentWithQualifier.kt new file mode 100644 index 00000000000..15856f7b6b0 --- /dev/null +++ b/javatests/dagger/functional/kotlin/TestComponentWithQualifier.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 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.functional.kotlin + +import dagger.Component +import dagger.Module +import dagger.Provides +import javax.inject.Inject + +@Component(modules = [TestKotlinModuleWithQualifier::class]) +interface TestKotlinComponentWithQualifier { + fun inject(testInjectedClassWithQualifier: TestMemberInjectedClassWithQualifier) + fun inject(fooWithInjectedQualifier: FooWithInjectedQualifier) +} + +@Module +class TestKotlinModuleWithQualifier { + @Provides + @JavaTestQualifier + fun provideJavaDataA() = TestDataA("test") + + @Provides + @JavaTestQualifier + fun provideJavaDataB() = TestDataB("test") + + @Provides + @JavaTestQualifierWithTarget + fun provideJavaWithTargetDataA() = TestDataA("test") + + @Provides + @KotlinTestQualifier + fun provideKotlinDataA() = TestDataA("test") + + @Provides + @JavaTestQualifier + fun provideString() = "qualified string" +} + +class TestConstructionInjectedClassWithQualifier @Inject constructor( + @JavaTestQualifier val data: TestDataA +) + +class TestMemberInjectedClassWithQualifier { + @Inject + @JavaTestQualifier + lateinit var javaDataA: TestDataA + + @Inject + @field:JavaTestQualifier + lateinit var javaDataB: TestDataB + + @Inject + @JavaTestQualifierWithTarget + lateinit var javaWithTargetDataA: TestDataA + + @Inject + @JavaTestQualifier + lateinit var kotlinDataA: TestDataA + + @Inject + lateinit var dataWithConstructionInjection: TestConstructionInjectedClassWithQualifier + + val noBackingFieldProperty: Int + get() = 0 +} diff --git a/javatests/dagger/functional/kotlin/TestKotlinClasses.kt b/javatests/dagger/functional/kotlin/TestKotlinClasses.kt new file mode 100644 index 00000000000..9cf8c73291d --- /dev/null +++ b/javatests/dagger/functional/kotlin/TestKotlinClasses.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 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.functional.kotlin + +import javax.inject.Qualifier + +data class TestDataA(val data: String) +data class TestDataB(val data: String) + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class KotlinTestQualifier diff --git a/javatests/dagger/internal/codegen/BUILD b/javatests/dagger/internal/codegen/BUILD index 3658a473901..1766961911d 100644 --- a/javatests/dagger/internal/codegen/BUILD +++ b/javatests/dagger/internal/codegen/BUILD @@ -15,11 +15,20 @@ # Description: # Tests for the Dagger compiler/codegen -package(default_visibility = ["//:src"]) - +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX") load("//:test_defs.bzl", "GenJavaTests") +package(default_visibility = ["//:src"]) + +kt_jvm_library( + name = "kotlin_injected_qualifier", + srcs = ["KotlinInjectedQualifier.kt"], + deps = [ + "//java/dagger:core", + ], +) + GenJavaTests( name = "compiler_tests", srcs = glob(["*.java"]), @@ -27,6 +36,7 @@ GenJavaTests( javacopts = DOCLINT_HTML_AND_SYNTAX, plugins = ["//java/dagger/internal/codegen/bootstrap"], deps = [ + ":kotlin_injected_qualifier", "//java/dagger:core", "//java/dagger/internal/codegen:package_info", "//java/dagger/internal/codegen:processor", diff --git a/javatests/dagger/internal/codegen/KotlinInjectedQualifier.kt b/javatests/dagger/internal/codegen/KotlinInjectedQualifier.kt new file mode 100644 index 00000000000..05e27581274 --- /dev/null +++ b/javatests/dagger/internal/codegen/KotlinInjectedQualifier.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 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 + +import javax.inject.Inject +import javax.inject.Named + +class KotlinInjectedQualifier { + @Inject + @Named("TheString") + lateinit var qualifiedString: String +} diff --git a/javatests/dagger/internal/codegen/MembersInjectionTest.java b/javatests/dagger/internal/codegen/MembersInjectionTest.java index 7d3cd5ff013..a30f1af9286 100644 --- a/javatests/dagger/internal/codegen/MembersInjectionTest.java +++ b/javatests/dagger/internal/codegen/MembersInjectionTest.java @@ -184,44 +184,47 @@ public void parentClass_injectedMembersInSupertype() { "", " @Inject void register(B b) {}", "}"); - JavaFileObject expected = JavaFileObjects.forSourceLines( - "test.GenericClass_MembersInjector", - "package test;", - "", - "import dagger.MembersInjector;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", - "", - GENERATED_CODE_ANNOTATIONS, - "public final class GenericClass_MembersInjector", - " implements MembersInjector> {", - " private final Provider aProvider;", - " private final Provider bProvider;", - "", - " public GenericClass_MembersInjector(Provider aProvider, Provider bProvider) {", - " this.aProvider = aProvider;", - " this.bProvider = bProvider;", - " }", - "", - " public static MembersInjector> create(", - " Provider aProvider, Provider bProvider) {", - " return new GenericClass_MembersInjector(aProvider, bProvider);", - " }", - "", - " @Override", - " public void injectMembers(GenericClass instance) {", - " injectA(instance, aProvider.get());", - " injectRegister(instance, bProvider.get());", - " }", - "", - " public static void injectA(Object instance, A a) {", - " ((GenericClass) instance).a = a;", - " }", - "", - " public static void injectRegister(Object instance, B b) {", - " ((GenericClass) instance).register(b);", - " }", - "}"); + JavaFileObject expected = + JavaFileObjects.forSourceLines( + "test.GenericClass_MembersInjector", + "package test;", + "", + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + IMPORT_GENERATED_ANNOTATION, + "import javax.inject.Provider;", + "", + GENERATED_CODE_ANNOTATIONS, + "public final class GenericClass_MembersInjector", + " implements MembersInjector> {", + " private final Provider aProvider;", + " private final Provider bProvider;", + "", + " public GenericClass_MembersInjector(Provider aProvider, Provider bProvider) {", + " this.aProvider = aProvider;", + " this.bProvider = bProvider;", + " }", + "", + " public static MembersInjector> create(", + " Provider aProvider, Provider bProvider) {", + " return new GenericClass_MembersInjector(aProvider, bProvider);", + " }", + "", + " @Override", + " public void injectMembers(GenericClass instance) {", + " injectA(instance, aProvider.get());", + " injectRegister(instance, bProvider.get());", + " }", + "", + " @InjectedFieldSignature(\"test.GenericClass.a\")", + " public static void injectA(Object instance, A a) {", + " ((GenericClass) instance).a = a;", + " }", + "", + " public static void injectRegister(Object instance, B b) {", + " ((GenericClass) instance).register(b);", + " }", + "}"); assertAbout(javaSource()) .that(file) .withCompilerOptions(compilerMode.javacopts()) @@ -277,6 +280,7 @@ public void parentClass_injectedMembersInSupertype() { "package test;", "", "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", IMPORT_GENERATED_ANNOTATION, "import javax.inject.Provider;", "", @@ -321,10 +325,12 @@ public void parentClass_injectedMembersInSupertype() { " injectT(instance, tProvider.get());", " }", "", + " @InjectedFieldSignature(\"test.Child.a\")", " public static void injectA(Object instance, Object a) {", " ((Child) instance).a = (A) a;", " }", "", + " @InjectedFieldSignature(\"test.Child.t\")", " public static void injectT(Object instance, T t) {", " ((Child) instance).t = t;", " }", @@ -359,6 +365,7 @@ public void parentClass_injectedMembersInSupertype() { "import dagger.Lazy;", "import dagger.MembersInjector;", "import dagger.internal.DoubleCheck;", + "import dagger.internal.InjectedFieldSignature;", IMPORT_GENERATED_ANNOTATION, "import javax.inject.Provider;", "", @@ -391,14 +398,17 @@ public void parentClass_injectedMembersInSupertype() { " injectStringProvider(instance, stringProvider3);", " }", "", + " @InjectedFieldSignature(\"test.FieldInjection.string\")", " public static void injectString(Object instance, String string) {", " ((FieldInjection) instance).string = string;", " }", "", + " @InjectedFieldSignature(\"test.FieldInjection.lazyString\")", " public static void injectLazyString(Object instance, Lazy lazyString) {", " ((FieldInjection) instance).lazyString = lazyString;", " }", "", + " @InjectedFieldSignature(\"test.FieldInjection.stringProvider\")", " public static void injectStringProvider(", " Object instance, Provider stringProvider) {", " ((FieldInjection) instance).stringProvider = stringProvider;", @@ -413,6 +423,77 @@ public void parentClass_injectedMembersInSupertype() { .generatesSources(expected); } + @Test + public void fieldInjectionWithQualifier() { + JavaFileObject file = + JavaFileObjects.forSourceLines( + "test.FieldInjectionWithQualifier", + "package test;", + "", + "import dagger.Lazy;", + "import javax.inject.Inject;", + "import javax.inject.Named;", + "import javax.inject.Provider;", + "", + "class FieldInjectionWithQualifier {", + " @Inject @Named(\"A\") String a;", + " @Inject @Named(\"B\") String b;", + "}"); + JavaFileObject expected = + JavaFileObjects.forSourceLines( + "test.FieldInjectionWithQualifier_MembersInjector", + "package test;", + "", + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + IMPORT_GENERATED_ANNOTATION, + "import javax.inject.Named;", + "import javax.inject.Provider;", + "", + GENERATED_CODE_ANNOTATIONS, + "public final class FieldInjectionWithQualifier_MembersInjector", + " implements MembersInjector {", + " private final Provider aProvider;", + " private final Provider bProvider;", + "", + " public FieldInjectionWithQualifier_MembersInjector(Provider aProvider,", + " Provider bProvider) {", + " this.aProvider = aProvider;", + " this.bProvider = bProvider;", + " }", + "", + " public static MembersInjector create(", + " Provider aProvider, Provider bProvider) {", + " return new FieldInjectionWithQualifier_MembersInjector(aProvider, bProvider);", + " }", + "", + "@Override", + " public void injectMembers(FieldInjectionWithQualifier instance) {", + " injectA(instance, aProvider.get());", + " injectB(instance, bProvider.get());", + "}", + "", + " @InjectedFieldSignature(\"test.FieldInjectionWithQualifier.a\")", + " @Named(\"A\")", + " public static void injectA(Object instance, String a) {", + " ((FieldInjectionWithQualifier) instance).a = a;", + " }", + "", + " @InjectedFieldSignature(\"test.FieldInjectionWithQualifier.b\")", + " @Named(\"B\")", + " public static void injectB(Object instance, String b) {", + " ((FieldInjectionWithQualifier) instance).b = b;", + " }", + "}"); + assertAbout(javaSource()) + .that(file) + .withCompilerOptions(compilerMode.javacopts()) + .processedWith(new ComponentProcessor()) + .compilesWithoutError() + .and() + .generatesSources(expected); + } + @Test public void methodInjection() { JavaFileObject file = JavaFileObjects.forSourceLines("test.MethodInjection", "package test;", @@ -523,6 +604,7 @@ public void mixedMemberInjection() { "package test;", "", "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", IMPORT_GENERATED_ANNOTATION, "import javax.inject.Provider;", "", @@ -561,10 +643,12 @@ public void mixedMemberInjection() { " injectSetObject(instance, oProvider.get());", " }", "", + " @InjectedFieldSignature(\"test.MixedMemberInjection.string\")", " public static void injectString(Object instance, String string) {", " ((MixedMemberInjection) instance).string = string;", " }", "", + " @InjectedFieldSignature(\"test.MixedMemberInjection.object\")", " public static void injectObject(Object instance, Object object) {", " ((MixedMemberInjection) instance).object = object;", " }", @@ -603,6 +687,7 @@ public void mixedMemberInjection() { "package test;", "", "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", IMPORT_GENERATED_ANNOTATION, "import javax.inject.Provider;", "", @@ -631,6 +716,7 @@ public void mixedMemberInjection() { // TODO(b/64477506): now that these all take "object", it would be nice to rename // "instance" // to the type name + " @InjectedFieldSignature(\"test.AllInjections.s\")", " public static void injectS(Object instance, String s) {", " ((AllInjections) instance).s = s;", " }", @@ -662,35 +748,38 @@ public void mixedMemberInjection() { "class B extends A {", " @Inject String s;", "}"); - JavaFileObject expectedMembersInjector = JavaFileObjects.forSourceLines( - "test.AllInjections_MembersInjector", - "package test;", - "", - "import dagger.MembersInjector;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", - "", - GENERATED_CODE_ANNOTATIONS, - "public final class B_MembersInjector implements MembersInjector {", - " private final Provider sProvider;", - "", - " public B_MembersInjector(Provider sProvider) {", - " this.sProvider = sProvider;", - " }", - "", - " public static MembersInjector create(Provider sProvider) {", - " return new B_MembersInjector(sProvider);", - " }", - "", - " @Override", - " public void injectMembers(B instance) {", - " injectS(instance, sProvider.get());", - " }", - "", - " public static void injectS(Object instance, String s) {", - " ((B) instance).s = s;", - " }", - "}"); + JavaFileObject expectedMembersInjector = + JavaFileObjects.forSourceLines( + "test.AllInjections_MembersInjector", + "package test;", + "", + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + IMPORT_GENERATED_ANNOTATION, + "import javax.inject.Provider;", + "", + GENERATED_CODE_ANNOTATIONS, + "public final class B_MembersInjector implements MembersInjector {", + " private final Provider sProvider;", + "", + " public B_MembersInjector(Provider sProvider) {", + " this.sProvider = sProvider;", + " }", + "", + " public static MembersInjector create(Provider sProvider) {", + " return new B_MembersInjector(sProvider);", + " }", + "", + " @Override", + " public void injectMembers(B instance) {", + " injectS(instance, sProvider.get());", + " }", + "", + " @InjectedFieldSignature(\"test.B.s\")", + " public static void injectS(Object instance, String s) {", + " ((B) instance).s = s;", + " }", + "}"); assertAbout(javaSources()) .that(ImmutableList.of(aFile, bFile)) .withCompilerOptions(compilerMode.javacopts()) @@ -721,36 +810,40 @@ public void simpleComponentWithNesting() { " void inject(B b);", " }", "}"); - JavaFileObject bMembersInjector = JavaFileObjects.forSourceLines( - "test.OuterType_B_MembersInjector", - "package test;", - "", - "import dagger.MembersInjector;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", - "", - GENERATED_CODE_ANNOTATIONS, - "public final class OuterType_B_MembersInjector", - " implements MembersInjector {", - " private final Provider aProvider;", - "", - " public OuterType_B_MembersInjector(Provider aProvider) {", - " this.aProvider = aProvider;", - " }", - "", - " public static MembersInjector create(Provider aProvider) {", - " return new OuterType_B_MembersInjector(aProvider);", - " }", - "", - " @Override", - " public void injectMembers(OuterType.B instance) {", - " injectA(instance, aProvider.get());", - " }", - "", - " public static void injectA(Object instance, Object a) {", - " ((OuterType.B) instance).a = (OuterType.A) a;", - " }", - "}"); + JavaFileObject bMembersInjector = + JavaFileObjects.forSourceLines( + "test.OuterType_B_MembersInjector", + "package test;", + "", + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + IMPORT_GENERATED_ANNOTATION, + "import javax.inject.Provider;", + "", + GENERATED_CODE_ANNOTATIONS, + "public final class OuterType_B_MembersInjector", + " implements MembersInjector {", + " private final Provider aProvider;", + "", + " public OuterType_B_MembersInjector(Provider aProvider) {", + " this.aProvider = aProvider;", + " }", + "", + " public static MembersInjector create(", + " Provider aProvider) {", + " return new OuterType_B_MembersInjector(aProvider);", + " }", + "", + " @Override", + " public void injectMembers(OuterType.B instance) {", + " injectA(instance, aProvider.get());", + " }", + "", + " @InjectedFieldSignature(\"test.OuterType.B.a\")", + " public static void injectA(Object instance, Object a) {", + " ((OuterType.B) instance).a = (OuterType.A) a;", + " }", + "}"); assertAbout(javaSources()) .that(ImmutableList.of(nestedTypesFile)) .withCompilerOptions(compilerMode.javacopts()) @@ -789,6 +882,7 @@ public void componentWithNestingAndGeneratedType() { "package test;", "", "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", IMPORT_GENERATED_ANNOTATION, "import javax.inject.Provider;", "", @@ -811,6 +905,7 @@ public void componentWithNestingAndGeneratedType() { " injectA(instance, aProvider.get());", " }", "", + " @InjectedFieldSignature(\"test.OuterType.B.a\")", " public static void injectA(Object instance, Object a) {", " ((OuterType.B) instance).a = (OuterType.A) a;", " }", @@ -964,6 +1059,7 @@ public void fieldInjectionForShadowedMember() { "package test;", "", "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", IMPORT_GENERATED_ANNOTATION, "import javax.inject.Provider;", "", @@ -989,6 +1085,7 @@ public void fieldInjectionForShadowedMember() { " injectObject(instance, objectProvider2.get());", " }", "", + " @InjectedFieldSignature(\"test.Child.object\")", " public static void injectObject(Object instance, Object object) {", " ((Child) instance).object = (Bar) object;", " }", @@ -1139,6 +1236,7 @@ public void injectsPrimitive() { "package test;", "", "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", IMPORT_GENERATED_ANNOTATION, "import javax.inject.Provider;", "", @@ -1164,10 +1262,12 @@ public void injectsPrimitive() { " injectBoxedInt(instance, boxedIntProvider.get());", " }", "", + " @InjectedFieldSignature(\"test.InjectedType.primitiveInt\")", " public static void injectPrimitiveInt(Object instance, int primitiveInt) {", " ((InjectedType) instance).primitiveInt = primitiveInt;", " }", "", + " @InjectedFieldSignature(\"test.InjectedType.boxedInt\")", " public static void injectBoxedInt(Object instance, Integer boxedInt) {", " ((InjectedType) instance).boxedInt = boxedInt;", " }", @@ -1282,6 +1382,7 @@ public void accessibility() { "package other;", "", "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", IMPORT_GENERATED_ANNOTATION, "import javax.inject.Provider;", "", @@ -1307,6 +1408,7 @@ public void accessibility() { " injectMethod(instance, fooProvider2.get());", " }", "", + " @InjectedFieldSignature(\"other.Inaccessible.foo\")", " public static void injectFoo(Object instance, Object foo) {", " ((Inaccessible) instance).foo = (Foo) foo;", " }", diff --git a/javatests/dagger/internal/codegen/MembersInjectionValidationTest.java b/javatests/dagger/internal/codegen/MembersInjectionValidationTest.java index 68d5daaa737..939fee706a3 100644 --- a/javatests/dagger/internal/codegen/MembersInjectionValidationTest.java +++ b/javatests/dagger/internal/codegen/MembersInjectionValidationTest.java @@ -236,4 +236,39 @@ public void staticFieldInjection() { assertThat(compilation).failed(); assertThat(compilation).hadErrorContaining("static fields").inFile(injected).onLine(6); } + + @Test + public void missingMembersInjectorForKotlinProperty() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "import dagger.internal.codegen.KotlinInjectedQualifier;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " void inject(KotlinInjectedQualifier injected);", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import javax.inject.Named;", + "", + "@Module", + "class TestModule {", + " @Provides", + " @Named(\"TheString\")", + " String theString() { return \"\"; }", + "}"); + Compilation compilation = daggerCompiler().compile(component, module); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Unable to read annotations on an injected Kotlin property."); + } } diff --git a/javatests/dagger/internal/codegen/MethodSignatureFormatterTest.java b/javatests/dagger/internal/codegen/MethodSignatureFormatterTest.java index 379b7e385b5..6d2f98988c9 100644 --- a/javatests/dagger/internal/codegen/MethodSignatureFormatterTest.java +++ b/javatests/dagger/internal/codegen/MethodSignatureFormatterTest.java @@ -22,7 +22,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.testing.compile.CompilationRule; +import dagger.BindsInstance; import dagger.Component; +import dagger.Module; +import dagger.Provides; import dagger.internal.codegen.MethodSignatureFormatterTest.OuterClass.InnerClass; import dagger.internal.codegen.binding.InjectionAnnotations; import dagger.internal.codegen.binding.MethodSignatureFormatter; @@ -42,6 +45,8 @@ public class MethodSignatureFormatterTest { @Rule public CompilationRule compilationRule = new CompilationRule(); + @Inject DaggerElements elements; + @Inject DaggerTypes types; @Inject InjectionAnnotations injectionAnnotations; static class OuterClass { @@ -62,13 +67,10 @@ String foo( @Before public void setUp() { - DaggerMethodSignatureFormatterTest_TestComponent.create().inject(this); + DaggerMethodSignatureFormatterTest_TestComponent.factory().create(compilationRule).inject(this); } @Test public void methodSignatureTest() { - DaggerElements elements = - new DaggerElements(compilationRule.getElements(), compilationRule.getTypes()); - DaggerTypes types = new DaggerTypes(compilationRule.getTypes(), elements); TypeElement inner = elements.getTypeElement(InnerClass.class); ExecutableElement method = Iterables.getOnlyElement(methodsIn(inner.getEnclosedElements())); String formatted = new MethodSignatureFormatter(types, injectionAnnotations).format(method); @@ -85,8 +87,26 @@ public void setUp() { } @Singleton - @Component + @Component(modules = TestModule.class) interface TestComponent { void inject(MethodSignatureFormatterTest test); + + @Component.Factory + interface Factory { + TestComponent create(@BindsInstance CompilationRule compilationRule); + } + } + + @Module + static class TestModule { + @Provides + static DaggerElements elements(CompilationRule compilationRule) { + return new DaggerElements(compilationRule.getElements(), compilationRule.getTypes()); + } + + @Provides + static DaggerTypes types(CompilationRule compilationRule, DaggerElements elements) { + return new DaggerTypes(compilationRule.getTypes(), elements); + } } }