Skip to content

Commit

Permalink
Propagate qualifiers for field injections in MemberInjectors.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
danysantiago authored and cgdecker committed Dec 6, 2019
1 parent 814d2da commit c96fc3e
Show file tree
Hide file tree
Showing 26 changed files with 1,049 additions and 148 deletions.
21 changes: 21 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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()
32 changes: 32 additions & 0 deletions java/dagger/internal/InjectedFieldSignature.java
Original file line number Diff line number Diff line change
@@ -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();
}
27 changes: 27 additions & 0 deletions java/dagger/internal/codegen/base/MoreAnnotationValues.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {}
}
68 changes: 61 additions & 7 deletions java/dagger/internal/codegen/binding/InjectionAnnotations.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand All @@ -65,11 +77,10 @@ public Optional<AnnotationMirror> getQualifier(Element e) {
public ImmutableSet<? extends AnnotationMirror> getQualifiers(Element element) {
ImmutableSet<? extends AnnotationMirror> 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;
Expand All @@ -82,4 +93,47 @@ public static ImmutableSet<ExecutableElement> 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<? extends AnnotationMirror> 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);
}
}
}
7 changes: 7 additions & 0 deletions java/dagger/internal/codegen/binding/SourceFiles.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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());
}
Expand Down
3 changes: 3 additions & 0 deletions java/dagger/internal/codegen/javapoet/TypeNames.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit c96fc3e

Please sign in to comment.